From bf621290eb94c88d03e55128a942c15f096f227c Mon Sep 17 00:00:00 2001
From: Virgile Prevosto <virgile.prevosto@m4x.org>
Date: Fri, 7 Jan 2022 11:15:27 +0100
Subject: [PATCH] [kernel] start taking into account possible recursions when
 comparing ASTs

a priori, we start from a well-ordered AST, so that not all symbols are
concerned by a possible recursion, but this nevertheless might be the case
for aggregate and function definitions, as well as ACSL types and
functions/predicates.
---
 src/kernel_services/ast_queries/ast_diff.ml | 197 ++++++++++++--------
 1 file changed, 124 insertions(+), 73 deletions(-)

diff --git a/src/kernel_services/ast_queries/ast_diff.ml b/src/kernel_services/ast_queries/ast_diff.ml
index 1eedd3ec67e..e24d70335dd 100644
--- a/src/kernel_services/ast_queries/ast_diff.ml
+++ b/src/kernel_services/ast_queries/ast_diff.ml
@@ -136,37 +136,68 @@ module Info(I: sig val name: string end) =
     let size = 43
   end)
 
-module Build(D:Datatype.S_with_collections) =
-  State_builder.Hashtbl(D.Hashtbl)(Correspondance.Make(D))
-    (Info(struct let name = "Ast_diff." ^ D.name end))
+(* Map of symbols under analysis, in case of recursion.
+   Note that this can only happen with aggregate types, logic
+   types, and function (both C and ACSL). Other symbols must be
+   correctly ordered in a well-formed AST
+*)
+[@@@ ocaml.warning "-69"] (* WIP: currently not all fields are used *)
+type is_same_env =
+  {
+    compinfo: compinfo Cil_datatype.Compinfo.Map.t;
+    kernel_function: kernel_function Kernel_function.Map.t;
+    logic_info: logic_info Cil_datatype.Logic_info.Map.t;
+    logic_type_info: logic_type_info Cil_datatype.Logic_type_info.Map.t;
+  }
 
-module Varinfo = Build(Cil_datatype.Varinfo)
+module Build(H:Datatype.S_with_collections)(D:Datatype.S) = struct
+  include
+    State_builder.Hashtbl(H.Hashtbl)(D)
+      (Info(struct let name = "Ast_diff." ^ D.name end))
 
-module Compinfo = Build(Cil_datatype.Compinfo)
+  let memo_env mk_val key env =
+    if mem key then find key, env
+    else let (data, _) as res = mk_val key env in
+      add key data; res
+end
 
-module Enuminfo = Build(Cil_datatype.Enuminfo)
+module Build_correspondance(H:Datatype.S_with_collections) =
+  Build(H)(Correspondance.Make(H))
 
-module Enumitem = Build(Cil_datatype.Enumitem)
+module Varinfo = Build_correspondance(Cil_datatype.Varinfo)
 
-module Typeinfo = Build(Cil_datatype.Typeinfo)
+module Compinfo = Build_correspondance(Cil_datatype.Compinfo)
 
-module Stmt = Build(Cil_datatype.Stmt)
+module Enuminfo = Build_correspondance(Cil_datatype.Enuminfo)
 
-module Logic_info = Build(Cil_datatype.Logic_info)
+module Enumitem = Build_correspondance(Cil_datatype.Enumitem)
 
-module Logic_type_info = Build(Cil_datatype.Logic_type_info)
+module Typeinfo = Build_correspondance(Cil_datatype.Typeinfo)
 
-module Fieldinfo = Build(Cil_datatype.Fieldinfo)
+module Stmt = Build_correspondance(Cil_datatype.Stmt)
 
-module Model_info = Build(Cil_datatype.Model_info)
+module Logic_info = Build_correspondance(Cil_datatype.Logic_info)
 
-module Logic_var = Build(Cil_datatype.Logic_var)
+module Logic_type_info = Build_correspondance(Cil_datatype.Logic_type_info)
 
-module Kernel_function =
-  State_builder.Hashtbl(Kernel_function.Hashtbl)(Kf_correspondance)
-    (Info(struct let name = "Ast_diff.Kernel_function" end))
+module Fieldinfo = Build_correspondance(Cil_datatype.Fieldinfo)
 
