From f5b131fe353566ebb440a220fea97002e8f872b9 Mon Sep 17 00:00:00 2001 From: Julien Signoles <julien.signoles@cea.fr> Date: Mon, 10 Aug 2020 17:55:12 +0200 Subject: [PATCH] [e-acsl] remove the main visitor in Prepare_ast in order to propagate property statuses soundly [e-acsl] simplify and document Prepare_ast a bit more --- .../src/project_initializer/prepare_ast.ml | 595 ++++++++---------- 1 file changed, 274 insertions(+), 321 deletions(-) diff --git a/src/plugins/e-acsl/src/project_initializer/prepare_ast.ml b/src/plugins/e-acsl/src/project_initializer/prepare_ast.ml index 29f4c1df264..2b01ed8d9a8 100644 --- a/src/plugins/e-acsl/src/project_initializer/prepare_ast.ml +++ b/src/plugins/e-acsl/src/project_initializer/prepare_ast.ml @@ -21,6 +21,7 @@ (**************************************************************************) open Cil_types +open Cil_datatype let dkey = Options.dkey_prepare @@ -28,81 +29,69 @@ let dkey = Options.dkey_prepare (* Environment *) (* ********************************************************************** *) -let fct_tbl: unit Kernel_function.Hashtbl.t = Kernel_function.Hashtbl.create 7 - -(* The purpose of [actions] is similar to the Frama-C visitor's - [get_filling_actions] but we need to fill it outside the visitor. So it is - our own version. *) -let actions = Queue.create () - -(* global table for ensuring that logic info are not shared between a function - definition and its duplicate *) -module Global: sig - val add_logic_info: logic_info -> unit - val mem_logic_info: logic_info -> bool +module Dup_functions: sig + val generate_vi: varinfo -> varinfo (* return the new varinfo *) + val mem: varinfo -> bool + val find: varinfo -> varinfo val reset: unit -> unit end = struct - let tbl = Cil_datatype.Logic_info.Hashtbl.create 7 - let add_logic_info x = Cil_datatype.Logic_info.Hashtbl.add tbl x () - let mem_logic_info x = Cil_datatype.Logic_info.Hashtbl.mem tbl x - let reset () = Cil_datatype.Logic_info.Hashtbl.clear tbl + let tbl: varinfo Varinfo.Hashtbl.t = Varinfo.Hashtbl.create 7 + + (* assume [vi] is not already in [tbl] *) + let generate_vi vi = + let new_name = Functions.RTL.mk_gen_name vi.vname in + let new_vi = + Cil.makeGlobalVar + ~referenced:vi.vreferenced + ~loc:vi.vdecl + new_name + vi.vtype + in + Varinfo.Hashtbl.add tbl vi new_vi; + new_vi -end + let mem = Varinfo.Hashtbl.mem tbl + let find = Varinfo.Hashtbl.find tbl + let reset () = Varinfo.Hashtbl.clear tbl -let reset () = - Kernel_function.Hashtbl.clear fct_tbl; - Global.reset (); - Queue.clear actions +end (* ********************************************************************** *) (* Duplicating a function *) (* ********************************************************************** *) -(* [tbl] associates the old formals to the new ones *) -let dup_funspec tbl bhv spec = +(* [formals] associates the old formals to the new ones in order to + substitute them in [spec]. *) +let dup_funspec formals spec = (* Options.feedback "DUP SPEC %a" Cil.d_funspec spec;*) let o = object - inherit Cil.genericCilVisitor bhv - - val already_visited = Cil_datatype.Logic_var.Hashtbl.create 7 - - method !vlogic_info_use li = - if Global.mem_logic_info li then - Cil.ChangeDoChildrenPost - ({ li with l_var_info = li.l_var_info } (* force a copy *), - Visitor_behavior.Get.logic_info bhv) - else - Cil.JustCopy + inherit Visitor.frama_c_refresh (Project.current ()) - method !vterm_offset _ = - Cil.DoChildrenPost - (function (* no way to directly visit fieldinfo and model_info uses *) - | TField(fi, off) -> - TField(Visitor_behavior.Get.fieldinfo bhv fi, off) - | TModel(mi, off) -> - TModel(Visitor_behavior.Get.model_info bhv mi, off) - | off -> - off) + val already_visited = Logic_var.Hashtbl.create 7 + (* just substituting in [!vvrbl] (when using a varinfo) does not work + because varinfo's occurrences are shared in logic_vars, so modifying the + varinfo would modify any logic_var based on it, even if it is not part of + this [spec] (e.g., if it is in another annotation of the same + function) *) method !vlogic_var_use orig_lvi = match orig_lvi.lv_origin with | None -> Cil.JustCopy | Some vi -> try - let new_lvi = - Cil_datatype.Logic_var.Hashtbl.find already_visited orig_lvi - in + let new_lvi = Logic_var.Hashtbl.find already_visited orig_lvi in + (* recreate sharing of the logic_var in this [spec] *) Cil.ChangeTo new_lvi with Not_found -> + (* first time visiting this logic var *) Cil.ChangeDoChildrenPost ({ orig_lvi with lv_id = orig_lvi.lv_id } (* force a copy *), fun lvi -> try - let new_vi = Cil_datatype.Varinfo.Hashtbl.find tbl vi in - Cil_datatype.Logic_var.Hashtbl.add - already_visited orig_lvi lvi; + let new_vi = Varinfo.Hashtbl.find formals vi in + Logic_var.Hashtbl.add already_visited orig_lvi lvi; (* [lvi] is the logic counterpart of a formal varinfo that has been replaced by a new one: propagate this change *) lvi.lv_id <- new_vi.vid; @@ -112,20 +101,12 @@ let dup_funspec tbl bhv spec = lvi with Not_found -> assert vi.vglob; - (* using [Visitor_behavior.Get.logic_var bhv lvi] is correct - only because the lv_id used to compare the lvi does not - change between the original one and this copy *) - Visitor_behavior.Get.logic_var bhv lvi) - - method !videntified_term _ = - Cil.DoChildrenPost Logic_const.refresh_identified_term + lvi) - method !videntified_predicate _ = - Cil.DoChildrenPost Logic_const.refresh_predicate end in - Cil.visitCilFunspec o spec + Cil.visitCilFunspec (o :> Cil.cilVisitor) spec -let dup_fundec loc spec bhv sound_verdict_vi kf vi new_vi = +let dup_fundec loc spec sound_verdict_vi kf vi new_vi = new_vi.vdefined <- true; let formals = Kernel_function.get_formals kf in let mk_formal vi = @@ -174,9 +155,9 @@ let dup_fundec loc spec bhv sound_verdict_vi kf vi new_vi = let locals = match res with None -> [] | Some r -> [ r ] in let body = Cil.mkBlock stmts in body.blocals <- locals; - let tbl = Cil_datatype.Varinfo.Hashtbl.create 7 in - List.iter2 (Cil_datatype.Varinfo.Hashtbl.add tbl) formals new_formals; - let new_spec = dup_funspec tbl bhv spec in + let tbl = Varinfo.Hashtbl.create 7 in + List.iter2 (Varinfo.Hashtbl.add tbl) formals new_formals; + let new_spec = dup_funspec tbl spec in let fundec = { svar = new_vi; sformals = new_formals; @@ -192,54 +173,69 @@ let dup_fundec loc spec bhv sound_verdict_vi kf vi new_vi = Cfg.cfgFun fundec; fundec -let dup_global loc actions spec bhv sound_verdict_vi kf vi new_vi = +let dup_global loc spec sound_verdict_vi kf vi new_vi = let name = vi.vname in Options.feedback ~dkey ~level:2 "entering in function %s" name; - let fundec = dup_fundec loc spec bhv sound_verdict_vi kf vi new_vi in - let fct = Definition(fundec, loc) in - let new_spec = fundec.sspec in - let new_kf = { fundec = fct; spec = new_spec } in - Queue.add - (fun () -> - Kernel_function.Hashtbl.add fct_tbl new_kf (); - Globals.Functions.register new_kf; - Globals.Functions.replace_by_definition new_spec fundec loc; - Annotations.register_funspec new_kf; - if new_vi.vname = "main" then begin - (* recompute the info about the old main since its name has changed; - see the corresponding part in the main visitor *) - Globals.Functions.remove vi; - Globals.Functions.register kf - end) - actions; - (* remove the specs attached to the previous kf iff it is a definition: - it is necessary to keep stable the number of annotations in order to get - [Keep_status] working fine. *) - let kf = Visitor_behavior.Get.kernel_function bhv kf in + let new_fundec = dup_fundec loc spec sound_verdict_vi kf vi new_vi in + let new_fct = Definition(new_fundec, loc) in + let new_spec = new_fundec.sspec in + let new_kf = { fundec = new_fct; spec = new_spec } in + Globals.Functions.register new_kf; + Globals.Functions.replace_by_definition new_spec new_fundec loc; + Annotations.register_funspec new_kf; + if Datatype.String.equal new_vi.vname "main" then begin + (* recompute kernel's info about the old main since its name has changed; + see the corresponding part in the main visitor *) + Globals.Functions.remove vi; + Globals.Functions.register kf + end; + (* copy property statuses to the new spec *) + let copy old_ip new_ip = + let open Property_status in + let cp status ep = + let e = Emitter.Usable_emitter.get ep.emitter in + if ep.logical_consequence + then logical_consequence e new_ip ep.properties + else emit e new_ip ~hyps:ep.properties status + in + match get old_ip with + | Never_tried -> + () + | Best(s, epl) -> + List.iter (cp s) epl + | Inconsistent icst -> + List.iter (cp True) icst.valid; + (* either the program is reachable and [False_and_reachable] is + fine, or the program point is not reachable and it does not + matter for E-ACSL that checks it at runtime. *) + List.iter (cp False_and_reachable) icst.invalid + in + let ips kf s = Property.ip_of_spec kf Kglobal ~active:[] s in + List.iter2 copy (ips kf spec) (ips new_kf new_spec); + (* remove annotations from the old spec. Yet keep them in functions only + declared since, otherwise, the kernel would complain about functions + with neither contract nor body *) if Kernel_function.is_definition kf then begin - Queue.add - (fun () -> - let bhvs = - Annotations.fold_behaviors (fun e b acc -> (e, b) :: acc) kf [] - in - List.iter - (fun (e, b) -> Annotations.remove_behavior ~force:true e kf b) - bhvs; - Annotations.iter_decreases - (fun e _ -> Annotations.remove_decreases e kf) - kf; - Annotations.iter_terminates - (fun e _ -> Annotations.remove_terminates e kf) - kf; - Annotations.iter_complete - (fun e l -> Annotations.remove_complete e kf l) - kf; - Annotations.iter_disjoint - (fun e l -> Annotations.remove_disjoint e kf l) - kf) - actions + let old_bhvs = + Annotations.fold_behaviors (fun e b acc -> (e, b) :: acc) kf [] + in + List.iter + (fun (e, b) -> Annotations.remove_behavior ~force:true e kf b) + old_bhvs; + Annotations.iter_decreases + (fun e _ -> Annotations.remove_decreases e kf) + kf; + Annotations.iter_terminates + (fun e _ -> Annotations.remove_terminates e kf) + kf; + Annotations.iter_complete + (fun e l -> Annotations.remove_complete e kf l) + kf; + Annotations.iter_disjoint + (fun e l -> Annotations.remove_disjoint e kf l) + kf end; - GFun(fundec, loc), GFunDecl(new_spec, new_vi, loc) + GFun(new_fundec, loc), GFunDecl(new_spec, new_vi, loc) (* ********************************************************************** *) (* Alignment *) @@ -293,230 +289,187 @@ let require_alignment vi algn = Cil.bitsSizeOf vi.vtype < algn*8 && not (sufficiently_aligned vi algn) (* ********************************************************************** *) -(* Visitor *) +(* Visiting the globals *) (* ********************************************************************** *) -class prepare_visitor = object (self) - inherit Visitor.frama_c_inplace - - val mutable has_new_stmt_in_fundec = false - - (* ---------------------------------------------------------------------- *) - (* visitor's local variable *) - (* ---------------------------------------------------------------------- *) - - val terms = Misc.Id_term.Hashtbl.create 7 - (* table for ensuring absence of term sharing *) +(* perform a few modifications in the [kf]'s code and annotations that are + required by E-ACSL's analyses and transformations *) +let prepare_fundec kf = + let o = object + inherit Visitor.frama_c_inplace + + (* table for ensuring absence of term sharing *) + val terms = Misc.Id_term.Hashtbl.create 7 + + (* substitute function's varinfos by their duplicate in function calls *) + method !vvrbl vi = + try Cil.ChangeTo (Dup_functions.find vi) + with Not_found -> Cil.SkipChildren + + (* temporal analysis requires that attributes are aligned to local + variables *) + method !vblock _ = + if Options.Temporal_validity.get () then + Cil.DoChildrenPost + (fun blk -> + List.iter + (fun vi -> + (* 4 bytes alignment is required to allow sufficient space for + storage of 32-bit timestamps in a 1:1 shadow. *) + if require_alignment vi 4 then + vi.vattr <- + Attr("aligned", [ AInt Integer.four ]) :: vi.vattr) + blk.blocals; + blk) + else + Cil.DoChildren - val unduplicable_functions = - let white_list = - [ "__builtin_va_arg"; - "__builtin_va_end"; - "__builtin_va_start"; - "__builtin_va_copy" ] - in - List.fold_left - (fun acc s -> Datatype.String.Set.add s acc) - Datatype.String.Set.empty - white_list - - val fct_tbl = Cil_datatype.Varinfo.Hashtbl.create 7 - val mutable new_definitions: global list = [] - (* new definitions of the annotated functions which will contain the - translation of the E-ACSL contract *) - - (* the variable [sound_verdict] belongs to the E-ACSL's RTL and indicates - whether the verdict emitted by E-ACSL is sound. It needs to be visible at - that point because it is set in all function duplicates - (see [dup_fundec]). *) - val mutable sound_verdict_vi = - let name = Functions.RTL.mk_api_name "sound_verdict" in - let vi = Cil.makeGlobalVar name Cil.intType in - vi.vstorage <- Extern; - vi.vreferenced <- true; - vi - - (* ---------------------------------------------------------------------- *) - (* visitor's private methods *) - (* ---------------------------------------------------------------------- *) - - method private is_variadic_function vi = - match Cil.unrollType vi.vtype with - | TFun(_, _, variadic, _) -> variadic - | _ -> true - - (* ---------------------------------------------------------------------- *) - (* visitor's method overloading *) - (* ---------------------------------------------------------------------- *) - - method !vlogic_info_decl li = - Global.add_logic_info li; - Cil.SkipChildren - - method !vvrbl vi = - try - let new_vi = Cil_datatype.Varinfo.Hashtbl.find fct_tbl vi in - (* replace functions at callsite by its duplicated version *) - Cil.ChangeTo new_vi - with Not_found -> - Cil.SkipChildren - - method !vterm _t = - Cil.DoChildrenPost - (fun t -> - if Misc.Id_term.Hashtbl.mem terms t then - (* remove term sharing for soundness of E-ACSL typing - (see typing.ml) *) - { t with term_node = t.term_node } - else begin - Misc.Id_term.Hashtbl.add terms t (); - t - end) - - (* Add align attributes to local variables (required by temporal analysis) *) - method !vblock _ = - if Options.Temporal_validity.get () then + (* remove term sharing introduced by the Frama-C kernel. For instance, an + annotation such as [a <= t <= b] is replaced by [a <= t && t <= b] with + both occurrences of [t] being shared. Yet, the E-ACSL type system may + require to assign them different types. *) + method !vterm _ = Cil.DoChildrenPost - (fun blk -> - List.iter - (fun vi -> - (* 4 bytes alignment is required to allow sufficient space for - storage of 32-bit timestamps in a 1:1 shadow. *) - if require_alignment vi 4 then - vi.vattr <- - Attr("aligned", [ AInt Integer.four ]) :: vi.vattr) - blk.blocals; - blk) - else - Cil.DoChildren + (fun t -> + if Misc.Id_term.Hashtbl.mem terms t then + { t with term_node = t.term_node } + else begin + Misc.Id_term.Hashtbl.add terms t (); + t + end) - method !vfunc fundec = - Cil.DoChildrenPost - (fun _ -> - if has_new_stmt_in_fundec then begin - has_new_stmt_in_fundec <- false; - (* recompute the CFG *) - Cfg.clearCFGinfo ~clear_id:false fundec; - Cfg.cfgFun fundec; - end; - fundec) - - method !vglob_aux = function - | GFunDecl(_, vi, loc) | GFun({ svar = vi }, loc) - when (* duplicate a function iff: *) - (* it is not already duplicated *) - not (Cil_datatype.Varinfo.Hashtbl.mem fct_tbl vi) - && (* it is duplicable *) - not (Datatype.String.Set.mem vi.vname unduplicable_functions) - && (* it is not a variadic function *) - not (self#is_variadic_function vi) - && (* it is not a built-in *) - not (Misc.is_fc_or_compiler_builtin vi) - && - (let kf = - try Globals.Functions.get vi with Not_found -> assert false - in - (* either explicitely listed as to be not instrumented *) - not (Functions.instrument kf) - || - (* or: *) - ((* it has a function contract *) - not (Cil.is_empty_funspec - (Annotations.funspec ~populate:false - (Extlib.the self#current_kf))) - && (* its annotations must be monitored *) - Functions.check kf)) - && (* it is neither malloc nor free *) - (* [TODO:] this special case preserves the former behavior. - Should be generalized latter by considering only preconditions of - libc functions *) - vi.vname <> "malloc" && vi.vname <> "calloc" && vi.vname <> "realloc" - && vi.vname <> "free" - -> - let name = Functions.RTL.mk_gen_name vi.vname in - let new_vi = Cil.makeGlobalVar name vi.vtype in - Cil_datatype.Varinfo.Hashtbl.add fct_tbl vi new_vi; - Cil.DoChildrenPost - (fun l -> match l with - | [ GFunDecl(_, vi, _) | GFun({ svar = vi }, _) as g ] - -> - let kf = Extlib.the self#current_kf in - (match g with - | GFunDecl _ -> - if not (Kernel_function.is_definition kf) then - Options.warning - "@[annotating undefined function `%a':@ \ - the generated program may miss memory instrumentation@ \ - if there are memory-related annotations.@]" - Printer.pp_varinfo vi - | GFun _ -> () - | _ -> assert false); - let tmp = vi.vname in - if tmp = "main" then begin - (* the new function becomes the new main: *) - (* 1. swap the name of both functions *) - vi.vname <- new_vi.vname; - new_vi.vname <- tmp; - (* 2. force recomputation of the entry point if necessary *) - if Kernel.MainFunction.get () = tmp then begin - let selection = - State_selection.with_dependencies Kernel.MainFunction.self - in - Project.clear ~selection () - end - (* 3. recompute what is necessary in [Globals.Functions]: - done in [dup_global] *) - end; - let new_g, new_decl = - dup_global - loc - self#get_filling_actions - (Annotations.funspec ~populate:false kf) - self#behavior - sound_verdict_vi - kf - vi - new_vi - in - (* postpone the introduction of the new function definition to the - end *) - new_definitions <- new_g :: new_definitions; - (* put the declaration before the original function in order to - solve issue with recursive functions *) - [ new_decl; g ] - | _ -> assert false) - - | _ -> + method !videntified_predicate _ = + (* when entering a new annotation, clear the context to keep it small: + anyway, no sharing is possible between two identified predicates *) + Misc.Id_term.Hashtbl.clear terms; Cil.DoChildren - method !vfile f = - Cil.DoChildrenPost - (fun _ -> - match new_definitions with - | [] -> f - | _ :: _ -> - (* add the generated definitions of libc at the end of - [new_definitions]. This way, we are sure that they have access to - all of it (in particular, the memory model, GMP and the soundness - variable). Also add the [__e_acsl_sound_verdict] variable at the - beginning *) - let new_globals = - GVarDecl(sound_verdict_vi, Cil_datatype.Location.unknown) - :: f.globals - @ new_definitions - in - f.globals <- new_globals; - f) - - initializer - reset () - -end + end in + ignore (Visitor.visitFramacKf o kf) + +(* the variable [sound_verdict] belongs to the E-ACSL's RTL and indicates + whether the verdict emitted by E-ACSL is sound. It needs to be visible at + that point because it is set in all function duplicates (see + [dup_fundec]). *) +let sound_verdict_vi = + lazy + (let name = Functions.RTL.mk_api_name "sound_verdict" in + let vi = Cil.makeGlobalVar name Cil.intType in + vi.vstorage <- Extern; + vi.vreferenced <- true; + vi) + +let is_variadic_function vi = match Cil.unrollType vi.vtype with + | TFun(_, _, variadic, _) -> variadic + | _ -> false + +(* set of functions that must never be duplicated *) +let unduplicable_functions = + let white_list = + [ "__builtin_va_arg"; + "__builtin_va_end"; + "__builtin_va_start"; + "__builtin_va_copy"; + (* *alloc and free should not be duplicated. *) + (* [TODO:] it preserves the former behavior. Should be modified latter by + checking only preconditions for any libc functions *) + "malloc"; + "calloc"; + "realloc"; + "free" ] + in + List.fold_left + (fun acc s -> Datatype.String.Set.add s acc) + Datatype.String.Set.empty + white_list + +let must_duplicate kf vi = + (* it is not already duplicated *) + not (Dup_functions.mem vi) + && (* it is duplicable *) + not (Datatype.String.Set.mem vi.vname unduplicable_functions) + && (* it is not a variadic function *) + not (is_variadic_function vi) + && (* it is not a built-in *) + not (Misc.is_fc_or_compiler_builtin vi) + && + ((* either explicitely listed as to be not instrumented *) + not (Functions.instrument kf) + || + (* or: *) + ((* it has a function contract *) + not (Cil.is_empty_funspec (Annotations.funspec ~populate:false kf)) + && (* its annotations must be monitored *) + Functions.check kf)) + +let prepare_global (globals, new_defs) = function + | GFunDecl(_, vi, loc) | GFun({ svar = vi }, loc) as g -> + let kf = try Globals.Functions.get vi with Not_found -> assert false in + if must_duplicate kf vi then begin + let new_vi = Dup_functions.generate_vi vi in + if Kernel_function.is_definition kf then + prepare_fundec kf + else + (* TODO: this warning could be more precise if emitted during code + generation; see also E-ACSL issue #85 about partial verdicts *) + Options.warning + "@[annotating undefined function `%a':@ \ + the generated program may miss memory instrumentation@ \ + if there are memory-related annotations.@]" + Printer.pp_varinfo vi; + let tmp = vi.vname in + if Datatype.String.equal tmp "main" then begin + (* the new function becomes the new main: *) + (* 1. swap the name of both functions *) + vi.vname <- new_vi.vname; + new_vi.vname <- tmp; + (* 2. force recomputation of the entry point if necessary *) + if Kernel.MainFunction.get () = tmp then begin + let selection = + State_selection.with_dependencies Kernel.MainFunction.self + in + Project.clear ~selection () + end + end; + let new_def, new_decl = + dup_global + loc + (Annotations.funspec ~populate:false kf) + (Lazy.force sound_verdict_vi) + kf + vi + new_vi + in + (* postpone the introduction of the new function definition *) + let new_defs = new_def :: new_defs in + (* put the declaration before the original function in order to solve + issues with recursive functions *) + g :: new_decl :: globals, new_defs + end else begin (* not duplicated *) + prepare_fundec kf; + g :: globals, new_defs + end + | g -> + (* nothing to do for globals that are not functions *) + g :: globals, new_defs + +let prepare_file file = + Dup_functions.reset (); + let rev_globals, new_defs = + List.fold_left prepare_global ([], []) file.globals + in + match new_defs with + | [] -> () + | _ :: _ -> + (* insert the new_definitions at the end and reverse back the globals *) + let globals = List.fold_left (fun acc g -> g :: acc) new_defs rev_globals in + (* insert [__e_acsl_sound_verdict] at the beginning *) + let sg = GVarDecl(Lazy.force sound_verdict_vi, Location.unknown) in + file.globals <- sg :: globals let prepare () = Options.feedback ~level:2 "prepare AST for E-ACSL transformations"; - Visitor.visitFramacFile (new prepare_visitor) (Ast.get ()); - Queue.iter (fun f -> f ()) actions; + prepare_file (Ast.get ()); Ast.mark_as_grown () (* -- GitLab