diff --git a/.github/workflows/locked.yml b/.github/workflows/locked.yml
index a7488fa27f..d7310b809a 100644
--- a/.github/workflows/locked.yml
+++ b/.github/workflows/locked.yml
@@ -44,8 +44,9 @@ jobs:
run: |
ruby scripts/update_suite.rb group apron -s
ruby scripts/update_suite.rb group apron2 -s
+
- name: Test apron octagon regression # skipped by default but CI has apron, so explicitly test group (which ignores skipping -- it's now a feature!)
- run: ruby scripts/update_suite.rb group octagon -s
+ run: ruby scripts/update_suite.rb group octagon -s
- name: Test apron regression (Mukherjee et. al SAS '17 paper') # skipped by default but CI has apron, so explicitly test group (which ignores skipping -- it's now a feature!)
run: ruby scripts/update_suite.rb group apron-mukherjee -s
diff --git a/.github/workflows/unlocked.yml b/.github/workflows/unlocked.yml
index 70e2be10bd..01ddf64200 100644
--- a/.github/workflows/unlocked.yml
+++ b/.github/workflows/unlocked.yml
@@ -64,7 +64,7 @@ jobs:
- name: Test apron octagon regression # skipped by default but CI has apron, so explicitly test group (which ignores skipping -- it's now a feature!)
if: ${{ matrix.apron }}
- run: ruby scripts/update_suite.rb group octagon -s
+ run: ruby scripts/update_suite.rb group octagon -s
- name: Test apron regression (Mukherjee et. al SAS '17 paper') # skipped by default but CI has apron, so explicitly test group (which ignores skipping -- it's now a feature!)
if: ${{ matrix.apron }}
@@ -135,10 +135,12 @@ jobs:
- name: Test apron regression # skipped by default but CI has apron, so explicitly test group (which ignores skipping -- it's now a feature!)
# if: ${{ matrix.apron }}
- run: ruby scripts/update_suite.rb group apron -s
+ run: |
+ ruby scripts/update_suite.rb group apron -s
+ ruby scripts/update_suite.rb group apron2 -s
- name: Test apron octagon regression # skipped by default but CI has apron, so explicitly test group (which ignores skipping -- it's now a feature!)
- run: ruby scripts/update_suite.rb group octagon -s
+ run: ruby scripts/update_suite.rb group octagon -s
- name: Test apron regression (Mukherjee et. al SAS '17 paper') # skipped by default but CI has apron, so explicitly test group (which ignores skipping -- it's now a feature!)
# if: ${{ matrix.apron }}
@@ -228,10 +230,12 @@ jobs:
- name: Test apron regression # skipped by default but CI has apron, so explicitly test group (which ignores skipping -- it's now a feature!)
# if: ${{ matrix.apron }}
- run: ruby scripts/update_suite.rb group apron -s
+ run: |
+ ruby scripts/update_suite.rb group apron -s
+ ruby scripts/update_suite.rb group apron2 -s
- name: Test apron octagon regression # skipped by default but CI has apron, so explicitly test group (which ignores skipping -- it's now a feature!)
- run: ruby scripts/update_suite.rb group octagon -s
+ run: ruby scripts/update_suite.rb group octagon -s
- name: Test apron regression (Mukherjee et. al SAS '17 paper') # skipped by default but CI has apron, so explicitly test group (which ignores skipping -- it's now a feature!)
# if: ${{ matrix.apron }}
diff --git a/.semgrep/cil.yml b/.semgrep/cil.yml
index de65b5d9d8..7c3459a0b1 100644
--- a/.semgrep/cil.yml
+++ b/.semgrep/cil.yml
@@ -7,8 +7,10 @@ rules:
- pattern: Cil.typeOffset
- pattern: Cil.mkCast
- pattern: Cil.get_stmtLoc
+ - pattern: Cil.get_instrLoc
paths:
exclude:
+ - cilfacade0.ml
- cilfacade.ml
message: use Cilfacade instead
languages: [ocaml]
diff --git a/conf/zstd-race.json b/conf/zstd-race.json
new file mode 100644
index 0000000000..6465d61719
--- /dev/null
+++ b/conf/zstd-race.json
@@ -0,0 +1,96 @@
+{
+ "ana": {
+ "activated": [
+ "expRelation", "base", "threadid", "threadflag", "threadreturn",
+ "escape", "mutexEvents", "mutex", "access", "mallocWrapper", "mhp",
+ "symb_locks", "var_eq", "mallocFresh"
+ ],
+ "ctx_insens": [
+ "var_eq"
+ ],
+ "base": {
+ "privatization": "none",
+ "context": {
+ "non-ptr": false
+ }
+ },
+ "thread": {
+ "domain": "plain",
+ "include-node": false
+ },
+ "malloc": {
+ "wrappers": [
+ "ZSTD_customMalloc",
+ "ZSTD_customCalloc"
+ ]
+ },
+ "race": {
+ "free": false
+ },
+ "dead-code": {
+ "lines": true
+ }
+ },
+ "sem": {
+ "unknown_function": {
+ "spawn": false,
+ "invalidate": {
+ "globals": false,
+ "args": false
+ }
+ }
+ },
+ "incremental": {
+ "reluctant": {
+ "compare": "leq"
+ },
+ "restart": {
+ "sided": {
+ "enabled": false
+ }
+ }
+ },
+ "solvers": {
+ "td3": {
+ "restart": {
+ "wpoint": {
+ "enabled": false
+ }
+ }
+ }
+ },
+ "exp": {
+ "earlyglobs": true,
+ "extraspecials": [
+ "ZSTD_customMalloc",
+ "ZSTD_customCalloc",
+ "ZSTD_customFree"
+ ]
+ },
+ "pre": {
+ "cppflags": ["-DZSTD_NO_INTRINSICS", "-D_FORTIFY_SOURCE=0", "-DGOBLINT_NO_ASSERT", "-DGOBLINT_NO_BSEARCH"]
+ },
+ "cil": {
+ "merge": {
+ "inlines": false
+ }
+ },
+ "printstats": true,
+ "warn": {
+ "assert": false,
+ "behavior": false,
+ "integer": false,
+ "cast": false,
+ "race": true,
+ "deadcode": true,
+ "analyzer": false,
+ "unsound": false,
+ "imprecise": false,
+ "unknown": false,
+ "error": false,
+ "warning": true,
+ "info": false,
+ "debug": false,
+ "success": true
+ }
+}
diff --git a/docs/user-guide/annotating.md b/docs/user-guide/annotating.md
index 17698e6eda..db4749ccbb 100644
--- a/docs/user-guide/annotating.md
+++ b/docs/user-guide/annotating.md
@@ -25,3 +25,11 @@ The following string arguments are supported:
3. `base.non-ptr`/`base.no-non-ptr` to override the `ana.base.context.non-ptr` option.
4. `apron.context`/`apron.no-context` to override the `ana.apron.context` option.
5. `widen`/`no-widen` to override the `ana.context.widen` option.
+
+
+## Functions
+Goblint-specific functions can be called in the code, where they assist the analyzer but have no runtime effect.
+
+* `__goblint_assume_join(id)` is like `pthread_join(id)`, but considers the given thread IDs must-joined even if Goblint cannot, e.g. due to non-uniqueness.
+ Notably, this annotation can be used after a threads joining loop to make the assumption that the loop correctly joined all those threads.
+ _Misuse of this annotation can cause unsoundness._
diff --git a/docs/user-guide/configuring.md b/docs/user-guide/configuring.md
index e2698efbaf..e71057e96b 100644
--- a/docs/user-guide/configuring.md
+++ b/docs/user-guide/configuring.md
@@ -21,7 +21,8 @@ In `.vscode/settings.json` add the following:
"json.schemas": [
{
"fileMatch": [
- "/conf/*.json"
+ "/conf/*.json",
+ "/tests/incremental/*/*.json"
],
"url": "/src/util/options.schema.json"
}
diff --git a/gobview b/gobview
index d8d364bc9c..467672d0c1 160000
--- a/gobview
+++ b/gobview
@@ -1 +1 @@
-Subproject commit d8d364bc9ce6047777d57291a9eea7de4036bd29
+Subproject commit 467672d0c1e12203eb227b7b399eea630aac48e0
diff --git a/scripts/fix_patch_headers.sh b/scripts/fix_patch_headers.sh
new file mode 100755
index 0000000000..b48ca06c0b
--- /dev/null
+++ b/scripts/fix_patch_headers.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+#Run from root, e.g.,: ./scripts/fix_patch_headers.sh tests/incremental/11-restart/*.patch
+for file in "$@"
+do
+ echo $file
+ cfile="${file%.patch}.c"
+ efile="${cfile//\//\\/}"
+ perl -0777 -i -pe "s/.*?@/--- $efile\n+++ $efile\n@/is" $file
+done
diff --git a/scripts/test-incremental.sh b/scripts/test-incremental.sh
index 375857bfac..40c244b405 100755
--- a/scripts/test-incremental.sh
+++ b/scripts/test-incremental.sh
@@ -11,7 +11,7 @@ source=$base/$test.c
conf=$base/$test.json
patch=$base/$test.patch
-args="--enable dbg.debug --enable printstats -v"
+args="--enable dbg.debug --enable printstats -v --enable allglobs"
./goblint --conf $conf $args --enable incremental.save $source &> $base/$test.before.log
@@ -19,7 +19,7 @@ patch -p0 -b <$patch
./goblint --conf $conf $args --enable incremental.load --set save_run $base/$test-incrementalrun $source &> $base/$test.after.incr.log
./goblint --conf $conf $args --enable incremental.only-rename --set save_run $base/$test-originalrun $source &> $base/$test.after.scratch.log
-./goblint --conf $conf --enable dbg.compare_runs.diff --compare_runs $base/$test-originalrun $base/$test-incrementalrun $source
+./goblint --conf $conf --disable dbg.compare_runs.globsys --enable dbg.compare_runs.diff --compare_runs $base/$test-originalrun $base/$test-incrementalrun $source
patch -p0 -b -R <$patch
rm -r $base/$test-originalrun $base/$test-incrementalrun
diff --git a/scripts/update_suite.rb b/scripts/update_suite.rb
index 7f32cc9c27..0ab00e16d3 100755
--- a/scripts/update_suite.rb
+++ b/scripts/update_suite.rb
@@ -161,6 +161,7 @@ def collect_warnings
when /invariant refuted/ then "fail"
when /^\[Warning\]/ then "warn"
when /^\[Error\]/ then "warn"
+ when /^\[Info\]/ then "warn"
when /^\[Success\]/ then "success"
when /\[Debug\]/ then next # debug "warnings" shouldn't count as other warnings (against NOWARN)
when /^ on line \d+ $/ then next # dead line warnings shouldn't count (used for unreachability with NOWARN)
@@ -416,8 +417,13 @@ def create_test_set(lines)
super(lines)
@testset.p = self
`patch -p0 -b <#{patch_path}`
+ status = $?.exitstatus
lines_incr = IO.readlines(path)
`patch -p0 -b -R <#{patch_path}`
+ if status != 0
+ puts "Failed to apply patch: #{patch_path}"
+ exit 1
+ end
@testset_incr = parse_tests(lines_incr)
@testset_incr.p = self
@testset_incr.warnfile = File.join($testresults, group, name + ".incr.warn.txt")
diff --git a/src/analyses/accessAnalysis.ml b/src/analyses/accessAnalysis.ml
index 2704435dad..83a6f38233 100644
--- a/src/analyses/accessAnalysis.ml
+++ b/src/analyses/accessAnalysis.ml
@@ -17,13 +17,36 @@ struct
module D = Lattice.Unit
module C = Lattice.Unit
+ (* Two global invariants:
+ 1. (lval, type) -> accesses -- used for warnings
+ 2. varinfo -> set of (lval, type) -- used for IterSysVars Global *)
+
+ module V0 = Printable.Prod (Access.LVOpt) (Access.T)
+ module V =
+ struct
+ include Printable.Either (V0) (CilType.Varinfo)
+ let name () = "access"
+ let access x = `Left x
+ let vars x = `Right x
+ let is_write_only _ = true
+ end
+
+ module V0Set = SetDomain.Make (V0)
module G =
struct
- include Access.AS
+ include Lattice.Lift2 (Access.AS) (V0Set) (Printable.DefaultNames)
- let leq x y = !GU.postsolving || leq x y (* HACK: to pass verify*)
+ let access = function
+ | `Bot -> Access.AS.bot ()
+ | `Lifted1 x -> x
+ | _ -> failwith "Access.access"
+ let vars = function
+ | `Bot -> V0Set.bot ()
+ | `Lifted2 x -> x
+ | _ -> failwith "Access.vars"
+ let create_access access = `Lifted1 access
+ let create_vars vars = `Lifted2 vars
end
- module V = Printable.Prod (Access.LVOpt) (Access.T)
let safe = ref 0
let vulnerable = ref 0
@@ -34,6 +57,14 @@ struct
vulnerable := 0;
unsafe := 0
+ let side_vars ctx lv_opt ty =
+ match lv_opt with
+ | Some (v, _) ->
+ if !GU.should_warn then
+ ctx.sideg (V.vars v) (G.create_vars (V0Set.singleton (lv_opt, ty)))
+ | None ->
+ ()
+
let side_access ctx ty lv_opt (conf, w, loc, e, a) =
let ty =
if Option.is_some lv_opt then
@@ -41,13 +72,9 @@ struct
else
ty
in
- let d =
- if !GU.should_warn then
- Access.AS.singleton (conf, w, loc, e, a)
- else
- G.bot () (* HACK: just to pass validation with MCP DomVariantLattice *)
- in
- ctx.sideg (lv_opt, ty) d
+ if !GU.should_warn then
+ ctx.sideg (V.access (lv_opt, ty)) (G.create_access (Access.AS.singleton (conf, w, loc, e, a)));
+ side_vars ctx lv_opt ty
let do_access (ctx: (D.t, G.t, C.t, V.t) ctx) (kind:AccessKind.t) (reach:bool) (conf:int) (e:exp) =
if M.tracing then M.trace "access" "do_access %a %a %B\n" d_exp e AccessKind.pretty kind reach;
@@ -214,9 +241,18 @@ struct
match q with
| WarnGlobal g ->
let g: V.t = Obj.obj g in
- (* ignore (Pretty.printf "WarnGlobal %a\n" CilType.Varinfo.pretty g); *)
- let accs = ctx.global g in
- Stats.time "access" (Access.warn_global safe vulnerable unsafe g) accs
+ begin match g with
+ | `Left g' -> (* accesses *)
+ (* ignore (Pretty.printf "WarnGlobal %a\n" CilType.Varinfo.pretty g); *)
+ let accs = G.access (ctx.global g) in
+ Stats.time "access" (Access.warn_global safe vulnerable unsafe g') accs
+ | `Right _ -> (* vars *)
+ ()
+ end
+ | IterSysVars (Global g, vf) ->
+ V0Set.iter (fun v ->
+ vf (Obj.repr (V.access v))
+ ) (G.vars (ctx.global (V.vars g)))
| _ -> Queries.Result.top q
let finalize () =
diff --git a/src/analyses/apron/apronAnalysis.apron.ml b/src/analyses/apron/apronAnalysis.apron.ml
index b5bb4c4daf..8585a91879 100644
--- a/src/analyses/apron/apronAnalysis.apron.ml
+++ b/src/analyses/apron/apronAnalysis.apron.ml
@@ -22,7 +22,11 @@ struct
module D = ApronComponents (AD) (Priv.D)
module G = Priv.G
module C = D
- module V = Priv.V
+ module V =
+ struct
+ include Priv.V
+ include StdV
+ end
open AD
open (ApronDomain: (sig module V: (module type of ApronDomain.V) end)) (* open only V from ApronDomain (to shadow V of Spec), but don't open D (to not shadow D here) *)
@@ -439,6 +443,9 @@ struct
| _ ->
raise Deadcode
end
+ | Unknown, "__goblint_assume_join" ->
+ let id = List.hd args in
+ Priv.thread_join ~force:true ask ctx.global id st
| _, _ ->
let lvallist e =
let s = ask.f (Queries.MayPointTo e) in
@@ -515,6 +522,9 @@ struct
let r = eval_int e in
if M.tracing then M.traceu "evalint" "apron query %a -> %a\n" d_exp e ID.pretty r;
r
+ | Queries.IterSysVars (vq, vf) ->
+ let vf' x = vf (Obj.repr x) in
+ Priv.iter_sys_vars ctx.global vq vf'
| Queries.Invariant context ->
query_invariant ctx context
| _ -> Result.top q
diff --git a/src/analyses/apron/apronPriv.apron.ml b/src/analyses/apron/apronPriv.apron.ml
index 34e58589e7..bfc1e09086 100644
--- a/src/analyses/apron/apronPriv.apron.ml
+++ b/src/analyses/apron/apronPriv.apron.ml
@@ -37,8 +37,9 @@ module type S =
val enter_multithreaded: Q.ask -> (V.t -> G.t) -> (V.t -> G.t -> unit) -> apron_components_t -> apron_components_t
val threadenter: Q.ask -> (V.t -> G.t) -> apron_components_t -> apron_components_t
- val thread_join: Q.ask -> (V.t -> G.t) -> Cil.exp -> apron_components_t -> apron_components_t
+ val thread_join: ?force:bool -> Q.ask -> (V.t -> G.t) -> Cil.exp -> apron_components_t -> apron_components_t
val thread_return: Q.ask -> (V.t -> G.t) -> (V.t -> G.t -> unit) -> ThreadIdDomain.Thread.t -> apron_components_t -> apron_components_t
+ val iter_sys_vars: (V.t -> G.t) -> VarQuery.t -> V.t VarQuery.f -> unit (** [Queries.IterSysVars] for apron. *)
val init: unit -> unit
val finalize: unit -> unit
@@ -72,7 +73,7 @@ struct
let lock ask getg st m = st
let unlock ask getg sideg st m = st
- let thread_join ask getg exp st = st
+ let thread_join ?(force=false) ask getg exp st = st
let thread_return ask getg sideg tid st = st
let escape node ask getg sideg (st:apron_components_t) escaped:apron_components_t =
@@ -125,6 +126,8 @@ struct
let threadenter ask getg (st: apron_components_t): apron_components_t =
{apr = AD.bot (); priv = startstate ()}
+ let iter_sys_vars getg vq vf = ()
+
let init () = ()
let finalize () = ()
end
@@ -324,7 +327,7 @@ struct
{apr = apr_local'; priv = (p', w')}
- let thread_join ask getg exp st = st
+ let thread_join ?(force=false) ask getg exp st = st
let thread_return ask getg sideg tid st = st
let sync ask getg sideg (st: apron_components_t) reason =
@@ -400,6 +403,8 @@ struct
let threadenter ask getg (st: apron_components_t): apron_components_t =
{apr = getg (); priv = startstate ()}
+ let iter_sys_vars getg vq vf = () (* TODO: or report singleton global for any Global query? *)
+
let finalize () = ()
end
@@ -515,7 +520,7 @@ struct
let apr_local = remove_globals_unprotected_after_unlock ask m apr in
{st with apr = apr_local}
- let thread_join ask getg exp st = st
+ let thread_join ?(force=false) ask getg exp st = st
let thread_return ask getg sideg tid st = st
let sync ask getg sideg (st: apron_components_t) reason =
@@ -1043,22 +1048,40 @@ struct
let l' = L.add lm apr_side l in
{apr = apr_local; priv = (w',LMust.add lm lmust,l')}
- let thread_join (ask:Q.ask) getg exp (st: apron_components_t) =
+ let thread_join ?(force=false) (ask:Q.ask) getg exp (st: apron_components_t) =
let w,lmust,l = st.priv in
let tids = ask.f (Q.EvalThread exp) in
- if ConcDomain.ThreadSet.is_top tids then
- st (* TODO: why needed? *)
+ if force then (
+ if ConcDomain.ThreadSet.is_top tids then (
+ M.info ~category:Unsound "Unknown thread ID assume-joined, Apron privatization unsound"; (* TODO: something more sound *)
+ st (* cannot find all thread IDs to join them all *)
+ )
+ else (
+ (* fold throws if the thread set is top *)
+ let tids' = ConcDomain.ThreadSet.diff tids (ask.f Q.MustJoinedThreads) in (* avoid unnecessary imprecision by force joining already must-joined threads, e.g. 46-apron2/04-other-assume-inprec *)
+ let (lmust', l') = ConcDomain.ThreadSet.fold (fun tid (lmust, l) ->
+ let lmust',l' = G.thread (getg (V.thread tid)) in
+ (LMust.union lmust' lmust, L.join l l')
+ ) tids' (lmust, l)
+ in
+ {st with priv = (w, lmust', l')}
+ )
+ )
else (
- (* elements throws if the thread set is top *)
- let tids = ConcDomain.ThreadSet.elements tids in
- match tids with
- | [tid] ->
- let lmust',l' = G.thread (getg (V.thread tid)) in
- {st with priv = (w, LMust.union lmust' lmust, L.join l l')}
- | _ ->
- (* To match the paper more closely, one would have to join in the non-definite case too *)
- (* Given how we handle lmust (for initialization), doing this might actually be beneficial given that it grows lmust *)
- st
+ if ConcDomain.ThreadSet.is_top tids then
+ st (* TODO: why needed? *)
+ else (
+ (* elements throws if the thread set is top *)
+ let tids = ConcDomain.ThreadSet.elements tids in
+ match tids with
+ | [tid] ->
+ let lmust',l' = G.thread (getg (V.thread tid)) in
+ {st with priv = (w, LMust.union lmust' lmust, L.join l l')}
+ | _ ->
+ (* To match the paper more closely, one would have to join in the non-definite case too *)
+ (* Given how we handle lmust (for initialization), doing this might actually be beneficial given that it grows lmust *)
+ st
+ )
)
let thread_return ask getg sideg tid (st: apron_components_t) =
@@ -1119,6 +1142,11 @@ struct
let _,lmust,l = st.priv in
{apr = AD.bot (); priv = (W.bot (),lmust,l)}
+ let iter_sys_vars getg vq vf =
+ match vq with
+ | VarQuery.Global g -> vf (V.global g)
+ | _ -> ()
+
let finalize () = finalize ()
end
diff --git a/src/analyses/base.ml b/src/analyses/base.ml
index e9e70d8566..581a13beef 100644
--- a/src/analyses/base.ml
+++ b/src/analyses/base.ml
@@ -39,11 +39,16 @@ struct
module D = Dom
module C = Dom
+ (* Two global invariants:
+ 1. Priv.V -> Priv.G -- used for Priv
+ 2. thread -> VD -- used for thread returns *)
+
module V =
struct
include Printable.Either (Priv.V) (ThreadIdDomain.Thread)
let priv x = `Left x
let thread x = `Right x
+ include StdV
end
module G =
@@ -1310,6 +1315,9 @@ struct
Queries.Result.top q
end
| Q.IsMultiple v -> WeakUpdates.mem v ctx.local.weak
+ | Q.IterSysVars (vq, vf) ->
+ let vf' x = vf (Obj.repr (V.priv x)) in
+ Priv.iter_sys_vars (priv_getg ctx.global) vq vf'
| Q.Invariant context -> query_invariant ctx context
| _ -> Q.Result.top q
@@ -2468,6 +2476,11 @@ struct
invalidate ~ctx (Analyses.ask_of_ctx ctx) ctx.global st [Cil.mkAddrOrStartOf lv]
| None -> st
in
+ let addr_type_of_exp exp =
+ let lval = mkMem ~addr:(Cil.stripCasts exp) ~off:NoOffset in
+ let addr = eval_lv (Analyses.ask_of_ctx ctx) ctx.global ctx.local lval in
+ (addr, AD.get_type addr)
+ in
let forks = forkfun ctx lv f args in
if M.tracing then if not (List.is_empty forks) then M.tracel "spawn" "Base.special %s: spawning functions %a\n" f.vname (d_list "," d_varinfo) (List.map BatTuple.Tuple3.second forks);
List.iter (BatTuple.Tuple3.uncurry ctx.spawn) forks;
@@ -2478,10 +2491,7 @@ struct
| Memset { dest; ch; count; }, _ ->
(* TODO: check count *)
let eval_ch = eval_rv (Analyses.ask_of_ctx ctx) gs st ch in
- let dest_lval = mkMem ~addr:(Cil.stripCasts dest) ~off:NoOffset in
- let dest_a = eval_lv (Analyses.ask_of_ctx ctx) gs st dest_lval in
- (* let dest_typ = Cilfacade.typeOfLval dest_lval in *)
- let dest_typ = AD.get_type dest_a in (* TODO: what is the right way? *)
+ let dest_a, dest_typ = addr_type_of_exp dest in
let value =
match eval_ch with
| `Int i when ID.to_int i = Some Z.zero ->
@@ -2493,10 +2503,7 @@ struct
| Bzero { dest; count; }, _ ->
(* TODO: share something with memset special case? *)
(* TODO: check count *)
- let dest_lval = mkMem ~addr:(Cil.stripCasts dest) ~off:NoOffset in
- let dest_a = eval_lv (Analyses.ask_of_ctx ctx) gs st dest_lval in
- (* let dest_typ = Cilfacade.typeOfLval dest_lval in *)
- let dest_typ = AD.get_type dest_a in (* TODO: what is the right way? *)
+ let dest_a, dest_typ = addr_type_of_exp dest in
let value = VD.zero_init_value dest_typ in
set ~ctx (Analyses.ask_of_ctx ctx) gs st dest_a dest_typ value
| Unknown, "strcpy"
@@ -2507,6 +2514,12 @@ struct
| _, [dst; src]
| _, [dst; src; _]
| "__builtin___memcpy_chk", [dst; src; _; _] ->
+ (* invalidating from interactive *)
+ (* let dest_a, dest_typ = addr_type_of_exp dst in
+ let value = VD.top_value dest_typ in
+ set ~ctx (Analyses.ask_of_ctx ctx) gs st dest_a dest_typ value *)
+ (* TODO: reuse addr_type_of_exp for master *)
+ (* assigning from master *)
let get_type lval =
let address = eval_lv (Analyses.ask_of_ctx ctx) gs st lval in
AD.get_type address
diff --git a/src/analyses/basePriv.ml b/src/analyses/basePriv.ml
index b2ecd0f729..7535a18d11 100644
--- a/src/analyses/basePriv.ml
+++ b/src/analyses/basePriv.ml
@@ -35,6 +35,7 @@ sig
val escape: Q.ask -> (V.t -> G.t) -> (V.t -> G.t -> unit) -> BaseComponents (D).t -> EscapeDomain.EscapedVars.t -> BaseComponents (D).t
val enter_multithreaded: Q.ask -> (V.t -> G.t) -> (V.t -> G.t -> unit) -> BaseComponents (D).t -> BaseComponents (D).t
val threadenter: Q.ask -> BaseComponents (D).t -> BaseComponents (D).t
+ val iter_sys_vars: (V.t -> G.t) -> VarQuery.t -> V.t VarQuery.f -> unit
val init: unit -> unit
val finalize: unit -> unit
@@ -75,6 +76,11 @@ struct
let enter_multithreaded ask getg sideg st = st
let threadenter = old_threadenter
+ let iter_sys_vars getg vq vf =
+ match vq with
+ | VarQuery.Global g -> vf g
+ | _ -> ()
+
let read_global ask getg (st: BaseComponents (D).t) x =
getg x
@@ -462,6 +468,13 @@ struct
) st.cpa st
let threadenter = startstate_threadenter startstate
+
+ let iter_sys_vars getg vq vf =
+ match vq with
+ | VarQuery.Global g ->
+ vf (V.unprotected g);
+ vf (V.protected g);
+ | _ -> ()
end
module AbstractLockCenteredGBase (WeakRange: Lattice.S) (SyncRange: Lattice.S) =
@@ -1214,6 +1227,7 @@ struct
let escape ask getg sideg st escaped = time "escape" (Priv.escape ask getg sideg st) escaped
let enter_multithreaded ask getg sideg st = time "enter_multithreaded" (Priv.enter_multithreaded ask getg sideg) st
let threadenter ask st = time "threadenter" (Priv.threadenter ask) st
+ let iter_sys_vars getg vq vf = time "iter_sys_vars" (Priv.iter_sys_vars getg vq) vf
let init () = time "init" (Priv.init) ()
let finalize () = time "finalize" (Priv.finalize) ()
diff --git a/src/analyses/basePriv.mli b/src/analyses/basePriv.mli
index c2d078df5f..771ac272fd 100644
--- a/src/analyses/basePriv.mli
+++ b/src/analyses/basePriv.mli
@@ -21,6 +21,7 @@ sig
val escape: Queries.ask -> (V.t -> G.t) -> (V.t -> G.t -> unit) -> BaseDomain.BaseComponents (D).t -> EscapeDomain.EscapedVars.t -> BaseDomain.BaseComponents (D).t
val enter_multithreaded: Queries.ask -> (V.t -> G.t) -> (V.t -> G.t -> unit) -> BaseDomain.BaseComponents (D).t -> BaseDomain.BaseComponents (D).t
val threadenter: Queries.ask -> BaseDomain.BaseComponents (D).t -> BaseDomain.BaseComponents (D).t
+ val iter_sys_vars: (V.t -> G.t) -> VarQuery.t -> V.t VarQuery.f -> unit (** [Queries.IterSysVars] for base. *)
val init: unit -> unit
val finalize: unit -> unit
diff --git a/src/analyses/commonPriv.ml b/src/analyses/commonPriv.ml
index e99db361a2..983c4570c1 100644
--- a/src/analyses/commonPriv.ml
+++ b/src/analyses/commonPriv.ml
@@ -86,6 +86,11 @@ struct
let mutex_inits: t = `Left (`Right ())
let global x: t = `Right x
end
+
+ let iter_sys_vars getg vq vf =
+ match vq with
+ | VarQuery.Global g -> vf (V.global g)
+ | _ -> ()
end
module MayVars =
diff --git a/src/analyses/deadlock.ml b/src/analyses/deadlock.ml
index 75a8f98562..91810887d6 100644
--- a/src/analyses/deadlock.ml
+++ b/src/analyses/deadlock.ml
@@ -14,22 +14,17 @@ struct
module Arg =
struct
module D = MayLockEvents
- module V = Lock
-
- module G =
+ module V =
struct
- include MapDomain.MapBot (Lock) (MayLockEventPairs)
- let leq x y = !GU.postsolving || leq x y (* HACK: to pass verify*)
+ include Lock
+ let is_write_only _ = true
end
- let side_lock_event_pair ctx before after =
- let d =
- if !GU.should_warn then
- G.singleton (Tuple3.first after) (MayLockEventPairs.singleton (before, after))
- else
- G.bot () (* HACK: just to pass validation with MCP DomVariantLattice *)
- in
- ctx.sideg (Tuple3.first before) d
+ module G = MapDomain.MapBot (Lock) (MayLockEventPairs)
+
+ let side_lock_event_pair ctx ((before_node, _, _) as before) ((after_node, _, _) as after) =
+ if !GU.should_warn then
+ ctx.sideg before_node (G.singleton after_node (MayLockEventPairs.singleton (before, after)))
let part_access ctx: MCPAccess.A.t =
Obj.obj (ctx.ask (PartAccess Point))
@@ -93,8 +88,8 @@ struct
let normalized = List.rev_append init (List.rev tail) in (* backwards to get correct printout order *)
let msgs = List.concat_map (fun ((before_lock, before_node, before_access), (after_lock, after_node, after_access)) ->
[
- (Pretty.dprintf "lock before: %a with %a" Lock.pretty before_lock MCPAccess.A.pretty before_access, Some (UpdateCil.getLoc before_node));
- (Pretty.dprintf "lock after: %a with %a" Lock.pretty after_lock MCPAccess.A.pretty after_access, Some (UpdateCil.getLoc after_node));
+ (Pretty.dprintf "lock before: %a with %a" Lock.pretty before_lock MCPAccess.A.pretty before_access, Some (M.Location.Node before_node));
+ (Pretty.dprintf "lock after: %a with %a" Lock.pretty after_lock MCPAccess.A.pretty after_access, Some (M.Location.Node after_node));
]
) normalized
in
diff --git a/src/analyses/fileUse.ml b/src/analyses/fileUse.ml
index d578b9f6de..9fc51b0ee6 100644
--- a/src/analyses/fileUse.ml
+++ b/src/analyses/fileUse.ml
@@ -144,7 +144,7 @@ struct
let enter ctx (lval: lval option) (f:fundec) (args:exp list) : (D.t * D.t) list =
let m = if f.svar.vname <> "main" then
(* push current location onto stack *)
- D.edit_callstack (BatList.cons !Tracing.current_loc) ctx.local
+ D.edit_callstack (BatList.cons (Option.get !Node.current_node)) ctx.local
else ctx.local in
(* we need to remove all variables that are neither globals nor special variables from the domain for f *)
(* problem: we need to be able to check aliases of globals in check_overwrite_open -> keep those in too :/ *)
@@ -192,7 +192,7 @@ struct
(* is f a pointer to a function we look out for? *)
let f = eval_fv (Analyses.ask_of_ctx ctx) (Lval (Var f, NoOffset)) |? f in
let m = ctx.local in
- let loc = !Tracing.current_loc::(D.callstack m) in
+ let loc = (Option.get !Node.current_node)::(D.callstack m) in
let arglist = List.map (Cil.stripCasts) arglist in (* remove casts, TODO safe? *)
let split_err_branch lval dom =
(* type? NULL = 0 = 0-ptr? Cil.intType, Cil.intPtrType, Cil.voidPtrType -> no difference *)
diff --git a/src/analyses/libraryFunctions.ml b/src/analyses/libraryFunctions.ml
index 03d15a7e9e..03cbb4afea 100644
--- a/src/analyses/libraryFunctions.ml
+++ b/src/analyses/libraryFunctions.ml
@@ -348,6 +348,7 @@ let invalidate_actions = [
"strlen", readsAll;(*safe*)
"strncmp", readsAll;(*safe*)
"strncpy", writes [1];(*keep [1]*)
+ "strncat", writes [1];(*keep [1]*)
"strstr", readsAll;(*safe*)
"strdup", readsAll;(*safe*)
"toupper", readsAll;(*safe*)
@@ -391,6 +392,9 @@ let invalidate_actions = [
"pthread_attr_setdetachstate", writesAll;(*unsafe*)
"pthread_attr_setstacksize", writesAll;(*unsafe*)
"pthread_attr_setscope", writesAll;(*unsafe*)
+ "pthread_attr_getdetachstate", readsAll;(*safe*)
+ "pthread_attr_getstacksize", readsAll;(*safe*)
+ "pthread_attr_getscope", readsAll;(*safe*)
"pthread_cond_init", readsAll; (*safe*)
"pthread_cond_wait", readsAll; (*safe*)
"pthread_cond_signal", readsAll;(*safe*)
@@ -418,7 +422,8 @@ let invalidate_actions = [
"strcpy", writes [1];(*keep [1]*)
"__builtin___strcpy", writes [1];(*keep [1]*)
"__builtin___strcpy_chk", writes [1];(*keep [1]*)
- "strcat", writes [2];(*keep [2]*)
+ "strcat", writes [1];(*keep [1]*)
+ "strtok", readsAll;(*safe*)
"getpgrp", readsAll;(*safe*)
"umount2", readsAll;(*safe*)
"memchr", readsAll;(*safe*)
@@ -453,6 +458,7 @@ let invalidate_actions = [
"fputs", readsAll;(*safe*)
"fputc", readsAll;(*safe*)
"fseek", writes[1];
+ "rewind", writesAll;
"fileno", readsAll;
"ferror", readsAll;
"ftell", readsAll;
@@ -768,6 +774,7 @@ let invalidate_actions = [
"y0", readsAll;
"y1", readsAll;
"yn", readsAll;
+ "__goblint_assume_join", readsAll;
]
@@ -809,7 +816,8 @@ let unknown_desc ~f name = (* TODO: remove name argument, unknown function shoul
let old_accesses (kind: AccessKind.t) args = match kind with
| Write when GobConfig.get_bool "sem.unknown_function.invalidate.args" -> args
| Write -> []
- | Read -> args
+ | Read when GobConfig.get_bool "sem.unknown_function.read.args" -> args
+ | Read -> []
| Free -> []
| Spawn when get_bool "sem.unknown_function.spawn" -> args
| Spawn -> []
diff --git a/src/analyses/locksetAnalysis.ml b/src/analyses/locksetAnalysis.ml
index a102732f57..2e9e08f03d 100644
--- a/src/analyses/locksetAnalysis.ml
+++ b/src/analyses/locksetAnalysis.ml
@@ -27,7 +27,7 @@ module type MayArg =
sig
module D: DS
module G: Lattice.S
- module V: Printable.S
+ module V: SpecSysVar
val add: (D.t, G.t, D.t, V.t) ctx -> LockDomain.Lockset.Lock.t -> D.t
val remove: (D.t, G.t, D.t, V.t) ctx -> ValueDomain.Addr.t -> D.t
diff --git a/src/analyses/mCP.ml b/src/analyses/mCP.ml
index 0277429698..b8efd4a624 100644
--- a/src/analyses/mCP.ml
+++ b/src/analyses/mCP.ml
@@ -10,12 +10,12 @@ module MCP2 : Analyses.Spec
with module D = DomListLattice (LocalDomainListSpec)
and module G = DomVariantLattice (GlobalDomainListSpec)
and module C = DomListPrintable (ContextListSpec)
- and module V = DomVariantPrintable (VarListSpec) =
+ and module V = DomVariantSysVar (VarListSpec) =
struct
module D = DomListLattice (LocalDomainListSpec)
module G = DomVariantLattice (GlobalDomainListSpec)
module C = DomListPrintable (ContextListSpec)
- module V = DomVariantPrintable (VarListSpec)
+ module V = DomVariantSysVar (VarListSpec)
open List open Obj
let v_of n v = (n, repr v)
@@ -263,6 +263,13 @@ struct
f ~q:(WarnGlobal (Obj.repr g)) (Result.top ()) (n, spec n, assoc n ctx.local)
| Queries.PartAccess a ->
Obj.repr (access ctx a)
+ | Queries.IterSysVars (vq, fi) ->
+ (* IterSysVars is special: argument function is lifted for each analysis *)
+ iter (fun ((n,(module S:MCPSpec),d) as t) ->
+ let fi' x = fi (Obj.repr (v_of n x)) in
+ let q' = Queries.IterSysVars (vq, fi') in
+ f ~q:q' () t
+ ) @@ spec_list ctx.local
(* | EvalInt e ->
(* TODO: only query others that actually respond to EvalInt *)
(* 2x speed difference on SV-COMP nla-digbench-scaling/ps6-ll_valuebound5.c *)
diff --git a/src/analyses/mCPRegistry.ml b/src/analyses/mCPRegistry.ml
index ac35e75d99..620fbc8c79 100644
--- a/src/analyses/mCPRegistry.ml
+++ b/src/analyses/mCPRegistry.ml
@@ -7,7 +7,7 @@ type spec_modules = { name : string
; dom : (module Lattice.S)
; glob : (module Lattice.S)
; cont : (module Printable.S)
- ; var : (module Printable.S)
+ ; var : (module SpecSysVar)
; acc : (module MCPA) }
let activated : (int * spec_modules) list ref = ref []
@@ -25,7 +25,7 @@ let register_analysis =
; dom = (module S.D : Lattice.S)
; glob = (module S.G : Lattice.S)
; cont = (module S.C : Printable.S)
- ; var = (module S.V : Printable.S)
+ ; var = (module S.V : SpecSysVar)
; acc = (module S.A : MCPA)
}
in
@@ -45,6 +45,12 @@ sig
val domain_list : unit -> (int * (module Printable.S)) list
end
+module type DomainListSysVarSpec =
+sig
+ val assoc_dom : int -> (module SpecSysVar)
+ val domain_list : unit -> (int * (module SpecSysVar)) list
+end
+
module type DomainListMCPASpec =
sig
val assoc_dom : int -> (module MCPA)
@@ -81,6 +87,18 @@ struct
List.map (fun (x,y) -> (x,f y)) (D.domain_list ())
end
+module PrintableOfSysVarSpec (D:DomainListSysVarSpec) : DomainListPrintableSpec =
+struct
+ let assoc_dom n =
+ let f (module L:SpecSysVar) = (module L : Printable.S)
+ in
+ f (D.assoc_dom n)
+
+ let domain_list () =
+ let f (module L:SpecSysVar) = (module L : Printable.S) in
+ List.map (fun (x,y) -> (x,f y)) (D.domain_list ())
+end
+
module DomListPrintable (DLSpec : DomainListPrintableSpec)
: Printable.S with type t = (int * unknown) list
=
@@ -231,6 +249,23 @@ struct
QCheck.oneof arbs
end
+module DomVariantSysVar (DLSpec : DomainListSysVarSpec)
+ : SpecSysVar with type t = int * unknown
+=
+struct
+ open DLSpec
+ open Obj
+
+ include DomVariantPrintable (PrintableOfSysVarSpec (DLSpec))
+
+ let unop_map f ((n, d):t) =
+ f n (assoc_dom n) d
+
+ let is_write_only = unop_map (fun n (module S: SpecSysVar) x ->
+ S.is_write_only (obj x)
+ )
+end
+
module DomListLattice (DLSpec : DomainListLatticeSpec)
: Lattice.S with type t = (int * unknown) list
=
@@ -331,7 +366,7 @@ struct
let domain_list () = List.map (fun (n,p) -> n, p.cont) !activated_ctx_sens
end
-module VarListSpec : DomainListPrintableSpec =
+module VarListSpec : DomainListSysVarSpec =
struct
let assoc_dom n = (find_spec n).var
let domain_list () = List.map (fun (n,p) -> n, p.var) !activated
diff --git a/src/analyses/mutexAnalysis.ml b/src/analyses/mutexAnalysis.ml
index dbe7fac0c5..beb2070c99 100644
--- a/src/analyses/mutexAnalysis.ml
+++ b/src/analyses/mutexAnalysis.ml
@@ -100,6 +100,8 @@ struct
| Queries.MustBeAtomic ->
let held_locks = Lockset.export_locks (Lockset.filter snd ctx.local) in
Mutexes.mem MutexEventsAnalysis.verifier_atomic held_locks
+ | Queries.IterSysVars (Global g, f) ->
+ f (Obj.repr g)
| _ -> Queries.Result.top q
module A =
diff --git a/src/analyses/region.ml b/src/analyses/region.ml
index 0f55467ee3..ef77d1db95 100644
--- a/src/analyses/region.ml
+++ b/src/analyses/region.ml
@@ -14,7 +14,11 @@ struct
module D = RegionDomain.RegionDom
module G = RegPart
module C = D
- module V = Printable.UnitConf (struct let name = "partitions" end)
+ module V =
+ struct
+ include Printable.UnitConf (struct let name = "partitions" end)
+ include StdV
+ end
let regions exp part st : Lval.CilLval.t list =
match st with
diff --git a/src/analyses/spec.ml b/src/analyses/spec.ml
index 4633849616..0fced7891c 100644
--- a/src/analyses/spec.ml
+++ b/src/analyses/spec.ml
@@ -44,7 +44,7 @@ struct
struct
(* custom goto (D.goto is just for modifying) that checks if the target state is a warning and acts accordingly *)
let goto ?may:(may=false) ?change_state:(change_state=true) key state m ws =
- let loc = !Tracing.current_loc::(D.callstack m) in
+ let loc = (Option.get !Node.current_node)::(D.callstack m) in
let warn key m msg =
Str.global_replace (Str.regexp_string "$") (D.string_of_key key) msg
|> D.warn ~may:(D.is_may key m || D.is_unknown key m)
@@ -411,7 +411,7 @@ struct
(* M.debug ~category:Analyzer @@ "entering function "^f.vname^D.string_of_callstack ctx.local; *)
if f.svar.vname = "main" then load_specfile ();
let m = if f.svar.vname <> "main" then
- D.edit_callstack (BatList.cons !Tracing.current_loc) ctx.local
+ D.edit_callstack (BatList.cons (Option.get !Node.current_node)) ctx.local
else ctx.local in [m, m]
let combine ctx (lval:lval option) fexp (f:fundec) (args:exp list) fc (au:D.t) : D.t =
diff --git a/src/analyses/symbLocks.ml b/src/analyses/symbLocks.ml
index 4a5e4d72d6..b36dfc1b59 100644
--- a/src/analyses/symbLocks.ml
+++ b/src/analyses/symbLocks.ml
@@ -62,8 +62,12 @@ struct
| a when not (Queries.ES.is_bot a) -> Queries.ES.add e a
| _ -> Queries.ES.singleton e
in
+ if M.tracing then M.tracel "symb_locks" "get_all_locks exps %a = %a\n" d_plainexp e Queries.ES.pretty exps;
+ if M.tracing then M.tracel "symb_locks" "get_all_locks st = %a\n" D.pretty st;
let add_locks x xs = PS.union (get_locks x st) xs in
- Queries.ES.fold add_locks exps (PS.empty ())
+ let r = Queries.ES.fold add_locks exps (PS.empty ()) in
+ if M.tracing then M.tracel "symb_locks" "get_all_locks %a = %a\n" d_plainexp e PS.pretty r;
+ r
let same_unknown_index (ask: Queries.ask) exp slocks =
let uk_index_equal = Queries.must_be_equal ask in
@@ -134,6 +138,7 @@ struct
*)
let one_perelem (e,a,l) xs =
(* ignore (printf "one_perelem (%a,%a,%a)\n" Exp.pretty e Exp.pretty a Exp.pretty l); *)
+ if M.tracing then M.tracel "symb_locks" "one_perelem (%a,%a,%a)\n" Exp.pretty e Exp.pretty a Exp.pretty l;
match Exp.fold_offs (Exp.replace_base (dummyFunDec.svar,`NoOffset) e l) with
| Some (v, o) ->
(* ignore (printf "adding lock %s\n" l); *)
diff --git a/src/analyses/threadAnalysis.ml b/src/analyses/threadAnalysis.ml
index a74ae15cdd..181879ae18 100644
--- a/src/analyses/threadAnalysis.ml
+++ b/src/analyses/threadAnalysis.ml
@@ -14,7 +14,11 @@ struct
module D = ConcDomain.CreatedThreadSet
module C = D
module G = ConcDomain.ThreadCreation
- module V = T
+ module V =
+ struct
+ include T
+ include StdV
+ end
let should_join = D.equal
diff --git a/src/analyses/threadJoins.ml b/src/analyses/threadJoins.ml
index 17500a4072..2c48fbd75a 100644
--- a/src/analyses/threadJoins.ml
+++ b/src/analyses/threadJoins.ml
@@ -14,7 +14,11 @@ struct
module D = MustTIDs
module C = D
module G = MustTIDs
- module V = TID
+ module V =
+ struct
+ include TID
+ include StdV
+ end
(* transfer functions *)
let return ctx (exp:exp option) (f:fundec) : D.t =
@@ -27,13 +31,13 @@ struct
let special ctx (lval: lval option) (f:varinfo) (arglist:exp list) : D.t =
let desc = LibraryFunctions.find f in
- match desc.special arglist with
- | ThreadExit _ -> (match ctx.ask CurrentThreadId with
+ match desc.special arglist, f.vname with
+ | ThreadExit _, _ -> (match ctx.ask CurrentThreadId with
| `Lifted tid -> ctx.sideg tid ctx.local
| _ -> () (* correct? *)
);
ctx.local
- | ThreadJoin { thread = id; ret_var } ->
+ | ThreadJoin { thread = id; ret_var }, _ ->
let threads = ctx.ask (Queries.EvalThread id) in
if TIDs.is_top threads then
ctx.local
@@ -47,7 +51,36 @@ struct
| _ -> ctx.local (* if multiple possible thread ids are joined, none of them is must joined*)
(* Possible improvement: Do the intersection first, things that are must joined in all possibly joined threads are must-joined *)
)
- | _ -> ctx.local
+ | Unknown, "__goblint_assume_join" ->
+ let id = List.hd arglist in
+ let threads = ctx.ask (Queries.EvalThread id) in
+ if TIDs.is_top threads then (
+ M.info ~category:Unsound "Unknown thread ID assume-joined, assuming ALL threads must-joined.";
+ D.bot () (* consider everything joined, D is reversed so bot is All threads *)
+ )
+ else (
+ (* elements throws if the thread set is top *)
+ let threads = TIDs.elements threads in
+ if List.compare_length_with threads 1 > 0 then
+ M.info ~category:Unsound "Ambiguous thread ID assume-joined, assuming all of those threads must-joined.";
+ List.fold_left (fun acc tid ->
+ let joined = ctx.global tid in
+ D.union (D.add tid acc) joined
+ ) ctx.local threads
+ )
+ | _, _ -> ctx.local
+
+ let threadspawn ctx lval f args fctx =
+ if D.is_bot ctx.local then ( (* bot is All threads *)
+ M.info ~category:Imprecise "Thread created while ALL threads must-joined, continuing with no threads joined.";
+ D.top () (* top is no threads *)
+ )
+ else
+ match ThreadId.get_current (Analyses.ask_of_ctx fctx) with
+ | `Lifted tid ->
+ D.remove tid ctx.local
+ | _ ->
+ ctx.local
let query ctx (type a) (q: a Queries.t): a Queries.result =
match q with
diff --git a/src/analyses/varEq.ml b/src/analyses/varEq.ml
index 91c6312101..985ce24cf3 100644
--- a/src/analyses/varEq.ml
+++ b/src/analyses/varEq.ml
@@ -112,6 +112,8 @@ struct
(* TODO: what does interesting mean? *)
let rec interesting x =
match x with
+ | AddrOf (Mem (BinOp (IndexPI, a, _i, _)), _os) ->
+ interesting a
| SizeOf _
| SizeOfE _
| SizeOfStr _
@@ -333,7 +335,8 @@ struct
| Question (b, t, f, _) -> lval_may_change_pt b bl || lval_may_change_pt t bl || lval_may_change_pt f bl
in
let r =
- if Queries.LS.is_top bls || Queries.LS.mem (dummyFunDec.svar, `NoOffset) bls
+ if Cil.isConstant b then false
+ else if Queries.LS.is_top bls || Queries.LS.mem (dummyFunDec.svar, `NoOffset) bls
then ((*Messages.warn ~category:Analyzer "No PT-set: switching to types ";*) type_may_change_apt a )
else Queries.LS.exists (lval_may_change_pt a) bls
in
@@ -341,7 +344,9 @@ struct
then (Messages.warn ~category:Analyzer ~msg:("Kill " ^sprint 80 (Exp.pretty () a)^" because of "^sprint 80 (Exp.pretty () b)) (); r)
else (Messages.warn ~category:Analyzer ~msg:("Keep " ^sprint 80 (Exp.pretty () a)^" because of "^sprint 80 (Exp.pretty () b)) (); r)
Messages.warn ~category:Analyzer ~msg:(sprint 80 (Exp.pretty () b) ^" changed lvalues: "^sprint 80 (Queries.LS.pretty () bls)) ();
- *) r
+ *)
+ if M.tracing then M.tracel "var_eq" "may_change %a %a = %B\n" CilType.Exp.pretty b CilType.Exp.pretty a r;
+ r
(* Remove elements, that would change if the given lval would change.*)
let remove_exp ask (e:exp) (st:D.t) : D.t =
@@ -391,6 +396,12 @@ struct
(* Set given lval equal to the result of given expression. On doubt do nothing. *)
let add_eq ask (lv:lval) (rv:Exp.t) st =
let lvt = unrollType @@ Cilfacade.typeOfLval lv in
+ if M.tracing then (
+ M.tracel "var_eq" "add_eq is_global_var %a = %B\n" d_plainlval lv (is_global_var ask (Lval lv) = Some false);
+ M.tracel "var_eq" "add_eq interesting %a = %B\n" d_plainexp rv (interesting rv);
+ M.tracel "var_eq" "add_eq is_global_var %a = %B\n" d_plainexp rv (is_global_var ask rv = Some false);
+ M.tracel "var_eq" "add_eq type %a = %B\n" d_plainlval lv ((isArithmeticType lvt && match lvt with | TFloat _ -> false | _ -> true ) || isPointerType lvt);
+ );
if is_global_var ask (Lval lv) = Some false
&& interesting rv
&& is_global_var ask rv = Some false
@@ -533,37 +544,54 @@ struct
D.B.fold add es (Queries.ES.empty ())
let rec eq_set_clos e s =
- match e with
- | SizeOf _
- | SizeOfE _
- | SizeOfStr _
- | AlignOf _
- | Const _
- | AlignOfE _
- | UnOp _
- | BinOp _
- | Question _
- | AddrOfLabel _
- | Real _
- | Imag _
- | AddrOf (Var _,_)
- | StartOf (Var _,_)
- | Lval (Var _,_) -> eq_set e s
- | AddrOf (Mem e,ofs) ->
- Queries.ES.map (fun e -> mkAddrOf (mkMem ~addr:e ~off:ofs)) (eq_set_clos e s)
- | StartOf (Mem e,ofs) ->
- Queries.ES.map (fun e -> mkAddrOrStartOf (mkMem ~addr:e ~off:ofs)) (eq_set_clos e s)
- | Lval (Mem e,ofs) ->
- Queries.ES.map (fun e -> Lval (mkMem ~addr:e ~off:ofs)) (eq_set_clos e s)
- | CastE (t,e) ->
- Queries.ES.map (fun e -> CastE (t,e)) (eq_set_clos e s)
+ if M.tracing then M.traceli "var_eq" "eq_set_clos %a\n" d_plainexp e;
+ let r = match e with
+ | AddrOf (Mem (BinOp (IndexPI, a, i, _)), os) ->
+ (* convert IndexPI to Index offset *)
+ (* TODO: this applies eq_set_clos under the offset, unlike cases below; should generalize? *)
+ Queries.ES.fold (fun e acc -> (* filter_map *)
+ match e with
+ | CastE (_, StartOf a') -> (* eq_set adds casts *)
+ let e' = AddrOf (Cil.addOffsetLval (Index (i, os)) a') in (* TODO: re-add cast? *)
+ Queries.ES.add e' acc
+ | _ -> acc
+ ) (eq_set_clos a s) (Queries.ES.empty ())
+ | SizeOf _
+ | SizeOfE _
+ | SizeOfStr _
+ | AlignOf _
+ | Const _
+ | AlignOfE _
+ | UnOp _
+ | BinOp _
+ | Question _
+ | AddrOfLabel _
+ | Real _
+ | Imag _
+ | AddrOf (Var _,_)
+ | StartOf (Var _,_)
+ | Lval (Var _,_) -> eq_set e s
+ | AddrOf (Mem e,ofs) ->
+ Queries.ES.map (fun e -> mkAddrOf (mkMem ~addr:e ~off:ofs)) (eq_set_clos e s)
+ | StartOf (Mem e,ofs) ->
+ Queries.ES.map (fun e -> mkAddrOrStartOf (mkMem ~addr:e ~off:ofs)) (eq_set_clos e s)
+ | Lval (Mem e,ofs) ->
+ Queries.ES.map (fun e -> Lval (mkMem ~addr:e ~off:ofs)) (eq_set_clos e s)
+ | CastE (t,e) ->
+ Queries.ES.map (fun e -> CastE (t,e)) (eq_set_clos e s)
+ in
+ if M.tracing then M.traceu "var_eq" "eq_set_clos %a = %a\n" d_plainexp e Queries.ES.pretty r;
+ r
let query ctx (type a) (x: a Queries.t): a Queries.result =
match x with
| Queries.EvalInt (BinOp (Eq, e1, e2, t)) when query_exp_equal (Analyses.ask_of_ctx ctx) e1 e2 ctx.global ctx.local ->
Queries.ID.of_bool (Cilfacade.get_ikind t) true
- | Queries.EqualSet e -> eq_set_clos e ctx.local
+ | Queries.EqualSet e ->
+ let r = eq_set_clos e ctx.local in
+ if M.tracing then M.tracel "var_eq" "equalset %a = %a\n" d_plainexp e Queries.ES.pretty r;
+ r
| Queries.Invariant context ->
let scope = Node.find_fundec ctx.node in
D.invariant ~scope ctx.local
diff --git a/src/cdomains/basetype.ml b/src/cdomains/basetype.ml
index 9b0bf728be..7984734a13 100644
--- a/src/cdomains/basetype.ml
+++ b/src/cdomains/basetype.ml
@@ -77,6 +77,7 @@ module Bools: Lattice.S with type t = [`Bot | `Lifted of bool | `Top] =
module CilExp =
struct
+ include Printable.Std (* for Groupable *)
include CilType.Exp
let name () = "expressions"
diff --git a/src/cdomains/lvalMapDomain.ml b/src/cdomains/lvalMapDomain.ml
index 916a10121b..ed60410d71 100644
--- a/src/cdomains/lvalMapDomain.ml
+++ b/src/cdomains/lvalMapDomain.ml
@@ -21,7 +21,7 @@ sig
val string_of_record: r -> string
(* constructing *)
- val make: k -> location list -> s -> t
+ val make: k -> Node.t list -> s -> t
(* manipulation *)
val map: (r -> r) -> t -> t
@@ -42,8 +42,8 @@ sig
val may: (r -> bool) -> t -> bool
(* properties of records *)
val key: r -> k
- val loc: r -> location list
- val edit_loc: (location list -> location list) -> r -> r
+ val loc: r -> Node.t list
+ val edit_loc: (Node.t list -> Node.t list) -> r -> r
val state: r -> s
val in_state: s -> r -> bool
@@ -70,7 +70,7 @@ struct
type s = Impl.s [@@deriving eq, ord, hash]
module R = struct
include Printable.Blank
- type t = { key: k; loc: CilType.Location.t list; state: s } [@@deriving eq, ord, hash]
+ type t = { key: k; loc: Node.t list; state: s } [@@deriving eq, ord, hash]
let to_yojson _ = failwith "TODO to_yojson"
let name () = "LValMapDomainValue"
end
@@ -94,7 +94,7 @@ struct
(* Printing *)
let string_of_key k = Lval.CilLval.show k
- let string_of_loc xs = String.concat ", " (List.map CilType.Location.show xs)
+ let string_of_loc xs = String.concat ", " (List.map (CilType.Location.show % Node.location) xs)
let string_of_record r = Impl.string_of_state r.state^" ("^string_of_loc r.loc^")"
let string_of (x,y) =
if is_alias (x,y) then
@@ -210,7 +210,7 @@ struct
(* callstack for locations *)
let callstack_var = Goblintutil.create_var @@ Cil.makeVarinfo false "@callstack" Cil.voidType, `NoOffset
let callstack m = get_record callstack_var m |> Option.map_default V.loc []
- let string_of_callstack m = " [call stack: "^String.concat ", " (List.map CilType.Location.show (callstack m))^"]"
+ let string_of_callstack m = " [call stack: "^String.concat ", " (List.map (CilType.Location.show % Node.location) (callstack m))^"]"
let edit_callstack f m = edit_record callstack_var (V.edit_loc f) m
@@ -238,12 +238,12 @@ struct
let string_of_entry k m = string_of_key k ^ ": " ^ string_of_state k m
let string_of_map m = List.map (fun (k,v) -> string_of_entry k m) (bindings m)
- let warn ?may:(may=false) ?loc:(loc=[!Tracing.current_loc]) msg =
+ let warn ?may:(may=false) ?loc:(loc=[Option.get !Node.current_node]) msg =
match msg |> Str.split (Str.regexp "[ \n\r\x0c\t]+") with
- | [] -> (if may then Messages.warn else Messages.error) ~loc:(List.last loc) "%s" msg
+ | [] -> (if may then Messages.warn else Messages.error) ~loc:(Node (List.last loc)) "%s" msg
| h :: t ->
let warn_type = Messages.Category.from_string_list (h |> Str.split (Str.regexp "[.]"))
- in (if may then Messages.warn else Messages.error) ~loc:(List.last loc) ~category:warn_type "%a" (Pretty.docList ~sep:(Pretty.text " ") Pretty.text) t
+ in (if may then Messages.warn else Messages.error) ~loc:(Node (List.last loc)) ~category:warn_type "%a" (Pretty.docList ~sep:(Pretty.text " ") Pretty.text) t
(* getting keys from Cil Lvals *)
let sprint f x = Pretty.sprint ~width:80 (f () x)
diff --git a/src/cdomains/mHP.ml b/src/cdomains/mHP.ml
index 6133857e00..7715f3b7bb 100644
--- a/src/cdomains/mHP.ml
+++ b/src/cdomains/mHP.ml
@@ -55,7 +55,7 @@ let exists_definitely_not_started_in_joined (current,created) other_joined =
(** Must the thread with thread id other be already joined *)
let must_be_joined other joined =
if ConcDomain.ThreadSet.is_top joined then
- false
+ true (* top means all threads are joined, so [other] must be as well *)
else
List.mem other (ConcDomain.ThreadSet.elements joined)
diff --git a/src/cdomains/symbLocksDomain.ml b/src/cdomains/symbLocksDomain.ml
index 1075edea16..e5d049069b 100644
--- a/src/cdomains/symbLocksDomain.ml
+++ b/src/cdomains/symbLocksDomain.ml
@@ -1,6 +1,8 @@
open GoblintCil
open Pretty
+module M = Messages
+
module Exp =
struct
include CilType.Exp
@@ -203,6 +205,7 @@ struct
in
let rec helper exp =
match exp with
+ (* TODO: handle IndexPI like var_eq eq_set_clos? *)
| SizeOf _
| SizeOfE _
| SizeOfStr _
@@ -210,7 +213,6 @@ struct
| AlignOfE _
| UnOp _
| BinOp _
- | StartOf _
| Const _
| Question _
| Real _
@@ -220,6 +222,8 @@ struct
| Lval (Mem e, os) -> helper e @ [EDeref] @ conv_o os
| AddrOf (Var v, os) -> EVar v :: conv_o os @ [EAddr]
| AddrOf (Mem e, os) -> helper e @ [EDeref] @ conv_o os @ [EAddr]
+ | StartOf (Var v, os) -> EVar v :: conv_o os @ [EAddr]
+ | StartOf (Mem e, os) -> helper e @ [EDeref] @ conv_o os @ [EAddr]
| CastE (_,e) -> helper e
in
try helper exp
@@ -236,6 +240,7 @@ struct
| _ , _ -> raise (Invalid_argument "")
let from_exps a l : t option =
+ if M.tracing then M.tracel "symb_locks" "from_exps %a (%s) %a (%s)\n" d_plainexp a (ees_to_str (toEl a)) d_plainexp l (ees_to_str (toEl l));
let a, l = toEl a, toEl l in
(* ignore (printf "from_exps:\n %s\n %s\n" (ees_to_str a) (ees_to_str l)); *)
(*let rec fold_left2 f a xs ys =
diff --git a/src/domains/access.ml b/src/domains/access.ml
index d4ea953f22..2176d3aa8f 100644
--- a/src/domains/access.ml
+++ b/src/domains/access.ml
@@ -142,7 +142,7 @@ let get_val_type e (vo: var_o) (oo: off_o) : acc_typ =
let add_one side (e:exp) (kind:AccessKind.t) (conf:int) (ty:acc_typ) (lv:(varinfo*offs) option) a: unit =
if is_ignorable lv then () else begin
- let loc = !Tracing.current_loc in
+ let loc = Option.get !Node.current_node in
side ty lv (conf, kind, loc, e, a)
end
@@ -307,10 +307,10 @@ let add side e kind conf vo oo a =
module A =
struct
include Printable.Std
- type t = int * AccessKind.t * CilType.Location.t * CilType.Exp.t * MCPAccess.A.t [@@deriving eq, ord, hash]
+ type t = int * AccessKind.t * Node.t * CilType.Exp.t * MCPAccess.A.t [@@deriving eq, ord, hash]
- let pretty () (conf, kind, loc, e, lp) =
- Pretty.dprintf "%d, %a, %a, %a, %a" conf AccessKind.pretty kind CilType.Location.pretty loc CilType.Exp.pretty e MCPAccess.A.pretty lp
+ let pretty () (conf, kind, node, e, lp) =
+ Pretty.dprintf "%d, %a, %a, %a, %a" conf AccessKind.pretty kind CilType.Location.pretty (Node.location node) CilType.Exp.pretty e MCPAccess.A.pretty lp
include Printable.SimplePretty (
struct
@@ -437,7 +437,7 @@ let print_accesses (lv, ty) grouped_accs =
let debug = get_bool "dbg.debug" in
let race_threshold = get_int "warn.race-threshold" in
let msgs race_accs =
- let h (conf,kind,loc,e,a) =
+ let h (conf,kind,node,e,a) =
let d_msg () = dprintf "%a with %a (conf. %d)" AccessKind.pretty kind MCPAccess.A.pretty a conf in
let doc =
if debug then
@@ -445,7 +445,7 @@ let print_accesses (lv, ty) grouped_accs =
else
d_msg ()
in
- (doc, Some loc)
+ (doc, Some (Messages.Location.Node node))
in
AS.elements race_accs
|> List.map h
diff --git a/src/domains/queries.ml b/src/domains/queries.ml
index d633ffa862..c95c20a3f7 100644
--- a/src/domains/queries.ml
+++ b/src/domains/queries.ml
@@ -111,6 +111,7 @@ type _ t =
| MustJoinedThreads: ConcDomain.MustThreadSet.t t
| Invariant: invariant_context -> Invariant.t t
| WarnGlobal: Obj.t -> Unit.t t (** Argument must be of corresponding [Spec.V.t]. *)
+ | IterSysVars: VarQuery.t * Obj.t VarQuery.f -> Unit.t t (** [iter_vars] for [Constraints.FromSpec]. [Obj.t] represents [Spec.V.t]. *)
type 'a result = 'a
@@ -159,6 +160,7 @@ struct
| MustJoinedThreads -> (module ConcDomain.MustThreadSet)
| Invariant _ -> (module Invariant)
| WarnGlobal _ -> (module Unit)
+ | IterSysVars _ -> (module Unit)
(** Get bottom result for query. *)
let bot (type a) (q: a t): a result =
@@ -206,6 +208,7 @@ struct
| MustJoinedThreads -> ConcDomain.MustThreadSet.top ()
| Invariant _ -> Invariant.top ()
| WarnGlobal _ -> Unit.top ()
+ | IterSysVars _ -> Unit.top ()
end
(* The type any_query can't be directly defined in Any as t,
@@ -250,6 +253,7 @@ struct
| Any MustJoinedThreads -> 34
| Any (WarnGlobal _) -> 35
| Any (Invariant _) -> 36
+ | Any (IterSysVars _) -> 37
let compare a b =
let r = Stdlib.compare (order a) (order b) in
@@ -280,6 +284,7 @@ struct
| Any (EvalThread e1), Any (EvalThread e2) -> CilType.Exp.compare e1 e2
| Any (WarnGlobal vi1), Any (WarnGlobal vi2) -> compare (Hashtbl.hash vi1) (Hashtbl.hash vi2)
| Any (Invariant i1), Any (Invariant i2) -> compare_invariant_context i1 i2
+ | Any (IterSysVars (vq1, vf1)), Any (IterSysVars (vq2, vf2)) -> VarQuery.compare vq1 vq2 (* not comparing fs *)
(* only argumentless queries should remain *)
| _, _ -> Stdlib.compare (order a) (order b)
diff --git a/src/domains/setDomain.ml b/src/domains/setDomain.ml
index 46c9dbb22e..398e715328 100644
--- a/src/domains/setDomain.ml
+++ b/src/domains/setDomain.ml
@@ -91,6 +91,8 @@ struct
let hash x = fold (fun x y -> y + Base.hash x) x 0
+ let relift x = map Base.relift x
+
let pretty_diff () ((x:t),(y:t)): Pretty.doc =
if leq x y then dprintf "%s: These are fine!" (name ()) else
if is_bot y then dprintf "%s: %a instead of bot" (name ()) pretty x else begin
diff --git a/src/framework/analyses.ml b/src/framework/analyses.ml
index e63d5a423f..9aa0a6d4e4 100644
--- a/src/framework/analyses.ml
+++ b/src/framework/analyses.ml
@@ -12,9 +12,16 @@ module M = Messages
* other functions. *)
type fundecs = fundec list * fundec list * fundec list
+module type SysVar =
+sig
+ type t
+ val is_write_only: t -> bool
+end
+
module type VarType =
sig
include Hashtbl.HashedType
+ include SysVar with type t := t
val pretty_trace: unit -> t -> doc
val compare : t -> t -> int
@@ -46,6 +53,7 @@ struct
let pretty_trace () ((n,c) as x) =
if get_bool "dbg.trace.context" then dprintf "(%a, %a) on %a \n" Node.pretty_trace n LD.pretty c CilType.Location.pretty (getLocation x)
+ (* if get_bool "dbg.trace.context" then dprintf "(%a, %d) on %a" Node.pretty_trace n (LD.tag c) CilType.Location.pretty (getLocation x) *)
else dprintf "%a on %a" Node.pretty_trace n CilType.Location.pretty (getLocation x)
let printXml f (n,c) =
@@ -56,15 +64,59 @@ struct
let var_id (n,_) = Var.var_id n
let node (n,_) = n
+ let is_write_only _ = false
+end
+
+module type SpecSysVar =
+sig
+ include Printable.S
+ include SysVar with type t := t
end
-module GVarF (V: Printable.S) =
+module GVarF (V: SpecSysVar) =
struct
- include V
+ include Printable.Either (V) (CilType.Fundec)
+ let spec x = `Left x
+ let contexts x = `Right x
+
(* from Basetype.Variables *)
- let var_id _ = "globals"
+ let var_id = show
let node _ = MyCFG.Function Cil.dummyFunDec
let pretty_trace = pretty
+ let is_write_only = function
+ | `Left x -> V.is_write_only x
+ | `Right _ -> true
+end
+
+module GVarG (G: Lattice.S) (C: Printable.S) =
+struct
+ module CSet =
+ struct
+ include SetDomain.Make (
+ struct
+ include C
+ let printXml f c = BatPrintf.fprintf f "%a" printXml c (* wrap in for HTML printing *)
+ end
+ )
+ end
+
+ include Lattice.Lift2 (G) (CSet) (Printable.DefaultNames)
+
+ let spec = function
+ | `Bot -> G.bot ()
+ | `Lifted1 x -> x
+ | _ -> failwith "GVarG.spec"
+ let contexts = function
+ | `Bot -> CSet.bot ()
+ | `Lifted2 x -> x
+ | _ -> failwith "GVarG.contexts"
+ let create_spec spec = `Lifted1 spec
+ let create_contexts contexts = `Lifted2 contexts
+
+ let printXml f = function
+ | `Lifted1 x -> G.printXml f x
+ | `Lifted2 x -> BatPrintf.fprintf f "%a" CSet.printXml x
+ | x -> BatPrintf.fprintf f "%a" printXml x
end
@@ -157,7 +209,8 @@ struct
let printXmlWarning f () =
let one_text f Messages.Piece.{loc; text = m; _} =
match loc with
- | Some l ->
+ | Some loc ->
+ let l = Messages.Location.to_cil loc in
BatPrintf.fprintf f "\n%s" l.file l.line l.column (GU.escape m)
| None ->
() (* TODO: not outputting warning without location *)
@@ -365,7 +418,7 @@ sig
module D : Lattice.S
module G : Lattice.S
module C : Printable.S
- module V: Printable.S (** Global constraint variables. *)
+ module V: SpecSysVar (** Global constraint variables. *)
val name : unit -> string
@@ -436,13 +489,18 @@ type increment_data = {
server: bool;
old_data: analyzed_data option;
- changes: CompareCIL.change_info
+ changes: CompareCIL.change_info;
+
+ (* Globals for which the constraint
+ system unknowns should be restarted *)
+ restarting: VarQuery.t list;
}
let empty_increment_data ?(server=false) () = {
server;
old_data = None;
- changes = CompareCIL.empty_change_info ()
+ changes = CompareCIL.empty_change_info ();
+ restarting = []
}
(** A side-effecting system. *)
@@ -466,6 +524,8 @@ sig
(** Data used for incremental analysis *)
val increment : increment_data
+
+ val iter_vars: (v -> d) -> VarQuery.t -> v VarQuery.f -> unit
end
(** Any system of side-effecting equations over lattices. *)
@@ -481,6 +541,7 @@ sig
module G : Lattice.S
val increment : increment_data
val system : LVar.t -> ((LVar.t -> D.t) -> (LVar.t -> D.t -> unit) -> (GVar.t -> G.t) -> (GVar.t -> G.t -> unit) -> D.t) option
+ val iter_vars: (LVar.t -> D.t) -> (GVar.t -> G.t) -> VarQuery.t -> LVar.t VarQuery.f -> GVar.t VarQuery.f -> unit
end
(** A solver is something that can translate a system into a solution (hash-table).
@@ -545,8 +606,22 @@ struct
BatPrintf.fprintf f "\n%a\n%a" C.printXml c D.printXml d
end
-module VarinfoV = CilType.Varinfo (* TODO: or Basetype.Variables? *)
-module EmptyV = Printable.Empty
+module StdV =
+struct
+ let is_write_only _ = false
+end
+
+module VarinfoV =
+struct
+ include CilType.Varinfo (* TODO: or Basetype.Variables? *)
+ include StdV
+end
+
+module EmptyV =
+struct
+ include Printable.Empty
+ include StdV
+end
module UnitA =
struct
diff --git a/src/framework/constraints.ml b/src/framework/constraints.ml
index 4d8d1042a8..790bb80353 100644
--- a/src/framework/constraints.ml
+++ b/src/framework/constraints.ml
@@ -443,7 +443,7 @@ module FromSpec (S:Spec) (Cfg:CfgBackward) (I: Increment)
include GlobConstrSys with module LVar = VarF (S.C)
and module GVar = GVarF (S.V)
and module D = S.D
- and module G = S.G
+ and module G = GVarG (S.G) (S.C)
end
=
struct
@@ -454,7 +454,11 @@ struct
module LVar = VarF (S.C)
module GVar = GVarF (S.V)
module D = S.D
- module G = S.G
+ module G = GVarG (S.G) (S.C)
+
+ (* Two global invariants:
+ 1. S.V -> S.G -- used for Spec
+ 2. fundec -> set of S.C -- used for IterSysVars Node *)
(* Dummy module. No incremental analysis supported here*)
let increment = I.increment
@@ -464,7 +468,11 @@ struct
| _ :: _ :: _ -> S.sync ctx `Join
| _ -> S.sync ctx `Normal
- let common_ctx var edge prev_node pval (getl:lv -> ld) sidel getg sideg : (D.t, G.t, S.C.t, S.V.t) ctx * D.t list ref * (lval option * varinfo * exp list * D.t) list ref =
+ let side_context sideg f c =
+ if !GU.postsolving then
+ sideg (GVar.contexts f) (G.create_contexts (G.CSet.singleton c))
+
+ let common_ctx var edge prev_node pval (getl:lv -> ld) sidel getg sideg : (D.t, S.G.t, S.C.t, S.V.t) ctx * D.t list ref * (lval option * varinfo * exp list * D.t) list ref =
let r = ref [] in
let spawns = ref [] in
(* now watch this ... *)
@@ -477,10 +485,10 @@ struct
; context = snd var |> Obj.obj
; edge = edge
; local = pval
- ; global = getg
+ ; global = (fun g -> G.spec (getg (GVar.spec g)))
; spawn = spawn
; split = (fun (d:D.t) es -> assert (List.is_empty es); r := d::!r)
- ; sideg = sideg
+ ; sideg = (fun g d -> sideg (GVar.spec g) (G.create_spec d))
}
and spawn lval f args =
(* TODO: adjust ctx node/edge? *)
@@ -566,6 +574,10 @@ struct
common_join ctx d !r !spawns
let tf_entry var edge prev_node fd getl sidel getg sideg d =
+ (* Side effect function context here instead of at sidel to FunctionEntry,
+ because otherwise context for main functions (entrystates) will be missing or pruned during postsolving. *)
+ let c: unit -> S.C.t = snd var |> Obj.obj in
+ side_context sideg fd (c ());
let ctx, r, spawns = common_ctx var edge prev_node d getl sidel getg sideg in
common_join ctx (S.body ctx fd) !r !spawns
@@ -661,12 +673,15 @@ struct
let tf var getl sidel getg sideg prev_node (_,edge) d (f,t) =
let old_loc = !Tracing.current_loc in
let old_loc2 = !Tracing.next_loc in
- let _ = Tracing.current_loc := f in
- let _ = Tracing.next_loc := t in
- let d = tf var getl sidel getg sideg prev_node edge d in
- let _ = Tracing.current_loc := old_loc in
- let _ = Tracing.next_loc := old_loc2 in
- d
+ Tracing.current_loc := f;
+ Tracing.next_loc := t;
+ Fun.protect ~finally:(fun () ->
+ Tracing.current_loc := old_loc;
+ Tracing.next_loc := old_loc2
+ ) (fun () ->
+ let d = tf var getl sidel getg sideg prev_node edge d in
+ d
+ )
let tf (v,c) (edges, u) getl sidel getg sideg =
let pval = getl (u,c) in
@@ -676,12 +691,15 @@ struct
let tf (v,c) (e,u) getl sidel getg sideg =
let old_node = !current_node in
let old_context = !M.current_context in
- let _ = current_node := Some u in
+ current_node := Some u;
M.current_context := Some (Obj.repr c);
- let d = tf (v,c) (e,u) getl sidel getg sideg in
- let _ = current_node := old_node in
- M.current_context := old_context;
- d
+ Fun.protect ~finally:(fun () ->
+ current_node := old_node;
+ M.current_context := old_context
+ ) (fun () ->
+ let d = tf (v,c) (e,u) getl sidel getg sideg in
+ d
+ )
let system (v,c) =
match v with
@@ -711,6 +729,37 @@ struct
List.fold_left S.D.join (S.D.bot ()) xs
in
Some tf
+
+ let iter_vars getl getg vq fl fg =
+ (* vars for Spec *)
+ let rec ctx =
+ { ask = (fun (type a) (q: a Queries.t) -> S.query ctx q)
+ ; emit = (fun _ -> failwith "Cannot \"emit\" in query context.")
+ ; node = MyCFG.dummy_node (* TODO maybe ask should take a node (which could be used here) instead of a location *)
+ ; prev_node = MyCFG.dummy_node
+ ; control_context = (fun () -> ctx_failwith "No context in query context.")
+ ; context = (fun () -> ctx_failwith "No context in query context.")
+ ; edge = MyCFG.Skip
+ ; local = S.startstate Cil.dummyFunDec.svar (* bot and top both silently raise and catch Deadcode in DeadcodeLifter *)
+ ; global = (fun g -> G.spec (getg (GVar.spec g)))
+ ; spawn = (fun v d -> failwith "Cannot \"spawn\" in query context.")
+ ; split = (fun d es -> failwith "Cannot \"split\" in query context.")
+ ; sideg = (fun v g -> failwith "Cannot \"split\" in query context.")
+ }
+ in
+ let f v = fg (GVar.spec (Obj.obj v)) in
+ S.query ctx (IterSysVars (vq, f));
+
+ (* node vars for locals *)
+ match vq with
+ | Node {node; fundec} ->
+ let fd = Option.default_delayed (fun () -> Node.find_fundec node) fundec in
+ let cs = G.contexts (getg (GVar.contexts fd)) in
+ G.CSet.iter (fun c ->
+ fl (node, c)
+ ) cs
+ | _ ->
+ ()
end
(** Convert a non-incremental solver into an "incremental" solver.
@@ -756,6 +805,10 @@ struct
let node = function
| `L a -> LV.node a
| `G a -> GV.node a
+
+ let is_write_only = function
+ | `L a -> LV.is_write_only a
+ | `G a -> GV.is_write_only a
end
(** Translate a [GlobConstrSys] into a [EqConstrSys] *)
@@ -804,6 +857,9 @@ struct
let system = function
| `G _ -> None
| `L x -> Option.map conv (S.system x)
+
+ let iter_vars get vq f =
+ S.iter_vars (getL % get % l) (getG % get % g) vq (f % l) (f % g)
end
(** Splits a [EqConstrSys] solution into a [GlobConstrSys] solution with given [Hashtbl.S] for the [EqConstrSys]. *)
@@ -1004,27 +1060,118 @@ module DeadBranchLifter (S: Spec): Spec =
struct
include S
- let name () = "DeadBranch (" ^ name () ^ ")"
+ let name () = "DeadBranch (" ^ S.name () ^ ")"
- module Locmap = Deadcode.Locmap
+ (* Two global invariants:
+ 1. S.V -> S.G -- used for S
+ 2. node -> (exp -> flat bool) -- used for warnings *)
- let dead_branches = function true -> Deadcode.dead_branches_then | false -> Deadcode.dead_branches_else
+ module V =
+ struct
+ include Printable.Either (S.V) (Node)
+ let s x = `Left x
+ let node x = `Right x
+ let is_write_only = function
+ | `Left x -> S.V.is_write_only x
+ | `Right _ -> true
+ end
+
+ module EM = MapDomain.MapBot (Basetype.CilExp) (Basetype.Bools)
+
+ module G =
+ struct
+ include Lattice.Lift2 (S.G) (EM) (Printable.DefaultNames)
+
+ let s = function
+ | `Bot -> S.G.bot ()
+ | `Lifted1 x -> x
+ | _ -> failwith "DeadBranchLifter.s"
+ let node = function
+ | `Bot -> EM.bot ()
+ | `Lifted2 x -> x
+ | _ -> failwith "DeadBranchLifter.node"
+ let create_s s = `Lifted1 s
+ let create_node node = `Lifted2 node
+
+ let printXml f = function
+ | `Lifted1 x -> S.G.printXml f x
+ | `Lifted2 x -> BatPrintf.fprintf f "%a" EM.printXml x
+ | x -> BatPrintf.fprintf f "%a" printXml x
+ end
+
+ let conv (ctx: (_, G.t, _, V.t) ctx): (_, S.G.t, _, S.V.t) ctx =
+ { ctx with
+ global = (fun v -> G.s (ctx.global (V.s v)));
+ sideg = (fun v g -> ctx.sideg (V.s v) (G.create_s g));
+ }
+
+ let query ctx (type a) (q: a Queries.t): a Queries.result =
+ match q with
+ | WarnGlobal g ->
+ let g: V.t = Obj.obj g in
+ begin match g with
+ | `Left g ->
+ S.query (conv ctx) (WarnGlobal (Obj.repr g))
+ | `Right g ->
+ let em = G.node (ctx.global (V.node g)) in
+ EM.iter (fun exp tv ->
+ match tv with
+ | `Lifted tv ->
+ let loc = Node.location g in (* TODO: looking up location now doesn't work nicely with incremental *)
+ let cilinserted = if loc.synthetic then "(possibly inserted by CIL) " else "" in
+ M.warn ~loc:(Node g) ~tags:[CWE (if tv then 571 else 570)] ~category:Deadcode "condition '%a' %sis always %B" d_exp exp cilinserted tv
+ | `Bot (* all branches dead? can happen at our inserted Neg(1)-s because no Pos(1) *)
+ | `Top -> (* may be both true and false *)
+ ()
+ ) em;
+ end
+ | IterSysVars (vq, vf) ->
+ (* vars for S *)
+ let vf' x = vf (Obj.repr (V.s (Obj.obj x))) in
+ S.query (conv ctx) (IterSysVars (vq, vf'));
+
+ (* node vars for dead branches *)
+ begin match vq with
+ | Node {node; _} ->
+ vf (Obj.repr (V.node node))
+ | _ ->
+ ()
+ end
+ | _ ->
+ S.query (conv ctx) q
+
+
+ let branch ctx = S.branch (conv ctx)
let branch ctx exp tv =
if !GU.postsolving then (
- Locmap.replace Deadcode.dead_branches_cond !Tracing.current_loc exp;
try
let r = branch ctx exp tv in
(* branch is live *)
- Locmap.replace (dead_branches tv) !Tracing.current_loc false; (* set to live (false) *)
+ ctx.sideg (V.node ctx.prev_node) (G.create_node (EM.singleton exp (`Lifted tv))); (* record expression with reached tv *)
r
with Deadcode ->
(* branch is dead *)
- Locmap.modify_def true !Tracing.current_loc Fun.id (dead_branches tv); (* set to dead (true) if not mem, otherwise keep existing (Fun.id) since it may be live (false) in another context *)
+ ctx.sideg (V.node ctx.prev_node) (G.create_node (EM.singleton exp `Bot)); (* record expression without reached tv *)
raise Deadcode
)
- else
+ else (
+ ctx.sideg (V.node ctx.prev_node) (G.create_node (EM.bot ())); (* create global variable during solving, to allow postsolving leq hack to pass verify *)
branch ctx exp tv
+ )
+
+ let assign ctx = S.assign (conv ctx)
+ let vdecl ctx = S.vdecl (conv ctx)
+ let enter ctx = S.enter (conv ctx)
+ let body ctx = S.body (conv ctx)
+ let return ctx = S.return (conv ctx)
+ let combine ctx = S.combine (conv ctx)
+ let special ctx = S.special (conv ctx)
+ let threadenter ctx = S.threadenter (conv ctx)
+ let threadspawn ctx lv f args fctx = S.threadspawn (conv ctx) lv f args (conv fctx)
+ let sync ctx = S.sync (conv ctx)
+ let skip ctx = S.skip (conv ctx)
+ let asm ctx = S.asm (conv ctx)
end
module CompareGlobSys
@@ -1032,12 +1179,13 @@ module CompareGlobSys
(Sys:GlobConstrSys with module LVar = VarF (S.C)
and module GVar = GVarF (S.V)
and module D = S.D
- and module G = S.G)
+ and module G = GVarG (S.G) (S.C))
(LH:Hashtbl.S with type key=Sys.LVar.t)
(GH:Hashtbl.S with type key=Sys.GVar.t)
=
struct
open S
+ module G = Sys.G
module PP = Hashtbl.Make (Node)
@@ -1055,14 +1203,19 @@ struct
f_eq ()
else if b1 then begin
if get_bool "dbg.compare_runs.diff" then
- ignore (Pretty.printf "Global %a is more precise using left:\n%a\n" Sys.GVar.pretty_trace k G.pretty_diff (v1,v2));
+ ignore (Pretty.printf "Global %a is more precise using left:\n%a\n" Sys.GVar.pretty_trace k G.pretty_diff (v2,v1));
f_le ()
end else if b2 then begin
if get_bool "dbg.compare_runs.diff" then
ignore (Pretty.printf "Global %a is more precise using right:\n%a\n" Sys.GVar.pretty_trace k G.pretty_diff (v1,v2));
f_gr ()
- end else
+ end else begin
+ if get_bool "dbg.compare_runs.diff" then (
+ ignore (Pretty.printf "Global %a is incomparable (diff):\n%a\n" Sys.GVar.pretty_trace k G.pretty_diff (v1,v2));
+ ignore (Pretty.printf "Global %a is incomparable (reverse diff):\n%a\n" Sys.GVar.pretty_trace k G.pretty_diff (v2,v1));
+ );
f_uk ()
+ end
in
GH.iter f g1;
Printf.printf "globals:\tequal = %d\tleft = %d\tright = %d\tincomparable = %d\n" !eq !le !gr !uk
@@ -1078,14 +1231,19 @@ struct
incr eq
else if b1 then begin
if get_bool "dbg.compare_runs.diff" then
- ignore (Pretty.printf "%a @@ %a is more precise using left:\n%a\n" Node.pretty_plain k CilType.Location.pretty (Node.location k) D.pretty_diff (v1,v2));
+ ignore (Pretty.printf "%a @@ %a is more precise using left:\n%a\n" Node.pretty_plain k CilType.Location.pretty (Node.location k) D.pretty_diff (v2,v1));
incr le
end else if b2 then begin
if get_bool "dbg.compare_runs.diff" then
ignore (Pretty.printf "%a @@ %a is more precise using right:\n%a\n" Node.pretty_plain k CilType.Location.pretty (Node.location k) D.pretty_diff (v1,v2));
incr gr
- end else
+ end else begin
+ if get_bool "dbg.compare_runs.diff" then (
+ ignore (Pretty.printf "%a @@ %a is incomparable (diff):\n%a\n" Node.pretty_plain k CilType.Location.pretty (Node.location k) D.pretty_diff (v1,v2));
+ ignore (Pretty.printf "%a @@ %a is incomparable (reverse diff):\n%a\n" Node.pretty_plain k CilType.Location.pretty (Node.location k) D.pretty_diff (v2,v1));
+ );
incr uk
+ end
in
PP.iter f h1;
(* let k1 = Set.of_enum @@ PP.keys h1 in
@@ -1096,7 +1254,7 @@ struct
Printf.printf "locals: \tequal = %d\tleft = %d\tright = %d\tincomparable = %d\n" !eq !le !gr !uk
let compare_locals_ctx h1 h2 =
- let eq, le, gr, uk, no2 = ref 0, ref 0, ref 0, ref 0, ref 0 in
+ let eq, le, gr, uk, no2, no1 = ref 0, ref 0, ref 0, ref 0, ref 0, ref 0 in
let f_eq () = incr eq in
let f_le () = incr le in
let f_gr () = incr gr in
@@ -1109,22 +1267,31 @@ struct
if b1 && b2 then
f_eq ()
else if b1 then begin
- (* if get_bool "dbg.compare_runs.diff" then *)
- (* ignore (Pretty.printf "%a @@ %a is more precise using left:\n%a\n" pretty_node k CilType.Location.pretty (getLoc k) D.pretty_diff (v1,v2)); *)
+ if get_bool "dbg.compare_runs.diff" then
+ ignore (Pretty.printf "%a is more precise using left:\n%a\n" Sys.LVar.pretty_trace k D.pretty_diff (v2,v1));
f_le ()
end else if b2 then begin
- (* if get_bool "dbg.compare_runs.diff" then *)
- (* ignore (Pretty.printf "%a @@ %a is more precise using right:\n%a\n" pretty_node k CilType.Location.pretty (getLoc k) D.pretty_diff (v1,v2)); *)
+ if get_bool "dbg.compare_runs.diff" then
+ ignore (Pretty.printf "%a is more precise using right:\n%a\n" Sys.LVar.pretty_trace k D.pretty_diff (v1,v2));
f_gr ()
- end else
+ end else begin
+ if get_bool "dbg.compare_runs.diff" then (
+ ignore (Pretty.printf "%a is incomparable (diff):\n%a\n" Sys.LVar.pretty_trace k D.pretty_diff (v1,v2));
+ ignore (Pretty.printf "%a is incomparable (reverse diff):\n%a\n" Sys.LVar.pretty_trace k D.pretty_diff (v2,v1));
+ );
f_uk ()
+ end
in
LH.iter f h1;
+ let f k v2 =
+ if not (LH.mem h1 k) then incr no1
+ in
+ LH.iter f h2;
(* let k1 = Set.of_enum @@ PP.keys h1 in *)
(* let k2 = Set.of_enum @@ PP.keys h2 in *)
(* let o1 = Set.cardinal @@ Set.diff k1 k2 in *)
(* let o2 = Set.cardinal @@ Set.diff k2 k1 in *)
- Printf.printf "locals_ctx:\tequal = %d\tleft = %d\tright = %d\tincomparable = %d\tno_ctx_in_right = %d\n" !eq !le !gr !uk !no2
+ Printf.printf "locals_ctx:\tequal = %d\tleft = %d\tright = %d\tincomparable = %d\tno_ctx_in_right = %d\tno_ctx_in_left = %d\n" !eq !le !gr !uk !no2 !no1
let compare (name1,name2) (l1,g1) (l2,g2) =
let one_ctx (n,_) v h =
@@ -1193,6 +1360,7 @@ struct
include Node
let var_id _ = "nodes"
let node x = x
+ let is_write_only _ = false
end
module NH = Hashtbl.Make (Node)
@@ -1215,3 +1383,29 @@ struct
ignore (Pretty.printf "Nodes comparison summary: %t\n" (fun () -> msg));
print_newline ();
end
+
+(** [EqConstrSys] where [current_var] indicates the variable whose right-hand side is currently being evaluated. *)
+module CurrentVarEqConstrSys (S: EqConstrSys) =
+struct
+ let current_var = ref None
+
+ module S =
+ struct
+ include S
+
+ let system x =
+ match S.system x with
+ | None -> None
+ | Some f ->
+ let f' get set =
+ let old_current_var = !current_var in
+ current_var := Some x;
+ Fun.protect ~finally:(fun () ->
+ current_var := old_current_var
+ ) (fun () ->
+ f get set
+ )
+ in
+ Some f'
+ end
+end
diff --git a/src/framework/control.ml b/src/framework/control.ml
index d0a4ab7f7f..c859814cb0 100644
--- a/src/framework/control.ml
+++ b/src/framework/control.ml
@@ -80,7 +80,6 @@ struct
(* print out information about dead code *)
let print_dead_code (xs:Result.t) uncalled_fn_loc =
- let dead_locations : unit Deadcode.Locmap.t = Deadcode.Locmap.create 10 in
let module NH = Hashtbl.Make (Node) in
let live_nodes : unit NH.t = NH.create 10 in
let count = ref 0 in (* Is only populated if "ana.dead-code.lines" or "ana.dead-code.branches" is true *)
@@ -97,8 +96,7 @@ struct
let add_file = StringMap.modify_def BatISet.empty f.svar.vname add_fun in
let is_dead = LT.for_all (fun (_,x,f) -> Spec.D.is_bot x) v in
if is_dead then (
- dead_lines := StringMap.modify_def StringMap.empty l.file add_file !dead_lines;
- Deadcode.Locmap.add dead_locations l ();
+ dead_lines := StringMap.modify_def StringMap.empty l.file add_file !dead_lines
) else (
live_lines := StringMap.modify_def StringMap.empty l.file add_file !live_lines;
NH.add live_nodes n ()
@@ -139,7 +137,7 @@ struct
synthetic = false;
}
in
- (doc, Some loc)
+ (doc, Some (Messages.Location.CilLocation loc)) (* CilLocation is fine because always printed from scratch *)
in
let msgs =
BatISet.fold_range (fun b e acc ->
@@ -160,21 +158,6 @@ struct
);
printf "Total lines (logical LoC): %d\n" (live_count + !count + uncalled_fn_loc); (* We can only give total LoC if we counted dead code *)
);
- let str = function true -> "then" | false -> "else" in
- let cilinserted = function true -> "(possibly inserted by CIL) " | false -> "" in
- let report tv (loc, dead) =
- match dead, Deadcode.Locmap.find_option Deadcode.dead_branches_cond loc with
- | true, Some exp -> M.warn ~loc ~category:Deadcode ~tags:[CWE (if tv then 570 else 571)] "the %s branch %sover expression '%a' is dead" (str tv) (cilinserted loc.synthetic) d_exp exp
- | true, None -> M.warn ~loc ~category:Deadcode ~tags:[CWE (if tv then 570 else 571)] "an %s branch %sis dead" (str tv) (cilinserted loc.synthetic)
- | _ -> ()
- in
- if get_bool "ana.dead-code.branches" then (
- let by_fst (a,_) (b,_) = Stdlib.compare a b in
- Deadcode.Locmap.to_list Deadcode.dead_branches_then |> List.sort by_fst |> List.iter (report true) ;
- Deadcode.Locmap.to_list Deadcode.dead_branches_else |> List.sort by_fst |> List.iter (report false) ;
- Deadcode.Locmap.clear Deadcode.dead_branches_then;
- Deadcode.Locmap.clear Deadcode.dead_branches_else
- );
NH.mem live_nodes
(* convert result that can be out-put *)
@@ -199,7 +182,7 @@ struct
(* If the function is not defined, and yet has been included to the
* analysis result, we generate a warning. *)
with Not_found ->
- Messages.warn ~category:Analyzer "Calculated state for undefined function: unexpected node %a" Node.pretty_plain n
+ Messages.debug ~category:Analyzer ~loc:(CilLocation loc) "Calculated state for undefined function: unexpected node %a" Node.pretty_trace n
in
LHT.iter add_local_var h;
res
@@ -213,7 +196,7 @@ struct
let make_global_fast_xml f g =
let open Printf in
let print_globals k v =
- fprintf f "\n%s%a" (XmlUtil.escape (Spec.V.show k)) Spec.G.printXml v;
+ fprintf f "\n%s%a" (XmlUtil.escape (EQSys.GVar.show k)) EQSys.G.printXml v;
in
GHT.iter print_globals g
in
@@ -246,14 +229,14 @@ struct
(* Simulate globals before analysis. *)
(* TODO: make extern/global inits part of constraint system so all of this would be unnecessary. *)
let gh = GHT.create 13 in
- let getg v = GHT.find_default gh v (Spec.G.bot ()) in
+ let getg v = GHT.find_default gh v (EQSys.G.bot ()) in
let sideg v d =
- if M.tracing then M.trace "global_inits" "sideg %a = %a\n" Spec.V.pretty v Spec.G.pretty d;
- GHT.replace gh v (Spec.G.join (getg v) d)
+ if M.tracing then M.trace "global_inits" "sideg %a = %a\n" EQSys.GVar.pretty v EQSys.G.pretty d;
+ GHT.replace gh v (EQSys.G.join (getg v) d)
in
(* Old-style global function for context.
* This indirectly prevents global initializers from depending on each others' global side effects, which would require proper solving. *)
- let getg v = Spec.G.bot () in
+ let getg v = EQSys.G.bot () in
(* analyze cil's global-inits function to get a starting state *)
let do_global_inits (file: file) : Spec.D.t * fundec list =
@@ -266,10 +249,10 @@ struct
; context = (fun () -> ctx_failwith "Global initializers have no context.")
; edge = MyCFG.Skip
; local = Spec.D.top ()
- ; global = getg
+ ; global = (fun g -> EQSys.G.spec (getg (EQSys.GVar.spec g)))
; spawn = (fun _ -> failwith "Global initializers should never spawn threads. What is going on?")
; split = (fun _ -> failwith "Global initializers trying to split paths.")
- ; sideg = sideg
+ ; sideg = (fun g d -> sideg (EQSys.GVar.spec g) (EQSys.G.create_spec d))
}
in
let edges = CfgTools.getGlobalInits file in
@@ -310,7 +293,7 @@ struct
let print_globals glob =
let out = M.get_out (Spec.name ()) !GU.out in
let print_one v st =
- ignore (Pretty.fprintf out "%a -> %a\n" EQSys.GVar.pretty_trace v Spec.G.pretty st)
+ ignore (Pretty.fprintf out "%a -> %a\n" EQSys.GVar.pretty_trace v EQSys.G.pretty st)
in
GHT.iter print_one glob
in
@@ -365,10 +348,10 @@ struct
; context = (fun () -> ctx_failwith "enter_func has no context.")
; edge = MyCFG.Skip
; local = st
- ; global = getg
+ ; global = (fun g -> EQSys.G.spec (getg (EQSys.GVar.spec g)))
; spawn = (fun _ -> failwith "Bug1: Using enter_func for toplevel functions with 'otherstate'.")
; split = (fun _ -> failwith "Bug2: Using enter_func for toplevel functions with 'otherstate'.")
- ; sideg = sideg
+ ; sideg = (fun g d -> sideg (EQSys.GVar.spec g) (EQSys.G.create_spec d))
}
in
let args = List.map (fun x -> MyCFG.unknown_exp) fd.sformals in
@@ -397,10 +380,10 @@ struct
; context = (fun () -> ctx_failwith "enter_func has no context.")
; edge = MyCFG.Skip
; local = st
- ; global = getg
+ ; global = (fun g -> EQSys.G.spec (getg (EQSys.GVar.spec g)))
; spawn = (fun _ -> failwith "Bug1: Using enter_func for toplevel functions with 'otherstate'.")
; split = (fun _ -> failwith "Bug2: Using enter_func for toplevel functions with 'otherstate'.")
- ; sideg = sideg
+ ; sideg = (fun g d -> sideg (EQSys.GVar.spec g) (EQSys.G.create_spec d))
}
in
(* TODO: don't hd *)
@@ -556,7 +539,7 @@ struct
let cnt = Cilfacade.countLoc fn in
uncalled_dead := !uncalled_dead + cnt;
if get_bool "ana.dead-code.functions" then
- M.warn ~loc ~category:Deadcode "Function \"%a\" will never be called: %dLoC" CilType.Fundec.pretty fn cnt
+ M.warn ~loc:(CilLocation loc) ~category:Deadcode "Function \"%a\" will never be called: %dLoC" CilType.Fundec.pretty fn cnt (* CilLocation is fine because always printed from scratch *)
| _ -> ()
in
List.iter print_and_calculate_uncalled file.globals;
@@ -597,7 +580,7 @@ struct
; context = (fun () -> ctx_failwith "No context in query context.")
; edge = MyCFG.Skip
; local = local
- ; global = GHT.find gh
+ ; global = (fun g -> EQSys.G.spec (GHT.find gh (EQSys.GVar.spec g)))
; spawn = (fun v d -> failwith "Cannot \"spawn\" in query context.")
; split = (fun d es -> failwith "Cannot \"split\" in query context.")
; sideg = (fun v g -> failwith "Cannot \"split\" in query context.")
@@ -617,7 +600,7 @@ struct
(* Use "normal" constraint solving *)
let timeout_reached () =
- M.error ~loc:!Tracing.current_loc "Timeout reached!";
+ M.error "Timeout reached!";
(* let module S = Generic.SolverStats (EQSys) (LHT) in *)
(* Can't call Generic.SolverStats...print_stats :(
print_stats is triggered by dbg.solver-signal, so we send that signal to ourself in maingoblint before re-raising Timeout.
@@ -639,7 +622,7 @@ struct
CfgTools.dead_code_cfg file (module Cfg : CfgBidir) liveness;
let warn_global g v =
- (* ignore (Pretty.printf "warn_global %a %a\n" CilType.Varinfo.pretty g EQSys.G.pretty v); *)
+ (* ignore (Pretty.printf "warn_global %a %a\n" EQSys.GVar.pretty_trace g EQSys.G.pretty v); *)
(* build a ctx for using the query system *)
let rec ctx =
{ ask = (fun (type a) (q: a Queries.t) -> Spec.query ctx q)
@@ -650,13 +633,17 @@ struct
; context = (fun () -> ctx_failwith "No context in query context.")
; edge = MyCFG.Skip
; local = snd (List.hd startvars) (* bot and top both silently raise and catch Deadcode in DeadcodeLifter *)
- ; global = (fun v -> try GHT.find gh v with Not_found -> EQSys.G.bot ())
+ ; global = (fun v -> EQSys.G.spec (try GHT.find gh (EQSys.GVar.spec v) with Not_found -> EQSys.G.bot ()))
; spawn = (fun v d -> failwith "Cannot \"spawn\" in query context.")
; split = (fun d es -> failwith "Cannot \"split\" in query context.")
; sideg = (fun v g -> failwith "Cannot \"split\" in query context.")
}
in
- Spec.query ctx (WarnGlobal (Obj.repr g))
+ match g with
+ | `Left g -> (* Spec global *)
+ Spec.query ctx (WarnGlobal (Obj.repr g))
+ | `Right _ -> (* contexts global *)
+ ()
in
Stats.time "warn_global" (GHT.iter warn_global) gh;
diff --git a/src/framework/myCFG.ml b/src/framework/myCFG.ml
index 8689ae729d..6b986f27fc 100644
--- a/src/framework/myCFG.ml
+++ b/src/framework/myCFG.ml
@@ -44,7 +44,7 @@ end
module NodeH = BatHashtbl.Make (Node)
-let current_node : node option ref = ref None
+let current_node = Node.current_node
let current_cfg : (module CfgBidir) ref =
let module Cfg =
struct
diff --git a/src/framework/node.ml b/src/framework/node.ml
index 2b275af1ed..187775f14c 100644
--- a/src/framework/node.ml
+++ b/src/framework/node.ml
@@ -3,18 +3,7 @@ open Pretty
include Printable.Std
-(** A node in the Control Flow Graph is either a statement or function. Think of
- * the function node as last node that all the returning nodes point to. So
- * the result of the function call is contained in the function node. *)
-type t =
- | Statement of CilType.Stmt.t
- (** The statements as identified by CIL *)
- (* The stmt in a Statement node is misleading because nodes are program points between transfer functions (edges), which actually correspond to statement execution. *)
- | FunctionEntry of CilType.Fundec.t
- (** *)
- | Function of CilType.Fundec.t
- (** The variable information associated with the function declaration. *)
-[@@deriving eq, ord, hash, to_yojson]
+include Node0
let name () = "node"
@@ -35,8 +24,8 @@ let pretty_plain_short () = function
(** Pretty node for solver variable tracing with short stmt. *)
let pretty_trace () = function
| Statement stmt -> dprintf "node %d \"%a\"" stmt.sid Cilfacade.stmt_pretty_short stmt
- | Function fd -> dprintf "call of %s" fd.svar.vname
- | FunctionEntry fd -> dprintf "entry state of %s" fd.svar.vname
+ | Function fd -> dprintf "call of %s (%d)" fd.svar.vname fd.svar.vid
+ | FunctionEntry fd -> dprintf "entry state of %s (%d)" fd.svar.vname fd.svar.vid
(** Output functions for Printable interface *)
let pretty () x = pretty_trace () x
@@ -60,12 +49,6 @@ let show_cfg = function
| FunctionEntry fd -> fd.svar.vname ^ "()"
-let location (node: t) =
- match node with
- | Statement stmt -> Cilfacade.get_stmtLoc stmt
- | Function fd -> fd.svar.vdecl
- | FunctionEntry fd -> fd.svar.vdecl
-
(** Find [fundec] which the node is in. In an incremental run this might yield old fundecs for pseudo-return nodes from the old file. *)
let find_fundec (node: t) =
match node with
diff --git a/src/framework/node0.ml b/src/framework/node0.ml
new file mode 100644
index 0000000000..0bcfa13510
--- /dev/null
+++ b/src/framework/node0.ml
@@ -0,0 +1,22 @@
+(** Node functions to avoid dependency cycles. *)
+
+(** A node in the Control Flow Graph is either a statement or function. Think of
+ * the function node as last node that all the returning nodes point to. So
+ * the result of the function call is contained in the function node. *)
+type t =
+ | Statement of CilType.Stmt.t
+ (** The statements as identified by CIL *)
+ (* The stmt in a Statement node is misleading because nodes are program points between transfer functions (edges), which actually correspond to statement execution. *)
+ | FunctionEntry of CilType.Fundec.t
+ (** *)
+ | Function of CilType.Fundec.t
+ (** The variable information associated with the function declaration. *)
+[@@deriving eq, ord, hash, to_yojson]
+
+let location (node: t) =
+ match node with
+ | Statement stmt -> Cilfacade0.get_stmtLoc stmt
+ | Function fd -> fd.svar.vdecl
+ | FunctionEntry fd -> fd.svar.vdecl
+
+let current_node: t option ref = ref None
diff --git a/src/framework/varQuery.ml b/src/framework/varQuery.ml
new file mode 100644
index 0000000000..40778e0700
--- /dev/null
+++ b/src/framework/varQuery.ml
@@ -0,0 +1,40 @@
+open GoblintCil
+
+type t =
+ | Global of CilType.Varinfo.t
+ | Node of {node: Node.t; fundec: CilType.Fundec.t option}
+[@@deriving ord]
+
+type 'v f = 'v -> unit
+
+let varinfo_from_global (g : Cil.global) : Cil.varinfo option = match g with
+ | GFun (f, _) -> Some f.svar
+ | GVar (v, _, _) -> Some v
+ | GVarDecl (v, _) -> Some v
+ | _ -> None
+
+let varquery_from_global (g : Cil.global) : t option = match g with
+ | GFun (f, _) -> Some (Node {node = FunctionEntry f; fundec = Some f})
+ | GVar (v, _, _) -> Some (Global v)
+ | GVarDecl (v, _) -> Some (Global v)
+ | _ -> None
+
+let varqueries_from_names (file: Cil.file) (names: string list): t list * string list =
+ let module SM = Set.Make(Printable.Strings) in
+ let set = SM.of_list names in
+
+ (* Find list of [Cil.global]s that have one of the queried names, and a set of the found names *)
+ let globals, matched =
+ Cil.foldGlobals file (fun ((globs, matched) as acc) g ->
+ match varinfo_from_global g, varquery_from_global g with
+ | Some v, Some vq ->
+ begin match SM.mem v.vname set with
+ | true -> (vq::globs, SM.add v.vname matched)
+ | _ -> acc
+ end
+ | None, None -> acc
+ | _, _ -> assert false
+ ) ([], SM.empty) in
+ (* List of queried but not found names *)
+ let unmatched = List.filter (fun s -> not @@ SM.mem s matched) names in
+ globals, unmatched
diff --git a/src/framework/varQuery.mli b/src/framework/varQuery.mli
new file mode 100644
index 0000000000..77894b62ef
--- /dev/null
+++ b/src/framework/varQuery.mli
@@ -0,0 +1,13 @@
+open GoblintCil
+
+type t =
+ | Global of Cil.varinfo
+ | Node of {node: Node.t; fundec : Cil.fundec option} (** Optional [fundec] override to allow querying old state in incremental. *)
+[@@deriving ord]
+
+type 'v f = 'v -> unit
+
+(** Takes a [Cil.file] and a list of names of globals.contents
+ Returns a list of [VarQuery.t]s of globals whose [vname] is contained in the argument list,
+ and the list of names for which no global with the name could be found. *)
+val varqueries_from_names : Cil.file -> string list -> t list * string list
diff --git a/src/goblint.ml b/src/goblint.ml
index 64fbb80f17..1770dfd0af 100644
--- a/src/goblint.ml
+++ b/src/goblint.ml
@@ -24,14 +24,30 @@ let main () =
print_endline (localtime ());
print_endline Goblintutil.command_line;
);
- let file = Fun.protect ~finally:GoblintDir.finalize preprocess_and_merge in
- if get_bool "server.enabled" then Server.start file else (
- let changeInfo = if GobConfig.get_bool "incremental.load" || GobConfig.get_bool "incremental.save" then diff_and_rename file else Analyses.empty_increment_data () in
- file|> do_analyze changeInfo;
+ let file = lazy (Fun.protect ~finally:GoblintDir.finalize preprocess_parse_merge) in
+ if get_bool "server.enabled" then (
+ let file =
+ if get_bool "server.reparse" then
+ None
+ else
+ Some (Lazy.force file)
+ in
+ Server.start file
+ )
+ else (
+ let file = Lazy.force file in
+ let changeInfo =
+ if GobConfig.get_bool "incremental.load" || GobConfig.get_bool "incremental.save" then
+ diff_and_rename file
+ else
+ Analyses.empty_increment_data ()
+ in
+ file |> do_analyze changeInfo;
do_stats ();
do_html_output ();
do_gobview ();
- if !verified = Some false then exit 3) (* verifier failed! *)
+ if !verified = Some false then exit 3 (* verifier failed! *)
+ )
with
| Exit ->
do_stats ();
diff --git a/src/incremental/compareCIL.ml b/src/incremental/compareCIL.ml
index 3cbd3f925b..6d70a229a9 100644
--- a/src/incremental/compareCIL.ml
+++ b/src/incremental/compareCIL.ml
@@ -22,14 +22,29 @@ type changed_global = {
diff: nodes_diff option
}
+module VarinfoSet = Set.Make(CilType.Varinfo)
+
type change_info = {
mutable changed: changed_global list;
mutable unchanged: unchanged_global list;
mutable removed: global list;
- mutable added: global list
+ mutable added: global list;
+ mutable exclude_from_rel_destab: VarinfoSet.t;
+ (** Set of functions that are to be force-reanalyzed.
+ These functions are additionally included in the [changed] field, among the other changed globals. *)
}
-let empty_change_info () : change_info = {added = []; removed = []; changed = []; unchanged = []}
+let empty_change_info () : change_info =
+ {added = []; removed = []; changed = []; unchanged = []; exclude_from_rel_destab = VarinfoSet.empty}
+
+(* 'ChangedFunHeader' is used for functions whose varinfo or formal parameters changed. 'Changed' is used only for
+ * changed functions whose header is unchanged and changed non-function globals *)
+type change_status = Unchanged | Changed | ChangedFunHeader of Cil.fundec | ForceReanalyze of Cil.fundec
+
+(** Given a boolean that indicates whether the code object is identical to the previous version, returns the corresponding [change_status]*)
+let unchanged_to_change_status = function
+ | true -> Unchanged
+ | false -> Changed
let should_reanalyze (fdec: Cil.fundec) =
List.mem fdec.svar.vname (GobConfig.get_string_list "incremental.force-reanalyze.funs")
@@ -37,65 +52,63 @@ let should_reanalyze (fdec: Cil.fundec) =
(* If some CFGs of the two functions to be compared are provided, a fine-grained CFG comparison is done that also determines which
* nodes of the function changed. If on the other hand no CFGs are provided, the "old" AST comparison on the CIL.file is
* used for functions. Then no information is collected regarding which parts/nodes of the function changed. *)
-let eqF (a: Cil.fundec) (b: Cil.fundec) (cfgs : (cfg * (cfg * cfg)) option) (global_rename_mapping: method_rename_assumptions) =
- let emptyRenameMapping = (StringMap.empty, VarinfoMap.empty) in
-
- (* Compares the two varinfo lists, returning as a first element, if the size of the two lists are equal,
- * and as a second a rename_mapping, holding the rename assumptions *)
- let rec rename_mapping_aware_compare (alocals: varinfo list) (blocals: varinfo list) (rename_mapping: string StringMap.t) = match alocals, blocals with
- | [], [] -> true, rename_mapping
- | origLocal :: als, nowLocal :: bls ->
- let new_mapping = StringMap.add origLocal.vname nowLocal.vname rename_mapping in
-
- (*TODO: maybe optimize this with eq_varinfo*)
- rename_mapping_aware_compare als bls new_mapping
- | _, _ -> false, rename_mapping
- in
-
- let unchangedHeader, headerRenameMapping = match cfgs with
- | None -> (
- let headerSizeEqual, headerRenameMapping = rename_mapping_aware_compare a.sformals b.sformals (StringMap.empty) in
- let actHeaderRenameMapping = (headerRenameMapping, global_rename_mapping) in
- eq_varinfo a.svar b.svar actHeaderRenameMapping && GobList.equal (eq_varinfo2 actHeaderRenameMapping) a.sformals b.sformals, headerRenameMapping
- )
- | Some _ -> (
- eq_varinfo a.svar b.svar emptyRenameMapping && GobList.equal (eq_varinfo2 emptyRenameMapping) a.sformals b.sformals, StringMap.empty
- )
- in
-
- let identical, diffOpt =
- if should_reanalyze a then
- false, None
+let eqF (old: Cil.fundec) (current: Cil.fundec) (cfgs : (cfg * (cfg * cfg)) option) (global_rename_mapping: method_rename_assumptions) =
+ if should_reanalyze current then
+ ForceReanalyze current, None
+ else
+ (* let unchangedHeader = eq_varinfo old.svar current.svar && GobList.equal eq_varinfo old.sformals current.sformals in *)
+ let emptyRenameMapping = (StringMap.empty, VarinfoMap.empty) in
+
+ (* Compares the two varinfo lists, returning as a first element, if the size of the two lists are equal,
+ and as a second a rename_mapping, holding the rename assumptions *)
+ let rec rename_mapping_aware_compare (alocals: varinfo list) (blocals: varinfo list) (rename_mapping: string StringMap.t) = match alocals, blocals with
+ | [], [] -> true, rename_mapping
+ | origLocal :: als, nowLocal :: bls ->
+ let new_mapping = StringMap.add origLocal.vname nowLocal.vname rename_mapping in
+
+ (*TODO: maybe optimize this with eq_varinfo*)
+ rename_mapping_aware_compare als bls new_mapping
+ | _, _ -> false, rename_mapping
+ in
+
+ let unchangedHeader, headerRenameMapping = match cfgs with
+ | None -> (
+ let headerSizeEqual, headerRenameMapping = rename_mapping_aware_compare old.sformals current.sformals (StringMap.empty) in
+ let actHeaderRenameMapping = (headerRenameMapping, global_rename_mapping) in
+ eq_varinfo old.svar current.svar actHeaderRenameMapping && GobList.equal (eq_varinfo2 actHeaderRenameMapping) old.sformals current.sformals, headerRenameMapping
+ )
+ | Some _ -> (
+ eq_varinfo old.svar current.svar emptyRenameMapping && GobList.equal (eq_varinfo2 emptyRenameMapping) old.sformals current.sformals, StringMap.empty
+ )
+ in
+ if not unchangedHeader then ChangedFunHeader current, None
else
(* Here the local variables are checked to be equal *)
(*flag: when running on cfg, true iff the locals are identical; on ast: if the size of the locals stayed the same*)
- let flag, local_rename =
+ let sameLocals, local_rename =
match cfgs with
- | None -> rename_mapping_aware_compare a.slocals b.slocals headerRenameMapping
- | Some _ -> GobList.equal (eq_varinfo2 emptyRenameMapping) a.slocals b.slocals, StringMap.empty
+ | None -> rename_mapping_aware_compare old.slocals current.slocals headerRenameMapping
+ | Some _ -> GobList.equal (eq_varinfo2 emptyRenameMapping) old.slocals current.slocals, StringMap.empty
in
let rename_mapping: rename_mapping = (local_rename, global_rename_mapping) in
- let sameDef = unchangedHeader && flag in
- if not sameDef then
- (false, None)
+ if not sameLocals then
+ (Changed, None)
else
match cfgs with
- | None -> eq_block (a.sbody, a) (b.sbody, b) rename_mapping, None
+ | None -> unchanged_to_change_status (eq_block (old.sbody, old) (current.sbody, current) rename_mapping), None
| Some (cfgOld, (cfgNew, cfgNewBack)) ->
let module CfgOld : MyCFG.CfgForward = struct let next = cfgOld end in
let module CfgNew : MyCFG.CfgBidir = struct let prev = cfgNewBack let next = cfgNew end in
- let matches, diffNodes1 = compareFun (module CfgOld) (module CfgNew) a b in
- if diffNodes1 = [] then (true, None)
- else (false, Some {unchangedNodes = matches; primObsoleteNodes = diffNodes1})
- in
- identical, unchangedHeader, diffOpt
+ let matches, diffNodes1 = compareFun (module CfgOld) (module CfgNew) old current in
+ if diffNodes1 = [] then (Changed, None)
+ else (Changed, Some {unchangedNodes = matches; primObsoleteNodes = diffNodes1})
-let eq_glob (a: global) (b: global) (cfgs : (cfg * (cfg * cfg)) option) (global_rename_mapping: method_rename_assumptions) = match a, b with
+let eq_glob (old: global) (current: global) (cfgs : (cfg * (cfg * cfg)) option) (global_rename_mapping: method_rename_assumptions) = match old, current with
| GFun (f,_), GFun (g,_) -> eqF f g cfgs global_rename_mapping
- | 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 *)
- | GVarDecl (x, _), GVarDecl (y, _) -> eq_varinfo x y (StringMap.empty, VarinfoMap.empty), false, None
- | _ -> ignore @@ Pretty.printf "Not comparable: %a and %a\n" Cil.d_global a Cil.d_global b; false, false, None
+ | GVar (x, init_x, _), GVar (y, init_y, _) -> unchanged_to_change_status (eq_varinfo x y (StringMap.empty, VarinfoMap.empty)), None (* ignore the init_info - a changed init of a global will lead to a different start state *)
+ | GVarDecl (x, _), GVarDecl (y, _) -> unchanged_to_change_status (eq_varinfo x y (StringMap.empty, VarinfoMap.empty)), None
+ | _ -> ignore @@ Pretty.printf "Not comparable: %a and %a\n" Cil.d_global old Cil.d_global current; Changed, None
let compareCilFiles ?(eq=eq_glob) (oldAST: file) (newAST: file) =
let cfgs = if GobConfig.get_string "incremental.compare" = "cfg"
@@ -137,7 +150,7 @@ let compareCilFiles ?(eq=eq_glob) (oldAST: file) (newAST: file) =
with
Not_found -> map
in
-
+
let changes = empty_change_info () in
global_typ_acc := [];
let findChanges map global global_rename_mapping =
@@ -145,10 +158,18 @@ let compareCilFiles ?(eq=eq_glob) (oldAST: file) (newAST: file) =
let ident = identifier_of_global global in
let old_global = GlobalMap.find ident map in
(* Do a (recursive) equal comparison ignoring location information *)
- let identical, unchangedHeader, diff = eq old_global global cfgs global_rename_mapping in
- if identical
- then changes.unchanged <- {current = global; old = old_global} :: changes.unchanged
- else changes.changed <- {current = global; old = old_global; unchangedHeader; diff} :: changes.changed
+ let change_status, diff = eq old_global global cfgs global_rename_mapping in
+ let append_to_changed ~unchangedHeader =
+ changes.changed <- {current = global; old = old_global; unchangedHeader; diff} :: changes.changed
+ in
+ match change_status with
+ | Changed ->
+ append_to_changed ~unchangedHeader:true
+ | Unchanged -> changes.unchanged <- {current = global; old = old_global} :: changes.unchanged
+ | ChangedFunHeader f
+ | ForceReanalyze f ->
+ changes.exclude_from_rel_destab <- VarinfoSet.add f.svar changes.exclude_from_rel_destab;
+ append_to_changed ~unchangedHeader:false;
with Not_found -> () (* Global was no variable or function, it does not belong into the map *)
in
let checkExists map global =
diff --git a/src/incremental/updateCil.ml b/src/incremental/updateCil.ml
index 00ab0ed9fb..d7fe25f438 100644
--- a/src/incremental/updateCil.ml
+++ b/src/incremental/updateCil.ml
@@ -3,21 +3,11 @@ open CompareCIL
open MaxIdUtil
open MyCFG
-module NodeMap = Hashtbl.Make(Node)
-
-let location_map = ref (NodeMap.create 103: location NodeMap.t)
-
-let getLoc (node: Node.t) =
- (* In case this belongs to a changed function, we will find the true location in the map*)
- try
- NodeMap.find !location_map node
- with Not_found ->
- Node.location node
-
-let store_node_location (n: Node.t) (l: location): unit =
- NodeMap.add !location_map n l
+include UpdateCil0
let update_ids (old_file: file) (ids: max_ids) (new_file: file) (changes: change_info) =
+ UpdateCil0.init (); (* reset for server mode *)
+
let vid_max = ref ids.max_vid in
let sid_max = ref ids.max_sid in
diff --git a/src/incremental/updateCil0.ml b/src/incremental/updateCil0.ml
new file mode 100644
index 0000000000..5327daeaec
--- /dev/null
+++ b/src/incremental/updateCil0.ml
@@ -0,0 +1,19 @@
+(** UpdateCil functions to avoid dependency cycles.*)
+open GoblintCil
+
+module NodeMap = Hashtbl.Make(Node0)
+
+let location_map = ref (NodeMap.create 103: Cil.location NodeMap.t)
+
+let init () =
+ NodeMap.clear !location_map
+
+let getLoc (node: Node0.t) =
+ (* In case this belongs to a changed function, we will find the true location in the map*)
+ try
+ NodeMap.find !location_map node
+ with Not_found ->
+ Node0.location node
+
+let store_node_location (n: Node0.t) (l: Cil.location): unit =
+ NodeMap.add !location_map n l
diff --git a/src/maingoblint.ml b/src/maingoblint.ml
index 1d0a0d7434..86857dae71 100644
--- a/src/maingoblint.ml
+++ b/src/maingoblint.ml
@@ -314,8 +314,8 @@ let preprocess_files () =
);
preprocessed
-(** Possibly merge all postprocessed files *)
-let merge_preprocessed preprocessed =
+(** Parse preprocessed files *)
+let parse_preprocessed preprocessed =
(* get the AST *)
if get_bool "dbg.verbose" then print_endline "Parsing files.";
@@ -340,8 +340,10 @@ let merge_preprocessed preprocessed =
Cilfacade.getAST preprocessed_file
in
- let files_AST = List.map (get_ast_and_record_deps) preprocessed in
+ List.map get_ast_and_record_deps preprocessed
+(** Merge parsed files *)
+let merge_parsed parsed =
let cilout =
if get_string "dbg.cilout" = "" then Legacy.stderr else Legacy.open_out (get_string "dbg.cilout")
in
@@ -350,7 +352,7 @@ let merge_preprocessed preprocessed =
(* we use CIL to merge all inputs to ONE file *)
let merged_AST =
- match files_AST with
+ match parsed with
| [one] -> Cilfacade.callConstructors one
| [] ->
prerr_endline "No files to analyze!";
@@ -365,12 +367,15 @@ let merge_preprocessed preprocessed =
Cilfacade.current_file := merged_AST;
merged_AST
-let preprocess_and_merge () = preprocess_files () |> merge_preprocessed
+let preprocess_parse_merge () =
+ preprocess_files ()
+ |> parse_preprocessed
+ |> merge_parsed
let do_stats () =
if get_bool "printstats" then (
print_newline ();
- ignore (Pretty.printf "vars = %d evals = %d \n" !Goblintutil.vars !Goblintutil.evals);
+ ignore (Pretty.printf "vars = %d evals = %d narrow_reuses = %d\n" !Goblintutil.vars !Goblintutil.evals !Goblintutil.narrow_reuses);
print_newline ();
Stats.print (Messages.get_out "timing" Legacy.stderr) "Timings:\n";
flush_all ()
@@ -379,9 +384,7 @@ let do_stats () =
let reset_stats () =
Goblintutil.vars := 0;
Goblintutil.evals := 0;
- (* TODO: uncomment on interactive *)
- (* Goblintutil.narrow_reuses := 0; *)
- (* Goblintutil.aborts := 0; *)
+ Goblintutil.narrow_reuses := 0;
Stats.reset SoftwareTimer
(** Perform the analysis over the merged AST. *)
@@ -415,9 +418,8 @@ let do_analyze change_info merged_AST =
try Control.analyze change_info ast funs
with e ->
let backtrace = Printexc.get_raw_backtrace () in (* capture backtrace immediately, otherwise the following loses it (internal exception usage without raise_notrace?) *)
- let loc = !Tracing.current_loc in
Goblintutil.should_warn := true; (* such that the `about to crash` message gets printed *)
- Messages.error ~category:Analyzer ~loc "About to crash!";
+ Messages.error ~category:Analyzer "About to crash!";
(* trigger Generic.SolverStats...print_stats *)
Goblintutil.(self_signal (signal_of_string (get_string "dbg.solver-signal")));
do_stats ();
@@ -484,7 +486,8 @@ let check_arguments () =
if get_bool "ana.base.context.int" && not (get_bool "ana.base.context.non-ptr") then (set_bool "ana.base.context.int" false; warn "ana.base.context.int implicitly disabled by ana.base.context.non-ptr");
(* order matters: non-ptr=false, int=true -> int=false cascades to interval=false with warning *)
if get_bool "ana.base.context.interval" && not (get_bool "ana.base.context.int") then (set_bool "ana.base.context.interval" false; warn "ana.base.context.interval implicitly disabled by ana.base.context.int");
- if get_bool "incremental.only-rename" then (set_bool "incremental.load" true; warn "incremental.only-rename implicitly activates incremental.load. Previous AST is loaded for diff and rename, but analyis results are not reused.")
+ if get_bool "incremental.only-rename" then (set_bool "incremental.load" true; warn "incremental.only-rename implicitly activates incremental.load. Previous AST is loaded for diff and rename, but analyis results are not reused.");
+ if get_bool "incremental.restart.sided.enabled" && get_string_list "incremental.restart.list" <> [] then warn "Passing a non-empty list to incremental.restart.list (manual restarting) while incremental.restart.sided.enabled (automatic restarting) is activated."
let handle_extraspecials () =
let funs = get_string_list "exp.extraspecials" in
@@ -494,21 +497,31 @@ let handle_extraspecials () =
let diff_and_rename current_file =
(* Create change info, either from old results, or from scratch if there are no previous results. *)
let change_info: Analyses.increment_data =
+ let warn m = eprint_color ("{yellow}Warning: "^m) in
if GobConfig.get_bool "incremental.load" && not (Serialize.results_exist ()) then begin
- let warn m = eprint_color ("{yellow}Warning: "^m) in
warn "incremental.load is activated but no data exists that can be loaded."
end;
- let (changes, old_file, max_ids) =
+ let (changes, restarting, old_file, max_ids) =
if Serialize.results_exist () && GobConfig.get_bool "incremental.load" then begin
Serialize.Cache.load_data ();
let old_file = Serialize.Cache.(get_data CilFile) in
let changes = CompareCIL.compareCilFiles old_file current_file in
let max_ids = Serialize.Cache.(get_data VersionData) in
let max_ids = UpdateCil.update_ids old_file max_ids current_file changes in
- (changes, Some old_file, max_ids)
+
+ let restarting = GobConfig.get_string_list "incremental.restart.list" in
+ let restarting, not_found = VarQuery.varqueries_from_names current_file restarting in
+ if not (List.is_empty not_found) then begin
+ List.iter
+ (fun s ->
+ warn @@ "Should restart " ^ s ^ " but no such global could not be found in the CIL-file.")
+ not_found;
+ flush stderr
+ end;
+ (changes, restarting, Some old_file, max_ids)
end else begin
let max_ids = MaxIdUtil.get_file_max_ids current_file in
- (CompareCIL.empty_change_info (), None, max_ids)
+ (CompareCIL.empty_change_info (), [], None, max_ids)
end
in
let solver_data = if Serialize.results_exist () && GobConfig.get_bool "incremental.load" && not (GobConfig.get_bool "incremental.only-rename")
@@ -523,7 +536,7 @@ let diff_and_rename current_file =
| Some cil_file, Some solver_data -> Some ({solver_data}: Analyses.analyzed_data)
| _, _ -> None
in
- {server = false; Analyses.changes = changes; old_data}
+ {server = false; Analyses.changes = changes; restarting; old_data}
in change_info
let () = (* signal for printing backtrace; other signals in Generic.SolverStats and Timeout *)
diff --git a/src/solvers/postSolver.ml b/src/solvers/postSolver.ml
index a2be879a30..849bf13304 100644
--- a/src/solvers/postSolver.ml
+++ b/src/solvers/postSolver.ml
@@ -18,7 +18,7 @@ end
(** Functorial postsolver for any system. *)
module type F =
functor (S: EqConstrSys) (VH: Hashtbl.S with type key = S.v) ->
- S with module S = S and module VH = VH
+ S with module S = S and module VH = VH
(** Base implementation for postsolver. *)
module Unit: F =
@@ -86,10 +86,10 @@ module Verify: F =
let one_side ~vh ~x ~y ~d =
let y_lhs = try VH.find vh y with Not_found -> S.Dom.bot () in
- if not (S.Dom.leq d y_lhs) then
+ if S.Var.is_write_only y then
+ VH.replace vh y (S.Dom.join y_lhs d) (* HACK: allow warnings/accesses to be added without complaining *)
+ else if not (S.Dom.leq d y_lhs) then
complain_side x y ~lhs:y_lhs ~rhs:d
- else
- VH.replace vh y (S.Dom.join y_lhs d) (* HACK: allow warnings/accesses to be added *)
let one_constraint ~vh ~x ~rhs =
let lhs = try VH.find vh x with Not_found -> S.Dom.bot () in
@@ -162,9 +162,15 @@ struct
| Some f, Some d -> Some (fun get set -> S.Dom.join (f get set) d)
end
-(** Make complete postsolving function from postsolver.
- This is generic and non-incremental. *)
-module Make (PS: S) =
+(** Postsolver for incremental. *)
+module type IncrS =
+sig
+ include S
+ val init_reachable: vh:S.Dom.t VH.t -> unit VH.t
+end
+
+(** Make incremental postsolving function from incremental postsolver. *)
+module MakeIncr (PS: IncrS) =
struct
module S = PS.S
module VH = PS.VH
@@ -184,7 +190,7 @@ struct
Goblintutil.postsolving := true;
PS.init ();
- let reachable = VH.create (VH.length vh) in
+ let reachable = PS.init_reachable ~vh in
let rec one_var x =
if M.tracing then M.trace "postsolver" "one_var %a reachable=%B system=%B\n" S.Var.pretty_trace x (VH.mem reachable x) (Option.is_some (S.system x));
if not (VH.mem reachable x) then (
@@ -206,7 +212,7 @@ struct
if M.tracing then M.trace "postsolver" "one_constraint %a %a\n" S.Var.pretty_trace x S.Dom.pretty rhs;
PS.one_constraint ~vh ~x ~rhs
in
- List.iter one_var vs;
+ (GoblintCil.Stats.time "postsolver_iter" (List.iter one_var)) vs;
PS.finalize ~vh ~reachable;
Goblintutil.postsolving := false
@@ -227,10 +233,17 @@ sig
val postsolvers: (module M) list
end
-(** Make complete postsolving function from list of postsolvers.
- If list is empty, no postsolving is performed.
- This is generic and non-incremental. *)
-module MakeList (Arg: MakeListArg) =
+(** List of postsolvers for incremental. *)
+module type MakeIncrListArg =
+sig
+ include MakeListArg
+
+ val init_reachable: vh:S.Dom.t VH.t -> unit VH.t
+end
+
+(** Make incremental postsolving function from incremental list of postsolvers.
+ If list is empty, no postsolving is performed. *)
+module MakeIncrList (Arg: MakeIncrListArg) =
struct
module S = Arg.S
module VH = Arg.VH
@@ -248,10 +261,28 @@ struct
match postsolver_opt with
| None -> ()
| Some (module PS) ->
- let module M = Make (PS) in
+ let module IncrPS =
+ struct
+ include PS
+ let init_reachable = Arg.init_reachable
+ end
+ in
+ let module M = MakeIncr (IncrPS) in
M.post xs vs vh
end
+(** Make complete (non-incremental) postsolving function from list of postsolvers.
+ If list is empty, no postsolving is performed. *)
+module MakeList (Arg: MakeListArg) =
+struct
+ module IncrArg =
+ struct
+ include Arg
+ let init_reachable ~vh = VH.create (VH.length vh)
+ end
+ include MakeIncrList (IncrArg)
+end
+
(** Standard postsolver options. *)
module type MakeStdArg =
sig
diff --git a/src/solvers/td3.ml b/src/solvers/td3.ml
index 26122bdba7..a345a6c9ea 100644
--- a/src/solvers/td3.ml
+++ b/src/solvers/td3.ml
@@ -21,8 +21,6 @@ module WP =
functor (S:EqConstrSys) ->
functor (HM:Hashtbl.S with type key = S.v) ->
struct
- module Post = PostSolver.MakeList (PostSolver.ListArgFromStdArg (S) (HM) (Arg))
-
include Generic.SolverStats (S) (HM)
module VS = Set.Make (S.Var)
@@ -32,7 +30,12 @@ module WP =
mutable sides: VS.t HM.t;
mutable rho: S.Dom.t HM.t;
mutable wpoint: unit HM.t;
- mutable stable: unit HM.t
+ mutable stable: unit HM.t;
+ mutable side_dep: VS.t HM.t; (** Dependencies of side-effected variables. Knowing these allows restarting them and re-triggering all side effects. *)
+ mutable side_infl: VS.t HM.t; (** Influences to side-effected variables. Not normally in [infl], but used for restarting them. *)
+ mutable var_messages: Message.t HM.t; (** Messages from right-hand sides of variables. Used for incremental postsolving. *)
+ mutable rho_write: S.Dom.t HM.t HM.t; (** Side effects from variables to write-only variables with values. Used for fast incremental restarting of write-only variables. *)
+ mutable dep: VS.t HM.t; (** Dependencies of variables. Inverse of [infl]. Used for fast pre-reachable pruning in incremental postsolving. *)
}
type marshal = solver_data
@@ -43,13 +46,18 @@ module WP =
sides = HM.create 10;
rho = HM.create 10;
wpoint = HM.create 10;
- stable = HM.create 10
+ stable = HM.create 10;
+ side_dep = HM.create 10;
+ side_infl = HM.create 10;
+ var_messages = HM.create 10;
+ rho_write = HM.create 10;
+ dep = HM.create 10;
}
let print_data data str =
if GobConfig.get_bool "dbg.verbose" then
- Printf.printf "%s:\n|rho|=%d\n|stable|=%d\n|infl|=%d\n|wpoint|=%d\n"
- str (HM.length data.rho) (HM.length data.stable) (HM.length data.infl) (HM.length data.wpoint)
+ Printf.printf "%s:\n|rho|=%d\n|stable|=%d\n|infl|=%d\n|wpoint|=%d\n|side_dep|=%d\n|side_infl|=%d\n|rho_write|=%d\n|dep|=%d\n"
+ str (HM.length data.rho) (HM.length data.stable) (HM.length data.infl) (HM.length data.wpoint) (HM.length data.side_dep) (HM.length data.side_infl) (HM.length data.rho_write) (HM.length data.dep)
let verify_data data =
if GobConfig.get_bool "solvers.td3.verify" then (
@@ -72,7 +80,10 @@ module WP =
module HPM = Hashtbl.Make (P)
- type phase = Widen | Narrow
+ type phase = Widen | Narrow [@@deriving show]
+
+ module CurrentVarS = Constraints.CurrentVarEqConstrSys (S)
+ module S = CurrentVarS.S
let solve box st vs data =
let term = GobConfig.get_bool "solvers.td3.term" in
@@ -87,10 +98,31 @@ module WP =
let wpoint = data.wpoint in
let stable = data.stable in
+ let narrow_reuse = GobConfig.get_bool "solvers.td3.narrow-reuse" in
+
+ let side_dep = data.side_dep in
+ let side_infl = data.side_infl in
+ let restart_sided = GobConfig.get_bool "incremental.restart.sided.enabled" in
+ let restart_vars = GobConfig.get_string "incremental.restart.sided.vars" in
+
+ let restart_wpoint = GobConfig.get_bool "solvers.td3.restart.wpoint.enabled" in
+ let restart_once = GobConfig.get_bool "solvers.td3.restart.wpoint.once" in
+ let restarted_wpoint = HM.create 10 in
+
+ let incr_verify = GobConfig.get_bool "incremental.postsolver.enabled" in
+ let consider_superstable_reached = GobConfig.get_bool "incremental.postsolver.superstable-reached" in
+ (* In incremental load, initially stable nodes, which are never destabilized.
+ These don't have to be re-verified and warnings can be reused. *)
+ let superstable = HM.copy stable in
+
+ let var_messages = data.var_messages in
+ let rho_write = data.rho_write in
+ let dep = data.dep in
+
let () = print_solver_stats := fun () ->
- Printf.printf "|rho|=%d\n|called|=%d\n|stable|=%d\n|infl|=%d\n|wpoint|=%d\n"
- (HM.length rho) (HM.length called) (HM.length stable) (HM.length infl) (HM.length wpoint);
- print_context_stats rho
+ Printf.printf "|rho|=%d\n|called|=%d\n|stable|=%d\n|infl|=%d\n|wpoint|=%d\n|side_dep|=%d\n|side_infl|=%d\n|rho_write|=%d\n|dep|=%d\n"
+ (HM.length rho) (HM.length called) (HM.length stable) (HM.length infl) (HM.length wpoint) (HM.length side_dep) (HM.length side_infl) (HM.length rho_write) (HM.length dep);
+ print_context_stats rho
in
if GobConfig.get_bool "incremental.load" then (
@@ -102,46 +134,57 @@ module WP =
let add_infl y x =
if tracing then trace "sol2" "add_infl %a %a\n" S.Var.pretty_trace y S.Var.pretty_trace x;
- HM.replace infl y (VS.add x (try HM.find infl y with Not_found -> VS.empty))
+ HM.replace infl y (VS.add x (try HM.find infl y with Not_found -> VS.empty));
+ HM.replace dep x (VS.add y (HM.find_default dep x VS.empty));
in
let add_sides y x = HM.replace sides y (VS.add x (try HM.find sides y with Not_found -> VS.empty)) in
- let rec destabilize x =
- if tracing then trace "sol2" "destabilize %a\n" S.Var.pretty_trace x;
- let w = HM.find_default infl x VS.empty in
- HM.replace infl x VS.empty;
- VS.iter (fun y ->
- HM.remove stable y;
- if not (HM.mem called y) then destabilize y
- ) w
- and destabilize_vs x = (* TODO remove? Only used for side_widen cycle. *)
+
+ let destabilize_ref: (S.v -> unit) ref = ref (fun _ -> failwith "no destabilize yet") in
+ let destabilize x = !destabilize_ref x in (* must be eta-expanded to use changed destabilize_ref *)
+
+ let rec destabilize_vs x = (* TODO remove? Only used for side_widen cycle. *)
if tracing then trace "sol2" "destabilize_vs %a\n" S.Var.pretty_trace x;
let w = HM.find_default infl x VS.empty in
HM.replace infl x VS.empty;
VS.fold (fun y b ->
let was_stable = HM.mem stable y in
HM.remove stable y;
+ HM.remove superstable y;
HM.mem called y || destabilize_vs y || b || was_stable && List.mem y vs
) w false
and solve ?reuse_eq x phase =
- if tracing then trace "sol2" "solve %a, called: %b, stable: %b\n" S.Var.pretty_trace x (HM.mem called x) (HM.mem stable x);
+ if tracing then trace "sol2" "solve %a, phase: %s, called: %b, stable: %b\n" S.Var.pretty_trace x (show_phase phase) (HM.mem called x) (HM.mem stable x);
init x;
assert (S.system x <> None);
if not (HM.mem called x || HM.mem stable x) then (
+ if tracing then trace "sol2" "stable add %a\n" S.Var.pretty_trace x;
HM.replace stable x ();
HM.replace called x ();
+ (* Here we cache HM.mem wpoint x before eq. If during eq eval makes x wpoint, then be still don't apply widening the first time, but just overwrite.
+ It means that the first iteration at wpoint is still precise.
+ This doesn't matter during normal solving (?), because old would be bot.
+ This matters during incremental loading, when wpoints have been removed (or not marshaled) and are redetected.
+ Then the previous local wpoint value is discarded automagically and not joined/widened, providing limited restarting of local wpoints. (See eval for more complete restarting.) *)
let wp = HM.mem wpoint x in
- let old = HM.find rho x in
let l = HM.create 10 in
let tmp =
match reuse_eq with
- | Some d -> d
- | None -> eq x (eval l x) (side ~x)
+ | Some d when narrow_reuse ->
+ (* Do not reset deps for reuse of eq *)
+ if tracing then trace "sol2" "eq reused %a\n" S.Var.pretty_trace x;
+ incr Goblintutil.narrow_reuses;
+ d
+ | _ ->
+ (* The RHS is re-evaluated, all deps are re-trigerred *)
+ HM.replace dep x VS.empty;
+ eq x (eval l x) (side ~x)
in
let new_eq = tmp in
(* let tmp = if GobConfig.get_bool "ana.opt.hashcons" then S.Dom.join (S.Dom.bot ()) tmp else tmp in (* Call hashcons via dummy join so that the tag of the rhs value is up to date. Otherwise we might get the same value as old, but still with a different tag (because no lattice operation was called after a change), and since Printable.HConsed.equal just looks at the tag, we would unnecessarily destabilize below. Seems like this does not happen. *) *)
if tracing then trace "sol" "Var: %a\n" S.Var.pretty_trace x ;
if tracing then trace "sol" "Contrib:%a\n" S.Dom.pretty tmp;
HM.remove called x;
+ let old = HM.find rho x in (* find old value after eq since wpoint restarting in eq/eval might have changed it meanwhile *)
let tmp =
if not wp then tmp
else
@@ -155,20 +198,28 @@ module WP =
if tracing then trace "cache" "cache size %d for %a\n" (HM.length l) S.Var.pretty_trace x;
cache_sizes := HM.length l :: !cache_sizes;
if not (Stats.time "S.Dom.equal" (fun () -> S.Dom.equal old tmp) ()) then (
+ if tracing then trace "sol" "Changed\n";
update_var_event x old tmp;
HM.replace rho x tmp;
destabilize x;
- (solve[@tailcall]) x phase;
- ) else if not (HM.mem stable x) then (
- if tracing then trace "sol2" "solve still unstable %a\n" S.Var.pretty_trace x;
- (solve[@tailcall]) x Widen;
- ) else if term && phase = Widen && HM.mem wpoint x then ( (* TODO: or use wp? *)
- if tracing then trace "sol2" "solve switching to narrow %a\n" S.Var.pretty_trace x;
- HM.remove stable x;
- (solve[@tailcall]) ~reuse_eq:new_eq x Narrow;
- ) else if not space && (not term || phase = Narrow) then ( (* this makes e.g. nested loops precise, ex. tests/regression/34-localization/01-nested.c - if we do not remove wpoint, the inner loop head will stay a wpoint and widen the outer loop variable. *)
- if tracing then trace "sol2" "solve removing wpoint %a\n" S.Var.pretty_trace x;
- HM.remove wpoint x;
+ (solve[@tailcall]) x phase
+ ) else (
+ (* TODO: why non-equal and non-stable checks in switched order compared to TD3 paper? *)
+ if not (HM.mem stable x) then (
+ if tracing then trace "sol2" "solve still unstable %a\n" S.Var.pretty_trace x;
+ (solve[@tailcall]) x Widen
+ ) else (
+ if term && phase = Widen && HM.mem wpoint x then ( (* TODO: or use wp? *)
+ if tracing then trace "sol2" "solve switching to narrow %a\n" S.Var.pretty_trace x;
+ if tracing then trace "sol2" "stable remove %a\n" S.Var.pretty_trace x;
+ HM.remove stable x;
+ HM.remove superstable x;
+ (solve[@tailcall]) ~reuse_eq:new_eq x Narrow
+ ) else if not space && (not term || phase = Narrow) then ( (* this makes e.g. nested loops precise, ex. tests/regression/34-localization/01-nested.c - if we do not remove wpoint, the inner loop head will stay a wpoint and widen the outer loop variable. *)
+ if tracing then trace "sol2" "solve removing wpoint %a (%b)\n" S.Var.pretty_trace x (HM.mem wpoint x);
+ HM.remove wpoint x
+ )
+ )
)
)
and eq x get set =
@@ -194,9 +245,23 @@ module WP =
and eval l x y =
if tracing then trace "sol2" "eval %a ## %a\n" S.Var.pretty_trace x S.Var.pretty_trace y;
get_var_event y;
- if HM.mem called y then HM.replace wpoint y ();
+ if HM.mem called y then (
+ if restart_wpoint && not (HM.mem wpoint y) then (
+ (* Even though solve cleverly restarts redetected wpoints during incremental load, the loop body would be calculated based on the old wpoint value.
+ The loop body might then side effect the old value, see tests/incremental/06-local-wpoint-read.
+ Here we avoid this, by setting it to bottom for the loop body eval. *)
+ if not (restart_once && HM.mem restarted_wpoint y) then (
+ if tracing then trace "sol2" "wpoint restart %a ## %a\n" S.Var.pretty_trace y S.Dom.pretty (HM.find_default rho y (S.Dom.bot ()));
+ HM.replace rho y (S.Dom.bot ());
+ if restart_once then (* avoid populating hashtable unnecessarily *)
+ HM.replace restarted_wpoint y ();
+ )
+ );
+ HM.replace wpoint y ();
+ );
let tmp = simple_solve l x y in
if HM.mem rho y then add_infl y x;
+ if tracing then trace "sol2" "eval %a ## %a -> %a\n" S.Var.pretty_trace x S.Var.pretty_trace y S.Dom.pretty tmp;
tmp
and side ?x y d = (* side from x to y; only to variables y w/o rhs; x only used for trace *)
if tracing then trace "sol2" "side to %a (wpx: %b) from %a ## value: %a\n" S.Var.pretty_trace y (HM.mem wpoint y) (Pretty.docOpt (S.Var.pretty_trace ())) x S.Dom.pretty d;
@@ -216,6 +281,7 @@ module WP =
in
let old = HM.find rho y in
let tmp = op old d in
+ if tracing then trace "sol2" "stable add %a\n" S.Var.pretty_trace y;
HM.replace stable y ();
if not (S.Dom.leq tmp old) then (
(* if there already was a `side x y d` that changed rho[y] and now again, we make y a wpoint *)
@@ -273,81 +339,356 @@ module WP =
(* solve x Widen *)
in
+ let rec destabilize_normal x =
+ if tracing then trace "sol2" "destabilize %a\n" S.Var.pretty_trace x;
+ let w = HM.find_default infl x VS.empty in
+ HM.replace infl x VS.empty;
+ VS.iter (fun y ->
+ if tracing then trace "sol2" "stable remove %a\n" S.Var.pretty_trace y;
+ HM.remove stable y;
+ HM.remove superstable y;
+ if not (HM.mem called y) then destabilize_normal y
+ ) w
+ in
+
start_event ();
+ (* reluctantly unchanged return nodes to additionally query for postsolving to get warnings, etc. *)
+ let reluctant_vs: S.Var.t list ref = ref [] in
+
+ let restart_write_only = GobConfig.get_bool "incremental.restart.write-only" in
+
if GobConfig.get_bool "incremental.load" then (
let c = S.increment.changes in
List.(Printf.printf "change_info = { unchanged = %d; changed = %d; added = %d; removed = %d }\n" (length c.unchanged) (length c.changed) (length c.added) (length c.removed));
- let filter_map f l =
- List.fold_left (fun acc el -> match f el with Some x -> x::acc | _ -> acc) [] l
+ let restart_leaf x =
+ if tracing then trace "sol2" "Restarting to bot %a\n" S.Var.pretty_trace x;
+ ignore (Pretty.printf "Restarting to bot %a\n" S.Var.pretty_trace x);
+ HM.replace rho x (S.Dom.bot ());
+ (* HM.remove rho x; *)
+ HM.remove wpoint x; (* otherwise gets immediately widened during resolve *)
+ HM.remove sides x; (* just in case *)
+
+ (* immediately redo "side effect" from st *)
+ match GobList.assoc_eq_opt S.Var.equal x st with
+ | Some d ->
+ HM.replace rho x d;
+ | None ->
+ ()
+ in
+
+ let restart_fuel_only_globals = GobConfig.get_bool "incremental.restart.sided.fuel-only-global" in
+
+ (* destabilize which restarts side-effected vars *)
+ (* side_fuel specifies how many times (in recursion depth) to destabilize side_infl, None means infinite *)
+ let rec destabilize_with_side ~side_fuel x =
+ if tracing then trace "sol2" "destabilize_with_side %a %a\n" S.Var.pretty_trace x (Pretty.docOpt (Pretty.dprintf "%d")) side_fuel;
+
+ (* is side-effected var (global/function entry)? *)
+ let w = HM.find_default side_dep x VS.empty in
+ HM.remove side_dep x;
+
+ let should_restart =
+ match restart_write_only, S.Var.is_write_only x with
+ | true, true -> false (* prefer efficient write-only restarting during postsolving *)
+ | _, is_write_only ->
+ match restart_vars with
+ | "all" -> true
+ | "global" -> Node.equal (S.Var.node x) (Function Cil.dummyFunDec) (* non-function entry node *)
+ | "write-only" -> is_write_only
+ | _ -> assert false
+ in
+
+ if not (VS.is_empty w) && should_restart then (
+ (* restart side-effected var *)
+ restart_leaf x;
+
+ (* destabilize side dep to redo side effects *)
+ VS.iter (fun y ->
+ if tracing then trace "sol2" "destabilize_with_side %a side_dep %a\n" S.Var.pretty_trace x S.Var.pretty_trace y;
+ if tracing then trace "sol2" "stable remove %a\n" S.Var.pretty_trace y;
+ HM.remove stable y;
+ HM.remove superstable y;
+ destabilize_with_side ~side_fuel y
+ ) w
+ );
+
+ (* destabilize eval infl *)
+ let w = HM.find_default infl x VS.empty in
+ HM.replace infl x VS.empty;
+ VS.iter (fun y ->
+ if tracing then trace "sol2" "destabilize_with_side %a infl %a\n" S.Var.pretty_trace x S.Var.pretty_trace y;
+ if tracing then trace "sol2" "stable remove %a\n" S.Var.pretty_trace y;
+ HM.remove stable y;
+ HM.remove superstable y;
+ destabilize_with_side ~side_fuel y
+ ) w;
+
+ (* destabilize side infl *)
+ let w = HM.find_default side_infl x VS.empty in
+ HM.remove side_infl x;
+
+ if side_fuel <> Some 0 then ( (* non-0 or infinite fuel is fine *)
+ let side_fuel' =
+ if not restart_fuel_only_globals || Node.equal (S.Var.node x) (Function Cil.dummyFunDec) then
+ Option.map Int.pred side_fuel
+ else
+ side_fuel (* don't decrease fuel for function entry side effect *)
+ in
+ (* TODO: should this also be conditional on restart_only_globals? right now goes through function entry side effects, but just doesn't restart them *)
+ VS.iter (fun y ->
+ if tracing then trace "sol2" "destabilize_with_side %a side_infl %a\n" S.Var.pretty_trace x S.Var.pretty_trace y;
+ if tracing then trace "sol2" "stable remove %a\n" S.Var.pretty_trace y;
+ HM.remove stable y;
+ HM.remove superstable y;
+ destabilize_with_side ~side_fuel:side_fuel' y
+ ) w
+ )
+ in
+
+ destabilize_ref :=
+ if restart_sided then (
+ let side_fuel =
+ match GobConfig.get_int "incremental.restart.sided.fuel" with
+ | fuel when fuel >= 0 -> Some fuel
+ | _ -> None (* infinite *)
+ in
+ destabilize_with_side ~side_fuel
+ )
+ else
+ destabilize_normal;
+
+ let changed_funs = List.filter_map (function
+ | {old = GFun (f, _); diff = None; _} ->
+ print_endline ("Completely changed function: " ^ f.svar.vname);
+ Some f
+ | _ -> None
+ ) S.increment.changes.changed
+ in
+ let part_changed_funs = List.filter_map (function
+ | {old = GFun (f, _); diff = Some nd; _} ->
+ print_endline ("Partially changed function: " ^ f.svar.vname);
+ Some (f, nd.primObsoleteNodes, nd.unchangedNodes)
+ | _ -> None
+ ) S.increment.changes.changed
+ in
+ let removed_funs = List.filter_map (function
+ | GFun (f, _) ->
+ print_endline ("Removed function: " ^ f.svar.vname);
+ Some f
+ | _ -> None
+ ) S.increment.changes.removed
+ in
+
+ let mark_node hm f node =
+ let get x = try HM.find rho x with Not_found -> S.Dom.bot () in
+ S.iter_vars get (Node {node; fundec = Some f}) (fun v ->
+ HM.replace hm v ()
+ )
in
- let changed_funs = filter_map (fun c -> match c.old, c.diff with GFun (f,l), None -> Some f | _ -> None) S.increment.changes.changed in
- let part_changed_funs = filter_map (fun c -> match c.old, c.diff with GFun (f,l), Some nd -> Some (f,nd.primObsoleteNodes,nd.unchangedNodes) | _ -> None) S.increment.changes.changed in
- let prim_old_nodes_ids = Set.of_list (List.concat_map (fun (_,pn,_) -> List.map Node.show_id pn) part_changed_funs) in
- let removed_funs = filter_map (fun g -> match g with GFun (f,l) -> Some f | _ -> None) S.increment.changes.removed in
- (* TODO: don't use string-based nodes, make obsolete of type Node.t BatSet.t *)
- let obsolete_ret = Set.union (Set.of_list (List.map (fun f -> Node.show_id (Function f)) changed_funs))
- (Set.of_list (List.map (fun (f,_,_) -> Node.show_id (Function f)) part_changed_funs)) in
- let obsolete_entry = Set.of_list (List.map (fun f -> Node.show_id (FunctionEntry f)) changed_funs) in
-
- List.iter (fun a -> print_endline ("Completely changed function: " ^ a.svar.vname)) changed_funs;
- List.iter (fun (f,_,_) -> print_endline ("Partially changed function: " ^ (f.svar.vname))) part_changed_funs;
-
- let old_ret = Hashtbl.create 103 in
- if GobConfig.get_bool "incremental.reluctant.on" then (
+
+ let reluctant = GobConfig.get_bool "incremental.reluctant.enabled" in
+ let reanalyze_entry f =
+ (* destabilize the entry points of a changed function when reluctant is off,
+ or the function is to be force-reanalyzed *)
+ (not reluctant) || CompareCIL.VarinfoSet.mem f.svar S.increment.changes.exclude_from_rel_destab
+ in
+ let obsolete_ret = HM.create 103 in
+ let obsolete_entry = HM.create 103 in
+ let obsolete_prim = HM.create 103 in
+
+ (* When reluctant is on:
+ Only add function entry nodes to obsolete_entry if they are in force-reanalyze *)
+ List.iter (fun f ->
+ if reanalyze_entry f then
+ (* collect function entry for eager destabilization *)
+ mark_node obsolete_entry f (FunctionEntry f)
+ else
+ (* collect function return for reluctant analysis *)
+ mark_node obsolete_ret f (Function f)
+ ) changed_funs;
+ (* Unknowns from partially changed functions need only to be collected for eager destabilization when reluctant is off *)
+ (* We utilize that force-reanalyzed functions are always considered as completely changed (and not partially changed) *)
+ if not reluctant then (
+ List.iter (fun (f, pn, _) ->
+ List.iter (fun n ->
+ mark_node obsolete_prim f n
+ ) pn;
+ mark_node obsolete_ret f (Function f);
+ ) part_changed_funs;
+ );
+
+ let old_ret = HM.create 103 in
+ if reluctant then (
(* save entries of changed functions in rho for the comparison whether the result has changed after a function specific solve *)
- HM.iter (fun k v -> if Set.mem (S.Var.var_id k) obsolete_ret then ( (* TODO: don't use string-based nodes *)
- let old_rho = HM.find rho k in
- let old_infl = HM.find_default infl k VS.empty in
- Hashtbl.replace old_ret k (old_rho, old_infl))) rho;
- ) else (
- (* If reluctant destabilization is turned off we need to destabilize all nodes in completely changed functions
- and the primary obsolete nodes of partly changed functions *)
- print_endline "Destabilizing changed functions and primary old nodes ...";
- HM.iter (fun k _ -> if Set.mem (S.Var.var_id k) obsolete_entry || Set.mem (S.Var.var_id k) prim_old_nodes_ids then destabilize k) stable;
+ HM.iter (fun k v ->
+ if HM.mem rho k then (
+ let old_rho = HM.find rho k in
+ let old_infl = HM.find_default infl k VS.empty in
+ HM.replace old_ret k (old_rho, old_infl)
+ )
+ ) obsolete_ret;
);
+ if not (HM.is_empty obsolete_entry) || not (HM.is_empty obsolete_prim) then
+ print_endline "Destabilizing changed functions and primary old nodes ...";
+ HM.iter (fun k _ ->
+ if HM.mem stable k then
+ destabilize k
+ ) obsolete_entry;
+ HM.iter (fun k _ ->
+ if HM.mem stable k then
+ destabilize k
+ ) obsolete_prim;
+
(* We remove all unknowns for program points in changed or removed functions from rho, stable, infl and wpoint *)
- (* TODO: don't use string-based nodes, make marked_for_deletion of type unit (Hashtbl.Make (Node)).t *)
- let add_nodes_of_fun (functions: fundec list) (nodes) withEntry =
+ let marked_for_deletion = HM.create 103 in
+
+ let dummy_pseudo_return_node f =
+ (* not the same as in CFG, but compares equal because of sid *)
+ Node.Statement ({Cil.dummyStmt with sid = CfgTools.get_pseudo_return_id f})
+ in
+ let add_nodes_of_fun (functions: fundec list) (withEntry: fundec -> bool) =
let add_stmts (f: fundec) =
- List.iter (fun s -> Hashtbl.replace nodes (Node.show_id (Statement s)) ()) (f.sallstmts)
+ List.iter (fun s ->
+ mark_node marked_for_deletion f (Statement s)
+ ) f.sallstmts
in
- List.iter (fun f -> if withEntry then Hashtbl.replace nodes (Node.show_id (FunctionEntry f)) (); Hashtbl.replace nodes (Node.show_id (Function f)) (); add_stmts f; Hashtbl.replace nodes (string_of_int (CfgTools.get_pseudo_return_id f)) ()) functions;
+ List.iter (fun f ->
+ if withEntry f then
+ mark_node marked_for_deletion f (FunctionEntry f);
+ mark_node marked_for_deletion f (Function f);
+ add_stmts f;
+ mark_node marked_for_deletion f (dummy_pseudo_return_node f)
+ ) functions;
in
- let marked_for_deletion = Hashtbl.create 103 in
- add_nodes_of_fun changed_funs marked_for_deletion (not (GobConfig.get_bool "incremental.reluctant.on"));
- add_nodes_of_fun removed_funs marked_for_deletion true;
+ add_nodes_of_fun changed_funs reanalyze_entry;
+ add_nodes_of_fun removed_funs (fun _ -> true);
(* it is necessary to remove all unknowns for changed pseudo-returns because they have static ids *)
let add_pseudo_return f un =
- let pid = CfgTools.get_pseudo_return_id f in
- let is_pseudo_return n = match n with MyCFG.Statement s -> s.sid = pid | _ -> false in
- if not (List.exists (fun x -> is_pseudo_return @@ fst @@ x) un)
- then Hashtbl.replace marked_for_deletion (string_of_int pid) () in
- List.iter (fun (f,_,un) -> Hashtbl.replace marked_for_deletion (Node.show_id (Function f)) (); add_pseudo_return f un) part_changed_funs;
+ let pseudo = dummy_pseudo_return_node f in
+ if not (List.exists (Node.equal pseudo % fst) un) then
+ mark_node marked_for_deletion f (dummy_pseudo_return_node f)
+ in
+ List.iter (fun (f,_,un) ->
+ mark_node marked_for_deletion f (Function f);
+ add_pseudo_return f un
+ ) part_changed_funs;
print_endline "Removing data for changed and removed functions...";
- let delete_marked s = HM.filteri_inplace (fun k _ -> not (Hashtbl.mem marked_for_deletion (S.Var.var_id k))) s in (* TODO: don't use string-based nodes *)
+ let delete_marked s = HM.iter (fun k _ -> HM.remove s k) marked_for_deletion in
delete_marked rho;
- delete_marked infl;
+ delete_marked infl; (* TODO: delete from inner sets? *)
delete_marked wpoint;
- delete_marked stable;
+ delete_marked dep;
+
+ (* destabilize_with_side doesn't have all infl to follow anymore, so should somewhat work with reluctant *)
+ if restart_sided then (
+ (* restarts old copies of functions and their (removed) side effects *)
+ print_endline "Destabilizing sides of changed functions, primary old nodes and removed functions ...";
+ HM.iter (fun k _ ->
+ if HM.mem stable k then (
+ ignore (Pretty.printf "marked %a\n" S.Var.pretty_trace k);
+ destabilize k
+ )
+ ) marked_for_deletion
+ );
+ (* [destabilize_leaf] is meant for restarting of globals selected by the user. *)
+ (* Must be called on a leaf! *)
+ let destabilize_leaf (x : S.v) =
+ let destab_side_dep (x : S.v) =
+ let w = HM.find_default side_dep x VS.empty in
+ if not (VS.is_empty w) then (
+ HM.remove side_dep x;
+ (* destabilize side dep to redo side effects *)
+ VS.iter (fun y ->
+ if tracing then trace "sol2" "destabilize_leaf %a side_dep %a\n" S.Var.pretty_trace x S.Var.pretty_trace y;
+ if tracing then trace "sol2" "stable remove %a\n" S.Var.pretty_trace y;
+ HM.remove stable y;
+ HM.remove superstable y;
+ destabilize_normal y
+ ) w
+ )
+ in
+ restart_leaf x;
+ destab_side_dep x;
+ destabilize_normal x
- print_data data "Data after clean-up";
+ in
+ let globals_to_restart = S.increment.restarting in
+ let get x = try HM.find rho x with Not_found -> S.Dom.bot () in
+
+ List.iter
+ (fun g ->
+ S.iter_vars get g
+ (fun v ->
+ if S.system v <> None then
+ ignore @@ Pretty.printf "Trying to restart non-leaf unknown %a. This has no effect.\n" S.Var.pretty_trace v
+ else if HM.mem stable v then
+ destabilize_leaf v)
+ )
+ globals_to_restart;
+
+ let restart_and_destabilize x = (* destabilize_with_side doesn't restart x itself *)
+ restart_leaf x;
+ destabilize x
+ in
+
+ let should_restart_start = restart_sided && restart_vars <> "write-only" in (* assuming start vars are not write-only *)
+ (* TODO: should this distinguish non-global (function entry) and global (earlyglobs) start vars? *)
(* Call side on all globals and functions in the start variables to make sure that changes in the initializers are propagated.
* This also destabilizes start functions if their start state changes because of globals that are neither in the start variables nor in the contexts *)
- List.iter (fun (v,d) -> side v d) st;
+ List.iter (fun (v,d) ->
+ if should_restart_start then (
+ match GobList.assoc_eq_opt S.Var.equal v data.st with
+ | Some old_d when not (S.Dom.equal old_d d) ->
+ ignore (Pretty.printf "Destabilizing and restarting changed start var %a\n" S.Var.pretty_trace v);
+ restart_and_destabilize v (* restart side effect from start *)
+ | _ ->
+ (* don't restart unchanged start global *)
+ (* no need to restart added start global (implicit bot before) *)
+ (* restart removed start global below *)
+ ()
+ );
+ side v d
+ ) st;
+
+ if should_restart_start then (
+ List.iter (fun (v, _) ->
+ match GobList.assoc_eq_opt S.Var.equal v st with
+ | None ->
+ (* restart removed start global to allow it to be pruned from incremental solution *)
+ (* this gets rid of its warnings and makes comparing with from scratch sensible *)
+ ignore (Pretty.printf "Destabilizing and restarting removed start var %a\n" S.Var.pretty_trace v);
+ restart_and_destabilize v
+ | _ ->
+ ()
+ ) data.st
+ );
+
+ delete_marked stable;
+ delete_marked side_dep; (* TODO: delete from inner sets? *)
+ delete_marked side_infl; (* TODO: delete from inner sets? *)
+
+ (* delete from incremental postsolving/warning structures to remove spurious warnings *)
+ delete_marked superstable;
+ delete_marked var_messages;
+ delete_marked rho_write;
+ HM.iter (fun x w -> delete_marked w) rho_write;
+
+ print_data data "Data after clean-up";
+
+ (* TODO: reluctant doesn't call destabilize on removed functions or old copies of modified functions (e.g. after removing write), so those globals don't get restarted *)
- if GobConfig.get_bool "incremental.reluctant.on" then (
+ if reluctant then (
(* solve on the return node of changed functions. Only destabilize the function's return node if the analysis result changed *)
print_endline "Separately solving changed functions...";
let op = if GobConfig.get_string "incremental.reluctant.compare" = "leq" then S.Dom.leq else S.Dom.equal in
- Hashtbl.iter (
- fun x (old_rho, old_infl) ->
+ HM.iter (fun x (old_rho, old_infl) ->
ignore @@ Pretty.printf "test for %a\n" Node.pretty_trace (S.Var.node x);
solve x Widen;
if not (op (HM.find rho x) old_rho) then (
@@ -356,13 +697,20 @@ module WP =
destabilize x;
HM.replace stable x ()
)
- ) old_ret;
+ else (
+ print_endline "Destabilization not required...";
+ reluctant_vs := x :: !reluctant_vs
+ )
+ ) old_ret;
print_endline "Final solve..."
- )
+ );
) else (
List.iter set_start st;
);
+
+ destabilize_ref := destabilize_normal; (* always use normal destabilize during actual solve *)
+
List.iter init vs;
(* If we have multiple start variables vs, we might solve v1, then while solving v2 we side some global which v1 depends on with a new value. Then v1 is no longer stable and we have to solve it again. *)
let i = ref 0 in
@@ -445,10 +793,197 @@ module WP =
print_newline ();
);
- Post.post st vs rho; (* TODO: add side_infl postsolver *)
+ (* Prune other data structures than rho with reachable.
+ These matter for the incremental data. *)
+ let module IncrPrune: PostSolver.S with module S = S and module VH = HM =
+ struct
+ include PostSolver.Unit (S) (HM)
+
+ let finalize ~vh ~reachable =
+ VH.filteri_inplace (fun x _ ->
+ VH.mem reachable x
+ ) stable;
+
+ (* filter both keys and value sets of a VS.t HM.t *)
+ let filter_vs_hm hm =
+ VH.filter_map_inplace (fun x vs ->
+ if VH.mem reachable x then
+ Some (VS.filter (VH.mem reachable) vs)
+ else
+ None
+ ) hm
+ in
+ filter_vs_hm infl;
+ filter_vs_hm side_infl;
+ filter_vs_hm side_dep;
+ filter_vs_hm dep;
+
+ VH.filteri_inplace (fun x w ->
+ if VH.mem reachable x then (
+ VH.filteri_inplace (fun y _ ->
+ VH.mem reachable y
+ ) w;
+ true
+ )
+ else
+ false
+ ) rho_write
+
+ (* TODO: prune other data structures? *)
+ end
+ in
+
+ (* postsolver also populates side_dep, side_infl, and dep *)
+ let module SideInfl: PostSolver.S with module S = S and module VH = HM =
+ struct
+ include PostSolver.Unit (S) (HM)
+
+ (* TODO: We should be able to reset side_infl before executing the RHS, as all relevant side-effects should happen here again *)
+ (* However, this currently breaks some tests https://github.com/goblint/analyzer/pull/713#issuecomment-1114764937 *)
+ let one_side ~vh ~x ~y ~d =
+ (* Also record side-effects caused by post-solver *)
+ HM.replace side_dep y (VS.add x (try HM.find side_dep y with Not_found -> VS.empty));
+ HM.replace side_infl x (VS.add y (try HM.find side_infl x with Not_found -> VS.empty));
+ end
+ in
+
+ let reachable_and_superstable =
+ if incr_verify && not consider_superstable_reached then
+ (* Perform reachability on whole constraint system, but cheaply by using logged dependencies *)
+ (* This only works if the other reachability has been performed before, so dependencies created only during postsolve are recorded *)
+ let reachable' = HM.create (HM.length rho) in
+ let reachable_and_superstable = HM.create (HM.length rho) in
+ let rec one_var' x =
+ if (not (HM.mem reachable' x)) then (
+ if HM.mem superstable x then HM.replace reachable_and_superstable x ();
+ HM.replace reachable' x ();
+ Option.may (VS.iter one_var') (HM.find_option dep x);
+ Option.may (VS.iter one_var') (HM.find_option side_infl x)
+ )
+ in
+ (Stats.time "cheap_full_reach" (List.iter one_var')) (vs @ !reluctant_vs);
+
+ reachable_and_superstable (* consider superstable reached if it is still reachable: stop recursion (evaluation) and keep from being pruned *)
+ else if incr_verify then
+ superstable
+ else
+ HM.create 0 (* doesn't matter, not used *)
+ in
+
+ if restart_write_only then (
+ (* restart write-only *)
+ HM.iter (fun x w ->
+ HM.iter (fun y d ->
+ ignore (Pretty.printf "Restarting write-only to bot %a\n" S.Var.pretty_trace y);
+ HM.replace rho y (S.Dom.bot ());
+ ) w
+ ) rho_write
+ );
+
+ if incr_verify then (
+ HM.filteri_inplace (fun x _ -> HM.mem reachable_and_superstable x) var_messages;
+ HM.filteri_inplace (fun x _ -> HM.mem reachable_and_superstable x) rho_write
+ )
+ else (
+ HM.clear var_messages;
+ HM.clear rho_write
+ );
+
+ let init_reachable = reachable_and_superstable in
+
+ let module IncrWarn: PostSolver.S with module S = S and module VH = HM =
+ struct
+ include PostSolver.Warn (S) (HM)
+
+ let init () =
+ init (); (* enable warning like standard Warn *)
+
+ (* replay superstable messages from unknowns that are still reachable *)
+ if incr_verify then (
+ HM.iter (fun _ m ->
+ Messages.add m
+ ) var_messages;
+ );
+
+ (* hook to collect new messages *)
+ Messages.Table.add_hook := (fun m ->
+ match !CurrentVarS.current_var with
+ | Some x -> HM.add var_messages x m
+ | None -> ()
+ )
+
+ let finalize ~vh ~reachable =
+ finalize ~vh ~reachable; (* disable warning like standard Warn *)
+
+ (* unhook to avoid accidental var_messages modifications *)
+ Messages.Table.add_hook := (fun _ -> ())
+ end
+ in
+
+ (** Incremental write-only side effect restart handling:
+ retriggers superstable ones (after restarting above) and collects new (non-superstable) ones. *)
+ let module IncrWrite: PostSolver.S with module S = S and module VH = HM =
+ struct
+ include PostSolver.Unit (S) (HM)
+
+ let init () =
+ (* retrigger superstable side writes from unknowns that are still reachable *)
+ if incr_verify then (
+ HM.iter (fun x w ->
+ HM.iter (fun y d ->
+ let old_d = try HM.find rho y with Not_found -> S.Dom.bot () in
+ (* ignore (Pretty.printf "rho_write retrigger %a %a %a %a\n" S.Var.pretty_trace x S.Var.pretty_trace y S.Dom.pretty old_d S.Dom.pretty d); *)
+ HM.replace rho y (S.Dom.join old_d d);
+ HM.replace init_reachable y ();
+ HM.replace stable y (); (* make stable just in case, so following incremental load would have in superstable *)
+ ) w
+ ) rho_write
+ )
+
+ let one_side ~vh ~x ~y ~d =
+ if S.Var.is_write_only y then (
+ (* ignore (Pretty.printf "rho_write collect %a %a %a\n" S.Var.pretty_trace x S.Var.pretty_trace y S.Dom.pretty d); *)
+ HM.replace stable y (); (* make stable just in case, so following incremental load would have in superstable *)
+ let w =
+ try
+ VH.find rho_write x
+ with Not_found ->
+ let w = VH.create 1 in (* only create on demand, modify_def would eagerly allocate *)
+ VH.replace rho_write x w;
+ w
+ in
+ VH.add w y d (* intentional add *)
+ )
+ end
+ in
+
+ let module MakeIncrListArg =
+ struct
+ module Arg =
+ struct
+ include Arg
+ let should_warn = false (* disable standard Warn in favor of IncrWarn *)
+ end
+ include PostSolver.ListArgFromStdArg (S) (HM) (Arg)
+
+ let postsolvers = (module IncrPrune: M) :: (module SideInfl: M) :: (module IncrWrite: M) :: (module IncrWarn: M) :: postsolvers
+
+ let init_reachable ~vh =
+ if incr_verify then
+ init_reachable
+ else
+ HM.create (HM.length vh)
+ end
+ in
+
+ let module Post = PostSolver.MakeIncrList (MakeIncrListArg) in
+
+ Post.post st (!reluctant_vs @ vs) rho;
+
+ print_data data "Data after postsolve";
verify_data data;
- {st; infl; sides; rho; wpoint; stable}
+ {st; infl; sides; rho; wpoint; stable; side_dep; side_infl; var_messages; rho_write; dep}
let solve box st vs =
let reuse_stable = GobConfig.get_bool "incremental.stable" in
@@ -478,7 +1013,12 @@ module WP =
data.stable <- HM.copy data.stable;
data.wpoint <- HM.copy data.wpoint;
data.infl <- HM.copy data.infl;
+ data.side_infl <- HM.copy data.side_infl;
+ data.side_dep <- HM.copy data.side_dep;
(* data.st is immutable, no need to copy *)
+ data.var_messages <- HM.copy data.var_messages;
+ data.rho_write <- HM.map (fun x w -> HM.copy w) data.rho_write; (* map copies outer HM *)
+ data.dep <- HM.copy data.dep;
)
else if loaded && GobConfig.get_bool "ana.opt.hashcons" then (
let rho' = HM.create (HM.length data.rho) in
@@ -504,7 +1044,36 @@ module WP =
HM.replace infl' (S.Var.relift k) (VS.map S.Var.relift v)
) data.infl;
data.infl <- infl';
+ let side_infl' = HM.create (HM.length data.side_infl) in
+ HM.iter (fun k v ->
+ HM.replace side_infl' (S.Var.relift k) (VS.map S.Var.relift v)
+ ) data.side_infl;
+ data.side_infl <- side_infl';
+ let side_dep' = HM.create (HM.length data.side_dep) in
+ HM.iter (fun k v ->
+ HM.replace side_dep' (S.Var.relift k) (VS.map S.Var.relift v)
+ ) data.side_dep;
+ data.side_dep <- side_dep';
data.st <- List.map (fun (k, v) -> S.Var.relift k, S.Dom.relift v) data.st;
+ let var_messages' = HM.create (HM.length data.var_messages) in
+ HM.iter (fun k v ->
+ HM.add var_messages' (S.Var.relift k) v (* var_messages contains duplicate keys, so must add not replace! *)
+ ) data.var_messages;
+ data.var_messages <- var_messages';
+ let rho_write' = HM.create (HM.length data.rho_write) in
+ HM.iter (fun x w ->
+ let w' = HM.create (HM.length w) in
+ HM.iter (fun y d ->
+ HM.add w' (S.Var.relift y) (S.Dom.relift d) (* w contains duplicate keys, so must add not replace! *)
+ ) w;
+ HM.replace rho_write' (S.Var.relift x) w';
+ ) data.rho_write;
+ data.rho_write <- rho_write';
+ let dep' = HM.create (HM.length data.dep) in
+ HM.iter (fun k v ->
+ HM.replace dep' (S.Var.relift k) (VS.map S.Var.relift v)
+ ) data.dep;
+ data.dep <- dep';
);
if not reuse_stable then (
print_endline "Destabilizing everything!";
diff --git a/src/transform/evalAssert.ml b/src/transform/evalAssert.ml
index e589ae2d7b..de4c1a4291 100644
--- a/src/transform/evalAssert.ml
+++ b/src/transform/evalAssert.ml
@@ -81,16 +81,16 @@ module EvalAssert = struct
let rec instrument_instructions = function
| i1 :: ((i2 :: _) as is) ->
(* List contains successor statement, use location of successor for values *)
- let loc = get_instrLoc i2 in
+ let loc = get_instrLoc i2 in (* TODO: why not using Cilfacade.get_instrLoc? *)
i1 :: ((instrument i1 loc) @ instrument_instructions is)
| [i] when unique_succ ->
(* Last statement in list *)
(* Successor of it has only one predecessor, we can query for the value there *)
- let loc = get_stmtLoc (List.hd s.succs).skind in
+ let loc = get_stmtLoc (List.hd s.succs).skind in (* TODO: why not using Cilfacade.get_stmtLoc? *)
i :: (instrument i loc)
| [i] when s.succs <> [] ->
(* Successor has multiple predecessors, results may be imprecise but remain correct *)
- let loc = get_stmtLoc (List.hd s.succs).skind in
+ let loc = get_stmtLoc (List.hd s.succs).skind in (* TODO: why not using Cilfacade.get_stmtLoc? *)
i :: (instrument i loc)
| x -> x
in
@@ -101,7 +101,7 @@ module EvalAssert = struct
match s.preds with
| [p1; p2] when emit_other ->
(* exactly two predecessors -> join point, assert locals if they changed *)
- let join_loc = get_stmtLoc s.skind in
+ let join_loc = get_stmtLoc s.skind in (* TODO: why not using Cilfacade.get_stmtLoc? *)
(* Possible enhancement: It would be nice to only assert locals here that were modified in either branch if witness.invariant.full is false *)
let asserts = make_assert join_loc None in
self#queueInstr asserts; ()
@@ -120,7 +120,7 @@ module EvalAssert = struct
let add_asserts block =
if block.bstmts <> [] then
let with_asserts =
- let b_loc = get_stmtLoc (List.hd block.bstmts).skind in
+ let b_loc = get_stmtLoc (List.hd block.bstmts).skind in (* TODO: why not using Cilfacade.get_stmtLoc? *)
let b_assert_instr = asserts b_loc vars in
[cStmt "{ %I:asserts %S:b }" (fun n t -> makeVarinfo true "unknown" (TVoid [])) b_loc [("asserts", FI b_assert_instr); ("b", FS block.bstmts)]]
in
diff --git a/src/util/cilfacade.ml b/src/util/cilfacade.ml
index 8c2f828886..a9b7484114 100644
--- a/src/util/cilfacade.ml
+++ b/src/util/cilfacade.ml
@@ -5,31 +5,7 @@ open GoblintCil
module E = Errormsg
module GU = Goblintutil
-
-let get_labelLoc = function
- | Label (_, loc, _) -> loc
- | Case (_, loc, _) -> loc
- | CaseRange (_, _, loc, _) -> loc
- | Default (loc, _) -> loc
-
-let rec get_labelsLoc = function
- | [] -> Cil.locUnknown
- | label :: labels ->
- let loc = get_labelLoc label in
- if CilType.Location.equal loc Cil.locUnknown then
- get_labelsLoc labels (* maybe another label has known location *)
- else
- loc
-
-let get_stmtkindLoc = Cil.get_stmtLoc (* CIL has a confusing name for this function *)
-
-let get_stmtLoc stmt =
- match stmt.skind with
- (* Cil.get_stmtLoc returns Cil.locUnknown in these cases, so try labels instead *)
- | Instr []
- | Block {bstmts = []; _} ->
- get_labelsLoc stmt.labels
- | _ -> get_stmtkindLoc stmt.skind
+include Cilfacade0
(** Is character type (N1570 6.2.5.15)? *)
let isCharType = function
@@ -645,6 +621,7 @@ let find_original_name vi = VarinfoH.find_opt (ResettableLazy.force original_nam
let reset_lazy () =
+ StmtH.clear pseudo_return_to_fun;
ResettableLazy.reset stmt_fundecs;
ResettableLazy.reset varinfo_fundecs;
ResettableLazy.reset name_fundecs;
diff --git a/src/util/cilfacade0.ml b/src/util/cilfacade0.ml
new file mode 100644
index 0000000000..80b21d8592
--- /dev/null
+++ b/src/util/cilfacade0.ml
@@ -0,0 +1,47 @@
+(** Cilfacade functions to avoid dependency cycles.*)
+open GoblintCil
+
+let get_labelLoc = function
+ | Label (_, loc, _) -> loc
+ | Case (_, loc, _) -> loc
+ | CaseRange (_, _, loc, _) -> loc
+ | Default (loc, _) -> loc
+
+let rec get_labelsLoc = function
+ | [] -> Cil.locUnknown
+ | label :: labels ->
+ let loc = get_labelLoc label in
+ if CilType.Location.equal loc Cil.locUnknown then
+ get_labelsLoc labels (* maybe another label has known location *)
+ else
+ loc
+
+(** Following functions are similar to [Cil] versions, but return expression location instead of entire statement location, where possible. *)
+(* Ideally we would have both copies of the functions available, but UpdateCil would have to be adapted per-stmtkind/instr to store and update either one or two locations. *)
+
+(** Get expression location for [Cil.instr]. *)
+let get_instrLoc = function
+ | Set (_, _, _loc, eloc) -> eloc
+ | Call (_, _, _, _loc, eloc) -> eloc
+ | Asm (_, _, _, _, _, loc) -> loc
+ | VarDecl (_, loc) -> loc
+
+(** Get expression location for [Cil.stmt]. *)
+(* confusingly CIL.get_stmtLoc works on stmtkind instead *)
+let rec get_stmtLoc stmt =
+ match stmt.skind with
+ (* no stmtkind/instr location in these cases, so try labels instead *)
+ | Instr []
+ | Block {bstmts = []; _} ->
+ get_labelsLoc stmt.labels
+
+ | Instr (hd :: _) -> get_instrLoc hd
+ | Return (_, loc) -> loc
+ | Goto (_, loc) -> loc
+ | ComputedGoto (_, loc) -> loc
+ | Break loc -> loc
+ | Continue loc -> loc
+ | If (_, _, _, _loc, eloc) -> eloc
+ | Switch (_, _, _, _loc, eloc) -> eloc
+ | Loop (_, _loc, eloc, _, _) -> eloc
+ | Block {bstmts = hd :: _; _} -> get_stmtLoc hd
diff --git a/src/util/deadcode.ml b/src/util/deadcode.ml
deleted file mode 100644
index cf25d64f5e..0000000000
--- a/src/util/deadcode.ml
+++ /dev/null
@@ -1,5 +0,0 @@
-module Locmap = BatHashtbl.Make (CilType.Location)
-
-let dead_branches_then : bool Locmap.t = Locmap.create 10
-let dead_branches_else : bool Locmap.t = Locmap.create 10
-let dead_branches_cond : GoblintCil.exp Locmap.t = Locmap.create 10
diff --git a/src/util/goblintutil.ml b/src/util/goblintutil.ml
index 3181c12498..ae2ee45cd2 100644
--- a/src/util/goblintutil.ml
+++ b/src/util/goblintutil.ml
@@ -97,6 +97,7 @@ let seconds_of_duration_string =
let vars = ref 0
let evals = ref 0
+let narrow_reuses = ref 0
(* print GC statistics; taken from Cil.Stats.print which also includes timing; there's also Gc.print_stat, but it's in words instead of MB and more info than we want (also slower than quick_stat since it goes through the heap) *)
let print_gc_quick_stat chn =
diff --git a/src/util/messages.ml b/src/util/messages.ml
index 24bbab01ff..577b0fd2da 100644
--- a/src/util/messages.ml
+++ b/src/util/messages.ml
@@ -39,10 +39,27 @@ struct
| _ -> Result.Error "Messages.Severity.of_yojson"
end
+module Location =
+struct
+ type t =
+ | Node of Node0.t (** Location identified by a node. Strongly preferred, because output location updates incrementally. *)
+ | CilLocation of CilType.Location.t (** Location identified by a literal CIL location. Strongly discouraged, because not updated incrementally. *)
+ [@@deriving eq, ord, hash]
+
+ let to_cil = function
+ | Node node -> UpdateCil0.getLoc node (* use incrementally updated location *)
+ | CilLocation loc -> loc
+
+ let to_yojson x = CilType.Location.to_yojson (to_cil x)
+ let of_yojson x =
+ CilType.Location.of_yojson x
+ |> BatResult.map (fun loc -> CilLocation loc)
+end
+
module Piece =
struct
type t = {
- loc: CilType.Location.t option; (* only *_each warnings have this, used for deduplication *)
+ loc: Location.t option; (* only *_each warnings have this, used for deduplication *)
text: string;
context: (Obj.t [@equal fun x y -> Hashtbl.hash (Obj.obj x) = Hashtbl.hash (Obj.obj y)] [@compare fun x y -> Stdlib.compare (Hashtbl.hash (Obj.obj x)) (Hashtbl.hash (Obj.obj y))] [@hash fun x -> Hashtbl.hash (Obj.obj x)] [@to_yojson fun x -> `Int (Hashtbl.hash (Obj.obj x))] [@of_yojson fun x -> Result.Ok Goblintutil.dummy_obj]) option; (* TODO: this equality is terrible... *)
} [@@deriving eq, ord, hash, yojson]
@@ -135,9 +152,12 @@ struct
let mem = MH.mem messages_table
+ let add_hook: (Message.t -> unit) ref = ref (fun _ -> ())
+
let add m =
MH.replace messages_table m ();
- messages_list := m :: !messages_list
+ messages_list := m :: !messages_list;
+ !add_hook m
let to_list () =
List.rev !messages_list (* reverse to get in addition order *)
@@ -172,7 +192,7 @@ let print ?(ppf= !formatter) (m: Message.t) =
let pp_prefix = Format.dprintf "@{<%s>[%a]%a@}" severity_stag Severity.pp m.severity Tags.pp m.tags in
let pp_piece ppf piece =
let pp_loc ppf = Format.fprintf ppf " @{(%a)@}" CilType.Location.pp in
- Format.fprintf ppf "@{<%s>%s@}%a" severity_stag (Piece.text_with_context piece) (Format.pp_print_option pp_loc) piece.loc
+ Format.fprintf ppf "@{<%s>%s@}%a" severity_stag (Piece.text_with_context piece) (Format.pp_print_option pp_loc) (Option.map Location.to_cil piece.loc)
in
let pp_quote ppf (loc: GoblintCil.location) =
let lines = BatFile.lines_of loc.file in
@@ -200,7 +220,7 @@ let print ?(ppf= !formatter) (m: Message.t) =
let pp_piece ppf piece =
if get_bool "warn.quote-code" then (
let pp_cut_quote ppf = Format.fprintf ppf "@,@[%a@,@]" (Format.pp_print_option pp_quote) in
- Format.fprintf ppf "%a%a" pp_piece piece pp_cut_quote piece.loc
+ Format.fprintf ppf "%a%a" pp_piece piece pp_cut_quote (Option.map Location.to_cil piece.loc)
)
else
pp_piece ppf piece
@@ -232,10 +252,14 @@ let msg_context () =
else
None (* avoid identical messages from multiple contexts without any mention of context *)
-let msg severity ?loc:(loc= !Tracing.current_loc) ?(tags=[]) ?(category=Category.Unknown) fmt =
+let msg severity ?loc ?(tags=[]) ?(category=Category.Unknown) fmt =
let finish doc =
let text = Pretty.sprint ~width:max_int doc in
- add {tags = Category category :: tags; severity; multipiece = Single {loc = Some loc; text; context = msg_context ()}}
+ let loc = match loc with
+ | Some node -> Some node
+ | None -> Option.map (fun node -> Location.Node node) !Node0.current_node
+ in
+ add {tags = Category category :: tags; severity; multipiece = Single {loc; text; context = msg_context ()}}
in
Pretty.gprintf finish fmt
diff --git a/src/util/options.schema.json b/src/util/options.schema.json
index 55a6264398..554883351c 100644
--- a/src/util/options.schema.json
+++ b/src/util/options.schema.json
@@ -961,12 +961,12 @@
"title": "incremental.reluctant",
"type": "object",
"properties": {
- "on": {
- "title": "incremental.reluctant.on",
+ "enabled": {
+ "title": "incremental.reluctant.enabled",
"description":
"Destabilize nodes in changed functions reluctantly",
"type": "boolean",
- "default": true
+ "default": false
},
"compare": {
"title": "incremental.reluctant.compare",
@@ -974,7 +974,7 @@
"In order to reuse the function's old abstract value the new abstract value must be leq (focus on efficiency) or equal (focus on precision) compared to the old.",
"type": "string",
"enum": ["leq", "equal"],
- "default": "leq"
+ "default": "equal"
}
},
"additionalProperties": false
@@ -996,8 +996,83 @@
"description":
"List of functions that are to be re-analayzed from scratch",
"type": "array",
- "items": { "type": "string" },
+ "items": {
+ "type": "string"
+ },
+ "default": []
+ }
+ },
+ "additionalProperties": false
+ },
+ "restart": {
+ "title": "incremental.restart",
+ "type": "object",
+ "properties": {
+ "sided": {
+ "title": "incremental.restart.sided",
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "title": "incremental.restart.sided.enabled",
+ "description": "Restart affected side-effected variables (transitively) to bot.",
+ "type": "boolean",
+ "default": false
+ },
+ "vars": {
+ "title": "incremental.restart.sided.vars",
+ "description": "Side-effected variables to restart. Globals are non-function entry nodes. Write-only is a subset of globals.",
+ "type": "string",
+ "enum": ["all", "global", "write-only"],
+ "default": "all"
+ },
+ "fuel": {
+ "title": "incremental.restart.sided.fuel",
+ "description": "Initial fuel for bounding transitive restarting, which uses one fuel each time when following side_fuel to restart. Zero fuel never restarts. Negative fuel doesn't bound (infinite fuel).",
+ "type": "integer",
+ "default": -1
+ },
+ "fuel-only-global": {
+ "title": "incremental.restart.sided.fuel-only-global",
+ "description": "Decrease fuel only when going to constraint system globals (not function entry nodes).",
+ "type": "boolean",
+ "default": false
+ }
+ },
+ "additionalProperties": false
+ },
+ "list": {
+ "title": "incremental.restart.list",
+ "description": "List of globals variables and function definitions for which the analysis is to be restarted.",
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
"default": []
+ },
+ "write-only": {
+ "title": "incremental.restart.write-only",
+ "description": "Restart write-only variables to bot during postprocessing.",
+ "type": "boolean",
+ "default": true
+ }
+ },
+ "additionalProperties": false
+ },
+ "postsolver": {
+ "title": "incremental.postsolver",
+ "type" : "object",
+ "properties": {
+ "enabled": {
+ "title": "incremental.postsolver.enabled",
+ "description": "Use incremental postsolver",
+ "type": "boolean",
+ "default": true
+ },
+ "superstable-reached" : {
+ "title": "incremental.postsolver.superstable-reached",
+ "description": "Consider superstable set reached, may be faster but can lead to spurious warnings",
+ "type": "boolean",
+ "default": false
}
},
"additionalProperties": false
@@ -1041,6 +1116,20 @@
}
},
"additionalProperties": false
+ },
+ "read": {
+ "title": "sem.unknown_function.read",
+ "type": "object",
+ "properties": {
+ "args": {
+ "title": "sem.unknown_function.read.args",
+ "description":
+ "Unknown function call reads arguments passed to it",
+ "type": "boolean",
+ "default": true
+ }
+ },
+ "additionalProperties": false
}
},
"additionalProperties": false
@@ -1785,6 +1874,38 @@
"type": "boolean",
"default": true
},
+ "narrow-reuse": {
+ "title": "solvers.td3.narrow-reuse",
+ "description": "Reuse value when switching from widening to narrowing phase. Avoids one unnecessary re-evaluation.",
+ "type": "boolean",
+ "default": true
+ },
+ "restart": {
+ "title": "solvers.td3.restart",
+ "type": "object",
+ "properties": {
+ "wpoint": {
+ "title": "solvers.td3.restart.wpoint",
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "title": "solvers.td3.restart.wpoint.enabled",
+ "description": "Restart wpoint to bot when (re-)detected. Allows incremental to avoid reusing and republishing imprecise local values due to globals (which get restarted).",
+ "type": "boolean",
+ "default": false
+ },
+ "once": {
+ "title": "solvers.td3.restart.wpoint.once",
+ "description": "Restart wpoint only on first detection. Useful for incremental loading.",
+ "type": "boolean",
+ "default": false
+ }
+ },
+ "additionalProperties": false
+ }
+ },
+ "additionalProperties": false
+ },
"verify": {
"title": "solvers.td3.verify",
"description": "Check TD3 data structure invariants",
diff --git a/src/util/sarif.ml b/src/util/sarif.ml
index 5eeb12ce69..99fb78f377 100644
--- a/src/util/sarif.ml
+++ b/src/util/sarif.ml
@@ -70,7 +70,7 @@ let result_of_message (message: Messages.Message.t): Result.t list =
| Success -> ("pass", "none")
in
let piece_location (piece: Messages.Piece.t) = match piece.loc with
- | Some loc -> [location_of_cil_location loc]
+ | Some loc -> [location_of_cil_location (Messages.Location.to_cil loc)]
| None -> []
in
let prefix = Format.asprintf "%a " Messages.Tags.pp message.tags in
@@ -106,7 +106,7 @@ let result_of_message (message: Messages.Message.t): Result.t list =
let files_of_message (message: Messages.Message.t): string list =
let piece_file (piece: Messages.Piece.t) = match piece.loc with
- | Some loc -> Some loc.file
+ | Some loc -> Some (Messages.Location.to_cil loc).file
| None -> None
in
match message.multipiece with
diff --git a/src/util/server.ml b/src/util/server.ml
index ba10fbc08c..b6a81eb7a1 100644
--- a/src/util/server.ml
+++ b/src/util/server.ml
@@ -3,7 +3,7 @@ open Jsonrpc
open GoblintCil
type t = {
- mutable file: Cil.file;
+ mutable file: Cil.file option;
mutable max_ids: MaxIdUtil.max_ids;
input: IO.input;
output: unit IO.output;
@@ -107,7 +107,11 @@ let serve serv =
|> Seq.iter (handle_packet serv)
let make ?(input=stdin) ?(output=stdout) file : t =
- let max_ids = MaxIdUtil.get_file_max_ids file in
+ let max_ids =
+ match file with
+ | Some file -> MaxIdUtil.get_file_max_ids file
+ | None -> MaxIdUtil.get_file_max_ids Cil.dummyFile (* TODO: avoid this altogether *)
+ in
{
file;
max_ids;
@@ -138,27 +142,40 @@ let start file =
let reparse (s: t) =
if GobConfig.get_bool "server.reparse" then (
GoblintDir.init ();
- Fun.protect ~finally:GoblintDir.finalize Maingoblint.preprocess_and_merge, true)
- else s.file, false
+ let file = Fun.protect ~finally:GoblintDir.finalize Maingoblint.preprocess_parse_merge in
+ begin match s.file with
+ | None ->
+ let max_ids = MaxIdUtil.get_file_max_ids file in
+ s.max_ids <- max_ids
+ | Some _ ->
+ ()
+ end;
+ (file, true)
+ )
+ else
+ (Option.get s.file, false)
(* Only called when the file has not been reparsed, so we can skip the expensive CFG comparison. *)
let virtual_changes file =
let eq (glob: Cil.global) _ _ _ = match glob with
- | GFun (fdec, _) -> not (CompareCIL.should_reanalyze fdec), false, None
- | _ -> true, false, None
+ | GFun (fdec, _) when CompareCIL.should_reanalyze fdec -> CompareCIL.ForceReanalyze fdec, None
+ | _ -> Unchanged, None
in
CompareCIL.compareCilFiles ~eq file file
let increment_data (s: t) file reparsed = match Serialize.Cache.get_opt_data SolverData with
| Some solver_data when reparsed ->
- let changes = CompareCIL.compareCilFiles s.file file in
+ let s_file = Option.get s.file in
+ let changes = CompareCIL.compareCilFiles s_file file in
let old_data = Some { Analyses.solver_data } in
- s.max_ids <- UpdateCil.update_ids s.file s.max_ids file changes;
- { server = true; Analyses.changes; old_data }, false
+ s.max_ids <- UpdateCil.update_ids s_file s.max_ids file changes;
+ (* TODO: get globals for restarting from config *)
+ { server = true; Analyses.changes; old_data; restarting = [] }, false
| Some solver_data ->
let changes = virtual_changes file in
let old_data = Some { Analyses.solver_data } in
- { server = true; Analyses.changes; old_data }, false
+ (* TODO: get globals for restarting from config *)
+ { server = true; Analyses.changes; old_data; restarting = [] }, false
| _ -> Analyses.empty_increment_data ~server:true (), true
let analyze ?(reset=false) (s: t) =
@@ -176,12 +193,12 @@ let analyze ?(reset=false) (s: t) =
IntDomain.reset_lazy ();
ApronDomain.reset_lazy ();
Access.reset ();
- s.file <- file;
+ s.file <- Some file;
GobConfig.set_bool "incremental.load" (not fresh);
Fun.protect ~finally:(fun () ->
GobConfig.set_bool "incremental.load" true
) (fun () ->
- Maingoblint.do_analyze increment_data s.file
+ Maingoblint.do_analyze increment_data (Option.get s.file)
)
let () =
@@ -241,18 +258,32 @@ let () =
let process () _ = Preprocessor.dependencies_to_yojson ()
end);
+ register (module struct
+ let name = "pre_files"
+ type params = unit [@@deriving of_yojson]
+ type response = Yojson.Safe.t [@@deriving to_yojson]
+ let process () s =
+ if GobConfig.get_bool "server.reparse" then (
+ GoblintDir.init ();
+ Fun.protect ~finally:GoblintDir.finalize (fun () ->
+ ignore Maingoblint.(preprocess_files () |> parse_preprocessed)
+ )
+ );
+ Preprocessor.dependencies_to_yojson ()
+ end);
+
register (module struct
let name = "functions"
type params = unit [@@deriving of_yojson]
type response = Function.t list [@@deriving to_yojson]
- let process () serv = Function.getFunctionsList serv.file.globals
+ let process () serv = Function.getFunctionsList (Option.get serv.file).globals
end);
register (module struct
let name = "cfg"
type params = { fname: string } [@@deriving of_yojson]
type response = { cfg : string } [@@deriving to_yojson]
- let process { fname } serv =
+ let process { fname } serv =
let fundec = Cilfacade.find_name_fundec fname in
let live _ = true in (* TODO: fix this *)
let cfg = CfgTools.sprint_fundec_html_dot !MyCFG.current_cfg live fundec in
diff --git a/src/util/tracing.ml b/src/util/tracing.ml
index f0a46d0bbf..6a4dce6354 100644
--- a/src/util/tracing.ml
+++ b/src/util/tracing.ml
@@ -86,7 +86,7 @@ let traceTag (sys : string) : Pretty.doc =
(text ((ind !indent_level) ^ "%%% " ^ sys ^ ": "))
let printtrace sys d: unit =
- fprint stderr ~width:80 ((traceTag sys) ++ d);
+ fprint stderr ~width:max_int ((traceTag sys) ++ d);
flush stderr
let gtrace always f sys var ?loc do_subsys fmt =
diff --git a/src/witness/witness.ml b/src/witness/witness.ml
index ff109a7212..f3978a204d 100644
--- a/src/witness/witness.ml
+++ b/src/witness/witness.ml
@@ -264,7 +264,7 @@ module Result (Cfg : CfgBidir)
(EQSys : GlobConstrSys with module LVar = VarF (Spec.C)
and module GVar = GVarF (Spec.V)
and module D = Spec.D
- and module G = Spec.G)
+ and module G = GVarG (Spec.G) (Spec.C))
(LHT : BatHashtbl.S with type key = EQSys.LVar.t)
(GHT : BatHashtbl.S with type key = EQSys.GVar.t) =
struct
@@ -296,7 +296,7 @@ struct
; context = (fun () -> snd lvar)
; edge = MyCFG.Skip
; local = local
- ; global = GHT.find gh
+ ; global = (fun g -> EQSys.G.spec (GHT.find gh (EQSys.GVar.spec g)))
; spawn = (fun v d -> failwith "Cannot \"spawn\" in witness context.")
; split = (fun d es -> failwith "Cannot \"split\" in witness context.")
; sideg = (fun v g -> failwith "Cannot \"sideg\" in witness context.")
diff --git a/src/witness/yamlWitness.ml b/src/witness/yamlWitness.ml
index a2a7160769..3eb7c52cba 100644
--- a/src/witness/yamlWitness.ml
+++ b/src/witness/yamlWitness.ml
@@ -115,10 +115,10 @@ module Query
(EQSys : GlobConstrSys with module LVar = VarF (Spec.C)
and module GVar = GVarF (Spec.V)
and module D = Spec.D
- and module G = Spec.G)
+ and module G = GVarG (Spec.G) (Spec.C))
(GHT : BatHashtbl.S with type key = EQSys.GVar.t) =
struct
- let ask_local (gh: Spec.G.t GHT.t) (lvar:EQSys.LVar.t) local =
+ let ask_local (gh: EQSys.G.t GHT.t) (lvar:EQSys.LVar.t) local =
(* build a ctx for using the query system *)
let rec ctx =
{ ask = (fun (type a) (q: a Queries.t) -> Spec.query ctx q)
@@ -129,7 +129,7 @@ struct
; context = (fun () -> snd lvar)
; edge = MyCFG.Skip
; local = local
- ; global = (fun v -> try GHT.find gh v with Not_found -> Spec.G.bot ()) (* TODO: how can be missing? *)
+ ; global = (fun g -> try EQSys.G.spec (GHT.find gh (EQSys.GVar.spec g)) with Not_found -> Spec.G.bot ()) (* TODO: how can be missing? *)
; spawn = (fun v d -> failwith "Cannot \"spawn\" in witness context.")
; split = (fun d es -> failwith "Cannot \"split\" in witness context.")
; sideg = (fun v g -> failwith "Cannot \"sideg\" in witness context.")
@@ -137,7 +137,7 @@ struct
in
Spec.query ctx
- let ask_local_node (gh: Spec.G.t GHT.t) (n: Node.t) local =
+ let ask_local_node (gh: EQSys.G.t GHT.t) (n: Node.t) local =
(* build a ctx for using the query system *)
let rec ctx =
{ ask = (fun (type a) (q: a Queries.t) -> Spec.query ctx q)
@@ -148,7 +148,7 @@ struct
; context = (fun () -> ctx_failwith "No context in witness context.")
; edge = MyCFG.Skip
; local = local
- ; global = (fun v -> try GHT.find gh v with Not_found -> Spec.G.bot ()) (* TODO: how can be missing? *)
+ ; global = (fun g -> try EQSys.G.spec (GHT.find gh (EQSys.GVar.spec g)) with Not_found -> Spec.G.bot ()) (* TODO: how can be missing? *)
; spawn = (fun v d -> failwith "Cannot \"spawn\" in witness context.")
; split = (fun d es -> failwith "Cannot \"split\" in witness context.")
; sideg = (fun v g -> failwith "Cannot \"sideg\" in witness context.")
@@ -164,7 +164,7 @@ module Make
(EQSys : GlobConstrSys with module LVar = VarF (Spec.C)
and module GVar = GVarF (Spec.V)
and module D = Spec.D
- and module G = Spec.G)
+ and module G = GVarG (Spec.G) (Spec.C))
(LHT : BatHashtbl.S with type key = EQSys.LVar.t)
(GHT : BatHashtbl.S with type key = EQSys.GVar.t) =
struct
@@ -366,7 +366,7 @@ module Validator
(EQSys : GlobConstrSys with module LVar = VarF (Spec.C)
and module GVar = GVarF (Spec.V)
and module D = Spec.D
- and module G = Spec.G)
+ and module G = GVarG (Spec.G) (Spec.C))
(LHT : BatHashtbl.S with type key = EQSys.LVar.t)
(GHT : BatHashtbl.S with type key = EQSys.GVar.t) =
struct
@@ -414,6 +414,7 @@ struct
let target_type = YamlWitnessType.EntryType.entry_type entry.entry_type in
let validate_lvars_invariant ~entry_certificate ~loc ~lvars inv =
+ let msgLoc: M.Location.t = CilLocation loc in
match InvariantParser.parse_cabs inv with
| Ok inv_cabs ->
@@ -445,31 +446,31 @@ struct
begin match Option.get (VR.result_of_enum result) with
| Confirmed ->
incr cnt_confirmed;
- M.success ~category:Witness ~loc "invariant confirmed: %s" inv;
+ M.success ~category:Witness ~loc:msgLoc "invariant confirmed: %s" inv;
let target = Entry.target ~uuid ~type_:target_type ~file_name:loc.file in
let certification = Entry.certification true in
let certificate_entry = entry_certificate ~target ~certification in
Some certificate_entry
| Unconfirmed ->
incr cnt_unconfirmed;
- M.warn ~category:Witness ~loc "invariant unconfirmed: %s" inv;None
+ M.warn ~category:Witness ~loc:msgLoc "invariant unconfirmed: %s" inv;None
| Refuted ->
incr cnt_refuted;
- M.error ~category:Witness ~loc "invariant refuted: %s" inv;
+ M.error ~category:Witness ~loc:msgLoc "invariant refuted: %s" inv;
let target = Entry.target ~uuid ~type_:target_type ~file_name:loc.file in
let certification = Entry.certification false in
let certificate_entry = entry_certificate ~target ~certification in
Some certificate_entry
| ParseError ->
incr cnt_error;
- M.error ~category:Witness ~loc "CIL couldn't parse invariant: %s" inv;
- M.info ~category:Witness ~loc "invariant has undefined variables or side effects: %s" inv;
+ M.error ~category:Witness ~loc:msgLoc "CIL couldn't parse invariant: %s" inv;
+ M.info ~category:Witness ~loc:msgLoc "invariant has undefined variables or side effects: %s" inv;
None
end
| Error e ->
incr cnt_error;
- M.error ~category:Witness ~loc "Frontc couldn't parse invariant: %s" inv;
- M.info ~category:Witness ~loc "invariant has invalid syntax: %s" inv;
+ M.error ~category:Witness ~loc:msgLoc "Frontc couldn't parse invariant: %s" inv;
+ M.info ~category:Witness ~loc:msgLoc "invariant has invalid syntax: %s" inv;
None
in
@@ -477,13 +478,14 @@ struct
let loc = loc_of_location loop_invariant.location in
let inv = loop_invariant.loop_invariant.string in
let entry_certificate = Entry.loop_invariant_certificate in
+ let msgLoc: M.Location.t = CilLocation loc in
match Locator.find_opt locator loc with
| Some lvars ->
validate_lvars_invariant ~entry_certificate ~loc ~lvars inv
| None ->
incr cnt_error;
- M.warn ~category:Witness ~loc "couldn't locate invariant: %s" inv;
+ M.warn ~category:Witness ~loc:msgLoc "couldn't locate invariant: %s" inv;
None
in
@@ -492,6 +494,7 @@ struct
let pre = precondition_loop_invariant.precondition.string in
let inv = precondition_loop_invariant.loop_invariant.string in
let entry_certificate = Entry.precondition_loop_invariant_certificate in
+ let msgLoc: M.Location.t = CilLocation loc in
match Locator.find_opt locator loc with
| Some lvars ->
@@ -512,28 +515,28 @@ struct
else
false
| Error e ->
- M.error ~category:Witness ~loc "CIL couldn't parse precondition: %s" inv;
- M.info ~category:Witness ~loc "precondition has undefined variables or side effects: %s" inv;
+ M.error ~category:Witness ~loc:msgLoc "CIL couldn't parse precondition: %s" inv;
+ M.info ~category:Witness ~loc:msgLoc "precondition has undefined variables or side effects: %s" inv;
false
in
let lvars = LvarS.filter precondition_holds lvars in
if LvarS.is_empty lvars then (
incr cnt_unchecked;
- M.warn ~category:Witness ~loc "precondition never definitely holds: %s" pre;
+ M.warn ~category:Witness ~loc:msgLoc "precondition never definitely holds: %s" pre;
None
)
else
validate_lvars_invariant ~entry_certificate ~loc ~lvars inv
| Error e ->
incr cnt_error;
- M.error ~category:Witness ~loc "Frontc couldn't parse precondition: %s" pre;
- M.info ~category:Witness ~loc "precondition has invalid syntax: %s" pre;
+ M.error ~category:Witness ~loc:msgLoc "Frontc couldn't parse precondition: %s" pre;
+ M.info ~category:Witness ~loc:msgLoc "precondition has invalid syntax: %s" pre;
None
end
| None ->
incr cnt_error;
- M.warn ~category:Witness ~loc "couldn't locate invariant: %s" inv;
+ M.warn ~category:Witness ~loc:msgLoc "couldn't locate invariant: %s" inv;
None
in
diff --git a/tests/incremental/00-basic/03-changed_start_state2.json b/tests/incremental/00-basic/03-changed_start_state2.json
index 82849741fd..05fd4ebc64 100644
--- a/tests/incremental/00-basic/03-changed_start_state2.json
+++ b/tests/incremental/00-basic/03-changed_start_state2.json
@@ -5,5 +5,12 @@
"int": false
}
}
+ },
+ "incremental": {
+ "restart": {
+ "sided": {
+ "enabled": true
+ }
+ }
}
}
diff --git a/tests/incremental/00-basic/03-changed_start_state2.patch b/tests/incremental/00-basic/03-changed_start_state2.patch
index e2399fa8f6..e37ef4d7a2 100644
--- a/tests/incremental/00-basic/03-changed_start_state2.patch
+++ b/tests/incremental/00-basic/03-changed_start_state2.patch
@@ -13,7 +13,7 @@
// overwriting, therefore the current imprecision.
- __goblint_check(g == 1);
- __goblint_check(g != 2);
-+ __goblint_check(g != 1); // TODO (restarting)
-+ __goblint_check(g == 2); // TODO
++ __goblint_check(g != 1);
++ __goblint_check(g == 2);
return 0;
}
diff --git a/tests/incremental/00-basic/07-dead-branch.c b/tests/incremental/00-basic/07-dead-branch.c
new file mode 100644
index 0000000000..13d6b81436
--- /dev/null
+++ b/tests/incremental/00-basic/07-dead-branch.c
@@ -0,0 +1,16 @@
+#include
+
+void foo() {
+
+}
+
+int main() {
+ int a = 1;
+
+ if (a) // WARN
+ __goblint_check(a);
+
+ foo();
+
+ return 0;
+}
\ No newline at end of file
diff --git a/tests/incremental/00-basic/07-dead-branch.json b/tests/incremental/00-basic/07-dead-branch.json
new file mode 100644
index 0000000000..82d2b61774
--- /dev/null
+++ b/tests/incremental/00-basic/07-dead-branch.json
@@ -0,0 +1,7 @@
+{
+ "ana": {
+ "dead-code": {
+ "branches": true
+ }
+ }
+}
diff --git a/tests/incremental/00-basic/07-dead-branch.patch b/tests/incremental/00-basic/07-dead-branch.patch
new file mode 100644
index 0000000000..43a0508cff
--- /dev/null
+++ b/tests/incremental/00-basic/07-dead-branch.patch
@@ -0,0 +1,11 @@
+--- tests/incremental/00-basic/07-dead-branch.c
++++ tests/incremental/00-basic/07-dead-branch.c
+@@ -1,7 +1,7 @@
+ #include
+
+ void foo() {
+-
++ __goblint_check(1);
+ }
+
+ int main() {
diff --git a/tests/incremental/00-basic/09-unreach.c b/tests/incremental/00-basic/09-unreach.c
new file mode 100644
index 0000000000..77ae9e4bea
--- /dev/null
+++ b/tests/incremental/00-basic/09-unreach.c
@@ -0,0 +1,14 @@
+#include
+
+void foo() {
+ int x = 2;
+ __goblint_check(x == 3); //FAIL
+}
+
+int main() {
+ int a = 1;
+
+ foo();
+
+ return 0;
+}
diff --git a/tests/incremental/00-basic/09-unreach.json b/tests/incremental/00-basic/09-unreach.json
new file mode 100644
index 0000000000..c1e5e17542
--- /dev/null
+++ b/tests/incremental/00-basic/09-unreach.json
@@ -0,0 +1,10 @@
+{
+ "dbg": {
+ "debug": true
+ },
+ "incremental" : {
+ "postsolver": {
+ "enabled": true
+ }
+ }
+}
diff --git a/tests/incremental/00-basic/09-unreach.patch b/tests/incremental/00-basic/09-unreach.patch
new file mode 100644
index 0000000000..146287fea5
--- /dev/null
+++ b/tests/incremental/00-basic/09-unreach.patch
@@ -0,0 +1,17 @@
+--- tests/incremental/00-basic/09-unreach.c
++++ tests/incremental/00-basic/09-unreach.c
+@@ -2,13 +2,12 @@
+
+ void foo() {
+ int x = 2;
+- __goblint_check(x == 3); //FAIL
++ __goblint_check(x == 3); //NOWARN
+ }
+
+ int main() {
+ int a = 1;
+
+- foo();
+
+ return 0;
+ }
diff --git a/tests/incremental/00-basic/10-reach.c b/tests/incremental/00-basic/10-reach.c
new file mode 100644
index 0000000000..6619cc4e81
--- /dev/null
+++ b/tests/incremental/00-basic/10-reach.c
@@ -0,0 +1,14 @@
+#include
+#include
+
+void foo() {
+ int x = 2;
+ __goblint_check(x == 2);
+}
+
+int main() {
+ pthread_t id;
+ pthread_create(&id, NULL, foo, NULL); // just go multithreaded
+
+ return 0;
+}
diff --git a/tests/incremental/00-basic/10-reach.json b/tests/incremental/00-basic/10-reach.json
new file mode 100644
index 0000000000..c1e5e17542
--- /dev/null
+++ b/tests/incremental/00-basic/10-reach.json
@@ -0,0 +1,10 @@
+{
+ "dbg": {
+ "debug": true
+ },
+ "incremental" : {
+ "postsolver": {
+ "enabled": true
+ }
+ }
+}
diff --git a/tests/incremental/00-basic/10-reach.patch b/tests/incremental/00-basic/10-reach.patch
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/incremental/00-basic/11-unreach-reusesuper.c b/tests/incremental/00-basic/11-unreach-reusesuper.c
new file mode 100644
index 0000000000..77ae9e4bea
--- /dev/null
+++ b/tests/incremental/00-basic/11-unreach-reusesuper.c
@@ -0,0 +1,14 @@
+#include
+
+void foo() {
+ int x = 2;
+ __goblint_check(x == 3); //FAIL
+}
+
+int main() {
+ int a = 1;
+
+ foo();
+
+ return 0;
+}
diff --git a/tests/incremental/00-basic/11-unreach-reusesuper.json b/tests/incremental/00-basic/11-unreach-reusesuper.json
new file mode 100644
index 0000000000..ef6bdab239
--- /dev/null
+++ b/tests/incremental/00-basic/11-unreach-reusesuper.json
@@ -0,0 +1,11 @@
+{
+ "dbg": {
+ "debug": true
+ },
+ "incremental" : {
+ "postsolver": {
+ "enabled": true,
+ "superstable-reached" : true
+ }
+ }
+}
diff --git a/tests/incremental/00-basic/11-unreach-reusesuper.patch b/tests/incremental/00-basic/11-unreach-reusesuper.patch
new file mode 100644
index 0000000000..af42647435
--- /dev/null
+++ b/tests/incremental/00-basic/11-unreach-reusesuper.patch
@@ -0,0 +1,17 @@
+--- tests/incremental/00-basic/11-unreach-reusesuper.c
++++ tests/incremental/00-basic/11-unreach-reusesuper.c
+@@ -2,13 +2,12 @@
+
+ void foo() {
+ int x = 2;
+- __goblint_check(x == 3); //FAIL
++ __goblint_check(x == 3); //TODO (considered rechable without cheap from scratch re-analysis)
+ }
+
+ int main() {
+ int a = 1;
+
+- foo();
+
+ return 0;
+ }
diff --git a/tests/incremental/01-force-reanalyze/00-int.json b/tests/incremental/01-force-reanalyze/00-int.json
index 5448288b6b..a459e0a7a9 100644
--- a/tests/incremental/01-force-reanalyze/00-int.json
+++ b/tests/incremental/01-force-reanalyze/00-int.json
@@ -15,7 +15,7 @@
"funs": ["f"]
},
"reluctant" : {
- "on" : false
+ "enabled" : false
}
}
}
diff --git a/tests/incremental/01-force-reanalyze/01-int-reluctant.c b/tests/incremental/01-force-reanalyze/01-int-reluctant.c
new file mode 100644
index 0000000000..15c10713ed
--- /dev/null
+++ b/tests/incremental/01-force-reanalyze/01-int-reluctant.c
@@ -0,0 +1,17 @@
+#include
+
+int f(int in){
+ while(in < 17) {
+ in++;
+ }
+ __goblint_check(in == 17); //UNKNOWN
+ return in;
+}
+
+int main() {
+ int a = 0;
+ __goblint_check(a); // FAIL!
+ a = f(a);
+ __goblint_check(a == 17); //UNKNOWN
+ return 0;
+}
diff --git a/tests/incremental/01-force-reanalyze/01-int-reluctant.json b/tests/incremental/01-force-reanalyze/01-int-reluctant.json
new file mode 100644
index 0000000000..006e469abb
--- /dev/null
+++ b/tests/incremental/01-force-reanalyze/01-int-reluctant.json
@@ -0,0 +1,24 @@
+{
+ "annotation" : {
+ "int" : {
+ "enabled" : true
+ }
+ },
+ "ana" : {
+ "int" : {
+ "refinement" : "fixpoint",
+ "interval_narrow_by_meet": true
+ }
+ },
+ "incremental" : {
+ "force-reanalyze" : {
+ "funs": ["f"]
+ },
+ "reluctant" : {
+ "enabled": true
+ },
+ "postsolver": {
+ "enabled": false
+ }
+ }
+}
diff --git a/tests/incremental/01-force-reanalyze/01-int-reluctant.patch b/tests/incremental/01-force-reanalyze/01-int-reluctant.patch
new file mode 100644
index 0000000000..b573b36217
--- /dev/null
+++ b/tests/incremental/01-force-reanalyze/01-int-reluctant.patch
@@ -0,0 +1,37 @@
+diff --git tests/incremental/01-force-reanalyze/01-int-reluctant.c tests/incremental/01-force-reanalyze/01-int-reluctant.c
+index 38187f1c0..6126fe8cf 100644
+--- tests/incremental/01-force-reanalyze/01-int-reluctant.c
++++ tests/incremental/01-force-reanalyze/01-int-reluctant.c
+@@ -4,7 +4,7 @@ int f(int in){
+ while(in < 17) {
+ in++;
+ }
+- __goblint_check(in == 17); //UNKNOWN
++ __goblint_check(in == 17);
+ return in;
+ }
+
+@@ -12,6 +12,6 @@ int main() {
+ int a = 0;
+ __goblint_check(a); // FAIL!
+ a = f(a);
+- __goblint_check(a == 17); //UNKNOWN
++ __goblint_check(a == 17);
+ return 0;
+ }
+diff --git tests/incremental/01-force-reanalyze/01-int-reluctant.json tests/incremental/01-force-reanalyze/01-int-reluctant.json
+index d58c2254b..8834d182d 100644
+--- tests/incremental/01-force-reanalyze/01-int-reluctant.json
++++ tests/incremental/01-force-reanalyze/01-int-reluctant.json
+@@ -2,6 +2,11 @@
+ "annotation" : {
+ "int" : {
+ "enabled" : true
++ },
++ "goblint_precision": {
++ "f": [
++ "interval"
++ ]
+ }
+ },
+ "ana" : {
diff --git a/tests/incremental/03-precision-annotation/02-reluctant-int-annotation.c b/tests/incremental/03-precision-annotation/02-reluctant-int-annotation.c
new file mode 100644
index 0000000000..15c10713ed
--- /dev/null
+++ b/tests/incremental/03-precision-annotation/02-reluctant-int-annotation.c
@@ -0,0 +1,17 @@
+#include
+
+int f(int in){
+ while(in < 17) {
+ in++;
+ }
+ __goblint_check(in == 17); //UNKNOWN
+ return in;
+}
+
+int main() {
+ int a = 0;
+ __goblint_check(a); // FAIL!
+ a = f(a);
+ __goblint_check(a == 17); //UNKNOWN
+ return 0;
+}
diff --git a/tests/incremental/03-precision-annotation/02-reluctant-int-annotation.json b/tests/incremental/03-precision-annotation/02-reluctant-int-annotation.json
new file mode 100644
index 0000000000..6964e3b4cc
--- /dev/null
+++ b/tests/incremental/03-precision-annotation/02-reluctant-int-annotation.json
@@ -0,0 +1,21 @@
+{
+ "annotation": {
+ "int": {
+ "enabled": true
+ }
+ },
+ "ana": {
+ "int": {
+ "refinement": "fixpoint",
+ "interval_narrow_by_meet": true
+ }
+ },
+ "incremental": {
+ "reluctant": {
+ "enabled": true
+ },
+ "postsolver": {
+ "enabled": false
+ }
+ }
+}
diff --git a/tests/incremental/03-precision-annotation/02-reluctant-int-annotation.patch b/tests/incremental/03-precision-annotation/02-reluctant-int-annotation.patch
new file mode 100644
index 0000000000..c279d53cfb
--- /dev/null
+++ b/tests/incremental/03-precision-annotation/02-reluctant-int-annotation.patch
@@ -0,0 +1,25 @@
+diff --git tests/incremental/03-precision-annotation/02-reluctant-int-annotation.c tests/incremental/03-precision-annotation/02-reluctant-int-annotation.c
+index 38187f1c0..698e45b62 100644
+--- tests/incremental/03-precision-annotation/02-reluctant-int-annotation.c
++++ tests/incremental/03-precision-annotation/02-reluctant-int-annotation.c
+@@ -1,10 +1,11 @@
+ #include
+
++int f(int in) __attribute__ ((goblint_precision("def_exc", "interval")));
+ int f(int in){
+ while(in < 17) {
+ in++;
+ }
+- __goblint_check(in == 17); //UNKNOWN
++ __goblint_check(in == 17);
+ return in;
+ }
+
+@@ -12,6 +13,6 @@ int main() {
+ int a = 0;
+ __goblint_check(a); // FAIL!
+ a = f(a);
+- __goblint_check(a == 17); //UNKNOWN
++ __goblint_check(a == 17);
+ return 0;
+ }
diff --git a/tests/incremental/03-precision-annotation/03-reluctant-int-annotation-dyn.c b/tests/incremental/03-precision-annotation/03-reluctant-int-annotation-dyn.c
new file mode 100644
index 0000000000..14f1b07cce
--- /dev/null
+++ b/tests/incremental/03-precision-annotation/03-reluctant-int-annotation-dyn.c
@@ -0,0 +1,24 @@
+#include
+
+typedef int int_to_int_fun (int);
+
+int f(int in){
+ while(in < 17) {
+ in++;
+ }
+ __goblint_check(in == 17); //UNKNOWN
+ return in;
+}
+
+int_to_int_fun *get_fun(){
+ return &f;
+}
+
+int main() {
+ int_to_int_fun *fun = get_fun();
+ int a = 0;
+ __goblint_check(a); // FAIL!
+ a = fun(a);
+ __goblint_check(a == 17); //UNKNOWN
+ return 0;
+}
diff --git a/tests/incremental/03-precision-annotation/03-reluctant-int-annotation-dyn.json b/tests/incremental/03-precision-annotation/03-reluctant-int-annotation-dyn.json
new file mode 100644
index 0000000000..6964e3b4cc
--- /dev/null
+++ b/tests/incremental/03-precision-annotation/03-reluctant-int-annotation-dyn.json
@@ -0,0 +1,21 @@
+{
+ "annotation": {
+ "int": {
+ "enabled": true
+ }
+ },
+ "ana": {
+ "int": {
+ "refinement": "fixpoint",
+ "interval_narrow_by_meet": true
+ }
+ },
+ "incremental": {
+ "reluctant": {
+ "enabled": true
+ },
+ "postsolver": {
+ "enabled": false
+ }
+ }
+}
diff --git a/tests/incremental/03-precision-annotation/03-reluctant-int-annotation-dyn.patch b/tests/incremental/03-precision-annotation/03-reluctant-int-annotation-dyn.patch
new file mode 100644
index 0000000000..0dc881d93a
--- /dev/null
+++ b/tests/incremental/03-precision-annotation/03-reluctant-int-annotation-dyn.patch
@@ -0,0 +1,26 @@
+diff --git tests/incremental/03-precision-annotation/03-reluctant-int-annotation-dyn.c tests/incremental/03-precision-annotation/03-reluctant-int-annotation-dyn.c
+index 30391cdf6..4d2620e84 100644
+--- tests/incremental/03-precision-annotation/03-reluctant-int-annotation-dyn.c
++++ tests/incremental/03-precision-annotation/03-reluctant-int-annotation-dyn.c
+@@ -2,11 +2,12 @@
+
+ typedef int int_to_int_fun (int);
+
++int f(int in) __attribute__ ((goblint_precision("def_exc", "interval")));
+ int f(int in){
+ while(in < 17) {
+ in++;
+ }
+- __goblint_check(in == 17); //UNKNOWN
++ __goblint_check(in == 17);
+ return in;
+ }
+
+@@ -19,6 +20,6 @@ int main() {
+ int a = 0;
+ __goblint_check(a); // FAIL!
+ a = fun(a);
+- __goblint_check(a == 17); //UNKNOWN
++ __goblint_check(a == 17);
+ return 0;
+ }
diff --git a/tests/incremental/11-restart/00-justglob.c b/tests/incremental/11-restart/00-justglob.c
new file mode 100644
index 0000000000..61b059f8ea
--- /dev/null
+++ b/tests/incremental/11-restart/00-justglob.c
@@ -0,0 +1,2 @@
+int max_domains = 0;
+int main() {}
diff --git a/tests/incremental/11-restart/00-justglob.json b/tests/incremental/11-restart/00-justglob.json
new file mode 100644
index 0000000000..0e0dcd235c
--- /dev/null
+++ b/tests/incremental/11-restart/00-justglob.json
@@ -0,0 +1,3 @@
+{
+
+}
\ No newline at end of file
diff --git a/tests/incremental/11-restart/00-justglob.patch b/tests/incremental/11-restart/00-justglob.patch
new file mode 100644
index 0000000000..cc43da4357
--- /dev/null
+++ b/tests/incremental/11-restart/00-justglob.patch
@@ -0,0 +1,6 @@
+--- tests/incremental/11-restart/00-justglob.c
++++ tests/incremental/11-restart/00-justglob.c
+@@ -1,2 +1,2 @@
+-int max_domains = 0;
++int max_domains = 4;
+ int main() {}
diff --git a/tests/incremental/11-restart/01-global-nochange.c b/tests/incremental/11-restart/01-global-nochange.c
new file mode 100644
index 0000000000..4c75e32d16
--- /dev/null
+++ b/tests/incremental/11-restart/01-global-nochange.c
@@ -0,0 +1,19 @@
+#include
+#include
+
+int g = 1;
+
+void* t_fun(void *arg) {
+ g = 2;
+ return NULL;
+}
+
+int main() {
+ pthread_t id;
+ pthread_create(&id, NULL, t_fun, NULL); // just go multithreaded
+
+ __goblint_check(g == 1); // UNKNOWN before and after
+ __goblint_check(g == 2); // UNKNOWN before and after
+ __goblint_check(g == 0); // FAIL before and after
+ return 0;
+}
\ No newline at end of file
diff --git a/tests/incremental/11-restart/01-global-nochange.json b/tests/incremental/11-restart/01-global-nochange.json
new file mode 100644
index 0000000000..5be265f269
--- /dev/null
+++ b/tests/incremental/11-restart/01-global-nochange.json
@@ -0,0 +1,7 @@
+{
+ "ana": {
+ "int": {
+ "interval": true
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/incremental/11-restart/01-global-nochange.patch b/tests/incremental/11-restart/01-global-nochange.patch
new file mode 100644
index 0000000000..af96d85502
--- /dev/null
+++ b/tests/incremental/11-restart/01-global-nochange.patch
@@ -0,0 +1,11 @@
+--- tests/incremental/11-restart/01-global-nochange.c
++++ tests/incremental/11-restart/01-global-nochange.c
+@@ -2,7 +2,7 @@
+ #include
+
+ int g = 1;
+-
++// cmt
+ void* t_fun(void *arg) {
+ g = 2;
+ return NULL;
diff --git a/tests/incremental/11-restart/02-global-remove.c b/tests/incremental/11-restart/02-global-remove.c
new file mode 100644
index 0000000000..d52e9d472d
--- /dev/null
+++ b/tests/incremental/11-restart/02-global-remove.c
@@ -0,0 +1,17 @@
+#include
+#include
+
+int g = 1;
+
+void* t_fun(void *arg) {
+ g = 2;
+ return NULL;
+}
+
+int main() {
+ pthread_t id;
+ pthread_create(&id, NULL, t_fun, NULL); // just go multithreaded
+
+ __goblint_check(g == 1); // UNKNOWN before, success after
+ return 0;
+}
\ No newline at end of file
diff --git a/tests/incremental/11-restart/02-global-remove.json b/tests/incremental/11-restart/02-global-remove.json
new file mode 100644
index 0000000000..d8addf1280
--- /dev/null
+++ b/tests/incremental/11-restart/02-global-remove.json
@@ -0,0 +1,9 @@
+{
+ "incremental": {
+ "restart": {
+ "sided": {
+ "enabled": true
+ }
+ }
+ }
+}
diff --git a/tests/incremental/11-restart/02-global-remove.patch b/tests/incremental/11-restart/02-global-remove.patch
new file mode 100644
index 0000000000..cd05a22ddd
--- /dev/null
+++ b/tests/incremental/11-restart/02-global-remove.patch
@@ -0,0 +1,20 @@
+--- tests/incremental/11-restart/02-global-remove.c
++++ tests/incremental/11-restart/02-global-remove.c
+@@ -4,7 +4,7 @@
+ int g = 1;
+
+ void* t_fun(void *arg) {
+- g = 2;
++
+ return NULL;
+ }
+
+@@ -12,6 +12,6 @@ int main() {
+ pthread_t id;
+ pthread_create(&id, NULL, t_fun, NULL); // just go multithreaded
+
+- __goblint_check(g == 1); // UNKNOWN before, success after
++ __goblint_check(g == 1); // unknown before, SUCCESS after
+ return 0;
+ }
+\ No newline at end of file
diff --git a/tests/incremental/11-restart/03-global-change.c b/tests/incremental/11-restart/03-global-change.c
new file mode 100644
index 0000000000..6ce696b4d4
--- /dev/null
+++ b/tests/incremental/11-restart/03-global-change.c
@@ -0,0 +1,19 @@
+#include
+#include
+
+int g = 1;
+
+void* t_fun(void *arg) {
+ g = 2;
+ return NULL;
+}
+
+int main() {
+ pthread_t id;
+ pthread_create(&id, NULL, t_fun, NULL); // just go multithreaded
+
+ __goblint_check(g == 1); // UNKNOWN before, unknown after
+ __goblint_check(g == 2); // UNKNOWN before, fail after
+ __goblint_check(g == 0); // FAIL before, unknown after
+ return 0;
+}
\ No newline at end of file
diff --git a/tests/incremental/11-restart/03-global-change.json b/tests/incremental/11-restart/03-global-change.json
new file mode 100644
index 0000000000..fde70c9018
--- /dev/null
+++ b/tests/incremental/11-restart/03-global-change.json
@@ -0,0 +1,14 @@
+{
+ "ana": {
+ "int": {
+ "interval": true
+ }
+ },
+ "incremental": {
+ "restart": {
+ "sided": {
+ "enabled": true
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/incremental/11-restart/03-global-change.patch b/tests/incremental/11-restart/03-global-change.patch
new file mode 100644
index 0000000000..60d990004e
--- /dev/null
+++ b/tests/incremental/11-restart/03-global-change.patch
@@ -0,0 +1,24 @@
+--- tests/incremental/11-restart/03-global-change.c
++++ tests/incremental/11-restart/03-global-change.c
+@@ -4,7 +4,7 @@
+ int g = 1;
+
+ void* t_fun(void *arg) {
+- g = 2;
++ g = 0;
+ return NULL;
+ }
+
+@@ -12,8 +12,8 @@ int main() {
+ pthread_t id;
+ pthread_create(&id, NULL, t_fun, NULL); // just go multithreaded
+
+- __goblint_check(g == 1); // UNKNOWN before, unknown after
+- __goblint_check(g == 2); // UNKNOWN before, fail after
+- __goblint_check(g == 0); // FAIL before, unknown after
++ __goblint_check(g == 1); // unknown before, UNKNOWN after
++ __goblint_check(g == 2); // unknown before, FAIL after
++ __goblint_check(g == 0); // fail before, UNKNOWN after
+ return 0;
+ }
+\ No newline at end of file
diff --git a/tests/incremental/11-restart/04-global-add.c b/tests/incremental/11-restart/04-global-add.c
new file mode 100644
index 0000000000..29371035ba
--- /dev/null
+++ b/tests/incremental/11-restart/04-global-add.c
@@ -0,0 +1,17 @@
+#include
+#include
+
+int g = 1;
+
+void* t_fun(void *arg) {
+
+ return NULL;
+}
+
+int main() {
+ pthread_t id;
+ pthread_create(&id, NULL, t_fun, NULL); // just go multithreaded
+
+ __goblint_check(g == 1); // SUCCESS before, unknown after
+ return 0;
+}
\ No newline at end of file
diff --git a/tests/incremental/11-restart/04-global-add.json b/tests/incremental/11-restart/04-global-add.json
new file mode 100644
index 0000000000..0e0dcd235c
--- /dev/null
+++ b/tests/incremental/11-restart/04-global-add.json
@@ -0,0 +1,3 @@
+{
+
+}
\ No newline at end of file
diff --git a/tests/incremental/11-restart/04-global-add.patch b/tests/incremental/11-restart/04-global-add.patch
new file mode 100644
index 0000000000..d0ad0ac4eb
--- /dev/null
+++ b/tests/incremental/11-restart/04-global-add.patch
@@ -0,0 +1,20 @@
+--- tests/incremental/11-restart/04-global-add.c
++++ tests/incremental/11-restart/04-global-add.c
+@@ -4,7 +4,7 @@
+ int g = 1;
+
+ void* t_fun(void *arg) {
+-
++ g = 2;
+ return NULL;
+ }
+
+@@ -12,6 +12,6 @@ int main() {
+ pthread_t id;
+ pthread_create(&id, NULL, t_fun, NULL); // just go multithreaded
+
+- __goblint_check(g == 1); // SUCCESS before, unknown after
++ __goblint_check(g == 1); // success before, UNKNOWN after
+ return 0;
+ }
+\ No newline at end of file
diff --git a/tests/incremental/11-restart/05-local-wpoint.c b/tests/incremental/11-restart/05-local-wpoint.c
new file mode 100644
index 0000000000..0a05ac684c
--- /dev/null
+++ b/tests/incremental/11-restart/05-local-wpoint.c
@@ -0,0 +1,23 @@
+#include
+#include
+
+int g = 1;
+
+void* t_fun(void *arg) {
+ g = 0;
+ return NULL;
+}
+
+int main() {
+ pthread_t id;
+ pthread_create(&id, NULL, t_fun, NULL); // just go multithreaded
+
+ int x;
+ x = g;
+ while (1) {
+ __goblint_check(x == 1); // UNKNOWN before, success after
+ x = g;
+ }
+
+ return 0;
+}
\ No newline at end of file
diff --git a/tests/incremental/11-restart/05-local-wpoint.json b/tests/incremental/11-restart/05-local-wpoint.json
new file mode 100644
index 0000000000..1ba4f95d61
--- /dev/null
+++ b/tests/incremental/11-restart/05-local-wpoint.json
@@ -0,0 +1,24 @@
+{
+ "ana": {
+ "int": {
+ "interval": true
+ }
+ },
+ "incremental": {
+ "wpoint": false,
+ "restart": {
+ "sided": {
+ "enabled": true
+ }
+ }
+ },
+ "solvers": {
+ "td3": {
+ "restart": {
+ "wpoint": {
+ "enabled": true
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/incremental/11-restart/05-local-wpoint.patch b/tests/incremental/11-restart/05-local-wpoint.patch
new file mode 100644
index 0000000000..e1ff915450
--- /dev/null
+++ b/tests/incremental/11-restart/05-local-wpoint.patch
@@ -0,0 +1,19 @@
+--- tests/incremental/11-restart/05-local-wpoint.c
++++ tests/incremental/11-restart/05-local-wpoint.c
+@@ -4,7 +4,7 @@
+ int g = 1;
+
+ void* t_fun(void *arg) {
+- g = 0;
++
+ return NULL;
+ }
+
+@@ -15,7 +15,7 @@ int main() {
+ int x;
+ x = g;
+ while (1) {
+- __goblint_check(x == 1); // UNKNOWN before, success after
++ __goblint_check(x == 1); // unknown before, SUCCESS after
+ x = g;
+ }
diff --git a/tests/incremental/11-restart/06-local-wpoint-read.c b/tests/incremental/11-restart/06-local-wpoint-read.c
new file mode 100644
index 0000000000..df161fda05
--- /dev/null
+++ b/tests/incremental/11-restart/06-local-wpoint-read.c
@@ -0,0 +1,24 @@
+#include
+#include
+
+int g = 1;
+
+void* t_fun(void *arg) {
+ g = 0;
+ return NULL;
+}
+
+int main() {
+ pthread_t id;
+ pthread_create(&id, NULL, t_fun, NULL); // just go multithreaded
+
+ int x;
+ x = g;
+ while (1) {
+ g = x;
+ __goblint_check(x == 1); // UNKNOWN before, success after
+ x = g;
+ }
+
+ return 0;
+}
\ No newline at end of file
diff --git a/tests/incremental/11-restart/06-local-wpoint-read.json b/tests/incremental/11-restart/06-local-wpoint-read.json
new file mode 100644
index 0000000000..9349d0e2d7
--- /dev/null
+++ b/tests/incremental/11-restart/06-local-wpoint-read.json
@@ -0,0 +1,24 @@
+{
+ "ana": {
+ "int": {
+ "interval": true
+ }
+ },
+ "incremental": {
+ "wpoint": false,
+ "restart": {
+ "sided": {
+ "enabled": true
+ }
+ }
+ },
+ "solvers": {
+ "td3": {
+ "restart": {
+ "wpoint": {
+ "enabled": true
+ }
+ }
+ }
+ }
+}
diff --git a/tests/incremental/11-restart/06-local-wpoint-read.patch b/tests/incremental/11-restart/06-local-wpoint-read.patch
new file mode 100644
index 0000000000..474682668a
--- /dev/null
+++ b/tests/incremental/11-restart/06-local-wpoint-read.patch
@@ -0,0 +1,19 @@
+--- tests/incremental/11-restart/06-local-wpoint-read.c
++++ tests/incremental/11-restart/06-local-wpoint-read.c
+@@ -4,7 +4,7 @@
+ int g = 1;
+
+ void* t_fun(void *arg) {
+- g = 0;
++
+ return NULL;
+ }
+
+@@ -16,7 +16,7 @@ int main() {
+ x = g;
+ while (1) {
+ g = x;
+- __goblint_check(x == 1); // UNKNOWN before, success after
++ __goblint_check(x == 1); // unknown before, SUCCESS after
+ x = g;
+ }
diff --git a/tests/incremental/11-restart/07-local-wpoint-nochange.c b/tests/incremental/11-restart/07-local-wpoint-nochange.c
new file mode 100644
index 0000000000..8bd397c1bc
--- /dev/null
+++ b/tests/incremental/11-restart/07-local-wpoint-nochange.c
@@ -0,0 +1,23 @@
+#include
+#include
+
+int g = 1;
+
+void* t_fun(void *arg) {
+ g = 0;
+ return NULL;
+}
+
+int main() {
+ pthread_t id;
+ pthread_create(&id, NULL, t_fun, NULL); // just go multithreaded
+
+ int x;
+ x = g;
+ while (1) {
+ __goblint_check(x == 1); // UNKNOWN before, UNKNOWN after
+ x = g;
+ }
+
+ return 0;
+}
\ No newline at end of file
diff --git a/tests/incremental/11-restart/07-local-wpoint-nochange.json b/tests/incremental/11-restart/07-local-wpoint-nochange.json
new file mode 100644
index 0000000000..ff773f6993
--- /dev/null
+++ b/tests/incremental/11-restart/07-local-wpoint-nochange.json
@@ -0,0 +1,10 @@
+{
+ "ana": {
+ "int": {
+ "interval": true
+ }
+ },
+ "incremental": {
+ "wpoint": false
+ }
+}
\ No newline at end of file
diff --git a/tests/incremental/11-restart/07-local-wpoint-nochange.patch b/tests/incremental/11-restart/07-local-wpoint-nochange.patch
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/incremental/11-restart/08-side-restart.c b/tests/incremental/11-restart/08-side-restart.c
new file mode 100644
index 0000000000..48ee93381d
--- /dev/null
+++ b/tests/incremental/11-restart/08-side-restart.c
@@ -0,0 +1,34 @@
+#include
+#include
+
+int g;
+
+void* t_fun1(void *arg) {
+ int x = g;
+ __goblint_check(x <= 8); // TODO
+ return NULL;
+}
+
+void* t_fun2(void *arg) {
+ g = 0;
+ return NULL;
+}
+
+int main() {
+ pthread_t id1, id2;
+ pthread_create(&id1, NULL, t_fun1, NULL);
+
+
+ int i = 0;
+ int j;
+
+ for (j = 1; j < 10; j++) {
+ for (i = 0; i < j; i++) {
+ g = i;
+ }
+ }
+ __goblint_check(i <= 9);
+
+ pthread_create(&id2, NULL, t_fun2, NULL);
+ return 0;
+}
\ No newline at end of file
diff --git a/tests/incremental/11-restart/08-side-restart.json b/tests/incremental/11-restart/08-side-restart.json
new file mode 100644
index 0000000000..e87f2900a0
--- /dev/null
+++ b/tests/incremental/11-restart/08-side-restart.json
@@ -0,0 +1,21 @@
+{
+ "ana": {
+ "int": {
+ "interval": true
+ }
+ },
+ "incremental": {
+ "reluctant": {
+ "enabled": false
+ }
+ },
+ "solvers": {
+ "td3": {
+ "restart": {
+ "wpoint": {
+ "enabled": false
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/incremental/11-restart/08-side-restart.patch b/tests/incremental/11-restart/08-side-restart.patch
new file mode 100644
index 0000000000..e3b22d3aca
--- /dev/null
+++ b/tests/incremental/11-restart/08-side-restart.patch
@@ -0,0 +1,10 @@
+--- tests/incremental/11-restart/08-side-restart.c
++++ tests/incremental/11-restart/08-side-restart.c
+@@ -10,7 +10,7 @@ void* t_fun1(void *arg) {
+ }
+
+ void* t_fun2(void *arg) {
+- g = 0;
++
+ return NULL;
+ }
diff --git a/tests/incremental/11-restart/09-call-remove.c b/tests/incremental/11-restart/09-call-remove.c
new file mode 100644
index 0000000000..deac2bb6b5
--- /dev/null
+++ b/tests/incremental/11-restart/09-call-remove.c
@@ -0,0 +1,25 @@
+#include
+#include
+
+int g = 1;
+
+void foo() {
+ g = 2;
+}
+
+void* t_fun(void *arg) {
+ foo();
+ return NULL;
+}
+
+void* t_fun2(void *arg) {
+ __goblint_check(g == 1); // UNKNOWN before, success after
+ return NULL;
+}
+
+int main() {
+ pthread_t id, id2;
+ pthread_create(&id2, NULL, t_fun2, NULL);
+ pthread_create(&id, NULL, t_fun, NULL);
+ return 0;
+}
\ No newline at end of file
diff --git a/tests/incremental/11-restart/09-call-remove.json b/tests/incremental/11-restart/09-call-remove.json
new file mode 100644
index 0000000000..d8addf1280
--- /dev/null
+++ b/tests/incremental/11-restart/09-call-remove.json
@@ -0,0 +1,9 @@
+{
+ "incremental": {
+ "restart": {
+ "sided": {
+ "enabled": true
+ }
+ }
+ }
+}
diff --git a/tests/incremental/11-restart/09-call-remove.patch b/tests/incremental/11-restart/09-call-remove.patch
new file mode 100644
index 0000000000..65ea456c35
--- /dev/null
+++ b/tests/incremental/11-restart/09-call-remove.patch
@@ -0,0 +1,19 @@
+--- tests/incremental/11-restart/09-call-remove.c
++++ tests/incremental/11-restart/09-call-remove.c
+@@ -8,7 +8,7 @@
+ }
+
+ void* t_fun(void *arg) {
+- foo();
++ // foo();
+ return NULL;
+ }
+
+@@ -13,7 +13,7 @@ void* t_fun(void *arg) {
+ }
+
+ void* t_fun2(void *arg) {
+- __goblint_check(g == 1); // UNKNOWN before, success after
++ __goblint_check(g == 1); // unknown before, SUCCESS after
+ return NULL;
+ }
\ No newline at end of file
diff --git a/tests/incremental/11-restart/11-paper-example.c b/tests/incremental/11-restart/11-paper-example.c
new file mode 100644
index 0000000000..d2bc4a030f
--- /dev/null
+++ b/tests/incremental/11-restart/11-paper-example.c
@@ -0,0 +1,42 @@
+#include
+#include
+
+pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
+
+int (*fp)() = NULL;
+
+int bad() {
+ return -1;
+}
+
+int good() {
+ return 1;
+}
+
+void* consumer(void *arg) {
+ int res = 0;
+ pthread_mutex_lock(&mutex);
+ if (fp != NULL) {
+ res = fp();
+ }
+ pthread_mutex_unlock(&mutex);
+ __goblint_check(res >= 0); // UNKNOWN before, success after
+ res = 0;
+ // change absorbed
+ return NULL;
+}
+
+void* producer(void *arg) {
+ int res = 0;
+ pthread_mutex_lock(&mutex);
+ fp = bad;
+ pthread_mutex_unlock(&mutex);
+ return NULL;
+}
+
+int main() {
+ pthread_t id1 = NULL, id2 = NULL;
+ pthread_create(&id1, NULL, consumer, NULL);
+ pthread_create(&id2, NULL, producer, NULL);
+ return 0;
+}
\ No newline at end of file
diff --git a/tests/incremental/11-restart/11-paper-example.json b/tests/incremental/11-restart/11-paper-example.json
new file mode 100644
index 0000000000..706a224f30
--- /dev/null
+++ b/tests/incremental/11-restart/11-paper-example.json
@@ -0,0 +1,14 @@
+{
+ "ana": {
+ "int": {
+ "interval": true
+ }
+ },
+ "incremental": {
+ "restart": {
+ "sided": {
+ "enabled": true
+ }
+ }
+ }
+}
diff --git a/tests/incremental/11-restart/11-paper-example.patch b/tests/incremental/11-restart/11-paper-example.patch
new file mode 100644
index 0000000000..1644165b46
--- /dev/null
+++ b/tests/incremental/11-restart/11-paper-example.patch
@@ -0,0 +1,20 @@
+--- tests/incremental/11-restart/11-paper-example.c
++++ tests/incremental/11-restart/11-paper-example.c
+@@ -20,7 +20,7 @@ void* consumer(void *arg) {
+ res = fp();
+ }
+ pthread_mutex_unlock(&mutex);
+- __goblint_check(res >= 0); // UNKNOWN before, success after
++ __goblint_check(res >= 0); // unknown before, SUCCESS after
+ res = 0;
+ // change absorbed
+ return NULL;
+@@ -29,7 +29,7 @@ void* consumer(void *arg) {
+ void* producer(void *arg) {
+ int res = 0;
+ pthread_mutex_lock(&mutex);
+- fp = bad;
++ fp = good;
+ pthread_mutex_unlock(&mutex);
+ return NULL;
+ }
diff --git a/tests/incremental/11-restart/12-mutex-simple-access.c b/tests/incremental/11-restart/12-mutex-simple-access.c
new file mode 100644
index 0000000000..8a1c25768b
--- /dev/null
+++ b/tests/incremental/11-restart/12-mutex-simple-access.c
@@ -0,0 +1,24 @@
+// Same as 13-restart-write/01-mutex-simple
+#include
+#include
+
+int myglobal;
+pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
+pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
+
+void *t_fun(void *arg) {
+ pthread_mutex_lock(&mutex1);
+ myglobal=myglobal+1; // RACE!
+ pthread_mutex_unlock(&mutex1);
+ return NULL;
+}
+
+int main(void) {
+ pthread_t id;
+ pthread_create(&id, NULL, t_fun, NULL);
+ pthread_mutex_lock(&mutex2);
+ myglobal=myglobal+1; // RACE!
+ pthread_mutex_unlock(&mutex2);
+ pthread_join (id, NULL);
+ return 0;
+}
diff --git a/tests/incremental/11-restart/12-mutex-simple-access.json b/tests/incremental/11-restart/12-mutex-simple-access.json
new file mode 100644
index 0000000000..d038608da1
--- /dev/null
+++ b/tests/incremental/11-restart/12-mutex-simple-access.json
@@ -0,0 +1,12 @@
+{
+ "incremental": {
+ "restart": {
+ "sided": {
+ "enabled": true,
+ "vars": "write-only",
+ "fuel": 1
+ },
+ "write-only": false
+ }
+ }
+}
diff --git a/tests/incremental/11-restart/12-mutex-simple-access.patch b/tests/incremental/11-restart/12-mutex-simple-access.patch
new file mode 100644
index 0000000000..655af16f1b
--- /dev/null
+++ b/tests/incremental/11-restart/12-mutex-simple-access.patch
@@ -0,0 +1,24 @@
+--- tests/incremental/11-restart/12-mutex-simple-access.c
++++ tests/incremental/11-restart/12-mutex-simple-access.c
+@@ -8,7 +8,7 @@ pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
+
+ void *t_fun(void *arg) {
+ pthread_mutex_lock(&mutex1);
+- myglobal=myglobal+1; // RACE!
++ myglobal=myglobal+1; // NORACE
+ pthread_mutex_unlock(&mutex1);
+ return NULL;
+ }
+@@ -16,9 +16,9 @@ void *t_fun(void *arg) {
+ int main(void) {
+ pthread_t id;
+ pthread_create(&id, NULL, t_fun, NULL);
+- pthread_mutex_lock(&mutex2);
+- myglobal=myglobal+1; // RACE!
+- pthread_mutex_unlock(&mutex2);
++ pthread_mutex_lock(&mutex1);
++ myglobal=myglobal+1; // NORACE
++ pthread_mutex_unlock(&mutex1);
+ pthread_join (id, NULL);
+ return 0;
+ }
diff --git a/tests/incremental/11-restart/13-changed_start_state2.c b/tests/incremental/11-restart/13-changed_start_state2.c
new file mode 100644
index 0000000000..87c72c5c1f
--- /dev/null
+++ b/tests/incremental/11-restart/13-changed_start_state2.c
@@ -0,0 +1,13 @@
+#include
+
+int g = 1;
+
+int main() {
+ // After the presolve phase, g is in the start state but neither in the context nor in the start variables.
+ // If the change of the start state of main would not be propagated by the call to side on all start variables, the
+ // asserts in the incremental run would wrongly fail. Side however only joins with the previous value instead of
+ // overwriting, therefore the current imprecision.
+ __goblint_check(g == 1);
+ __goblint_check(g != 2);
+ return 0;
+}
diff --git a/tests/incremental/11-restart/13-changed_start_state2.json b/tests/incremental/11-restart/13-changed_start_state2.json
new file mode 100644
index 0000000000..f2c44d4aae
--- /dev/null
+++ b/tests/incremental/11-restart/13-changed_start_state2.json
@@ -0,0 +1,17 @@
+{
+ "ana": {
+ "base": {
+ "context": {
+ "int": false
+ }
+ }
+ },
+ "incremental": {
+ "restart": {
+ "sided": {
+ "enabled": true,
+ "vars": "write-only"
+ }
+ }
+ }
+}
diff --git a/tests/incremental/11-restart/13-changed_start_state2.patch b/tests/incremental/11-restart/13-changed_start_state2.patch
new file mode 100644
index 0000000000..b6cc568f92
--- /dev/null
+++ b/tests/incremental/11-restart/13-changed_start_state2.patch
@@ -0,0 +1,19 @@
+--- tests/incremental/11-restart/13-changed_start_state2.c
++++ tests/incremental/11-restart/13-changed_start_state2.c
+@@ -1,13 +1,13 @@
+ #include
+
+-int g = 1;
++int g = 2;
+
+ int main() {
+ // After the presolve phase, g is in the start state but neither in the context nor in the start variables.
+ // If the change of the start state of main would not be propagated by the call to side on all start variables, the
+ // asserts in the incremental run would wrongly fail. Side however only joins with the previous value instead of
+ // overwriting, therefore the current imprecision.
+- __goblint_check(g == 1);
+- __goblint_check(g != 2);
++ __goblint_check(g != 1); //TODO
++ __goblint_check(g == 2); //TODO
+ return 0;
+ }
diff --git a/tests/incremental/11-restart/14-mutex-simple-wrap2.c b/tests/incremental/11-restart/14-mutex-simple-wrap2.c
new file mode 100644
index 0000000000..53ff298524
--- /dev/null
+++ b/tests/incremental/11-restart/14-mutex-simple-wrap2.c
@@ -0,0 +1,27 @@
+#include
+#include
+
+int myglobal;
+pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
+pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
+
+void *t_fun(void *arg) {
+ pthread_mutex_lock(&mutex1);
+ myglobal=myglobal+1; // RACE!
+ pthread_mutex_unlock(&mutex1);
+ return NULL;
+}
+
+void wrap() {
+ pthread_mutex_lock(&mutex2);
+ myglobal=myglobal+1; // RACE!
+ pthread_mutex_unlock(&mutex2);
+}
+
+int main(void) {
+ pthread_t id;
+ pthread_create(&id, NULL, t_fun, NULL);
+ wrap();
+ pthread_join (id, NULL);
+ return 0;
+}
diff --git a/tests/incremental/11-restart/14-mutex-simple-wrap2.json b/tests/incremental/11-restart/14-mutex-simple-wrap2.json
new file mode 100644
index 0000000000..6caf371761
--- /dev/null
+++ b/tests/incremental/11-restart/14-mutex-simple-wrap2.json
@@ -0,0 +1,11 @@
+{
+ "incremental": {
+ "restart": {
+ "sided": {
+ "enabled": true,
+ "fuel": 2
+ },
+ "write-only": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/incremental/11-restart/14-mutex-simple-wrap2.patch b/tests/incremental/11-restart/14-mutex-simple-wrap2.patch
new file mode 100644
index 0000000000..e11e3e3d3c
--- /dev/null
+++ b/tests/incremental/11-restart/14-mutex-simple-wrap2.patch
@@ -0,0 +1,22 @@
+--- tests/incremental/11-restart/14-mutex-simple-wrap2.c
++++ tests/incremental/11-restart/14-mutex-simple-wrap2.c
+@@ -7,15 +7,15 @@ pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
+
+ void *t_fun(void *arg) {
+ pthread_mutex_lock(&mutex1);
+- myglobal=myglobal+1; // RACE!
++ myglobal=myglobal+1; // NORACE
+ pthread_mutex_unlock(&mutex1);
+ return NULL;
+ }
+
+ void wrap() {
+- pthread_mutex_lock(&mutex2);
+- myglobal=myglobal+1; // RACE!
+- pthread_mutex_unlock(&mutex2);
++ pthread_mutex_lock(&mutex1);
++ myglobal=myglobal+1; // NORACE
++ pthread_mutex_unlock(&mutex1);
+ }
+
+ int main(void) {
diff --git a/tests/incremental/11-restart/15-mutex-simple-wrap1.c b/tests/incremental/11-restart/15-mutex-simple-wrap1.c
new file mode 100644
index 0000000000..53ff298524
--- /dev/null
+++ b/tests/incremental/11-restart/15-mutex-simple-wrap1.c
@@ -0,0 +1,27 @@
+#include
+#include
+
+int myglobal;
+pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
+pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
+
+void *t_fun(void *arg) {
+ pthread_mutex_lock(&mutex1);
+ myglobal=myglobal+1; // RACE!
+ pthread_mutex_unlock(&mutex1);
+ return NULL;
+}
+
+void wrap() {
+ pthread_mutex_lock(&mutex2);
+ myglobal=myglobal+1; // RACE!
+ pthread_mutex_unlock(&mutex2);
+}
+
+int main(void) {
+ pthread_t id;
+ pthread_create(&id, NULL, t_fun, NULL);
+ wrap();
+ pthread_join (id, NULL);
+ return 0;
+}
diff --git a/tests/incremental/11-restart/15-mutex-simple-wrap1.json b/tests/incremental/11-restart/15-mutex-simple-wrap1.json
new file mode 100644
index 0000000000..92c18ab381
--- /dev/null
+++ b/tests/incremental/11-restart/15-mutex-simple-wrap1.json
@@ -0,0 +1,11 @@
+{
+ "incremental": {
+ "restart": {
+ "sided": {
+ "enabled": true,
+ "fuel": 1
+ },
+ "write-only": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/incremental/11-restart/15-mutex-simple-wrap1.patch b/tests/incremental/11-restart/15-mutex-simple-wrap1.patch
new file mode 100644
index 0000000000..b4ff89ef71
--- /dev/null
+++ b/tests/incremental/11-restart/15-mutex-simple-wrap1.patch
@@ -0,0 +1,22 @@
+--- tests/incremental/11-restart/15-mutex-simple-wrap1.c
++++ tests/incremental/11-restart/15-mutex-simple-wrap1.c
+@@ -7,15 +7,15 @@ pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
+
+ void *t_fun(void *arg) {
+ pthread_mutex_lock(&mutex1);
+- myglobal=myglobal+1; // RACE!
++ myglobal=myglobal+1; // RACE (not enough fuel)
+ pthread_mutex_unlock(&mutex1);
+ return NULL;
+ }
+
+ void wrap() {
+- pthread_mutex_lock(&mutex2);
+- myglobal=myglobal+1; // RACE!
+- pthread_mutex_unlock(&mutex2);
++ pthread_mutex_lock(&mutex1);
++ myglobal=myglobal+1; // RACE (not enough fuel)
++ pthread_mutex_unlock(&mutex1);
+ }
+
+ int main(void) {
diff --git a/tests/incremental/11-restart/16-mutex-simple-wrap1-global.c b/tests/incremental/11-restart/16-mutex-simple-wrap1-global.c
new file mode 100644
index 0000000000..53ff298524
--- /dev/null
+++ b/tests/incremental/11-restart/16-mutex-simple-wrap1-global.c
@@ -0,0 +1,27 @@
+#include
+#include
+
+int myglobal;
+pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
+pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
+
+void *t_fun(void *arg) {
+ pthread_mutex_lock(&mutex1);
+ myglobal=myglobal+1; // RACE!
+ pthread_mutex_unlock(&mutex1);
+ return NULL;
+}
+
+void wrap() {
+ pthread_mutex_lock(&mutex2);
+ myglobal=myglobal+1; // RACE!
+ pthread_mutex_unlock(&mutex2);
+}
+
+int main(void) {
+ pthread_t id;
+ pthread_create(&id, NULL, t_fun, NULL);
+ wrap();
+ pthread_join (id, NULL);
+ return 0;
+}
diff --git a/tests/incremental/11-restart/16-mutex-simple-wrap1-global.json b/tests/incremental/11-restart/16-mutex-simple-wrap1-global.json
new file mode 100644
index 0000000000..403fe4615c
--- /dev/null
+++ b/tests/incremental/11-restart/16-mutex-simple-wrap1-global.json
@@ -0,0 +1,12 @@
+{
+ "incremental": {
+ "restart": {
+ "sided": {
+ "enabled": true,
+ "fuel": 1,
+ "fuel-only-global": true
+ },
+ "write-only": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/incremental/11-restart/16-mutex-simple-wrap1-global.patch b/tests/incremental/11-restart/16-mutex-simple-wrap1-global.patch
new file mode 100644
index 0000000000..5fdd33c2bf
--- /dev/null
+++ b/tests/incremental/11-restart/16-mutex-simple-wrap1-global.patch
@@ -0,0 +1,22 @@
+--- tests/incremental/11-restart/16-mutex-simple-wrap1-global.c
++++ tests/incremental/11-restart/16-mutex-simple-wrap1-global.c
+@@ -7,15 +7,15 @@ pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
+
+ void *t_fun(void *arg) {
+ pthread_mutex_lock(&mutex1);
+- myglobal=myglobal+1; // RACE!
++ myglobal=myglobal+1; // NORACE
+ pthread_mutex_unlock(&mutex1);
+ return NULL;
+ }
+
+ void wrap() {
+- pthread_mutex_lock(&mutex2);
+- myglobal=myglobal+1; // RACE!
+- pthread_mutex_unlock(&mutex2);
++ pthread_mutex_lock(&mutex1);
++ myglobal=myglobal+1; // NORACE
++ pthread_mutex_unlock(&mutex1);
+ }
+
+ int main(void) {
diff --git a/tests/incremental/11-restart/17-mutex-simple-fuel.c b/tests/incremental/11-restart/17-mutex-simple-fuel.c
new file mode 100644
index 0000000000..82c1642a93
--- /dev/null
+++ b/tests/incremental/11-restart/17-mutex-simple-fuel.c
@@ -0,0 +1,23 @@
+#include
+#include
+
+int myglobal;
+pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
+pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
+
+void *t_fun(void *arg) {
+ pthread_mutex_lock(&mutex1);
+ myglobal=myglobal+1; // RACE!
+ pthread_mutex_unlock(&mutex1);
+ return NULL;
+}
+
+int main(void) {
+ pthread_t id;
+ pthread_create(&id, NULL, t_fun, NULL);
+ pthread_mutex_lock(&mutex2);
+ myglobal=myglobal+1; // RACE!
+ pthread_mutex_unlock(&mutex2);
+ pthread_join (id, NULL);
+ return 0;
+}
diff --git a/tests/incremental/11-restart/17-mutex-simple-fuel.json b/tests/incremental/11-restart/17-mutex-simple-fuel.json
new file mode 100644
index 0000000000..92c18ab381
--- /dev/null
+++ b/tests/incremental/11-restart/17-mutex-simple-fuel.json
@@ -0,0 +1,11 @@
+{
+ "incremental": {
+ "restart": {
+ "sided": {
+ "enabled": true,
+ "fuel": 1
+ },
+ "write-only": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/incremental/11-restart/17-mutex-simple-fuel.patch b/tests/incremental/11-restart/17-mutex-simple-fuel.patch
new file mode 100644
index 0000000000..cd26774267
--- /dev/null
+++ b/tests/incremental/11-restart/17-mutex-simple-fuel.patch
@@ -0,0 +1,24 @@
+--- tests/incremental/11-restart/17-mutex-simple-fuel.c
++++ tests/incremental/11-restart/17-mutex-simple-fuel.c
+@@ -7,7 +7,7 @@ pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
+
+ void *t_fun(void *arg) {
+ pthread_mutex_lock(&mutex1);
+- myglobal=myglobal+1; // RACE!
++ myglobal=myglobal+1; // NORACE
+ pthread_mutex_unlock(&mutex1);
+ return NULL;
+ }
+@@ -15,9 +15,9 @@ void *t_fun(void *arg) {
+ int main(void) {
+ pthread_t id;
+ pthread_create(&id, NULL, t_fun, NULL);
+- pthread_mutex_lock(&mutex2);
+- myglobal=myglobal+1; // RACE!
+- pthread_mutex_unlock(&mutex2);
++ pthread_mutex_lock(&mutex1);
++ myglobal=myglobal+1; // NORACE
++ pthread_mutex_unlock(&mutex1);
+ pthread_join (id, NULL);
+ return 0;
+ }
diff --git a/tests/incremental/11-restart/48-local-wpoint-funcall.c b/tests/incremental/11-restart/48-local-wpoint-funcall.c
new file mode 100644
index 0000000000..e504f75316
--- /dev/null
+++ b/tests/incremental/11-restart/48-local-wpoint-funcall.c
@@ -0,0 +1,18 @@
+#include
+
+int f(int x) {
+ return 1;
+}
+
+int main() {
+ int x = 0;
+ int y;
+ while (x < 10) {
+ y = f(x);
+ x = x + y;
+ __goblint_check(x == 0); // FAIL before, success after
+ }
+
+ __goblint_check(0); // FAIL before, nowarn after
+ return 0;
+}
\ No newline at end of file
diff --git a/tests/incremental/11-restart/48-local-wpoint-funcall.json b/tests/incremental/11-restart/48-local-wpoint-funcall.json
new file mode 100644
index 0000000000..2f428497b1
--- /dev/null
+++ b/tests/incremental/11-restart/48-local-wpoint-funcall.json
@@ -0,0 +1,17 @@
+{
+ "ana": {
+ "int": {
+ "interval": true
+ }
+ },
+ "solvers": {
+ "td3": {
+ "restart": {
+ "wpoint": {
+ "enabled": true,
+ "once": true
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/incremental/11-restart/48-local-wpoint-funcall.patch b/tests/incremental/11-restart/48-local-wpoint-funcall.patch
new file mode 100644
index 0000000000..859946ebfd
--- /dev/null
+++ b/tests/incremental/11-restart/48-local-wpoint-funcall.patch
@@ -0,0 +1,24 @@
+--- tests/incremental/11-restart/48-local-wpoint-funcall.c
++++ tests/incremental/11-restart/48-local-wpoint-funcall.c
+@@ -1,7 +1,7 @@
+ #include
+
+ int f(int x) {
+- return 1;
++ return 0;
+ }
+
+ int main() {
+@@ -10,9 +10,9 @@ int main() {
+ while (x < 10) {
+ y = f(x);
+ x = x + y;
+- __goblint_check(x == 0); // FAIL before, success after
++ __goblint_check(x == 0); // fail before, SUCCESS after
+ }
+
+- __goblint_check(0); // FAIL before, nowarn after
++ __goblint_check(0); // fail before, NOWARN after
+ return 0;
+ }
+\ No newline at end of file
\ No newline at end of file
diff --git a/tests/incremental/13-restart-write/01-mutex-simple.c b/tests/incremental/13-restart-write/01-mutex-simple.c
new file mode 100644
index 0000000000..82c1642a93
--- /dev/null
+++ b/tests/incremental/13-restart-write/01-mutex-simple.c
@@ -0,0 +1,23 @@
+#include
+#include
+
+int myglobal;
+pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
+pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
+
+void *t_fun(void *arg) {
+ pthread_mutex_lock(&mutex1);
+ myglobal=myglobal+1; // RACE!
+ pthread_mutex_unlock(&mutex1);
+ return NULL;
+}
+
+int main(void) {
+ pthread_t id;
+ pthread_create(&id, NULL, t_fun, NULL);
+ pthread_mutex_lock(&mutex2);
+ myglobal=myglobal+1; // RACE!
+ pthread_mutex_unlock(&mutex2);
+ pthread_join (id, NULL);
+ return 0;
+}
diff --git a/tests/incremental/13-restart-write/01-mutex-simple.json b/tests/incremental/13-restart-write/01-mutex-simple.json
new file mode 100644
index 0000000000..daff0678ce
--- /dev/null
+++ b/tests/incremental/13-restart-write/01-mutex-simple.json
@@ -0,0 +1,9 @@
+{
+ "incremental": {
+ "restart": {
+ "sided": {
+ "enabled": false
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/incremental/13-restart-write/01-mutex-simple.patch b/tests/incremental/13-restart-write/01-mutex-simple.patch
new file mode 100644
index 0000000000..156312b915
--- /dev/null
+++ b/tests/incremental/13-restart-write/01-mutex-simple.patch
@@ -0,0 +1,24 @@
+--- tests/incremental/13-restart-write/01-mutex-simple.c
++++ tests/incremental/13-restart-write/01-mutex-simple.c
+@@ -7,7 +7,7 @@ pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
+
+ void *t_fun(void *arg) {
+ pthread_mutex_lock(&mutex1);
+- myglobal=myglobal+1; // RACE!
++ myglobal=myglobal+1; // NORACE
+ pthread_mutex_unlock(&mutex1);
+ return NULL;
+ }
+@@ -15,9 +15,9 @@ void *t_fun(void *arg) {
+ int main(void) {
+ pthread_t id;
+ pthread_create(&id, NULL, t_fun, NULL);
+- pthread_mutex_lock(&mutex2);
+- myglobal=myglobal+1; // RACE!
+- pthread_mutex_unlock(&mutex2);
++ pthread_mutex_lock(&mutex1);
++ myglobal=myglobal+1; // NORACE
++ pthread_mutex_unlock(&mutex1);
+ pthread_join (id, NULL);
+ return 0;
+ }
diff --git a/tests/incremental/13-restart-write/02-mutex-simple-wrap.c b/tests/incremental/13-restart-write/02-mutex-simple-wrap.c
new file mode 100644
index 0000000000..53ff298524
--- /dev/null
+++ b/tests/incremental/13-restart-write/02-mutex-simple-wrap.c
@@ -0,0 +1,27 @@
+#include
+#include
+
+int myglobal;
+pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
+pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
+
+void *t_fun(void *arg) {
+ pthread_mutex_lock(&mutex1);
+ myglobal=myglobal+1; // RACE!
+ pthread_mutex_unlock(&mutex1);
+ return NULL;
+}
+
+void wrap() {
+ pthread_mutex_lock(&mutex2);
+ myglobal=myglobal+1; // RACE!
+ pthread_mutex_unlock(&mutex2);
+}
+
+int main(void) {
+ pthread_t id;
+ pthread_create(&id, NULL, t_fun, NULL);
+ wrap();
+ pthread_join (id, NULL);
+ return 0;
+}
diff --git a/tests/incremental/13-restart-write/02-mutex-simple-wrap.json b/tests/incremental/13-restart-write/02-mutex-simple-wrap.json
new file mode 100644
index 0000000000..daff0678ce
--- /dev/null
+++ b/tests/incremental/13-restart-write/02-mutex-simple-wrap.json
@@ -0,0 +1,9 @@
+{
+ "incremental": {
+ "restart": {
+ "sided": {
+ "enabled": false
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/incremental/13-restart-write/02-mutex-simple-wrap.patch b/tests/incremental/13-restart-write/02-mutex-simple-wrap.patch
new file mode 100644
index 0000000000..e38d432722
--- /dev/null
+++ b/tests/incremental/13-restart-write/02-mutex-simple-wrap.patch
@@ -0,0 +1,22 @@
+--- tests/incremental/13-restart-write/02-mutex-simple-wrap.c
++++ tests/incremental/13-restart-write/02-mutex-simple-wrap.c
+@@ -7,15 +7,15 @@ pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
+
+ void *t_fun(void *arg) {
+ pthread_mutex_lock(&mutex1);
+- myglobal=myglobal+1; // RACE!
++ myglobal=myglobal+1; // NORACE
+ pthread_mutex_unlock(&mutex1);
+ return NULL;
+ }
+
+ void wrap() {
+- pthread_mutex_lock(&mutex2);
+- myglobal=myglobal+1; // RACE!
+- pthread_mutex_unlock(&mutex2);
++ pthread_mutex_lock(&mutex1);
++ myglobal=myglobal+1; // NORACE
++ pthread_mutex_unlock(&mutex1);
+ }
+
+ int main(void) {
diff --git a/tests/incremental/13-restart-write/03-mutex-simple-nochange.c b/tests/incremental/13-restart-write/03-mutex-simple-nochange.c
new file mode 100644
index 0000000000..82c1642a93
--- /dev/null
+++ b/tests/incremental/13-restart-write/03-mutex-simple-nochange.c
@@ -0,0 +1,23 @@
+#include
+#include
+
+int myglobal;
+pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
+pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
+
+void *t_fun(void *arg) {
+ pthread_mutex_lock(&mutex1);
+ myglobal=myglobal+1; // RACE!
+ pthread_mutex_unlock(&mutex1);
+ return NULL;
+}
+
+int main(void) {
+ pthread_t id;
+ pthread_create(&id, NULL, t_fun, NULL);
+ pthread_mutex_lock(&mutex2);
+ myglobal=myglobal+1; // RACE!
+ pthread_mutex_unlock(&mutex2);
+ pthread_join (id, NULL);
+ return 0;
+}
diff --git a/tests/incremental/13-restart-write/03-mutex-simple-nochange.json b/tests/incremental/13-restart-write/03-mutex-simple-nochange.json
new file mode 100644
index 0000000000..daff0678ce
--- /dev/null
+++ b/tests/incremental/13-restart-write/03-mutex-simple-nochange.json
@@ -0,0 +1,9 @@
+{
+ "incremental": {
+ "restart": {
+ "sided": {
+ "enabled": false
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/incremental/13-restart-write/03-mutex-simple-nochange.patch b/tests/incremental/13-restart-write/03-mutex-simple-nochange.patch
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/incremental/13-restart-write/04-malloc-node.c b/tests/incremental/13-restart-write/04-malloc-node.c
new file mode 100644
index 0000000000..2e61e5429f
--- /dev/null
+++ b/tests/incremental/13-restart-write/04-malloc-node.c
@@ -0,0 +1,21 @@
+#include
+#include
+
+void *t_fun(void *arg) {
+ int *iarg = (int*) arg;
+ *iarg = 1; // RACE (without mallocFresh)
+ return NULL;
+}
+
+int main() {
+ pthread_t id;
+ int *iarg;
+
+ for (int i = 0; i < 10; i++) {
+ iarg = malloc(sizeof(int));
+ *iarg = 0; // RACE (without mallocFresh)
+ pthread_create(&id, NULL, t_fun, (void*) iarg);
+ }
+
+ return 0;
+}
\ No newline at end of file
diff --git a/tests/incremental/13-restart-write/04-malloc-node.json b/tests/incremental/13-restart-write/04-malloc-node.json
new file mode 100644
index 0000000000..b68cac8440
--- /dev/null
+++ b/tests/incremental/13-restart-write/04-malloc-node.json
@@ -0,0 +1,18 @@
+{
+ "incremental": {
+ "restart": {
+ "sided": {
+ "enabled": false
+ }
+ },
+ "postsolver": {
+ "enabled": true,
+ "superstable-reached": false
+ }
+ },
+ "ana": {
+ "thread": {
+ "include-node": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/incremental/13-restart-write/04-malloc-node.patch b/tests/incremental/13-restart-write/04-malloc-node.patch
new file mode 100644
index 0000000000..3a597f3cd0
--- /dev/null
+++ b/tests/incremental/13-restart-write/04-malloc-node.patch
@@ -0,0 +1,19 @@
+--- tests/incremental/13-restart-write/04-malloc-node.c
++++ tests/incremental/13-restart-write/04-malloc-node.c
+@@ -3,6 +3,7 @@
+
+ void *t_fun(void *arg) {
+ int *iarg = (int*) arg;
++ int y = 0; // NORACE (old access)
+ *iarg = 1; // RACE (without mallocFresh)
+ return NULL;
+ }
+@@ -11,6 +12,8 @@ int main() {
+ pthread_t id;
+ int *iarg;
+
++ __goblint_check(1);
++ int x = 0; // NORACE (old access)
+ for (int i = 0; i < 10; i++) {
+ iarg = malloc(sizeof(int));
+ *iarg = 0; // RACE (without mallocFresh)
diff --git a/tests/incremental/13-restart-write/05-race-call-remove.c b/tests/incremental/13-restart-write/05-race-call-remove.c
new file mode 100644
index 0000000000..cf9f5c7133
--- /dev/null
+++ b/tests/incremental/13-restart-write/05-race-call-remove.c
@@ -0,0 +1,20 @@
+#include
+#include
+
+int g;
+
+void *t_fun(void *arg) {
+ g++; // RACE!
+ return NULL;
+}
+
+void foo() {
+ g++; // RACE!
+}
+
+int main() {
+ pthread_t id;
+ pthread_create(&id, NULL, t_fun, NULL);
+ foo();
+ return 0;
+}
\ No newline at end of file
diff --git a/tests/incremental/13-restart-write/05-race-call-remove.json b/tests/incremental/13-restart-write/05-race-call-remove.json
new file mode 100644
index 0000000000..f65fca9568
--- /dev/null
+++ b/tests/incremental/13-restart-write/05-race-call-remove.json
@@ -0,0 +1,14 @@
+{
+ "incremental": {
+ "restart": {
+ "sided": {
+ "enabled": false
+ }
+ }
+ },
+ "ana": {
+ "thread": {
+ "include-node": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/incremental/13-restart-write/05-race-call-remove.patch b/tests/incremental/13-restart-write/05-race-call-remove.patch
new file mode 100644
index 0000000000..212df350cc
--- /dev/null
+++ b/tests/incremental/13-restart-write/05-race-call-remove.patch
@@ -0,0 +1,25 @@
+diff --git tests/incremental/13-restart-write/05-race-call-remove.c tests/incremental/13-restart-write/05-race-call-remove.c
+index cf9f5c713..20e09db1a 100644
+--- tests/incremental/13-restart-write/05-race-call-remove.c
++++ tests/incremental/13-restart-write/05-race-call-remove.c
+@@ -4,17 +4,16 @@
+ int g;
+
+ void *t_fun(void *arg) {
+- g++; // RACE!
++ g++; // NORACE (unique thread)
+ return NULL;
+ }
+
+ void foo() {
+- g++; // RACE!
++ g++; // NORACE
+ }
+
+ int main() {
+ pthread_t id;
+ pthread_create(&id, NULL, t_fun, NULL);
+- foo();
+ return 0;
+ }
+\ No newline at end of file
diff --git a/tests/incremental/13-restart-write/06-mutex-simple-reluctant.c b/tests/incremental/13-restart-write/06-mutex-simple-reluctant.c
new file mode 100644
index 0000000000..82c1642a93
--- /dev/null
+++ b/tests/incremental/13-restart-write/06-mutex-simple-reluctant.c
@@ -0,0 +1,23 @@
+#include
+#include
+
+int myglobal;
+pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
+pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
+
+void *t_fun(void *arg) {
+ pthread_mutex_lock(&mutex1);
+ myglobal=myglobal+1; // RACE!
+ pthread_mutex_unlock(&mutex1);
+ return NULL;
+}
+
+int main(void) {
+ pthread_t id;
+ pthread_create(&id, NULL, t_fun, NULL);
+ pthread_mutex_lock(&mutex2);
+ myglobal=myglobal+1; // RACE!
+ pthread_mutex_unlock(&mutex2);
+ pthread_join (id, NULL);
+ return 0;
+}
diff --git a/tests/incremental/13-restart-write/06-mutex-simple-reluctant.json b/tests/incremental/13-restart-write/06-mutex-simple-reluctant.json
new file mode 100644
index 0000000000..e9d2a403fe
--- /dev/null
+++ b/tests/incremental/13-restart-write/06-mutex-simple-reluctant.json
@@ -0,0 +1,12 @@
+{
+ "incremental": {
+ "restart": {
+ "sided": {
+ "enabled": false
+ }
+ },
+ "reluctant": {
+ "enabled": true
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/incremental/13-restart-write/06-mutex-simple-reluctant.patch b/tests/incremental/13-restart-write/06-mutex-simple-reluctant.patch
new file mode 100644
index 0000000000..3674a31c23
--- /dev/null
+++ b/tests/incremental/13-restart-write/06-mutex-simple-reluctant.patch
@@ -0,0 +1,24 @@
+--- tests/incremental/13-restart-write/06-mutex-simple-reluctant.c
++++ tests/incremental/13-restart-write/06-mutex-simple-reluctant.c
+@@ -7,7 +7,7 @@ pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
+
+ void *t_fun(void *arg) {
+ pthread_mutex_lock(&mutex1);
+- myglobal=myglobal+1; // RACE!
++ myglobal=myglobal+1; // NORACE
+ pthread_mutex_unlock(&mutex1);
+ return NULL;
+ }
+@@ -15,9 +15,9 @@ void *t_fun(void *arg) {
+ int main(void) {
+ pthread_t id;
+ pthread_create(&id, NULL, t_fun, NULL);
+- pthread_mutex_lock(&mutex2);
+- myglobal=myglobal+1; // RACE!
+- pthread_mutex_unlock(&mutex2);
++ pthread_mutex_lock(&mutex1);
++ myglobal=myglobal+1; // NORACE
++ pthread_mutex_unlock(&mutex1);
+ pthread_join (id, NULL);
+ return 0;
+ }
diff --git a/tests/incremental/13-restart-write/07-mutex-simple-reluctant2.c b/tests/incremental/13-restart-write/07-mutex-simple-reluctant2.c
new file mode 100644
index 0000000000..82c1642a93
--- /dev/null
+++ b/tests/incremental/13-restart-write/07-mutex-simple-reluctant2.c
@@ -0,0 +1,23 @@
+#include
+#include
+
+int myglobal;
+pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
+pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
+
+void *t_fun(void *arg) {
+ pthread_mutex_lock(&mutex1);
+ myglobal=myglobal+1; // RACE!
+ pthread_mutex_unlock(&mutex1);
+ return NULL;
+}
+
+int main(void) {
+ pthread_t id;
+ pthread_create(&id, NULL, t_fun, NULL);
+ pthread_mutex_lock(&mutex2);
+ myglobal=myglobal+1; // RACE!
+ pthread_mutex_unlock(&mutex2);
+ pthread_join (id, NULL);
+ return 0;
+}
diff --git a/tests/incremental/13-restart-write/07-mutex-simple-reluctant2.json b/tests/incremental/13-restart-write/07-mutex-simple-reluctant2.json
new file mode 100644
index 0000000000..74bc650477
--- /dev/null
+++ b/tests/incremental/13-restart-write/07-mutex-simple-reluctant2.json
@@ -0,0 +1,12 @@
+{
+ "incremental": {
+ "restart": {
+ "sided": {
+ "enabled": false
+ }
+ },
+ "reluctant": {
+ "enabled": true
+ }
+ }
+}
diff --git a/tests/incremental/13-restart-write/07-mutex-simple-reluctant2.patch b/tests/incremental/13-restart-write/07-mutex-simple-reluctant2.patch
new file mode 100644
index 0000000000..3d2a480c67
--- /dev/null
+++ b/tests/incremental/13-restart-write/07-mutex-simple-reluctant2.patch
@@ -0,0 +1,11 @@
+--- tests/incremental/13-restart-write/07-mutex-simple-reluctant2.c
++++ tests/incremental/13-restart-write/07-mutex-simple-reluctant2.c
+@@ -7,7 +7,7 @@ pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
+
+ void *t_fun(void *arg) {
+ pthread_mutex_lock(&mutex1);
+- myglobal=myglobal+1; // RACE!
++ myglobal=myglobal+2; // RACE!
+ pthread_mutex_unlock(&mutex1);
+ return NULL;
+ }
diff --git a/tests/incremental/13-restart-write/08-mutex-simple-reluctant3.c b/tests/incremental/13-restart-write/08-mutex-simple-reluctant3.c
new file mode 100644
index 0000000000..b817928884
--- /dev/null
+++ b/tests/incremental/13-restart-write/08-mutex-simple-reluctant3.c
@@ -0,0 +1,22 @@
+#include
+#include
+
+int myglobal;
+pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
+pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
+
+void *t_fun(void *arg) {
+ pthread_mutex_lock(&mutex1);
+ pthread_mutex_unlock(&mutex1);
+ return NULL;
+}
+
+int main(void) {
+ pthread_t id;
+ pthread_create(&id, NULL, t_fun, NULL);
+ pthread_mutex_lock(&mutex2);
+ myglobal=myglobal+1; // NORACE
+ pthread_mutex_unlock(&mutex2);
+ pthread_join (id, NULL);
+ return 0;
+}
diff --git a/tests/incremental/13-restart-write/08-mutex-simple-reluctant3.json b/tests/incremental/13-restart-write/08-mutex-simple-reluctant3.json
new file mode 100644
index 0000000000..e9d2a403fe
--- /dev/null
+++ b/tests/incremental/13-restart-write/08-mutex-simple-reluctant3.json
@@ -0,0 +1,12 @@
+{
+ "incremental": {
+ "restart": {
+ "sided": {
+ "enabled": false
+ }
+ },
+ "reluctant": {
+ "enabled": true
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/incremental/13-restart-write/08-mutex-simple-reluctant3.patch b/tests/incremental/13-restart-write/08-mutex-simple-reluctant3.patch
new file mode 100644
index 0000000000..e85790ca7c
--- /dev/null
+++ b/tests/incremental/13-restart-write/08-mutex-simple-reluctant3.patch
@@ -0,0 +1,19 @@
+--- tests/incremental/13-restart-write/08-mutex-simple-reluctant3.c
++++ tests/incremental/13-restart-write/08-mutex-simple-reluctant3.c
+@@ -7,6 +7,7 @@ pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
+
+ void *t_fun(void *arg) {
+ pthread_mutex_lock(&mutex1);
++ myglobal=myglobal+1; // RACE!
+ pthread_mutex_unlock(&mutex1);
+ return NULL;
+ }
+@@ -15,7 +16,7 @@ int main(void) {
+ pthread_t id;
+ pthread_create(&id, NULL, t_fun, NULL);
+ pthread_mutex_lock(&mutex2);
+- myglobal=myglobal+1; // NORACE
++ myglobal=myglobal+1; // RACE!
+ pthread_mutex_unlock(&mutex2);
+ pthread_join (id, NULL);
+ return 0;
diff --git a/tests/incremental/14-manual-restart/01-restart_manual_simple.c b/tests/incremental/14-manual-restart/01-restart_manual_simple.c
new file mode 100644
index 0000000000..964054f88f
--- /dev/null
+++ b/tests/incremental/14-manual-restart/01-restart_manual_simple.c
@@ -0,0 +1,9 @@
+#include
+
+int g = 4;
+
+int main() {
+ int x = g;
+ __goblint_check(x == 4);
+ return 0;
+}
diff --git a/tests/incremental/14-manual-restart/01-restart_manual_simple.json b/tests/incremental/14-manual-restart/01-restart_manual_simple.json
new file mode 100644
index 0000000000..dbdb1d651e
--- /dev/null
+++ b/tests/incremental/14-manual-restart/01-restart_manual_simple.json
@@ -0,0 +1,13 @@
+{
+ "exp": {
+ "earlyglobs": true
+ },
+ "incremental": {
+ "restart": {
+ "sided": {
+ "enabled": false
+ },
+ "list": []
+ }
+ }
+}
diff --git a/tests/incremental/14-manual-restart/01-restart_manual_simple.patch b/tests/incremental/14-manual-restart/01-restart_manual_simple.patch
new file mode 100644
index 0000000000..e5c345183f
--- /dev/null
+++ b/tests/incremental/14-manual-restart/01-restart_manual_simple.patch
@@ -0,0 +1,29 @@
+diff --git tests/incremental/14-manual-restart/01-restart_manual_simple.c tests/incremental/14-manual-restart/01-restart_manual_simple.c
+index cbfb0ba70..aa83393ac 100644
+--- tests/incremental/14-manual-restart/01-restart_manual_simple.c
++++ tests/incremental/14-manual-restart/01-restart_manual_simple.c
+@@ -1,9 +1,9 @@
+ #include
+
+-int g = 4;
++int g = 5;
+
+ int main() {
+ int x = g;
+- __goblint_check(x == 4);
++ __goblint_check(x == 4); //FAIL
+ return 0;
+ }
+diff --git tests/incremental/14-manual-restart/01-restart_manual_simple.json tests/incremental/14-manual-restart/01-restart_manual_simple.json
+index dbdb1d651..d66a6cf36 100644
+--- tests/incremental/14-manual-restart/01-restart_manual_simple.json
++++ tests/incremental/14-manual-restart/01-restart_manual_simple.json
+@@ -7,7 +7,7 @@
+ "sided": {
+ "enabled": false
+ },
+- "list": []
++ "list": ["g"]
+ }
+ }
+ }
diff --git a/tests/incremental/14-manual-restart/02-read_write_global.c b/tests/incremental/14-manual-restart/02-read_write_global.c
new file mode 100644
index 0000000000..21087e88c2
--- /dev/null
+++ b/tests/incremental/14-manual-restart/02-read_write_global.c
@@ -0,0 +1,16 @@
+int g = 0;
+
+void foo(){
+ g = 1;
+}
+
+void bar(){
+ int x = g;
+ __goblint_check(x % 2 == 0); // UNKNOWN (imprecision caused by earlyglobs)
+}
+
+int main(){
+ foo();
+ bar();
+ return 0;
+}
diff --git a/tests/incremental/14-manual-restart/02-read_write_global.json b/tests/incremental/14-manual-restart/02-read_write_global.json
new file mode 100644
index 0000000000..33dd19da40
--- /dev/null
+++ b/tests/incremental/14-manual-restart/02-read_write_global.json
@@ -0,0 +1,19 @@
+{
+ "exp": {
+ "earlyglobs": true
+ },
+ "ana": {
+ "int": {
+ "interval": true,
+ "congruence": true
+ }
+ },
+ "incremental": {
+ "restart": {
+ "sided": {
+ "enabled": false
+ },
+ "list": []
+ }
+ }
+}
diff --git a/tests/incremental/14-manual-restart/02-read_write_global.patch b/tests/incremental/14-manual-restart/02-read_write_global.patch
new file mode 100644
index 0000000000..e6c81c87e7
--- /dev/null
+++ b/tests/incremental/14-manual-restart/02-read_write_global.patch
@@ -0,0 +1,34 @@
+diff --git tests/incremental/14-manual-restart/02-read_write_global.c tests/incremental/14-manual-restart/02-read_write_global.c
+index 8a93caabe..521322dd0 100644
+--- tests/incremental/14-manual-restart/02-read_write_global.c
++++ tests/incremental/14-manual-restart/02-read_write_global.c
+@@ -1,12 +1,12 @@
+ int g = 0;
+
+ void foo(){
+- g = 1;
++ g = 2;
+ }
+
+ void bar(){
+ int x = g;
+- __goblint_check(x % 2 == 0); // UNKNOWN (imprecision caused by earlyglobs)
++ __goblint_check(x % 2 == 0);
+ }
+
+ int main(){
+diff --git tests/incremental/14-manual-restart/02-read_write_global.json tests/incremental/14-manual-restart/02-read_write_global.json
+index 33dd19da4..0820029df 100644
+--- tests/incremental/14-manual-restart/02-read_write_global.json
++++ tests/incremental/14-manual-restart/02-read_write_global.json
+@@ -13,7 +13,9 @@
+ "sided": {
+ "enabled": false
+ },
+- "list": []
++ "list": [
++ "g"
++ ]
+ }
+ }
+ }
diff --git a/tests/incremental/14-manual-restart/03-partial_contexts.c b/tests/incremental/14-manual-restart/03-partial_contexts.c
new file mode 100644
index 0000000000..40c5f0e7ac
--- /dev/null
+++ b/tests/incremental/14-manual-restart/03-partial_contexts.c
@@ -0,0 +1,11 @@
+#include
+int foo(int x){
+ return x;
+}
+
+int main(){
+ int x = 12;
+ int y = foo(x);
+ __goblint_check(x == y);
+ return 0;
+}
diff --git a/tests/incremental/14-manual-restart/03-partial_contexts.json b/tests/incremental/14-manual-restart/03-partial_contexts.json
new file mode 100644
index 0000000000..96011c8711
--- /dev/null
+++ b/tests/incremental/14-manual-restart/03-partial_contexts.json
@@ -0,0 +1,17 @@
+{
+ "ana": {
+ "base": {
+ "context": {
+ "int": false
+ }
+ }
+ },
+ "incremental": {
+ "restart": {
+ "sided": {
+ "enabled": false
+ },
+ "list": []
+ }
+ }
+}
diff --git a/tests/incremental/14-manual-restart/03-partial_contexts.patch b/tests/incremental/14-manual-restart/03-partial_contexts.patch
new file mode 100644
index 0000000000..7f3146e895
--- /dev/null
+++ b/tests/incremental/14-manual-restart/03-partial_contexts.patch
@@ -0,0 +1,28 @@
+diff --git tests/incremental/14-manual-restart/03-partial_contexts.c tests/incremental/14-manual-restart/03-partial_contexts.c
+index a2a701673..d0b4d3efc 100644
+--- tests/incremental/14-manual-restart/03-partial_contexts.c
++++ tests/incremental/14-manual-restart/03-partial_contexts.c
+@@ -4,7 +4,7 @@ int foo(int x){
+ }
+
+ int main(){
+- int x = 12;
++ int x = 13;
+ int y = foo(x);
+ __goblint_check(x == y);
+ return 0;
+diff --git tests/incremental/14-manual-restart/03-partial_contexts.json tests/incremental/14-manual-restart/03-partial_contexts.json
+index 96011c871..0a42408a0 100644
+--- tests/incremental/14-manual-restart/03-partial_contexts.json
++++ tests/incremental/14-manual-restart/03-partial_contexts.json
+@@ -11,7 +11,9 @@
+ "sided": {
+ "enabled": false
+ },
+- "list": []
++ "list": [
++ "foo"
++ ]
+ }
+ }
+ }
diff --git a/tests/regression/00-sanity/40-wpoint-restart-sound.c b/tests/regression/00-sanity/40-wpoint-restart-sound.c
new file mode 100644
index 0000000000..97aefd0f88
--- /dev/null
+++ b/tests/regression/00-sanity/40-wpoint-restart-sound.c
@@ -0,0 +1,24 @@
+// PARAM: --enable ana.int.interval
+#include
+#include
+
+int g = 0;
+
+void *worker(void *arg )
+{
+ return NULL;
+}
+
+int main(int argc , char **argv )
+{
+ pthread_t tid;
+ pthread_create(& tid, NULL, & worker, NULL);
+
+ while (g >= 10) {
+
+ }
+ __goblint_check(1); // reachable
+ g++;
+ __goblint_check(1); // reachable
+ return 0;
+}
diff --git a/tests/regression/01-cpa/70-dead-branch-multiple.c b/tests/regression/01-cpa/70-dead-branch-multiple.c
new file mode 100644
index 0000000000..67b5d3d826
--- /dev/null
+++ b/tests/regression/01-cpa/70-dead-branch-multiple.c
@@ -0,0 +1,12 @@
+#include
+
+int main() {
+ int a = 1;
+ int b = 0;
+
+ if (a && b) { // WARN
+ __goblint_check(0); // NOWARN (unreachable)
+ }
+
+ return 0;
+}
diff --git a/tests/regression/03-practical/27-charptr_null.c b/tests/regression/03-practical/27-charptr_null.c
new file mode 100644
index 0000000000..08317fb791
--- /dev/null
+++ b/tests/regression/03-practical/27-charptr_null.c
@@ -0,0 +1,33 @@
+#include
+#include
+#include
+#include
+
+struct options
+{
+ char *ip_range;
+};
+
+struct options o;
+
+int get_ip_range(int *iprange)
+{
+ char *r = iprange;
+
+ while (*r++)
+ {
+ *r = '\0';
+ __goblint_check(1);
+ }
+
+ return (0);
+}
+
+int main()
+{
+ char *optarg = "was from unistd.h";
+ o.ip_range = malloc((strlen(optarg) + 1) * sizeof(char));
+ strncpy(o.ip_range, optarg, strlen(optarg));
+ o.ip_range[strlen(optarg)] = '\0';
+ get_ip_range(o.ip_range);
+}
\ No newline at end of file
diff --git a/tests/regression/04-mutex/81-if-cond-race-loc.c b/tests/regression/04-mutex/81-if-cond-race-loc.c
new file mode 100644
index 0000000000..b5228f6e70
--- /dev/null
+++ b/tests/regression/04-mutex/81-if-cond-race-loc.c
@@ -0,0 +1,26 @@
+#include
+#include
+
+int myglobal;
+
+void *t_fun(void *arg) {
+ // awkward formatting to check that warning is just on condition expression, not entire if
+ if // NORACE
+ (myglobal > 0) { // RACE!
+ printf("Stupid!");
+ printf("Stupid!");
+ printf("Stupid!");
+ printf("Stupid!");
+ printf("Stupid!");
+ printf("Stupid!");
+ }
+ return NULL;
+}
+
+int main(void) {
+ pthread_t id;
+ pthread_create(&id, NULL, t_fun, NULL);
+ myglobal=myglobal+1; // RACE!
+ pthread_join (id, NULL);
+ return 0;
+}
diff --git a/tests/regression/06-symbeq/37-funloop_index.c b/tests/regression/06-symbeq/37-funloop_index.c
new file mode 100644
index 0000000000..d4c269cc05
--- /dev/null
+++ b/tests/regression/06-symbeq/37-funloop_index.c
@@ -0,0 +1,36 @@
+// PARAM: --disable ana.mutex.disjoint_types --set ana.activated[+] "'var_eq'" --set ana.activated[+] "'symb_locks'"
+// copy of 06/02 with additional index accesses
+#include
+#include
+
+struct cache_entry {
+ int refs;
+ pthread_mutex_t refs_mutex;
+} cache[10];
+
+void cache_entry_addref(struct cache_entry *entry) {
+ pthread_mutex_lock(&entry->refs_mutex);
+ entry->refs++; // TODO NORACE
+ (*entry).refs++; // TODO NORACE
+ entry[0].refs++; // TODO NORACE
+ pthread_mutex_unlock(&entry->refs_mutex);
+}
+
+void *t_fun(void *arg) {
+ int i;
+ for(i=0; i<10; i++)
+ cache_entry_addref(&cache[i]); // NORACE
+ return NULL;
+}
+
+int main () {
+ for (int i = 0; i < 10; i++)
+ pthread_mutex_init(&cache[i].refs_mutex, NULL);
+
+ int i;
+ pthread_t t1;
+ pthread_create(&t1, NULL, t_fun, NULL);
+ for(i=0; i<10; i++)
+ cache_entry_addref(&cache[i]); // NORACE
+ return 0;
+}
diff --git a/tests/regression/06-symbeq/38-chrony-name2ipaddress.c b/tests/regression/06-symbeq/38-chrony-name2ipaddress.c
new file mode 100644
index 0000000000..3f5edd967a
--- /dev/null
+++ b/tests/regression/06-symbeq/38-chrony-name2ipaddress.c
@@ -0,0 +1,222 @@
+// PARAM: --set ana.activated[+] "'var_eq'" --set ana.activated[+] "'symb_locks'" --set ana.activated[+] "'mallocFresh'" --set ana.malloc.wrappers '["Malloc"]' --disable sem.unknown_function.spawn --disable sem.unknown_function.invalidate.globals --set pre.cppflags[+] -D_FORTIFY_SOURCE=2 --set pre.cppflags[+] -O3
+#include
+#include
+// #include
+// #include
+#include
+#include
+#include
+
+void *
+Malloc(size_t size)
+{
+ void *r;
+
+ r = malloc(size);
+ // if (!r && size)
+ // LOG_FATAL("Could not allocate memory");
+
+ return r;
+}
+
+typedef enum {
+ DNS_Success,
+ DNS_TryAgain,
+ DNS_Failure
+} DNS_Status;
+
+typedef struct {
+ union {
+ uint32_t in4;
+ uint8_t in6[16];
+ uint32_t id;
+ } addr;
+ uint16_t family;
+ uint16_t _pad;
+} IPAddr;
+
+#define DNS_MAX_ADDRESSES 16
+#define IPADDR_UNSPEC 0
+#define IPADDR_INET4 1
+#define IPADDR_INET6 2
+#define IPADDR_ID 3
+
+#define FEAT_IPV6 1
+
+static int address_family = IPADDR_UNSPEC;
+
+int
+UTI_StringToIP(const char *addr, IPAddr *ip)
+{
+ struct in_addr in4;
+#ifdef FEAT_IPV6
+ struct in6_addr in6;
+#endif
+
+ if (inet_pton(AF_INET, addr, &in4) > 0) {
+ ip->family = IPADDR_INET4;
+ ip->_pad = 0;
+ ip->addr.in4 = ntohl(in4.s_addr);
+ return 1;
+ }
+
+#ifdef FEAT_IPV6
+ if (inet_pton(AF_INET6, addr, &in6) > 0) {
+ ip->family = IPADDR_INET6;
+ ip->_pad = 0;
+ memcpy(ip->addr.in6, in6.s6_addr, sizeof (ip->addr.in6));
+ return 1;
+ }
+#endif
+
+ return 0;
+}
+
+
+#define MIN(x, y) ((x) < (y) ? (x) : (y))
+
+DNS_Status
+DNS_Name2IPAddress(const char *name, IPAddr *ip_addrs, int max_addrs)
+{
+ struct addrinfo hints, *res, *ai;
+ int i, result;
+ IPAddr ip;
+
+ max_addrs = MIN(max_addrs, DNS_MAX_ADDRESSES);
+
+ for (i = 0; i < max_addrs; i++)
+ ip_addrs[i].family = IPADDR_UNSPEC; // NORACE
+
+// #if 0
+ /* Avoid calling getaddrinfo() if the name is an IP address */
+ if (UTI_StringToIP(name, &ip)) {
+ if (address_family != IPADDR_UNSPEC && ip.family != address_family)
+ return DNS_Failure;
+ if (max_addrs >= 1)
+ ip_addrs[0] = ip; // NORACE
+ return DNS_Success;
+ }
+
+ memset(&hints, 0, sizeof (hints));
+
+ switch (address_family) {
+ case IPADDR_INET4:
+ hints.ai_family = AF_INET;
+ break;
+#ifdef FEAT_IPV6
+ case IPADDR_INET6:
+ hints.ai_family = AF_INET6;
+ break;
+#endif
+ default:
+ hints.ai_family = AF_UNSPEC;
+ }
+ hints.ai_socktype = SOCK_DGRAM;
+
+ result = getaddrinfo(name, NULL, &hints, &res);
+
+ if (result) {
+#ifdef FORCE_DNSRETRY
+ return DNS_TryAgain;
+#else
+ return result == EAI_AGAIN ? DNS_TryAgain : DNS_Failure;
+#endif
+ }
+
+ for (ai = res, i = 0; i < max_addrs && ai != NULL; ai = ai->ai_next) {
+ switch (ai->ai_family) {
+ case AF_INET:
+ if (address_family != IPADDR_UNSPEC && address_family != IPADDR_INET4)
+ continue;
+ ip_addrs[i].family = IPADDR_INET4; // NORACE
+ ip_addrs[i].addr.in4 = ntohl(((struct sockaddr_in *)ai->ai_addr)->sin_addr.s_addr); // NORACE
+ i++;
+ break;
+#ifdef FEAT_IPV6
+ case AF_INET6:
+ if (address_family != IPADDR_UNSPEC && address_family != IPADDR_INET6)
+ continue;
+ /* Don't return an address that would lose a scope ID */
+ if (((struct sockaddr_in6 *)ai->ai_addr)->sin6_scope_id != 0)
+ continue;
+ ip_addrs[i].family = IPADDR_INET6; // NORACE
+ memcpy(&ip_addrs[i].addr.in6, &((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr.s6_addr, // NORACE
+ sizeof (ip_addrs->addr.in6));
+ i++;
+ break;
+#endif
+ }
+ }
+
+ freeaddrinfo(res);
+// #endif
+
+ return !max_addrs || ip_addrs[0].family != IPADDR_UNSPEC ? DNS_Success : DNS_Failure; // NORACE
+}
+
+struct DNS_Async_Instance {
+ const char *name;
+ DNS_Status status;
+ IPAddr addresses[DNS_MAX_ADDRESSES];
+ // DNS_NameResolveHandler handler;
+ // void *arg;
+ pthread_mutex_t mutex;
+
+ pthread_t thread;
+ // int pipe[2];
+};
+
+static pthread_mutex_t privops_lock = PTHREAD_MUTEX_INITIALIZER;
+
+/* ================================================== */
+
+static void *
+start_resolving(void *anything)
+{
+ struct DNS_Async_Instance *inst = (struct DNS_Async_Instance *)anything;
+
+ pthread_mutex_lock(&inst->mutex);
+ inst->status = DNS_Name2IPAddress(inst->name, inst->addresses, DNS_MAX_ADDRESSES);
+ pthread_mutex_unlock(&inst->mutex);
+
+ /* Notify the main thread that the result is ready */
+ // if (write(inst->pipe[1], "", 1) < 0)
+ // ;
+
+ return NULL;
+}
+
+
+#define MallocNew(T) ((T *) Malloc(sizeof(T)))
+
+void
+DNS_Name2IPAddressAsync(const char *name)
+{
+ struct DNS_Async_Instance *inst;
+
+ inst = MallocNew(struct DNS_Async_Instance);
+ inst->name = name;
+ // inst->handler = handler;
+ // inst->arg = anything;
+ inst->status = DNS_Failure;
+ pthread_mutex_init(&inst->mutex, NULL);
+
+ // if (pipe(inst->pipe)) {
+ // LOG_FATAL("pipe() failed");
+ // }
+
+ // UTI_FdSetCloexec(inst->pipe[0]);
+ // UTI_FdSetCloexec(inst->pipe[1]);
+
+ if (pthread_create(&inst->thread, NULL, start_resolving, inst)) {
+ // LOG_FATAL("pthread_create() failed");
+ }
+
+ // SCH_AddFileHandler(inst->pipe[0], SCH_FILE_INPUT, end_resolving, inst);
+}
+
+int main() {
+ DNS_Name2IPAddressAsync("foo");
+ DNS_Name2IPAddressAsync("bar");
+ return 0;
+}
diff --git a/tests/regression/06-symbeq/39-funloop_index_bad.c b/tests/regression/06-symbeq/39-funloop_index_bad.c
new file mode 100644
index 0000000000..1983887796
--- /dev/null
+++ b/tests/regression/06-symbeq/39-funloop_index_bad.c
@@ -0,0 +1,36 @@
+// PARAM: --disable ana.mutex.disjoint_types --set ana.activated[+] "'var_eq'" --set ana.activated[+] "'symb_locks'"
+// copy of 06/02 with additional index accesses (that are wrong)
+#include
+#include
+
+struct cache_entry {
+ int refs;
+ pthread_mutex_t refs_mutex;
+} cache[10];
+
+void cache_entry_addref(struct cache_entry *entry) {
+ pthread_mutex_lock(&entry->refs_mutex);
+ entry->refs++; // RACE!
+ (*entry).refs++; // RACE!
+ entry[1].refs++; // RACE!
+ pthread_mutex_unlock(&entry->refs_mutex);
+}
+
+void *t_fun(void *arg) {
+ int i;
+ for(i=0; i<9; i++)
+ cache_entry_addref(&cache[i]); // NORACE
+ return NULL;
+}
+
+int main () {
+ for (int i = 0; i < 10; i++)
+ pthread_mutex_init(&cache[i].refs_mutex, NULL);
+
+ int i;
+ pthread_t t1;
+ pthread_create(&t1, NULL, t_fun, NULL);
+ for(i=0; i<9; i++)
+ cache_entry_addref(&cache[i]); // NORACE
+ return 0;
+}
diff --git a/tests/regression/29-svcomp/28-svcomp-marshal.c b/tests/regression/29-svcomp/28-svcomp-marshal.c
new file mode 100644
index 0000000000..b61335f029
--- /dev/null
+++ b/tests/regression/29-svcomp/28-svcomp-marshal.c
@@ -0,0 +1,11 @@
+// SKIP PARAM: --enable ana.sv-comp.enabled --set ana.specification "CHECK( init(main()), LTL(G ! call(reach_error())) )"
+// SV-COMP marshaling doesn't work
+
+void f() {
+
+}
+
+int main() {
+ f();
+ return 0;
+}
diff --git a/tests/regression/34-localization/02-hybrid.c b/tests/regression/34-localization/02-hybrid.c
deleted file mode 100644
index 5a015b1091..0000000000
--- a/tests/regression/34-localization/02-hybrid.c
+++ /dev/null
@@ -1,19 +0,0 @@
-// PARAM: --enable ana.int.interval --set solver slr4
-// Example from Amato-Scozzari, SAS 2013
-// Localized narrowing with restart policy should be able to prove that
-// 0 <= i <= 10 inside the inner loop.
-#include
-
-void main()
-{
- int i = 0;
- while (1) {
- i++;
- for (int j=0; j < 10; j++) {
- __goblint_check(0 <= i); // UNKNOWN
- __goblint_check(i <= 10);
- }
- if (i>9) i=0;
- }
- return;
-}
diff --git a/tests/regression/34-localization/04-hh.c b/tests/regression/34-localization/04-hh.c
deleted file mode 100644
index d20b290bb4..0000000000
--- a/tests/regression/34-localization/04-hh.c
+++ /dev/null
@@ -1,20 +0,0 @@
-// PARAM: --enable ana.int.interval --set solver slr4
-// Example from Amato-Scozzari, SAS 2013
-// Localized widening or restart policy should be able to prove that i <= j+3
-// if the abstract domain is powerful enough.
-#include
-
-void main()
-{
- int i = 0;
- while (i<4) {
- int j=0;
- while (j<4) {
- i=i+1;
- j=j+1;
- }
- i = i-j+1;
- __goblint_check(i <= j+3); // UNKNOWN
- }
- return ;
-}
diff --git a/tests/regression/34-localization/05-nested.w.counter.c b/tests/regression/34-localization/05-nested.w.counter.c
deleted file mode 100644
index 1f2118f072..0000000000
--- a/tests/regression/34-localization/05-nested.w.counter.c
+++ /dev/null
@@ -1,12 +0,0 @@
-// Variant of nested.c with a counter.
-void main()
-{
- int z = 0;
- for (int i=0; i<10 ; i++)
- {
- z = z+1;
- for (int j = 0; j < 10 ; j++) ;
- z = z+1;
- }
- return ;
-}
diff --git a/tests/regression/34-localization/01-nested.c b/tests/regression/34-localwn_restart/01-nested.c
similarity index 61%
rename from tests/regression/34-localization/01-nested.c
rename to tests/regression/34-localwn_restart/01-nested.c
index edf82c1b25..b5d381b8f9 100644
--- a/tests/regression/34-localization/01-nested.c
+++ b/tests/regression/34-localwn_restart/01-nested.c
@@ -1,5 +1,6 @@
-// PARAM: --enable ana.int.interval --set solver slr3
-// Example from Amato-Scozzari, SAS 2013
+// PARAM: --enable ana.int.interval --set solver td3
+// ALSO: --enable ana.int.interval --set solver slr3
+// Example from Halbwachs-Henry, SAS 2012
// Localized widening should be able to prove that i=10 at the end
// of the nested loops.
#include
diff --git a/tests/regression/34-localwn_restart/02-hybrid.c b/tests/regression/34-localwn_restart/02-hybrid.c
new file mode 100644
index 0000000000..baf67e2311
--- /dev/null
+++ b/tests/regression/34-localwn_restart/02-hybrid.c
@@ -0,0 +1,20 @@
+// PARAM: --enable ana.int.interval --set solver td3 --enable solvers.td3.restart.wpoint.enabled --disable solvers.td3.restart.wpoint.once --set sem.int.signed_overflow assume_none
+// ALSO: --enable ana.int.interval --set solver sl4 --set sem.int.signed_overflow assume_none
+// Example from Amato-Scozzari, SAS 2013, based on Halbwachs-Henry, SAS 2012.
+// Localized narrowing with restart policy should be able to prove that
+// 0 <= i <= 10 inside the inner loop.
+#include
+
+void main()
+{
+ int i = 0;
+ while (1) {
+ i++;
+ for (int j=0; j < 10; j++) {
+ __goblint_check(0 <= i);
+ __goblint_check(i <= 10);
+ }
+ if (i>9) i=0;
+ }
+ return;
+}
diff --git a/tests/regression/34-localization/03-nested2.c b/tests/regression/34-localwn_restart/03-nested2.c
similarity index 51%
rename from tests/regression/34-localization/03-nested2.c
rename to tests/regression/34-localwn_restart/03-nested2.c
index cc1d26e550..fb42bde139 100644
--- a/tests/regression/34-localization/03-nested2.c
+++ b/tests/regression/34-localwn_restart/03-nested2.c
@@ -1,7 +1,7 @@
-// PARAM: --enable ana.int.interval --set solver slr4
+// PARAM: --enable ana.int.interval --set solver td3 --set sem.int.signed_overflow assume_none
+// ALSO: --enable ana.int.interval --set solver slr3 --set sem.int.signed_overflow assume_none
// Example from Amato-Scozzari, SAS 2013
-// Localized narrowing should be able to prove that i >= 0 in the outer
-// loop.
+// Localized narrowing should be able to prove that i >= 0 in the outer loop.
#include
void main()
@@ -11,7 +11,7 @@ void main()
int j = 0;
for (; j<10; j++) ;
i=i+11-j;
- __goblint_check(i >= 0); // UNKNOWN
+ __goblint_check(i >= 0);
}
return;
}
diff --git a/tests/regression/34-localwn_restart/04-hh.c b/tests/regression/34-localwn_restart/04-hh.c
new file mode 100644
index 0000000000..2c655fd983
--- /dev/null
+++ b/tests/regression/34-localwn_restart/04-hh.c
@@ -0,0 +1,23 @@
+// SKIP PARAM: --enable ana.int.interval --set solver td3 --set ana.activated[+] apron --set sem.int.signed_overflow assume_none
+// This is part of 34-localization, but also symlinked to 36-apron.
+
+// ALSO: --enable ana.int.interval --set solver slr3 --set ana.activated[+] apron --set sem.int.signed_overflow assume_none
+// Example from Halbwachs-Henry, SAS 2012
+// Localized widening or restart policy should be able to prove that i <= j+3
+// if the abstract domain is powerful enough.
+#include
+
+void main()
+{
+ int i = 0;
+ while (i<4) {
+ int j=0;
+ while (j<4) {
+ i=i+1;
+ j=j+1;
+ }
+ i = i-j+1;
+ __goblint_check(i <= j+3);
+ }
+ return ;
+}
diff --git a/tests/regression/34-localwn_restart/05-nested.w.counter.c b/tests/regression/34-localwn_restart/05-nested.w.counter.c
new file mode 100644
index 0000000000..2d0cca1853
--- /dev/null
+++ b/tests/regression/34-localwn_restart/05-nested.w.counter.c
@@ -0,0 +1,11 @@
+// Variant of nested.c with a counter.
+void main()
+{
+ int z = 0;
+ for (int i=0; i<10 ; i++) {
+ z = z+1;
+ for (int j = 0; j < 10 ; j++) ;
+ z = z+1; // was this intended to be inner loop?
+ }
+ return ;
+}
diff --git a/tests/regression/34-localwn_restart/11-side-restart.c b/tests/regression/34-localwn_restart/11-side-restart.c
new file mode 100644
index 0000000000..3bf93696cf
--- /dev/null
+++ b/tests/regression/34-localwn_restart/11-side-restart.c
@@ -0,0 +1,27 @@
+// SKIP PARAM: --enable ana.int.interval
+// TODO: requires restart of global during/after normal solving
+#include
+#include
+
+int g;
+
+void* t_fun(void *arg) {
+ int x = g;
+ __goblint_check(x <= 8);
+ return NULL;
+}
+
+int main() {
+ pthread_t id;
+ pthread_create(&id, NULL, t_fun, NULL);
+
+ int i = 0;
+ int j;
+
+ for (j = 1; j < 10; j++) {
+ for (i = 0; i < j; i++) {
+ g = i;
+ }
+ }
+ return 0;
+}
\ No newline at end of file
diff --git a/tests/regression/34-localwn_restart/12-side-restart-mutual.c b/tests/regression/34-localwn_restart/12-side-restart-mutual.c
new file mode 100644
index 0000000000..3c0b6cc0b2
--- /dev/null
+++ b/tests/regression/34-localwn_restart/12-side-restart-mutual.c
@@ -0,0 +1,36 @@
+// SKIP PARAM: --enable ana.int.interval
+// requires reuse of final local value for global restart
+#include
+#include
+
+int g, h;
+
+void* t_fun(void *arg) {
+ int x = g;
+ if (x < 10) {
+ x++;
+ h = x;
+ }
+ return NULL;
+}
+
+void* t_fun2(void *arg) {
+ int y = h;
+ if (y < 10) {
+ y++;
+ g = y;
+ }
+ return NULL;
+}
+
+int main() {
+ pthread_t id, id2;
+ pthread_create(&id, NULL, t_fun, NULL);
+ pthread_create(&id2, NULL, t_fun2, NULL);
+
+ int x = g;
+ int y = h;
+ __goblint_check(x <= 10);
+ __goblint_check(y <= 10);
+ return 0;
+}
\ No newline at end of file
diff --git a/tests/regression/34-localwn_restart/13-side-restart-self.c b/tests/regression/34-localwn_restart/13-side-restart-self.c
new file mode 100644
index 0000000000..2f8d2aec8e
--- /dev/null
+++ b/tests/regression/34-localwn_restart/13-side-restart-self.c
@@ -0,0 +1,24 @@
+// SKIP PARAM: --enable ana.int.interval
+// requires reuse of final local value for global restart
+#include
+#include
+
+int g;
+
+void* t_fun(void *arg) {
+ int x = g;
+ if (x < 10) {
+ x++;
+ g = x;
+ }
+ return NULL;
+}
+
+int main() {
+ pthread_t id;
+ pthread_create(&id, NULL, t_fun, NULL);
+
+ int x = g;
+ __goblint_check(x <= 10);
+ return 0;
+}
\ No newline at end of file
diff --git a/tests/regression/42-annotated-precision/08-22_01-simple_array.c b/tests/regression/42-annotated-precision/08-22_01-simple_array.c
index 4cb25c16e2..55da554bd9 100644
--- a/tests/regression/42-annotated-precision/08-22_01-simple_array.c
+++ b/tests/regression/42-annotated-precision/08-22_01-simple_array.c
@@ -1,4 +1,5 @@
-// PARAM: --enable ana.int.interval --set ana.base.arrays.domain partitioned --enable annotation.int.enabled --set ana.int.refinement fixpoint
+// SKIP PARAM: --enable ana.int.interval --set ana.base.arrays.domain partitioned --enable annotation.int.enabled --set ana.int.refinement fixpoint
+// skipped because https://github.com/goblint/analyzer/issues/468
#include
int global;
diff --git a/tests/regression/42-annotated-precision/15-23_01-simple_array.c b/tests/regression/42-annotated-precision/15-23_01-simple_array.c
index af1d4cae4f..672f4fb2a2 100644
--- a/tests/regression/42-annotated-precision/15-23_01-simple_array.c
+++ b/tests/regression/42-annotated-precision/15-23_01-simple_array.c
@@ -1,4 +1,5 @@
-// PARAM: --enable ana.int.interval --set ana.base.arrays.domain partitioned --set ana.base.partition-arrays.keep-expr "last" --enable annotation.int.enabled --set ana.int.refinement fixpoint
+// SKIP PARAM: --enable ana.int.interval --set ana.base.arrays.domain partitioned --set ana.base.partition-arrays.keep-expr "last" --enable annotation.int.enabled --set ana.int.refinement fixpoint
+// skipped because https://github.com/goblint/analyzer/issues/468
#include
int global;
diff --git a/tests/regression/46-apron2/02-localization-hh.c b/tests/regression/46-apron2/02-localization-hh.c
new file mode 120000
index 0000000000..b111e77e97
--- /dev/null
+++ b/tests/regression/46-apron2/02-localization-hh.c
@@ -0,0 +1 @@
+../34-localwn_restart/04-hh.c
\ No newline at end of file
diff --git a/tests/regression/46-apron2/03-other-assume.c b/tests/regression/46-apron2/03-other-assume.c
new file mode 100644
index 0000000000..5bc4405f09
--- /dev/null
+++ b/tests/regression/46-apron2/03-other-assume.c
@@ -0,0 +1,38 @@
+// SKIP PARAM: --set ana.activated[+] apron --set ana.path_sens[+] threadflag --set ana.activated[+] threadJoins --sets ana.apron.privatization mutex-meet-tid
+#include
+#include
+
+int g = 10;
+int h = 10;
+pthread_mutex_t A = PTHREAD_MUTEX_INITIALIZER;
+
+void *t_fun(void *arg) {
+ return NULL;
+}
+
+void *t_benign(void *arg) {
+ pthread_t id2;
+ pthread_create(&id2, NULL, t_fun, NULL);
+ __goblint_assume_join(id2, NULL);
+ // t_fun should be in here
+
+ g = 7;
+
+ return NULL;
+}
+
+int main(void) {
+ int t;
+
+ pthread_t id2[10];
+ for(int i =0; i < 10;i++) {
+ pthread_create(&id2[i], NULL, t_benign, NULL);
+ }
+
+ __goblint_assume_join(id2[2]);
+ // t_benign and t_fun should be in here
+
+ __goblint_check(g==h); //FAIL
+
+ return 0;
+}
diff --git a/tests/regression/46-apron2/04-other-assume-inprec.c b/tests/regression/46-apron2/04-other-assume-inprec.c
new file mode 100644
index 0000000000..e8f9be629f
--- /dev/null
+++ b/tests/regression/46-apron2/04-other-assume-inprec.c
@@ -0,0 +1,30 @@
+// SKIP PARAM: --set ana.activated[+] apron --set ana.path_sens[+] threadflag --set ana.activated[+] threadJoins --sets ana.apron.privatization mutex-meet-tid
+#include
+#include
+
+int g = 10;
+int h = 10;
+pthread_mutex_t A = PTHREAD_MUTEX_INITIALIZER;
+
+void *t_fun(void *arg) {
+ g = 7;
+ return NULL;
+}
+
+int main(void) {
+ int t;
+
+ pthread_t id;
+ pthread_create(&id, NULL, t_fun, NULL);
+ pthread_join(id,NULL);
+
+ g = h;
+ __goblint_check(g == h);
+
+ // __goblint_assume_join for something Goblint knows is joined should not worsen precision
+ __goblint_assume_join(id);
+
+ __goblint_check(g == h);
+
+ return 0;
+}
diff --git a/tests/regression/51-threadjoins/01-trivial.c b/tests/regression/51-threadjoins/01-trivial.c
index 1eba60bf56..6ba9b6f47c 100644
--- a/tests/regression/51-threadjoins/01-trivial.c
+++ b/tests/regression/51-threadjoins/01-trivial.c
@@ -7,10 +7,12 @@ int h = 10;
pthread_mutex_t A = PTHREAD_MUTEX_INITIALIZER;
void *t_fun(void *arg) {
+ g++; // NORACE
return NULL;
}
void *t_benign(void *arg) {
+ h++; // NORACE
pthread_t id2;
pthread_create(&id2, NULL, t_fun, NULL);
pthread_join(id2, NULL);
@@ -25,5 +27,8 @@ int main(void) {
pthread_join(id2, NULL);
// t_benign and t_fun should be in here
+ g++; // NORACE
+ h++; // NORACE
+
return 0;
}
diff --git a/tests/regression/51-threadjoins/02-other.c b/tests/regression/51-threadjoins/02-other.c
index a4d0bb62f6..52200ad9bf 100644
--- a/tests/regression/51-threadjoins/02-other.c
+++ b/tests/regression/51-threadjoins/02-other.c
@@ -7,10 +7,12 @@ int h = 10;
pthread_mutex_t A = PTHREAD_MUTEX_INITIALIZER;
void *t_fun(void *arg) {
+ g++; // RACE!
return NULL;
}
void *t_benign(void *arg) {
+ h++; // RACE!
pthread_t id2;
pthread_create(&id2, NULL, t_fun, NULL);
pthread_join(id2, NULL);
@@ -30,5 +32,8 @@ int main(void) {
// should be empty
+ g++; // RACE!
+ h++; // RACE!
+
return 0;
}
diff --git a/tests/regression/51-threadjoins/03-other-assume.c b/tests/regression/51-threadjoins/03-other-assume.c
new file mode 100644
index 0000000000..7827135e04
--- /dev/null
+++ b/tests/regression/51-threadjoins/03-other-assume.c
@@ -0,0 +1,39 @@
+//PARAM: --set ana.activated[+] threadJoins
+#include
+#include
+
+int g = 10;
+int h = 10;
+pthread_mutex_t A = PTHREAD_MUTEX_INITIALIZER;
+
+void *t_fun(void *arg) {
+ g++; // RACE!
+ return NULL;
+}
+
+void *t_benign(void *arg) {
+ h++; // RACE!
+ pthread_t id2;
+ pthread_create(&id2, NULL, t_fun, NULL);
+ __goblint_assume_join(id2, NULL);
+ // t_fun should be in here
+ return NULL;
+}
+
+int main(void) {
+ int t;
+
+ pthread_t id2[10];
+ for(int i =0; i < 10;i++) {
+ pthread_create(&id2[i], NULL, t_benign, NULL);
+ }
+
+ __goblint_assume_join(id2[2]);
+
+ // t_benign and t_fun should be in here
+
+ g++; // NORACE
+ h++; // NORACE
+
+ return 0;
+}
diff --git a/tests/regression/51-threadjoins/04-assume-recreate.c b/tests/regression/51-threadjoins/04-assume-recreate.c
new file mode 100644
index 0000000000..8424303e97
--- /dev/null
+++ b/tests/regression/51-threadjoins/04-assume-recreate.c
@@ -0,0 +1,23 @@
+//PARAM: --set ana.activated[+] threadJoins --disable ana.thread.include-node --set ana.thread.domain plain
+#include
+#include
+
+int g = 0;
+
+void *t_fun(void *arg) {
+ g++; // RACE!
+ return NULL;
+}
+
+int main() {
+ pthread_t id;
+ pthread_create(&id, NULL, t_fun, NULL);
+
+ __goblint_assume_join(id); // should add to must-joined
+
+ pthread_create(&id, NULL, t_fun, NULL); // should remove from must-joined again
+
+ g++; // RACE!
+
+ return 0;
+}
diff --git a/tests/regression/51-threadjoins/05-assume-unknown.c b/tests/regression/51-threadjoins/05-assume-unknown.c
new file mode 100644
index 0000000000..7c113715cd
--- /dev/null
+++ b/tests/regression/51-threadjoins/05-assume-unknown.c
@@ -0,0 +1,24 @@
+//PARAM: --set ana.activated[+] threadJoins
+#include
+#include
+
+int g = 0;
+
+void *t_fun(void *arg) {
+ g++; // RACE!
+ return NULL;
+}
+
+int main() {
+ pthread_t id, id2;
+ pthread_create(&id, NULL, t_fun, NULL);
+
+ __goblint_assume_join(id2); // WARN joining unknown thread ID, make joined set All threads
+
+ g++; // NORACE
+
+ pthread_create(&id, NULL, t_fun, NULL); // WARN make joined set different from All threads
+ g++; // RACE!
+
+ return 0;
+}
diff --git a/unittest/solver/solverTest.ml b/unittest/solver/solverTest.ml
index 5371561e27..6668aacceb 100644
--- a/unittest/solver/solverTest.ml
+++ b/unittest/solver/solverTest.ml
@@ -15,6 +15,7 @@ struct
let var_id x = x
let node _ = failwith "no node"
let relift x = x
+ let is_write_only _ = false
end
(* domain is (reversed) integers *)
@@ -42,6 +43,8 @@ module ConstrSys = struct
| "z" -> Some (fun loc _ glob gside -> (ignore (loc "y"); loc "y"))
| "w" -> Some (fun loc _ glob gside -> (gside "g" (Int.of_int (Z.of_int64 42L)); ignore (loc "z"); try Int.add (loc "w") (Int.of_int (Z.of_int64 1L)) with IntDomain.ArithmeticOnIntegerBot _ -> Int.top ()))
| _ -> None
+
+ let iter_vars _ _ _ _ _ = ()
end
module LH = BatHashtbl.Make (ConstrSys.LVar)