Skip to content

Commit 9f5560e

Browse files
authored
Merge pull request #1886 from goblint/arg-uncil-ambiguous
Fix ambiguous ARG uncilling crash by remembering and following CFG paths
2 parents 3b66ba0 + 708edac commit 9f5560e

17 files changed

+499
-122
lines changed

β€Žsrc/arg/argTools.mlβ€Ž

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -137,19 +137,6 @@ struct
137137
| Statement stmt -> Printf.sprintf "s%d(%d)[%s]" stmt.sid c_tag i_str
138138
| Function f -> Printf.sprintf "ret%d%s(%d)[%s]" f.svar.vid f.svar.vname c_tag i_str
139139
| FunctionEntry f -> Printf.sprintf "fun%d%s(%d)[%s]" f.svar.vid f.svar.vname c_tag i_str
140-
141-
(* TODO: less hacky way (without ask_indices) to move node *)
142-
let is_live (n, c, i) = not (Spec.D.is_bot (get (n, c)))
143-
let move_opt (n, c, i) to_n =
144-
match ask_indices (to_n, c) with
145-
| [] -> None
146-
| [to_i] ->
147-
let to_node = (to_n, c, to_i) in
148-
BatOption.filter is_live (Some to_node)
149-
| _ :: _ :: _ ->
150-
failwith "Node.move_opt: ambiguous moved index"
151-
let equal_node_context (n1, c1, i1) (n2, c2, i2) =
152-
EQSys.LVar.equal (n1, c1) (n2, c2)
153140
end
154141

155142
module NHT = BatHashtbl.Make (Node)

β€Žsrc/arg/myARG.mlβ€Ž

Lines changed: 58 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,19 @@ sig
1212
val context_id: t -> int
1313
val path_id: t -> int
1414
val to_string: t -> string
15-
16-
val move_opt: t -> MyCFG.node -> t option
17-
val equal_node_context: t -> t -> bool
1815
end
1916

2017
module type Edge =
2118
sig
22-
type t
19+
type t [@@deriving eq, ord]
2320

2421
val embed: MyCFG.edge -> t
2522
val to_string: t -> string
2623
end
2724

2825
module CFGEdge: Edge with type t = MyCFG.edge =
2926
struct
30-
type t = edge
27+
type t = Edge.t [@@deriving eq, ord]
3128

3229
let embed e = e
3330
let to_string e = GobPretty.sprint Edge.pretty_plain e
@@ -102,7 +99,7 @@ end
10299

103100
module InlineEdge: Edge with type t = inline_edge =
104101
struct
105-
type t = inline_edge
102+
type t = inline_edge [@@deriving eq, ord]
106103

107104
let embed e = CFGEdge e
108105
let to_string e = InlineEdgePrintable.show e
@@ -130,15 +127,6 @@ struct
130127
nl
131128
|> List.map Node.to_string
132129
|> String.concat "@"
133-
134-
let move_opt nl to_node =
135-
let open GobOption.Syntax in
136-
match nl with
137-
| [] -> None
138-
| n :: stack ->
139-
let+ to_n = Node.move_opt n to_node in
140-
to_n :: stack
141-
let equal_node_context _ _ = failwith "StackNode: equal_node_context"
142130
end
143131

144132
module Stack (Arg: S with module Edge = InlineEdge):
@@ -265,16 +253,19 @@ struct
265253
end
266254
end
267255

256+
type cfg_path = (MyCFG.edge * MyCFG.node) list
268257

269258
module type SIntra =
270259
sig
271-
val next: MyCFG.node -> (MyCFG.edge * MyCFG.node) list
260+
val next: MyCFG.node -> (MyCFG.edge * MyCFG.node * cfg_path list) list
261+
(** @return Also the original CFG paths corresponding to the step. *)
272262
end
273263

274264
module type SIntraOpt =
275265
sig
276266
include SIntra
277-
val next_opt: MyCFG.node -> ((MyCFG.edge * MyCFG.node) list) option
267+
val next_opt: MyCFG.node -> ((MyCFG.edge * MyCFG.node * cfg_path list) list) option
268+
(** @return Also the original CFG paths corresponding to the step. *)
278269
end
279270

280271
module CfgIntra (Cfg:CfgForward): SIntraOpt =
@@ -283,51 +274,38 @@ struct
283274
let open GobList.Syntax in
284275
let* (es, to_n) = Cfg.next node in
285276
let+ (_, e) = es in
286-
(e, to_n)
277+
(e, to_n, [[(e, to_n)]])
287278
let next_opt _ = None
288279
end
289280

