From 49c690aab04fe9d4a1f32fb9aae54ba170e4e181 Mon Sep 17 00:00:00 2001 From: Valentin Perrelle <valentin.perrelle@cea.fr> Date: Mon, 12 Jun 2023 16:54:21 +0200 Subject: [PATCH] [Eva] Generalize the callstack type to include global initialization --- src/dune | 3 +- .../abstract_interp/eva_types.ml | 152 ++++++++++++------ .../abstract_interp/eva_types.mli | 13 +- src/plugins/eva/Eva.mli | 14 +- src/plugins/eva/api/values_request.ml | 23 +-- src/plugins/eva/engine/compute_functions.ml | 5 +- src/plugins/eva/engine/initialization.ml | 22 ++- src/plugins/eva/types/callstack.mli | 14 +- src/plugins/eva/utils/eva_utils.ml | 10 +- src/plugins/eva/utils/results.ml | 2 +- 10 files changed, 178 insertions(+), 80 deletions(-) diff --git a/src/dune b/src/dune index 599d3f7423a..7cd55aa0b82 100644 --- a/src/dune +++ b/src/dune @@ -39,6 +39,7 @@ (echo " - dune-site.plugins:" %{lib-available:dune-site.plugins} "\n") (echo " - ppx_import:" %{lib-available:ppx_import} "\n") (echo " - ppx_deriving.eq:" %{lib-available:ppx_deriving.eq} "\n") + (echo " - ppx_deriving.ord:" %{lib-available:ppx_deriving.ord} "\n") (echo " - ppx_deriving_yaml:" %{lib-available:ppx_deriving_yaml} "\n") ) ) @@ -51,7 +52,7 @@ (flags :standard -w -9) (libraries frama-c.init fpath str unix zarith ocamlgraph dynlink bytes yaml.unix yojson menhirLib dune-site dune-site.plugins) (instrumentation (backend landmarks)) - (preprocess (staged_pps ppx_import ppx_deriving.eq ppx_deriving_yaml)) + (preprocess (staged_pps ppx_import ppx_deriving.eq ppx_deriving.ord ppx_deriving_yaml)) ) (generate_sites_module (module config_data) (sites frama-c) (plugins (frama-c plugins) (frama-c plugins_gui))) diff --git a/src/kernel_services/abstract_interp/eva_types.ml b/src/kernel_services/abstract_interp/eva_types.ml index 4e4c5c83558..de54d6beef0 100644 --- a/src/kernel_services/abstract_interp/eva_types.ml +++ b/src/kernel_services/abstract_interp/eva_types.ml @@ -25,31 +25,27 @@ struct module Thread = Int (* Threads are identified by integers *) module Kf = Kernel_function module Stmt = Cil_datatype.Stmt - - type call = Cil_types.kernel_function * Cil_types.stmt + module Var = Cil_datatype.Varinfo module Call = Datatype.Pair_with_collections(Kf)(Stmt) (struct let module_name = "Eva.Callstack.Call" end) - module Calls = Datatype.List (Call) - type callstack = { + type local_stack = { thread: int; - entry_point: Cil_types.kernel_function; - stack: call list; + entry_point: Kernel_function.t; + stack: Call.t list; } - - (* Datatype *) - - module Prototype = + module LocalStack = struct - include Datatype.Serializable_undefined - - type t = callstack - - let name = "Eva.Callstack" + type t = local_stack = { + thread: int; + entry_point: Kernel_function.t; + stack: Call.t list; + } + [@@deriving eq, ord] let reprs = List.concat_map (fun stack -> @@ -65,22 +61,46 @@ struct in Format.fprintf fmt "@[<hv>"; List.iter (pp_call fmt) cs.stack; - Format.fprintf fmt "%a@]" Kf.pretty cs.entry_point + Format.fprintf fmt "%a@]" Kernel_function.pretty cs.entry_point + + let hash cs = + Hashtbl.hash + (cs.thread, Kernel_function.hash cs.entry_point, Calls.hash cs.stack) + end + + type call = Call.t - let equal cs1 cs2 = - Thread.equal cs1.thread cs2.thread && - Calls.equal cs1.stack cs2.stack && - Kf.equal cs1.entry_point cs2.entry_point + type callstack = + | Global of Cil_datatype.Varinfo.t + | Local of LocalStack.t - let compare cs1 cs2 = - let r = Thread.compare cs1.thread cs2.thread in - if r <> 0 then r else - let r = Kf.compare cs1.entry_point cs2.entry_point in - if r <> 0 then r else - Calls.compare cs1.stack cs2.stack + (* Datatype *) + + module Prototype = + struct + open Cil_datatype + include Datatype.Serializable_undefined + + type t = callstack = + | Global of Varinfo.t + | Local of LocalStack.t + [@@deriving eq, ord] + + let name = "Eva.Callstack" + + let reprs = + List.map (fun vi -> Global vi) Varinfo.reprs @ + List.map (fun ls -> Local ls) LocalStack.reprs + + let pretty fmt cs = + match cs with + | Global vi -> Format.fprintf fmt "init %a" Varinfo.pretty vi + | Local ls -> LocalStack.pretty fmt ls let hash cs = - Hashtbl.hash (cs.thread, Kf.hash cs.entry_point, Calls.hash cs.stack) + match cs with + | Global vi -> Hashtbl.hash (1, Varinfo.hash vi) + | Local ls -> Hashtbl.hash (2, LocalStack.hash ls) end include Datatype.Make_with_collections (Prototype) @@ -88,50 +108,86 @@ struct (* Constructor *) - let init ?(thread=0) entry_point = { thread ; entry_point ; stack = [] } + let init_global vi = + Global vi + + let init_local ?(thread=0) kf = + Local { thread; entry_point=kf; stack = [] } + + (* Query *) + let is_local = function + | Global _ -> false + | Local _ -> true (* Stack manipulation *) + let local = function + | Global _vi -> + invalid_arg "invalid stack manipulation on a global callstack" + | Local ls -> ls + let push kf stmt cs = - { cs with stack = (kf, stmt) :: cs.stack } + let ls = local cs in + Local { ls with stack = (kf, stmt) :: ls.stack } let pop cs = - match cs.stack with - | [] -> None - | (kf,stmt) :: tail -> Some (kf, stmt, { cs with stack = tail }) + match cs with + | Global _vi -> None + | Local ls -> + match ls.stack with + | [] -> None + | (kf,stmt) :: tail -> Some (kf, stmt, Local { ls with stack = tail }) let top cs = - match cs.stack with - | [] -> None - | (kf, stmt) :: _ -> Some (kf, stmt) + match cs with + | Global _vi -> None + | Local ls -> + match ls.stack with + | [] -> None + | (kf, stmt) :: _ -> Some (kf, stmt) let top_kf cs = - match cs.stack with - | [] -> cs.entry_point + let ls = local cs in + match ls.stack with | (kf, _stmt) :: _ -> kf + | [] -> ls.entry_point let top_callsite cs = - match cs.stack with - | [] -> None - | (_kf, stmt) :: _ -> Some (stmt) + match cs with + | Global _vi -> None + | Local ls -> + match ls.stack with + | [] -> None + | (_kf, stmt) :: _ -> Some (stmt) let top_call cs = - match cs.stack with - | [] -> cs.entry_point, Cil_types.Kglobal + let ls = local cs in + match ls.stack with | (kf, stmt) :: _ -> kf, Cil_types.Kstmt stmt + | [] -> ls.entry_point, Cil_types.Kglobal + (* Conversion *) let to_legacy cs = - let l = - List.rev_map (fun (kf, stmt) -> (kf, Cil_types.Kstmt stmt)) cs.stack - in - List.rev ((cs.entry_point, Cil_types.Kglobal) :: l) + match cs with + | Global _vi -> [] + | Local ls -> + let l = + List.rev_map (fun (kf, stmt) -> (kf, Cil_types.Kstmt stmt)) ls.stack + in + List.rev ((ls.entry_point, Cil_types.Kglobal) :: l) let to_kf_list cs = - cs.entry_point :: List.rev_map fst cs.stack + match cs with + | Global _vi -> [] + | Local ls -> + ls.entry_point :: List.rev_map fst ls.stack let to_stmt_list cs = - List.rev_map snd cs.stack + match cs with + | Global _vi -> [] + | Local ls -> + List.rev_map snd ls.stack end diff --git a/src/kernel_services/abstract_interp/eva_types.mli b/src/kernel_services/abstract_interp/eva_types.mli index 4862853563f..dc6b71c84df 100644 --- a/src/kernel_services/abstract_interp/eva_types.mli +++ b/src/kernel_services/abstract_interp/eva_types.mli @@ -29,21 +29,30 @@ sig module Call : Datatype.S with type t = call - type callstack = private { + type local_stack = private { thread: int; entry_point: Cil_types.kernel_function; stack: call list; } + type callstack = private + | Global of Cil_types.varinfo + | Local of local_stack + include Datatype.S_with_collections with type t = callstack - val init : ?thread:int -> Cil_types.kernel_function -> t + val init_global : Cil_types.varinfo -> t + val init_local : ?thread:int -> Cil_types.kernel_function -> t + + val is_local : t -> bool + val push : Cil_types.kernel_function -> Cil_types.stmt -> t -> t val pop : t -> (Cil_types.kernel_function * Cil_types.stmt * t) option val top : t -> (Cil_types.kernel_function * Cil_types.stmt) option val top_kf : t -> Cil_types.kernel_function val top_callsite : t -> Cil_types.stmt option val top_call : t -> Cil_types.kernel_function * Cil_types.kinstr + val to_legacy : t -> Value_types.callstack val to_kf_list : t -> Cil_types.kernel_function list val to_stmt_list : t -> Cil_types.stmt list diff --git a/src/plugins/eva/Eva.mli b/src/plugins/eva/Eva.mli index ae6966402a3..18d459bb886 100644 --- a/src/plugins/eva/Eva.mli +++ b/src/plugins/eva/Eva.mli @@ -122,25 +122,33 @@ module Callstack: sig module Call : Datatype.S with type t = call - (** [callstack] is used to describe the analysis context when analysing a + (** [local_stack] is used to describe the analysis context when analysing a function. It contains the thread, the entry point and the list of calls from the entry point. This type is very likely to change in the future. Never use this type directly, prefer the use of the following functions when possible. *) - type callstack = Eva_types.Callstack.callstack = private { + type local_stack = Eva_types.Callstack.local_stack = private { thread: int; (* An identifier of the thread's callstack *) entry_point: Cil_types.kernel_function; (* The first function in the callstack *) stack: call list; } + type callstack = Eva_types.Callstack.callstack = private + | Global of Cil_types.varinfo + | Local of local_stack + include Datatype.S_with_collections with type t = callstack and module Hashtbl = Eva_types.Callstack.Hashtbl (* Constructor *) - val init : ?thread:int -> Cil_types.kernel_function -> t + val init_global : Cil_types.varinfo -> t + val init_local : ?thread:int -> Cil_types.kernel_function -> t + + (* Query *) + val is_local : t -> bool (* Stack manipulation *) val push : Cil_types.kernel_function -> Cil_types.stmt -> t -> t diff --git a/src/plugins/eva/api/values_request.ml b/src/plugins/eva/api/values_request.ml index a88453bcc12..468a733da7b 100644 --- a/src/plugins/eva/api/values_request.ml +++ b/src/plugins/eva/api/values_request.ml @@ -243,16 +243,19 @@ module Jcalls : Request.Output with type t = callstack = struct ] let to_json (cs : t) = - let aux (acc, jcaller) (callee, stmt) = - let jcallee = Jfct.to_json callee in - jcallsite jcaller jcallee stmt :: acc, jcallee - in - let entry_point = Jfct.to_json cs.entry_point in - let l, _last_callee = List.fold_left aux - ([`Assoc [ "callee", entry_point ]], entry_point) - cs.stack - in - `List (List.rev l) + match cs with + | Global _vi -> `List [] + | Local ls -> + let aux (acc, jcaller) (callee, stmt) = + let jcallee = Jfct.to_json callee in + jcallsite jcaller jcallee stmt :: acc, jcallee + in + let entry_point = Jfct.to_json ls.entry_point in + let l, _last_callee = List.fold_left aux + ([`Assoc [ "callee", entry_point ]], entry_point) + ls.stack + in + `List (List.rev l) end module Jtruth : Data.S with type t = truth = struct diff --git a/src/plugins/eva/engine/compute_functions.ml b/src/plugins/eva/engine/compute_functions.ml index 9996485515f..4dd648dea88 100644 --- a/src/plugins/eva/engine/compute_functions.ml +++ b/src/plugins/eva/engine/compute_functions.ml @@ -352,14 +352,14 @@ module Make (Abstract: Abstractions.S_with_evaluation) = struct let compute kf init_state = let restore_signals = register_signal_handler () in let compute () = - let callstack = Callstack.init kf in + let callstack = Callstack.init_local kf in Eva_utils.set_call_stack callstack; store_initial_state callstack init_state; let call = { kf; callstack; arguments = []; rest = []; return = None; } in let final_result = compute_call Kglobal call None init_state in let final_states = List.map snd (final_result.Transfer.states) in let final_state = PowersetDomain.(final_states |> of_list |> join) in - Eva_utils.pop_call_stack (); + Eva_utils.clear_call_stack (); Self.feedback "done for function %a" Kernel_function.pretty kf; Abstract.Dom.Store.mark_as_computed (); Self.(set_computation_state Computed); @@ -372,6 +372,7 @@ module Make (Abstract: Abstractions.S_with_evaluation) = struct let cleanup () = Abstract.Dom.Store.mark_as_computed (); Self.(set_computation_state Aborted); + Eva_utils.clear_call_stack (); post_analysis_cleanup ~aborted:true in Eva_utils.protect compute ~cleanup diff --git a/src/plugins/eva/engine/initialization.ml b/src/plugins/eva/engine/initialization.ml index ba5c5ea1c15..c8850b93bee 100644 --- a/src/plugins/eva/engine/initialization.ml +++ b/src/plugins/eva/engine/initialization.ml @@ -308,15 +308,21 @@ module Make let initialize_global_variable ~lib_entry vi init state = Cil.CurrentLoc.set vi.vdecl; + Eva_utils.set_call_stack (Callstack.init_global vi); let state = Domain.enter_scope Abstract_domain.Global [vi] state in - if vi.vsource then - let initialize = - if lib_entry || (vi.vstorage = Extern) - then initialize_var_lib_entry - else initialize_var_not_lib_entry ~local:false - in - initialize Kglobal vi init.init state - else state + let state = if vi.vsource then + let initialize = + if lib_entry || (vi.vstorage = Extern) + then initialize_var_lib_entry + else initialize_var_not_lib_entry ~local:false + in + initialize Kglobal vi init.init state + else state + in + Eva_utils.clear_call_stack (); + state + + (* Compute the initial state with all global variable initialized. *) let compute_global_state ~lib_entry () = diff --git a/src/plugins/eva/types/callstack.mli b/src/plugins/eva/types/callstack.mli index f794735b54f..4a22d47031d 100644 --- a/src/plugins/eva/types/callstack.mli +++ b/src/plugins/eva/types/callstack.mli @@ -27,25 +27,33 @@ type call = Cil_types.kernel_function * Cil_types.stmt module Call : Datatype.S with type t = call -(** [callstack] is used to describe the analysis context when analysing a +(** [local_stack] is used to describe the analysis context when analysing a function. It contains the thread, the entry point and the list of calls from the entry point. This type is very likely to change in the future. Never use this type directly, prefer the use of the following functions when possible. *) -type callstack = Eva_types.Callstack.callstack = private { +type local_stack = Eva_types.Callstack.local_stack = private { thread: int; (* An identifier of the thread's callstack *) entry_point: Cil_types.kernel_function; (* The first function in the callstack *) stack: call list; } +type callstack = Eva_types.Callstack.callstack = private + | Global of Cil_types.varinfo + | Local of local_stack + include Datatype.S_with_collections with type t = callstack and module Hashtbl = Eva_types.Callstack.Hashtbl (* Constructor *) -val init : ?thread:int -> Cil_types.kernel_function -> t +val init_global : Cil_types.varinfo -> t +val init_local : ?thread:int -> Cil_types.kernel_function -> t + +(* Query *) +val is_local : t -> bool (* Stack manipulation *) val push : Cil_types.kernel_function -> Cil_types.stmt -> t -> t diff --git a/src/plugins/eva/utils/eva_utils.ml b/src/plugins/eva/utils/eva_utils.ml index 9ccd9540a5a..0e9c181d9f0 100644 --- a/src/plugins/eva/utils/eva_utils.ml +++ b/src/plugins/eva/utils/eva_utils.ml @@ -27,12 +27,18 @@ open Cil_types let current_callstack : Callstack.t option ref = ref None let clear_call_stack () = - current_callstack := None + match !current_callstack with + | None -> () + | Some cs -> + if Callstack.is_local cs then + Eva_perf.stop_doing (Callstack.to_legacy cs); + current_callstack := None let set_call_stack cs = assert (!current_callstack = None); current_callstack := Some cs; - Eva_perf.start_doing (Callstack.to_legacy cs) + if Callstack.is_local cs then + Eva_perf.start_doing (Callstack.to_legacy cs) let current_call_stack () = match !current_callstack with diff --git a/src/plugins/eva/utils/results.ml b/src/plugins/eva/utils/results.ml index 75728f5f0eb..897592d765a 100644 --- a/src/plugins/eva/utils/results.ml +++ b/src/plugins/eva/utils/results.ml @@ -243,7 +243,7 @@ struct A.get_stmt_state_by_callstack ?selection ~after:true stmt |> by_callstack ctx | Initial -> - let cs = Callstack.init (fst (Globals.entry_point ())) in + let cs = Callstack.init_local (fst (Globals.entry_point ())) in A.get_global_state () |> singleton cs | Start kf -> A.get_initial_state_by_callstack ?selection kf |> by_callstack ctx -- GitLab