From e76ace79997e451fc6af52b5ee3a80c2847545b4 Mon Sep 17 00:00:00 2001 From: Basile Desloges <basile.desloges@cea.fr> Date: Tue, 19 Jan 2021 11:58:45 +0100 Subject: [PATCH] [eacsl] Merge the specifications from the RTL functions with the corresponding functions from the user's project --- .../e-acsl/src/project_initializer/rtl.ml | 316 ++++++++++++++++-- 1 file changed, 283 insertions(+), 33 deletions(-) diff --git a/src/plugins/e-acsl/src/project_initializer/rtl.ml b/src/plugins/e-acsl/src/project_initializer/rtl.ml index 07be50d1a64..8c96f04f908 100644 --- a/src/plugins/e-acsl/src/project_initializer/rtl.ml +++ b/src/plugins/e-acsl/src/project_initializer/rtl.ml @@ -100,6 +100,71 @@ end = struct *) end +(* Internal module to store the associations between symbols in the RTL project + and their corresponding symbols in the user's project. *) +module Assocs: sig + val clear: unit -> unit + (** Clear stored associations. *) + + val add_kf: global -> kernel_function -> unit + (** Add an association between a global in the RTL project and the + corresponding kernel function in the user's project. *) + + val mem_kf: global -> bool + (** Returns true if the given global from the RTL project has a corresponding + kernel function in the user's project. *) + + val get_kf: global -> kernel_function + (** Returns the corresponding kernel function from the user's project for the + given global in the RTL project. *) + + val add_vi: varinfo -> varinfo -> unit + (** [add_vi rtl_vi user_vi] adds an association between a [varinfo] in the + RTL project and the corresponding [varinfo] in the user's project. *) + + val find_vi: varinfo -> varinfo + (** [find_vi rtl_vi] returns the corresponding [varinfo] from the user's + project for the given [varinfo] in the RTL project if it exists, or raise + [Not_found] otherwise. *) + + val add_li: logic_info -> logic_info -> unit + (** [add_li rtl_li user_li] adds an association between a [logic_info] in the + RTL project and the corresponding [logic_info] in the user's project. *) + + val find_li: logic_info -> logic_info + (** [find_li rtl_li] returns the corresponding [logic_info] from the user's + project for the given [logic_info] in the RTL project if it exists, + or raise [Not_found] otherwise. *) + +end = struct + let kfs: kernel_function Global.Hashtbl.t = Global.Hashtbl.create 17 + let vars: varinfo Varinfo.Hashtbl.t = Varinfo.Hashtbl.create 17 + let logic_vars: logic_info Logic_info.Hashtbl.t = Logic_info.Hashtbl.create 17 + + let clear () = + Global.Hashtbl.clear kfs; + Varinfo.Hashtbl.clear vars; + Logic_info.Hashtbl.clear logic_vars + + let add_kf rtl_g user_kf = Global.Hashtbl.replace kfs rtl_g user_kf + let mem_kf rtl_g = Global.Hashtbl.mem kfs rtl_g + let get_kf rtl_g = + try + Global.Hashtbl.find kfs rtl_g + with Not_found -> + Options.fatal + "Unable to find user kf corresponding to %a. Assocs.mem_kf should be \ + used before calling Assocs.get_kf" + Printer.pp_global rtl_g + + let add_vi rtl_vi user_vi = Varinfo.Hashtbl.replace vars rtl_vi user_vi + let find_vi rtl_vi = Varinfo.Hashtbl.find vars rtl_vi + + let add_li rtl_li user_li = Logic_info.Hashtbl.replace logic_vars rtl_li user_li + let find_li rtl_li = Logic_info.Hashtbl.find logic_vars rtl_li +end + + (* NOTE: do not use [Mergecil.merge] since all [ast]'s symbols must be kept unchanged: so do it ourselves. Thanksfully, we merge in a particular setting because we know what the RTL is. Therefore merging is far from being @@ -109,11 +174,15 @@ end tables. @return the list of globals to be inserted into [ast], in reverse order. *) let lookup_rtl_globals rtl_ast = - (* [do_it ~add mem acc l g] checks whether the global does exist in the user's - AST. If not, add it to the corresponding symbol table(s). *) - let rec do_it ?(add=fun _g -> ()) mem acc l g = - if mem g then do_globals (g :: acc) l - else begin + (* [do_it ~add ~assoc mem acc l g] checks whether the global does exist in the + user's AST. If not, add it to the corresponding symbol table(s). If yes, + save the association between the global in the user's AST and the global in + the RTL. *) + let rec do_it ?(add=fun _g -> ()) ?(assoc=fun _g -> ()) mem acc l g = + if mem g then begin + assoc g; + do_globals (g :: acc) l + end else begin add g; Symbols.add_global g; do_globals (g :: acc) l @@ -134,8 +203,11 @@ let lookup_rtl_globals rtl_ast = do_ty acc l g Logic_typing.Enum ei.ename | GVarDecl(vi, (pos, _)) | GVar(vi, _, (pos, _)) as g :: l -> let tunit = Translation_unit pos.Filepath.pos_path in + let get_old_vi_opt vi = + Globals.Syntactic_search.find_in_scope vi.vorig_name tunit + in let mem _g = - let g = Globals.Syntactic_search.find_in_scope vi.vorig_name tunit in + let g = get_old_vi_opt vi in Option.is_some g in let add g = @@ -145,16 +217,95 @@ let lookup_rtl_globals rtl_ast = | GVar(_, ii, _) -> Globals.Vars.add vi ii | _ -> assert false in - do_it ~add mem acc l g + let assoc _g = + (* [assoc] is called if [mem] returns true, so the option returned by + [get_old_vi_opt] is [Some] by construction. *) + let old_vi = get_old_vi_opt vi in + Assocs.add_vi vi (Option.get old_vi) + in + do_it ~add ~assoc mem acc l g | GFunDecl(_, vi, _loc) | GFun({ svar = vi }, _loc) as g :: l -> let add _g = Symbols.add_vi vi.vname vi; (* functions will be registered in kernel's table after substitution of RTL's symbols inside them *) in - do_it ~add (fun _ -> Globals.Functions.mem_name vi.vname) acc l g + let assoc g = + let kf = Globals.Functions.find_by_name vi.vname in + Assocs.add_kf g kf + in + do_it ~add ~assoc (fun _ -> Globals.Functions.mem_name vi.vname) acc l g + | GAnnot (Daxiomatic (name, galist, _, _), _) as g :: l -> + (* processing axiomatics *) + let fun_or_preds = + (* extract the functions and predicates from the axiomatic *) + List.filter_map + (fun ga -> + match ga with + | Dfun_or_pred (li, _) -> Some li + | _ -> None) + galist + in + let mem _ = + (* check if some function or predicate of the axiomatic [exists] in the + user's project, if all the functions and predicates are in the user's + project ([forall]), and store which function and predicate are in the + user's project in [conflicting_lis]. *) + let exists, forall, conflicting_lis = + List.fold_left + (fun (exists, forall, conflicting_lis) li -> + let lv_name = li.l_var_info.lv_name in + let li_exists = Logic_env.Logic_info.mem lv_name in + let conflicting_lis = + if li_exists then li :: conflicting_lis else conflicting_lis + in + exists || li_exists, forall && li_exists, conflicting_lis) + (false, true, []) + fun_or_preds + in + (* The axiomatic is considered "in the user's project" if and only if + all its functions and predicates are in the user's project. If only + some of them are in it then it is an error, and if none of them are + then the axiomatic is not in the user's project. *) + if exists && not forall then + Options.abort + "@[The following logic functions or predicates@ \ + are in conflict with logic functions or predicates from the@ \ + axiomatic '%s' in the E-ACSL runtime library, please rename@ \ + them:@ %a@]" + name + (Pretty_utils.pp_list + ~sep:",@ " + Printer.pp_logic_info) + conflicting_lis + else + forall + in + let assoc _g = + List.iter + (fun li -> + let lv_name = li.l_var_info.lv_name in + let old_lis = Logic_env.Logic_info.find lv_name in + let old_li = + try + List.find + (fun old_li -> Logic_utils.is_same_logic_info li old_li) + old_lis + with Not_found -> + Options.fatal + "Unable to find the logic_info in the user's AST \ + corresponding to the logic_info %a in the RTL's AST. This \ + most likely comes from a mismatch between the axiomatic %s \ + in the RTL that is supposed to mirror one from the stdlib." + Printer.pp_logic_info li + name + in + Assocs.add_li li old_li) + fun_or_preds + in + do_it ~assoc mem acc l g | GAnnot _ :: l -> - (* ignoring annotations from the AST *) + (* ignoring other annotations from the AST *) do_globals acc l | GPragma _ as g :: l -> do_it Symbols.mem_global acc l g @@ -163,34 +314,86 @@ let lookup_rtl_globals rtl_ast = in do_globals [] rtl_ast.globals +(** [get_kf vi] returns the kernel function corresponding to the given varinfo + in the current project. The varinfo must correspond to a kernel function. *) +let get_kf vi = + try + Globals.Functions.get vi + with Not_found -> + Options.fatal "unknown function %s in project %a" + vi.vname + Project.pretty (Project.current ()) + +(** [get_formals_and_spec prj vi] returns a couple with the formals and function + specification of the function corresponding to the varinfo [vi] in the + project [prj]. The varinfo must correspond to a function of the given + project. *) +let get_formals_and_spec prj vi = + let selection = + State_selection.of_list + [ Globals.Functions.self; Annotations.funspec_state ] + in + Project.on + prj + ~selection + (fun vi -> + let kf = get_kf vi in + Kernel_function.get_formals kf, + Annotations.funspec ~populate:false kf) + vi + +(** Visitor to replace [varinfo] and [logic_info] leaves of an AST with + corresponding values from the [Assocs] tables. + If the visitor is created with the [formals] associating the [varinfo] of + the RTL formals with the [varinfo] of the user formals, then replace the + corresponding [varinfo] leaves. *) +class vis_replace_leaves ?formals () = + object + (* Since we are already working on a copied project, we can use an inplace + visitor. *) + inherit Visitor.frama_c_inplace + + val formals: varinfo Varinfo.Hashtbl.t = + match formals with + | None -> Varinfo.Hashtbl.create 7 + | Some formals -> formals + + (* Since the RTL project will be deleted at the end of the AST + merge, we can temporarily violate the Frama-C invariant that says + that a varinfo or logic_info must be in a single project and directly + replace the varinfo/logic_info of the RTL in the specification with the + logic_info/varinfo of the user's AST. *) + + method! vlogic_info_use rtl_li = + try + (* Replace the logic_info if it is a logic_info from the RTL project. *) + let user_li = Assocs.find_li rtl_li in + Cil.ChangeTo user_li + with Not_found -> + Cil.SkipChildren + + method! vvrbl rtl_vi = + try + (* Replace the varinfo if it is a global from the RTL project.*) + let user_vi = Assocs.find_vi rtl_vi in + Cil.ChangeTo user_vi + with Not_found -> + try + (* Replace the varinfo if it is a formal. *) + let user_vi = Varinfo.Hashtbl.find formals rtl_vi in + Cil.ChangeTo user_vi + with Not_found -> + (* Otherwise do nothing. *) + Cil.SkipChildren + end + (* [substitute_rtl_symbols] registers the correct symbols for RTL's functions *) let substitute_rtl_symbols rtl_prj = function | GVarDecl _ | GVar _ as g -> g | GFunDecl(_, vi, loc) | GFun({ svar = vi }, loc) as g -> - let get_kf vi = - try - Globals.Functions.get vi - with Not_found -> - Options.fatal "unknown function %s in project %a" - vi.vname - Project.pretty (Project.current ()) - in - let selection = - State_selection.of_list - [ Globals.Functions.self; Annotations.funspec_state ] - in (* get the RTL's formals and spec *) - let formals, spec = - Project.on - rtl_prj - ~selection - (fun vi -> - let kf = get_kf vi in - Kernel_function.get_formals kf, - Annotations.funspec ~populate:false kf) - vi - in + let formals, spec = get_formals_and_spec rtl_prj vi in (match g with | GFunDecl _ -> Globals.Functions.replace_by_declaration spec vi loc @@ -209,10 +412,47 @@ let substitute_rtl_symbols rtl_prj = function Annotations.register_funspec ~emitter:Options.emitter kf; Symbols.add_kf kf; g + | GAnnot (Daxiomatic _ as ga, loc) -> + let vis_replace_leaves = new vis_replace_leaves () in + let ga = Visitor.visitFramacAnnotation vis_replace_leaves ga in + Annotations.add_global Options.emitter ga; + GAnnot (ga, loc) | GType _ | GCompTag _ | GCompTagDecl _ | GEnumTag _ | GEnumTagDecl _ | GAnnot _ | GAsm _ | GPragma _ | GText _ -> assert false +(* [merge_rtl_spec rtl_prj rtl_global] adds the spec of the given global + function from the RTL project to the corresponding function in the current + project. Before adding the spec, the varinfos of the global variables and + formals in the spec are replaced with their corresponding varinfos in the + current project. *) +let merge_rtl_spec rtl_prj rtl_global = + match rtl_global with + | GFunDecl(_, vi, _loc) | GFun({ svar = vi }, _loc) as g -> + (* get the RTL's formals and spec *) + let rtl_formals, spec = get_formals_and_spec rtl_prj vi in + (* if the spec is not empty, then merge it with the user's spec *) + if not (Cil.is_empty_funspec spec) then begin + (* get the user's kf *) + let user_kf = Assocs.get_kf g in + (* get the user's formals *) + let user_formals = Kernel_function.get_formals user_kf in + (* replace all the formals and global variables in the spec with the + formals and globals from the user's project. *) + let formals_assoc: varinfo Varinfo.Hashtbl.t = Varinfo.Hashtbl.create 7 in + List.iter2 + (fun rtl_vi user_vi -> Varinfo.Hashtbl.add formals_assoc rtl_vi user_vi) + rtl_formals + user_formals; + let vis_replace_formals = new vis_replace_leaves ~formals:formals_assoc () in + let spec = Visitor.visitFramacFunspec vis_replace_formals spec in + (* Add the updated spec to the user's kf *) + Annotations.add_spec Options.emitter user_kf spec + end + | GVarDecl _ | GVar _ | GType _ | GCompTag _ | GCompTagDecl _ | GEnumTag _ + | GEnumTagDecl _ | GAnnot _ | GAsm _ | GPragma _ | GText _ -> + assert false + (* [insert_rtl_globals] adds the rtl symbols into the user's AST *) let insert_rtl_globals rtl_prj rtl_globals ast = let add_ty acc old_g new_g = @@ -238,12 +478,21 @@ let insert_rtl_globals rtl_prj rtl_globals ast = only declared *) let acc = add_ty acc g (GEnumTagDecl(ei, loc)) in add acc l - | GVarDecl _ | GVar _ | GFunDecl _ | GFun _ as g :: l -> + | GVarDecl _ | GVar _ | GFunDecl _ | GFun _ + | GAnnot (Daxiomatic _, _) as g :: l -> let acc = if Symbols.mem_global g then + (* The symbol is in the RTL project, but not in the user's project. + Perform the symbols substitution and add the global to the user's + project. *) let g = substitute_rtl_symbols rtl_prj g in g :: acc - else + else if Assocs.mem_kf g then begin + (* The symbol is in the RTL project and associated with a kf in the + user's project. Merge both contracts. *) + merge_rtl_spec rtl_prj g; + acc + end else acc in add acc l @@ -261,6 +510,7 @@ let link rtl_prj = let rtl_globals = lookup_rtl_globals rtl_ast in (* Symbols.debug ();*) insert_rtl_globals rtl_prj rtl_globals ast; + Assocs.clear (); Ast.mark_as_grown () (* -- GitLab