290-
let partition_if_next if_next_n =
291-
(* TODO: refactor, check extra edges for error *)
292-
let test_next b = List.find (function
293-
| (Test (_, b'), _) when b = b' -> true
294-
| (_, _) -> false
295-
) if_next_n
281+
let cartesian_concat_paths (ps : cfg_path list) (qs : cfg_path list) : cfg_path list = List.concat_map (fun p -> List.map (fun q -> p @ q) qs) ps
282+
283+
let partition_if_next if_next =
284+
let (if_next_trues, if_next_falses) = List.partition (function
285+
| (Test (_, b), _, _) -> b
286+
| (_, _, _) -> failwith "partition_if_next: not Test edge"
287+
) if_next
296288
in
297-
(* assert (List.length if_next <= 2); *)
298-
match test_next true, test_next false with
299-
| (Test (e_true, true), if_true_next_n), (Test (e_false, false), if_false_next_n) when Basetype.CilExp.equal e_true e_false ->
300-
(e_true, if_true_next_n, if_false_next_n)
301-
| _, _ -> failwith "partition_if_next: bad branches"
289+
match if_next_trues, if_next_falses with
290+
| [(Test (e_true, true), if_true_next_n, if_true_next_ps)], [(Test (e_false, false), if_false_next_n, if_false_next_ps)] when Basetype.CilExp.equal e_true e_false ->
291+
(e_true, (if_true_next_n, if_true_next_ps), (if_false_next_n, if_false_next_ps))
292+
| _, _ ->
293+
(* This fails due to any of the following:
294+
- Either true or false branch is missing.
295+
- Either true or false branch has multiple different exps or nodes (same exp, branch and node should only occur once by construction/assumption).
296+
- True and false branch have different exps. *)
297+
failwith "partition_if_next: bad branches"
302298

303299
module UnCilLogicIntra (Arg: SIntraOpt): SIntraOpt =
304300
struct
305301
open Cil
306-
(* TODO: questionable (=) and (==) use here *)
307-
308-
let is_equiv_stmtkind sk1 sk2 = match sk1, sk2 with
309-
| Instr is1, Instr is2 -> GobList.equal (=) is1 is2
310-
| Return _, Return _ -> sk1 = sk2
311-
| _, _ -> false (* TODO: also consider others? not sure if they ever get duplicated *)
312-
let is_equiv_stmt s1 s2 = is_equiv_stmtkind s1.skind s2.skind (* TODO: also consider labels *)
313-
let is_equiv_node n1 n2 = match n1, n2 with
314-
| Statement s1, Statement s2 -> is_equiv_stmt s1 s2
315-
| _, _ -> false (* TODO: also consider FunctionEntry & Function? *)
316-
let is_equiv_edge e1 e2 = match e1, e2 with
317-
| Entry f1, Entry f2 -> f1 == f2 (* physical equality for fundec to avoid cycle *)
318-
| Ret (exp1, f1), Ret (exp2, f2) -> exp1 = exp2 && f1 == f2 (* physical equality for fundec to avoid cycle *)
319-
| _, _ -> e1 = e2
320-
let rec is_equiv_chain n1 n2 =
321-
Node.equal n1 n2 || (is_equiv_node n1 n2 && is_equiv_chain_next n1 n2)
322-
and is_equiv_chain_next n1 n2 = match Arg.next n1, Arg.next n2 with
323-
| [(e1, to_n1)], [(e2, to_n2)] ->
324-
is_equiv_edge e1 e2 && is_equiv_chain to_n1 to_n2
325-
| _, _-> false
326302

303+
let () =
304+
assert (not !Cabs2cil.allowDuplication) (* duplication makes it more annoying to detect cilling *)
327305

328306
let rec next_opt' n = match n with
329307
| Statement {sid; skind=If _; _} when GobConfig.get_bool "exp.arg.uncil" ->
330-
let (e, if_true_next_n, if_false_next_n) = partition_if_next (Arg.next n) in
308+
let (e, (if_true_next_n, if_true_next_ps), (if_false_next_n, if_false_next_ps)) = partition_if_next (Arg.next n) in
331309
(* avoid infinite recursion with sid <> sid2 in if_nondet_var *)
332310
(* TODO: why physical comparison if_false_next_n != n doesn't work? *)
333311
(* TODO: need to handle longer loops? *)
@@ -336,24 +314,24 @@ struct
336314
(* && *)
337315
| Statement {sid=sid2; skind=If _; _}, _ when sid <> sid2 && CilType.Location.equal loc (Node.location if_true_next_n) ->
338316
(* get e2 from edge because recursive next returns it there *)
339-
let (e2, if_true_next_true_next_n, if_true_next_false_next_n) = partition_if_next (next if_true_next_n) in
340-
if is_equiv_chain if_false_next_n if_true_next_false_next_n then
317+
let (e2, (if_true_next_true_next_n, if_true_next_true_next_ps), (if_true_next_false_next_n, if_true_next_false_next_ps)) = partition_if_next (next if_true_next_n) in
318+
if Node.equal if_false_next_n if_true_next_false_next_n then
341319
let exp = BinOp (LAnd, e, e2, intType) in
342320
Some [
343-
(Test (exp, true), if_true_next_true_next_n);
344-
(Test (exp, false), if_false_next_n)
321+
(Test (exp, true), if_true_next_true_next_n, cartesian_concat_paths if_true_next_ps if_true_next_true_next_ps);
322+
(Test (exp, false), if_true_next_false_next_n, if_false_next_ps @ cartesian_concat_paths if_true_next_ps if_true_next_false_next_ps) (* concat two different path families to same false node *)
345323
]
346324
else
347325
None
348326
(* || *)
349327
| _, Statement {sid=sid2; skind=If _; _} when sid <> sid2 && CilType.Location.equal loc (Node.location if_false_next_n) ->
350328
(* get e2 from edge because recursive next returns it there *)
351-
let (e2, if_false_next_true_next_n, if_false_next_false_next_n) = partition_if_next (next if_false_next_n) in
352-
if is_equiv_chain if_true_next_n if_false_next_true_next_n then
329+
let (e2, (if_false_next_true_next_n, if_false_next_true_next_ps), (if_false_next_false_next_n, if_false_next_false_next_ps)) = partition_if_next (next if_false_next_n) in
330+
if Node.equal if_true_next_n if_false_next_true_next_n then
353331
let exp = BinOp (LOr, e, e2, intType) in
354332
Some [
355-
(Test (exp, true), if_true_next_n);
356-
(Test (exp, false), if_false_next_false_next_n)
333+
(Test (exp, true), if_false_next_true_next_n, if_true_next_ps @ cartesian_concat_paths if_false_next_ps if_false_next_true_next_ps); (* concat two different path families to same true node *)
334+
(Test (exp, false), if_false_next_false_next_n, cartesian_concat_paths if_false_next_ps if_false_next_false_next_ps)
357335
]
358336
else
359337
None
@@ -381,14 +359,14 @@ struct
381359

382360
let next_opt' n = match n with
383361
| Statement {skind=If _; _} when GobConfig.get_bool "exp.arg.uncil" ->
384-
let (e_cond, if_true_next_n, if_false_next_n) = partition_if_next (Arg.next n) in
362+
let (e_cond, (if_true_next_n, if_true_next_ps), (if_false_next_n, if_false_next_ps)) = partition_if_next (Arg.next n) in
385363
let loc = Node.location n in
386364
if CilType.Location.equal (Node.location if_true_next_n) loc && CilType.Location.equal (Node.location if_false_next_n) loc then
387365
match Arg.next if_true_next_n, Arg.next if_false_next_n with
388-
| [(Assign (v_true, e_true), if_true_next_next_n)], [(Assign (v_false, e_false), if_false_next_next_n)] when v_true = v_false && Node.equal if_true_next_next_n if_false_next_next_n ->
366+
| [(Assign (v_true, e_true), if_true_next_next_n, if_true_next_next_ps)], [(Assign (v_false, e_false), if_false_next_next_n, if_false_next_next_ps)] when v_true = v_false && Node.equal if_true_next_next_n if_false_next_next_n ->
389367
let exp = ternary e_cond e_true e_false in
390368
Some [
391-
(Assign (v_true, exp), if_true_next_next_n)
369+
(Assign (v_true, exp), if_true_next_next_n, cartesian_concat_paths if_true_next_ps if_true_next_next_ps @ cartesian_concat_paths if_false_next_ps if_false_next_next_ps) (* concat two different path families with same variable to same node *)
392370
]
393371
| _, _ -> None
394372
else
@@ -407,14 +385,30 @@ module Intra (ArgIntra: SIntraOpt) (Arg: S):
407385
struct
408386
include Arg
409387

388+
(** Starting from ARG node [node], follow CFG path [p] to the resulting ARG node.
389+
Returns multiple ARG nodes if ARG contains path-sensitivity splits on the same CFG path. *)
390+
let rec follow node p =
391+
let open GobList.Syntax in
392+
match p with
393+
| [] -> [node]
394+
| (e, to_n) :: p' ->
395+
let* (_, node') = List.filter (fun (e', to_node) ->
396+
Edge.equal (Edge.embed e) e' && Node0.equal to_n (Node.cfgnode to_node)
397+
) (Arg.next node)
398+
in
399+
follow node' p'
400+
410401
let next node =
411-
let open GobOption.Syntax in
402+
let open GobList.Syntax in
412403
match ArgIntra.next_opt (Node.cfgnode node) with
413404
| None -> Arg.next node
414405
| Some next ->
415406
next
416-
|> BatList.filter_map (fun (e, to_n) ->
417-
let+ to_node = Node.move_opt node to_n in
407+
|> BatList.concat_map (fun (e, to_n, p) ->
408+
let* p in
409+
let+ to_node = follow node p in
410+
assert (Node0.equal to_n (Node.cfgnode to_node)); (* should always hold by follow filter above *)
418411
(Edge.embed e, to_node)
419412
)
413+
|> BatList.unique_cmp ~cmp:[%ord: Edge.t * Node.t] (* after following paths, there may be duplicates because same ARG node can be reached via same ARG edge via multiple uncilled CFG paths *) (* TODO: avoid generating duplicates in the first place? *)
420414
end

β€Žsrc/common/util/cilfacade.mlβ€Ž

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ let init () =
9090
(* lineDirectiveStyle := None; *)
9191
RmUnused.keepUnused := true;
9292
print_CIL_Input := true;
93-
Cabs2cil.allowDuplication := false;
93+
Cabs2cil.allowDuplication := false; (* needed for ARG uncilling, maybe something else as well? *)
9494
Cabs2cil.silenceLongDoubleWarning := true;
9595
Cabs2cil.addLoopConditionLabels := true
9696

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#include <goblint.h>
2+
3+
int main() {
4+
int a, b;
5+
6+
__goblint_split_begin(a);
7+
if (a && b) {
8+
return 1;
9+
}
10+
else {
11+
return 0;
12+
}
13+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
2+
β”‚ _ β”‚
3+
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
4+
β”‚
5+
β”‚ Entry main
6+
β–Ό
7+
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
8+
β”‚ _ β”‚
9+
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
10+
β”‚
11+
β”‚ Proc '__goblint_split_begin(a)'
12+
β–Ό
13+
β”Œβ”€β”€β”€β” Test (a && b,false) β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” Test (a && b,false) β”Œβ”€β”€β”€β”
14+
β”‚ _ β”‚ ◀───────────────────── β”‚ _ β”‚ ─────────────────────▢ β”‚ _ β”‚
15+
β””β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”˜
16+
β”‚ β”‚ β”‚
17+
β”‚ β”‚ Test (a && b,true) β”‚
18+
β”‚ β–Ό β”‚
19+
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
20+
β”‚ β”‚ _ β”‚ β”‚
21+
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
22+
β”‚ β”‚ β”‚
23+
β”‚ β”‚ Ret (Some 1, main) β”‚
24+
β”‚ β–Ό β”‚
25+
β”‚ Ret (Some 0, main) β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” Ret (Some 0, main) β”‚
26+
└────────────────────────▢ β”‚ _ β”‚ β—€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
27+
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#include <goblint.h>
2+
3+
int main() {
4+
int a, b;
5+
6+
__goblint_split_begin(b);
7+
if (a && b) {
8+
return 1;
9+
}
10+
else {
11+
return 0;
12+
}
13+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
2+
β”‚ _ β”‚
3+
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
4+
β”‚
5+
β”‚ Entry main
6+
β–Ό
7+
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
8+
β”‚ _ β”‚
9+
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
10+
β”‚
11+
β”‚ Proc '__goblint_split_begin(b)'
12+
β–Ό
13+
β”Œβ”€β”€β”€β” Test (a && b,false) β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” Test (a && b,false) β”Œβ”€β”€β”€β”
14+
β”‚ _ β”‚ ◀───────────────────── β”‚ _ β”‚ ─────────────────────▢ β”‚ _ β”‚
15+
β””β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”˜
16+
β”‚ β”‚ β”‚
17+
β”‚ β”‚ Test (a && b,true) β”‚
18+
β”‚ β–Ό β”‚
19+
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
20+
β”‚ β”‚ _ β”‚ β”‚
21+
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
22+
β”‚ β”‚ β”‚
23+
β”‚ β”‚ Ret (Some 1, main) β”‚
24+
β”‚ β–Ό β”‚
25+
β”‚ Ret (Some 0, main) β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” Ret (Some 0, main) β”‚
26+
└────────────────────────▢ β”‚ _ β”‚ β—€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
27+
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#include <goblint.h>
2+
3+
int main() {
4+
int a, b = 0, c;
5+
6+
__goblint_split_begin(a);
7+
if (a && b && c) {
8+
return 1;
9+
}
10+
else {
11+
return 0;
12+
}
13+
}

0 commit comments

Comments
Β (0)