Skip to content

Commit 015966e

Browse files
authored
Handle renaming of local variables in incremental analysis (AST) (#731)
* Added basic test cases for changed variable names. * Rename detection works for simple cases * Rename detection for method parameters, too * Renaming of method params should work now. * Renaming of results does work for the log files. * Added multiple incremental runs test * Renamed local vars are now also shown in g2html. * Added incremental aware print statements and replaced traditional print statements with those in many places * Renamed variable names are now displayed with their new name in g2html * Cleanup print statements and added some docu. * Renamed context to rename_mapping * Replaced rename mapping lists with Hashtbls for increased performance * cleanup print statements and code. * cherry picked context -> rename mapping * Replaced rename mapping lists with Hashtbls for increased performance * Old locals are now renamed to the new local names. * Fixed duplicate id tests * Moved multiple incremental run tests to subdirectory. * Removed unused currentFunctionName global state. * Removed nothing test case * Added include assert to tests. Removed useless test.c * Replaced tupletostring with fancy syntax. * Hashtbl.add is now replaced by Hashtbl.replace in many places. * List optimization. * method_rename_assumptions now uses varinfo map instead of string hashtable. * Removed RenameMapping. * Added documentation to tests in 04-var-rename * Add comment to test-incremental-multiple.sh * Removed syntactic noise introduced by addition and removal of RenameMapping * Removed diffs directory in tests/incremental/04-var-rename * function parameter names are now also updated. Cleanup code. * eqF now uses empty rename mapping for header comparison. * CFG comparison now verifies locals again.
1 parent 024aa58 commit 015966e

39 files changed

+578
-115
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#This file runs 3 incremental tests in total. As such it is similar to test-incremental.sh but performs an additional incremental run on top of it.
2+
test=$1
3+
4+
base=./tests/incremental
5+
source=$base/${test}_1.c
6+
conf=$base/$test.json
7+
patch1=$base/${test}_1.patch
8+
patch2=$base/${test}_2.patch
9+
10+
args="--enable dbg.debug --enable printstats -v"
11+
12+
cat $source
13+
14+
./goblint --conf $conf $args --enable incremental.save $source &> $base/$test.before.log --html
15+
16+
patch -p0 -b <$patch1
17+
18+
cat $source
19+
20+
./goblint --conf $conf $args --enable incremental.load --enable incremental.save $source &> $base/$test.after.incr1.log --html
21+
22+
patch -p0 <$patch2
23+
24+
cat $source
25+
26+
./goblint --conf $conf $args --enable incremental.load --enable incremental.save --set save_run $base/$test-incrementalrun $source &> $base/$test.after.incr2.log --html
27+
28+
29+
#./goblint --conf $conf $args --enable incremental.only-rename --set save_run $base/$test-originalrun $source &> $base/$test.after.scratch.log --html
30+
#./goblint --conf $conf --enable solverdiffs --compare_runs $base/$test-originalrun $base/$test-incrementalrun $source --html
31+
32+
patch -p0 -b -R <$patch2
33+
patch -p0 -b -R <$patch1
34+
# rm -r $base/$test-originalrun $base/$test-incrementalrun
35+
rm -r $base/$test-incrementalrun
36+
37+
cat $source

src/incremental/compareAST.ml

Lines changed: 178 additions & 89 deletions
Large diffs are not rendered by default.

src/incremental/compareCFG.ml

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,32 @@
11
open MyCFG
22
open Queue
33
open Cil
4+
open CilMaps
45
include CompareAST
56

67
let eq_node (x, fun1) (y, fun2) =
8+
let empty_rename_mapping: rename_mapping = (StringMap.empty, VarinfoMap.empty) in
79
match x,y with
8-
| Statement s1, Statement s2 -> eq_stmt ~cfg_comp:true (s1, fun1) (s2, fun2)
9-
| Function f1, Function f2 -> eq_varinfo f1.svar f2.svar
10-
| FunctionEntry f1, FunctionEntry f2 -> eq_varinfo f1.svar f2.svar
10+
| Statement s1, Statement s2 -> eq_stmt ~cfg_comp:true (s1, fun1) (s2, fun2) empty_rename_mapping
11+
| Function f1, Function f2 -> eq_varinfo f1.svar f2.svar empty_rename_mapping
12+
| FunctionEntry f1, FunctionEntry f2 -> eq_varinfo f1.svar f2.svar empty_rename_mapping
1113
| _ -> false
1214

1315
(* TODO: compare ASMs properly instead of simply always assuming that they are not the same *)
14-
let eq_edge x y = match x, y with
15-
| Assign (lv1, rv1), Assign (lv2, rv2) -> eq_lval lv1 lv2 && eq_exp rv1 rv2
16-
| Proc (None,f1,ars1), Proc (None,f2,ars2) -> eq_exp f1 f2 && GobList.equal eq_exp ars1 ars2
16+
let eq_edge x y =
17+
let empty_rename_mapping: rename_mapping = (StringMap.empty, VarinfoMap.empty) in
18+
match x, y with
19+
| Assign (lv1, rv1), Assign (lv2, rv2) -> eq_lval lv1 lv2 empty_rename_mapping && eq_exp rv1 rv2 empty_rename_mapping
20+
| Proc (None,f1,ars1), Proc (None,f2,ars2) -> eq_exp f1 f2 empty_rename_mapping && GobList.equal (eq_exp2 empty_rename_mapping) ars1 ars2
1721
| Proc (Some r1,f1,ars1), Proc (Some r2,f2,ars2) ->
18-
eq_lval r1 r2 && eq_exp f1 f2 && GobList.equal eq_exp ars1 ars2
19-
| Entry f1, Entry f2 -> eq_varinfo f1.svar f2.svar
20-
| Ret (None,fd1), Ret (None,fd2) -> eq_varinfo fd1.svar fd2.svar
21-
| Ret (Some r1,fd1), Ret (Some r2,fd2) -> eq_exp r1 r2 && eq_varinfo fd1.svar fd2.svar
22-
| Test (p1,b1), Test (p2,b2) -> eq_exp p1 p2 && b1 = b2
22+
eq_lval r1 r2 empty_rename_mapping && eq_exp f1 f2 empty_rename_mapping && GobList.equal (eq_exp2 empty_rename_mapping) ars1 ars2
23+
| Entry f1, Entry f2 -> eq_varinfo f1.svar f2.svar empty_rename_mapping
24+
| Ret (None,fd1), Ret (None,fd2) -> eq_varinfo fd1.svar fd2.svar empty_rename_mapping
25+
| Ret (Some r1,fd1), Ret (Some r2,fd2) -> eq_exp r1 r2 empty_rename_mapping && eq_varinfo fd1.svar fd2.svar empty_rename_mapping
26+
| Test (p1,b1), Test (p2,b2) -> eq_exp p1 p2 empty_rename_mapping && b1 = b2
2327
| ASM _, ASM _ -> false
2428
| Skip, Skip -> true
25-
| VDecl v1, VDecl v2 -> eq_varinfo v1 v2
29+
| VDecl v1, VDecl v2 -> eq_varinfo v1 v2 empty_rename_mapping
2630
| _ -> false
2731

2832
(* The order of the edges in the list is relevant. Therefore compare them one to one without sorting first *)

src/incremental/compareCIL.ml

Lines changed: 78 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
open Cil
22
open MyCFG
3+
open CilMaps
34
include CompareAST
45
include CompareCFG
56

@@ -36,18 +37,51 @@ let should_reanalyze (fdec: Cil.fundec) =
3637
(* If some CFGs of the two functions to be compared are provided, a fine-grained CFG comparison is done that also determines which
3738
* nodes of the function changed. If on the other hand no CFGs are provided, the "old" AST comparison on the CIL.file is
3839
* used for functions. Then no information is collected regarding which parts/nodes of the function changed. *)
39-
let eqF (a: Cil.fundec) (b: Cil.fundec) (cfgs : (cfg * (cfg * cfg)) option) =
40-
let unchangedHeader = eq_varinfo a.svar b.svar && GobList.equal eq_varinfo a.sformals b.sformals in
40+
let eqF (a: Cil.fundec) (b: Cil.fundec) (cfgs : (cfg * (cfg * cfg)) option) (global_rename_mapping: method_rename_assumptions) =
41+
let emptyRenameMapping = (StringMap.empty, VarinfoMap.empty) in
42+
43+
(* Compares the two varinfo lists, returning as a first element, if the size of the two lists are equal,
44+
* and as a second a rename_mapping, holding the rename assumptions *)
45+
let rec rename_mapping_aware_compare (alocals: varinfo list) (blocals: varinfo list) (rename_mapping: string StringMap.t) = match alocals, blocals with
46+
| [], [] -> true, rename_mapping
47+
| origLocal :: als, nowLocal :: bls ->
48+
let new_mapping = StringMap.add origLocal.vname nowLocal.vname rename_mapping in
49+
50+
(*TODO: maybe optimize this with eq_varinfo*)
51+
rename_mapping_aware_compare als bls new_mapping
52+
| _, _ -> false, rename_mapping
53+
in
54+
55+
let unchangedHeader, headerRenameMapping = match cfgs with
56+
| None -> (
57+
let headerSizeEqual, headerRenameMapping = rename_mapping_aware_compare a.sformals b.sformals (StringMap.empty) in
58+
let actHeaderRenameMapping = (headerRenameMapping, global_rename_mapping) in
59+
eq_varinfo a.svar b.svar actHeaderRenameMapping && GobList.equal (eq_varinfo2 actHeaderRenameMapping) a.sformals b.sformals, headerRenameMapping
60+
)
61+
| Some _ -> (
62+
eq_varinfo a.svar b.svar emptyRenameMapping && GobList.equal (eq_varinfo2 emptyRenameMapping) a.sformals b.sformals, StringMap.empty
63+
)
64+
in
65+
4166
let identical, diffOpt =
4267
if should_reanalyze a then
4368
false, None
4469
else
45-
let sameDef = unchangedHeader && GobList.equal eq_varinfo a.slocals b.slocals in
70+
(* Here the local variables are checked to be equal *)
71+
(*flag: when running on cfg, true iff the locals are identical; on ast: if the size of the locals stayed the same*)
72+
let flag, local_rename =
73+
match cfgs with
74+
| None -> rename_mapping_aware_compare a.slocals b.slocals headerRenameMapping
75+
| Some _ -> GobList.equal (eq_varinfo2 emptyRenameMapping) a.slocals b.slocals, StringMap.empty
76+
in
77+
let rename_mapping: rename_mapping = (local_rename, global_rename_mapping) in
78+
79+
let sameDef = unchangedHeader && flag in
4680
if not sameDef then
4781
(false, None)
4882
else
4983
match cfgs with
50-
| None -> eq_block (a.sbody, a) (b.sbody, b), None
84+
| None -> eq_block (a.sbody, a) (b.sbody, b) rename_mapping, None
5185
| Some (cfgOld, (cfgNew, cfgNewBack)) ->
5286
let module CfgOld : MyCFG.CfgForward = struct let next = cfgOld end in
5387
let module CfgNew : MyCFG.CfgBidir = struct let prev = cfgNewBack let next = cfgNew end in
@@ -57,17 +91,41 @@ let eqF (a: Cil.fundec) (b: Cil.fundec) (cfgs : (cfg * (cfg * cfg)) option) =
5791
in
5892
identical, unchangedHeader, diffOpt
5993

60-
let eq_glob (a: global) (b: global) (cfgs : (cfg * (cfg * cfg)) option) = match a, b with
61-
| GFun (f,_), GFun (g,_) -> eqF f g cfgs
62-
| GVar (x, init_x, _), GVar (y, init_y, _) -> eq_varinfo x y, false, None (* ignore the init_info - a changed init of a global will lead to a different start state *)
63-
| GVarDecl (x, _), GVarDecl (y, _) -> eq_varinfo x y, false, None
94+
let eq_glob (a: global) (b: global) (cfgs : (cfg * (cfg * cfg)) option) (global_rename_mapping: method_rename_assumptions) = match a, b with
95+
| GFun (f,_), GFun (g,_) -> eqF f g cfgs global_rename_mapping
96+
| GVar (x, init_x, _), GVar (y, init_y, _) -> eq_varinfo x y (StringMap.empty, VarinfoMap.empty), false, None (* ignore the init_info - a changed init of a global will lead to a different start state *)
97+
| GVarDecl (x, _), GVarDecl (y, _) -> eq_varinfo x y (StringMap.empty, VarinfoMap.empty), false, None
6498
| _ -> ignore @@ Pretty.printf "Not comparable: %a and %a\n" Cil.d_global a Cil.d_global b; false, false, None
6599

66100
let compareCilFiles ?(eq=eq_glob) (oldAST: file) (newAST: file) =
67101
let cfgs = if GobConfig.get_string "incremental.compare" = "cfg"
68102
then Some (CfgTools.getCFG oldAST |> fst, CfgTools.getCFG newAST)
69103
else None in
70104

105+
let generate_global_rename_mapping map global =
106+
try
107+
let ident = identifier_of_global global in
108+
let old_global = GlobalMap.find ident map in
109+
110+
match old_global, global with
111+
| GFun(f, _), GFun (g, _) ->
112+
let renamed_params: string StringMap.t = if (List.length f.sformals) = (List.length g.sformals) then
113+
let mappings = List.combine f.sformals g.sformals |>
114+
List.filter (fun (original, now) -> not (original.vname = now.vname)) |>
115+
List.map (fun (original, now) -> (original.vname, now.vname)) |>
116+
List.to_seq
117+
in
118+
119+
StringMap.add_seq mappings StringMap.empty
120+
else StringMap.empty in
121+
122+
if not (f.svar.vname = g.svar.vname) || (StringMap.cardinal renamed_params) > 0 then
123+
Some (f.svar, {original_method_name=f.svar.vname; new_method_name=g.svar.vname; parameter_renames=renamed_params})
124+
else None
125+
| _, _ -> None
126+
with Not_found -> None
127+
in
128+
71129
let addGlobal map global =
72130
try
73131
let gid = identifier_of_global global in
@@ -79,14 +137,15 @@ let compareCilFiles ?(eq=eq_glob) (oldAST: file) (newAST: file) =
79137
with
80138
Not_found -> map
81139
in
140+
82141
let changes = empty_change_info () in
83142
global_typ_acc := [];
84-
let checkUnchanged map global =
143+
let findChanges map global global_rename_mapping =
85144
try
86145
let ident = identifier_of_global global in
87146
let old_global = GlobalMap.find ident map in
88147
(* Do a (recursive) equal comparison ignoring location information *)
89-
let identical, unchangedHeader, diff = eq old_global global cfgs in
148+
let identical, unchangedHeader, diff = eq old_global global cfgs global_rename_mapping in
90149
if identical
91150
then changes.unchanged <- {current = global; old = old_global} :: changes.unchanged
92151
else changes.changed <- {current = global; old = old_global; unchangedHeader; diff} :: changes.changed
@@ -100,10 +159,18 @@ let compareCilFiles ?(eq=eq_glob) (oldAST: file) (newAST: file) =
100159
(* Store a map from functionNames in the old file to the function definition*)
101160
let oldMap = Cil.foldGlobals oldAST addGlobal GlobalMap.empty in
102161
let newMap = Cil.foldGlobals newAST addGlobal GlobalMap.empty in
162+
163+
let global_rename_mapping: method_rename_assumptions = Cil.foldGlobals newAST (fun (current_global_rename_mapping: method_rename_assumption VarinfoMap.t) global ->
164+
match generate_global_rename_mapping oldMap global with
165+
| Some (funVar, rename_mapping) -> VarinfoMap.add funVar rename_mapping current_global_rename_mapping
166+
| None -> current_global_rename_mapping
167+
) VarinfoMap.empty
168+
in
169+
103170
(* For each function in the new file, check whether a function with the same name
104171
already existed in the old version, and whether it is the same function. *)
105172
Cil.iterGlobals newAST
106-
(fun glob -> checkUnchanged oldMap glob);
173+
(fun glob -> findChanges oldMap glob global_rename_mapping);
107174

108175
(* We check whether functions have been added or removed *)
109176
Cil.iterGlobals newAST (fun glob -> if not (checkExists oldMap glob) then changes.added <- (glob::changes.added));

src/incremental/updateCil.ml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ let update_ids (old_file: file) (ids: max_ids) (new_file: file) (changes: change
4343
in
4444
let reset_fun (f: fundec) (old_f: fundec) =
4545
f.svar.vid <- old_f.svar.vid;
46-
List.iter2 (fun l o_l -> l.vid <- o_l.vid) f.slocals old_f.slocals;
47-
List.iter2 (fun lo o_f -> lo.vid <- o_f.vid) f.sformals old_f.sformals;
46+
List.iter2 (fun l o_l -> l.vid <- o_l.vid; o_l.vname <- l.vname) f.slocals old_f.slocals;
47+
List.iter2 (fun lo o_f -> lo.vid <- o_f.vid; o_f.vname <- lo.vname) f.sformals old_f.sformals;
4848
List.iter2 (fun s o_s -> s.sid <- o_s.sid) f.sallstmts old_f.sallstmts;
4949
List.iter (fun s -> store_node_location (Statement s) (Cilfacade.get_stmtLoc s)) f.sallstmts;
5050

src/util/cilMaps.ml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
open Cil
2+
3+
module VarinfoOrdered = struct
4+
type t = varinfo
5+
6+
(*x.svar.uid cannot be used, as they may overlap between old and now AST*)
7+
let compare (x: varinfo) (y: varinfo) = String.compare x.vname y.vname
8+
end
9+
10+
11+
module VarinfoMap = Map.Make(VarinfoOrdered)

src/util/server.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ let reparse (s: t) =
117117

118118
(* Only called when the file has not been reparsed, so we can skip the expensive CFG comparison. *)
119119
let virtual_changes file =
120-
let eq (glob: Cil.global) _ _ = match glob with
120+
let eq (glob: Cil.global) _ _ _ = match glob with
121121
| GFun (fdec, _) -> not (CompareCIL.should_reanalyze fdec), false, None
122122
| _ -> true, false, None
123123
in
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
int main() {
2+
int a = 0;
3+
return 0;
4+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
3+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
--- tests/incremental/04-var-rename/01-unused_rename.c
2+
+++ tests/incremental/04-var-rename/01-unused_rename.c
3+
@@ -1,4 +1,4 @@
4+
int main() {
5+
- int a = 0;
6+
+ int b = 0;
7+
return 0;
8+
}

0 commit comments

Comments
 (0)