diff --git a/src/kernel_internals/typing/cabs2cil.ml b/src/kernel_internals/typing/cabs2cil.ml index 845919d8d1d0f03e82e61fb3fcc0d2d8fc5f221a..eb3177307c5d71e8f9037b17f89cb6f1e121fded 100644 --- a/src/kernel_internals/typing/cabs2cil.ml +++ b/src/kernel_internals/typing/cabs2cil.ml @@ -841,6 +841,21 @@ let env : (string, envdata * location) H.t = H.create 307 (* We also keep a global environment. This is always a subset of the env *) let genv : (string, envdata * location) H.t = H.create 307 +let label_env = Datatype.String.Hashtbl.create 307 + +let add_label_env lab = + let add_if_absent v (d,_) map = + match d with + | EnvVar vi when not (Datatype.String.Map.mem v map) -> + Datatype.String.Map.add v vi map + | _ -> map + in + let lab_env = H.fold add_if_absent env Datatype.String.Map.empty in + Datatype.String.Hashtbl.add label_env lab lab_env + +let remove_label_env lab = + Datatype.String.Hashtbl.remove label_env lab + (* In the scope we keep the original name, so we can remove them from the * hash table easily *) type undoScope = @@ -1021,6 +1036,7 @@ let constrExprId = ref 0 let startFile () = + Datatype.String.Hashtbl.clear label_env; H.clear env; H.clear genv; H.clear alphaTable; @@ -1503,13 +1519,13 @@ struct try ref (H.find labels s) with Not_found when List.mem s !label_current -> - let my_ref = - ref - (mkEmptyStmt - (* just a placeholder that will never be used. no need to - check for ghost status here. *) - ~ghost:false ~valid_sid ~loc:(cabslu "_find_label") ()) + (* just a placeholder that will never be used. no need to + check for ghost status here. *) + let my_stmt = + mkEmptyStmt ~ghost:false ~valid_sid ~loc:(cabslu "_find_label") () in + my_stmt.labels <- [Label(s,cabslu "_find_label",true)]; + let my_ref = ref my_stmt in addGoto s my_ref; my_ref end @@ -3725,9 +3741,27 @@ struct let anonCompFieldName = anonCompFieldName let conditionalConversion = logicConditionalConversion let find_macro _ = raise Not_found - let find_var x = match H.find env x with - | EnvVar vi, _ -> cvar_to_lvar vi - | _ -> raise Not_found + let find_var ?label ~var = + let find_from_curr_env test = + match H.find env var with + | EnvVar vi, _ when test vi -> cvar_to_lvar vi + | _ -> raise Not_found + in + match label with + | None -> find_from_curr_env (fun _ -> true) + | Some "Here" | Some "Old" | Some "Post" -> + (* the last two labels can only be found in contracts and refer + to the pre/post state of the contracts: all local variables + in scope at current point are also in scope in the labels. *) + find_from_curr_env (fun _ -> true) + | Some "Pre" -> + find_from_curr_env (fun vi -> vi.vformal || vi.vglob) + | Some "Init" -> find_from_curr_env (fun vi -> vi.vglob) + | Some lab -> + cvar_to_lvar + (Datatype.String.Map.find var + (Datatype.String.Hashtbl.find label_env lab)) + let find_enum_tag x = match H.find env x with | EnvEnum item,_ -> dummy_exp (Const (CEnum item)), typeOf item.eival @@ -3770,6 +3804,8 @@ module Ltyping = Logic_typing.Make (C_logic_env) let startLoop iswhile = incr C_logic_env.nb_loop; + add_label_env "LoopEntry"; + add_label_env "LoopCurrent"; continues := (if iswhile then While (ref "") else NotWhile (ref "")) :: !continues; enter_break_env () @@ -3777,6 +3813,8 @@ let startLoop iswhile = let exitLoop () = decr C_logic_env.nb_loop; exit_break_env (); + remove_label_env "LoopEntry"; + remove_label_env "LoopCurrent"; match !continues with | [] -> Kernel.error ~once:true ~current:true "exit Loop not in a loop" | _ :: rest -> continues := rest @@ -9669,6 +9707,7 @@ and doStatement local_env (s : A.statement) : chunk = | A.LABEL (l, s, loc) -> let loc' = convLoc loc in CurrentLoc.set loc'; + add_label_env l; C_logic_env.add_current_label l; (* Lookup the label because it might have been locally defined *) let chunk = diff --git a/src/kernel_services/analysis/logic_interp.ml b/src/kernel_services/analysis/logic_interp.ml index 8a82bb7009c44cdb18b0358052dc123bf79527ad..e8af8bc2a58e2fde1a50c2f14031a506b46a3388 100644 --- a/src/kernel_services/analysis/logic_interp.ml +++ b/src/kernel_services/analysis/logic_interp.ml @@ -27,14 +27,43 @@ open Cil_datatype exception Error of Cil_types.location * string exception Unbound of string -let find_var kf x = +let find_var kf kinstr ?label ~var = let vi = - try Globals.Vars.find_from_astinfo x (VLocal kf) + try + let vi = Globals.Vars.find_from_astinfo var (VLocal kf) in + (match kinstr with + | Kglobal -> vi (* don't refine search: the Kglobal here + does not indicate the function contract, but merely + the fact that we do not have any information about + the targeted program point. Hence, no scope check + can be performed or we might reject many legitimate + terms and predicates. + *) + | Kstmt stmt -> + let scope = + match label with + | None | Some "Here" | Some "Post" | Some "Old" -> stmt + | Some "Pre" -> raise Not_found (* no local variable in scope. *) + | Some "Init" -> raise Not_found (* no local variable in scope. *) + | Some "LoopEntry" | Some "LoopCurrent" -> + if not (Kernel_function.stmt_in_loop kf stmt) then + Kernel.fatal + "Use of LoopEntry or LoopCurrent outside of a loop"; + Kernel_function.find_enclosing_loop kf stmt + | Some l -> + (try let s = Kernel_function.find_label kf l in !s + with Not_found -> + Kernel.fatal + "Use of label %s that does not exist in function %a" + l Kernel_function.pretty kf) + in + if Kernel_function.var_is_in_scope scope vi then vi + else raise Not_found) with Not_found -> try - Globals.Vars.find_from_astinfo x (VFormal kf) + Globals.Vars.find_from_astinfo var (VFormal kf) with Not_found -> - Globals.Vars.find_from_astinfo x VGlobal + Globals.Vars.find_from_astinfo var VGlobal in cvar_to_lvar vi @@ -46,18 +75,21 @@ let find_var kf x = module DefaultLT (X: sig val kf: Kernel_function.t - val in_loop: bool (* Only useful for code annotations *) + val kinstr: Cil_types.kinstr end) = Logic_typing.Make (struct let anonCompFieldName = Cabs2cil.anonCompFieldName let conditionalConversion = Cabs2cil.logicConditionalConversion - let is_loop () = X.in_loop + let is_loop () = + match X.kinstr with + | Kglobal -> false + | Kstmt s -> Kernel_function.stmt_in_loop X.kf s let find_macro _ = raise Not_found - let find_var x = find_var X.kf x + let find_var ?label ~var = find_var X.kf X.kinstr ?label ~var let find_enum_tag x = try @@ -118,7 +150,7 @@ let code_annot kf stmt s = sync_typedefs (); let module LT = DefaultLT(struct let kf = kf - let in_loop = Kernel_function.stmt_in_loop kf stmt + let kinstr = Kstmt stmt end) in let loc = Stmt.loc stmt in let pa = @@ -141,7 +173,7 @@ let term kf ?(loc=Location.unknown) ?(env=default_term_env ()) s = sync_typedefs (); let module LT = DefaultLT(struct let kf = kf - let in_loop = false (* unused *) + let kinstr = Kglobal end) in let pa_expr = Extlib.opt_map snd (Logic_lexer.lexpr (fst loc, s)) in let parse pa_expr = LT.term env pa_expr in @@ -156,7 +188,7 @@ let predicate kf ?(loc=Location.unknown) ?(env=default_term_env ()) s = sync_typedefs (); let module LT = DefaultLT(struct let kf = kf - let in_loop = false (* unused *) + let kinstr = Kglobal end) in let pa_expr = Extlib.opt_map snd (Logic_lexer.lexpr (fst loc, s)) in let parse pa_expr = LT.predicate env pa_expr in diff --git a/src/kernel_services/ast_data/kernel_function.ml b/src/kernel_services/ast_data/kernel_function.ml index 1d7d7d982b0cda0c31270cbb7c6692eb9d426f48..8e876e4d23bfee17053055c823884a4565903dd8 100644 --- a/src/kernel_services/ast_data/kernel_function.ml +++ b/src/kernel_services/ast_data/kernel_function.ml @@ -425,6 +425,12 @@ let find_syntactic_callsites kf = try CallSites.find table kf with Not_found -> [] +let var_is_in_scope stmt vi = + let blocks = find_all_enclosing_blocks stmt in + List.exists + (fun b -> List.exists (Cil_datatype.Varinfo.equal vi) b.blocals) + blocks + (* ************************************************************************* *) (** {2 Checkers} *) (* ************************************************************************* *) diff --git a/src/kernel_services/ast_data/kernel_function.mli b/src/kernel_services/ast_data/kernel_function.mli index d6700783c495fe773ecb3fc9af317b1732058340..18c1dac1287a83abf9be394c92e837802c8c22c9 100644 --- a/src/kernel_services/ast_data/kernel_function.mli +++ b/src/kernel_services/ast_data/kernel_function.mli @@ -127,6 +127,14 @@ val find_syntactic_callsites : t -> (t * stmt) list [stmt]. @since Carbon-20110201 *) +val var_is_in_scope: stmt -> varinfo -> bool + (** [var_is_in_scope kf stmt vi] returns [true] iff the local variable [vi] + is syntactically visible from statement [stmt] in function [kf]. Note + that on the contrary to {!Globals.Syntactic_search.find_in_scope}, the + variable is searched according to its [vid], not its [vorig_name]. + + @since Frama-C+dev *) + (* ************************************************************************* *) (** {2 Checkers} *) (* ************************************************************************* *) diff --git a/src/kernel_services/ast_queries/logic_typing.ml b/src/kernel_services/ast_queries/logic_typing.ml index 3696935c9266797f8316aa6d5b0fedc9d83bedd0..a5a205947e2da1f126523a65eacaee1c753ddcef 100644 --- a/src/kernel_services/ast_queries/logic_typing.ml +++ b/src/kernel_services/ast_queries/logic_typing.ml @@ -326,6 +326,27 @@ module Lenv = struct *) } + let string_of_current_label env = + Extlib.opt_bind ( + function + | FormalLabel _ -> None + | BuiltinLabel Init -> Some "Init" + | BuiltinLabel Pre -> Some "Pre" + | BuiltinLabel Old -> Some "Old" + | BuiltinLabel Post -> Some "Post" + | BuiltinLabel Here -> Some "Here" + | BuiltinLabel LoopCurrent -> Some "LoopCurrent" + | BuiltinLabel LoopEntry -> Some "LoopEntry" + | StmtLabel s -> + (match + Transitioning.List.find_opt + (function Label (_,_,b) -> b | _ -> false) !s.labels + with + | None -> None + | Some (Label (lab,_,_)) -> Some lab + | Some _ -> None)) + env.current_logic_label + let fresh_var env name kind typ = let name = let exists name = @@ -468,7 +489,7 @@ type typing_context = { anonCompFieldName : string; conditionalConversion : typ -> typ -> typ; find_macro : string -> lexpr; - find_var : string -> logic_var; + find_var : ?label:string -> var:string -> logic_var; find_enum_tag : string -> exp * typ; find_comp_field: compinfo -> string -> offset; find_type : type_namespace -> string -> typ; @@ -633,7 +654,7 @@ module Make val anonCompFieldName : string val conditionalConversion : typ -> typ -> typ val find_macro : string -> lexpr - val find_var : string -> logic_var + val find_var : ?label:string -> var:string -> logic_var val find_enum_tag : string -> exp * typ val find_comp_field: compinfo -> string -> offset val find_type : type_namespace -> string -> typ @@ -2512,7 +2533,8 @@ struct | _ -> old_val lv) with Not_found -> try - let info = ctxt.find_var x in + let label = Lenv.string_of_current_label env in + let info = ctxt.find_var ?label ~var:x in (match info.lv_origin with | Some lv -> check_current_label loc env; diff --git a/src/kernel_services/ast_queries/logic_typing.mli b/src/kernel_services/ast_queries/logic_typing.mli index c204d9df8e233d2970b951de6848ad4b04e0c0eb..eed2c7af32cd78be27c16b78726f20f7543bb883 100644 --- a/src/kernel_services/ast_queries/logic_typing.mli +++ b/src/kernel_services/ast_queries/logic_typing.mli @@ -97,7 +97,11 @@ type typing_context = { anonCompFieldName : string; conditionalConversion : typ -> typ -> typ; find_macro : string -> Logic_ptree.lexpr; - find_var : string -> logic_var; + find_var : ?label:string -> var:string -> logic_var; + (** the label argument is a C label (obeying the restrictions + of which label can be present in a \at). If present, the scope for + searching local C variables is the one of the statement with + the corresponding label. *) find_enum_tag : string -> exp * typ; find_comp_field: compinfo -> string -> offset; find_type : type_namespace -> string -> typ; @@ -246,7 +250,8 @@ module Make val anonCompFieldName : string val conditionalConversion : typ -> typ -> typ val find_macro : string -> Logic_ptree.lexpr - val find_var : string -> logic_var + val find_var : ?label:string -> var:string -> logic_var + (** see corresponding field in {!Logic_typing.typing_context}. *) val find_enum_tag : string -> exp * typ val find_type : type_namespace -> string -> typ val find_comp_field: compinfo -> string -> offset diff --git a/src/plugins/aorai/data_for_aorai.ml b/src/plugins/aorai/data_for_aorai.ml index b2933c2f6e1ffc0be435368090607402e25e9095..f5faca352f87dd495392aca4076b3f42c1f17b8d 100644 --- a/src/plugins/aorai/data_for_aorai.ml +++ b/src/plugins/aorai/data_for_aorai.ml @@ -652,7 +652,7 @@ struct let conditionalConversion = Cabs2cil.logicConditionalConversion let is_loop () = false let find_macro _ = raise Not_found - let find_var _ = raise Not_found + let find_var ?label:_ ~var:_ = raise Not_found let find_enum_tag _ = raise Not_found (*let find_comp_type ~kind:_ _ = raise Not_found*) let find_comp_field info s = diff --git a/tests/spec/at.c b/tests/spec/at.c index b18cc464836c8badd181b0f7591e6a0dcc26c74b..9be4a3153ebb6356c671010287cb6bc457da0f95 100644 --- a/tests/spec/at.c +++ b/tests/spec/at.c @@ -22,6 +22,35 @@ int f(int y) { return x; } +void test () { + int x = 0; + L1: { + int x = 1; + L2: + // assert below speaks about two distinct x. + /*@ assert \at(&x, L1) != \at(&x,L2); */ + x = 2; + } +} + +void ko (int z) { + L: { + int y = 0; + // assert below should not typecheck: y is not in scope at L (nor at Pre) + //@ assert KO: \at(y,L) == 0; + //@ assert KO: \at(y,Pre) == 0; + //@ assert KO: \at(z,Init) == 0; // at Init, only globals are in scope + //@ assert OK: \at (x,Init) == 0; + //@ assert OK: \at(z,Pre) == 0; + } + while (x>0) { + int i = 1; + x--; + //@ assert KO: \at(i,LoopCurrent) == 1; + //@ assert OK: \at(z,LoopCurrent) == \at(z,Pre); + } +} + /* Local Variables: compile-command: "PPCHOME=../.. LC_ALL=C make at" diff --git a/tests/spec/oracle/at.res.oracle b/tests/spec/oracle/at.res.oracle index 31284982e1e899a3ff33e9be9367f392707e2676..8194269ad908387250693fe8d3d6a20f7d49b366 100644 --- a/tests/spec/oracle/at.res.oracle +++ b/tests/spec/oracle/at.res.oracle @@ -1,4 +1,12 @@ [kernel] Parsing tests/spec/at.c (with preprocessing) +[kernel:annot-error] tests/spec/at.c:40: Warning: + unbound logic variable y. Ignoring code annotation +[kernel:annot-error] tests/spec/at.c:41: Warning: + unbound logic variable y. Ignoring code annotation +[kernel:annot-error] tests/spec/at.c:42: Warning: + unbound logic variable z. Ignoring code annotation +[kernel:annot-error] tests/spec/at.c:49: Warning: + unbound logic variable i. Ignoring code annotation /* Generated by Frama-C */ int x; /*@ @@ -28,4 +36,32 @@ int f(int y) return x; } +void test(void) +{ + int x_0 = 0; + L1: + { + int x_1 = 1; + L2: /*@ assert \at(&x_0,L1) ≢ \at(&x_1,L2); */ ; + x_1 = 2; + } + return; +} + +void ko(int z) +{ + L: + { + int y = 0; + /*@ assert OK: \at(x,Init) ≡ 0; */ ; + /*@ assert OK: \at(z,Pre) ≡ 0; */ ; + } + while (x > 0) { + int i = 1; + x --; + /*@ assert OK: \at(z,LoopCurrent) ≡ \at(z,Pre); */ ; + } + return; +} +