@@ -58,12 +58,12 @@ struct
5858
5959 module VH = BatHashtbl. Make (Basetype. Variables )
6060
61- let read_globals_to_locals ask getg st e =
61+ let read_globals_to_locals ( ask :Queries.ask ) getg st e =
6262 let v_ins = VH. create 10 in
6363 let visitor = object
6464 inherit nopCilVisitor
6565 method! vvrbl (v: varinfo ) =
66- if v.vglob then (
66+ if v.vglob || ThreadEscape. has_escaped ask v then (
6767 let v_in =
6868 if VH. mem v_ins v then
6969 VH. find v_ins v
@@ -110,14 +110,14 @@ struct
110110 {st with apr = apr'}
111111 )
112112
113- let assign_to_global_wrapper ask getg sideg st lv f =
113+ let rec assign_to_global_wrapper ( ask :Queries.ask ) getg sideg st lv f =
114114 match lv with
115- (* Lvals which are numbers, have no offset and their address wasn't taken *)
116- (* This means that variables of which multiple copies may be reachable via pointers are also also excluded (they have their address taken) *)
117- (* and no special handling for them is required (https://github.com/goblint/analyzer/pull/310) *)
118115 | (Var v , NoOffset) when AD. varinfo_tracked v ->
119- if not v.vglob then
120- {st with apr = f st v}
116+ if not v.vglob && not (ThreadEscape. has_escaped ask v) then
117+ (if ask.f (Queries. IsMultiple v) then
118+ {st with apr = AD. join (f st v) st.apr}
119+ else
120+ {st with apr = f st v})
121121 else (
122122 let v_out = Goblintutil. create_var @@ makeVarinfo false (v.vname ^ " #out" ) v.vtype in (* temporary local g#out for global g *)
123123 let st = {st with apr = AD. add_vars st.apr [V. local v_out]} in (* add temporary g#out *)
@@ -127,6 +127,16 @@ struct
127127 let apr'' = AD. remove_vars st'.apr [V. local v_out] in (* remove temporary g#out *)
128128 {st' with apr = apr''}
129129 )
130+ | (Mem v , NoOffset) ->
131+ (let r = ask.f (Queries. MayPointTo v) in
132+ match r with
133+ | `Top ->
134+ st
135+ | `Lifted s ->
136+ let lvals = Queries.LS. elements r in
137+ let ass' = List. map (fun lv -> assign_to_global_wrapper ask getg sideg st (Lval.CilLval. to_lval lv) f) lvals in
138+ List. fold_right D. join ass' (D. bot () )
139+ )
130140 (* Ignoring all other assigns *)
131141 | _ ->
132142 st
@@ -145,17 +155,37 @@ struct
145155 apr
146156
147157
158+ let replace_deref_exps ask e =
159+ let rec inner e = match e with
160+ | Const x -> e
161+ | UnOp (unop , e , typ ) -> UnOp (unop, inner e, typ)
162+ | BinOp (binop , e1 , e2 , typ ) -> BinOp (binop, inner e1, inner e2, typ)
163+ | CastE (t ,e ) -> CastE (t, inner e)
164+ | Lval (Var v , off ) -> Lval (Var v, off)
165+ | Lval (Mem e , NoOffset) ->
166+ (match ask (Queries. MayPointTo e) with
167+ | a when not (Queries.LS. is_top a || Queries.LS. mem (dummyFunDec.svar, `NoOffset ) a) && (Queries.LS. cardinal a) = 1 ->
168+ let lval = Lval.CilLval. to_lval (Queries.LS. choose a) in
169+ Lval lval
170+ (* It would be possible to do better here, exploiting e.g. that the things pointed to are known to be equal *)
171+ (* see: https://github.com/goblint/analyzer/pull/742#discussion_r879099745 *)
172+ | _ -> Lval (Mem e, NoOffset ))
173+ | e -> e (* TODO: Potentially recurse further? *)
174+ in
175+ inner e
176+
148177 (* Basic transfer functions. *)
149178
150179 let assign ctx (lv :lval ) e =
151180 let st = ctx.local in
152181 if ! GU. global_initialization && e = MyCFG. unknown_exp then
153182 st (* ignore extern inits because there's no body before assign, so the apron env is empty... *)
154183 else (
155- if M. tracing then M. traceli " apron" " assign %a = %a\n " d_lval lv d_exp e;
184+ let simplified_e = replace_deref_exps ctx.ask e in
185+ if M. tracing then M. traceli " apron" " assign %a = %a (simplified to %a)\n " d_lval lv d_exp e d_exp simplified_e;
156186 let ask = Analyses. ask_of_ctx ctx in
157187 let r = assign_to_global_wrapper ask ctx.global ctx.sideg st lv (fun st v ->
158- assign_from_globals_wrapper ask ctx.global st e (fun apr' e' ->
188+ assign_from_globals_wrapper ask ctx.global st simplified_e (fun apr' e' ->
159189 if M. tracing then M. traceli " apron" " assign inner %a = %a (%a)\n " d_varinfo v d_exp e' d_plainexp e';
160190 if M. tracing then M. trace " apron" " st: %a\n " AD. pretty apr';
161191 let r = AD. assign_exp apr' (V. local v) e' in
@@ -181,7 +211,23 @@ struct
181211
182212 (* Function call transfer functions. *)
183213
214+ let any_local_reachable fundec reachable_from_args =
215+ let locals = fundec.sformals @ fundec.slocals in
216+ let locals_id = List. map (fun v -> v.vid) locals in
217+ Queries.LS. exists (fun (v' ,_ ) -> List. mem v'.vid locals_id && AD. varinfo_tracked v') reachable_from_args
218+
219+ let pass_to_callee fundec any_local_reachable var =
220+ (* TODO: currently, we pass all locals of the caller to the callee, provided one of them is reachbale to preserve relationality *)
221+ (* there should be smarter ways to do this, e.g. by keeping track of which values are written etc. ... *)
222+ (* Also, a local *)
223+ let vname = Var. to_string var in
224+ let locals = fundec.sformals @ fundec.slocals in
225+ match List. find_opt (fun v -> V. local_name v = vname) locals with
226+ | None -> true
227+ | Some v -> any_local_reachable
228+
184229 let enter ctx r f args =
230+ let fundec = Node. find_fundec ctx.node in
185231 let st = ctx.local in
186232 if M. tracing then M. tracel " combine" " apron enter f: %a\n " d_varinfo f.svar;
187233 if M. tracing then M. tracel " combine" " apron enter formals: %a\n " (d_list " ," d_varinfo) f.sformals;
@@ -191,6 +237,7 @@ struct
191237 |> List. filter (fun (x , _ ) -> AD. varinfo_tracked x)
192238 |> List. map (Tuple2. map1 V. arg)
193239 in
240+ let reachable_from_args = List. fold (fun ls e -> Queries.LS. join ls (ctx.ask (ReachableFrom e))) (Queries.LS. empty () ) args in
194241 let arg_vars = List. map fst arg_assigns in
195242 let new_apr = AD. add_vars st.apr arg_vars in
196243 (* AD.assign_exp_parallel_with new_oct arg_assigns; (* doesn't need to be parallel since exps aren't arg vars directly *) *)
@@ -202,9 +249,10 @@ struct
202249 )
203250 ) new_apr arg_assigns
204251 in
252+ let any_local_reachable = any_local_reachable fundec reachable_from_args in
205253 AD. remove_filter_with new_apr (fun var ->
206254 match V. find_metadata var with
207- | Some Local -> true (* remove caller locals *)
255+ | Some Local when not (pass_to_callee fundec any_local_reachable var) -> true (* remove caller locals provided they are unreachable *)
208256 | Some Arg when not (List. mem_cmp Var. compare var arg_vars) -> true (* remove caller args, but keep just added args *)
209257 | _ -> false (* keep everything else (just added args, globals, global privs) *)
210258 );
@@ -259,13 +307,16 @@ struct
259307
260308 let combine ctx r fe f args fc fun_st =
261309 let st = ctx.local in
310+ let reachable_from_args = List. fold (fun ls e -> Queries.LS. join ls (ctx.ask (ReachableFrom e))) (Queries.LS. empty () ) args in
311+ let fundec = Node. find_fundec ctx.node in
262312 if M. tracing then M. tracel " combine" " apron f: %a\n " d_varinfo f.svar;
263313 if M. tracing then M. tracel " combine" " apron formals: %a\n " (d_list " ," d_varinfo) f.sformals;
264314 if M. tracing then M. tracel " combine" " apron args: %a\n " (d_list " ," d_exp) args;
265315 let new_fun_apr = AD. add_vars fun_st.apr (AD. vars st.apr) in
266316 let arg_substitutes =
267317 GobList. combine_short f.sformals args (* TODO: is it right to ignore missing formals/args? *)
268- |> List. filter (fun (x , _ ) -> AD. varinfo_tracked x)
318+ (* Do not do replacement for actuals whose value may be modified after the call *)
319+ |> List. filter (fun (x , e ) -> AD. varinfo_tracked x && List. for_all (fun v -> not (Queries.LS. exists (fun (v' ,_ ) -> v'.vid = v.vid) reachable_from_args)) (Basetype.CilExp. get_vars e))
269320 |> List. map (Tuple2. map1 V. arg)
270321 in
271322 (* AD.substitute_exp_parallel_with new_fun_oct arg_substitutes; (* doesn't need to be parallel since exps aren't arg vars directly *) *)
@@ -278,14 +329,15 @@ struct
278329 )
279330 ) new_fun_apr arg_substitutes
280331 in
281- let arg_vars = List. map fst arg_substitutes in
332+ let any_local_reachable = any_local_reachable fundec reachable_from_args in
333+ let arg_vars = f.sformals |> List. filter (AD. varinfo_tracked) |> List. map V. arg in
282334 if M. tracing then M. tracel " combine" " apron remove vars: %a\n " (docList (fun v -> Pretty. text (Var. to_string v))) arg_vars;
283335 AD. remove_vars_with new_fun_apr arg_vars; (* fine to remove arg vars that also exist in caller because unify from new_apr adds them back with proper constraints *)
284336 let new_apr = AD. keep_filter st.apr (fun var ->
285337 match V. find_metadata var with
286- | Some Local -> true (* keep caller locals *)
338+ | Some Local when not (pass_to_callee fundec any_local_reachable var) -> true (* keep caller locals, provided they were not passed to the function *)
287339 | Some Arg -> true (* keep caller args *)
288- | _ -> false (* remove everything else (globals, global privs) *)
340+ | _ -> false (* remove everything else (globals, global privs, reachable things from the caller ) *)
289341 )
290342 in
291343 let unify_apr = AD. unify new_apr new_fun_apr in (* TODO: unify_with *)
@@ -306,14 +358,49 @@ struct
306358 else
307359 unify_st
308360
309- let special ctx r f args =
361+
362+ let invalidate_one ask ctx st lv =
363+ assign_to_global_wrapper ask ctx.global ctx.sideg st lv (fun st v ->
364+ let apr' = AD. forget_vars st.apr [V. local v] in
365+ assert_type_bounds apr' v (* re-establish type bounds after forget *)
366+ )
367+
368+
369+ (* Give the set of reachables from argument. *)
370+ let reachables (ask : Queries.ask ) es =
371+ let reachable e st =
372+ match st with
373+ | None -> None
374+ | Some st ->
375+ let vs = ask.f (Queries. ReachableFrom e) in
376+ if Queries.LS. is_top vs then
377+ None
378+ else
379+ Some (Queries.LS. join vs st)
380+ in
381+ List. fold_right reachable es (Some (Queries.LS. empty () ))
382+
383+
384+ let forget_reachable ctx st es =
310385 let ask = Analyses. ask_of_ctx ctx in
311- let invalidate_one st lv =
312- assign_to_global_wrapper ask ctx.global ctx.sideg st lv (fun st v ->
313- let apr' = AD. forget_vars st.apr [V. local v] in
314- assert_type_bounds apr' v (* re-establish type bounds after forget *)
315- )
386+ let rs =
387+ match reachables ask es with
388+ | None ->
389+ (* top reachable, so try to invalidate everything *)
390+ let fd = Node. find_fundec ctx.node in
391+ AD. vars st.apr
392+ |> List. filter_map (V. to_cil_varinfo fd)
393+ |> List. map Cil. var
394+ | Some rs ->
395+ Queries.LS. elements rs
396+ |> List. map Lval.CilLval. to_lval
316397 in
398+ List. fold_left (fun st lval ->
399+ invalidate_one ask ctx st lval
400+ ) st rs
401+
402+ let special ctx r f args =
403+ let ask = Analyses. ask_of_ctx ctx in
317404 let st = ctx.local in
318405 let desc = LibraryFunctions. find f in
319406 match desc.special args, f.vname with
@@ -323,40 +410,42 @@ struct
323410 | Unknown , "__goblint_commit" -> st
324411 | Unknown , "__goblint_assert" -> st
325412 | ThreadJoin { thread = id ; ret_var = retvar } , _ ->
326- (* nothing to invalidate as only arguments that have their AddrOf taken may be invalidated *)
327413 (
328- let st' = Priv. thread_join ask ctx.global id st in
414+ (* Forget value that thread return is assigned to *)
415+ let st' = forget_reachable ctx st [retvar] in
416+ let st' = Priv. thread_join ask ctx.global id st' in
329417 match r with
330- | Some lv -> invalidate_one st' lv
418+ | Some lv -> invalidate_one ask ctx st' lv
331419 | None -> st'
332420 )
333421 | _ , _ ->
334- let ask = Analyses. ask_of_ctx ctx in
335- let invalidate_one st lv =
336- assign_to_global_wrapper ask ctx.global ctx.sideg st lv (fun st v ->
337- let apr' = AD. forget_vars st.apr [V. local v] in
338- assert_type_bounds apr' v (* re-establish type bounds after forget *)
339- )
422+ let lvallist e =
423+ let s = ask.f (Queries. MayPointTo e) in
424+ match s with
425+ | `Top -> []
426+ | `Lifted _ -> List. map (Lval.CilLval. to_lval) (Queries.LS. elements s)
340427 in
341- (* nothing to do for args because only AddrOf arguments may be invalidated *)
342- let st' =
428+ let shallow_addrs = LibraryDesc.Accesses. find desc.accs { kind = Write ; deep = false } args in
429+ let deep_addrs = LibraryDesc.Accesses. find desc.accs { kind = Write ; deep = true } args in
430+ let deep_addrs =
343431 if List. mem LibraryDesc. InvalidateGlobals desc.attrs then (
344- let globals = foldGlobals ! Cilfacade. current_file (fun acc global ->
432+ foldGlobals ! Cilfacade. current_file (fun acc global ->
345433 match global with
346434 | GVar (vi , _ , _ ) when not (BaseUtil. is_static vi) ->
347- (Var vi, NoOffset ) :: acc
435+ mkAddrOf (Var vi, NoOffset ) :: acc
348436 (* TODO: what about GVarDecl? *)
349437 | _ -> acc
350- ) []
351- in
352- List. fold_left invalidate_one st globals
438+ ) deep_addrs
353439 )
354440 else
355- st
441+ deep_addrs
356442 in
443+ let st' = forget_reachable ctx st deep_addrs in
444+ let shallow_lvals = List. concat_map lvallist shallow_addrs in
445+ let st' = List. fold_left (invalidate_one ask ctx) st' shallow_lvals in
357446 (* invalidate lval if present *)
358447 match r with
359- | Some lv -> invalidate_one st' lv
448+ | Some lv -> invalidate_one ask ctx st' lv
360449 | None -> st'
361450
362451
@@ -392,9 +481,10 @@ struct
392481 let open Queries in
393482 let st = ctx.local in
394483 let eval_int e =
484+ let esimple = replace_deref_exps ctx.ask e in
395485 read_from_globals_wrapper
396486 (Analyses. ask_of_ctx ctx)
397- ctx.global st e
487+ ctx.global st esimple
398488 (fun apr' e' -> AD. eval_int apr' e')
399489 in
400490 match q with
@@ -461,6 +551,8 @@ struct
461551 (* No need to handle escape because escaped variables are always referenced but this analysis only considers unreferenced variables. *)
462552 | Events. EnterMultiThreaded ->
463553 Priv. enter_multithreaded (Analyses. ask_of_ctx ctx) ctx.global ctx.sideg st
554+ | Events. Escape escaped ->
555+ Priv. escape ctx.node (Analyses. ask_of_ctx ctx) ctx.global ctx.sideg st escaped
464556 | _ ->
465557 st
466558
0 commit comments