From d66660ba11a703d8947302e57adbcd4111b58ea3 Mon Sep 17 00:00:00 2001
From: Virgile Prevosto <virgile.prevosto@m4x.org>
Date: Fri, 29 Apr 2022 16:08:48 +0200
Subject: [PATCH] [kernel] AST diff keeps better track of goto

---
 src/kernel_services/ast_queries/ast_diff.ml | 67 ++++++++++++++++-----
 tests/syntax/ast_diff_1.i                   | 14 +++++
 tests/syntax/ast_diff_2.i                   | 14 +++++
 tests/syntax/oracle/ast_diff_1.res.oracle   | 10 ++-
 4 files changed, 89 insertions(+), 16 deletions(-)

diff --git a/src/kernel_services/ast_queries/ast_diff.ml b/src/kernel_services/ast_queries/ast_diff.ml
index 585bebb4e22..343fff1a0b4 100644
--- a/src/kernel_services/ast_queries/ast_diff.ml
+++ b/src/kernel_services/ast_queries/ast_diff.ml
@@ -210,6 +210,11 @@ type is_same_env =
     logic_type_info: logic_type_info Cil_datatype.Logic_type_info.Map.t;
     logic_local_vars: logic_var Cil_datatype.Logic_var.Map.t;
     logic_type_vars: string Datatype.String.Map.t;
+    (* goto targets pairs are checked afterwards, so that forward gotos
+       do not interrupt the linear visit.
+       We thus collect them in the environment.
+    *)
+    goto_targets: (stmt * stmt) list;
   }
 
 module type Correspondance_table = sig
@@ -294,6 +299,7 @@ let empty_env =
     logic_type_info = Cil_datatype.Logic_type_info.Map.empty;
     logic_local_vars = Cil_datatype.Logic_var.Map.empty;
     logic_type_vars = Datatype.String.Map.empty;
+    goto_targets = []
   }
 
 let add_locals f f' env =
@@ -390,6 +396,12 @@ let is_same_opt f o o' env =
   | Some v, Some v' -> f v v' env
   | _ -> false
 
+let is_same_opt_env f o o' env =
+  match o, o' with
+  | None, None -> true, env
+  | Some v, Some v' -> f v v' env
+  | _ -> false, env
+
 let is_same_pair f1 f2 (x1,x2) (y1,y2) env = f1 x1 y1 env && f2 x2 y2 env
 
 let rec is_same_list f l l' env =
@@ -410,6 +422,22 @@ let get_original_kf vi =
   in
   Project.on ~selection (Orig_project.get()) Globals.Functions.get vi
 
