From 41b2699de769d1cae87474ed80c76abce54dfed3 Mon Sep 17 00:00:00 2001
From: Virgile Prevosto <virgile.prevosto@m4x.org>
Date: Tue, 23 Jun 2020 18:07:49 +0200
Subject: [PATCH] [logic] do not create empty assigns in code annotations or
 statement contracts

---
 src/kernel_services/ast_data/annotations.ml   | 315 +++++++++---------
 .../ast_queries/logic_utils.ml                |  12 +
 .../ast_queries/logic_utils.mli               |  12 +
 3 files changed, 185 insertions(+), 154 deletions(-)

diff --git a/src/kernel_services/ast_data/annotations.ml b/src/kernel_services/ast_data/annotations.ml
index 998f3395e90..c2e7e615baf 100644
--- a/src/kernel_services/ast_data/annotations.ml
+++ b/src/kernel_services/ast_data/annotations.ml
@@ -1094,162 +1094,169 @@ let add_code_annot ?(keep_empty=true) emitter ?kf stmt ca =
       Stmt.pretty stmt
       stmt.sid;*)
   let kf = find_englobing_kf ?kf stmt in
-  let convert a =
-    match a.annot_content with
-    | AAssert(l, kind, p) ->
-      let a = { a with annot_content=AAssert(l,kind,extend_name emitter p) } in
-      a, Property.ip_of_code_annot kf stmt a
-    | AInvariant(l, b, p) ->
-      let a={a with annot_content=AInvariant(l,b,extend_name emitter p)} in
-      a, Property.ip_of_code_annot kf stmt a
-    | AStmtSpec (bhvs, spec) ->
-      let filter ca =
-        match ca.annot_content with
-        | AStmtSpec(bhvs',_) -> is_same_behavior_set bhvs bhvs'
-        | _ -> false
-      in
-      let contract = code_annot ~filter stmt in
-      (match contract with
-       | [] -> a, Property.ip_of_code_annot kf stmt a
-       | [ { annot_content = AStmtSpec _ } ] ->
-         let register_children = true in
-         let active = bhvs in
-         add_behaviors
-           ~register_children emitter kf ~stmt ~active spec.spec_behavior;
-         if spec.spec_variant <> None then
-           Kernel.fatal
-             "statement contract cannot have a decrease clause";
-         Extlib.may
-           (add_terminates emitter kf ~stmt ~active) spec.spec_terminates;
-         List.iter
-           (add_complete emitter kf ~stmt ~active)
-           spec.spec_complete_behaviors;
-         List.iter
-           (add_disjoint emitter kf ~stmt ~active)
-           spec.spec_disjoint_behaviors;
-         (* By construction, we have exactly one contract
-            corresponding to our criterion and emitter. *)
-         let ca = List.hd (code_annot ~emitter ~filter stmt) in
-         (* remove the previous binding in order to replace it
-            with the updated one. Statuses being attached to sub-elements
-            of the contract, they do not need any update here.
-          *)
-         remove_code_annot_internal emitter ~remove_statuses:false ~kf stmt ca;
-         { a with annot_content = ca.annot_content }, []
-       | _ ->
-         Kernel.fatal
-           "more than one contract attached to a given statement for \
-            emitter %a. Invariant of annotations management broken."
-           Emitter.pretty emitter)
-    | AVariant _ ->
-      let v = code_annot ~filter:Logic_utils.is_variant stmt in
-      (match v with
-       | [] -> a, Property.ip_of_code_annot kf stmt a
-       | _ ->
-         let source = fst (Cil_datatype.Stmt.loc stmt) in
-         Kernel.fatal ~source
-           "trying to register a second variant for statement %a"
-           Stmt.pretty stmt)
-    | AAssigns (bhvs, assigns) ->
-      let filter_ca ca =
-        match ca.annot_content with
-        | AAssigns (bhvs', _) -> is_same_behavior_set bhvs bhvs'
-        | _ -> false
-      in
-      let filter e ca = Emitter.equal e emitter && filter_ca ca in
-      let ca_e = code_annot_emitter ~filter stmt in
-      let ca_total = code_annot ~filter:filter_ca stmt in
-      let old_a =
-        match ca_e with
-        | [] -> WritesAny
-        | [ { annot_content = AAssigns(_, assigns') } as ca,_ ] ->
-          remove_code_annot_internal emitter ~kf stmt ca;
-          assigns'
-        | _ ->
-          Kernel.fatal
-            "More than one loop assigns clause for a statement. \
-             Annotations internal state broken."
-      in
-      let merged = merge_assigns ~keep_empty old_a assigns in
-      let new_a = { a with annot_content = AAssigns (bhvs, merged) } in
-      let ips =
-        match ca_total with
-        | [] -> Property.ip_of_code_annot kf stmt a
-        | [ { annot_content = AAssigns (_,assigns') } ] ->
-          let merged = merge_assigns ~keep_empty assigns' assigns in
-          Property.ip_of_code_annot
-            kf stmt { a with annot_content = AAssigns(bhvs,merged) }
-        | _ ->
-          Kernel.fatal
-            "More than one loop assigns clause for a statement. \
-             Annotations internal state broken."
-      in
-      new_a, ips
-    | AAllocation (bhvs, alloc) ->
-      let filter ca =
-        match ca.annot_content with
-        | AAllocation(bhvs',_) -> is_same_behavior_set bhvs bhvs'
-        | _ -> false
-      in
-      (match code_annot ~filter stmt with
-       | [] -> a, Property.ip_of_code_annot kf stmt a
-       | l ->
-         let merge_alloc_ca acc alloc =
-           match alloc.annot_content with
-           | AAllocation(_,a) -> merge_allocation ~keep_empty acc a
-           | _ -> acc
-         in
-         let alloc' = List.fold_left merge_alloc_ca FreeAllocAny l in
-         let merged_a =
-           { a with annot_content = AAllocation(bhvs,alloc') }
-         in
-         let ip =
-           Property.(
-             ip_of_allocation kf (Kstmt stmt)
-               (Id_loop merged_a) alloc')
-         in
-         Extlib.may Property_status.remove ip;
-         let new_alloc = merge_allocation ~keep_empty alloc' alloc in
-         let new_a =
-           { a with annot_content = AAllocation(bhvs,new_alloc) }
-         in
-         let ip =
-           Property.(
-             ip_of_allocation
-               kf (Kstmt stmt) (Id_loop new_a) new_alloc)
-         in
-         let emit_a =
-           match code_annot ~emitter ~filter stmt with
-           | [] -> a
-           | [ { annot_content = AAllocation(_,alloc') } as ca ] ->
-             remove_code_annot_internal emitter ~kf stmt ca;
-             { a with annot_content =
-                        AAllocation(
-                          bhvs,
-                          merge_allocation ~keep_empty alloc' alloc) }
-           | _ ->
-             Kernel.fatal
-               "More than one allocation clause for a statement. \
-                Annotations internal state broken"
-         in
-         emit_a, Extlib.list_of_opt ip)
-    | APragma _ | AExtended _ -> a, Property.ip_of_code_annot kf stmt a
-  in
-  let ca, ppts = convert ca in
-  let e = Emitter.get emitter in
-  List.iter Property_status.register ppts;
-  let add_emitter tbl = Emitter.Usable_emitter.Hashtbl.add tbl e (ref [ ca ]) in
-  try
-    let tbl = Code_annots.find stmt in
+  let fill_tables ca ppts =
+    let e = Emitter.get emitter in
+    List.iter Property_status.register ppts;
+    let add_emitter tbl =
+      Emitter.Usable_emitter.Hashtbl.add tbl e (ref [ ca ]) in
     try
-      let l = Emitter.Usable_emitter.Hashtbl.find tbl e in
-      l := ca :: !l;
+      let tbl = Code_annots.find stmt in
+      try
+        let l = Emitter.Usable_emitter.Hashtbl.find tbl e in
+        l := ca :: !l;
+      with Not_found ->
+        add_emitter tbl
     with Not_found ->
-      add_emitter tbl
-  with Not_found ->
-    let tbl = Emitter.Usable_emitter.Hashtbl.create 7 in
-    add_emitter tbl;
-    Code_annots.add stmt tbl
+      let tbl = Emitter.Usable_emitter.Hashtbl.create 7 in
+      add_emitter tbl;
+      Code_annots.add stmt tbl
+  in
+  match ca.annot_content with
+  | AAssert(l, kind, p) ->
+    let a = { ca with annot_content=AAssert(l,kind,extend_name emitter p) } in
+    fill_tables a (Property.ip_of_code_annot kf stmt a)
+  | AInvariant(l, b, p) ->
+    let a={ca with annot_content=AInvariant(l,b,extend_name emitter p)} in
+    fill_tables a (Property.ip_of_code_annot kf stmt a)
+  | AStmtSpec (bhvs, spec) ->
+    let filter ca =
+      match ca.annot_content with
+      | AStmtSpec(bhvs',_) -> is_same_behavior_set bhvs bhvs'
+      | _ -> false
+    in
+    let contract = code_annot ~filter stmt in
+    (match contract with
+     | [] ->
+       if not (keep_empty && Logic_utils.funspec_has_only_assigns spec) then
+         fill_tables ca (Property.ip_of_code_annot kf stmt ca)
+     | [ { annot_content = AStmtSpec _ } ] ->
+       let register_children = true in
+       let active = bhvs in
+       add_behaviors
+         ~register_children emitter kf ~stmt ~active spec.spec_behavior;
+       if spec.spec_variant <> None then
+         Kernel.fatal
+           "statement contract cannot have a decrease clause";
+       Extlib.may
+         (add_terminates emitter kf ~stmt ~active) spec.spec_terminates;
+       List.iter
+         (add_complete emitter kf ~stmt ~active)
+         spec.spec_complete_behaviors;
+       List.iter
+         (add_disjoint emitter kf ~stmt ~active)
+         spec.spec_disjoint_behaviors;
+       (* By construction, we have exactly one contract
+          corresponding to our criterion and emitter. *)
+       let ca' = List.hd (code_annot ~emitter ~filter stmt) in
+       (* remove the previous binding in order to replace it
+          with the updated one. Statuses being attached to sub-elements
+          of the contract, they do not need any update here.
+       *)
+       remove_code_annot_internal emitter ~remove_statuses:false ~kf stmt ca';
+       fill_tables ca []
+     | _ ->
+       Kernel.fatal
+         "more than one contract attached to a given statement for \
+          emitter %a. Invariant of annotations management broken."
+         Emitter.pretty emitter)
+  | AVariant _ ->
+    let v = code_annot ~filter:Logic_utils.is_variant stmt in
+    (match v with
+     | [] -> fill_tables ca (Property.ip_of_code_annot kf stmt ca)
+     | _ ->
+       let source = fst (Cil_datatype.Stmt.loc stmt) in
+       Kernel.fatal ~source
+         "trying to register a second variant for statement %a"
+         Stmt.pretty stmt)
+  | AAssigns (bhvs, assigns) ->
+    let filter_ca ca =
+      match ca.annot_content with
+      | AAssigns (bhvs', _) -> is_same_behavior_set bhvs bhvs'
+      | _ -> false
+    in
+    let filter e ca = Emitter.equal e emitter && filter_ca ca in
+    let ca_e = code_annot_emitter ~filter stmt in
+    let ca_total = code_annot ~filter:filter_ca stmt in
+    (match ca_total with
+     | [] when keep_empty -> ()
+     | [] -> fill_tables ca (Property.ip_of_code_annot kf stmt ca)
+     | [{ annot_content = AAssigns(_,assigns_total)}] ->
+       let assigns_e =
+         match ca_e with
+         | [] -> WritesAny
+         | [ { annot_content = AAssigns(_, assigns') } as ca,_ ] ->
+           remove_code_annot_internal emitter ~kf stmt ca;
+           assigns'
+         | _ ->
+           Kernel.fatal
+             "More than one loop assigns clause for a statement. \
+              Annotations internal state broken."
+       in
+       (* we have assigns at statement level, just not from this
+          emitter yet, hence merge at emitter level regardless of keep_empty *)
+       let merged_e = merge_assigns ~keep_empty:false assigns_e assigns in
+       let new_a = { ca with annot_content = AAssigns(bhvs,merged_e) } in
+       let merged_total = merge_assigns ~keep_empty assigns_total assigns in
+       let ips =
+         Property.ip_of_code_annot kf stmt
+           { ca with annot_content = AAssigns(bhvs,merged_total) }
+       in
+       fill_tables new_a ips
+     | _ ->
+       Kernel.fatal
+         "More than one loop assigns clause for a statement. \
+          Annotations internal state broken.")
+  | AAllocation (bhvs, alloc) ->
+    let filter ca =
+      match ca.annot_content with
+      | AAllocation(bhvs',_) -> is_same_behavior_set bhvs bhvs'
+      | _ -> false
+    in
+    (match code_annot ~filter stmt with
+     | [] when keep_empty -> ()
+     | [] -> fill_tables ca (Property.ip_of_code_annot kf stmt ca)
+     | l ->
+       let merge_alloc_ca acc alloc =
+         match alloc.annot_content with
+         | AAllocation(_,a) -> merge_allocation ~keep_empty acc a
+         | _ -> acc
+       in
+       let alloc' = List.fold_left merge_alloc_ca FreeAllocAny l in
+       let merged_a =
+         { ca with annot_content = AAllocation(bhvs,alloc') }
+       in
+       let ip =
+         Property.(
+           ip_of_allocation kf (Kstmt stmt)
+             (Id_loop merged_a) alloc')
+       in
+       Extlib.may Property_status.remove ip;
+       let new_alloc = merge_allocation ~keep_empty alloc' alloc in
+       let new_a =
+         { ca with annot_content = AAllocation(bhvs,new_alloc) }
+       in
+       let ip =
+         Property.(
+           ip_of_allocation
+             kf (Kstmt stmt) (Id_loop new_a) new_alloc)
+       in
+       let emit_a =
+         match code_annot ~emitter ~filter stmt with
+         | [] -> ca
+         | [ { annot_content = AAllocation(_,alloc') } as ca ] ->
+           remove_code_annot_internal emitter ~kf stmt ca;
+           { ca with annot_content =
+                      AAllocation(
+                        bhvs,
+                        merge_allocation ~keep_empty alloc' alloc) }
+         | _ ->
+           Kernel.fatal
+             "More than one allocation clause for a statement. \
+              Annotations internal state broken"
+       in
+       fill_tables emit_a (Extlib.list_of_opt ip))
+  | APragma _ | AExtended _ ->
+    fill_tables ca (Property.ip_of_code_annot kf stmt ca)
 
 let add_assert e ?kf stmt a =
   let a = Logic_const.new_code_annotation (AAssert ([],Assert,a)) in
diff --git a/src/kernel_services/ast_queries/logic_utils.ml b/src/kernel_services/ast_queries/logic_utils.ml
index 242feb6c6a0..bf0b1e828db 100644
--- a/src/kernel_services/ast_queries/logic_utils.ml
+++ b/src/kernel_services/ast_queries/logic_utils.ml
@@ -676,6 +676,18 @@ let rec add_attribute_glob_annot a g =
   | Dcustom_annot(c,n,al,l) -> Dcustom_annot(c,n,Cil.addAttribute a al, l)
   | Dextended (e,al,l) -> Dextended(e,Cil.addAttribute a al,l)
 
+let behavior_has_only_assigns bhvs =
+  bhvs.b_requires = [] && bhvs.b_assumes = [] &&
+  bhvs.b_post_cond = [] && bhvs.b_allocation = FreeAllocAny &&
+  bhvs.b_extended = []
+
+let funspec_has_only_assigns spec =
+  List.for_all behavior_has_only_assigns spec.spec_behavior &&
+  spec.spec_variant = None &&
+  spec.spec_terminates = None &&
+  spec.spec_complete_behaviors = [] &&
+  spec.spec_disjoint_behaviors = []
+
 let is_same_list f l1 l2 =
   try List.for_all2 f l1 l2 with Invalid_argument _ -> false
 
diff --git a/src/kernel_services/ast_queries/logic_utils.mli b/src/kernel_services/ast_queries/logic_utils.mli
index 1394e8408fb..b1e4074364f 100644
--- a/src/kernel_services/ast_queries/logic_utils.mli
+++ b/src/kernel_services/ast_queries/logic_utils.mli
@@ -312,6 +312,18 @@ val is_annot_next_stmt: code_annotation -> bool
 val add_attribute_glob_annot:
   attribute -> global_annotation -> global_annotation
 
+(** {2 Contracts } *)
+
+(** [true] if the behavior has only an assigns clause.
+    @since Frama-C+dev
+*)
+val behavior_has_only_assigns: behavior -> bool
+
+(** [true] if the only non-empty fields of the contract are assigns clauses
+    @since Frama-C+dev
+*)
+val funspec_has_only_assigns: funspec -> bool
+
 (** {2 Structural equality between annotations} *)
 
 val is_same_list: ('a -> 'a -> bool) -> 'a list -> 'a list -> bool
-- 
GitLab