Skip to content

Commit 57850dc

Browse files
authored
Merge pull request #742 from goblint/apron_track_address
Apron: Track relational information for variables that have their address taken
2 parents 713f2a1 + fa8113e commit 57850dc

24 files changed

+646
-118
lines changed

.github/workflows/locked.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,9 @@ jobs:
4141
run: ./make.sh headers testci
4242

4343
- name: Test apron regression # skipped by default but CI has apron, so explicitly test group (which ignores skipping -- it's now a feature!)
44-
run: ruby scripts/update_suite.rb group apron -s;
45-
44+
run: |
45+
ruby scripts/update_suite.rb group apron -s
46+
ruby scripts/update_suite.rb group apron2 -s
4647
- name: Test apron octagon regression # skipped by default but CI has apron, so explicitly test group (which ignores skipping -- it's now a feature!)
4748
run: ruby scripts/update_suite.rb group octagon -s
4849

.github/workflows/unlocked.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,9 @@ jobs:
5858

5959
- name: Test apron regression # skipped by default but CI has apron, so explicitly test group (which ignores skipping -- it's now a feature!)
6060
if: ${{ matrix.apron }}
61-
run: ruby scripts/update_suite.rb group apron -s
61+
run: |
62+
ruby scripts/update_suite.rb group apron -s
63+
ruby scripts/update_suite.rb group apron2 -s
6264
6365
- name: Test apron octagon regression # skipped by default but CI has apron, so explicitly test group (which ignores skipping -- it's now a feature!)
6466
if: ${{ matrix.apron }}

src/analyses/apron/apronAnalysis.apron.ml

Lines changed: 132 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)