+let check_goto_targets env =
+  let check_one (s,s') =
+    match Stmt.find s with
+    | `Not_present -> false
+    | `Same s'' | `Partial (s'',_) ->
+      (* From the goto point of view, what matters is that the targets
+         themselves have a correspondance. If they're e.g. calls to a
+         function that has itself changed, or blocks whose content has
+         changed, it has already been detected when comparing the targets,
+         and will be dealt with accordingly as the fundec level. *)
+      Cil_datatype.Stmt.equal s' s''
+    | exception Not_found -> false
+  in
+  if List.for_all check_one env.goto_targets then `Same_body, env
+  else `Body_changed, env
+
 let is_matching_fieldinfo fi fi' =
   match Fieldinfo.find fi with
   | `Not_present -> false
@@ -1014,12 +1042,19 @@ and is_same_extended_asm a a' env =
   and is_same_in (_,c,e) (_,c',e') env =
     Datatype.String.equal c c' && is_same_exp e e' env
   in
-  is_same_list is_same_out a.asm_outputs a'.asm_outputs env &&
-  is_same_list is_same_in a.asm_inputs a'.asm_inputs env &&
-  is_same_list
-    (fun s1 s2 _ -> Datatype.String.equal s1 s2)
-    a.asm_clobbers a'.asm_clobbers env
-(* we don't check goto targets, as explained below for goto statements. *)
+  let res =
+    is_same_list is_same_out a.asm_outputs a'.asm_outputs env &&
+    is_same_list is_same_in a.asm_inputs a'.asm_inputs env &&
+    is_same_list
+      (fun s1 s2 _ -> Datatype.String.equal s1 s2)
+      a.asm_clobbers a'.asm_clobbers env
+    &&
+    List.length a.asm_gotos = List.length a'.asm_gotos
+  in
+  let bind s s' env =
+    (true, { env with goto_targets = (!s,!s') :: env.goto_targets})
+  in
+  (res, env) &&& is_same_list_env bind a.asm_gotos a'.asm_gotos
 
 and is_same_instr i i' env: body_correspondance*is_same_env =
   match i, i' with
@@ -1051,12 +1086,12 @@ and is_same_instr i i' env: body_correspondance*is_same_env =
       res, env
     end else `Body_changed, env
   | Asm(a,c,e,_), Asm(a',c',e',_) ->
-    if Cil_datatype.Attributes.equal a a' &&
-       is_same_list (fun s1 s2 _ -> Datatype.String.equal s1 s2) c c' env &&
-       is_same_opt is_same_extended_asm e e' env
-    then
-      `Same_body, env
-    else `Body_changed, env
+    let res =
+      Cil_datatype.Attributes.equal a a' &&
+      is_same_list (fun s1 s2 _ -> Datatype.String.equal s1 s2) c c' env
+    in
+    let (res,env) = (res,env) &&& is_same_opt_env is_same_extended_asm e e' in
+    if res then `Same_body, env else `Body_changed, env
   | Skip _, Skip _ -> `Same_body, env
   | Code_annot _, Code_annot _ ->
     (* should not be present in normalized AST *)
@@ -1087,7 +1122,8 @@ and is_same_stmt s s' env =
         | Return (r,_), Return(r', _) ->
           if is_same_opt is_same_exp r r' env then `Same_body, env
           else `Body_changed, env
-        | Goto (_s,_), Goto(_s',_) -> `Same_body, env
+        | Goto (s,_), Goto(s',_) ->
+          `Same_body, { env with goto_targets = (!s,!s') :: env.goto_targets }
         | Break _, Break _ -> `Same_body, env
         | Continue _, Continue _ -> `Same_body, env
         | If(e,b1,b2,_), If(e',b1',b2',_) ->
@@ -1185,7 +1221,10 @@ and is_same_binder b b' env =
    and formals mapping to have been put in the local env
 *)
 and is_same_fundec f f' env: body_correspondance =
-  let res, env = is_same_block f.sbody f'.sbody env in
+  let res, env =
+    is_same_block f.sbody f'.sbody env &&>
+    check_goto_targets
+  in
   (* Since we add the locals only if the body is the same,
      we have explored all nodes, and added all locals bindings.
      Otherwise, is_same_block would have returned `Body_changed.
diff --git a/tests/syntax/ast_diff_1.i b/tests/syntax/ast_diff_1.i
index aada6a5b26a..4f5db3d6867 100644
--- a/tests/syntax/ast_diff_1.i
+++ b/tests/syntax/ast_diff_1.i
@@ -80,3 +80,17 @@ extern struct s s;
 enum e;
 
 struct s* use_s() { enum e* x; return &s; }
+
+void with_goto_changed(int c) {
+  if (c) goto L1;
+  X++;
+  L1: X++;
+  L2: X++;
+}
+
+void with_goto_unchanged(int c) {
+  if (c) goto L1;
+  X++;
+  L1: X++;
+  L2: X++;
+}
diff --git a/tests/syntax/ast_diff_2.i b/tests/syntax/ast_diff_2.i
index c0935612c3d..9f6b60d8dce 100644
--- a/tests/syntax/ast_diff_2.i
+++ b/tests/syntax/ast_diff_2.i
@@ -73,3 +73,17 @@ extern struct s s;
 enum e;
 
 struct s* use_s() { enum e* x; return &s; }
+
+void with_goto_changed(int c) {
+  if (c) goto L2;
+  X++;
+  L1: X++;
+  L2: X++;
+}
+
+void with_goto_unchanged(int c) {
+  if (c) goto L1;
+  X++;
+  L1: X++;
+  L2: X++;
+}
diff --git a/tests/syntax/oracle/ast_diff_1.res.oracle b/tests/syntax/oracle/ast_diff_1.res.oracle
index d0b45c4af63..7bf1eac2f1e 100644
--- a/tests/syntax/oracle/ast_diff_1.res.oracle
+++ b/tests/syntax/oracle/ast_diff_1.res.oracle
@@ -10,13 +10,17 @@
 [AST diff test] Variable s:  => s
 [AST diff test] Variable use_s:  => use_s
 [AST diff test] Variable x:  => x
-[AST diff test] Variable has_static_local_x:  => has_static_local_y
+[AST diff test] Variable with_goto_changed:  => with_goto_changed
 [AST diff test] Variable X:  => X
 [AST diff test] Variable Y: N/A
+[AST diff test] Variable c:  => c
 [AST diff test] Variable f:  => f
-[AST diff test] Variable __retres:  => __retres
+[AST diff test] Variable with_goto_unchanged:  => with_goto_unchanged
 [AST diff test] Variable x:  => x
+[AST diff test] Variable c:  => c
 [AST diff test] Variable g:  => g
+[AST diff test] Variable has_static_local_x:  => has_static_local_y
+[AST diff test] Variable __retres:  => __retres
 [AST diff test] Variable h:  => h
 [AST diff test] Variable use_logic_builtin:  => use_logic_builtin
 [AST diff test] Variable x:  => x
@@ -28,7 +32,9 @@
 [AST diff test] Function i:  => i
 [AST diff test] Function local_var_use:  => local_var_use
 [AST diff test] Function use_s:  => use_s
+[AST diff test] Function with_goto_changed: -> with_goto_changed (body changed)
 [AST diff test] Function f:  => f
+[AST diff test] Function with_goto_unchanged:  => with_goto_unchanged
 [AST diff test] Function g: N/A
 [AST diff test] Function h: -> h (body changed)
 [AST diff test] Function use_logic_builtin:  => use_logic_builtin
-- 
GitLab