Search code examples
frama-c

How do I declare a logic variable?


I have implemented the example code in chapter 4.17.7 in the Developer Manual.

The example is a copy visitor that adds an assertion for each division in the program, stating that the divisor is not zero.

The code is as follows:

    open Cil_types
    open Cil
    module M = Plugin.Register

    let syntax_alarm =
      Emitter.create "Syntactic check" [ Emitter.Code_annot ] ~correctness:[]
        ~tuning:[]

    class non_zero_divisor prj =
      object (self)
        inherit Visitor.generic_frama_c_visitor (Visitor_behavior.copy prj)

        method! vexpr e =
          match e.enode with
          | BinOp ((Div | Mod), _, denom, _) ->
              let logic_denom = Logic_utils.expr_to_term ~coerce:false denom in
              let assertion = Logic_const.prel (Rneq, logic_denom, Cil.lzero ()) in
              let stmt =
                match self#current_kinstr with
                | Kglobal -> assert false
                | Kstmt s -> s
              in
              let kf = Option.get self#current_kf in
              let new_stmt = Visitor_behavior.Get.stmt self#behavior stmt in
              Options.Self.result "NewStmt: %a" Printer.pp_stmt new_stmt;
              let new_kf = Visitor_behavior.Get.kernel_function self#behavior kf in
              Queue.add
                (fun () ->
                  Annotations.add_assert syntax_alarm ~kf:new_kf new_stmt assertion)
                self#get_filling_actions;
              DoChildren
          | _ -> DoChildren
      end

    let execute () =
      ignore
        (File.create_project_from_visitor "syntactic check" (new non_zero_divisor))

When I apply the plugin on a C source code that contains a division operation I get the expected annotation in the output.

The problem is however, when I write a test for this plugin the test fails with an error stating that the logic variable in the generated annotation is not declared.

The test code that I am using is:

/* run.config
OPT: -autoload-plugins -sandbox
*/

void main() {
    int a, b, c;
    a = 4;
    b = 2;
    c = a/b;
}

and the output that I get when I run the test is:

...
[kernel] tests/s1/test.c:9: Failure: 
  [AST Integrity Check]
  AST of syntactic check
  logic variable b (25) is not declared
[kernel] Current source was: tests/s1/test.c:5
  The full backtrace is:
  Raised at Project.on in file "src/libraries/project/project.ml", line 405, characters 59-66
  Called from File.init_project_from_visitor in file "src/kernel_services/ast_queries/file.ml", line 1818, characters 4-64
  Called from File.create_project_from_visitor in file "src/kernel_services/ast_queries/file.ml", line 1842, characters 2-43
  Called from Sandbox_visitor.execute in file "sandbox_visitor.ml", line 37, characters 4-79
  Called from Stdlib__Queue.iter.iter in file "queue.ml", line 121, characters 6-15
  Called from Boot.play_analysis in file "src/kernel_internals/runtime/boot.ml", line 36, characters 4-20
  Called from Cmdline.play_in_toplevel_one_shot in file "src/kernel_services/cmdline_parameters/cmdline.ml", line 846, characters 2-9
  Called from Cmdline.play_in_toplevel in file "src/kernel_services/cmdline_parameters/cmdline.ml", line 876, characters 18-64
  Called from Cmdline.catch_toplevel_run in file "src/kernel_services/cmdline_parameters/cmdline.ml", line 235, characters 4-8
  
  Frama-C aborted: internal error.
  Please report as 'crash' at https://git.frama-c.com/pub/frama-c/issues
  Your Frama-C version is 24.0 (Chromium).
  Note that a version and a backtrace alone often do not contain enough
  information to understand the bug. Guidelines for reporting bugs are at:
  https://git.frama-c.com/pub/frama-c/-/wikis/Guidelines-for-reporting-bugs

I wonder why am I getting this error and how to solve this problem. Thanks!


Solution

  • Indeed, there is an issue in the example of the manual you are referring to. It manifests itself in your setting because when using ptests, the option -check gets automatically added to frama-c's command line in order to ensure that incoherence in the AST gets discovered sooner than later.

    The bulk of the issue is that, while the visitor takes care of registering the annotation with the new statement of the new kernel function in the new project, it does not attempt to ensure that the logic_denom term refers to variables in the new project (and as a matter of fact it doesn't). Basically, we have to copy denom and update use of varinfo in order to use their copy in the new project, i.e. adding

    let denom = Visitor.visitFramacExpr self#frama_c_plain_copy denom in
    

    before translating denom into logic_denom in the code above. frama_c_plain_copy is a special method of the visitor that is a simple copy visitor (it copies each node it encounters) which shares its correspondance tables with self, so that denom will indeed be a copy of the original expression with references to varinfo in the new project.

    For what it's worth, here is a standalone script which solves the issue:

    open Cil_types
    open Cil
    
    let syntax_alarm =
      Emitter.create "Syntactic check" [ Emitter.Code_annot ] ~correctness:[]
        ~tuning:[]
    
    class non_zero_divisor prj =
      object (self)
        inherit Visitor.generic_frama_c_visitor (Visitor_behavior.copy prj)
    
        method! vexpr e =
          match e.enode with
          | BinOp ((Div | Mod), _, denom, _) ->
              let denom = Visitor.visitFramacExpr self#frama_c_plain_copy denom in
              let logic_denom = Logic_utils.expr_to_term ~coerce:false denom in
              let assertion = Logic_const.prel (Rneq, logic_denom, Cil.lzero ()) in
              let stmt =
                match self#current_kinstr with
                | Kglobal -> assert false
                | Kstmt s -> s
              in
              let kf = Option.get self#current_kf in
              let new_stmt = Visitor_behavior.Get.stmt self#behavior stmt in
              Kernel.result "NewStmt: %a" Printer.pp_stmt new_stmt;
              let new_kf = Visitor_behavior.Get.kernel_function self#behavior kf in
              Queue.add
                (fun () ->
                  Annotations.add_assert syntax_alarm ~kf:new_kf new_stmt assertion)
                self#get_filling_actions;
              DoChildren
          | _ -> DoChildren
      end
    
    let execute () =
      ignore
        (File.create_project_from_visitor "syntactic check" (new non_zero_divisor))
    
    let () = Db.Main.extend execute
    

    if it is in script_example.ml, and test.c is your example C file, then frama-c -check -load-script script_example.ml test.c -then-last -print gives the following output (tested with Frama-C 25.0):

    [kernel] Parsing test.c (with preprocessing)
    [kernel] NewStmt: c = a / b;
    [kernel] NewStmt: /*@ assert b ≢ 0; */
               c = a / b;
    /* Generated by Frama-C */
    void main(void)
    {
      int a;
      int b;
      int c;
      a = 4;
      b = 2;
      /*@ assert b ≢ 0; */
      c = a / b;
      return;
    }