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)