-module Fundec = Build(Cil_datatype.Fundec)
+module Model_info = Build_correspondance(Cil_datatype.Model_info)
+
+module Logic_var = Build_correspondance(Cil_datatype.Logic_var)
+
+module Kf = Kernel_function
+
+module Kernel_function = Build(Kernel_function)(Kf_correspondance)
+
+module Fundec = Build_correspondance(Cil_datatype.Fundec)
+
+let empty_env =
+  { compinfo = Cil_datatype.Compinfo.Map.empty;
+    kernel_function = Kf.Map.empty;
+    logic_info = Cil_datatype.Logic_info.Map.empty;
+    logic_type_info = Cil_datatype.Logic_type_info.Map.empty;
+  }
 
 (** TODO: use location info to detect potential renaming.
     Requires some information about syntactic diff. *)
@@ -205,96 +236,115 @@ let find_candidate_varinfo ?loc:_loc vi where =
     Some (Globals.Vars.find_from_astinfo vi.vname where)
   with Not_found -> None
 
-let is_same_opt f o o' =
-  match o, o' with
-  | None, None -> true
-  | Some v, Some v' -> f v v'
-  | _ -> false
+let (&&>) (res,env) f = if res then f env else false, env
+let (&&|) env f = f env
+let (&&&) (res,env) b = res && b, env
 
-let rec is_same_type t t' =
+let is_same_opt f o o' env =
+  match o, o' with
+  | None, None -> true, env
+  | Some v, Some v' -> f v v' env
+  | _ -> false, env
+
+let rec is_same_list f l l' env =
+  match l, l' with
+  | [], [] -> true, env
+  | h::t, h'::t' ->
+    env &&| f h h' &&> is_same_list f t t'
+  | _ -> false, env
+
+let rec is_same_type t t' env =
   match t, t' with
   | (TVoid _ | TInt _ | TFloat _ | TBuiltin_va_list _), _ ->
-    Cil_datatype.TypByName.equal t t'
+    Cil_datatype.TypByName.equal t t', env
   | TPtr(t,a), TPtr(t',a') ->
