(**************************************************************************) (* *) (* This file is part of the Frama-C's MetACSL plug-in. *) (* *) (* Copyright (C) 2018-2025 *) (* CEA (Commissariat à l'énergie atomique et aux énergies *) (* alternatives) *) (* *) (* you can redistribute it and/or modify it under the terms of the GNU *) (* Lesser General Public License as published by the Free Software *) (* Foundation, version 2.1. *) (* *) (* It is distributed in the hope that it will be useful, *) (* but WITHOUT ANY WARRANTY; without even the implied warranty of *) (* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *) (* GNU Lesser General Public License for more details. *) (* *) (* See the GNU Lesser General Public License version 2.1 *) (* for more details (enclosed in the file LICENSE) *) (* *) (**************************************************************************) open Meta_options open Meta_utils open Logic_ptree open Logic_typing open Cil_types (* Gathered bindings *) let types = Str_Hashtbl.create 5 let setname base = base ^ "_set" let sizename base = base ^ "_size" let capname base = base ^ "_capacity" (* Replace \bound(name) by name_set[name_i] where name_i is a newly created * quantified logic variable, which is remembered in the 'quantifiers' table to * close the predicate at the end (see after_parse) *) let parse_bound tc loc quantifiers bn = match bn.lexpr_node with | PLvar bname -> let content_type = begin match Str_Hashtbl.find_opt types bname with | Some x -> x | None -> tc.error loc "The name '%s' is used but not bound anywhere" bname end in let tabvar = tc.find_var (setname bname) in let quantvar = begin match Str_Hashtbl.find_opt quantifiers bname with | Some x -> x | None -> let res = Cil_const.make_logic_var_quant (bname ^ "_i") Linteger in Str_Hashtbl.add quantifiers bname res; res end in let tlv = (TVar tabvar, TIndex (Logic_const.tvar quantvar, TNoOffset)) in Logic_const.term (TLval tlv) content_type | _ -> tc.error loc "Expecting a simple variable name for \\bound" (* Replace a \bind(val, name) by the pair associating the actual C variable to the name. Used afterwards at the end of the translation process. *) let parse_bind tc loc cvar_name gvar_name = let cvar = tc.find_var cvar_name in let bdg_type = begin match cvar.lv_type with | Ctype _ as lt -> lt | _ -> tc.error loc "Only C variables can be bound to names" end in begin match Str_Hashtbl.find_opt types gvar_name with | None -> Str_Hashtbl.add types gvar_name bdg_type | Some old_type -> if not @@ Logic_utils.is_same_type bdg_type old_type then tc.error loc "The name %s was previously bound with variables of type %a \ but now you are trying to bind it with a variable of type %a" gvar_name Printer.pp_logic_type old_type Printer.pp_logic_type bdg_type end; let cvar_term = Logic_const.tvar cvar in let name_term = Logic_const.term (TConst (LStr gvar_name)) (Ctype Cil_const.charPtrType) in Ext_terms [cvar_term; name_term] (* Called by the delayed parser for each predicate * quanthash is a hash table with a pair for each \bound(val, name) associating * 'name' with the quantified logic_variable used to replace bound. * The predicate is closed with universal quantification on each of these * variables, with a proper interval 0 <= quant < name_size *) let after_parse tc pred quanthash = let open Logic_const in let quantifiers = Str_Hashtbl.fold (fun _ -> List.cons) quanthash [] in let conditions = Str_Hashtbl.fold (fun name var conds -> let mainterm = tvar var in let sizeterm = tc.find_var (sizename name) |> tvar in let supzero = prel (Rle, tinteger 0, mainterm) in let infmax = prel (Rlt, mainterm, sizeterm) in supzero :: infmax :: conds ) quanthash [] in let with_cond = pimplies (pands conditions, pred) in pforall (quantifiers, with_cond) let ext_flags = ref None (* to_add maps sids to statements that must be inserted before the * corresponding statements *) let to_add = Stmt_Hashtbl.create 5 (* Toggles the inline visitor *) let visit_inline = ref false (* Add ghost code needed for bindings, i.e. declarations of name_set/size and * their modification when encountering \binds *) let add_ghost_code flags = ext_flags := Some flags; visit_inline := true; let f = Ast.get () in let unkloc = Cil_datatype.Location.unknown in (* Declare _set and _size global variables *) let make_global name typ = let base_type = Logic_utils.logicCType typ in (* The _set is either a fixed array or a pointer *) let ctype = match flags.static_bindings with | Some n -> Cil_const.mk_tarray base_type (Some (Cil.integer ~loc:unkloc n)) | None -> Cil_const.mk_tptr base_type in let var = Cil.makeGlobalVar (setname name) ctype in let svar = Cil.makeGlobalVar (sizename name) Cil_const.uintType in var.vghost <- true; svar.vghost <- true; let sinit = {init = Some (Cil.makeZeroInit ~loc:unkloc Cil_const.uintType)} in Globals.Vars.add_decl var; Globals.Vars.add svar sinit; let pre = [GVarDecl (var, unkloc); GVar (svar, sinit, unkloc)] in match flags.static_bindings with | Some _ -> pre | None -> (* For dynamic arrays, add a capacity variable *) let cvar = Cil.makeGlobalVar (capname name) Cil_const.uintType in cvar.vghost <- true; Globals.Vars.add cvar sinit; GVar (cvar, sinit, unkloc) :: pre in let globs = Str_Hashtbl.fold (fun a b c -> (make_global a b) @ c) types [] in f.globals <- globs @ f.globals; (* Visit the file, resetting to_add when encoutering a function (since sids * are unique only within a function) and inserting values of to_add in * correct blocks *) let changed_funcs = ref Fundec_Set.empty in let mark_changed f = changed_funcs := Fundec_Set.add f !changed_funcs in let vis = object (self) inherit Visitor.frama_c_inplace method! vblock _ = Cil.DoChildrenPost (fun block -> let rec aux = function | [] -> [] | s :: t -> let aux_stmt = find_hash_list Stmt_Hashtbl.find_opt to_add s in if aux_stmt <> [] then mark_changed (Option.get self#current_func); aux_stmt @ [s] @ (aux t) in block.bstmts <- aux block.bstmts; block ) method! vfunc _ = Stmt_Hashtbl.clear to_add; Cil.DoChildren end in Visitor.visitFramacFile vis f; if not (Fundec_Set.is_empty !changed_funcs) then begin let reinit_cfg f = Cfg.clearCFGinfo ~clear_id:false f; Cfg.cfgFun f in Fundec_Set.iter reinit_cfg !changed_funcs; Ast.mark_as_changed () end let search_global_var name = Globals.Vars.find_from_astinfo name Global (* Replace \binds by code adequately modifying _set and _size *) let make_static_modif_code name lvar = let unkloc = Cil_datatype.Location.unknown in let tabvar = search_global_var (setname name) in let sizevar = search_global_var (sizename name) in List.rev [ (let tabcell = (Var tabvar, Index (Cil.evar sizevar, NoOffset)) in let value = Option.get lvar.lv_origin |> Cil.evar in Cil.mkStmtOneInstr ~ghost:true (Set (tabcell, value, unkloc))); (let increxp = Cil.increm (Cil.evar sizevar) 1 in Cil.mkStmtOneInstr ~ghost:true (Set (Cil.var sizevar, increxp, unkloc))) ] (* Dynamic version *) let make_dynamic_modif_code name lvar = let unkloc = Cil_datatype.Location.unknown in let tabvar = search_global_var (setname name) in let sizevar = search_global_var (sizename name) in let capvar = search_global_var (capname name) in List.rev [ (let cond = Cil.mkBinOp ~loc:unkloc Gt (Cil.evar sizevar) (Cil.evar capvar) in let newsize = Cil.mkBinOp ~loc:unkloc Mult (Cil.integer ~loc:unkloc 2) (Cil.increm (Cil.evar sizevar) 1) in let realloc = Globals.Functions.find_by_name "realloc" |> Kernel_function.get_vi |> Cil.evar in let arg = Cil.(new_exp ~loc:unkloc (CastE (Cil_const.voidPtrType, evar tabvar))) in let incinstr = Call (None, realloc, [arg; newsize], unkloc) in let inc = Cil.mkStmtOneInstr ~ghost:true incinstr in Cil.mkStmt ~ghost:true (If (cond, Cil.mkBlock [inc], Cil.mkBlock [], unkloc))); (let addr = Cil.mkBinOp ~loc:unkloc PlusPI (Cil.evar tabvar) (Cil.evar sizevar) in let tabcell = Cil.mkMem ~addr ~off:NoOffset in let value = Option.get lvar.lv_origin |> Cil.evar in Cil.mkStmtOneInstr ~ghost:true (Set (tabcell, value, unkloc))); (let increxp = Cil.increm (Cil.evar sizevar) 1 in Cil.mkStmtOneInstr ~ghost:true (Set (Cil.var sizevar, increxp, unkloc))) ] let gf () = match !ext_flags with | None -> Meta_options.Self.fatal "uninitialized bindings" | Some { static_bindings = None } -> make_dynamic_modif_code | Some { static_bindings = Some _ } -> make_static_modif_code (* When encountering an ID, create the correct ghost stmt and fill to_add *) let process_imeta vis = if !visit_inline then function | Ext_terms [ { term_node = TLval(TVar lvar, TNoOffset) }; { term_node = TConst(LStr name) }] -> let s = Option.get vis#current_stmt in let gf = gf () in List.iter (fun (v : stmt) -> add_to_hash_list Stmt_Hashtbl.(find_opt, replace) to_add s v ) (gf name lvar); Cil.SkipChildren | _ -> Cil.SkipChildren else fun _ -> Cil.SkipChildren