-    is_same_type t t' && Cil_datatype.Attributes.equal a a'
+    env &&| is_same_type t t' &&& Cil_datatype.Attributes.equal a a'
   | TArray(t,s,a), TArray(t',s',a') ->
-    is_same_type t t' && is_same_opt is_same_exp s s'
-    && Cil_datatype.Attributes.equal a a'
+    env &&|
+    is_same_type t t' &&>
+    is_same_opt is_same_exp s s' &&&
+    Cil_datatype.Attributes.equal a a'
   | TFun(rt,l,var,a), TFun(rt', l', var', a') ->
-    is_same_type rt rt' &&
-    is_same_opt (Logic_utils.is_same_list is_same_formal) l l' &&
-    var = var' &&
+    env &&|
+    is_same_type rt rt' &&>
+    is_same_opt (is_same_list is_same_formal) l l' &&&
+    (var = var') &&&
     Cil_datatype.Attributes.equal a a'
   | TNamed(t,a), TNamed(t',a') ->
-    let correspondance = typeinfo_correspondance t in
+    let correspondance, env = typeinfo_correspondance t env in
     (match correspondance with
-     | `Not_present -> false
-     | `Same t'' -> Cil_datatype.Typeinfo.equal t' t'') &&
+     | `Not_present -> false, env
+     | `Same t'' -> Cil_datatype.Typeinfo.equal t' t'',env) &&&
     Cil_datatype.Attributes.equal a a'
   | TComp(c,a), TComp(c', a') ->
-    let correspondance = compinfo_correspondance c in
+    let correspondance, env = compinfo_correspondance c env in
     (match correspondance with
-     | `Not_present -> false
-     | `Same c'' -> Cil_datatype.Compinfo.equal c' c'') &&
+     | `Not_present -> false, env
+     | `Same c'' -> Cil_datatype.Compinfo.equal c' c'', env) &&&
     Cil_datatype.Attributes.equal a a'
   | TEnum(e,a), TEnum(e',a') ->
-    let correspondance = enuminfo_correspondance e in
+    let correspondance, env = enuminfo_correspondance e env in
     (match correspondance with
-     | `Not_present -> false
-     | `Same e'' -> Cil_datatype.Enuminfo.equal e' e'') &&
+     | `Not_present -> false, env
+     | `Same e'' -> Cil_datatype.Enuminfo.equal e' e'', env) &&&
     Cil_datatype.Attributes.equal a a'
-  | (TPtr _ | TArray _ | TFun _ | TNamed _ | TComp _ | TEnum _), _ -> false
+  | (TPtr _ | TArray _ | TFun _ | TNamed _ | TComp _ | TEnum _), _ -> false, env
 
-and is_same_compinfo _ci _ci' = false
+and is_same_compinfo _ci _ci' env = false, env
 
-and is_same_enuminfo _ei _ei' = false
+and is_same_enuminfo _ei _ei' env = false, env
 
-and is_same_formal (_,t,a) (_,t',a') =
-  is_same_type t t' && Cil_datatype.Attributes.equal a a'
+and is_same_formal (_,t,a) (_,t',a') env =
+  is_same_type t t' env &&& Cil_datatype.Attributes.equal a a'
 
-and is_same_compound_init (o,i) (o',i') =
-  is_same_offset o o' && is_same_init i i'
+and is_same_compound_init (o,i) (o',i') env =
+  is_same_offset o o' env &&> is_same_init i i'
 
-and is_same_init i i' =
+and is_same_init i i' env =
   match i, i' with
-  | SingleInit e, SingleInit e' -> is_same_exp e e'
+  | SingleInit e, SingleInit e' -> is_same_exp e e' env
   | CompoundInit (t,l), CompoundInit (t', l') ->
-    is_same_type t t' &&
-    Logic_utils.is_same_list is_same_compound_init l l'
-  | (SingleInit _ | CompoundInit _), _ -> false
+    is_same_type t t' env &&>
+    (is_same_list is_same_compound_init) l l'
+  | (SingleInit _ | CompoundInit _), _ -> false, env
 
 and is_same_initinfo i i' = is_same_opt is_same_init i.init i'.init
 
-and is_same_exp _e _e' = false
+and is_same_exp _e _e' env = false, env
 
-and is_same_offset _o _o' = false
+and is_same_offset _o _o' env = false, env
 
-and typeinfo_correspondance ?loc ti =
-  let add ti =
+and typeinfo_correspondance ?loc ti env =
+  let add ti env =
     match find_candidate_type ?loc ti with
-    | None -> `Not_present
+    | None -> `Not_present, env
     | Some ti' ->
-      if is_same_type ti.ttype ti'.ttype then `Same ti' else `Not_present
+      let res, env = is_same_type ti.ttype ti'.ttype env in
+      (if res then `Same ti' else `Not_present), env
   in
-  Typeinfo.memo add ti
+  Typeinfo.memo_env add ti env
 
-and compinfo_correspondance ?loc ci =
-  let add ci =
+and compinfo_correspondance ?loc ci env =
+  let add ci env =
     match find_candidate_compinfo ?loc ci with
-    | None -> `Not_present
+    | None -> `Not_present, env
     | Some ci' ->
-      if is_same_compinfo ci ci' then `Same ci' else `Not_present
+      let res, env = is_same_compinfo ci ci' env in
+      (if res then `Same ci' else `Not_present), env
   in
-  Compinfo.memo add ci
+  match Cil_datatype.Compinfo.Map.find_opt ci env.compinfo with
+  | Some ci' -> `Same ci', env
+  | None -> Compinfo.memo_env add ci env
 
-and enuminfo_correspondance ?loc ei =
-  let add ei =
+and enuminfo_correspondance ?loc ei env =
+  let add ei env =
     match find_candidate_enuminfo ?loc ei with
-    | None -> `Not_present
+    | None -> `Not_present, env
     | Some ei' ->
-      if is_same_enuminfo ei ei' then `Same ei' else `Not_present
+      let res, env = is_same_enuminfo ei ei' env in
+      (if res then `Same ei' else `Not_present),env
   in
-  Enuminfo.memo add ei
+  Enuminfo.memo_env add ei env
 
 let gvar_correspondance ?loc vi =
   let add vi =
@@ -306,17 +356,18 @@ let gvar_correspondance ?loc vi =
         Project.on ~selection (Orig_project.get()) Globals.Vars.find vi
       in
       let init' = Globals.Vars.find vi' in
-      if is_same_initinfo init init' then `Same vi' else `Not_present
+      let res,_ = is_same_initinfo init init' empty_env in
+      if res then `Same vi' else `Not_present
   in
   Varinfo.memo add vi
 
 let global_correspondance g =
   match g with
-  | GType (ti,loc) -> ignore (typeinfo_correspondance ~loc ti)
+  | GType (ti,loc) -> ignore (typeinfo_correspondance ~loc ti empty_env)
   | GCompTag(ci,loc) | GCompTagDecl(ci,loc) ->
-    ignore (compinfo_correspondance ~loc ci)
+    ignore (compinfo_correspondance ~loc ci empty_env)
   | GEnumTag(ei,loc) | GEnumTagDecl(ei,loc) ->
-    ignore (enuminfo_correspondance ~loc ei)
+    ignore (enuminfo_correspondance ~loc ei empty_env)
   | GVar(vi,_,loc) | GVarDecl(vi,loc) ->
     ignore (gvar_correspondance ~loc vi)
   | GFunDecl(_,_vi,_loc) -> ()
-- 
GitLab