diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index d4fc872620..fd2c55b84e 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -16,9 +16,9 @@ jobs: fail-fast: false matrix: os: - - ubuntu-latest + - ubuntu-22.04 # https://github.com/ocaml/setup-ocaml/issues/872 ocaml-compiler: - - ocaml-variants.4.14.0+options,ocaml-option-flambda # matches opam lock file + - ocaml-variants.4.14.2+options,ocaml-option-flambda # matches opam lock file # don't add any other because they won't be used runs-on: ${{ matrix.os }} @@ -35,13 +35,12 @@ jobs: # otherwise setup-ocaml pins non-locked dependencies # https://github.com/ocaml/setup-ocaml/issues/166 OPAMLOCKED: locked - uses: ocaml/setup-ocaml@v2 + uses: ocaml/setup-ocaml@v3 with: ocaml-compiler: ${{ matrix.ocaml-compiler }} - opam-depext-flags: --with-test # doesn't work (https://github.com/ocaml/opam/issues/5836) - name: Install graph-easy # TODO: remove if depext --with-test works - if: ${{ matrix.os == 'ubuntu-latest' }} + if: ${{ matrix.os == 'ubuntu-22.04' }} run: sudo apt install -y libgraph-easy-perl - name: Install dependencies diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 36568e6cb2..daa7f224b8 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -59,7 +59,7 @@ jobs: - name: Build Docker image id: build - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 with: context: . load: true # load into docker instead of immediately pushing @@ -72,7 +72,7 @@ jobs: run: docker run --rm -v $(pwd):/data ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }} /data/tests/regression/04-mutex/01-simple_rc.c # run image by version in case multiple tags - name: Push Docker image - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 with: context: . push: true diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 242acef3de..0ada04e369 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -16,9 +16,9 @@ jobs: strategy: matrix: os: - - ubuntu-latest + - ubuntu-22.04 # https://github.com/ocaml/setup-ocaml/issues/872 ocaml-compiler: - - ocaml-variants.4.14.0+options,ocaml-option-flambda # matches opam lock file + - ocaml-variants.4.14.2+options,ocaml-option-flambda # matches opam lock file # don't add any other because they won't be used runs-on: ${{ matrix.os }} @@ -35,7 +35,7 @@ jobs: # otherwise setup-ocaml pins non-locked dependencies # https://github.com/ocaml/setup-ocaml/issues/166 OPAMLOCKED: locked - uses: ocaml/setup-ocaml@v2 + uses: ocaml/setup-ocaml@v3 with: ocaml-compiler: ${{ matrix.ocaml-compiler }} diff --git a/.github/workflows/indentation.yml b/.github/workflows/indentation.yml index e22e674301..1c788e7554 100644 --- a/.github/workflows/indentation.yml +++ b/.github/workflows/indentation.yml @@ -10,7 +10,7 @@ jobs: strategy: matrix: os: - - ubuntu-latest + - ubuntu-22.04 # https://github.com/ocaml/setup-ocaml/issues/872 ocaml-compiler: - 4.14.x @@ -25,7 +25,7 @@ jobs: fetch-depth: 0 - name: Set up OCaml ${{ matrix.ocaml-compiler }} - uses: ocaml/setup-ocaml@v2 + uses: ocaml/setup-ocaml@v3 with: ocaml-compiler: ${{ matrix.ocaml-compiler }} diff --git a/.github/workflows/locked.yml b/.github/workflows/locked.yml index 18935725ca..16655bfdc7 100644 --- a/.github/workflows/locked.yml +++ b/.github/workflows/locked.yml @@ -17,10 +17,10 @@ jobs: fail-fast: false matrix: os: - - ubuntu-latest + - ubuntu-22.04 # https://github.com/ocaml/setup-ocaml/issues/872 - macos-13 ocaml-compiler: - - ocaml-variants.4.14.0+options,ocaml-option-flambda # matches opam lock file + - ocaml-variants.4.14.2+options,ocaml-option-flambda # matches opam lock file # don't add any other because they won't be used runs-on: ${{ matrix.os }} @@ -37,13 +37,12 @@ jobs: # otherwise setup-ocaml pins non-locked dependencies # https://github.com/ocaml/setup-ocaml/issues/166 OPAMLOCKED: locked - uses: ocaml/setup-ocaml@v2 + uses: ocaml/setup-ocaml@v3 with: ocaml-compiler: ${{ matrix.ocaml-compiler }} - opam-depext-flags: --with-test # doesn't work (https://github.com/ocaml/opam/issues/5836) - name: Install graph-easy # TODO: remove if depext --with-test works - if: ${{ matrix.os == 'ubuntu-latest' }} + if: ${{ matrix.os == 'ubuntu-22.04' }} run: sudo apt install -y libgraph-easy-perl - name: Install dependencies @@ -71,9 +70,9 @@ jobs: fail-fast: false matrix: os: - - ubuntu-latest + - ubuntu-22.04 # https://github.com/ocaml/setup-ocaml/issues/872 ocaml-compiler: - - ocaml-variants.4.14.0+options,ocaml-option-flambda # matches opam lock file + - ocaml-variants.4.14.2+options,ocaml-option-flambda # matches opam lock file # don't add any other because they won't be used runs-on: ${{ matrix.os }} @@ -87,13 +86,12 @@ jobs: # otherwise setup-ocaml pins non-locked dependencies # https://github.com/ocaml/setup-ocaml/issues/166 OPAMLOCKED: locked - uses: ocaml/setup-ocaml@v2 + uses: ocaml/setup-ocaml@v3 with: ocaml-compiler: ${{ matrix.ocaml-compiler }} - opam-depext-flags: --with-test # doesn't work (https://github.com/ocaml/opam/issues/5836) - name: Install graph-easy # TODO: remove if depext --with-test works - if: ${{ matrix.os == 'ubuntu-latest' }} + if: ${{ matrix.os == 'ubuntu-22.04' }} run: sudo apt install -y libgraph-easy-perl - name: Install spin @@ -114,9 +112,9 @@ jobs: fail-fast: false matrix: os: - - ubuntu-latest + - ubuntu-22.04 # https://github.com/ocaml/setup-ocaml/issues/872 ocaml-compiler: - - ocaml-variants.4.14.0+options,ocaml-option-flambda # matches opam lock file + - ocaml-variants.4.14.2+options,ocaml-option-flambda # matches opam lock file # don't add any other because they won't be used node-version: - 14 @@ -132,7 +130,7 @@ jobs: # otherwise setup-ocaml pins non-locked dependencies # https://github.com/ocaml/setup-ocaml/issues/166 OPAMLOCKED: locked - uses: ocaml/setup-ocaml@v2 + uses: ocaml/setup-ocaml@v3 with: ocaml-compiler: ${{ matrix.ocaml-compiler }} diff --git a/.github/workflows/unlocked.yml b/.github/workflows/unlocked.yml index db41ad3007..f3fe6cc558 100644 --- a/.github/workflows/unlocked.yml +++ b/.github/workflows/unlocked.yml @@ -15,13 +15,13 @@ jobs: fail-fast: false matrix: os: - - ubuntu-latest + - ubuntu-22.04 # https://github.com/ocaml/setup-ocaml/issues/872 - macos-13 ocaml-compiler: - 5.2.x - 5.1.x - 5.0.x - - ocaml-variants.4.14.0+options,ocaml-option-flambda + - ocaml-variants.4.14.2+options,ocaml-option-flambda - 4.14.x apron: - false @@ -30,9 +30,11 @@ jobs: - false include: - - os: ubuntu-latest + - os: ubuntu-22.04 # https://github.com/ocaml/setup-ocaml/issues/872 ocaml-compiler: 4.14.x z3: true + - os: macos-latest + ocaml-compiler: 4.14.x # customize name to use readable string for apron instead of just a boolean # workaround for missing ternary operator: https://github.com/actions/runner/issues/409 @@ -45,13 +47,12 @@ jobs: uses: actions/checkout@v4 - name: Set up OCaml ${{ matrix.ocaml-compiler }} - uses: ocaml/setup-ocaml@v2 + uses: ocaml/setup-ocaml@v3 with: ocaml-compiler: ${{ matrix.ocaml-compiler }} - opam-depext-flags: --with-test # doesn't work (https://github.com/ocaml/opam/issues/5836) - name: Install graph-easy # TODO: remove if depext --with-test works - if: ${{ matrix.os == 'ubuntu-latest' }} + if: ${{ matrix.os == 'ubuntu-22.04' }} run: sudo apt install -y libgraph-easy-perl - name: Install dependencies @@ -89,10 +90,10 @@ jobs: fail-fast: false matrix: os: - - ubuntu-latest + - ubuntu-22.04 # https://github.com/ocaml/setup-ocaml/issues/872 - macos-13 ocaml-compiler: - - ocaml-variants.4.14.0+options,ocaml-option-flambda # matches opam lock file, downgrade deps step + - ocaml-variants.4.14.2+options,ocaml-option-flambda # matches opam lock file, downgrade deps step name: lower-bounds (${{ matrix.os }}, ${{ matrix.ocaml-compiler }}, downgrade) @@ -109,10 +110,9 @@ jobs: uses: ocaml/setup-ocaml@v2 with: ocaml-compiler: ${{ matrix.ocaml-compiler }} - opam-depext-flags: --with-test # doesn't work (https://github.com/ocaml/opam/issues/5836) - name: Install graph-easy # TODO: remove if depext --with-test works - if: ${{ matrix.os == 'ubuntu-latest' }} + if: ${{ matrix.os == 'ubuntu-22.04' }} run: sudo apt install -y libgraph-easy-perl - name: Install dependencies @@ -133,7 +133,7 @@ jobs: - name: Downgrade dependencies # must specify ocaml-base-compiler again to prevent it from being downgraded # prevent num downgrade to avoid dune/jbuilder error: https://github.com/ocaml/dune/issues/5280 - run: opam install $(opam exec -- opam-0install --prefer-oldest goblint ocaml-variants.4.14.0+options ocaml-option-flambda num.1.5) + run: opam install $(opam exec -- opam-0install --prefer-oldest goblint ocaml-variants.4.14.2+options ocaml-option-flambda num.1.5) - name: Build run: ./make.sh nat @@ -165,7 +165,7 @@ jobs: - name: Build dev Docker image id: build - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 with: context: . target: dev @@ -187,10 +187,10 @@ jobs: fail-fast: false matrix: os: - - ubuntu-latest + - ubuntu-22.04 # https://github.com/ocaml/setup-ocaml/issues/872 - macos-13 ocaml-compiler: - - ocaml-variants.4.14.0+options,ocaml-option-flambda # matches opam lock file + - ocaml-variants.4.14.2+options,ocaml-option-flambda # matches opam lock file runs-on: ${{ matrix.os }} @@ -199,13 +199,12 @@ jobs: uses: actions/checkout@v4 - name: Set up OCaml ${{ matrix.ocaml-compiler }} - uses: ocaml/setup-ocaml@v2 + uses: ocaml/setup-ocaml@v3 with: ocaml-compiler: ${{ matrix.ocaml-compiler }} - opam-depext-flags: --with-test # doesn't work (https://github.com/ocaml/opam/issues/5836) - name: Install graph-easy # TODO: remove if depext --with-test works - if: ${{ matrix.os == 'ubuntu-latest' }} + if: ${{ matrix.os == 'ubuntu-22.04' }} run: sudo apt install -y libgraph-easy-perl - name: Install Goblint with test diff --git a/.semgrep/fold.yml b/.semgrep/fold.yml new file mode 100644 index 0000000000..8e4739791f --- /dev/null +++ b/.semgrep/fold.yml @@ -0,0 +1,26 @@ +rules: + - id: fold-exists + patterns: + - pattern-either: + - pattern: $D.fold ... false + - pattern: $D.fold_left ... false + - pattern: $D.fold_right ... false + - pattern: fold ... false + - pattern: fold_left ... false + - pattern: fold_right ... false + message: consider replacing fold with exists + languages: [ocaml] + severity: WARNING + + - id: fold-for_all + patterns: + - pattern-either: + - pattern: $D.fold ... true + - pattern: $D.fold_left ... true + - pattern: $D.fold_right ... true + - pattern: fold ... true + - pattern: fold_left ... true + - pattern: fold_right ... true + message: consider replacing fold with for_all + languages: [ocaml] + severity: WARNING diff --git a/.semgrep/tracing.yml b/.semgrep/tracing.yml index 061b3efa0d..9c7813a7e8 100644 --- a/.semgrep/tracing.yml +++ b/.semgrep/tracing.yml @@ -8,8 +8,16 @@ rules: - pattern: Messages.tracec - pattern: Messages.traceu - pattern: Messages.traceli + - pattern: M.trace + - pattern: M.tracel + - pattern: M.tracei + - pattern: M.tracec + - pattern: M.traceu + - pattern: M.traceli - pattern-not-inside: if Messages.tracing then ... - pattern-not-inside: if Messages.tracing && ... then ... + - pattern-not-inside: if M.tracing then ... + - pattern-not-inside: if M.tracing && ... then ... message: trace functions should only be called if tracing is enabled at compile time languages: [ocaml] severity: WARNING diff --git a/.zenodo.json b/.zenodo.json index 22705c2d9c..1e71573948 100644 --- a/.zenodo.json +++ b/.zenodo.json @@ -23,6 +23,11 @@ "affiliation": "Technische Universität München", "orcid": "0009-0009-9644-7475" }, + { + "name": "Holter, Karoliine", + "affiliation": "University of Tartu", + "orcid": "0009-0008-3725-4131" + }, { "name": "Vogler, Ralf", "affiliation": "Technische Universität München" diff --git a/CHANGELOG.md b/CHANGELOG.md index d285480259..1fb07a7dc2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,33 @@ +## v2.5.0 +Functionally equivalent to Goblint in SV-COMP 2025. + +* Add 32bit vs 64bit architecture support (#54, #1574). +* Add per-function context gas analysis (#1569, #1570, #1598). +* Adapt automatic static loop unrolling (#1516, #1582, #1583, #1584, #1590, #1595, #1599). +* Adapt automatic configuration tuning (#1450, #1612, #1181, #1604). +* Simplify non-relational integer invariants in witnesses (#1517). +* Fix excessive hash collisions (#1594, #1602). +* Clean up various code (#1095, #1523, #1554, #1575, #1588, #1597, #1614). + +## v2.4.0 +* Remove unmaintained analyses: spec, file (#1281). +* Add linear two-variable equalities analysis (#1297, #1412, #1466). +* Add callstring, loopfree callstring and context gas analyses (#1038, #1340, #1379, #1427, #1439). +* Add non-relational thread-modular value analyses with thread IDs (#1366, #1398, #1399). +* Add NULL byte array domain (#1076). +* Fix spurious overflow warnings from internal evaluations (#1406, #1411, #1511). +* Refactor non-definite mutex handling to fix unsoundness (#1430, #1500, #1503, #1409). +* Fix non-relational thread-modular value analysis unsoundness with ambiguous points-to sets (#1457, #1458). +* Fix mutex type analysis unsoundness and enable it by default (#1414, #1416, #1510). +* Add points-to set refinement on mutex path splitting (#1287, #1343, #1374, #1396, #1407). +* Improve narrowing operators (#1502, #1540, #1543). +* Extract automatic configuration tuning for soundness (#1469). +* Fix many locations in witnesses (#1355, #1372, #1400, #1403). +* Improve output readability (#1294, #1312, #1405, #1497). +* Refactor logging (#1117). +* Modernize all library function specifications (#1029, #688, #1174, #1289, #1447, #1487). +* Remove OCaml 4.10, 4.11, 4.12 and 4.13 support (#1448). + ## v2.3.0 Functionally equivalent to Goblint in SV-COMP 2024. diff --git a/CITATION.cff b/CITATION.cff index 25d46cf762..7a93859c54 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -21,6 +21,10 @@ authors: # same authors as in .zenodo.json and dune-project family-names: Tilscher affiliation: "Technische Universität München" orcid: "https://orcid.org/0009-0009-9644-7475" + - given-names: Karoliine + family-names: Holter + affiliation: "University of Tartu" + orcid: "https://orcid.org/0009-0008-3725-4131" - given-names: Ralf family-names: Vogler affiliation: "Technische Universität München" diff --git a/bench/basic/benchSet.ml b/bench/basic/benchSet.ml index 14eb03be82..ae38c156da 100644 --- a/bench/basic/benchSet.ml +++ b/bench/basic/benchSet.ml @@ -73,7 +73,7 @@ let () = ] ); "const" @> lazy ( - let args = ((fun x -> 42), set1) in + let args = ((fun _ -> 42), set1) in throughputN 1 [ ("map1", map1, args); ("map2", map2, args); diff --git a/conf/examples/medium-program.json b/conf/examples/medium-program.json index 2c1e7c7346..5afc1aa2f8 100644 --- a/conf/examples/medium-program.json +++ b/conf/examples/medium-program.json @@ -9,6 +9,7 @@ "base", "threadid", "threadflag", + "threadreturn", "mallocWrapper", "mutexEvents", "mutex", @@ -18,6 +19,7 @@ "expRelation", "mhp", "assert", + "pthreadMutexType", "var_eq", "symb_locks", "region", diff --git a/conf/examples/very-precise.json b/conf/examples/very-precise.json index 2197335eaf..074d448a85 100644 --- a/conf/examples/very-precise.json +++ b/conf/examples/very-precise.json @@ -22,6 +22,7 @@ "base", "threadid", "threadflag", + "threadreturn", "mallocWrapper", "mutexEvents", "mutex", @@ -31,6 +32,7 @@ "expRelation", "mhp", "assert", + "pthreadMutexType", "var_eq", "symb_locks", "region", diff --git a/conf/svcomp-ghost.json b/conf/svcomp-ghost.json new file mode 100644 index 0000000000..d1b4171b2b --- /dev/null +++ b/conf/svcomp-ghost.json @@ -0,0 +1,139 @@ +{ + "ana": { + "sv-comp": { + "enabled": true, + "functions": true + }, + "int": { + "def_exc": true, + "enums": false, + "interval": true + }, + "float": { + "interval": true, + "evaluate_math_functions": true + }, + "activated": [ + "base", + "threadid", + "threadflag", + "threadreturn", + "mallocWrapper", + "mutexEvents", + "mutex", + "access", + "race", + "escape", + "expRelation", + "mhp", + "assert", + "var_eq", + "symb_locks", + "region", + "thread", + "threadJoins", + "abortUnless", + "mutexGhosts", + "pthreadMutexType" + ], + "path_sens": [ + "mutex", + "malloc_null", + "uninit", + "expsplit", + "activeSetjmp", + "memLeak", + "threadflag" + ], + "context": { + "widen": false + }, + "base": { + "arrays": { + "domain": "partitioned" + }, + "invariant": { + "local": false, + "global": true + } + }, + "relation": { + "invariant": { + "local": false, + "global": true, + "one-var": false + } + }, + "apron": { + "invariant": { + "diff-box": true + } + }, + "var_eq": { + "invariant": { + "enabled": false + } + }, + "race": { + "free": false, + "call": false + }, + "autotune": { + "enabled": true, + "activated": [ + "singleThreaded", + "mallocWrappers", + "noRecursiveIntervals", + "enums", + "congruence", + "octagon", + "wideningThresholds", + "loopUnrollHeuristic", + "memsafetySpecification", + "noOverflows", + "termination", + "tmpSpecialAnalysis" + ] + } + }, + "exp": { + "region-offsets": true + }, + "solver": "td3", + "sem": { + "unknown_function": { + "spawn": false + }, + "int": { + "signed_overflow": "assume_none" + }, + "null-pointer": { + "dereference": "assume_none" + } + }, + "witness": { + "graphml": { + "enabled": false + }, + "yaml": { + "enabled": true, + "format-version": "2.1", + "entry-types": [ + "flow_insensitive_invariant", + "ghost_instrumentation" + ] + }, + "invariant": { + "loop-head": true, + "after-lock": true, + "other": true, + "accessed": false, + "exact": true, + "all-locals": false, + "flow_insensitive-as": "invariant_set-location_invariant" + } + }, + "pre": { + "enabled": false + } +} diff --git a/conf/svcomp-validate.json b/conf/svcomp-validate.json new file mode 100644 index 0000000000..bec171f1e8 --- /dev/null +++ b/conf/svcomp-validate.json @@ -0,0 +1,123 @@ +{ + "ana": { + "sv-comp": { + "enabled": true, + "functions": true + }, + "int": { + "def_exc": true, + "enums": false, + "interval": true + }, + "float": { + "interval": true, + "evaluate_math_functions": true + }, + "activated": [ + "base", + "threadid", + "threadflag", + "threadreturn", + "mallocWrapper", + "mutexEvents", + "mutex", + "access", + "race", + "escape", + "expRelation", + "mhp", + "assert", + "var_eq", + "symb_locks", + "region", + "thread", + "threadJoins", + "abortUnless", + "unassume" + ], + "path_sens": [ + "mutex", + "malloc_null", + "uninit", + "expsplit", + "activeSetjmp", + "memLeak", + "threadflag" + ], + "context": { + "widen": false + }, + "base": { + "arrays": { + "domain": "partitioned" + } + }, + "race": { + "free": false, + "call": false + }, + "autotune": { + "enabled": true, + "activated": [ + "singleThreaded", + "mallocWrappers", + "noRecursiveIntervals", + "enums", + "congruence", + "octagon", + "wideningThresholds", + "loopUnrollHeuristic", + "memsafetySpecification", + "noOverflows", + "termination", + "tmpSpecialAnalysis" + ] + }, + "widen": { + "tokens": true + } + }, + "exp": { + "region-offsets": true + }, + "solver": "td3", + "sem": { + "unknown_function": { + "spawn": false + }, + "int": { + "signed_overflow": "assume_none" + }, + "null-pointer": { + "dereference": "assume_none" + } + }, + "witness": { + "graphml": { + "enabled": false + }, + "yaml": { + "enabled": false, + "strict": true, + "format-version": "2.0", + "entry-types": [ + "location_invariant", + "loop_invariant", + "invariant_set", + "violation_sequence" + ], + "invariant-types": [ + "location_invariant", + "loop_invariant" + ] + }, + "invariant": { + "loop-head": true, + "after-lock": true, + "other": true + } + }, + "pre": { + "enabled": false + } +} diff --git a/conf/svcomp.json b/conf/svcomp.json index 7e30554ceb..dedc393ba1 100644 --- a/conf/svcomp.json +++ b/conf/svcomp.json @@ -10,7 +10,8 @@ "interval": true }, "float": { - "interval": true + "interval": true, + "evaluate_math_functions": true }, "activated": [ "base", @@ -30,7 +31,8 @@ "symb_locks", "region", "thread", - "threadJoins" + "threadJoins", + "abortUnless" ], "path_sens": [ "mutex", @@ -44,27 +46,6 @@ "context": { "widen": false }, - "malloc": { - "wrappers": [ - "kmalloc", - "__kmalloc", - "usb_alloc_urb", - "__builtin_alloca", - "kzalloc", - - "ldv_malloc", - - "kzalloc_node", - "ldv_zalloc", - "kmalloc_array", - "kcalloc", - - "ldv_xmalloc", - "ldv_xzalloc", - "ldv_calloc", - "ldv_kzalloc" - ] - }, "base": { "arrays": { "domain": "partitioned" @@ -86,6 +67,7 @@ "wideningThresholds", "loopUnrollHeuristic", "memsafetySpecification", + "noOverflows", "termination", "tmpSpecialAnalysis" ] @@ -127,17 +109,7 @@ "after-lock": false, "other": false, "accessed": false, - "exact": true, - "exclude-vars": [ - "tmp\\(___[0-9]+\\)?", - "cond", - "RETURN", - "__\\(cil_\\)?tmp_?[0-9]*\\(_[0-9]+\\)?", - ".*____CPAchecker_TMP_[0-9]+", - "__VERIFIER_assert__cond", - "__ksymtab_.*", - "\\(ldv_state_variable\\|ldv_timer_state\\|ldv_timer_list\\|ldv_irq_\\(line_\\|data_\\)?[0-9]+\\|ldv_retval\\)_[0-9]+" - ] + "exact": true } }, "pre": { diff --git a/conf/svcomp24-validate.json b/conf/svcomp24-validate.json index 7832ffa6af..d83b1767a4 100644 --- a/conf/svcomp24-validate.json +++ b/conf/svcomp24-validate.json @@ -10,7 +10,8 @@ "interval": true }, "float": { - "interval": true + "interval": true, + "evaluate_math_functions": true }, "activated": [ "base", diff --git a/conf/svcomp24.json b/conf/svcomp24.json index 7e30554ceb..1c60f84920 100644 --- a/conf/svcomp24.json +++ b/conf/svcomp24.json @@ -10,7 +10,8 @@ "interval": true }, "float": { - "interval": true + "interval": true, + "evaluate_math_functions": true }, "activated": [ "base", diff --git a/conf/svcomp25-validate.json b/conf/svcomp25-validate.json new file mode 100644 index 0000000000..bec171f1e8 --- /dev/null +++ b/conf/svcomp25-validate.json @@ -0,0 +1,123 @@ +{ + "ana": { + "sv-comp": { + "enabled": true, + "functions": true + }, + "int": { + "def_exc": true, + "enums": false, + "interval": true + }, + "float": { + "interval": true, + "evaluate_math_functions": true + }, + "activated": [ + "base", + "threadid", + "threadflag", + "threadreturn", + "mallocWrapper", + "mutexEvents", + "mutex", + "access", + "race", + "escape", + "expRelation", + "mhp", + "assert", + "var_eq", + "symb_locks", + "region", + "thread", + "threadJoins", + "abortUnless", + "unassume" + ], + "path_sens": [ + "mutex", + "malloc_null", + "uninit", + "expsplit", + "activeSetjmp", + "memLeak", + "threadflag" + ], + "context": { + "widen": false + }, + "base": { + "arrays": { + "domain": "partitioned" + } + }, + "race": { + "free": false, + "call": false + }, + "autotune": { + "enabled": true, + "activated": [ + "singleThreaded", + "mallocWrappers", + "noRecursiveIntervals", + "enums", + "congruence", + "octagon", + "wideningThresholds", + "loopUnrollHeuristic", + "memsafetySpecification", + "noOverflows", + "termination", + "tmpSpecialAnalysis" + ] + }, + "widen": { + "tokens": true + } + }, + "exp": { + "region-offsets": true + }, + "solver": "td3", + "sem": { + "unknown_function": { + "spawn": false + }, + "int": { + "signed_overflow": "assume_none" + }, + "null-pointer": { + "dereference": "assume_none" + } + }, + "witness": { + "graphml": { + "enabled": false + }, + "yaml": { + "enabled": false, + "strict": true, + "format-version": "2.0", + "entry-types": [ + "location_invariant", + "loop_invariant", + "invariant_set", + "violation_sequence" + ], + "invariant-types": [ + "location_invariant", + "loop_invariant" + ] + }, + "invariant": { + "loop-head": true, + "after-lock": true, + "other": true + } + }, + "pre": { + "enabled": false + } +} diff --git a/conf/svcomp25.json b/conf/svcomp25.json new file mode 100644 index 0000000000..dedc393ba1 --- /dev/null +++ b/conf/svcomp25.json @@ -0,0 +1,118 @@ +{ + "ana": { + "sv-comp": { + "enabled": true, + "functions": true + }, + "int": { + "def_exc": true, + "enums": false, + "interval": true + }, + "float": { + "interval": true, + "evaluate_math_functions": true + }, + "activated": [ + "base", + "threadid", + "threadflag", + "threadreturn", + "mallocWrapper", + "mutexEvents", + "mutex", + "access", + "race", + "escape", + "expRelation", + "mhp", + "assert", + "var_eq", + "symb_locks", + "region", + "thread", + "threadJoins", + "abortUnless" + ], + "path_sens": [ + "mutex", + "malloc_null", + "uninit", + "expsplit", + "activeSetjmp", + "memLeak", + "threadflag" + ], + "context": { + "widen": false + }, + "base": { + "arrays": { + "domain": "partitioned" + } + }, + "race": { + "free": false, + "call": false + }, + "autotune": { + "enabled": true, + "activated": [ + "singleThreaded", + "mallocWrappers", + "noRecursiveIntervals", + "enums", + "congruence", + "octagon", + "wideningThresholds", + "loopUnrollHeuristic", + "memsafetySpecification", + "noOverflows", + "termination", + "tmpSpecialAnalysis" + ] + } + }, + "exp": { + "region-offsets": true + }, + "solver": "td3", + "sem": { + "unknown_function": { + "spawn": false + }, + "int": { + "signed_overflow": "assume_none" + }, + "null-pointer": { + "dereference": "assume_none" + } + }, + "witness": { + "graphml": { + "enabled": true, + "id": "enumerate", + "unknown": false + }, + "yaml": { + "enabled": true, + "format-version": "2.0", + "entry-types": [ + "invariant_set" + ], + "invariant-types": [ + "loop_invariant" + ] + }, + "invariant": { + "loop-head": true, + "after-lock": false, + "other": false, + "accessed": false, + "exact": true + } + }, + "pre": { + "enabled": false + } +} diff --git a/docs/artifact-descriptions/vmcai25.md b/docs/artifact-descriptions/vmcai25.md new file mode 100644 index 0000000000..282be65ff0 --- /dev/null +++ b/docs/artifact-descriptions/vmcai25.md @@ -0,0 +1,298 @@ +# VMCAI '25 Artifact Description +## Correctness Witnesses for Concurrent Programs: Bridging the Semantic Divide with Ghosts + +This is the artifact description for our [VMCAI '25 paper "Correctness Witnesses for Concurrent Programs: Bridging the Semantic Divide with Ghosts"](https://doi.org/10.48550/arXiv.2411.16612). +The artifact is available on [Zenodo](https://doi.org/10.5281/zenodo.13863579). + +**The description here is provided for convenience and not maintained.** +The artifact contains [Goblint at `vmcai25` git tag](https://github.com/goblint/analyzer/releases/tag/vmcai25). + + +## Overview + +This artifact contains the following components: + + Evaluation Results :: The evaluation results, and overview tables (HTML) + generated from the raw data. + Source Code :: Source code for Goblint and Ultimate GemCutter, + the verification tools used in the experiments + for the paper. + Verifier Binaries :: Binaries for Goblint and Ultimate GemCutter. + Benchmark Programs :: Benchmarks used for evaluation of the verifiers. + Benchmark Witnesses :: The witnesses generated by Goblint, as used in the + experiments. + BenchExec Tool :: The BenchExec benchmarking tool can be used + to replicate our results on these benchmarks. + +The next section gives instructions on how to setup and quickly get an overview of the artifact. +The subsequent sections then describe each of these components in detail. +The final section gives information on how to reuse this artifact. + + +## Getting Started + +### 1. Setup + +The artifact is a virtual machine (VM). Follow these steps to set it up: + +* If you have not done so already, install VirtualBox. + () +* Download the artifact. +* Import the artifact via the VirtualBox UI (`File > Import Appliance`) + or by running `VBoxManage import ghost-witnesses.ova`. + +You can then start the VM from the VirtualBox UI. +Login with user `vagrant` and password `vagrant`. +(Note: By default, the user `Ubuntu` may be selected on the login screen. Click on the name and switch to user `vagrant`.) + +You may want to install VirtualBox guest additions (). +If the usual installation does not work, try the following steps: + +* Add a disk drive to the VM in its settings (the VM must be off for this). +* After starting the VM and logging in, select `Devices > Insert Guest Additions CD image` from the VirtualBox menu. +* Run the following in a terminal: + `sudo mount /dev/cdrom /mnt && cd /mnt && sudo ./VBoxLinuxAdditions.run` + +### 2. Inspect the evaluation results + +To extract the table data used in the paper from our raw evaluation results, open a terminal in the VM (`Ctrl+Alt+T`) and run the following commands: + + ~/scripts/analyse-witnesses.py "$HOME/witness-generation/paper-evaluation/goblint.2024-09-02_08-21-23.files" + cd ~/witness-validation/paper-evaluation/ + WITNESS_DIR="$HOME/witness-generation/paper-evaluation/goblint.2024-09-02_08-21-23.files" ~/scripts/analyse-validation.py + +The times for the Table 2 are given first in seconds and then pretty-printed as in the paper. + +For a more detailed inspection and visualization of the data, take a look at the generated HTML report. +Just open the file `~/witness-validation/paper-evaluation/results.2024-09-24_22-00-48.table.html` in firefox. +For detailed information, see the ["Evaluation Results" section](#evaluation-results) below. + +### 3. Quick Test of the Benchmark Setup + +To analyse some programs with Goblint and analyse the generated witnesses with GemCutter, run + + ~/scripts/quick-run.sh + +on a console in the VM. +This will analyse a single program with Goblint and generate witnesses using the four analyses described in the paper. +Subsequently, the script will run GemCutter's verification, witness confirmation and witness validation analyses on the example. + +The whole run should should conclude successfully for all configurations in about 2min. +This is indicated by the benchmark results `true` printed on the console, and the fact that the final output looks roughly like this: + + Table 1: Witness Confirmation + ============================= + correct programs + ----------------------------- + protection-ghost : total= 1 , confirmed= 1 , rejected= 0 , confirmation rate= 100.0 % , resource out= 0 + mutex-meet-ghost : total= 1 , confirmed= 1 , rejected= 0 , confirmation rate= 100.0 % , resource out= 0 + protection-local : total= 1 , confirmed= 1 , rejected= 0 , confirmation rate= 100.0 % , resource out= 0 + mutex-meet-local : total= 1 , confirmed= 1 , rejected= 0 , confirmation rate= 100.0 % , resource out= 0 + + confirmation range: 100.0 % - 100.0 % + + incorrect programs + ----------------------------- + No programs with witnesses found for protection-ghost. Skipping benchmark set... + No programs with witnesses found for mutex-meet-ghost. Skipping benchmark set... + No programs with witnesses found for protection-local. Skipping benchmark set... + No programs with witnesses found for mutex-meet-local. Skipping benchmark set... + + confirmation range: 100.0 % - 0.0 % + + + Table 2: Witness Validation + =========================== + protection-ghost + ---------------- + validation : {'number': 1, 'time': 14, 'time_pretty': 0:00:14} + verification : {'number': 1, 'time': 12, 'time_pretty': 0:00:12} + 0 tasks could be validated but not verified + + mutex-meet-ghost + ---------------- + validation : {'number': 1, 'time': 13, 'time_pretty': 0:00:13} + verification : {'number': 1, 'time': 12, 'time_pretty': 0:00:12} + 0 tasks could be validated but not verified + + protection-local + ---------------- + validation : {'number': 1, 'time': 12, 'time_pretty': 0:00:12} + verification : {'number': 1, 'time': 12, 'time_pretty': 0:00:12} + 0 tasks could be validated but not verified + + mutex-meet-local + ---------------- + validation : {'number': 1, 'time': 13, 'time_pretty': 0:00:13} + verification : {'number': 1, 'time': 12, 'time_pretty': 0:00:12} + 0 tasks could be validated but not verified + + ~ + + =============================================================================== + Completed quick benchmark test run. + + Results of witness generation can be found in: /home/vagrant/witness-generation/2024-10-01_08-57-09 + Generated witnesses are located in: /home/vagrant/witness-generation/2024-10-01_08-57-09/goblint.2024-10-01_08-57-09.files + Results of witness validation can be found in: /home/vagrant/witness-validation/2024-10-01_08-57-09 + =============================================================================== + +For a slightly larger set of experiments, run + + ~/scripts/medium-run.sh + +This will run an entire folder of SV-COMP benchmarks through Goblint and subsequently analyse the generated witness with GemCutter. +The whole run should complete in 3-4h. +(Note: This run uses a smaller timeout and memory limit than the full evaluation used in the paper, so the results for individual benchmarks may differ.) + +#### Troubleshooting +On certain old host machines, GemCutter fails with `ERROR(7)`, and the log files (`/home/vagrant/witness-validation/YYYY-MM-DD_hh-mm-ss/concurrency-witness-validation-gemcutter.YYYY-MM-DD_hh-mm-ss.logfiles/*.log`) contain a message as follows: + + [2024-10-01 22:04:14,025 INFO L327 MonitoredProcess]: [MP /home/vagrant/ultimate/releaseScripts/default/UGemCutter-linux/z3 SMTLIB2_COMPLIANT=true -memory:2024 -smt2 -in -t:2000 (1)] Waiting until timeout for monitored process + [2024-10-01 22:04:14,058 FATAL L? ?]: An unrecoverable error occured during an interaction with an SMT solver: + de.uni_freiburg.informatik.ultimate.logic.SMTLIBException: External (MP /home/vagrant/ultimate/releaseScripts/default/UGemCutter-linux/z3 SMTLIB2_COMPLIANT=true -memory:2024 -smt2 -in -t:2000 (1) with exit command (exit)) Received EOF on stdin. No stderr output. + +This is because the version of Z3 shipped with GemCutter uses certain processor instructions that the host does not support or VirtualBox emulates incorrectly. +In this case, download an official build of Z3 () and replace the file `~/ultimate/releaseScripts/default/UGemCutter-linux/z3` with the corresponding binary from the official ZIP: + + wget https://github.com/Z3Prover/z3/releases/download/z3-4.12.5/z3-4.12.5-x64-glibc-2.31.zip + unzip z3-4.12.5-x64-glibc-2.31.zip + cp z3-4.12.5-x64-glibc-2.31/bin/z3 ~/ultimate/releaseScripts/default/UGemCutter-linux/z3 + +### 4. Running the Full Experiments + +To re-run the full experiments, execute + + ~/scripts/full-run.sh + +This script behaves similarly to the smaller variants in the previous sections. +Note however: + +- By default the experiments require 16 GB of memory per benchmark (this configuration was used for the experiments in the paper). + To reproduce this, you will have to modify the VM's settings in VirtualBox and increase the available memory (shutdown the machine while doing so). + + Alternatively, you can run the experiments with a reduced memory limit. + To do so, modify the environment variable `BENCHMARK_PARAMS`. + For instance, the following allows only 4GB of memory per benchmark: + + BENCHMARK_PARAMS="-M 4GB" ~/scripts/full-run.sh + +- The full evaluation for the paper required around 3 days. + In this evaluation, we used the benchexec tool to run 14 validation tasks in parallel (occupying up to 28 cores at a time). + + By default, the provided script only runs one benchmark at a time. + If you have sufficient cores and memory available (adjust the VM settings accordingly), you can run multiple benchmarks in parallel by setting the environment variable `BENCHEXEC_THREADS`. You may also execute the experiments with a reduced timeout. + For instance, the following command runs 4 benchmarks in parallel at a time (occupying up to 8 cores), and gives each benchmark a 300s timeout and 4GB memory limit: + + BENCHEXEC_THREADS=4 BENCHMARK_PARAMS="-T 300s -M 4GB" ~/scripts/full-run.sh + +Naturally, changes to the timeout or memory are expected to affect the evaluation numbers. + + +## Evaluation Results + +The evaluation results that form the basis for the experimental data in the paper can be found in the directory `~/witness-validation/paper-evaluation/`. +The witnesses generated by Goblint that formed the basis for this evaluation can be found in `~/witness-generation/paper-evaluation/`. +See below for detailed info to reproduce the tables and figures of the paper. + +The file `~/witness-validation/paper-evaluation/` contains an HTML overview page generated by the BenchExec benchmarking tool, which displays individual results, quantile and scatter plots. Through the filtering sidebar (top right corner), detailed analyses can be made. + +The table contains the following configurations : + +* `verify` -- GemCutter verification, without any witness +* `verify+validate-goblint-witnesses-{mutex-meet,protection}-{ghost,local}` -- GemCutter witness validation, applied to the 4 different witness sets generated by Goblint. + In this mode, GemCutter checks if the given witness is valid and the corresponding program is correct. +* `validate-goblint-witnesses-{mutex-meet,protection}-{ghost,local}` -- GemCutter witness _confirmation_, applied to the different witness sets. + In this mode, described in the paper, GemCutter only checks if the given witness' invariants are correct, but does not prove the corresponding program correct. + +The summary table shows how many benchmarks were analysed and the results. + +- The row `correct true` indicates tasks that were successfully verified (for `verify`), or where the witness was confirmed resp. validated (for the other configurations). +- The row `correct false` indicates that a bug was found (for `verify`), resp. that witness validation failed and a witness was rejected. + The latter only happens for programs that are incorrect, hence there can be no valid correctness witness and rejection is expected. + As witness validation is not possible for incorrect programs, this data is not discussed in the paper and only appears here due to the benchmark setup. +- The row `incorrect false` would indicate that GemCutter finds a supposed bug in a correct program (for `verify`). + For witness confirmation configurations, it indicates that GemCutter confirmed the correctness witness given by Goblint, for an incorrect program. + As witness confirmation ignores the program's correctness, these results are expected and do not indicate a problem in one of the tools. + +The *Table* tab gives access to detailed evaluation results for each file. +Clicking on a status shows the complete GemCutter log for the benchmark run. + +> **Note:** If you are trying to view logs for individual runs through the HTML table (by clicking on the evaluation result `true` or `false`), you may encounter a warning because browsers block access to local files. Follow the instructions in the message to enable log viewing. + +As described above (in [section _2. Inspect the evaluation results_](#2-inspect-the-evaluation-results)), the artifact provides python scripts to directly extract the data shown in the paper from the benchmark results. + + +## Source Code + +### GemCutter + +Ultimate GemCutter is developed as part of the Ultimate program analysis framework () and is implemented in Java. The source code for Ultimate at the time of evaluation can be found in this artifact in the `~/ultimate` directory. + +The directory `trunk/source/CACSL2BoogieTranslator/src/de/uni_freiburg/informatik/ultimate/plugins/generator/cacsl2boogietranslator/witness` is of particular interest for the present paper. +The code in this directory handles instrumentation of the program with various witness entries, as part of Ultimate's translation of the original C code to the intermediate verification language Boogie. + +More recent versions of Ultimate can be found at . + +### Goblint + +The Goblint analyzer () is developed by TU Munich and University of Tartu. The source code for Goblint at the time of evaluation can be found in this artifact in the `~/goblint` directory. + +The code for this paper is the following: + +1. `src/witness/witnessGhostVar.ml` and `src/witness/witnessGhost.ml` define the data types for ghost variables. +2. `src/analyses/mutexGhosts.ml` defines the analysis which determines the ghost variables for a specific program and their updates. +3. `src/analyses/basePriv.ml` lines 342-365 define the invariants with mutex ghost variables from non-relational _mutex-meet_ analysis. +4. `src/analyses/apron/relationPriv.apron.ml` lines 717-750 define the invariants with mutex ghost variables from relational _mutex-meet_ analysis. +5. `src/analyses/base.ml` lines 1269-1289 and `src/analyses/apron/relationAnalysis.apron.ml` lines 637-644 define the wrapping of the invariants with multithreaded mode ghost variables. +6. `src/analyses/basePriv.ml` lines 882-909 define the invariants with mutex ghost variables from (non-relational) _protection_ analysis. +7. `src/witness/yamlWitness.ml` lines 398-421 and 589-621 define the YAML output of ghost variables, their updates and the invariants. + +More recent versions of Goblint can be found at . + + +## Verifier Binaries + +A pre-built binary of GemCutter can be found in `~/ultimate/releaseScripts/default/UGemCutter-linux/`. +For information on how to execute GemCutter, consult the `README` in `~/ultimate/releaseScripts/default/UGemCutter-linux/`. +To build Ultimate GemCutter from scratch, go to `~/ultimate/releaseScripts/default/` and run `./makeFresh.sh`. + +A pre-built binary of Goblint is available as `~/goblint/goblint`. +See `~/goblint/README.md` for information on how to run Goblint. +To build Goblint from scratch, run `make setup && make release`. + +Both GemCutter and Goblint can be invoked via the BenchExec benchmarking tool () which is installed in the VM. +For examples, see the benchmark definition files `~/witness-generation/goblint.xml` resp. `~/witness-validation/gemcutter.xml` and the scripts `~/scripts/generate-witnesses.sh` resp. `~/scripts/validate-witnesses.sh`. + + +## Benchmark Programs + +This artifact includes the benchmark programs on which we evaluated the verifiers. +These benchmarks are taken from the publicly available sv-benchmarks set () +and correspond to the _ConcurrencySafety-Main_ category of SV-COMP'24 (). +The benchmarks are written in C and use POSIX threads (`pthreads`) to model concurrency. + + +## Extending & Reusing This Artifact + +* **Building a modified version of the VM:** This VM was created using the `vagrant` tool (). + The `Vagrantfile` used to build the artifact, along with several other files used in the build, is included in the directory `~/artifact`. + This can be used to inspect the setup of the VM, and even build a modified version. + + Note that, to rebuild the VM, some files (e.g. scripts, evaluation results, this README) need to be extracted from the image and placed in a suitable location on your machine. + +* **Adding benchmarks:** You can easily add your own benchmarks programs written in C. + C programs should contain an empty function called `reach_error()`. Goblint and GemCutter then check that this function is never invoked. Certain (gcc) preprocessing steps may be necessary, e.g. to resolve `#include`s. See the SV-COMP benchmarks for examples (the preprocessed files typically have the extension `.i`). + + To run the evaluation on your own programs, you must edit the benchmark definition files `~/witness-generation/goblint.xml.template` resp. `~/witness-validation/gemcutter.xml.template`. + Replace the `` path specified in the task set `minimal` with your own path. + You can then simply run `~/scripts/quick-run.sh`. + +* **Adding more tools:** As described above, you can reuse the `Vagrantfile` for this artifact and extend it with whatever installation measures are necessary for an additional tool. Also note, that in order to run other tools with BenchExec, you must write a *tool info module* in python (). + + Create a new benchmark definition file for your tool (). + The existing files `~/witness-generation/goblint.xml.template` resp. `~/witness-validation/gemcutter.xml.template` can serve as an example. + + If you are planning to support generation or validation of correctness witnesses using the proposed format, take a look at the YAML schema definition for the format linked in the paper. + The schema is also available in this artifact as `~/artifact-files/correctness-witness-schema.yml`. + diff --git a/docs/developer-guide/firstanalysis.md b/docs/developer-guide/firstanalysis.md index 4eb35e7f5d..bd4dfb3c11 100644 --- a/docs/developer-guide/firstanalysis.md +++ b/docs/developer-guide/firstanalysis.md @@ -35,7 +35,7 @@ This program is in the Goblint repository: `tests/regression/99-tutorials/01-fir But if you run Goblint out of the box on this example, it will not work: ```console -./goblint --enable warn.debug tests/regression/99-tutorials/01-first.c +./goblint tests/regression/99-tutorials/01-first.c ``` This will claim that the assertion in unknown. @@ -74,10 +74,10 @@ For more information on the signature of the individual transfer functions, plea ## Extending the domain You could now enrich the lattice to also have a representation for non-negative (i.e., zero or positive) values. -Then the join of `Zero` and `Pos` would be "non-negative" instead of `Top`, allowing you to prove that such join is greated than `Neg`. +Then the join of `Zero` and `Pos` would be "non-negative" instead of `Top`, allowing you to prove that such join is greater than `Neg`. For example, have a look at the following program: `tests/regression/99-tutorials/02-first-extend.c`. _Hint:_ The easiest way to do this is to use the powerset lattice of `{-, 0, +}`. For example, "non-negative" is represented by `{0, +}`, while negative is represented by `{-}`. -To do this, modify `SL` by using `SetDomain.FiniteSet` (takes a `struct` with a list of finite elements as second parameter) instead of `Lattice.Flat` and reimplementing the two functions using `singleton` and `for_all`. +To do this, modify `SL` by using `SetDomain.FiniteSet` (which needs a finite list of elements to be added to `Signs`) instead of `Lattice.Flat` and reimplementing the two functions using `singleton` and `for_all`. diff --git a/docs/developer-guide/releasing.md b/docs/developer-guide/releasing.md index d875c0d3bf..aca0749eb9 100644 --- a/docs/developer-guide/releasing.md +++ b/docs/developer-guide/releasing.md @@ -24,6 +24,8 @@ All changes must be committed because the working tree is not checked. + The warning `[FAIL] opam field doc cannot be parsed by dune-release` is fine and can be ignored (see ). + 8. Check that "unlocked" workflow passes on GitHub Actions. It can be run manually on the release branch for checking. @@ -36,12 +38,13 @@ 1. Pull Docker image: `docker pull ocaml/opam:ubuntu-22.04-ocaml-4.14` (or newer). 2. Extract distribution archive. 3. Run Docker container in extracted directory: `docker run -it --rm -v $(pwd):/goblint ocaml/opam:ubuntu-22.04-ocaml-4.14` (or newer). - 4. Navigate to distribution archive inside Docker container: `cd /goblint`. - 5. Install and test package from distribution archive: `opam-2.1 install --with-test .`. - 6. Activate opam environment: `eval $(opam env)`. - 7. Check version: `goblint --version`. - 8. Check that analysis works: `goblint -v tests/regression/04-mutex/01-simple_rc.c`. - 9. Exit Docker container. + 4. Update opam-repository from git: `opam-2.1 repository add git git+https://github.com/ocaml/opam-repository.git && opam-2.1 update`. + 5. Navigate to distribution archive inside Docker container: `cd /goblint`. + 6. Install and test package from distribution archive: `opam-2.1 install --with-test .`. + 7. Activate opam environment: `eval $(opam env)`. + 8. Check version: `goblint --version`. + 9. Check that analysis works: `goblint -v tests/regression/04-mutex/01-simple_rc.c`. + 10. Exit Docker container. 12. Temporarily enable Zenodo GitHub webhook. @@ -74,6 +77,8 @@ This includes: git tag name, git tag message and zipped conf file. +5. Open MR with conf file name to the [bench-defs](https://gitlab.com/sosy-lab/sv-comp/bench-defs) repository. + ### For each prerun 1. Update opam pins: diff --git a/docs/developer-guide/testing.md b/docs/developer-guide/testing.md index 22b0070fa4..9444a96007 100644 --- a/docs/developer-guide/testing.md +++ b/docs/developer-guide/testing.md @@ -69,6 +69,19 @@ Other useful constructs are the following: | `__goblint_check(1); // reachable` | Checks that the line is reachable according
to Goblint results (soundness). | | `__goblint_check(0); // NOWARN (unreachable)` | Checks that the line is unreachable (precision). | +#### Meta +Comments at the end of lines can also indicate metaproperties: + +| Annotation | Expected result/comment | +| ---------- | ----- | +| `NOCRASH` | No analyzer crash | +| `FIXPOINT` | No fixpoint error | +| `NOTIMEOUT` | Analyer terminates | +| `CRAM` | Automatic checks are only in corresponding Cram test | + +These comments only document the intention of the test (if there are no other checks in the test). +Analyzer crash, fixpoint error and non-termination are checked even when there are other checks. + ## Cram Tests [Cram-style tests](https://dune.readthedocs.io/en/stable/tests.html#cram-tests) are also used to verify that existing functionality hasn't been broken. They check the complete standard output of running the Goblint binary with specified command-line arguments. diff --git a/docs/user-guide/assumptions.md b/docs/user-guide/assumptions.md index f77e3b5097..33284ccc00 100644 --- a/docs/user-guide/assumptions.md +++ b/docs/user-guide/assumptions.md @@ -17,3 +17,22 @@ _NB! This list is likely incomplete._ See [PR #1414](https://github.com/goblint/analyzer/pull/1414). +2. Pointer arithmetic does not overflow. + + [C11's N1570][n1570] at 6.5.6.8 states that + + > When an expression that has integer type is added to or subtracted from a pointer, the result has the type of the pointer operand. + > [...] + > the evaluation shall not produce an overflow; otherwise, the behavior is undefined. + + after a long list of defined behaviors. + + Goblint does not report overflow and out-of-bounds pointer arithmetic (when the pointer _is not dereferenced_). + This affects the overflow analysis (SV-COMP no-overflow property) in the `base` analysis. + + This _does not_ affect the `memOutOfBounds` analysis (SV-COMP valid-memsafety property), which is for undefined behavior from _dereferencing_ such out-of-bounds pointers. + + See [PR #1511](https://github.com/goblint/analyzer/pull/1511). + + +[n1570]: https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf diff --git a/dune-project b/dune-project index 878abd3b4f..9a1d958484 100644 --- a/dune-project +++ b/dune-project @@ -15,23 +15,38 @@ (source (github goblint/analyzer)) (homepage "https://goblint.in.tum.de") (documentation "https://goblint.readthedocs.io/en/latest/") -(authors "Simmo Saan" "Michael Schwarz" "Julian Erhard" "Sarah Tilscher" "Ralf Vogler" "Kalmer Apinis" "Vesal Vojdani" ) ; same authors as in .zenodo.json and CITATION.cff -(maintainers "Simmo Saan " "Michael Schwarz " "Karoliine Holter") +(authors "Simmo Saan" "Michael Schwarz" "Julian Erhard" "Sarah Tilscher" "Karoliine Holter" "Ralf Vogler" "Kalmer Apinis" "Vesal Vojdani" ) ; same authors as in .zenodo.json and CITATION.cff +(maintainers "Simmo Saan " "Michael Schwarz " "Karoliine Holter ") (license MIT) (package (name goblint) (synopsis "Static analysis framework for C") + (description "\ +Goblint is a sound static analysis framework for C programs using abstract interpretation. +It specializes in thread-modular verification of multi-threaded programs, especially regarding data races. +Goblint includes analyses for assertions, overflows, deadlocks, etc and can be extended with new analyses. +") + (tags ( + "program analysis" + "program verification" + "static analysis" + "abstract interpretation" + "C" + "data race analysis" + "concurrency")) (depends (ocaml (>= 4.14)) - (goblint-cil (>= 2.0.3)) ; TODO no way to define as pin-depends? Used goblint.opam.template to add it for now. https://github.com/ocaml/dune/issues/3231. Alternatively, removing this line and adding cil as a git submodule and `(vendored_dirs cil)` as ./dune also works. This way, no more need to reinstall the pinned cil opam package on changes. However, then cil is cleaned and has to be rebuild together with goblint. + (goblint-cil (>= 2.0.5)) ; TODO no way to define as pin-depends? Used goblint.opam.template to add it for now. https://github.com/ocaml/dune/issues/3231. Alternatively, removing this line and adding cil as a git submodule and `(vendored_dirs cil)` as ./dune also works. This way, no more need to reinstall the pinned cil opam package on changes. However, then cil is cleaned and has to be rebuild together with goblint. (batteries (>= 3.5.1)) (zarith (>= 1.10)) (yojson (>= 2.0.0)) (qcheck-core (>= 0.19)) - ppx_deriving + (ppx_deriving (>= 6.0.2)) (ppx_deriving_hash (>= 0.1.2)) (ppx_deriving_yojson (>= 3.7.0)) + (ppx_blob (>= 0.8.0)) + (ppxlib (>= 0.30.0)) ; ppx_easy_deriving (ounit2 :with-test) (qcheck-ounit :with-test) (odoc :with-doc) @@ -54,7 +69,7 @@ conf-gcc ; ensures opam-repository CI installs real gcc from homebrew on MacOS ) (depopts - apron + (apron (>= v0.9.15)) z3 ) (conflicts diff --git a/goblint.opam b/goblint.opam index 692625c965..219c67d011 100644 --- a/goblint.opam +++ b/goblint.opam @@ -1,35 +1,52 @@ # This file is generated by dune, edit dune-project instead opam-version: "2.0" synopsis: "Static analysis framework for C" +description: """ +Goblint is a sound static analysis framework for C programs using abstract interpretation. +It specializes in thread-modular verification of multi-threaded programs, especially regarding data races. +Goblint includes analyses for assertions, overflows, deadlocks, etc and can be extended with new analyses. +""" maintainer: [ "Simmo Saan " "Michael Schwarz " - "Karoliine Holter" + "Karoliine Holter " ] authors: [ "Simmo Saan" "Michael Schwarz" "Julian Erhard" "Sarah Tilscher" + "Karoliine Holter" "Ralf Vogler" "Kalmer Apinis" "Vesal Vojdani" ] license: "MIT" +tags: [ + "program analysis" + "program verification" + "static analysis" + "abstract interpretation" + "C" + "data race analysis" + "concurrency" +] homepage: "https://goblint.in.tum.de" doc: "https://goblint.readthedocs.io/en/latest/" bug-reports: "https://github.com/goblint/analyzer/issues" depends: [ "dune" {>= "3.7"} "ocaml" {>= "4.14"} - "goblint-cil" {>= "2.0.3"} + "goblint-cil" {>= "2.0.5"} "batteries" {>= "3.5.1"} "zarith" {>= "1.10"} "yojson" {>= "2.0.0"} "qcheck-core" {>= "0.19"} - "ppx_deriving" + "ppx_deriving" {>= "6.0.2"} "ppx_deriving_hash" {>= "0.1.2"} "ppx_deriving_yojson" {>= "3.7.0"} + "ppx_blob" {>= "0.8.0"} + "ppxlib" {>= "0.30.0"} "ounit2" {with-test} "qcheck-ounit" {with-test} "odoc" {with-doc} @@ -51,7 +68,10 @@ depends: [ "benchmark" {with-test} "conf-gcc" ] -depopts: ["apron" "z3"] +depopts: [ + "apron" {>= "v0.9.15"} + "z3" +] conflicts: [ "result" {< "1.5"} "ez-conf-lib" {= "1"} @@ -75,12 +95,14 @@ build: [ dev-repo: "git+https://github.com/goblint/analyzer.git" # on `dune build` goblint.opam will be generated from goblint.opam.template and dune-project # also remember to generate/adjust goblint.opam.locked! -available: os-distribution != "alpine" & arch != "arm64" +available: os-family != "bsd" & os-distribution != "alpine" & (arch != "arm64" | os = "macos") pin-depends: [ - # published goblint-cil 2.0.3 is currently up-to-date, so no pin needed - [ "goblint-cil.2.0.3" "git+https://github.com/goblint/cil.git#ae3a4949d478fad77e004c6fe15a7c83427df59f" ] - # TODO: add back after release, only pinned for optimization (https://github.com/ocaml-ppx/ppx_deriving/pull/252) - [ "ppx_deriving.5.2.1" "git+https://github.com/ocaml-ppx/ppx_deriving.git#0a89b619f94cbbfc3b0fb3255ab4fe5bc77d32d6" ] + # published goblint-cil 2.0.5 is currently up-to-date, but pinned for reproducibility + [ "goblint-cil.2.0.5" "git+https://github.com/goblint/cil.git#c79208b21ea61d7b72eae29a18c1ddeda4795dfd" ] + # pinned for stability (https://github.com/goblint/analyzer/issues/1520), remove after new camlidl release + [ "camlidl.1.12" "git+https://github.com/xavierleroy/camlidl.git#1c1e87e3f56c2c6b3226dd0af3510ef414b462d0" ] + # pinned for stability (https://github.com/goblint/analyzer/issues/1520), remove after new apron release + [ "apron.v0.9.15" "git+https://github.com/antoinemine/apron.git#418a217c7a70dae3f422678f3aaba38ae374d91a" ] ] depexts: [ ["libgraph-easy-perl"] {os-distribution = "ubuntu" & with-test} @@ -88,3 +110,7 @@ depexts: [ post-messages: [ "Do not benchmark Goblint on OCaml 5 (https://goblint.readthedocs.io/en/latest/user-guide/benchmarking/)." {ocaml:version >= "5.0.0"} ] +x-ci-accept-failures: [ + "macos-homebrew" # newer MacOS headers cannot be parsed (https://github.com/ocaml/opam-repository/pull/26307#issuecomment-2258080206) + "opensuse-tumbleweed" # not GNU diff, so some cram tests fail (https://discuss.ocaml.org/t/opensuse-and-opam-tests/14641/2) +] diff --git a/goblint.opam.locked b/goblint.opam.locked index f8de683948..e5176b9007 100644 --- a/goblint.opam.locked +++ b/goblint.opam.locked @@ -5,13 +5,14 @@ synopsis: "Static analysis framework for C" maintainer: [ "Simmo Saan " "Michael Schwarz " - "Karoliine Holter" + "Karoliine Holter " ] authors: [ "Simmo Saan" "Michael Schwarz" "Julian Erhard" "Sarah Tilscher" + "Karoliine Holter" "Ralf Vogler" "Kalmer Apinis" "Vesal Vojdani" @@ -21,89 +22,95 @@ homepage: "https://goblint.in.tum.de" doc: "https://goblint.readthedocs.io/en/latest/" bug-reports: "https://github.com/goblint/analyzer/issues" depends: [ - "angstrom" {= "0.15.0"} - "apron" {= "v0.9.14~beta.2"} + "angstrom" {= "0.16.0"} + "apron" {= "v0.9.15"} "arg-complete" {= "0.1.0"} "astring" {= "0.8.5"} "base-bigarray" {= "base"} "base-bytes" {= "base"} "base-threads" {= "base"} "base-unix" {= "base"} - "batteries" {= "3.6.0"} + "batteries" {= "3.8.0"} "benchmark" {= "1.6" & with-test} "bigarray-compat" {= "1.1.0"} - "bigstringaf" {= "0.9.0"} + "bigstringaf" {= "0.9.1"} "bos" {= "0.2.1"} - "camlidl" {= "1.11"} + "camlidl" {= "1.12"} "camlp-streams" {= "5.0.1"} "catapult" {= "0.2"} "catapult-file" {= "0.2"} - "cmdliner" {= "1.1.1" & with-doc} - "conf-autoconf" {= "0.1"} + "cmdliner" {= "1.3.0" & with-doc} + "conf-autoconf" {= "0.2"} + "conf-findutils" {= "1"} "conf-gcc" {= "1.0"} "conf-gmp" {= "4"} - "conf-mpfr" {= "3"} + "conf-gmp-paths" {= "1"} + "conf-mpfr-paths" {= "1"} "conf-perl" {= "2"} - "conf-pkg-config" {= "2"} "conf-ruby" {= "1.0.0" & with-test} - "conf-which" {= "1"} "cppo" {= "1.6.9"} "cpu" {= "2.0.0"} - "csexp" {= "1.5.1"} - "ctypes" {= "0.20.1"} - "dune" {= "3.7.1"} - "dune-build-info" {= "3.7.1"} - "dune-configurator" {= "3.7.1"} - "dune-private-libs" {= "3.7.1"} - "dune-site" {= "3.7.1"} - "dyn" {= "3.7.1"} + "crunch" {= "3.3.1" & with-doc} + "csexp" {= "1.5.2"} + "cstruct" {= "6.2.0"} + "ctypes" {= "0.22.0"} + "dune" {= "3.16.0"} + "dune-build-info" {= "3.16.0"} + "dune-configurator" {= "3.16.0"} + "dune-private-libs" {= "3.16.0"} + "dune-site" {= "3.16.0"} + "dyn" {= "3.16.0"} + "ez-conf-lib" {= "2"} "fileutils" {= "0.6.4"} "fmt" {= "0.9.0"} "fpath" {= "0.7.3"} - "goblint-cil" {= "2.0.3"} + "goblint-cil" {= "2.0.5"} + "hex" {= "1.5.0"} "integers" {= "0.7.0"} - "json-data-encoding" {= "0.12.1"} - "jsonrpc" {= "1.15.0~5.0preview1"} + "json-data-encoding" {= "1.0.1"} + "jsonrpc" {= "1.17.0"} "logs" {= "0.7.0"} - "mlgmpidl" {= "1.2.15"} - "num" {= "1.4"} - "ocaml" {= "4.14.0"} + "mlgmpidl" {= "1.3.0"} + "num" {= "1.5"} + "ocaml" {= "4.14.2"} "ocaml-compiler-libs" {= "v0.12.4"} "ocaml-config" {= "2"} "ocaml-option-flambda" {= "1"} "ocaml-syntax-shims" {= "1.0.0"} - "ocaml-variants" {= "4.14.0+options"} - "ocamlbuild" {= "0.14.2"} - "ocamlfind" {= "1.9.5"} - "odoc" {= "2.2.0" & with-doc} - "odoc-parser" {= "2.0.0" & with-doc} - "ordering" {= "3.7.1"} - "ounit2" {= "2.2.6" & with-test} - "pp" {= "1.1.2"} + "ocaml-variants" {= "4.14.2+options"} + "ocamlbuild" {= "0.14.3"} + "ocamlfind" {= "1.9.6"} + "odoc" {= "2.4.2" & with-doc} + "odoc-parser" {= "2.4.2" & with-doc} + "ordering" {= "3.16.0"} + "ounit2" {= "2.2.7" & with-test} + "pp" {= "1.2.0"} + "ppx_blob" {= "0.9.0"} "ppx_derivers" {= "1.2.1"} - "ppx_deriving" {= "5.2.1"} + "ppx_deriving" {= "6.0.2"} "ppx_deriving_hash" {= "0.1.2"} - "ppx_deriving_yojson" {= "3.7.0"} - "ppxlib" {= "0.28.0"} - "qcheck-core" {= "0.20"} - "qcheck-ounit" {= "0.20" & with-test} - "re" {= "1.10.4" & with-doc} - "result" {= "1.5"} + "ppx_deriving_yojson" {= "3.8.0"} + "ppxlib" {= "0.32.1"} + "ptime" {= "1.1.0" & with-doc} + "qcheck-core" {= "0.21.3"} + "qcheck-ounit" {= "0.21.3" & with-test} + "re" {= "1.11.0" & with-doc} + "result" {= "1.5" & with-doc} "rresult" {= "0.7.0"} "seq" {= "base"} - "sexplib0" {= "v0.15.1"} - "sha" {= "1.15.2"} + "sexplib0" {= "v0.16.0"} + "sha" {= "1.15.4"} "stdlib-shims" {= "0.3.0"} - "stdune" {= "3.7.1"} + "stdune" {= "3.16.0"} "stringext" {= "1.6.0"} - "topkg" {= "1.0.6"} - "tyxml" {= "4.5.0" & with-doc} - "uri" {= "4.2.0"} + "topkg" {= "1.0.7"} + "tyxml" {= "4.6.0" & with-doc} + "uri" {= "4.4.0"} "uuidm" {= "0.9.8"} "uutf" {= "1.0.3" & with-doc} - "yaml" {= "3.1.0"} - "yojson" {= "2.0.2"} - "zarith" {= "1.12"} + "yaml" {= "3.2.0"} + "yojson" {= "2.2.1"} + "zarith" {= "1.14"} ] build: [ ["dune" "subst"] {dev} @@ -122,7 +129,7 @@ build: [ ["dune" "install" "-p" name "--create-install-files" name] ] dev-repo: "git+https://github.com/goblint/analyzer.git" -available: os-distribution != "alpine" & arch != "arm64" +available: os-family != "bsd" & os-distribution != "alpine" & (arch != "arm64" | os = "macos") conflicts: [ "result" {< "1.5"} "ez-conf-lib" {= "1"} @@ -130,15 +137,31 @@ conflicts: [ post-messages: [ "Do not benchmark Goblint on OCaml 5 (https://goblint.readthedocs.io/en/latest/user-guide/benchmarking/)." {ocaml:version >= "5.0.0"} ] -# TODO: manually reordered to avoid opam pin crash: https://github.com/ocaml/opam/issues/4936 pin-depends: [ [ - "goblint-cil.2.0.3" - "git+https://github.com/goblint/cil.git#ae3a4949d478fad77e004c6fe15a7c83427df59f" + "goblint-cil.2.0.5" + "git+https://github.com/goblint/cil.git#c79208b21ea61d7b72eae29a18c1ddeda4795dfd" ] [ - "ppx_deriving.5.2.1" - "git+https://github.com/ocaml-ppx/ppx_deriving.git#0a89b619f94cbbfc3b0fb3255ab4fe5bc77d32d6" + "camlidl.1.12" + "git+https://github.com/xavierleroy/camlidl.git#1c1e87e3f56c2c6b3226dd0af3510ef414b462d0" + ] + [ + "apron.v0.9.15" + "git+https://github.com/antoinemine/apron.git#418a217c7a70dae3f422678f3aaba38ae374d91a" ] ] depexts: ["libgraph-easy-perl"] {os-distribution = "ubuntu" & with-test} +description: """\ +Goblint is a sound static analysis framework for C programs using abstract interpretation. +It specializes in thread-modular verification of multi-threaded programs, especially regarding data races. +Goblint includes analyses for assertions, overflows, deadlocks, etc and can be extended with new analyses.""" +tags: [ + "program analysis" + "program verification" + "static analysis" + "abstract interpretation" + "C" + "data race analysis" + "concurrency" +] diff --git a/goblint.opam.template b/goblint.opam.template index 2d5ef10bc9..84dcc24d8d 100644 --- a/goblint.opam.template +++ b/goblint.opam.template @@ -1,11 +1,13 @@ # on `dune build` goblint.opam will be generated from goblint.opam.template and dune-project # also remember to generate/adjust goblint.opam.locked! -available: os-distribution != "alpine" & arch != "arm64" +available: os-family != "bsd" & os-distribution != "alpine" & (arch != "arm64" | os = "macos") pin-depends: [ - # published goblint-cil 2.0.3 is currently up-to-date, so no pin needed - [ "goblint-cil.2.0.3" "git+https://github.com/goblint/cil.git#ae3a4949d478fad77e004c6fe15a7c83427df59f" ] - # TODO: add back after release, only pinned for optimization (https://github.com/ocaml-ppx/ppx_deriving/pull/252) - [ "ppx_deriving.5.2.1" "git+https://github.com/ocaml-ppx/ppx_deriving.git#0a89b619f94cbbfc3b0fb3255ab4fe5bc77d32d6" ] + # published goblint-cil 2.0.5 is currently up-to-date, but pinned for reproducibility + [ "goblint-cil.2.0.5" "git+https://github.com/goblint/cil.git#c79208b21ea61d7b72eae29a18c1ddeda4795dfd" ] + # pinned for stability (https://github.com/goblint/analyzer/issues/1520), remove after new camlidl release + [ "camlidl.1.12" "git+https://github.com/xavierleroy/camlidl.git#1c1e87e3f56c2c6b3226dd0af3510ef414b462d0" ] + # pinned for stability (https://github.com/goblint/analyzer/issues/1520), remove after new apron release + [ "apron.v0.9.15" "git+https://github.com/antoinemine/apron.git#418a217c7a70dae3f422678f3aaba38ae374d91a" ] ] depexts: [ ["libgraph-easy-perl"] {os-distribution = "ubuntu" & with-test} @@ -13,3 +15,7 @@ depexts: [ post-messages: [ "Do not benchmark Goblint on OCaml 5 (https://goblint.readthedocs.io/en/latest/user-guide/benchmarking/)." {ocaml:version >= "5.0.0"} ] +x-ci-accept-failures: [ + "macos-homebrew" # newer MacOS headers cannot be parsed (https://github.com/ocaml/opam-repository/pull/26307#issuecomment-2258080206) + "opensuse-tumbleweed" # not GNU diff, so some cram tests fail (https://discuss.ocaml.org/t/opensuse-and-opam-tests/14641/2) +] diff --git a/gobview b/gobview index 72abbb576b..f8ae450f2a 160000 --- a/gobview +++ b/gobview @@ -1 +1 @@ -Subproject commit 72abbb576b8ffc8759dcfa31501738f6b561b05a +Subproject commit f8ae450f2ac83f5ccf0ee174d2b7ae8376bb4f76 diff --git a/lib/sv-comp/stub/src/sv-comp.c b/lib/sv-comp/stub/src/sv-comp.c index 12c04125d6..469a641e73 100644 --- a/lib/sv-comp/stub/src/sv-comp.c +++ b/lib/sv-comp/stub/src/sv-comp.c @@ -35,10 +35,10 @@ __VERIFIER_nondet2(unsigned int, u32) __VERIFIER_nondet2(unsigned short int, u16) // not in rules __VERIFIER_nondet2(unsigned char, u8) // not in rules __VERIFIER_nondet2(unsigned char, unsigned_char) // not in rules -__VERIFIER_nondet2(long long, longlong) // not in rules yet (https://gitlab.com/sosy-lab/benchmarking/sv-benchmarks/-/issues/1341) -__VERIFIER_nondet2(unsigned long long, ulonglong) // not in rules yet (https://gitlab.com/sosy-lab/benchmarking/sv-benchmarks/-/issues/1341) -__VERIFIER_nondet2(__uint128_t, uint128) // not in rules yet (https://gitlab.com/sosy-lab/benchmarking/sv-benchmarks/-/issues/1341) -__VERIFIER_nondet2(__int128_t, int128) // not in rules yet (https://gitlab.com/sosy-lab/benchmarking/sv-benchmarks/-/issues/1341) +__VERIFIER_nondet2(long long, longlong) +__VERIFIER_nondet2(unsigned long long, ulonglong) +__VERIFIER_nondet2(__uint128_t, uint128) +__VERIFIER_nondet2(__int128_t, int128) __VERIFIER_nondet2(unsigned char, uchar) __VERIFIER_nondet2(unsigned int, uint) __VERIFIER_nondet2(unsigned long, ulong) diff --git a/make.sh b/make.sh index 5b55e51beb..0f76759065 100755 --- a/make.sh +++ b/make.sh @@ -8,7 +8,7 @@ opam_setup() { set -x opam init -y -a --bare $SANDBOXING # sandboxing is disabled in travis and docker opam update - opam switch -y create . --deps-only --packages=ocaml-variants.4.14.0+options,ocaml-option-flambda --locked + opam switch -y create . --deps-only --packages=ocaml-variants.4.14.2+options,ocaml-option-flambda --locked } rule() { diff --git a/mkdocs.yml b/mkdocs.yml index a4c4238601..b55787f8da 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -41,3 +41,4 @@ nav: - "🇸 SAS '21": artifact-descriptions/sas21.md - "🇪 ESOP '23": artifact-descriptions/esop23.md - "🇻 VMCAI '24": artifact-descriptions/vmcai24.md + - "🇻 VMCAI '25": artifact-descriptions/vmcai25.md diff --git a/scripts/creduce/privPrecCompare.sh b/scripts/creduce/privPrecCompare.sh index fdc5f9219d..2165112924 100755 --- a/scripts/creduce/privPrecCompare.sh +++ b/scripts/creduce/privPrecCompare.sh @@ -22,7 +22,7 @@ for PRIV in "${PRIVS[@]}"; do PRIVDUMP="$OUTDIR/$PRIV" LOG="$OUTDIR/$PRIV.log" rm -f $PRIVDUMP - $GOBLINTDIR/goblint --sets exp.privatization $PRIV --sets exp.priv-prec-dump $PRIVDUMP $OPTS -v --enable warn.debug &> $LOG + $GOBLINTDIR/goblint --set exp.privatization $PRIV --set exp.priv-prec-dump $PRIVDUMP $OPTS -v --enable warn.debug &> $LOG grep -F "Function definition missing" $LOG && exit 1 done diff --git a/scripts/goblint-lib-modules.py b/scripts/goblint-lib-modules.py index 017530f838..90537e57fe 100755 --- a/scripts/goblint-lib-modules.py +++ b/scripts/goblint-lib-modules.py @@ -42,8 +42,19 @@ "Goblint_build_info", "Dune_build_info", + # ppx-s + "Ppx_deriving_printable", + "Ppx_deriving_lattice", + "MessageCategory", # included in Messages "PreValueDomain", # included in ValueDomain + "IntervalDomain", # included in IntDomain + "IntervalSetDomain", # included in IntDomain + "DefExcDomain", # included in IntDomain + "EnumsDomain", # included in IntDomain + "CongruenceDomain", # included in IntDomain + "IntDomTuple", # included in IntDomain + "WitnessGhostVar", # included in WitnessGhost "ConfigVersion", "ConfigProfile", diff --git a/scripts/privPrecCompare.sh b/scripts/privPrecCompare.sh index c5b9f2c01e..2b09fb80b5 100755 --- a/scripts/privPrecCompare.sh +++ b/scripts/privPrecCompare.sh @@ -10,7 +10,7 @@ mkdir -p $OUTDIR for PRIV in "${PRIVS[@]}"; do echo $PRIV PRIVDUMP="$OUTDIR/$PRIV" - ./goblint --sets exp.privatization $PRIV --sets exp.priv-prec-dump $PRIVDUMP "$@" + ./goblint --set exp.privatization $PRIV --set exp.priv-prec-dump $PRIVDUMP "$@" done PRIVDUMPS=("${PRIVS[*]/#/$OUTDIR/}") # why [*] here? diff --git a/scripts/sv-comp/archive.sh b/scripts/sv-comp/archive.sh index 37fa2758d9..aefac8f769 100755 --- a/scripts/sv-comp/archive.sh +++ b/scripts/sv-comp/archive.sh @@ -4,7 +4,7 @@ make clean -git tag -m "SV-COMP 2024" svcomp24 +git tag -m "SV-COMP 2025" svcomp25 dune build --profile=release src/goblint.exe rm -f goblint @@ -32,8 +32,8 @@ zip goblint/scripts/sv-comp/goblint.zip \ goblint/lib/libboxD.so \ goblint/lib/libpolkaMPQ.so \ goblint/lib/LICENSE.APRON \ - goblint/conf/svcomp24.json \ - goblint/conf/svcomp24-validate.json \ + goblint/conf/svcomp25.json \ + goblint/conf/svcomp25-validate.json \ goblint/lib/libc/stub/include/assert.h \ goblint/lib/goblint/runtime/include/goblint.h \ goblint/lib/libc/stub/src/stdlib.c \ diff --git a/scripts/test-gobview.py b/scripts/test-gobview.py index f5961108d7..10808a9b62 100644 --- a/scripts/test-gobview.py +++ b/scripts/test-gobview.py @@ -6,6 +6,7 @@ from webdriver_manager.chrome import ChromeDriverManager from selenium.webdriver.common.by import By from selenium.webdriver.chrome.options import Options +from selenium.webdriver.common.desired_capabilities import DesiredCapabilities from threading import Thread import subprocess @@ -17,6 +18,11 @@ # cleanup def cleanup(browser, thread): print("cleanup") + + # print messages + for entry in browser.get_log('browser'): + print(entry) + browser.close() p.kill() thread.join() @@ -35,6 +41,8 @@ def serve(): print("starting installation of browser\n") options = Options() options.add_argument('headless') +# options.set_capability("loggingPrefs", { 'browser':'ALL' }) +options.set_capability("goog:loggingPrefs", { 'browser':'ALL' }) browser = webdriver.Chrome(service=Service(ChromeDriverManager().install()),options=options) print("finished webdriver installation \n") browser.maximize_window() diff --git a/scripts/update_suite.rb b/scripts/update_suite.rb index cc45095ae7..b21fb4b532 100755 --- a/scripts/update_suite.rb +++ b/scripts/update_suite.rb @@ -246,6 +246,8 @@ def compare_warnings check.call warnings[idx] != "race" when "nodeadlock" check.call warnings[idx] != "deadlock" + when "nocrash", "fixpoint", "notimeout", "cram", "nocheck" + check.call true end end end @@ -324,6 +326,18 @@ def parse_tests (lines) if obj =~ /#line ([0-9]+).*$/ then i = $1.to_i - 1 end + # test annotations are stored by line, use impossible line -42 for these metaproperties + if obj =~ /NOCRASH/ then + tests[-42] = "nocrash" + elsif obj =~ /FIXPOINT/ then + tests[-42] = "fixpoint" + elsif obj =~ /NOTIMEOUT/ then + tests[-42] = "notimeout" + elsif obj =~ /CRAM/ then + tests[-42] = "cram" + elsif obj =~ /NOCHECK/ then + tests[-42] = "nocheck" + end next if obj =~ /^\s*\/\// || obj =~ /^\s*\/\*([^*]|\*+[^*\/])*\*\/$/ todo << i if obj =~ /TODO|SKIP/ tests_line[i] = obj @@ -356,6 +370,7 @@ def parse_tests (lines) end end case lines[0] + # test annotations are stored by line, use impossible line -1 for these whole-program properties when /NONTERM/ tests[-1] = "nonterm" when /TERM/ @@ -364,6 +379,10 @@ def parse_tests (lines) if lines[0] =~ /TODO/ then todo << -1 end + if tests.empty? then + puts "No automatic checks in #{@id} (maybe NOCRASH/FIXPOINT/NOTIMEOUT/CRAM?)" + exit 1 + end Tests.new(self, tests, tests_line, todo) end diff --git a/src/analyses/abortUnless.ml b/src/analyses/abortUnless.ml index 88937d027a..34d5b1a89b 100644 --- a/src/analyses/abortUnless.ml +++ b/src/analyses/abortUnless.ml @@ -13,7 +13,8 @@ struct module D = BoolDomain.MustBool module C = Printable.Unit - let context _ _ = () + let context ctx _ _ = () + let startcontext () = () (* transfer functions *) let assign ctx (lval:lval) (rval:exp) : D.t = diff --git a/src/analyses/accessAnalysis.ml b/src/analyses/accessAnalysis.ml index 24cb7399bc..bf1892fdf0 100644 --- a/src/analyses/accessAnalysis.ml +++ b/src/analyses/accessAnalysis.ml @@ -14,7 +14,7 @@ struct let name () = "access" module D = Lattice.Unit - module C = Printable.Unit + include Analyses.ValueContexts(D) module V = struct @@ -52,11 +52,12 @@ struct ); if M.tracing then M.traceu "access" "access_one_top" + (** We just lift start state, global and dependency functions: *) let startstate v = () let threadenter ctx ~multiple lval f args = [()] let exitstate v = () - let context fd d = () + let context ctx fd d = () (** Transfer functions: *) diff --git a/src/analyses/activeSetjmp.ml b/src/analyses/activeSetjmp.ml index be13489993..69db900d4c 100644 --- a/src/analyses/activeSetjmp.ml +++ b/src/analyses/activeSetjmp.ml @@ -10,7 +10,7 @@ struct let name () = "activeSetjmp" module D = JmpBufDomain.JmpBufSet - module C = JmpBufDomain.JmpBufSet + include Analyses.ValueContexts(D) module P = IdentityP (D) let combine_env ctx (lval:lval option) fexp (f:fundec) (args:exp list) fc (au:D.t) (f_ask:Queries.ask): D.t = diff --git a/src/analyses/apron/relationAnalysis.apron.ml b/src/analyses/apron/relationAnalysis.apron.ml index fa220552f1..df3cf545c5 100644 --- a/src/analyses/apron/relationAnalysis.apron.ml +++ b/src/analyses/apron/relationAnalysis.apron.ml @@ -20,7 +20,7 @@ struct module Priv = Priv (RD) module D = RelationDomain.RelComponents (RD) (Priv.D) module G = Priv.G - module C = D + include Analyses.ValueContexts(D) module V = struct include Priv.V @@ -40,7 +40,7 @@ struct (* Result map used for comparison of results for relational traces paper. *) let results = PCU.RH.create 103 - let context fd x = + let context ctx fd x = if ContextUtil.should_keep ~isAttr:GobContext ~keepOption:"ana.relation.context" ~removeAttr:"relation.no-context" ~keepAttr:"relation.context" fd then x else @@ -295,7 +295,7 @@ struct (* there should be smarter ways to do this, e.g. by keeping track of which values are written etc. ... *) (* See, e.g, Beckschulze E, Kowalewski S, Brauer J (2012) Access-based localization for octagons. Electron Notes Theor Comput Sci 287:29–40 *) (* Also, a local *) - let vname = Apron.Var.to_string var in + let vname = GobApron.Var.show var in let locals = fundec.sformals @ fundec.slocals in match List.find_opt (fun v -> VM.var_name (Local v) = vname) locals with (* TODO: optimize *) | None -> true @@ -388,11 +388,11 @@ struct let st = ctx.local in let reachable_from_args = reachable_from_args ctx args in let fundec = Node.find_fundec ctx.node in - if M.tracing then M.tracel "combine" "relation f: %a" CilType.Varinfo.pretty f.svar; - if M.tracing then M.tracel "combine" "relation formals: %a" (d_list "," CilType.Varinfo.pretty) f.sformals; - if M.tracing then M.tracel "combine" "relation args: %a" (d_list "," d_exp) args; - if M.tracing then M.tracel "combine" "relation st: %a" D.pretty st; - if M.tracing then M.tracel "combine" "relation fun_st: %a" D.pretty fun_st; + if M.tracing then M.tracel "combine-rel" "relation f: %a" CilType.Varinfo.pretty f.svar; + if M.tracing then M.tracel "combine-rel" "relation formals: %a" (d_list "," CilType.Varinfo.pretty) f.sformals; + if M.tracing then M.tracel "combine-rel" "relation args: %a" (d_list "," d_exp) args; + if M.tracing then M.tracel "combine-rel" "relation st: %a" D.pretty st; + if M.tracing then M.tracel "combine-rel" "relation fun_st: %a" D.pretty fun_st; let new_fun_rel = RD.add_vars fun_st.rel (RD.vars st.rel) in let arg_substitutes = let filter_actuals (x,e) = @@ -418,7 +418,7 @@ struct in let any_local_reachable = any_local_reachable fundec reachable_from_args in let arg_vars = f.sformals |> List.filter (RD.Tracked.varinfo_tracked) |> List.map RV.arg in - if M.tracing then M.tracel "combine" "relation remove vars: %a" (docList (fun v -> Pretty.text (Apron.Var.to_string v))) arg_vars; + if M.tracing then M.tracel "combine-rel" "relation remove vars: %a" (docList (GobApron.Var.pretty ())) arg_vars; RD.remove_vars_with new_fun_rel arg_vars; (* fine to remove arg vars that also exist in caller because unify from new_rel adds them back with proper constraints *) let tainted = f_ask.f Queries.MayBeTainted in let tainted_vars = TaintPartialContexts.conv_varset tainted in @@ -432,7 +432,7 @@ struct ) in let unify_rel = RD.unify new_rel new_fun_rel in (* TODO: unify_with *) - if M.tracing then M.tracel "combine" "relation unifying %a %a = %a" RD.pretty new_rel RD.pretty new_fun_rel RD.pretty unify_rel; + if M.tracing then M.tracel "combine-rel" "relation unifying %a %a = %a" RD.pretty new_rel RD.pretty new_fun_rel RD.pretty unify_rel; {fun_st with rel = unify_rel} let combine_assign ctx r fe f args fc fun_st (f_ask : Queries.ask) = @@ -440,8 +440,10 @@ struct if RD.Tracked.type_tracked (Cilfacade.fundec_return_type f) then ( let unify_st' = match r with | Some lv -> - assign_to_global_wrapper (Analyses.ask_of_ctx ctx) ctx.global ctx.sideg unify_st lv (fun st v -> - RD.assign_var st.rel (RV.local v) RV.return + let ask = Analyses.ask_of_ctx ctx in + assign_to_global_wrapper ask ctx.global ctx.sideg unify_st lv (fun st v -> + let rel = RD.assign_var st.rel (RV.local v) RV.return in + assert_type_bounds ask rel v (* TODO: should be done in return instead *) ) | None -> unify_st @@ -632,6 +634,15 @@ struct ) |> Enum.fold (fun acc x -> Invariant.(acc && of_exp x)) Invariant.none + let query_invariant_global ctx g = + if GobConfig.get_bool "ana.relation.invariant.global" && ctx.ask (GhostVarAvailable Multithreaded) then ( + let var = WitnessGhost.to_varinfo Multithreaded in + let inv = Priv.invariant_global (Analyses.ask_of_ctx ctx) ctx.global g in + Invariant.(of_exp (UnOp (LNot, Lval (GoblintCil.var var), GoblintCil.intType)) || inv) [@coverage off] (* bisect_ppx cannot handle redefined (||) *) + ) + else + Invariant.none + let query ctx (type a) (q: a Queries.t): a Queries.result = let open Queries in let st = ctx.local in @@ -653,6 +664,9 @@ struct let vf' x = vf (Obj.repr x) in Priv.iter_sys_vars ctx.global vq vf' | Queries.Invariant context -> query_invariant ctx context + | Queries.InvariantGlobal g -> + let g: V.t = Obj.obj g in + query_invariant_global ctx g | _ -> Result.top q @@ -680,15 +694,18 @@ struct ctx.local let event ctx e octx = + let ask = Analyses.ask_of_ctx ctx in let st = ctx.local in match e with - | Events.Lock (addr, _) when ThreadFlag.has_ever_been_multi (Analyses.ask_of_ctx ctx) -> (* TODO: is this condition sound? *) - Priv.lock (Analyses.ask_of_ctx ctx) ctx.global st addr - | Events.Unlock addr when ThreadFlag.has_ever_been_multi (Analyses.ask_of_ctx ctx) -> (* TODO: is this condition sound? *) - if addr = UnknownPtr then - M.info ~category:Unsound "Unknown mutex unlocked, relation privatization unsound"; (* TODO: something more sound *) - WideningTokens.with_local_side_tokens (fun () -> - Priv.unlock (Analyses.ask_of_ctx ctx) ctx.global ctx.sideg st addr + | Events.Lock (addr, _) when ThreadFlag.has_ever_been_multi ask -> (* TODO: is this condition sound? *) + CommonPriv.lift_lock ask (fun st m -> + Priv.lock ask ctx.global st m + ) st addr + | Events.Unlock addr when ThreadFlag.has_ever_been_multi ask -> (* TODO: is this condition sound? *) + WideningTokenLifter.with_local_side_tokens (fun () -> + CommonPriv.lift_unlock ask (fun st m -> + Priv.unlock ask ctx.global ctx.sideg st m + ) st addr ) | Events.EnterMultiThreaded -> Priv.enter_multithreaded (Analyses.ask_of_ctx ctx) ctx.global ctx.sideg st @@ -696,7 +713,7 @@ struct Priv.escape ctx.node (Analyses.ask_of_ctx ctx) ctx.global ctx.sideg st escaped | Assert exp -> assert_fn ctx exp true - | Events.Unassume {exp = e; uuids} -> + | Events.Unassume {exp = e; tokens} -> let e_orig = e in let ask = Analyses.ask_of_ctx ctx in let e = replace_deref_exps ctx.ask e in @@ -732,7 +749,7 @@ struct (* TODO: parallel write_global? *) let st = - WideningTokens.with_side_tokens (WideningTokens.TS.of_list uuids) (fun () -> + WideningTokenLifter.with_side_tokens (WideningTokenLifter.TS.of_list tokens) (fun () -> VH.fold (fun v v_in st -> (* TODO: is this sideg fine? *) write_global ask ctx.global ctx.sideg st v v_in @@ -751,7 +768,7 @@ struct let sync ctx reason = (* After the solver is finished, store the results (for later comparison) *) - if !AnalysisState.postsolving then begin + if !AnalysisState.postsolving && GobConfig.get_string "exp.relation.prec-dump" <> "" then begin let keep_local = GobConfig.get_bool "ana.relation.invariant.local" in let keep_global = GobConfig.get_bool "ana.relation.invariant.global" in @@ -766,8 +783,8 @@ struct let new_value = RD.join old_value st in PCU.RH.replace results ctx.node new_value; end; - WideningTokens.with_local_side_tokens (fun () -> - Priv.sync (Analyses.ask_of_ctx ctx) ctx.global ctx.sideg ctx.local (reason :> [`Normal | `Join | `Return | `Init | `Thread]) + WideningTokenLifter.with_local_side_tokens (fun () -> + Priv.sync (Analyses.ask_of_ctx ctx) ctx.global ctx.sideg ctx.local (reason :> [`Normal | `Join | `JoinCall of CilType.Fundec.t | `Return | `Init | `Thread]) ) let init marshal = diff --git a/src/analyses/apron/relationPriv.apron.ml b/src/analyses/apron/relationPriv.apron.ml index 1a91b4d39a..257bec24d8 100644 --- a/src/analyses/apron/relationPriv.apron.ml +++ b/src/analyses/apron/relationPriv.apron.ml @@ -33,10 +33,10 @@ module type S = the state when following conditional guards. *) val write_global: ?invariant:bool -> Q.ask -> (V.t -> G.t) -> (V.t -> G.t -> unit) -> relation_components_t -> varinfo -> varinfo -> relation_components_t - val lock: Q.ask -> (V.t -> G.t) -> relation_components_t -> LockDomain.Addr.t -> relation_components_t - val unlock: Q.ask -> (V.t -> G.t) -> (V.t -> G.t -> unit) -> relation_components_t -> LockDomain.Addr.t -> relation_components_t + val lock: Q.ask -> (V.t -> G.t) -> relation_components_t -> LockDomain.MustLock.t -> relation_components_t + val unlock: Q.ask -> (V.t -> G.t) -> (V.t -> G.t -> unit) -> relation_components_t -> LockDomain.MustLock.t -> relation_components_t - val sync: Q.ask -> (V.t -> G.t) -> (V.t -> G.t -> unit) -> relation_components_t -> [`Normal | `Join | `Return | `Init | `Thread] -> relation_components_t + val sync: Q.ask -> (V.t -> G.t) -> (V.t -> G.t -> unit) -> relation_components_t -> [`Normal | `Join | `JoinCall of CilType.Fundec.t | `Return | `Init | `Thread] -> relation_components_t val escape: Node.t -> Q.ask -> (V.t -> G.t) -> (V.t -> G.t -> unit) -> relation_components_t -> EscapeDomain.EscapedVars.t -> relation_components_t val enter_multithreaded: Q.ask -> (V.t -> G.t) -> (V.t -> G.t -> unit) -> relation_components_t -> relation_components_t @@ -46,6 +46,9 @@ module type S = val thread_return: Q.ask -> (V.t -> G.t) -> (V.t -> G.t -> unit) -> ThreadIdDomain.Thread.t -> relation_components_t -> relation_components_t val iter_sys_vars: (V.t -> G.t) -> VarQuery.t -> V.t VarQuery.f -> unit (** [Queries.IterSysVars] for apron. *) + val invariant_global: Q.ask -> (V.t -> G.t) -> V.t -> Invariant.t + (** Returns flow-insensitive invariant for global unknown. *) + val invariant_vars: Q.ask -> (V.t -> G.t) -> relation_components_t -> varinfo list (** Returns global variables which are privatized. *) @@ -96,8 +99,7 @@ struct { st with rel = rel_local } let sync (ask: Q.ask) getg sideg (st: relation_components_t) reason = - match reason with - | `Join -> + let branched_sync () = if ask.f (Q.MustBeSingleThreaded {since_start = true}) then st else @@ -110,6 +112,14 @@ struct ) in {st with rel = rel_local} + in + match reason with + | `Join when ConfCheck.branched_thread_creation () -> + branched_sync () + | `JoinCall _ when ConfCheck.branched_thread_creation () -> + branched_sync () + | `Join + | `JoinCall _ | `Normal | `Init | `Thread @@ -130,6 +140,7 @@ struct {rel = RD.top (); priv = startstate ()} let iter_sys_vars getg vq vf = () + let invariant_global ask getg g = Invariant.none let invariant_vars ask getg st = [] let init () = () @@ -336,18 +347,9 @@ struct 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: relation_components_t) reason = - match reason with - | `Return -> (* required for thread return *) - (* TODO: implement? *) - begin match ThreadId.get_current ask with - | `Lifted x (* when CPA.mem x st.cpa *) -> - st - | _ -> - st - end - | `Join -> - if (ask.f (Q.MustBeSingleThreaded { since_start= true })) then + let sync (ask:Q.ask) getg sideg (st: relation_components_t) reason = + let branched_sync () = + if ask.f (Q.MustBeSingleThreaded { since_start= true }) then st else (* must be like enter_multithreaded *) @@ -375,6 +377,22 @@ struct let rel_local' = RD.meet rel_local (getg ()) in {st with rel = rel_local'} *) st + in + match reason with + | `Return -> (* required for thread return *) + (* TODO: implement? *) + begin match ThreadId.get_current ask with + | `Lifted x (* when CPA.mem x st.cpa *) -> + st + | _ -> + st + end + | `Join when ConfCheck.branched_thread_creation () -> + branched_sync () + | `JoinCall f when ConfCheck.branched_thread_creation_at_call ask f -> + branched_sync () + | `Join + | `JoinCall _ | `Normal | `Init | `Thread -> @@ -410,6 +428,7 @@ struct {rel = getg (); priv = startstate ()} let iter_sys_vars getg vq vf = () (* TODO: or report singleton global for any Global query? *) + let invariant_global ask getg g = Invariant.none let invariant_vars ask getg st = protected_vars ask (* TODO: is this right? *) let finalize () = () @@ -469,7 +488,7 @@ struct let startstate () = () - let atomic_mutex = LockDomain.Addr.of_var LibraryFunctions.verifier_atomic_var + let atomic_mutex = LockDomain.MustLock.of_var LibraryFunctions.verifier_atomic_var let get_m_with_mutex_inits ask getg m = let get_m = getg (V.mutex m) in @@ -489,7 +508,7 @@ struct in let get_mutex_inits = getg V.mutex_inits in let get_mutex_inits' = RD.keep_vars get_mutex_inits [g_var] in - if not (RD.mem_var get_mutex_inits' g_var) then (* TODO: is this just a workaround for an escape bug? https://github.com/goblint/analyzer/pull/1354/files#r1498882657 *) + if RD.mem_var get_mutex_global_g g_var && not (RD.mem_var get_mutex_inits' g_var) then (* TODO: is this just a workaround for an escape bug? https://github.com/goblint/analyzer/pull/1354/files#r1498882657 *) (* This is an escaped variable whose value was never side-effected to get_mutex_inits' *) get_mutex_global_g else @@ -575,9 +594,9 @@ struct let write_escape = write_global_internal ~skip_meet:true let lock ask getg (st: relation_components_t) m = - let atomic = Param.handle_atomic && LockDomain.Addr.equal m (atomic_mutex) in + let atomic = Param.handle_atomic && LockDomain.MustLock.equal m atomic_mutex in (* TODO: somehow actually unneeded here? *) - if not atomic && Locksets.(not (Lockset.mem m (current_lockset ask))) then ( + if not atomic && Locksets.(not (MustLockset.mem m (current_lockset ask))) then ( let rel = st.rel in let get_m = get_m_with_mutex_inits ask getg m in (* Additionally filter get_m in case it contains variables it no longer protects. E.g. in 36/22. *) @@ -590,7 +609,7 @@ struct st (* sound w.r.t. recursive lock *) let unlock ask getg sideg (st: relation_components_t) m: relation_components_t = - let atomic = Param.handle_atomic && LockDomain.Addr.equal m (atomic_mutex) in + let atomic = Param.handle_atomic && LockDomain.MustLock.equal m atomic_mutex in let rel = st.rel in if not atomic then ( let rel_side = keep_only_protected_globals ask m rel in @@ -624,18 +643,9 @@ struct 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: relation_components_t) reason = - match reason with - | `Return -> (* required for thread return *) - (* TODO: implement? *) - begin match ThreadId.get_current ask with - | `Lifted x (* when CPA.mem x st.cpa *) -> - st - | _ -> - st - end - | `Join -> - if (ask.f (Q.MustBeSingleThreaded {since_start = true})) then + let sync (ask:Q.ask) getg sideg (st: relation_components_t) reason = + let branched_sync () = + if ask.f (Q.MustBeSingleThreaded {since_start = true}) then st else let rel = st.rel in @@ -657,6 +667,22 @@ struct ) in {st with rel = rel_local} + in + match reason with + | `Return -> (* required for thread return *) + (* TODO: implement? *) + begin match ThreadId.get_current ask with + | `Lifted x (* when CPA.mem x st.cpa *) -> + st + | _ -> + st + end + | `Join when ConfCheck.branched_thread_creation () -> + branched_sync () + | `JoinCall f when ConfCheck.branched_thread_creation_at_call ask f -> + branched_sync () + | `Join + | `JoinCall _ | `Normal | `Init | `Thread -> @@ -687,6 +713,41 @@ struct let init () = () let finalize () = () + + let invariant_global (ask: Q.ask) (getg: V.t -> G.t): V.t -> Invariant.t = function + | `Left m' -> (* mutex *) + let atomic = LockDomain.MustLock.equal m' (LockDomain.MustLock.of_var LibraryFunctions.verifier_atomic_var) in + if atomic || ask.f (GhostVarAvailable (Locked m')) then ( + (* filters like query_invariant *) + let one_var = GobConfig.get_bool "ana.relation.invariant.one-var" in + let exact = GobConfig.get_bool "witness.invariant.exact" in + + let rel = keep_only_protected_globals ask m' (get_m_with_mutex_inits ask getg m') in (* Could be more precise if mutex_inits invariant is added by disjunction instead of joining abstract values. *) + let inv = + RD.invariant rel + |> List.enum + |> Enum.filter_map (fun (lincons1: Apron.Lincons1.t) -> + (* filter one-vars and exact *) + (* TODO: exact filtering doesn't really work with octagon because it returns two SUPEQ constraints instead *) + if (one_var || GobApron.Lincons1.num_vars lincons1 >= 2) && (exact || Apron.Lincons1.get_typ lincons1 <> EQ) then + RD.cil_exp_of_lincons1 lincons1 + |> Option.filter (fun exp -> not (InvariantCil.exp_contains_tmp exp)) + else + None + ) + |> Enum.fold (fun acc x -> Invariant.(acc && of_exp x)) Invariant.none + in + if atomic then + inv + else ( + let var = WitnessGhost.to_varinfo (Locked m') in + Invariant.(of_exp (Lval (GoblintCil.var var)) || inv) [@coverage off] (* bisect_ppx cannot handle redefined (||) *) + ) + ) + else + Invariant.none + | g -> (* global *) + Invariant.none (* Could output unprotected one-variable (so non-relational) invariants, but probably not very useful. [BasePriv] does those anyway. *) end (** May written variables. *) @@ -700,7 +761,7 @@ module type ClusterArg = functor (RD: RelationDomain.RD) -> sig module LRD: Lattice.S - val keep_only_protected_globals: Q.ask -> LockDomain.Addr.t -> LRD.t -> LRD.t + val keep_only_protected_globals: Q.ask -> LockDomain.MustLock.t -> LRD.t -> LRD.t val keep_global: varinfo -> LRD.t -> LRD.t val lock: RD.t -> LRD.t -> LRD.t -> RD.t @@ -810,7 +871,9 @@ struct let g_var = V.global g in (* normal (strong) mapping: contains only still fully protected *) let g' = VS.singleton g in - let oct = LRD.find g' octs in + (* If there is no map entry yet which contains the global, default to top rather than bot *) + (* Happens e.g. in 46/86 because of escape *) + let oct = Option.default (RD.top ()) (LRD.find_opt g' octs) in LRD.singleton g' (RD.keep_vars oct [g_var]) let lock_get_m oct local_m get_m = @@ -957,7 +1020,7 @@ struct let get_m_with_mutex_inits inits ask getg m = let get_m = get_relevant_writes ask m (G.mutex @@ getg (V.mutex m)) in - if M.tracing then M.traceli "relationpriv" "get_m_with_mutex_inits %a\n get=%a" LockDomain.Addr.pretty m LRD.pretty get_m; + if M.tracing then M.traceli "relationpriv" "get_m_with_mutex_inits %a\n get=%a" LockDomain.MustLock.pretty m LRD.pretty get_m; let r = if not inits then get_m @@ -970,7 +1033,7 @@ struct if M.tracing then M.traceu "relationpriv" "-> %a" LRD.pretty r; r - let atomic_mutex = LockDomain.Addr.of_var LibraryFunctions.verifier_atomic_var + let atomic_mutex = LockDomain.MustLock.of_var LibraryFunctions.verifier_atomic_var let get_mutex_global_g_with_mutex_inits inits ask getg g = let get_mutex_global_g = @@ -1083,8 +1146,8 @@ struct {rel = rel_local; priv = (W.add g w,lmust,l)} (* Keep write local as if it were protected by the atomic section. *) let lock ask getg (st: relation_components_t) m = - let atomic = Param.handle_atomic && LockDomain.Addr.equal m (atomic_mutex) in - if not atomic && Locksets.(not (Lockset.mem m (current_lockset ask))) then ( + let atomic = Param.handle_atomic && LockDomain.MustLock.equal m atomic_mutex in + if not atomic && Locksets.(not (MustLockset.mem m (current_lockset ask))) then ( let rel = st.rel in let _,lmust,l = st.priv in let lm = LLock.mutex m in @@ -1107,7 +1170,7 @@ struct RD.keep_filter oct protected let unlock ask getg sideg (st: relation_components_t) m: relation_components_t = - let atomic = Param.handle_atomic && LockDomain.Addr.equal m (atomic_mutex) in + let atomic = Param.handle_atomic && LockDomain.MustLock.equal m atomic_mutex in let rel = st.rel in let w,lmust,l = st.priv in if not atomic then ( @@ -1187,10 +1250,8 @@ struct st let sync (ask:Q.ask) getg sideg (st: relation_components_t) reason = - match reason with - | `Return -> st (* TODO: implement? *) - | `Join -> - if (ask.f (Q.MustBeSingleThreaded {since_start = true})) then + let branched_sync () = + if ask.f (Q.MustBeSingleThreaded {since_start = true}) then st else let rel = st.rel in @@ -1204,6 +1265,15 @@ struct ) in {st with rel = rel_local} + in + match reason with + | `Return -> st (* TODO: implement? *) + | `Join when ConfCheck.branched_thread_creation () -> + branched_sync () + | `JoinCall f when ConfCheck.branched_thread_creation_at_call ask f -> + branched_sync () + | `Join + | `JoinCall _ | `Normal | `Init | `Thread -> @@ -1245,6 +1315,8 @@ struct | _ -> () let finalize () = () + + let invariant_global ask getg g = Invariant.none end module TracingPriv = functor (Priv: S) -> functor (RD: RelationDomain.RD) -> @@ -1284,7 +1356,7 @@ struct r let lock ask getg st m = - if M.tracing then M.traceli "relationpriv" "lock %a" LockDomain.Addr.pretty m; + if M.tracing then M.traceli "relationpriv" "lock %a" LockDomain.MustLock.pretty m; if M.tracing then M.trace "relationpriv" "st: %a" RelComponents.pretty st; let getg x = let r = getg x in @@ -1296,7 +1368,7 @@ struct r let unlock ask getg sideg st m = - if M.tracing then M.traceli "relationpriv" "unlock %a" LockDomain.Addr.pretty m; + if M.tracing then M.traceli "relationpriv" "unlock %a" LockDomain.MustLock.pretty m; if M.tracing then M.trace "relationpriv" "st: %a" RelComponents.pretty st; let getg x = let r = getg x in diff --git a/src/analyses/base.ml b/src/analyses/base.ml index 9f0dae4d9c..9f5370cd99 100644 --- a/src/analyses/base.ml +++ b/src/analyses/base.ml @@ -17,6 +17,7 @@ module IdxDom = ValueDomain.IndexDomain module AD = ValueDomain.AD module Addr = ValueDomain.Addr module Offs = ValueDomain.Offs +module ZeroInit = ValueDomain.ZeroInit module LF = LibraryFunctions module CArrays = ValueDomain.CArrays module PU = PrecisionUtil @@ -33,12 +34,10 @@ module MainFunctor (Priv:BasePriv.S) (RVEval:BaseDomain.ExpEvaluator with type t struct include Analyses.DefaultSpec - exception Top - module Dom = BaseDomain.DomFunctor (Priv.D) (RVEval) type t = Dom.t module D = Dom - module C = Dom + include Analyses.ValueContexts(D) (* Two global invariants: 1. Priv.V -> Priv.G -- used for Priv @@ -79,6 +78,7 @@ struct type glob_diff = (V.t * G.t) list let name () = "base" + let startstate v: store = { cpa = CPA.bot (); deps = Dep.bot (); weak = WeakUpdates.bot (); priv = Priv.startstate ()} let exitstate v: store = { cpa = CPA.bot (); deps = Dep.bot (); weak = WeakUpdates.bot (); priv = Priv.startstate ()} @@ -169,7 +169,7 @@ struct * Abstract evaluation functions **************************************************************************) - let iDtoIdx = ID.cast_to (Cilfacade.ptrdiff_ikind ()) + let iDtoIdx x = ID.cast_to (Cilfacade.ptrdiff_ikind ()) x let unop_ID = function | Neg -> ID.neg @@ -259,6 +259,8 @@ struct (* adds n to the last offset *) let rec addToOffset n (t:typ option) = function | `Index (i, `NoOffset) -> + (* Binary operations on pointer types should not generate warnings in SV-COMP *) + GobRef.wrap AnalysisState.executing_speculative_computations true @@ fun () -> (* If we have arrived at the last Offset and it is an Index, we add our integer to it *) `Index(IdxDom.add i (iDtoIdx n), `NoOffset) | `Field (f, `NoOffset) -> @@ -443,13 +445,13 @@ struct in if M.tracing then M.tracel "sync" "sync multi=%B earlyglobs=%B" multi !earlyglobs; if !earlyglobs || multi then - WideningTokens.with_local_side_tokens (fun () -> + WideningTokenLifter.with_local_side_tokens (fun () -> Priv.sync (Analyses.ask_of_ctx ctx) (priv_getg ctx.global) (priv_sideg ctx.sideg) ctx.local reason ) else ctx.local - let sync ctx reason = sync' (reason :> [`Normal | `Join | `Return | `Init | `Thread]) ctx + let sync ctx reason = sync' (reason :> [`Normal | `Join | `JoinCall of CilType.Fundec.t | `Return | `Init | `Thread]) ctx let publish_all ctx reason = ignore (sync' reason ctx) @@ -622,7 +624,7 @@ struct let drop_intervalSet = CPA.map (function Int x -> Int (ID.no_intervalSet x) | x -> x ) - let context (fd: fundec) (st: store): store = + let context ctx (fd: fundec) (st: store): store = let f keep drop_fn (st: store) = if keep then st else { st with cpa = drop_fn st.cpa} in st |> (* Here earlyglobs only drops syntactic globals from the context and does not consider e.g. escaped globals. *) @@ -1179,6 +1181,9 @@ struct not is_alloc || (is_alloc && not (ctx.ask (Queries.IsHeapVar v))) let query_invariant ctx context = + let keep_local = GobConfig.get_bool "ana.base.invariant.local" in + let keep_global = GobConfig.get_bool "ana.base.invariant.global" in + let cpa = ctx.local.BaseDomain.cpa in let ask = Analyses.ask_of_ctx ctx in @@ -1191,6 +1196,13 @@ struct in let module I = ValueDomain.ValueInvariant (Arg) in + let var_filter v = + if is_global ask v then + keep_global + else + keep_local + in + let var_invariant ?offset v = if not (InvariantCil.var_is_heap v) then I.key_invariant v ?offset (Arg.find v) @@ -1201,14 +1213,23 @@ struct if Lval.Set.is_top context.Invariant.lvals then ( if !earlyglobs || ThreadFlag.has_ever_been_multi ask then ( let cpa_invariant = - CPA.fold (fun k v a -> - if not (is_global ask k) then - Invariant.(a && var_invariant k) - else - a - ) cpa Invariant.none + if keep_local then ( + CPA.fold (fun k v a -> + if not (is_global ask k) then + Invariant.(a && var_invariant k) + else + a + ) cpa Invariant.none + ) + else + Invariant.none + in + let priv_vars = + if keep_global then + Priv.invariant_vars ask (priv_getg ctx.global) ctx.local + else + [] in - let priv_vars = Priv.invariant_vars ask (priv_getg ctx.global) ctx.local in let priv_invariant = List.fold_left (fun acc v -> Invariant.(var_invariant v && acc) @@ -1218,7 +1239,10 @@ struct ) else ( CPA.fold (fun k v a -> - Invariant.(a && var_invariant k) + if var_filter k then + Invariant.(a && var_invariant k) + else + a ) cpa Invariant.none ) ) @@ -1226,7 +1250,7 @@ struct Lval.Set.fold (fun k a -> let i = match k with - | (Var v, offset) when not (InvariantCil.var_is_heap v) -> + | (Var v, offset) when var_filter v && not (InvariantCil.var_is_heap v) -> (try I.key_invariant_lval v ~offset ~lval:k (Arg.find v) with Not_found -> Invariant.none) | _ -> Invariant.none in @@ -1241,13 +1265,23 @@ struct Invariant.none let query_invariant_global ctx g = - if GobConfig.get_bool "ana.base.invariant.enabled" && get_bool "exp.earlyglobs" then ( + if GobConfig.get_bool "ana.base.invariant.enabled" && GobConfig.get_bool "ana.base.invariant.global" then ( (* Currently these global invariants are only sound with earlyglobs enabled for both single- and multi-threaded programs. - Otherwise, the values of globals in single-threaded mode are not accounted for. *) - (* TODO: account for single-threaded values without earlyglobs. *) + Otherwise, the values of globals in single-threaded mode are not accounted for. + They are also made sound without earlyglobs using the multithreaded mode ghost variable. *) match g with | `Left g' -> (* priv *) - Priv.invariant_global (Analyses.ask_of_ctx ctx) (priv_getg ctx.global) g' + let inv = Priv.invariant_global (Analyses.ask_of_ctx ctx) (priv_getg ctx.global) g' in + if get_bool "exp.earlyglobs" then + inv + else ( + if ctx.ask (GhostVarAvailable Multithreaded) then ( + let var = WitnessGhost.to_varinfo Multithreaded in + Invariant.(of_exp (UnOp (LNot, Lval (GoblintCil.var var), GoblintCil.intType)) || inv) [@coverage off] (* bisect_ppx cannot handle redefined (||) *) + ) + else + Invariant.none + ) | `Right _ -> (* thread return *) Invariant.none ) @@ -1905,14 +1939,14 @@ struct in begin match current_val with | Bot -> (* current value is VD Bot *) - begin match Addr.to_mval (AD.choose lval_val) with - | Some (x,offs) -> + begin match AD.to_mval lval_val with + | [(x,offs)] -> let t = v.vtype in let iv = VD.bot_value ~varAttr:v.vattr t in (* correct bottom value for top level variable *) - if M.tracing then M.tracel "set" "init bot value: %a" VD.pretty iv; + if M.tracing then M.tracel "set" "init bot value (%a): %a" d_plaintype t VD.pretty iv; let nv = VD.update_offset (Queries.to_value_domain_ask (Analyses.ask_of_ctx ctx)) iv offs rval_val (Some (Lval lval)) lval t in (* do desired update to value *) set_savetop ~ctx ctx.local (AD.of_var v) lval_t nv ~lval_raw:lval ~rval_raw:rval (* set top-level variable to updated value *) - | None -> + | _ -> set_savetop ~ctx ctx.local lval_val lval_t rval_val ~lval_raw:lval ~rval_raw:rval end | _ -> @@ -2041,7 +2075,7 @@ struct List.map mpt exps ) - let invalidate ?(deep=true) ~ctx (st:store) (exps: exp list): store = + let invalidate ~(must: bool) ?(deep=true) ~ctx (st:store) (exps: exp list): store = if M.tracing && exps <> [] then M.tracel "invalidate" "Will invalidate expressions [%a]" (d_list ", " d_plainexp) exps; if exps <> [] then M.info ~category:Imprecise "Invalidating expressions: %a" (d_list ", " d_exp) exps; (* To invalidate a single address, we create a pair with its corresponding @@ -2068,7 +2102,15 @@ struct let vs = List.map (Tuple3.third) invalids' in M.tracel "invalidate" "Setting addresses [%a] to values [%a]" (d_list ", " AD.pretty) addrs (d_list ", " VD.pretty) vs ); - set_many ~ctx st invalids' + (* copied from set_many *) + let f (acc: store) ((lval:AD.t),(typ:Cil.typ),(value:value)): store = + let acc' = set ~ctx acc lval typ value in + if must then + acc' + else + D.join acc acc' + in + List.fold_left f st invalids' let make_entry ?(thread=false) (ctx:(D.t, G.t, C.t, V.t) Analyses.ctx) fundec args: D.t = @@ -2162,11 +2204,7 @@ struct in List.filter_map (create_thread ~multiple (Some (Mem id, NoOffset)) (Some ptc_arg)) start_funvars_with_unknown end - | _, _ when get_bool "sem.unknown_function.spawn" -> - (* TODO: Remove sem.unknown_function.spawn check because it is (and should be) really done in LibraryFunctions. - But here we consider all non-ThreadCrate functions also unknown, so old-style LibraryFunctions access - definitions using `Write would still spawn because they are not truly unknown functions (missing from LibraryFunctions). - Need this to not have memmove spawn in SV-COMP. *) + | _, _ -> let shallow_args = LibraryDesc.Accesses.find desc.accs { kind = Spawn; deep = false } args in let deep_args = LibraryDesc.Accesses.find desc.accs { kind = Spawn; deep = true } args in let shallow_flist = collect_invalidate ~deep:false ~ctx ctx.local shallow_args in @@ -2175,7 +2213,6 @@ struct let addrs = List.concat_map AD.to_var_may flist in if addrs <> [] then M.debug ~category:Analyzer "Spawning non-unique functions from unknown function: %a" (d_list ", " CilType.Varinfo.pretty) addrs; List.filter_map (create_thread ~multiple:true None None) addrs - | _, _ -> [] let assert_fn ctx e refine = (* make the state meet the assertion in the rest of the code *) @@ -2207,8 +2244,8 @@ struct in (* TODO: what about escaped local variables? *) (* invalidate arguments and non-static globals for unknown functions *) - let st' = invalidate ~deep:false ~ctx ctx.local shallow_addrs in - invalidate ~deep:true ~ctx st' deep_addrs + let st' = invalidate ~must:false ~deep:false ~ctx ctx.local shallow_addrs in + invalidate ~must:false ~deep:true ~ctx st' deep_addrs let check_invalid_mem_dealloc ctx special_fn ptr = let has_non_heap_var = AD.exists (function @@ -2298,7 +2335,7 @@ struct let invalidate_ret_lv st = match lv with | Some lv -> if M.tracing then M.tracel "invalidate" "Invalidating lhs %a for function call %s" d_plainlval lv f.vname; - invalidate ~deep:false ~ctx st [Cil.mkAddrOrStartOf lv] + invalidate ~must:true ~deep:false ~ctx st [Cil.mkAddrOrStartOf lv] | None -> st in let addr_type_of_exp exp = @@ -2632,26 +2669,35 @@ struct | Int n when GobOption.exists (Z.equal Z.zero) (ID.to_int n) -> st | Address ret_a -> begin match eval_rv ~ctx st id with - | Thread a when ValueDomain.Threads.is_top a -> invalidate ~ctx st [ret_var] + | Thread a when ValueDomain.Threads.is_top a -> invalidate ~must:true ~ctx st [ret_var] | Thread a -> let v = List.fold VD.join (VD.bot ()) (List.map (fun x -> G.thread (ctx.global (V.thread x))) (ValueDomain.Threads.elements a)) in (* TODO: is this type right? *) set ~ctx st ret_a (Cilfacade.typeOf ret_var) v - | _ -> invalidate ~ctx st [ret_var] + | _ -> invalidate ~must:true ~ctx st [ret_var] end - | _ -> invalidate ~ctx st [ret_var] + | _ -> invalidate ~must:true ~ctx st [ret_var] in let st' = invalidate_ret_lv st' in Priv.thread_join (Analyses.ask_of_ctx ctx) (priv_getg ctx.global) id st' | Unknown, "__goblint_assume_join" -> let id = List.hd args in Priv.thread_join ~force:true (Analyses.ask_of_ctx ctx) (priv_getg ctx.global) id st + | ThreadSelf, _ -> + begin match lv, ThreadId.get_current (Analyses.ask_of_ctx ctx) with + | Some lv, `Lifted tid -> + set ~ctx st (eval_lv ~ctx st lv) (Cilfacade.typeOfLval lv) (Thread (ValueDomain.Threads.singleton tid)) + | Some lv, _ -> + invalidate_ret_lv st + | None, _ -> + st + end | Alloca size, _ -> begin match lv with | Some lv -> let heap_var = AD.of_var (heap_var true ctx) in (* ignore @@ printf "alloca will allocate %a bytes\n" ID.pretty (eval_int ~ctx size); *) - set_many ~ctx st [(heap_var, TVoid [], Blob (VD.bot (), eval_int ~ctx st size, true)); + set_many ~ctx st [(heap_var, TVoid [], Blob (VD.bot (), eval_int ~ctx st size, ZeroInit.malloc)); (eval_lv ~ctx st lv, (Cilfacade.typeOfLval lv), Address heap_var)] | _ -> st end @@ -2664,7 +2710,7 @@ struct else AD.of_var (heap_var false ctx) in (* ignore @@ printf "malloc will allocate %a bytes\n" ID.pretty (eval_int ~ctx size); *) - set_many ~ctx st [(heap_var, TVoid [], Blob (VD.bot (), eval_int ~ctx st size, true)); + set_many ~ctx st [(heap_var, TVoid [], Blob (VD.bot (), eval_int ~ctx st size, ZeroInit.malloc)); (eval_lv ~ctx st lv, (Cilfacade.typeOfLval lv), Address heap_var)] | _ -> st end @@ -2681,7 +2727,7 @@ struct let countval = eval_int ~ctx st n in if ID.to_int countval = Some Z.one then ( set_many ~ctx st [ - (add_null (AD.of_var heap_var), TVoid [], Blob (VD.bot (), sizeval, false)); + (add_null (AD.of_var heap_var), TVoid [], Blob (VD.bot (), sizeval, ZeroInit.calloc)); (eval_lv ~ctx st lv, (Cilfacade.typeOfLval lv), Address (add_null (AD.of_var heap_var))) ] ) @@ -2689,7 +2735,7 @@ struct let blobsize = ID.mul (ID.cast_to ik @@ sizeval) (ID.cast_to ik @@ countval) in (* the memory that was allocated by calloc is set to bottom, but we keep track that it originated from calloc, so when bottom is read from memory allocated by calloc it is turned to zero *) set_many ~ctx st [ - (add_null (AD.of_var heap_var), TVoid [], Array (CArrays.make (IdxDom.of_int (Cilfacade.ptrdiff_ikind ()) Z.one) (Blob (VD.bot (), blobsize, false)))); + (add_null (AD.of_var heap_var), TVoid [], Array (CArrays.make (IdxDom.of_int (Cilfacade.ptrdiff_ikind ()) Z.one) (Blob (VD.bot (), blobsize, ZeroInit.calloc)))); (eval_lv ~ctx st lv, (Cilfacade.typeOfLval lv), Address (add_null (AD.of_mval (heap_var, `Index (IdxDom.of_int (Cilfacade.ptrdiff_ikind ()) Z.zero, `NoOffset))))) ] ) @@ -2712,7 +2758,7 @@ struct let p_addr' = AD.remove NullPtr p_addr in (* realloc with NULL is same as malloc, remove to avoid unknown value from NullPtr access *) let p_addr_get = get ~ctx st p_addr' None in (* implicitly includes join of malloc value (VD.bot) *) let size_int = eval_int ~ctx st size in - let heap_val:value = Blob (p_addr_get, size_int, true) in (* copy old contents with new size *) + let heap_val:value = Blob (p_addr_get, size_int, ZeroInit.malloc) in (* copy old contents with new size *) let heap_addr = AD.of_var (heap_var false ctx) in let heap_addr' = if get_bool "sem.malloc.fail" then @@ -3046,7 +3092,7 @@ struct (* Perform actual [set]-s with final unassumed values. This invokes [Priv.write_global], which was suppressed above. *) let e_d' = - WideningTokens.with_side_tokens (WideningTokens.TS.of_list uuids) (fun () -> + WideningTokenLifter.with_side_tokens (WideningTokenLifter.TS.of_list uuids) (fun () -> CPA.fold (fun x v acc -> let addr: AD.t = AD.of_mval (x, `NoOffset) in set ~ctx ~invariant:false acc addr x.vtype v @@ -3061,12 +3107,14 @@ struct match e with | Events.Lock (addr, _) when ThreadFlag.has_ever_been_multi ask -> (* TODO: is this condition sound? *) if M.tracing then M.tracel "priv" "LOCK EVENT %a" LockDomain.Addr.pretty addr; - Priv.lock ask (priv_getg ctx.global) st addr + CommonPriv.lift_lock ask (fun st m -> + Priv.lock ask (priv_getg ctx.global) st m + ) st addr | Events.Unlock addr when ThreadFlag.has_ever_been_multi ask -> (* TODO: is this condition sound? *) - if addr = UnknownPtr then - M.info ~category:Unsound "Unknown mutex unlocked, base privatization unsound"; (* TODO: something more sound *) - WideningTokens.with_local_side_tokens (fun () -> - Priv.unlock ask (priv_getg ctx.global) (priv_sideg ctx.sideg) st addr + WideningTokenLifter.with_local_side_tokens (fun () -> + CommonPriv.lift_unlock ask (fun st m -> + Priv.unlock ask (priv_getg ctx.global) (priv_sideg ctx.sideg) st m + ) st addr ) | Events.Escape escaped -> Priv.escape ask (priv_getg ctx.global) (priv_sideg ctx.sideg) st escaped @@ -3077,8 +3125,8 @@ struct set ~ctx ctx.local (eval_lv ~ctx ctx.local lval) (Cilfacade.typeOfLval lval) (Thread (ValueDomain.Threads.singleton tid)) | Events.Assert exp -> assert_fn ctx exp true - | Events.Unassume {exp; uuids} -> - Timing.wrap "base unassume" (unassume ctx exp) uuids + | Events.Unassume {exp; tokens} -> + Timing.wrap "base unassume" (unassume ctx exp) tokens | Events.Longjmped {lval} -> begin match lval with | Some lval -> diff --git a/src/analyses/baseInvariant.ml b/src/analyses/baseInvariant.ml index 51a27e19f8..52f0888d3f 100644 --- a/src/analyses/baseInvariant.ml +++ b/src/analyses/baseInvariant.ml @@ -777,33 +777,37 @@ struct | _ -> assert false end | Const _ , _ -> st (* nothing to do *) - | CastE ((TFloat (_, _)), e), Float c -> - (match unrollType (Cilfacade.typeOf e), FD.get_fkind c with - | TFloat (FLongDouble as fk, _), FFloat - | TFloat (FDouble as fk, _), FFloat - | TFloat (FLongDouble as fk, _), FDouble - | TFloat (fk, _), FLongDouble - | TFloat (FDouble as fk, _), FDouble - | TFloat (FFloat as fk, _), FFloat -> inv_exp (Float (FD.cast_to fk c)) e st - | _ -> fallback (fun () -> Pretty.text "CastE: incompatible types") st) - | CastE ((TInt (ik, _)) as t, e), Int c - | CastE ((TEnum ({ekind = ik; _ }, _)) as t, e), Int c -> (* Can only meet the t part of an Lval in e with c (unless we meet with all overflow possibilities)! Since there is no good way to do this, we only continue if e has no values outside of t. *) - (match eval e st with - | Int i -> - (match unrollType (Cilfacade.typeOf e) with - | (TInt(ik_e, _) as t') - | (TEnum ({ekind = ik_e; _ }, _) as t') -> - if VD.is_dynamically_safe_cast t t' (Int i) then - (* let c' = ID.cast_to ik_e c in *) - (* Suppressing overflow warnings as this is not a computation that comes from the program *) - let res_range = (ID.cast_to ~suppress_ovwarn:true ik (ID.top_of ik_e)) in - let c' = ID.cast_to ik_e (ID.meet c res_range) in (* TODO: cast without overflow, is this right for normal invariant? *) - if M.tracing then M.tracel "inv" "cast: %a from %a to %a: i = %a; cast c = %a to %a = %a" d_exp e d_ikind ik_e d_ikind ik ID.pretty i ID.pretty c d_ikind ik_e ID.pretty c'; - inv_exp (Int c') e st - else - fallback (fun () -> Pretty.dprintf "CastE: %a evaluates to %a which is bigger than the type it is cast to which is %a" d_plainexp e ID.pretty i CilType.Typ.pretty t) st - | x -> fallback (fun () -> Pretty.dprintf "CastE: e did evaluate to Int, but the type did not match %a" CilType.Typ.pretty t) st) - | v -> fallback (fun () -> Pretty.dprintf "CastE: e did not evaluate to Int, but %a" VD.pretty v) st) + | CastE (t, e), c_typed -> + begin match Cil.unrollType t, c_typed with + | TFloat (_, _), Float c -> + (match unrollType (Cilfacade.typeOf e), FD.get_fkind c with + | TFloat (FLongDouble as fk, _), FFloat + | TFloat (FDouble as fk, _), FFloat + | TFloat (FLongDouble as fk, _), FDouble + | TFloat (fk, _), FLongDouble + | TFloat (FDouble as fk, _), FDouble + | TFloat (FFloat as fk, _), FFloat -> inv_exp (Float (FD.cast_to fk c)) e st + | _ -> fallback (fun () -> Pretty.text "CastE: incompatible types") st) + | (TInt (ik, _) as t), Int c + | (TEnum ({ekind = ik; _ }, _) as t), Int c -> (* Can only meet the t part of an Lval in e with c (unless we meet with all overflow possibilities)! Since there is no good way to do this, we only continue if e has no values outside of t. *) + (match eval e st with + | Int i -> + (match unrollType (Cilfacade.typeOf e) with + | (TInt(ik_e, _) as t') + | (TEnum ({ekind = ik_e; _ }, _) as t') -> + if VD.is_dynamically_safe_cast t t' (Int i) then + (* let c' = ID.cast_to ik_e c in *) + (* Suppressing overflow warnings as this is not a computation that comes from the program *) + let res_range = (ID.cast_to ~suppress_ovwarn:true ik (ID.top_of ik_e)) in + let c' = ID.cast_to ik_e (ID.meet c res_range) in (* TODO: cast without overflow, is this right for normal invariant? *) + if M.tracing then M.tracel "inv" "cast: %a from %a to %a: i = %a; cast c = %a to %a = %a" d_exp e d_ikind ik_e d_ikind ik ID.pretty i ID.pretty c d_ikind ik_e ID.pretty c'; + inv_exp (Int c') e st + else + fallback (fun () -> Pretty.dprintf "CastE: %a evaluates to %a which is bigger than the type it is cast to which is %a" d_plainexp e ID.pretty i CilType.Typ.pretty t) st + | x -> fallback (fun () -> Pretty.dprintf "CastE: e did evaluate to Int, but the type did not match %a" CilType.Typ.pretty t) st) + | v -> fallback (fun () -> Pretty.dprintf "CastE: e did not evaluate to Int, but %a" VD.pretty v) st) + | _, _ -> fallback (fun () -> Pretty.dprintf "CastE: %a not implemented" d_plainexp (CastE (t, e))) st + end | e, _ -> fallback (fun () -> Pretty.dprintf "%a not implemented" d_plainexp e) st in if eval_bool exp st = Some (not tv) then contra st (* we already know that the branch is dead *) diff --git a/src/analyses/basePriv.ml b/src/analyses/basePriv.ml index 527d11b1de..3afd758daa 100644 --- a/src/analyses/basePriv.ml +++ b/src/analyses/basePriv.ml @@ -28,10 +28,10 @@ sig * the state when following conditional guards. *) val write_global: ?invariant:bool -> Q.ask -> (V.t -> G.t) -> (V.t -> G.t -> unit) -> BaseComponents (D).t -> varinfo -> VD.t -> BaseComponents (D).t - val lock: Q.ask -> (V.t -> G.t) -> BaseComponents (D).t -> LockDomain.Addr.t -> BaseComponents (D).t - val unlock: Q.ask -> (V.t -> G.t) -> (V.t -> G.t -> unit) -> BaseComponents (D).t -> LockDomain.Addr.t -> BaseComponents (D).t + val lock: Q.ask -> (V.t -> G.t) -> BaseComponents (D).t -> LockDomain.MustLock.t -> BaseComponents (D).t + val unlock: Q.ask -> (V.t -> G.t) -> (V.t -> G.t -> unit) -> BaseComponents (D).t -> LockDomain.MustLock.t -> BaseComponents (D).t - val sync: Q.ask -> (V.t -> G.t) -> (V.t -> G.t -> unit) -> BaseComponents (D).t -> [`Normal | `Join | `Return | `Init | `Thread] -> BaseComponents (D).t + val sync: Q.ask -> (V.t -> G.t) -> (V.t -> G.t -> unit) -> BaseComponents (D).t -> [`Normal | `Join | `JoinCall of CilType.Fundec.t | `Return | `Init | `Thread] -> BaseComponents (D).t 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 @@ -191,7 +191,7 @@ struct let get_mutex_inits = getg V.mutex_inits in let is_in_Gm x _ = is_protected_by ask m x in let get_mutex_inits' = CPA.filter is_in_Gm get_mutex_inits in - if M.tracing then M.tracel "priv" "get_m_with_mutex_inits %a:\n get_m: %a\n get_mutex_inits: %a\n get_mutex_inits': %a" LockDomain.Addr.pretty m CPA.pretty get_m CPA.pretty get_mutex_inits CPA.pretty get_mutex_inits'; + if M.tracing then M.tracel "priv" "get_m_with_mutex_inits %a:\n get_m: %a\n get_mutex_inits: %a\n get_mutex_inits': %a" LockDomain.MustLock.pretty m CPA.pretty get_m CPA.pretty get_mutex_inits CPA.pretty get_mutex_inits'; CPA.join get_m get_mutex_inits' (** [get_m_with_mutex_inits] optimized for implementation-specialized [read_global]. *) @@ -250,7 +250,7 @@ struct let invariant_global ask getg = function | `Right g' -> (* global *) - ValueDomain.invariant_global (read_unprotected_global getg) g' + ValueDomain.invariant_global (read_unprotected_global getg) g' (* Could be more precise if mutex_inits invariant is added by disjunction instead of joining abstract values. *) | _ -> (* mutex *) Invariant.none @@ -281,7 +281,7 @@ struct cpa' *) let lock ask getg (st: BaseComponents (D).t) m = - if Locksets.(not (Lockset.mem m (current_lockset ask))) then ( + if Locksets.(not (MustLockset.mem m (current_lockset ask))) then ( let get_m = get_m_with_mutex_inits ask getg m in (* Really we want is_unprotected, but pthread_cond_wait emits unlock-lock events, where our (necessary) original context still has the mutex, @@ -292,7 +292,7 @@ struct No other privatization uses is_unprotected, so this hack is only needed here. *) let is_in_V x _ = is_protected_by ask m x && is_unprotected_without ask x m in let cpa' = CPA.filter is_in_V get_m in - if M.tracing then M.tracel "priv" "PerMutexOplusPriv.lock m=%a cpa'=%a" LockDomain.Addr.pretty m CPA.pretty cpa'; + if M.tracing then M.tracel "priv" "PerMutexOplusPriv.lock m=%a cpa'=%a" LockDomain.MustLock.pretty m CPA.pretty cpa'; {st with cpa = CPA.fold CPA.add cpa' st.cpa} ) else @@ -301,13 +301,13 @@ struct let unlock ask getg sideg (st: BaseComponents (D).t) m = let is_in_Gm x _ = is_protected_by ask m x in let side_m_cpa = CPA.filter is_in_Gm st.cpa in - if M.tracing then M.tracel "priv" "PerMutexOplusPriv.unlock m=%a side_m_cpa=%a" LockDomain.Addr.pretty m CPA.pretty side_m_cpa; + if M.tracing then M.tracel "priv" "PerMutexOplusPriv.unlock m=%a side_m_cpa=%a" LockDomain.MustLock.pretty m CPA.pretty side_m_cpa; sideg (V.mutex m) side_m_cpa; st let sync ask getg sideg (st: BaseComponents (D).t) reason = - match reason with - | `Join -> (* required for branched thread creation *) + let branched_sync () = + (* required for branched thread creation *) let global_cpa = CPA.filter (fun x _ -> is_global ask x && is_unprotected ask x) st.cpa in sideg V.mutex_inits global_cpa; (* must be like enter_multithreaded *) (* TODO: this makes mutex-oplus less precise in 28-race_reach/10-ptrmunge_racefree and 28-race_reach/trylock2_racefree, why? *) @@ -318,6 +318,14 @@ struct sideg (V.global x) (CPA.singleton x v) ) st.cpa; st + in + match reason with + | `Join when ConfCheck.branched_thread_creation () -> + branched_sync () + | `JoinCall f when ConfCheck.branched_thread_creation_at_call ask f -> + branched_sync () + | `Join + | `JoinCall _ | `Return | `Normal | `Init @@ -331,6 +339,31 @@ module PerMutexMeetPrivBase = struct include PerMutexPrivBase + let invariant_global (ask: Q.ask) getg = function + | `Left m' -> (* mutex *) + let atomic = LockDomain.MustLock.equal m' (LockDomain.MustLock.of_var LibraryFunctions.verifier_atomic_var) in + if atomic || ask.f (GhostVarAvailable (Locked m')) then ( + let cpa = get_m_with_mutex_inits ask getg m' in (* Could be more precise if mutex_inits invariant is added by disjunction instead of joining abstract values. *) + let inv = CPA.fold (fun v _ acc -> + if ask.f (MustBeProtectedBy {mutex = m'; global = v; write = true; protection = Strong}) then + let inv = ValueDomain.invariant_global (fun g -> CPA.find g cpa) v in + Invariant.(acc && inv) + else + acc + ) cpa Invariant.none + in + if atomic then + inv + else ( + let var = WitnessGhost.to_varinfo (Locked m') in + Invariant.(of_exp (Lval (GoblintCil.var var)) || inv) [@coverage off] (* bisect_ppx cannot handle redefined (||) *) + ) + ) + else + Invariant.none + | g -> (* global *) + invariant_global ask getg g + let invariant_vars ask getg (st: _ BaseDomain.basecomponents_t) = (* Mutex-meet local states contain precisely the protected global variables, so we can do fewer queries than {!protected_vars}. *) @@ -366,8 +399,11 @@ struct in if not invariant then ( if M.tracing then M.tracel "priv" "WRITE GLOBAL SIDE %a = %a" CilType.Varinfo.pretty x VD.pretty v; - sideg (V.global x) (CPA.singleton x v) - (* Unlock after invariant will still side effect refined value (if protected) from CPA, because cannot distinguish from non-invariant write. *) + let side_cpa = CPA.singleton x v in + sideg (V.global x) side_cpa; + if !earlyglobs && not (ThreadFlag.is_currently_multi ask) then + sideg V.mutex_inits side_cpa (* Also side to inits because with earlyglobs enter_multithreaded does not side everything to inits *) + (* Unlock after invariant will still side effect refined value (if protected) from CPA, because cannot distinguish from non-invariant write. *) ); {st with cpa = cpa'} (* let write_global ask getg sideg cpa x v = @@ -376,14 +412,14 @@ struct cpa' *) let lock (ask: Queries.ask) getg (st: BaseComponents (D).t) m = - if Locksets.(not (Lockset.mem m (current_lockset ask))) then ( + if Locksets.(not (MustLockset.mem m (current_lockset ask))) then ( let get_m = get_m_with_mutex_inits ask getg m in (* Additionally filter get_m in case it contains variables it no longer protects. *) let is_in_Gm x _ = is_protected_by ask m x in let get_m = CPA.filter is_in_Gm get_m in let long_meet m1 m2 = CPA.long_map2 VD.meet m1 m2 in let meet = long_meet st.cpa get_m in - if M.tracing then M.tracel "priv" "LOCK %a:\n get_m: %a\n meet: %a" LockDomain.Addr.pretty m CPA.pretty get_m CPA.pretty meet; + if M.tracing then M.tracel "priv" "LOCK %a:\n get_m: %a\n meet: %a" LockDomain.MustLock.pretty m CPA.pretty get_m CPA.pretty meet; {st with cpa = meet} ) else @@ -403,8 +439,8 @@ struct {st with cpa = cpa'} let sync ask getg sideg (st: BaseComponents (D).t) reason = - match reason with - | `Join -> (* required for branched thread creation *) + let branched_sync () = + (* required for branched thread creation *) let global_cpa = CPA.filter (fun x _ -> is_global ask x && is_unprotected ask x) st.cpa in sideg V.mutex_inits global_cpa; (* must be like enter_multithreaded *) @@ -421,6 +457,14 @@ struct ) st.cpa st.cpa in {st with cpa = cpa'} + in + match reason with + | `Join when ConfCheck.branched_thread_creation () -> + branched_sync () + | `JoinCall f when ConfCheck.branched_thread_creation_at_call ask f -> + branched_sync () + | `Join + | `JoinCall _ | `Return | `Normal | `Init @@ -521,7 +565,7 @@ struct {st with cpa = cpa'; priv = (W.add x w,LMust.add lm lmust,l')} let lock (ask: Queries.ask) getg (st: BaseComponents (D).t) m = - if Locksets.(not (Lockset.mem m (current_lockset ask))) then ( + if Locksets.(not (MustLockset.mem m (current_lockset ask))) then ( let _,lmust,l = st.priv in let lm = LLock.mutex m in let get_m = get_m_with_mutex_inits (not (LMust.mem lm lmust)) ask getg m in @@ -660,7 +704,7 @@ struct let invariant_global ask getg = function | `Middle g -> (* global *) - ValueDomain.invariant_global (read_unprotected_global getg) g + ValueDomain.invariant_global (read_unprotected_global getg) g (* Could be more precise if mutex_inits invariant is added by disjunction instead of joining abstract values. *) | `Left _ | `Right _ -> (* mutex or thread *) Invariant.none @@ -733,8 +777,8 @@ struct if not invariant then ( if not (Param.handle_atomic && ask.f MustBeAtomic) then sideg (V.unprotected x) v; (* Delay publishing unprotected write in the atomic section. *) - if !earlyglobs then (* earlyglobs workaround for 13/60 *) - sideg (V.protected x) v + if !earlyglobs && not (ThreadFlag.is_currently_multi ask) then (* earlyglobs workaround for 13/60 *) + sideg (V.protected x) v (* Also side to protected because with earlyglobs enter_multithreaded does not side everything to protected *) (* Unlock after invariant will still side effect refined value (if protected) from CPA, because cannot distinguish from non-invariant write since W is implicit. *) ); if Param.handle_atomic && ask.f MustBeAtomic then @@ -748,7 +792,7 @@ struct let unlock ask getg sideg (st: BaseComponents (D).t) m = let sideg = Wrapper.sideg ask sideg in - let atomic = Param.handle_atomic && LockDomain.Addr.equal m (LockDomain.Addr.of_var LibraryFunctions.verifier_atomic_var) in + let atomic = Param.handle_atomic && LockDomain.MustLock.equal m (LockDomain.MustLock.of_var LibraryFunctions.verifier_atomic_var) in (* TODO: what about G_m globals in cpa that weren't actually written? *) CPA.fold (fun x v (st: BaseComponents (D).t) -> if is_protected_by ask m x then ( (* is_in_Gm *) @@ -770,9 +814,9 @@ struct ) st.cpa st let sync ask getg sideg (st: BaseComponents (D).t) reason = - let sideg = Wrapper.sideg ask sideg in - match reason with - | `Join -> (* required for branched thread creation *) + let branched_sync () = + (* required for branched thread creation *) + let sideg = Wrapper.sideg ask sideg in CPA.fold (fun x v (st: BaseComponents (D).t) -> if is_global ask x && is_unprotected ask x then ( sideg (V.unprotected x) v; @@ -782,6 +826,14 @@ struct else st ) st.cpa st + in + match reason with + | `Join when ConfCheck.branched_thread_creation () -> + branched_sync () + | `JoinCall f when ConfCheck.branched_thread_creation_at_call ask f -> + branched_sync () + | `Join + | `JoinCall _ | `Return | `Normal | `Init @@ -827,13 +879,37 @@ struct vf (V.protected g); | _ -> () - let invariant_global ask getg g = + let invariant_global (ask: Q.ask) getg g = let getg = Wrapper.getg ask getg in match g with | `Left g' -> (* unprotected *) ValueDomain.invariant_global (fun g -> getg (V.unprotected g)) g' - | `Right g -> (* protected *) - Invariant.none + | `Right g' -> (* protected *) + let locks = ask.f (Q.MustProtectingLocks g') in + if LockDomain.MustLockset.is_all locks || LockDomain.MustLockset.is_empty locks then + Invariant.none + else if VD.equal (getg (V.protected g')) (getg (V.unprotected g')) then + Invariant.none (* don't output protected invariant because it's the same as unprotected *) + else ( + (* Only read g' as protected, everything else (e.g. pointed to variables) may be unprotected. + See 56-witness/69-ghost-ptr-protection and https://github.com/goblint/analyzer/pull/1394#discussion_r1698136411. *) + let read_global g = getg (if CilType.Varinfo.equal g g' then V.protected g else V.unprotected g) in + let inv = ValueDomain.invariant_global read_global g' in + (* Very conservative about multiple (write-)protecting mutexes: invariant is not claimed when any of them is held. + It should be possible to be more precise because writes only happen with all of them held, + but conjunction is unsound when one of the mutexes is temporarily unlocked. + Hypothetical read-protection is also somehow relevant. *) + LockDomain.MustLockset.fold (fun m acc -> + if LockDomain.MustLock.equal m (LockDomain.MustLock.of_var LibraryFunctions.verifier_atomic_var) then + acc + else if ask.f (GhostVarAvailable (Locked m)) then ( + let var = WitnessGhost.to_varinfo (Locked m) in + Invariant.(of_exp (Lval (GoblintCil.var var)) || acc) [@coverage off] (* bisect_ppx cannot handle redefined (||) *) + ) + else + Invariant.none + ) locks inv + ) let invariant_vars ask getg st = protected_vars ask end @@ -844,12 +920,12 @@ struct module GWeak = struct - include MapDomain.MapBot (Lockset) (WeakRange) + include MapDomain.MapBot (MustLockset) (WeakRange) let name () = "weak" end module GSync = struct - include MapDomain.MapBot (Lockset) (SyncRange) + include MapDomain.MapBot (MustLockset) (SyncRange) let name () = "synchronized" end module G = @@ -905,7 +981,7 @@ struct let invariant_vars ask getg st = let module VS = Set.Make (CilType.Varinfo) in let s = current_lockset ask in - Lockset.fold (fun m acc -> + MustLockset.fold (fun m acc -> GSync.fold (fun s' cpa' acc -> SyncRange.fold_sync_vars VS.add cpa' acc ) (G.sync (getg (V.mutex m))) acc @@ -974,7 +1050,7 @@ struct (* sync: M -> (2^M -> (G -> D)) *) include AbstractLockCenteredBase (ThreadMap) (LockCenteredBase.CPA) - let global_init_thread = RichVarinfo.single ~name:"global_init" + let global_init_thread = RichVarinfo.single ~name:"global_init" ~typ:GoblintCil.voidType let current_thread (ask: Q.ask): Thread.t = if !AnalysisState.global_initialization then ThreadIdDomain.Thread.threadinit (global_init_thread ()) ~multiple:false @@ -984,7 +1060,7 @@ struct let read_global ask getg (st: BaseComponents (D).t) x = let s = current_lockset ask in GWeak.fold (fun s' tm acc -> - if Lockset.disjoint s s' then + if MustLockset.disjoint s s' then ThreadMap.fold (fun t' v acc -> VD.join v acc ) tm acc @@ -1004,7 +1080,7 @@ struct let lock ask getg (st: BaseComponents (D).t) m = let s = current_lockset ask in let cpa' = GSync.fold (fun s' cpa' acc -> - if Lockset.disjoint s s' then + if MustLockset.disjoint s s' then CPA.join cpa' acc else acc @@ -1013,14 +1089,14 @@ struct {st with cpa = cpa'} let unlock ask getg sideg (st: BaseComponents (D).t) m = - let s = Lockset.remove m (current_lockset ask) in + let s = MustLockset.remove m (current_lockset ask) in let t = current_thread ask in let side_cpa = CPA.filter (fun x _ -> - GWeak.fold (fun s' tm acc -> + GWeak.exists (fun s' tm -> (* TODO: swap 2^M and T partitioning for lookup by t here first? *) let v = ThreadMap.find t tm in - (Lockset.mem m s' && not (VD.is_bot v)) || acc - ) (G.weak (getg (V.global x))) false + (MustLockset.mem m s' && not (VD.is_bot v)) + ) (G.weak (getg (V.global x))) ) st.cpa in sideg (V.mutex m) (G.create_sync (GSync.singleton s side_cpa)); @@ -1031,6 +1107,7 @@ struct | `Return | `Normal | `Join (* TODO: no problem with branched thread creation here? *) + | `JoinCall _ | `Init | `Thread -> st @@ -1045,7 +1122,7 @@ struct let read_global ask getg (st: BaseComponents (D).t) x = let s = current_lockset ask in GWeak.fold (fun s' v acc -> - if Lockset.disjoint s s' then + if MustLockset.disjoint s s' then VD.join v acc else acc @@ -1062,7 +1139,7 @@ struct let lock ask getg (st: BaseComponents (D).t) m = let s = current_lockset ask in let cpa' = GSync.fold (fun s' cpa' acc -> - if Lockset.disjoint s s' then + if MustLockset.disjoint s s' then CPA.join cpa' acc else acc @@ -1071,11 +1148,11 @@ struct {st with cpa = cpa'} let unlock ask getg sideg (st: BaseComponents (D).t) m = - let s = Lockset.remove m (current_lockset ask) in + let s = MustLockset.remove m (current_lockset ask) in let side_cpa = CPA.filter (fun x _ -> - GWeak.fold (fun s' v acc -> - (Lockset.mem m s' && not (VD.is_bot v)) || acc - ) (G.weak (getg (V.global x))) false + GWeak.exists (fun s' v -> + (MustLockset.mem m s' && not (VD.is_bot v)) + ) (G.weak (getg (V.global x))) ) st.cpa in sideg (V.mutex m) (G.create_sync (GSync.singleton s side_cpa)); @@ -1086,6 +1163,7 @@ struct | `Return | `Normal | `Join (* TODO: no problem with branched thread creation here? *) + | `JoinCall _ | `Init | `Thread -> st @@ -1116,7 +1194,7 @@ struct let read_global ask getg (st: BaseComponents (D).t) x = let s = current_lockset ask in GWeak.fold (fun s' v acc -> - if Lockset.disjoint s s' then + if MustLockset.disjoint s s' then VD.join v acc else acc @@ -1137,7 +1215,7 @@ struct let lock ask getg (st: BaseComponents (D).t) m = let s = current_lockset ask in let cpa' = GSync.fold (fun s' cpa' acc -> - if Lockset.disjoint s s' then + if MustLockset.disjoint s s' then CPA.join cpa' acc else acc @@ -1146,7 +1224,7 @@ struct {st with cpa = cpa'} let unlock ask getg sideg (st: BaseComponents (D).t) m = - let s = Lockset.remove m (current_lockset ask) in + let s = MustLockset.remove m (current_lockset ask) in let is_in_W x _ = W.mem x st.priv in let side_cpa = CPA.filter is_in_W st.cpa in sideg (V.mutex m) (G.create_sync (GSync.singleton s side_cpa)); @@ -1157,6 +1235,7 @@ struct | `Return | `Normal | `Join (* TODO: no problem with branched thread creation here? *) + | `JoinCall _ | `Init | `Thread -> st @@ -1166,7 +1245,7 @@ struct if Param.side_effect_global_init then ( CPA.fold (fun x v (st: BaseComponents (D).t) -> if is_global ask x then ( - sideg (V.global x) (G.create_weak (GWeak.singleton (Lockset.empty ()) v)); + sideg (V.global x) (G.create_weak (GWeak.singleton (MustLockset.empty ()) v)); {st with priv = W.add x st.priv} (* TODO: is this add necessary? *) ) else @@ -1189,13 +1268,13 @@ struct module DV = struct - include MapDomain.MapBot_LiftTop (Lock) (MustVars) + include MapDomain.MapBot_LiftTop (LockDomain.MustLock) (MustVars) let name () = "V" end module L = struct - include MapDomain.MapBot_LiftTop (Lock) (MinLocksets) + include MapDomain.MapBot_LiftTop (LockDomain.MustLock) (MinLocksets) let name () = "L" end end @@ -1219,7 +1298,7 @@ struct let startstate () = (DV.bot (), L.bot ()) - let lockset_init = Lockset.top () + let lockset_init = MustLockset.all () let distr_init getg x v = if get_bool "exp.priv-distr-init" then @@ -1238,7 +1317,7 @@ struct let syncs = UnwrappedG.sync (getg (V.mutex m)) in MinLocksets.fold (fun b acc -> GSync.fold (fun s' cpa' acc -> - if Lockset.disjoint b s' then + if MustLockset.disjoint b s' then let v = CPA.find x cpa' in VD.join v acc else @@ -1251,7 +1330,7 @@ struct in let weaks = UnwrappedG.weak (getg (V.global x)) in let d_weak = GWeak.fold (fun s' v acc -> - if Lockset.disjoint s s' then + if MustLockset.disjoint s s' then VD.join v acc else acc @@ -1298,7 +1377,7 @@ struct let unlock ask getg sideg (st: BaseComponents (D).t) m = let sideg = Wrapper.sideg ask sideg in let getg = Wrapper.getg ask getg in - let s = Lockset.remove m (current_lockset ask) in + let s = MustLockset.remove m (current_lockset ask) in let is_in_G x _ = is_global ask x in let side_cpa = CPA.filter is_in_G st.cpa in let side_cpa = CPA.mapi (fun x v -> @@ -1315,6 +1394,7 @@ struct | `Return | `Normal | `Join (* TODO: no problem with branched thread creation here? *) + | `JoinCall _ | `Init | `Thread -> st @@ -1352,13 +1432,13 @@ struct module GWeakW = struct - include MapDomain.MapBot (Lockset) (VD) + include MapDomain.MapBot (MustLockset) (VD) let fold_weak f m a = fold (fun _ v a -> f v a) m a end module GSyncW = struct - include MapDomain.MapBot (Lockset) (LockCenteredBase.CPA) + include MapDomain.MapBot (MustLockset) (LockCenteredBase.CPA) let fold_sync_vars f m a = fold (fun _ cpa a -> @@ -1390,11 +1470,11 @@ struct let startstate () = (W.bot (), P.top ()) - let lockset_init = Lockset.top () + let lockset_init = MustLockset.all () let distr_init getg x v = if get_bool "exp.priv-distr-init" then - let v_init = GWeakW.find lockset_init (GWeak.find (Lockset.empty ()) (UnwrappedG.weak (getg (V.global x)))) in + let v_init = GWeakW.find lockset_init (GWeak.find (MustLockset.empty ()) (UnwrappedG.weak (getg (V.global x)))) in VD.join v v_init else v @@ -1405,13 +1485,13 @@ struct let (w, p) = st.priv in let p_x = P.find x p in let d_cpa = CPA.find x st.cpa in - let d_sync = Lockset.fold (fun m acc -> - if MinLocksets.exists (fun s''' -> not (Lockset.mem m s''')) p_x then + let d_sync = MustLockset.fold (fun m acc -> + if MinLocksets.exists (fun s''' -> not (MustLockset.mem m s''')) p_x then let syncs = UnwrappedG.sync (getg (V.mutex m)) in GSync.fold (fun s' gsyncw' acc -> - if Lockset.disjoint s s' then + if MustLockset.disjoint s s' then GSyncW.fold (fun w' cpa' acc -> - if MinLocksets.exists (fun s'' -> Lockset.disjoint s'' w') p_x then + if MinLocksets.exists (fun s'' -> MustLockset.disjoint s'' w') p_x then let v = CPA.find x cpa' in VD.join v acc else @@ -1426,9 +1506,9 @@ struct in let weaks = UnwrappedG.weak (getg (V.global x)) in let d_weak = GWeak.fold (fun s' gweakw' acc -> - if Lockset.disjoint s s' then + if MustLockset.disjoint s s' then GWeakW.fold (fun w' v acc -> - if MinLocksets.exists (fun s'' -> Lockset.disjoint s'' w') p_x then + if MinLocksets.exists (fun s'' -> MustLockset.disjoint s'' w') p_x then VD.join v acc else acc @@ -1468,10 +1548,10 @@ struct let unlock ask getg sideg (st: BaseComponents (D).t) m = let getg = Wrapper.getg ask getg in let sideg = Wrapper.sideg ask sideg in - let s = Lockset.remove m (current_lockset ask) in + let s = MustLockset.remove m (current_lockset ask) in let (w, p) = st.priv in let p' = P.map (fun s' -> MinLocksets.add s s') p in - if M.tracing then M.traceli "priv" "unlock %a %a" Lock.pretty m CPA.pretty st.cpa; + if M.tracing then M.traceli "priv" "unlock %a %a" LockDomain.MustLock.pretty m CPA.pretty st.cpa; let side_gsyncw = CPA.fold (fun x v acc -> if is_global ask x then ( let w_x = W.find x w in @@ -1484,7 +1564,7 @@ struct acc ) st.cpa (GSyncW.bot ()) in - if M.tracing then M.traceu "priv" "unlock %a %a" Lock.pretty m GSyncW.pretty side_gsyncw; + if M.tracing then M.traceu "priv" "unlock %a %a" LockDomain.MustLock.pretty m GSyncW.pretty side_gsyncw; sideg (V.mutex m) (UnwrappedG.create_sync (GSync.singleton s side_gsyncw)); {st with priv = (w, p')} @@ -1493,6 +1573,7 @@ struct | `Return | `Normal | `Join (* TODO: no problem with branched thread creation here? *) + | `JoinCall _ | `Init | `Thread -> st @@ -1504,7 +1585,7 @@ struct if EscapeDomain.EscapedVars.mem x escaped then ( let (w, p) = st.priv in let p' = P.add x (MinLocksets.singleton s) p in - sideg (V.global x) (UnwrappedG.create_weak (GWeak.singleton (Lockset.empty ()) (GWeakW.singleton lockset_init v))); + sideg (V.global x) (UnwrappedG.create_weak (GWeak.singleton (MustLockset.empty ()) (GWeakW.singleton lockset_init v))); {st with cpa = CPA.remove x st.cpa; priv = (w, p')} ) else @@ -1515,7 +1596,7 @@ struct let sideg = Wrapper.sideg ask sideg in CPA.fold (fun x v (st: BaseComponents (D).t) -> if is_global ask x then ( - sideg (V.global x) (UnwrappedG.create_weak (GWeak.singleton (Lockset.empty ()) (GWeakW.singleton lockset_init v))); + sideg (V.global x) (UnwrappedG.create_weak (GWeak.singleton (MustLockset.empty ()) (GWeakW.singleton lockset_init v))); {st with cpa = CPA.remove x st.cpa} ) else @@ -1545,11 +1626,11 @@ struct let startstate () = ((W.bot (), P.top ()), (DV.bot (), L.bot ())) - let lockset_init = Lockset.top () + let lockset_init = MustLockset.all () let distr_init getg x v = if get_bool "exp.priv-distr-init" then - let v_init = GWeakW.find lockset_init (GWeak.find (Lockset.empty ()) (UnwrappedG.weak (getg (V.global x)))) in + let v_init = GWeakW.find lockset_init (GWeak.find (MustLockset.empty ()) (UnwrappedG.weak (getg (V.global x)))) in VD.join v v_init else v @@ -1565,9 +1646,9 @@ struct let syncs = UnwrappedG.sync (getg (V.mutex m)) in MinLocksets.fold (fun b acc -> GSync.fold (fun s' gsyncw' acc -> - if Lockset.disjoint b s' then + if MustLockset.disjoint b s' then GSyncW.fold (fun w' cpa' acc -> - if MinLocksets.exists (fun s'' -> Lockset.disjoint s'' w') p_x then + if MinLocksets.exists (fun s'' -> MustLockset.disjoint s'' w') p_x then let v = CPA.find x cpa' in VD.join v acc else @@ -1583,9 +1664,9 @@ struct in let weaks = UnwrappedG.weak (getg (V.global x)) in let d_m_weak = GWeak.fold (fun s' gweakw' acc -> - if Lockset.disjoint s s' then + if MustLockset.disjoint s s' then GWeakW.fold (fun w' v acc -> - if MinLocksets.exists (fun s'' -> Lockset.disjoint s'' w') p_x then + if MinLocksets.exists (fun s'' -> MustLockset.disjoint s'' w') p_x then VD.join v acc else acc @@ -1595,13 +1676,13 @@ struct ) weaks (VD.bot ()) in let d_m = VD.join d_m_sync d_m_weak in - let d_g_sync = Lockset.fold (fun m acc -> - if MinLocksets.exists (fun s''' -> not (Lockset.mem m s''')) p_x then + let d_g_sync = MustLockset.fold (fun m acc -> + if MinLocksets.exists (fun s''' -> not (MustLockset.mem m s''')) p_x then let syncs = UnwrappedG.sync (getg (V.mutex m)) in GSync.fold (fun s' gsyncw' acc -> - if Lockset.disjoint s s' then + if MustLockset.disjoint s s' then GSyncW.fold (fun w' cpa' acc -> - if MinLocksets.exists (fun s'' -> Lockset.disjoint s'' w') p_x then + if MinLocksets.exists (fun s'' -> MustLockset.disjoint s'' w') p_x then let v = CPA.find x cpa' in VD.join v acc else @@ -1653,7 +1734,7 @@ struct let unlock ask getg sideg (st: BaseComponents (D).t) m = let getg = Wrapper.getg ask getg in let sideg = Wrapper.sideg ask sideg in - let s = Lockset.remove m (current_lockset ask) in + let s = MustLockset.remove m (current_lockset ask) in let ((w, p), vl) = st.priv in let p' = P.map (fun s' -> MinLocksets.add s s') p in let side_gsyncw = CPA.fold (fun x v acc -> @@ -1675,6 +1756,7 @@ struct | `Return | `Normal | `Join (* TODO: no problem with branched thread creation here? *) + | `JoinCall _ | `Init | `Thread -> st @@ -1686,7 +1768,7 @@ struct if EscapeDomain.EscapedVars.mem x escaped then ( let ((w, p), (vv, l)) = st.priv in let p' = P.add x (MinLocksets.singleton s) p in - sideg (V.global x) (UnwrappedG.create_weak (GWeak.singleton (Lockset.empty ()) (GWeakW.singleton lockset_init v))); + sideg (V.global x) (UnwrappedG.create_weak (GWeak.singleton (MustLockset.empty ()) (GWeakW.singleton lockset_init v))); {st with cpa = CPA.remove x st.cpa; priv = ((w, p'), (vv, l))} ) else @@ -1697,7 +1779,7 @@ struct let sideg = Wrapper.sideg ask sideg in CPA.fold (fun x v (st: BaseComponents (D).t) -> if is_global ask x then ( - sideg (V.global x) (UnwrappedG.create_weak (GWeak.singleton (Lockset.empty ()) (GWeakW.singleton lockset_init v))); + sideg (V.global x) (UnwrappedG.create_weak (GWeak.singleton (MustLockset.empty ()) (GWeakW.singleton lockset_init v))); {st with cpa = CPA.remove x st.cpa} ) else @@ -1806,7 +1888,7 @@ struct r let lock ask getg st m = - if M.tracing then M.traceli "priv" "lock %a" LockDomain.Addr.pretty m; + if M.tracing then M.traceli "priv" "lock %a" LockDomain.MustLock.pretty m; if M.tracing then M.trace "priv" "st: %a" BaseComponents.pretty st; let getg x = let r = getg x in @@ -1818,7 +1900,7 @@ struct r let unlock ask getg sideg st m = - if M.tracing then M.traceli "priv" "unlock %a" LockDomain.Addr.pretty m; + if M.tracing then M.traceli "priv" "unlock %a" LockDomain.MustLock.pretty m; if M.tracing then M.trace "priv" "st: %a" BaseComponents.pretty st; let getg x = let r = getg x in @@ -1872,6 +1954,17 @@ struct if M.tracing then M.traceu "priv" "-> %a" BaseComponents.pretty r; r + let invariant_global ask getg g = + if M.tracing then M.traceli "priv" "invariant_global %a" V.pretty g; + let getg x = + let r = getg x in + if M.tracing then M.trace "priv" "getg %a -> %a" V.pretty x G.pretty r; + r + in + let r = invariant_global ask getg g in + if M.tracing then M.traceu "priv" "-> %a" Invariant.pretty r; + r + end let priv_module: (module S) Lazy.t = diff --git a/src/analyses/basePriv.mli b/src/analyses/basePriv.mli index e176a450fa..edcf70ec98 100644 --- a/src/analyses/basePriv.mli +++ b/src/analyses/basePriv.mli @@ -17,10 +17,10 @@ sig val read_global: Queries.ask -> (V.t -> G.t) -> BaseDomain.BaseComponents (D).t -> varinfo -> BaseDomain.VD.t val write_global: ?invariant:bool -> Queries.ask -> (V.t -> G.t) -> (V.t -> G.t -> unit) -> BaseDomain.BaseComponents (D).t -> varinfo -> BaseDomain.VD.t -> BaseDomain.BaseComponents (D).t - val lock: Queries.ask -> (V.t -> G.t) -> BaseDomain.BaseComponents (D).t -> LockDomain.Addr.t -> BaseDomain.BaseComponents (D).t - val unlock: Queries.ask -> (V.t -> G.t) -> (V.t -> G.t -> unit) -> BaseDomain.BaseComponents (D).t -> LockDomain.Addr.t -> BaseDomain.BaseComponents (D).t + val lock: Queries.ask -> (V.t -> G.t) -> BaseDomain.BaseComponents (D).t -> LockDomain.MustLock.t -> BaseDomain.BaseComponents (D).t + val unlock: Queries.ask -> (V.t -> G.t) -> (V.t -> G.t -> unit) -> BaseDomain.BaseComponents (D).t -> LockDomain.MustLock.t -> BaseDomain.BaseComponents (D).t - val sync: Queries.ask -> (V.t -> G.t) -> (V.t -> G.t -> unit) -> BaseDomain.BaseComponents (D).t -> [`Normal | `Join | `Return | `Init | `Thread] -> BaseDomain.BaseComponents (D).t + val sync: Queries.ask -> (V.t -> G.t) -> (V.t -> G.t -> unit) -> BaseDomain.BaseComponents (D).t -> [`Normal | `Join | `JoinCall of CilType.Fundec.t | `Return | `Init | `Thread] -> BaseDomain.BaseComponents (D).t 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 diff --git a/src/analyses/callstring.ml b/src/analyses/callstring.ml index a8734cdf1c..391f5f6657 100644 --- a/src/analyses/callstring.ml +++ b/src/analyses/callstring.ml @@ -1,7 +1,7 @@ (** - Call String analysis [call_string] and/or Call Site analysis [call_site]. - The call string limitation for both approaches can be adjusted with the "callString_length" option. - By adding new implementations of the CallstringType, additional analyses can be added. + Call String analysis [call_string] and/or Call Site analysis [call_site]. + The call string limitation for both approaches can be adjusted with the "callString_length" option. + By adding new implementations of the CallstringType, additional analyses can be added. *) open Analyses @@ -18,20 +18,22 @@ end (** Analysis with infinite call string or with limited call string (k-CFA, tracks the last k call stack elements). With the CT argument it is possible to specify the type of the call string elements *) -module Spec (CT:CallstringType) : MCPSpec = +module Spec (CT:CallstringType) : MCPSpec = struct - include Analyses.IdentitySpec + include UnitAnalysis.Spec (* simulates a call string (with or without limitation)*) module CallString = struct include Printable.PQueue (CT) + let (empty:t) = BatDeque.empty + (* pushes "elem" to the call string, guarantees a depth of k if limitation is specified with "ana.context.callString_length" *) - let push callstr elem = + let push callstr elem = match elem with | None -> callstr - | Some e -> - let new_callstr = BatDeque.cons e callstr in (* pushes new element to callstr *) + | Some e -> + let new_callstr = BatDeque.cons e callstr in (* pushes new element to callstr *) if get_int "ana.context.callString_length" < 0 then new_callstr (* infinite call string *) else (* maximum of k elements *) @@ -41,38 +43,24 @@ struct | _ -> failwith "CallString Error: It shouldn't happen that more than one element must be deleted to maintain the correct height!" end - module D = Lattice.Flat (CallString) (* should be the CallString (C=D). Since a Lattice is required, Lattice.Flat is used to fulfill the type *) module C = CallString - module V = EmptyV - module G = Lattice.Unit let name () = "call_"^ CT.ana_name - let startstate v = `Lifted (BatDeque.empty) - let exitstate v = `Lifted (BatDeque.empty) - - let context fd x = match x with - | `Lifted x -> x - | _ -> failwith "CallString: Context error! The context cannot be derived from Top or Bottom!" - - let callee_state ctx f = - let elem = CT.new_ele f ctx in (* receive element that should be added to call string *) - let new_callstr = CallString.push (context f ctx.local) elem in - `Lifted new_callstr - - let enter ctx r f args = [ctx.local, callee_state ctx f] - let combine_env ctx lval fexp f args fc au f_ask = ctx.local + let startcontext () = CallString.empty - let threadenter ctx ~multiple lval v args = [callee_state ctx (Cilfacade.find_varinfo_fundec v)] + let context ctx fd _ = + let elem = CT.new_ele fd ctx in (* receive element that should be added to call string *) + CallString.push (ctx.context ()) elem end (* implementations of CallstringTypes*) module Callstring: CallstringType = struct include CilType.Fundec let ana_name = "string" - let new_ele f ctx = - let f' = Node.find_fundec ctx.node in - if CilType.Fundec.equal f' dummyFunDec + let new_ele f ctx = + let f' = Node.find_fundec ctx.node in + if CilType.Fundec.equal f' dummyFunDec then None else Some f' end @@ -80,7 +68,7 @@ end module Callsite: CallstringType = struct include CilType.Stmt let ana_name = "site" - let new_ele f ctx = + let new_ele f ctx = match ctx.prev_node with | Statement stmt -> Some stmt | _ -> None (* first statement is filtered *) diff --git a/src/analyses/commonPriv.ml b/src/analyses/commonPriv.ml index 6376a6ca11..915b3da063 100644 --- a/src/analyses/commonPriv.ml +++ b/src/analyses/commonPriv.ml @@ -50,6 +50,33 @@ struct if not threadflag_path_sens then failwith "The activated privatization requires the 'threadflag' analysis to be path sensitive if it is enabled (it is currently enabled, but not path sensitive)"; () end + + (** Whether branched thread creation needs to be handled by [sync `Join] of privatization. *) + let branched_thread_creation () = + let threadflag_active = List.mem "threadflag" (GobConfig.get_string_list "ana.activated") in + if threadflag_active then + let threadflag_path_sens = List.mem "threadflag" (GobConfig.get_string_list "ana.path_sens") in + not threadflag_path_sens + else + true + + (** Whether branched thread creation at start nodes of procedures needs to be handled by [sync `JoinCall] of privatization. *) + let branched_thread_creation_at_call (ask:Queries.ask) f = + let threadflag_active = List.mem "threadflag" (GobConfig.get_string_list "ana.activated") in + if threadflag_active then + let sens = GobConfig.get_string_list "ana.ctx_sens" in + let threadflag_ctx_sens = match sens with + | [] -> (* use values of "ana.ctx_insens" (blacklist) *) + not (List.mem "threadflag" @@ GobConfig.get_string_list "ana.ctx_insens") + | sens -> (* use values of "ana.ctx_sens" (whitelist) *) + List.mem "threadflag" sens + in + if not threadflag_ctx_sens then + true + else + ask.f (Queries.GasExhausted f) + else + true end module Protection = @@ -73,8 +100,8 @@ struct ask.f (Q.MustBeProtectedBy {mutex=m; global=x; write=true; protection}) let protected_vars (ask: Q.ask): varinfo list = - Q.AD.fold (fun m acc -> - Q.VS.join (ask.f (Q.MustProtectedVars {mutex = m; write = true})) acc + LockDomain.MustLockset.fold (fun ml acc -> + Q.VS.join (ask.f (Q.MustProtectedVars {mutex = ml; write = true})) acc ) (ask.f Q.MustLockset) (Q.VS.empty ()) |> Q.VS.elements end @@ -83,7 +110,7 @@ module MutexGlobals = struct module VMutex = struct - include LockDomain.Addr + include LockDomain.MustLock let name () = "mutex" end module VMutexInits = Printable.UnitConf (struct let name = "MUTEX_INITS" end) @@ -121,23 +148,14 @@ end module Locksets = struct - module Lock = - struct - include LockDomain.Addr - let name () = "lock" - end + module MustLockset = LockDomain.MustLockset - module Lockset = SetDomain.ToppedSet (Lock) (struct let topname = "All locks" end) - - module MustLockset = SetDomain.Reverse (Lockset) - - let current_lockset (ask: Q.ask): Lockset.t = + let current_lockset (ask: Q.ask): MustLockset.t = (* TODO: remove this global_init workaround *) if !AnalysisState.global_initialization then - Lockset.empty () + MustLockset.empty () else - let ad = ask.f Queries.MustLockset in - Q.AD.fold (fun mls acc -> Lockset.add mls acc) ad (Lockset.empty ()) (* TODO: use AD as Lockset *) + ask.f Queries.MustLockset (* TODO: reversed SetDomain.Hoare *) module MinLocksets = HoareDomain.Set_LiftTop (MustLockset) (struct let topname = "All locksets" end) (* reverse Lockset because Hoare keeps maximal, but we need minimal *) @@ -162,7 +180,7 @@ struct let name () = "P" (* TODO: change MinLocksets.exists/top instead? *) - let find x p = find_opt x p |? MinLocksets.singleton (Lockset.empty ()) (* ensure exists has something to check for thread returns *) + let find x p = find_opt x p |? MinLocksets.singleton (MustLockset.empty ()) (* ensure exists has something to check for thread returns *) end end @@ -243,7 +261,7 @@ struct module LLock = struct - include Printable.Either (Locksets.Lock) (struct include CilType.Varinfo let name () = "global" end) + include Printable.Either (LockDomain.MustLock) (struct include CilType.Varinfo let name () = "global" end) let mutex m = `Left m let global x = `Right x end @@ -296,3 +314,41 @@ struct let startstate () = W.bot (), LMust.top (), L.bot () end + + +let lift_lock (ask: Q.ask) f st (addr: LockDomain.Addr.t) = + (* Should be in sync with: + 1. LocksetAnalysis.MakeMust.event + 2. MutexAnalysis.Spec.Arg.add + 3. LockDomain.MustLocksetRW.add_mval_rw *) + match addr with + | UnknownPtr -> st + | Addr (v, _) when ask.f (IsMultiple v) -> st + | Addr mv when LockDomain.Mval.is_definite mv -> f st (LockDomain.MustLock.of_mval mv) + | Addr _ + | NullPtr + | StrPtr _ -> st + +let lift_unlock (ask: Q.ask) f st (addr: LockDomain.Addr.t) = + (* Should be in sync with: + 1. LocksetAnalysis.MakeMust.event + 2. MutexAnalysis.Spec.Arg.remove + 3. MutexAnalysis.Spec.Arg.remove_all + 4. LockDomain.MustLocksetRW.remove_mval_rw *) + match addr with + | UnknownPtr -> + LockDomain.MustLockset.fold (fun ml st -> + (* call privatization's unlock only with definite lock *) + f st ml + ) (ask.f MustLockset) st + | StrPtr _ + | NullPtr -> st + | Addr mv when LockDomain.Mval.is_definite mv -> f st (LockDomain.MustLock.of_mval mv) + | Addr mv -> + LockDomain.MustLockset.fold (fun ml st -> + if LockDomain.MustLock.semantic_equal_mval ml mv = Some false then + st + else + (* call privatization's unlock only with definite lock *) + f st ml + ) (ask.f MustLockset) st diff --git a/src/analyses/condVars.ml b/src/analyses/condVars.ml index 393be0a857..448e3a79e5 100644 --- a/src/analyses/condVars.ml +++ b/src/analyses/condVars.ml @@ -58,7 +58,7 @@ struct let name () = "condvars" module D = Domain - module C = Domain + include Analyses.ValueContexts(D) (* >? is >>=, |? is >> *) let (>?) = Option.bind @@ -116,7 +116,7 @@ struct match rval with | BinOp (op, _, _, _) when is_cmp op -> (* logical expression *) save_expr lval rval - | Lval k when Option.is_some (mustPointTo ctx (AddrOf k) >? flip D.get d) -> (* var-eq for transitive closure *) + | Lval k -> (* var-eq for transitive closure *) mustPointTo ctx (AddrOf k) >? flip D.get_elt d |> Option.map (save_expr lval) |? d | _ -> d diff --git a/src/analyses/deadlock.ml b/src/analyses/deadlock.ml index c23d6f4294..e83b122fd9 100644 --- a/src/analyses/deadlock.ml +++ b/src/analyses/deadlock.ml @@ -30,7 +30,7 @@ struct let part_access ctx: MCPAccess.A.t = Obj.obj (ctx.ask (PartAccess Point)) - let add ctx ((l, _): LockDomain.Lockset.Lock.t) = + let add ctx ((l, _): LockDomain.AddrRW.t) = let after: LockEvent.t = (l, ctx.prev_node, part_access ctx) in (* use octx for access to use locksets before event *) D.iter (fun before -> side_lock_event_pair ctx before after diff --git a/src/analyses/expsplit.ml b/src/analyses/expsplit.ml index fef3d9ff9f..46ec4774e5 100644 --- a/src/analyses/expsplit.ml +++ b/src/analyses/expsplit.ml @@ -13,7 +13,7 @@ struct let name () = "expsplit" module D = MapDomain.MapBot (Basetype.CilExp) (ID) - module C = D + include Analyses.ValueContexts(D) let startstate v = D.bot () let exitstate = startstate diff --git a/src/analyses/extractPthread.ml b/src/analyses/extractPthread.ml index 6cc347d511..a61c54ab96 100644 --- a/src/analyses/extractPthread.ml +++ b/src/analyses/extractPthread.ml @@ -866,7 +866,7 @@ module Spec : Analyses.MCPSpec = struct (** Domains *) module D = PthreadDomain.D - module C = D + include Analyses.ValueContexts(D) (** Set of created tasks to spawn when going multithreaded *) module G = Tasks diff --git a/src/analyses/locksetAnalysis.ml b/src/analyses/locksetAnalysis.ml index 6a816b9e6c..c73bd4703e 100644 --- a/src/analyses/locksetAnalysis.ml +++ b/src/analyses/locksetAnalysis.ml @@ -15,7 +15,7 @@ struct let name () = "lockset" module D = D - module C = D + include Analyses.ValueContexts(D) let startstate v = D.empty () let threadenter ctx ~multiple lval f args = [D.empty ()] @@ -29,7 +29,7 @@ sig module G: Lattice.S module V: SpecSysVar - val add: (D.t, G.t, D.t, V.t) ctx -> LockDomain.Lockset.Lock.t -> D.t + val add: (D.t, G.t, D.t, V.t) ctx -> LockDomain.AddrRW.t -> D.t val remove: (D.t, G.t, D.t, V.t) ctx -> ValueDomain.Addr.t -> D.t end diff --git a/src/analyses/loopTermination.ml b/src/analyses/loopTermination.ml index ab768e235d..66a50c17b7 100644 --- a/src/analyses/loopTermination.ml +++ b/src/analyses/loopTermination.ml @@ -30,7 +30,8 @@ struct let name () = "termination" module D = Lattice.Unit - module C = D + include Analyses.ValueContexts(D) + module V = struct include UnitV let is_write_only _ = true diff --git a/src/analyses/loopfreeCallstring.ml b/src/analyses/loopfreeCallstring.ml index 172114d37c..d9760e58a0 100644 --- a/src/analyses/loopfreeCallstring.ml +++ b/src/analyses/loopfreeCallstring.ml @@ -1,60 +1,49 @@ -(** +(** Loopfree Callstring analysis [loopfree_callstring] that reduces the call string length of the classical Call String approach for recursions - The idea is to improve the Call String analysis by representing all detected call cycle of the call string in a set + The idea is to improve the Call String analysis by representing all detected call cycle of the call string in a set In case no call cycles appears, the call string is identical to the call string of the Call String approach - For example: + For example: - call string [main, a, b, c, a] is represented as [main, {a, b, c}] - call string [main, a, a, b, b, b] is represented as [main, {a}, {b}] This approach is inspired by - @see Schwarz, M., Saan, S., Seidl, H., Erhard, J., Vojdani, V. Clustered Relational Thread-Modular Abstract Interpretation with Local Traces. Appendix F. + @see Schwarz, M., Saan, S., Seidl, H., Erhard, J., Vojdani, V. Clustered Relational Thread-Modular Abstract Interpretation with Local Traces. Appendix F. *) open Analyses module Spec : MCPSpec = struct - include Analyses.IdentitySpec + include UnitAnalysis.Spec let name () = "loopfree_callstring" module FundecSet = SetDomain.Make (CilType.Fundec) - module Either = Printable.Either (CilType.Fundec) (FundecSet) - - module D = Lattice.Flat (Printable.Liszt (Either)) (* A domain element is a list containing fundecs and sets of fundecs.*) - module C = D - module V = EmptyV - module G = Lattice.Unit - let startstate v = `Lifted([]) - let exitstate v = `Lifted([]) - - let get_list list = match list with - | `Lifted e -> e - | _ -> failwith "Error loopfreeCallstring (get_list): The list cannot be derived from Top or Bottom!" - - let loop_detected f = function - (* note: a call string contains each Fundec at most once *) - | `Left ele -> CilType.Fundec.equal f ele - | `Right set -> FundecSet.mem f set - - let add_to_set old = function - | `Left ele -> FundecSet.add ele old - | `Right set -> FundecSet.join old set - - let rec callee_state f prev_set prev_list = function - | [] -> (`Left f)::(List.rev prev_list) (* f is not yet contained in the call string *) - | e::rem_list -> - let new_set = add_to_set prev_set e in - if loop_detected f e (* f is already present in the call string *) - then (`Right new_set)::rem_list (* combine all elements of the call cycle in a set *) - else callee_state f new_set (e::prev_list) rem_list - - let callee_state f ctx = `Lifted(callee_state f (FundecSet.empty ()) [] (get_list ctx.local)) - - let enter ctx r f args = [ctx.local, callee_state f ctx] - - let threadenter ctx ~multiple lval v args = [callee_state (Cilfacade.find_varinfo_fundec v) ctx] - - let combine_env ctx lval fexp f args fc au f_ask = ctx.local + module Either = Printable.Either (CilType.Fundec) (FundecSet) + + module C = Printable.Liszt (Either) + + let append fd current = + let loop_detected f = function + (* note: a call string contains each Fundec at most once *) + | `Left ele -> CilType.Fundec.equal f ele + | `Right set -> FundecSet.mem f set + in + let add_to_set old = function + | `Left ele -> FundecSet.add ele old + | `Right set -> FundecSet.join old set + in + let rec append f prev_set prev_list = function + | [] -> (`Left f)::(List.rev prev_list) (* f is not yet contained in the call string *) + | e::rem_list -> + let new_set = add_to_set prev_set e in + if loop_detected f e (* f is already present in the call string *) + then (`Right new_set)::rem_list (* combine all elements of the call cycle in a set *) + else append f new_set (e::prev_list) rem_list + in + append fd (FundecSet.empty ()) [] current + + let startcontext () = [] + let context ctx fd x = append fd (ctx.context ()) end let _ = MCP.register_analysis (module Spec : MCPSpec) diff --git a/src/analyses/mCP.ml b/src/analyses/mCP.ml index 50e2f62931..f2f36b1360 100644 --- a/src/analyses/mCP.ml +++ b/src/analyses/mCP.ml @@ -83,10 +83,10 @@ struct check_deps !activated; activated := topo_sort_an !activated; begin - match get_string_list "ana.ctx_sens" with + match get_string_list "ana.ctx_sens" with | [] -> (* use values of "ana.ctx_insens" (blacklist) *) let cont_inse = map' find_id @@ get_string_list "ana.ctx_insens" in - activated_ctx_sens := List.filter (fun (n, _) -> not (List.mem n cont_inse)) !activated; + activated_ctx_sens := List.filter (fun (n, _) -> not (List.mem n cont_inse)) !activated; | sens -> (* use values of "ana.ctx_sens" (whitelist) *) let cont_sens = map' find_id @@ sens in activated_ctx_sens := List.filter (fun (n, _) -> List.mem n cont_sens) !activated; @@ -113,19 +113,18 @@ struct let ys = fold_left one_el [] xs in List.rev ys, !dead - let context fd x = - let x = spec_list x in - filter_map (fun (n,(module S:MCPSpec),d) -> - if Set.is_empty !act_cont_sens || not (Set.mem n !act_cont_sens) then (*n is insensitive*) - None - else - Some (n, Obj.repr @@ S.context fd (Obj.obj d)) - ) x - let exitstate v = map (fun (n,{spec=(module S:MCPSpec); _}) -> n, Obj.repr @@ S.exitstate v) !activated let startstate v = map (fun (n,{spec=(module S:MCPSpec); _}) -> n, Obj.repr @@ S.startstate v) !activated let morphstate v x = map (fun (n,(module S:MCPSpec),d) -> n, Obj.repr @@ S.morphstate v (Obj.obj d)) (spec_list x) + let startcontext () = + filter_map (fun (n,{spec=(module S:MCPSpec); _}) -> + if Set.is_empty !act_cont_sens || not (Set.mem n !act_cont_sens) then (*n is insensitive*) + None + else + Some (n, Obj.repr @@ S.startcontext ()) + ) !activated + let rec assoc_replace (n,c) = function | [] -> failwith "assoc_replace" | (n',c')::xs -> if n=n' then (n,c)::xs else (n',c') :: assoc_replace (n,c) xs @@ -157,20 +156,20 @@ struct else iter (uncurry spawn_one) @@ group_assoc_eq Basetype.Variables.equal xs - let do_sideg ctx (xs:(V.t * (WideningTokens.TS.t * G.t)) list) = + let do_sideg ctx (xs:(V.t * (WideningTokenLifter.TS.t * G.t)) list) = let side_one v dts = let side_one_ts ts d = (* Do side effects with the tokens that were active at the time. Transfer functions have exited the with_side_token wrappers by now. *) - let old_side_tokens = !WideningTokens.side_tokens in - WideningTokens.side_tokens := ts; + let old_side_tokens = !WideningTokenLifter.side_tokens in + WideningTokenLifter.side_tokens := ts; Fun.protect (fun () -> ctx.sideg v @@ fold_left G.join (G.bot ()) d ) ~finally:(fun () -> - WideningTokens.side_tokens := old_side_tokens + WideningTokenLifter.side_tokens := old_side_tokens ) in - iter (uncurry side_one_ts) @@ group_assoc_eq WideningTokens.TS.equal dts + iter (uncurry side_one_ts) @@ group_assoc_eq WideningTokenLifter.TS.equal dts in iter (uncurry side_one) @@ group_assoc_eq V.equal xs @@ -235,6 +234,17 @@ struct if M.tracing then M.traceu "event" ""; ctx'.local + and context ctx fd x = + let ctx'' = outer_ctx "context_computation" ctx in + let x = spec_list x in + filter_map (fun (n,(module S:MCPSpec),d) -> + if Set.is_empty !act_cont_sens || not (Set.mem n !act_cont_sens) then (*n is insensitive*) + None + else + let ctx' : (S.D.t, S.G.t, S.C.t, S.V.t) ctx = inner_ctx "context_computation" ctx'' n d in + Some (n, Obj.repr @@ S.context ctx' fd (Obj.obj d)) + ) x + and branch (ctx:(D.t, G.t, C.t, V.t) ctx) (e:exp) (tv:bool) = let spawns = ref [] in let splits = ref [] in @@ -293,6 +303,10 @@ struct (* InvariantGlobal is special: it only goes to corresponding analysis and the argument variant is unlifted for it *) let (n, g): V.t = Obj.obj g in f ~q:(InvariantGlobal (Obj.repr g)) (Result.top ()) (n, spec n, assoc n ctx.local) + | Queries.YamlEntryGlobal (g, task) -> + (* YamlEntryGlobal is special: it only goes to corresponding analysis and the argument variant is unlifted for it *) + let (n, g): V.t = Obj.obj g in + f ~q:(YamlEntryGlobal (Obj.repr g, task)) (Result.top ()) (n, spec n, assoc n ctx.local) | Queries.PartAccess a -> Obj.repr (access ctx a) | Queries.IterSysVars (vq, fi) -> @@ -308,6 +322,13 @@ struct f (Result.top ()) (!base_id, spec !base_id, assoc !base_id ctx.local) *) | Queries.DYojson -> `Lifted (D.to_yojson ctx.local) + | Queries.GasExhausted f -> + if (get_int "ana.context.gas_value" >= 0) then + (* There is a lifter above this that will answer it, save to ask *) + ctx.ask (Queries.GasExhausted f) + else + (* Abort to avoid infinite recursion *) + false | _ -> let r = fold_left (f ~q) (Result.top ()) @@ spec_list ctx.local in do_sideg ctx !sides; @@ -338,7 +359,7 @@ struct | None -> (fun ?(multiple=false) v d -> failwith ("Cannot \"spawn\" in " ^ tfname ^ " context.")) in let sideg = match sides with - | Some sides -> (fun v g -> sides := (v, (!WideningTokens.side_tokens, g)) :: !sides) + | Some sides -> (fun v g -> sides := (v, (!WideningTokenLifter.side_tokens, g)) :: !sides) | None -> (fun v g -> failwith ("Cannot \"sideg\" in " ^ tfname ^ " context.")) in let emit = match emits with diff --git a/src/analyses/mallocFresh.ml b/src/analyses/mallocFresh.ml index ca14de0a3c..020046c678 100644 --- a/src/analyses/mallocFresh.ml +++ b/src/analyses/mallocFresh.ml @@ -10,7 +10,7 @@ struct (* must fresh variables *) module D = SetDomain.Reverse (SetDomain.ToppedSet (CilType.Varinfo) (struct let topname = "All variables" end)) (* need bot (top) for hoare widen *) - module C = D + include Analyses.ValueContexts(D) let name () = "mallocFresh" diff --git a/src/analyses/malloc_null.ml b/src/analyses/malloc_null.ml index f993db0c6e..5e6225caac 100644 --- a/src/analyses/malloc_null.ml +++ b/src/analyses/malloc_null.ml @@ -13,7 +13,7 @@ struct module Addr = ValueDomain.Addr module D = ValueDomain.AddrSetDomain - module C = ValueDomain.AddrSetDomain + include Analyses.ValueContexts(D) module P = IdentityP (D) (* diff --git a/src/analyses/memLeak.ml b/src/analyses/memLeak.ml index f2d707067d..362800b7b4 100644 --- a/src/analyses/memLeak.ml +++ b/src/analyses/memLeak.ml @@ -14,13 +14,13 @@ struct let name () = "memLeak" module D = ToppedVarInfoSet - module C = D + include Analyses.ValueContexts(D) module P = IdentityP (D) module V = UnitV module G = WasMallocCalled - let context _ d = d + let context ctx _ d = d let must_be_single_threaded ~since_start ctx = ctx.ask (Queries.MustBeSingleThreaded { since_start }) diff --git a/src/analyses/memOutOfBounds.ml b/src/analyses/memOutOfBounds.ml index 51304f5bf5..914644a86e 100644 --- a/src/analyses/memOutOfBounds.ml +++ b/src/analyses/memOutOfBounds.ml @@ -20,9 +20,9 @@ struct include Analyses.IdentitySpec module D = Lattice.Unit - module C = D + include Analyses.ValueContexts(D) - let context _ _ = () + let context ctx _ _ = () let name () = "memOutOfBounds" diff --git a/src/analyses/mutexAnalysis.ml b/src/analyses/mutexAnalysis.ml index 138f65ab47..3710d2db9c 100644 --- a/src/analyses/mutexAnalysis.ml +++ b/src/analyses/mutexAnalysis.ml @@ -1,9 +1,12 @@ (** Must lockset and protecting lockset analysis ([mutex]). *) module M = Messages +module Mval = ValueDomain.Mval module Addr = ValueDomain.Addr -module Lockset = LockDomain.Lockset -module Mutexes = LockDomain.Mutexes +module AddrRW = LockDomain.AddrRW +module MustLockset = LockDomain.MustLockset +module MustLocksetRW = LockDomain.MustLocksetRW +module MustMultiplicity = LockDomain.MustMultiplicity module LF = LibraryFunctions open GoblintCil open Analyses @@ -15,40 +18,10 @@ module Spec = struct module Arg = struct - module Multiplicity = struct - (* the maximum multiplicity which we keep track of precisely *) - let max_count () = 4 - - module Count = Lattice.Reverse ( - Lattice.Chain ( - struct - let n () = max_count () + 1 - let names x = if x = max_count () then Format.asprintf ">= %d" x else Format.asprintf "%d" x - end - ) - ) - - include MapDomain.MapTop_LiftBot (ValueDomain.Addr) (Count) - - let name () = "multiplicity" - - let increment v x = - let current = find v x in - if current = max_count () then - x - else - add v (current + 1) x - - let decrement v x = - let current = find v x in - if current = 0 then - (x, true) - else - (add v (current - 1) x, current - 1 = 0) - end - - module D = struct include Lattice.Prod(Lockset)(Multiplicity) - let empty () = (Lockset.empty (), Multiplicity.empty ()) + module D = + struct + include Lattice.Prod (MustLocksetRW) (MustMultiplicity) + let empty () = (MustLocksetRW.empty (), MustMultiplicity.empty ()) end @@ -60,7 +33,7 @@ struct module V = struct - include Printable.Either (struct include CilType.Varinfo let name () = "protecting" end) (struct include ValueDomain.Addr let name () = "protected" end) + include Printable.Either (struct include CilType.Varinfo let name () = "protecting" end) (struct include LockDomain.MustLock let name () = "protected" end) let name () = "mutex" let protecting x = `Left x let protected x = `Right x @@ -98,17 +71,17 @@ struct (** Collects information about which variables are protected by which mutexes *) module GProtecting: sig include Lattice.S - val make: write:bool -> recovered:bool -> Mutexes.t -> t - val get: write:bool -> Queries.Protection.t -> t -> Mutexes.t + val make: write:bool -> recovered:bool -> MustLockset.t -> t + val get: write:bool -> Queries.Protection.t -> t -> MustLockset.t end = struct - include MakeP (LockDomain.Simple) + include MakeP (MustLockset) let make ~write ~recovered locks = (* If the access is not a write, set to T so intersection with current write-protecting is identity *) - let wlocks = if write then locks else Mutexes.top () in + let wlocks = if write then locks else MustLockset.all () in if recovered then (* If we are in single-threaded mode again, this does not need to be added to set of mutexes protecting in mt-mode only *) - ((locks, wlocks), (Mutexes.top (), Mutexes.top ())) + ((locks, wlocks), (MustLockset.all (), MustLockset.all ())) else ((locks, wlocks), (locks, wlocks)) end @@ -146,38 +119,60 @@ struct let create_protected protected = `Lifted2 protected end - let add ctx (l:Mutexes.elt*bool) = - let s,m = ctx.local in - let s' = Lockset.add l s in - match Addr.to_mval (fst l) with - | Some mval when MutexTypeAnalysis.must_be_recursive ctx mval -> - (s', Multiplicity.increment (fst l) m) - | _ -> (s', m) - - let remove' ctx ~warn l = - let s, m = ctx.local in - let rm s = Lockset.remove (l, true) (Lockset.remove (l, false) s) in - if warn && (not (Lockset.mem (l,true) s || Lockset.mem (l,false) s)) then M.warn "unlocking mutex (%a) which may not be held" Addr.pretty l; - match Addr.to_mval l with - | Some mval when MutexTypeAnalysis.must_be_recursive ctx mval -> - let m',rmed = Multiplicity.decrement l m in - if rmed then - (rm s, m') + let add ctx ((addr, rw): AddrRW.t): D.t = + match addr with + | Addr ((v, o) as mv) -> + let (s, m) = ctx.local in + let s' = MustLocksetRW.add_mval_rw (mv, rw) s in + let m' = + match ctx.ask (Queries.MutexType (v, Offset.Unit.of_offs o)) with + | `Lifted Recursive -> MustMultiplicity.increment mv m + | `Lifted NonRec -> + if MustLocksetRW.mem_mval mv s then + M.error ~category:M.Category.Behavior.Undefined.double_locking "Acquiring a non-recursive mutex that is already held"; + m + | `Bot | `Top -> m + in + (s', m') + | NullPtr -> + M.warn "locking NULL mutex"; + ctx.local + | StrPtr _ + | UnknownPtr -> ctx.local + + let remove' ctx ~warn (addr: Addr.t): D.t = + match addr with + | StrPtr _ + | UnknownPtr -> ctx.local + | NullPtr -> + if warn then + M.warn "unlocking NULL mutex"; + ctx.local + | Addr mv -> + let (s, m) = ctx.local in + if warn && not (MustLocksetRW.mem_mval mv s) then + M.warn "unlocking mutex (%a) which may not be held" Mval.pretty mv; + if MutexTypeAnalysis.must_be_recursive ctx mv then ( + let (m', rmed) = MustMultiplicity.decrement mv m in + if rmed then + (* TODO: don't repeat the same semantic_equal checks *) + (* TODO: rmed per lockset element, not aggregated *) + (MustLocksetRW.remove_mval mv s, m') + else + (s, m') + ) else - (s, m') - | _ -> (rm s, m) + (MustLocksetRW.remove_mval mv s, m) (* Should decrement something if may be recursive? No: https://github.com/goblint/analyzer/pull/1430#discussion_r1615266081. *) let remove = remove' ~warn:true - let remove_all ctx = + let remove_all ctx: D.t = (* Mutexes.iter (fun m -> ctx.emit (MustUnlock m) ) (D.export_locks ctx.local); *) (* TODO: used to have remove_nonspecial, which kept v.vname.[0] = '{' variables *) M.warn "unlocking unknown mutex which may not be held"; - (Lockset.empty (), Multiplicity.empty ()) - - let empty () = (Lockset.empty (), Multiplicity.empty ()) + D.empty () end include LocksetAnalysis.MakeMust (Arg) let name () = "mutex" @@ -190,7 +185,7 @@ struct module GProtected = Arg.GProtected module G = Arg.G - module GM = Hashtbl.Make (ValueDomain.Addr) + module GM = Hashtbl.Make (LockDomain.MustLock) let max_protected = ref 0 let num_mutexes = ref 0 @@ -201,46 +196,46 @@ struct num_mutexes := 0; sum_protected := 0 - let query ctx (type a) (q: a Queries.t): a Queries.result = + let query (ctx: (D.t, _, _, V.t) ctx) (type a) (q: a Queries.t): a Queries.result = let ls, m = ctx.local in (* get the set of mutexes protecting the variable v in the given mode *) let protecting ~write mode v = GProtecting.get ~write mode (G.protecting (ctx.global (V.protecting v))) in - let non_overlapping locks1 locks2 = Mutexes.is_empty @@ Mutexes.inter locks1 locks2 in match q with - | Queries.MayBePublic _ when Lockset.is_bot ls -> false + | Queries.MayBePublic _ when MustLocksetRW.is_all ls -> false | Queries.MayBePublic {global=v; write; protection} -> - let held_locks = Lockset.export_locks (Lockset.filter snd ls) in + let held_locks = MustLocksetRW.to_must_lockset (MustLocksetRW.filter snd ls) in let protecting = protecting ~write protection v in (* TODO: unsound in 29/24, why did we do this before? *) (* if Mutexes.mem verifier_atomic (Lockset.export_locks ctx.local) then false else *) - non_overlapping held_locks protecting - | Queries.MayBePublicWithout _ when Lockset.is_bot ls -> false + MustLockset.disjoint held_locks protecting + | Queries.MayBePublicWithout _ when MustLocksetRW.is_all ls -> false | Queries.MayBePublicWithout {global=v; write; without_mutex; protection} -> - let held_locks = Lockset.export_locks @@ fst @@ Arg.remove' ctx ~warn:false without_mutex in + let held_locks = MustLockset.remove without_mutex (MustLocksetRW.to_must_lockset ls) in let protecting = protecting ~write protection v in (* TODO: unsound in 29/24, why did we do this before? *) (* if Mutexes.mem verifier_atomic (Lockset.export_locks (Lockset.remove (without_mutex, true) ctx.local)) then false else *) - non_overlapping held_locks protecting - | Queries.MustBeProtectedBy {mutex; global=v; write; protection} -> - let mutex_lockset = Lockset.export_locks @@ Lockset.singleton (mutex, true) in + MustLockset.disjoint held_locks protecting + | Queries.MustBeProtectedBy {mutex = ml; global=v; write; protection} -> let protecting = protecting ~write protection v in (* TODO: unsound in 29/24, why did we do this before? *) (* if LockDomain.Addr.equal mutex (LockDomain.Addr.of_var LF.verifier_atomic_var) then true else *) - Mutexes.leq mutex_lockset protecting + MustLockset.mem ml protecting + | Queries.MustProtectingLocks g -> + protecting ~write:true Strong g | Queries.MustLockset -> - let held_locks = Lockset.export_locks (Lockset.filter snd ls) in - Mutexes.fold (fun addr ls -> Queries.AD.add addr ls) held_locks (Queries.AD.empty ()) + let held_locks = MustLocksetRW.to_must_lockset (MustLocksetRW.filter snd ls) in + held_locks | Queries.MustBeAtomic -> - let held_locks = Lockset.export_locks (Lockset.filter snd ls) in - Mutexes.mem (LockDomain.Addr.of_var LF.verifier_atomic_var) held_locks - | Queries.MustProtectedVars {mutex = m; write} -> - let protected = GProtected.get ~write Strong (G.protected (ctx.global (V.protected m))) in + let held_locks = MustLocksetRW.to_must_lockset (MustLocksetRW.filter snd ls) in + MustLockset.mem (LF.verifier_atomic_var, `NoOffset) held_locks (* TODO: Mval.of_var *) + | Queries.MustProtectedVars {mutex; write} -> + let protected = GProtected.get ~write Strong (G.protected (ctx.global (V.protected mutex))) in VarSet.fold (fun v acc -> Queries.VS.add v acc ) protected (Queries.VS.empty ()) @@ -252,8 +247,8 @@ struct | `Left g' -> (* protecting *) if GobConfig.get_bool "dbg.print_protection" then ( let protecting = GProtecting.get ~write:false Strong (G.protecting (ctx.global g)) in (* readwrite protecting *) - let s = Mutexes.cardinal protecting in - M.info_noloc ~category:Race "Variable %a read-write protected by %d mutex(es): %a" CilType.Varinfo.pretty g' s Mutexes.pretty protecting + let s = MustLockset.cardinal protecting in + M.info_noloc ~category:Race "Variable %a read-write protected by %d mutex(es): %a" CilType.Varinfo.pretty g' s MustLockset.pretty protecting ) | `Right m -> (* protected *) if GobConfig.get_bool "dbg.print_protection" then ( @@ -262,14 +257,14 @@ struct max_protected := max !max_protected s; sum_protected := !sum_protected + s; incr num_mutexes; - M.info_noloc ~category:Race "Mutex %a read-write protects %d variable(s): %a" ValueDomain.Addr.pretty m s VarSet.pretty protected + M.info_noloc ~category:Race "Mutex %a read-write protects %d variable(s): %a" LockDomain.MustLock.pretty m s VarSet.pretty protected ) end | _ -> Queries.Result.top q module A = struct - include Lockset + include MustLocksetRW let name () = "lock" let may_race ls1 ls2 = (* not mutually exclusive *) @@ -287,7 +282,7 @@ struct let access ctx (a: Queries.access) = fst ctx.local - let event ctx e octx = + let event (ctx: (D.t, _, _, V.t) ctx) e (octx: (D.t, _, _, _) ctx) = match e with | Events.Access {exp; ad; kind; _} when ThreadFlag.has_ever_been_multi (Analyses.ask_of_ctx ctx) -> (* threadflag query in post-threadspawn ctx *) let is_recovered_to_st = not (ThreadFlag.is_currently_multi (Analyses.ask_of_ctx ctx)) in @@ -297,8 +292,8 @@ struct (*privatization*) match var_opt with | Some v -> - if not (Lockset.is_bot (fst octx.local)) then - let locks = Lockset.export_locks (Lockset.filter snd (fst octx.local)) in + if not (MustLocksetRW.is_all (fst octx.local)) then + let locks = MustLocksetRW.to_must_lockset (MustLocksetRW.filter snd (fst octx.local)) in let write = match kind with | Write | Free -> true | Read -> false @@ -314,10 +309,10 @@ struct let held_weak = protecting Weak in let vs = VarSet.singleton v in let protected = G.create_protected @@ GProtected.make ~write vs in - Mutexes.iter (fun addr -> ctx.sideg (V.protected addr) protected) held_strong; + MustLockset.iter (fun ml -> ctx.sideg (V.protected ml) protected) held_strong; (* If the mutex set here is top, it is actually not accessed *) - if is_recovered_to_st && not @@ Mutexes.is_top held_weak then - Mutexes.iter (fun addr -> ctx.sideg (V.protected addr) protected) held_weak; + if is_recovered_to_st && not @@ MustLockset.is_all held_weak then + MustLockset.iter (fun ml -> ctx.sideg (V.protected ml) protected) held_weak; ) | None -> M.info ~category:Unsound "Write to unknown address: privatization is unsound." in diff --git a/src/analyses/mutexEventsAnalysis.ml b/src/analyses/mutexEventsAnalysis.ml index e644d68d92..5ea0afc809 100644 --- a/src/analyses/mutexEventsAnalysis.ml +++ b/src/analyses/mutexEventsAnalysis.ml @@ -4,10 +4,7 @@ module M = Messages module Addr = ValueDomain.Addr -module Lockset = LockDomain.Lockset -module Mutexes = LockDomain.Mutexes module LF = LibraryFunctions -open Batteries open GoblintCil open Analyses open GobConfig @@ -21,7 +18,7 @@ struct let eval_exp_addr (a: Queries.ask) exp = a.f (Queries.MayPointTo exp) let lock ctx rw may_fail nonzero_return_when_aquired a lv_opt arg = - let compute_refine_split (e:Mutexes.elt) = match e with + let compute_refine_split (e: Addr.t) = match e with | Addr a -> let arg_e = AddrOf (PreValueDomain.Mval.to_cil a) in if not (CilType.Exp.equal arg arg_e) then @@ -54,12 +51,12 @@ struct let return ctx exp fundec : D.t = (* deprecated but still valid SV-COMP convention for atomic block *) - if get_bool "ana.sv-comp.functions" && String.starts_with fundec.svar.vname "__VERIFIER_atomic_" then + if get_bool "ana.sv-comp.functions" && String.starts_with fundec.svar.vname ~prefix:"__VERIFIER_atomic_" then ctx.emit (Events.Unlock (LockDomain.Addr.of_var LF.verifier_atomic_var)) let body ctx f : D.t = (* deprecated but still valid SV-COMP convention for atomic block *) - if get_bool "ana.sv-comp.functions" && String.starts_with f.svar.vname "__VERIFIER_atomic_" then + if get_bool "ana.sv-comp.functions" && String.starts_with f.svar.vname ~prefix:"__VERIFIER_atomic_" then ctx.emit (Events.Lock (LockDomain.Addr.of_var LF.verifier_atomic_var, true)) let special (ctx: (unit, _, _, _) ctx) lv f arglist : D.t = diff --git a/src/analyses/mutexGhosts.ml b/src/analyses/mutexGhosts.ml new file mode 100644 index 0000000000..3deec3ef59 --- /dev/null +++ b/src/analyses/mutexGhosts.ml @@ -0,0 +1,191 @@ +(** Analysis for generating ghost variables corresponding to mutexes ([mutexGhosts]). *) + +open Analyses + +module NodeSet = Queries.NS + + +module Spec = +struct + include UnitAnalysis.Spec + let name () = "mutexGhosts" + + module V = + struct + include Printable.Either3 (Node) (LockDomain.MustLock) (BoolDomain.Bool) + let node x = `Left x + let lock x = `Middle x + let threadcreate = `Right false + let update = `Right true + let is_write_only = function + | `Left _ -> false + | `Middle _ -> true + | `Right false -> false + | `Right true -> true + end + + module Locked = + struct + include LockDomain.MayLocksetNoRW + let name () = "locked" + end + module Unlocked = + struct + include LockDomain.MayLocksetNoRW + let name () = "unlocked" + end + module MultiThread = + struct + include BoolDomain.MayBool + let name () = "multithread" + end + module G = + struct + include Lattice.Lift2 (Lattice.Prod3 (Locked) (Unlocked) (MultiThread)) (Lattice.Lift2 (BoolDomain.MayBool) (NodeSet)) + let node = function + | `Bot -> (Locked.bot (), Unlocked.bot (), MultiThread.bot ()) + | `Lifted1 x -> x + | _ -> failwith "MutexGhosts.node" + let lock = function + | `Bot -> BoolDomain.MayBool.bot () + | `Lifted2 (`Lifted1 x) -> x + | _ -> failwith "MutexGhosts.lock" + let threadcreate = function + | `Bot -> NodeSet.bot () + | `Lifted2 (`Lifted2 x) -> x + | _ -> failwith "MutexGhosts.threadcreate" + let update = threadcreate + let create_node node = `Lifted1 node + let create_lock lock = `Lifted2 (`Lifted1 lock) + let create_threadcreate threadcreate = `Lifted2 (`Lifted2 threadcreate) + let create_update = create_threadcreate + end + + let mustlock_of_addr (addr: LockDomain.Addr.t): LockDomain.MustLock.t option = + match addr with + | Addr mv when LockDomain.Mval.is_definite mv -> Some (LockDomain.MustLock.of_mval mv) + | _ -> None + + let event ctx e octx = + let verifier_atomic_addr = LockDomain.Addr.of_var LibraryFunctions.verifier_atomic_var in + begin match e with + | Events.Lock (l, _) when not (LockDomain.Addr.equal l verifier_atomic_addr) -> + ctx.sideg (V.node ctx.prev_node) (G.create_node (Locked.singleton l, Unlocked.bot (), MultiThread.bot ())); + if !AnalysisState.postsolving then ( + ctx.sideg V.update (G.create_update (NodeSet.singleton ctx.prev_node)); + let (locked, _, _) = G.node (ctx.global (V.node ctx.prev_node)) in + if Locked.cardinal locked > 1 then ( + Locked.iter (fun lock -> + Option.iter (fun lock -> + ctx.sideg (V.lock lock) (G.create_lock true) + ) (mustlock_of_addr lock) + ) locked + ); + ) + | Events.Unlock l when not (LockDomain.Addr.equal l verifier_atomic_addr) -> + ctx.sideg (V.node ctx.prev_node) (G.create_node (Locked.bot (), Unlocked.singleton l, MultiThread.bot ())); + if !AnalysisState.postsolving then ( + ctx.sideg V.update (G.create_update (NodeSet.singleton ctx.prev_node)); + let (_, unlocked, _) = G.node (ctx.global (V.node ctx.prev_node)) in + if Locked.cardinal unlocked > 1 then ( + Locked.iter (fun lock -> + Option.iter (fun lock -> + ctx.sideg (V.lock lock) (G.create_lock true) + ) (mustlock_of_addr lock) + ) unlocked + ); + ) + | Events.EnterMultiThreaded -> + ctx.sideg (V.node ctx.prev_node) (G.create_node (Locked.bot (), Unlocked.bot (), true)); + if !AnalysisState.postsolving then + ctx.sideg V.update (G.create_update (NodeSet.singleton ctx.prev_node)); + | _ -> () + end; + ctx.local + + let threadspawn ctx ~multiple lval f args octx = + ctx.sideg V.threadcreate (G.create_threadcreate (NodeSet.singleton ctx.node)); + ctx.local + + let ghost_var_available ctx = function + | WitnessGhost.Var.Locked ((v, o) as lock) -> not (Offset.Z.contains_index o) && not (G.lock (ctx.global (V.lock lock))) + | Multithreaded -> true + + let ghost_var_available ctx v = + WitnessGhost.enabled () && ghost_var_available ctx v + + module VariableSet = Set.Make (YamlWitnessType.GhostInstrumentation.Variable) + + let query ctx (type a) (q: a Queries.t): a Queries.result = + match q with + | GhostVarAvailable v -> ghost_var_available ctx v + | YamlEntryGlobal (g, task) -> + let g: V.t = Obj.obj g in + begin match g with + | `Right true when YamlWitness.entry_type_enabled YamlWitnessType.GhostInstrumentation.entry_type -> + let nodes = G.update (ctx.global g) in + let (variables, location_updates) = NodeSet.fold (fun node (variables, location_updates) -> + let (locked, unlocked, multithread) = G.node (ctx.global (V.node node)) in + let variables' = + Locked.fold (fun l acc -> + match mustlock_of_addr l with + | Some l when ghost_var_available ctx (Locked l) -> + let variable = WitnessGhost.variable' (Locked l) in + VariableSet.add variable acc + | _ -> + acc + ) (Locked.union locked unlocked) variables + in + let updates = + Locked.fold (fun l acc -> + match mustlock_of_addr l with + | Some l when ghost_var_available ctx (Locked l) -> + let update = WitnessGhost.update' (Locked l) GoblintCil.one in + update :: acc + | _ -> + acc + ) locked [] + in + let updates = + Unlocked.fold (fun l acc -> + match mustlock_of_addr l with + | Some l when ghost_var_available ctx (Locked l) -> + let update = WitnessGhost.update' (Locked l) GoblintCil.zero in + update :: acc + | _ -> + acc + ) unlocked updates + in + let (variables', updates) = + if not (GobConfig.get_bool "exp.earlyglobs") && multithread then ( + if ghost_var_available ctx Multithreaded then ( + let variable = WitnessGhost.variable' Multithreaded in + let update = WitnessGhost.update' Multithreaded GoblintCil.one in + let variables' = VariableSet.add variable variables' in + (variables', update :: updates) + ) + else + (variables', updates) + ) + else + (variables', updates) + in + match updates with + | [] -> (variables', location_updates) (* don't add location_update with no updates *) + | _ -> + let location_update = WitnessGhost.location_update' ~node ~updates in + (variables', location_update :: location_updates) + ) nodes (VariableSet.empty, []) + in + let entry = WitnessGhost.instrumentation_entry ~task ~variables:(VariableSet.elements variables) ~location_updates in + Queries.YS.singleton entry + | `Left _ -> Queries.Result.top q + | `Middle _ -> Queries.Result.top q + | `Right _ -> Queries.Result.top q + end + | InvariantGlobalNodes -> (G.threadcreate (ctx.global V.threadcreate): NodeSet.t) + | _ -> Queries.Result.top q +end + +let _ = + MCP.register_analysis ~dep:["mutexEvents"] (module Spec : MCPSpec) diff --git a/src/analyses/mutexTypeAnalysis.ml b/src/analyses/mutexTypeAnalysis.ml index 441f2e9953..4a993bbd7d 100644 --- a/src/analyses/mutexTypeAnalysis.ml +++ b/src/analyses/mutexTypeAnalysis.ml @@ -12,16 +12,17 @@ struct let name () = "pthreadMutexType" - (* Removing indexes here avoids complicated lookups and allows to have the LVals as vars here, at the price that different types of mutexes in arrays are not dinstinguished *) - module O = Offset.Unit - - module V = struct - include Printable.Prod(CilType.Varinfo)(O) (* TODO: use Mval.Unit *) + module V = + struct + (* Removing indexes here avoids complicated lookups and allows to have the LVals as vars here, at the price that different types of mutexes in arrays are not dinstinguished *) + include Mval.Unit let is_write_only _ = false end module G = MAttr + module O = Offset.Unit + (* transfer functions *) let assign ctx (lval:lval) (rval:exp) : D.t = match lval with diff --git a/src/analyses/pthreadSignals.ml b/src/analyses/pthreadSignals.ml index 70f1624922..68c2bf4f34 100644 --- a/src/analyses/pthreadSignals.ml +++ b/src/analyses/pthreadSignals.ml @@ -14,7 +14,7 @@ struct let name () = "pthreadSignals" module D = MustSignals - module C = MustSignals + include Analyses.ValueContexts(D) module G = SetDomain.ToppedSet (MHP) (struct let topname = "All Threads" end) let possible_vinfos (a: Queries.ask) cv_arg = diff --git a/src/analyses/region.ml b/src/analyses/region.ml index c2b40ff12f..e53dded304 100644 --- a/src/analyses/region.ml +++ b/src/analyses/region.ml @@ -16,7 +16,8 @@ struct module D = RegionDomain.RegionDom module G = RegPart - module C = D + include Analyses.ValueContexts(D) + module V = struct include Printable.UnitConf (struct let name = "partitions" end) diff --git a/src/analyses/stackTrace.ml b/src/analyses/stackTrace.ml index dd2cedf871..56656c0639 100644 --- a/src/analyses/stackTrace.ml +++ b/src/analyses/stackTrace.ml @@ -2,7 +2,6 @@ open GoblintCil open Analyses -module LF = LibraryFunctions module Spec (D: StackDomain.S) (N: sig val name : string end)= struct @@ -10,7 +9,7 @@ struct let name () = N.name module D = D - module C = D + include Analyses.ValueContexts(D) (* transfer functions *) @@ -31,7 +30,7 @@ struct let name () = "stack_loc" module D = StackDomain.Dom3 - module C = StackDomain.Dom3 + include Analyses.ValueContexts(D) (* transfer functions *) @@ -41,7 +40,6 @@ struct let combine_env ctx lval fexp f args fc au f_ask = ctx.local (* keep local as opposed to IdentitySpec *) - let startstate v = D.bot () let exitstate v = D.top () diff --git a/src/analyses/symbLocks.ml b/src/analyses/symbLocks.ml index 6fd18de6ff..ab116c525d 100644 --- a/src/analyses/symbLocks.ml +++ b/src/analyses/symbLocks.ml @@ -21,10 +21,8 @@ module Spec = struct include Analyses.DefaultSpec - exception Top - - module D = LockDomain.Symbolic - module C = LockDomain.Symbolic + module D = SymbLocksDomain.Symbolic + include Analyses.ValueContexts(D) let name () = "symb_locks" diff --git a/src/analyses/threadAnalysis.ml b/src/analyses/threadAnalysis.ml index ed30e3633e..435e1a6afe 100644 --- a/src/analyses/threadAnalysis.ml +++ b/src/analyses/threadAnalysis.ml @@ -12,7 +12,7 @@ struct let name () = "thread" module D = ConcDomain.CreatedThreadSet - module C = D + include Analyses.ValueContexts(D) module G = ConcDomain.ThreadCreation module V = struct @@ -95,9 +95,7 @@ struct let startstate v = D.bot () let threadenter ctx ~multiple lval f args = - if multiple then - (let tid = ThreadId.get_current_unlift (Analyses.ask_of_ctx ctx) in - ctx.sideg tid (true, TS.bot (), false)); + (* ctx is of creator, side-effects to denote non-uniqueness are performed in threadspawn *) [D.bot ()] let threadspawn ctx ~multiple lval f args fctx = @@ -106,9 +104,9 @@ struct let repeated = D.mem tid ctx.local in let eff = match creator with - | `Lifted ctid -> (repeated, TS.singleton ctid, false) - | `Top -> (true, TS.bot (), false) - | `Bot -> (false, TS.bot (), false) + | `Lifted ctid -> (repeated || multiple, TS.singleton ctid, false) + | `Top -> (true, TS.bot (), false) + | `Bot -> (multiple, TS.bot (), false) in ctx.sideg tid eff; D.join ctx.local (D.singleton tid) diff --git a/src/analyses/threadEscape.ml b/src/analyses/threadEscape.ml index fd7c564f2f..4311e72558 100644 --- a/src/analyses/threadEscape.ml +++ b/src/analyses/threadEscape.ml @@ -21,7 +21,7 @@ struct let name () = "escape" module D = EscapeDomain.EscapedVars - module C = EscapeDomain.EscapedVars + include Analyses.ValueContexts(D) module V = VarinfoV module G = ThreadIdSet diff --git a/src/analyses/threadFlag.ml b/src/analyses/threadFlag.ml index a751ae074a..e1efcaaba5 100644 --- a/src/analyses/threadFlag.ml +++ b/src/analyses/threadFlag.ml @@ -19,13 +19,14 @@ struct module Flag = ThreadFlagDomain.Simple module D = Flag - module C = Flag + include Analyses.ValueContexts(D) module P = IdentityP (D) module V = UnitV module G = BoolDomain.MayBool let name () = "threadflag" + let startstate v = Flag.bot () let exitstate v = Flag.get_multi () diff --git a/src/analyses/threadId.ml b/src/analyses/threadId.ml index 70ae13661e..80bab1ebf9 100644 --- a/src/analyses/threadId.ml +++ b/src/analyses/threadId.ml @@ -53,14 +53,14 @@ struct (** Uniqueness Counter * TID * (All thread creates of current thread * All thread creates of the current function and its callees) *) module D = Lattice.Prod3 (N) (ThreadLifted) (Created) - module C = D + include Analyses.ValueContexts(D) module P = IdentityP (D) let tids = ref (Hashtbl.create 20) let name () = "threadid" - let context fd ((n,current,td) as d) = + let context ctx fd ((n,current,td) as d) = if GobConfig.get_bool "ana.thread.context.create-edges" then d else diff --git a/src/analyses/threadJoins.ml b/src/analyses/threadJoins.ml index 160b123e78..eddbe184da 100644 --- a/src/analyses/threadJoins.ml +++ b/src/analyses/threadJoins.ml @@ -19,7 +19,7 @@ struct (* The first component is the set of must-joined TIDs, the second component tracks whether all TIDs recorded in MustTIDs have been exited cleanly, *) (* i.e., all created subthreads have also been joined. This is helpful as there is no set of all transitively created threads available. *) module D = Lattice.Prod(MustTIDs)(CleanExit) - module C = D + include Analyses.ValueContexts(D) module G = D module V = struct @@ -105,6 +105,7 @@ struct let (callee_joined, callee_clean) = au in (MustTIDs.union caller_joined callee_joined, local_clean && callee_clean) + let startstate v = (MustTIDs.empty (), true) let exitstate v = (MustTIDs.empty (), true) end diff --git a/src/analyses/threadReturn.ml b/src/analyses/threadReturn.ml index 0aed06851a..d72e2586e8 100644 --- a/src/analyses/threadReturn.ml +++ b/src/analyses/threadReturn.ml @@ -12,8 +12,8 @@ struct include Analyses.IdentitySpec let name () = "threadreturn" - module D = IntDomain.Booleans - module C = D + module D = BoolDomain.MayBool + include Analyses.ValueContexts(D) (* transfer functions *) diff --git a/src/analyses/tutorials/signs.ml b/src/analyses/tutorials/signs.ml index 2c26ad33b6..28f7e0acdc 100644 --- a/src/analyses/tutorials/signs.ml +++ b/src/analyses/tutorials/signs.ml @@ -50,7 +50,7 @@ struct (* Map of integers variables to our signs lattice. *) module D = MapDomain.MapBot (Basetype.Variables) (SL) - module C = D + include Analyses.ValueContexts(D) let startstate v = D.bot () let exitstate = startstate diff --git a/src/analyses/tutorials/taint.ml b/src/analyses/tutorials/taint.ml index 1015188d88..f62a5a4722 100644 --- a/src/analyses/tutorials/taint.ml +++ b/src/analyses/tutorials/taint.ml @@ -28,7 +28,9 @@ struct module C = Printable.Unit (* We are context insensitive in this analysis *) - let context _ _ = () + let context ctx _ _ = () + let startcontext () = () + (** Determines whether an expression [e] is tainted, given a [state]. *) let rec is_exp_tainted (state:D.t) (e:Cil.exp) = match e with diff --git a/src/analyses/tutorials/unitAnalysis.ml b/src/analyses/tutorials/unitAnalysis.ml index 319d0039ba..225767f010 100644 --- a/src/analyses/tutorials/unitAnalysis.ml +++ b/src/analyses/tutorials/unitAnalysis.ml @@ -38,6 +38,7 @@ struct let special ctx (lval: lval option) (f:varinfo) (arglist:exp list) : D.t = ctx.local + let startcontext () = () let startstate v = D.bot () let threadenter ctx ~multiple lval f args = [D.top ()] let threadspawn ctx ~multiple lval f args fctx = ctx.local diff --git a/src/analyses/unassumeAnalysis.ml b/src/analyses/unassumeAnalysis.ml index 265e9c6925..1b430e651a 100644 --- a/src/analyses/unassumeAnalysis.ml +++ b/src/analyses/unassumeAnalysis.ml @@ -29,7 +29,7 @@ struct type inv = { exp: Cil.exp; - uuid: string; + token: WideningToken.t; } let invs: inv NH.t = NH.create 100 @@ -71,21 +71,11 @@ struct | _ -> () ); - let loc_of_location (location: YamlWitnessType.Location.t): Cil.location = { - file = location.file_name; - line = location.line; - column = location.column; - byte = -1; - endLine = -1; - endColumn = -1; - endByte = -1; - synthetic = false; - } - in - - let yaml = match Yaml_unix.of_file (Fpath.v (GobConfig.get_string "witness.yaml.unassume")) with + let yaml = match GobResult.Syntax.(Fpath.of_string (GobConfig.get_string "witness.yaml.unassume") >>= Yaml_unix.of_file) with | Ok yaml -> yaml - | Error (`Msg m) -> failwith ("Yaml_unix.of_file: " ^ m) + | Error (`Msg m) -> + Logs.error "Yaml_unix.of_file: %s" m; + Svcomp.errorwith "witness missing" in let yaml_entries = yaml |> GobYaml.list |> BatResult.get_ok in @@ -100,7 +90,7 @@ struct let uuid = entry.metadata.uuid in let target_type = YamlWitnessType.EntryType.entry_type entry.entry_type in - let unassume_nodes_invariant ~loc ~nodes inv = + let unassume_nodes_invariant ~loc ~nodes ?i inv = let msgLoc: M.Location.t = CilLocation loc in match InvariantParser.parse_cabs inv with | Ok inv_cabs -> @@ -111,7 +101,7 @@ struct match InvariantParser.parse_cil inv_parser ~check:false ~fundec ~loc inv_cabs with | Ok inv_exp -> M.debug ~category:Witness ~loc:msgLoc "located invariant to %a: %a" Node.pretty n Cil.d_exp inv_exp; - NH.add invs n {exp = inv_exp; uuid} + NH.add invs n {exp = inv_exp; token = (uuid, i)} | Error e -> 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 @@ -123,7 +113,7 @@ struct in let unassume_location_invariant (location_invariant: YamlWitnessType.LocationInvariant.t) = - let loc = loc_of_location location_invariant.location in + let loc = YamlWitness.loc_of_location location_invariant.location in let inv = location_invariant.location_invariant.string in let msgLoc: M.Location.t = CilLocation loc in @@ -135,7 +125,7 @@ struct in let unassume_loop_invariant (loop_invariant: YamlWitnessType.LoopInvariant.t) = - let loc = loc_of_location loop_invariant.location in + let loc = YamlWitness.loc_of_location loop_invariant.location in let inv = loop_invariant.loop_invariant.string in let msgLoc: M.Location.t = CilLocation loc in @@ -164,7 +154,7 @@ struct M.debug ~category:Witness ~loc:msgLoc "located invariant to %a: %a" Node.pretty n Cil.d_exp inv_exp; if not (NH.mem pre_invs n) then NH.replace pre_invs n (EH.create 10); - EH.add (NH.find pre_invs n) pre_exp {exp = inv_exp; uuid} + EH.add (NH.find pre_invs n) pre_exp {exp = inv_exp; token = (uuid, None)} | Error e -> 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 @@ -185,7 +175,7 @@ struct in let unassume_precondition_loop_invariant (precondition_loop_invariant: YamlWitnessType.PreconditionLoopInvariant.t) = - let loc = loc_of_location precondition_loop_invariant.location in + let loc = YamlWitness.loc_of_location precondition_loop_invariant.location in let pre = precondition_loop_invariant.precondition.string in let inv = precondition_loop_invariant.loop_invariant.string in let msgLoc: M.Location.t = CilLocation loc in @@ -199,42 +189,42 @@ struct let unassume_invariant_set (invariant_set: YamlWitnessType.InvariantSet.t) = - let unassume_location_invariant (location_invariant: YamlWitnessType.InvariantSet.LocationInvariant.t) = - let loc = loc_of_location location_invariant.location in + let unassume_location_invariant ~i (location_invariant: YamlWitnessType.InvariantSet.LocationInvariant.t) = + let loc = YamlWitness.loc_of_location location_invariant.location in let inv = location_invariant.value in let msgLoc: M.Location.t = CilLocation loc in match Locator.find_opt location_locator loc with | Some nodes -> - unassume_nodes_invariant ~loc ~nodes inv + unassume_nodes_invariant ~loc ~nodes ~i inv | None -> M.warn ~category:Witness ~loc:msgLoc "couldn't locate invariant: %s" inv in - let unassume_loop_invariant (loop_invariant: YamlWitnessType.InvariantSet.LoopInvariant.t) = - let loc = loc_of_location loop_invariant.location in + let unassume_loop_invariant ~i (loop_invariant: YamlWitnessType.InvariantSet.LoopInvariant.t) = + let loc = YamlWitness.loc_of_location loop_invariant.location in let inv = loop_invariant.value in let msgLoc: M.Location.t = CilLocation loc in match Locator.find_opt loop_locator loc with | Some nodes -> - unassume_nodes_invariant ~loc ~nodes inv + unassume_nodes_invariant ~loc ~nodes ~i inv | None -> M.warn ~category:Witness ~loc:msgLoc "couldn't locate invariant: %s" inv in - let validate_invariant (invariant: YamlWitnessType.InvariantSet.Invariant.t) = + let validate_invariant i (invariant: YamlWitnessType.InvariantSet.Invariant.t) = let target_type = YamlWitnessType.InvariantSet.InvariantType.invariant_type invariant.invariant_type in match YamlWitness.invariant_type_enabled target_type, invariant.invariant_type with | true, LocationInvariant x -> - unassume_location_invariant x + unassume_location_invariant ~i x | true, LoopInvariant x -> - unassume_loop_invariant x + unassume_loop_invariant ~i x | false, (LocationInvariant _ | LoopInvariant _) -> M.info_noloc ~category:Witness "disabled invariant of type %s" target_type in - List.iter validate_invariant invariant_set.content + List.iteri validate_invariant invariant_set.content in match YamlWitness.entry_type_enabled target_type, entry.entry_type with @@ -249,13 +239,13 @@ struct | false, (LocationInvariant _ | LoopInvariant _ | PreconditionLoopInvariant _ | InvariantSet _) -> M.info_noloc ~category:Witness "disabled entry of type %s" target_type | _ -> - M.info_noloc ~category:Witness "cannot unassume entry of type %s" target_type + M.warn_noloc ~category:Witness "cannot unassume entry of type %s" target_type in List.iter (fun yaml_entry -> match YamlWitnessType.Entry.of_yaml yaml_entry with | Ok entry -> unassume_entry entry - | Error (`Msg e) -> M.info_noloc ~category:Witness "couldn't parse entry: %s" e + | Error (`Msg e) -> M.error_noloc ~category:Witness "couldn't parse entry: %s" e ) yaml_entries let emit_unassume ctx = @@ -272,9 +262,9 @@ struct M.info ~category:Witness "unassume invariant: %a" CilType.Exp.pretty e; if not !AnalysisState.postsolving then ( if not (GobConfig.get_bool "ana.unassume.precheck" && Queries.ID.to_bool (ctx.ask (EvalInt e)) = Some false) then ( - let uuids = x.uuid :: List.map (fun {uuid; _} -> uuid) xs in - ctx.emit (Unassume {exp = e; uuids}); - List.iter WideningTokens.add uuids + let tokens = x.token :: List.map (fun {token; _} -> token) xs in + ctx.emit (Unassume {exp = e; tokens}); + List.iter WideningTokenLifter.add tokens ) ); ctx.local diff --git a/src/analyses/uninit.ml b/src/analyses/uninit.ml index 8693599a4d..8c217cda4e 100644 --- a/src/analyses/uninit.ml +++ b/src/analyses/uninit.ml @@ -15,7 +15,7 @@ struct module Addr = ValueDomain.Addr module D = ValueDomain.AddrSetDomain - module C = ValueDomain.AddrSetDomain + include Analyses.ValueContexts(D) module P = IdentityP (D) type trans_in = D.t @@ -135,7 +135,7 @@ struct in let utar, uoth = unrollType target, unrollType other in match ofs, utar, uoth with - | `NoOffset, _ , _ when utar == uoth -> [v, rev cx] + | `NoOffset, _ , _ when CilType.Typ.equal utar uoth -> [v, rev cx] | `NoOffset, _ , TComp (c2,_) when not c2.cstruct -> (* unroll other (union) *) List.concat (List.rev_map (fun oth_f -> get_pfx v (`Field (oth_f, cx)) ofs utar oth_f.ftype) c2.cfields) diff --git a/src/analyses/varEq.ml b/src/analyses/varEq.ml index f56d3487bb..b220afc0d9 100644 --- a/src/analyses/varEq.ml +++ b/src/analyses/varEq.ml @@ -11,8 +11,6 @@ open Analyses module Spec = struct - exception Top - include Analyses.DefaultSpec module D = @@ -38,7 +36,7 @@ struct ) ss (Invariant.top ()) end - module C = D + include Analyses.ValueContexts(D) let name () = "var_eq" @@ -564,7 +562,7 @@ struct let r = eq_set_clos e ctx.local in if M.tracing then M.tracel "var_eq" "equalset %a = %a" d_plainexp e Queries.ES.pretty r; r - | Queries.Invariant context when GobConfig.get_bool "witness.invariant.exact" -> (* only exact equalities here *) + | Queries.Invariant context when GobConfig.get_bool "ana.var_eq.invariant.enabled" && GobConfig.get_bool "witness.invariant.exact" -> (* only exact equalities here *) let scope = Node.find_fundec ctx.node in D.invariant ~scope ctx.local | _ -> Queries.Result.top x diff --git a/src/analyses/wrapperFunctionAnalysis.ml b/src/analyses/wrapperFunctionAnalysis.ml index 9510304e56..32ca234e70 100644 --- a/src/analyses/wrapperFunctionAnalysis.ml +++ b/src/analyses/wrapperFunctionAnalysis.ml @@ -57,7 +57,7 @@ struct else remove unique_call counter |> add unique_call (count + 1) module D = Lattice.Prod (NodeFlatLattice) (UniqueCallCounter) - module C = D + include Analyses.ValueContexts(D) let wrappers = Hashtbl.create 13 @@ -144,6 +144,8 @@ module MallocWrapper : MCPSpec = struct Format.dprintf "@tid:%s" (ThreadLifted.show t) in Format.asprintf "(alloc@sid:%s%t%t)" (Node.show_id node) tid uniq_count + + let typ _ = GoblintCil.voidType end module NodeVarinfoMap = RichVarinfo.BiVarinfoMap.Make(ThreadNode) diff --git a/src/autoTune.ml b/src/autoTune.ml index e4b03acf84..05f651ee62 100644 --- a/src/autoTune.ml +++ b/src/autoTune.ml @@ -44,6 +44,34 @@ class functionVisitor(calling, calledBy, argLists, dynamicallyCalled) = object DoChildren end +exception Found +class findAllocsInLoops = object + inherit nopCilVisitor + + val mutable inloop = false + + method! vstmt stmt = + let outOfLoop stmt = + match stmt.skind with + | Loop _ -> inloop <- false; stmt + | _ -> stmt + in + match stmt.skind with + | Loop _ -> inloop <- true; ChangeDoChildrenPost(stmt, outOfLoop) + | _ -> DoChildren + + method! vinst = function + | Call (_, Lval (Var f, NoOffset), args,_,_) when LibraryFunctions.is_special f -> + Goblint_backtrace.protect ~mark:(fun () -> Cilfacade.FunVarinfo f) ~finally:Fun.id @@ fun () -> + let desc = LibraryFunctions.find f in + begin match desc.special args with + | Malloc _ + | Alloca _ when inloop -> raise Found + | _ -> DoChildren + end + | _ -> DoChildren +end + type functionCallMaps = { calling: FunctionSet.t FunctionCallMap.t; calledBy: (FunctionSet.t * int) FunctionCallMap.t; @@ -68,6 +96,7 @@ let functionArgs fd = (ResettableLazy.force functionCallMaps).argLists |> Functi let findMallocWrappers () = let isMalloc f = + Goblint_backtrace.wrap_val ~mark:(Cilfacade.FunVarinfo f) @@ fun () -> if LibraryFunctions.is_special f then ( let desc = LibraryFunctions.find f in match functionArgs f with @@ -104,7 +133,7 @@ let rec setCongruenceRecursive fd depth neigbourFunction = | exception Not_found -> () (* Happens for __goblint_bounded *) ) (FunctionSet.filter (*for extern and builtin functions there is no function definition in CIL*) - (fun x -> not (isExtern x.vstorage || BatString.starts_with x.vname "__builtin")) + (fun x -> not (isExtern x.vstorage || String.starts_with x.vname ~prefix:"__builtin")) (neigbourFunction fd.svar) ) ; @@ -153,20 +182,22 @@ let disableIntervalContextsInRecursiveFunctions () = let hasFunction pred = let relevant_static var = + Goblint_backtrace.wrap_val ~mark:(Cilfacade.FunVarinfo var) @@ fun () -> if LibraryFunctions.is_special var then let desc = LibraryFunctions.find var in - GobOption.exists (fun args -> pred (desc.special args)) (functionArgs var) + GobOption.exists (fun args -> pred desc args) (functionArgs var) else false in let relevant_dynamic var = + Goblint_backtrace.wrap_val ~mark:(Cilfacade.FunVarinfo var) @@ fun () -> if LibraryFunctions.is_special var then let desc = LibraryFunctions.find var in (* We don't really have arguments at hand, so we cheat and just feed it a list of MyCFG.unknown_exp of appropriate length *) match unrollType var.vtype with | TFun (_, args, _, _) -> let args = BatOption.map_default (List.map (fun (x,_,_) -> MyCFG.unknown_exp)) [] args in - pred (desc.special args) + pred desc args | _ -> false else false @@ -186,11 +217,12 @@ let enableAnalyses anas = (*escape is also still enabled, because otherwise we get a warning*) (*does not consider dynamic calls!*) -let notNeccessaryThreadAnalyses = ["race"; "deadlock"; "maylocks"; "symb_locks"; "thread"; "threadid"; "threadJoins"; "threadreturn"; "mhp"; "region"] +let notNeccessaryThreadAnalyses = ["race"; "deadlock"; "maylocks"; "symb_locks"; "thread"; "threadid"; "threadJoins"; "threadreturn"; "mhp"; "region"; "pthreadMutexType"] let reduceThreadAnalyses () = - let isThreadCreate = function + let isThreadCreate (desc: LibraryDesc.t) args = + match desc.special args with | LibraryDesc.ThreadCreate _ -> true - | _ -> false + | _ -> LibraryDesc.Accesses.find_kind desc.accs Spawn args <> [] in let hasThreadCreate = hasFunction isThreadCreate in if not @@ hasThreadCreate then ( @@ -198,42 +230,14 @@ let reduceThreadAnalyses () = disableAnalyses notNeccessaryThreadAnalyses; ) -(* This is run independent of the autotuner being enabled or not to be sound in the presence of setjmp/longjmp *) -(* It is done this way around to allow enabling some of these analyses also for programs without longjmp *) -let longjmpAnalyses = ["activeLongjmp"; "activeSetjmp"; "taintPartialContexts"; "modifiedSinceSetjmp"; "poisonVariables"; "expsplit"; "vla"] - -let activateLongjmpAnalysesWhenRequired () = - let isLongjmp = function - | LibraryDesc.Longjmp _ -> true - | _ -> false - in - if hasFunction isLongjmp then ( - Logs.info "longjmp -> enabling longjmp analyses \"%s\"" (String.concat ", " longjmpAnalyses); - enableAnalyses longjmpAnalyses; - ) - let focusOnMemSafetySpecification (spec: Svcomp.Specification.t) = match spec with - | ValidFree -> (* Enable the useAfterFree analysis *) - let uafAna = ["useAfterFree"] in - Logs.info "Specification: ValidFree -> enabling useAfterFree analysis \"%s\"" (String.concat ", " uafAna); - enableAnalyses uafAna - | ValidDeref -> (* Enable the memOutOfBounds analysis *) - let memOobAna = ["memOutOfBounds"] in - set_bool "ana.arrayoob" true; - Logs.info "Setting \"cil.addNestedScopeAttr\" to true"; - set_bool "cil.addNestedScopeAttr" true; - Logs.info "Specification: ValidDeref -> enabling memOutOfBounds analysis \"%s\"" (String.concat ", " memOobAna); - enableAnalyses memOobAna; | ValidMemtrack - | ValidMemcleanup -> (* Enable the memLeak analysis *) - let memLeakAna = ["memLeak"] in + | ValidMemcleanup -> if (get_int "ana.malloc.unique_address_count") < 1 then ( Logs.info "Setting \"ana.malloc.unique_address_count\" to 5"; set_int "ana.malloc.unique_address_count" 5; ); - Logs.info "Specification: ValidMemtrack and ValidMemcleanup -> enabling memLeak analysis \"%s\"" (String.concat ", " memLeakAna); - enableAnalyses memLeakAna | _ -> () let focusOnMemSafetySpecification () = @@ -242,7 +246,7 @@ let focusOnMemSafetySpecification () = let focusOnTermination (spec: Svcomp.Specification.t) = match spec with | Termination -> - let terminationAnas = ["termination"; "threadflag"; "apron"] in + let terminationAnas = ["threadflag"; "apron"] in Logs.info "Specification: Termination -> enabling termination analyses \"%s\"" (String.concat ", " terminationAnas); enableAnalyses terminationAnas; set_string "sem.int.signed_overflow" "assume_none"; @@ -254,19 +258,28 @@ let focusOnTermination (spec: Svcomp.Specification.t) = let focusOnTermination () = List.iter focusOnTermination (Svcomp.Specification.of_option ()) -let focusOnSpecification (spec: Svcomp.Specification.t) = +let concurrencySafety (spec: Svcomp.Specification.t) = match spec with - | UnreachCall s -> () | NoDataRace -> (*enable all thread analyses*) Logs.info "Specification: NoDataRace -> enabling thread analyses \"%s\"" (String.concat ", " notNeccessaryThreadAnalyses); enableAnalyses notNeccessaryThreadAnalyses; - | NoOverflow -> (*We focus on integer analysis*) + | _ -> () + +let noOverflows (spec: Svcomp.Specification.t) = + match spec with + | NoOverflow -> + (*We focus on integer analysis*) set_bool "ana.int.def_exc" true; - set_bool "ana.int.interval" true + begin + try + ignore @@ visitCilFileSameGlobals (new findAllocsInLoops) (!Cilfacade.current_file); + set_int "ana.malloc.unique_address_count" 1 + with Found -> set_int "ana.malloc.unique_address_count" 0; + end | _ -> () -let focusOnSpecification () = - List.iter focusOnSpecification (Svcomp.Specification.of_option ()) +let focusOn (f : SvcompSpec.t -> unit) = + List.iter f (Svcomp.Specification.of_option ()) (*Detect enumerations and enable the "ana.int.enums" option*) exception EnumFound @@ -335,20 +348,22 @@ let isComparison = function let isGoblintStub v = List.exists (fun (Attr(s,_)) -> s = "goblint_stub") v.vattr let rec extractVar = function - | UnOp (Neg, e, _) -> extractVar e - | Lval ((Var info),_) when not (isGoblintStub info) -> Some info + | UnOp (Neg, e, _) + | CastE (_, e) -> extractVar e + | Lval ((Var info),_) when not (isGoblintStub info) -> Some info | _ -> None +let extractBinOpVars e1 e2 = + match extractVar e1, extractVar e2 with + | Some a, Some b -> [a; b] + | Some a, None when isConstant e2 -> [a] + | None, Some b when isConstant e1 -> [b] + | _, _ -> [] + let extractOctagonVars = function | BinOp (PlusA, e1,e2, (TInt _)) - | BinOp (MinusA, e1,e2, (TInt _)) -> ( - match extractVar e1, extractVar e2 with - | Some a, Some b -> Some (`Left (a,b)) - | Some a, None - | None, Some a -> if isConstant e1 then Some (`Right a) else None - | _,_ -> None - ) - | _ -> None + | BinOp (MinusA, e1,e2, (TInt _)) -> extractBinOpVars e1 e2 + | e -> Option.to_list (extractVar e) let addOrCreateVarMapping varMap key v globals = if key.vglob = globals then varMap := if VariableMap.mem key !varMap then @@ -357,25 +372,19 @@ let addOrCreateVarMapping varMap key v globals = if key.vglob = globals then var else VariableMap.add key v !varMap -let handle varMap v globals = function - | Some (`Left (a,b)) -> - addOrCreateVarMapping varMap a v globals; - addOrCreateVarMapping varMap b v globals; - | Some (`Right a) -> addOrCreateVarMapping varMap a v globals; - | None -> () - class octagonVariableVisitor(varMap, globals) = object inherit nopCilVisitor method! vexpr = function (*an expression of type +/- a +/- b where a,b are either variables or constants*) | BinOp (op, e1,e2, (TInt _)) when isComparison op -> ( - handle varMap 5 globals (extractOctagonVars e1) ; - handle varMap 5 globals (extractOctagonVars e2) ; + List.iter (fun var -> addOrCreateVarMapping varMap var 5 globals) (extractOctagonVars e1); + List.iter (fun var -> addOrCreateVarMapping varMap var 5 globals) (extractOctagonVars e2); DoChildren ) - | Lval ((Var info),_) when not (isGoblintStub info) -> handle varMap 1 globals (Some (`Right info)) ; SkipChildren + | Lval ((Var info),_) when not (isGoblintStub info) -> addOrCreateVarMapping varMap info 1 globals; SkipChildren (*Traverse down only operations fitting for linear equations*) + | UnOp (LNot, _,_) | UnOp (Neg, _,_) | BinOp (PlusA,_,_,_) | BinOp (MinusA,_,_,_) @@ -476,7 +485,8 @@ let wideningOption factors file = } let activateTmpSpecialAnalysis () = - let isMathFun = function + let isMathFun (desc: LibraryDesc.t) args = + match desc.special args with | LibraryDesc.Math _ -> true | _ -> false in @@ -517,12 +527,6 @@ let isActivated a = get_bool "ana.autotune.enabled" && List.mem a @@ get_string_ let isTerminationTask () = List.mem Svcomp.Specification.Termination (Svcomp.Specification.of_option ()) -let specificationIsActivated () = - isActivated "specification" && get_string "ana.specification" <> "" - -let specificationTerminationIsActivated () = - isActivated "termination" - let chooseConfig file = let factors = collectFactors visitCilFileSameGlobals file in let fileCompplexity = estimateComplexity factors file in @@ -542,8 +546,9 @@ let chooseConfig file = if isActivated "mallocWrappers" then findMallocWrappers (); - if specificationIsActivated () then - focusOnSpecification (); + if isActivated "concurrencySafetySpecification" then focusOn concurrencySafety; + + if isActivated "noOverflows" then focusOn noOverflows; if isActivated "enums" && hasEnums file then set_bool "ana.int.enums" true; diff --git a/src/build-info/build_info_dune/dune b/src/build-info/build_info_dune/dune deleted file mode 100644 index ec46f4b3d1..0000000000 --- a/src/build-info/build_info_dune/dune +++ /dev/null @@ -1,6 +0,0 @@ -; goblint.build-info implementation which properly uses dune-build-info -(library - (name goblint_build_info_dune) - (public_name goblint.build-info.dune) - (implements goblint.build-info) - (libraries dune-build-info)) diff --git a/src/build-info/build_info_js/dune b/src/build-info/build_info_js/dune deleted file mode 100644 index 9400f564ff..0000000000 --- a/src/build-info/build_info_js/dune +++ /dev/null @@ -1,5 +0,0 @@ -; goblint.build-info implementation which works with js_of_ocaml and doesn't use dune-build-info -(library - (name goblint_build_info_js) - (public_name goblint.build-info.js) - (implements goblint.build-info)) diff --git a/src/build-info/build_info_js/dune_build_info.ml b/src/build-info/build_info_js/dune_build_info.ml deleted file mode 100644 index 002015cd31..0000000000 --- a/src/build-info/build_info_js/dune_build_info.ml +++ /dev/null @@ -1 +0,0 @@ -let statically_linked_libraries = [] diff --git a/src/build-info/dune b/src/build-info/dune index 5a64b399a4..085660531e 100644 --- a/src/build-info/dune +++ b/src/build-info/dune @@ -1,21 +1,15 @@ (include_subdirs no) -; virtual library to allow js build (for gobview) without dune-build-info -; dune-build-info seems to be incompatible with js_of_ocaml -; File "gobview/src/.App.eobjs/build_info_data.ml-gen", line 1: -; Error: Could not find the .cmi file for interface -; gobview/src/.App.eobjs/build_info_data.ml-gen. (library (name goblint_build_info) (public_name goblint.build-info) - (libraries batteries.unthreaded) - (virtual_modules dune_build_info)) + (libraries dune-build-info)) (rule (target configVersion.ml) (mode (promote (until-clean) (only configVersion.ml))) ; replace existing file in source tree, even if releasing (only overrides) (deps (universe)) ; do not cache, always regenerate - (action (pipe-stdout (bash "git describe --all --long --dirty || echo \"n/a\"") (with-stdout-to %{target} (bash "xargs printf '(* Automatically regenerated, changes do not persist! *)\nlet version = \"%s\"'"))))) + (action (pipe-stdout (bash "git describe --all --long --dirty || echo \"n/a\"") (with-stdout-to %{target} (bash "xargs printf '(* Automatically regenerated, changes do not persist! *)\nlet version = Sys.opaque_identity \"%s\"'"))))) (rule (target configProfile.ml) @@ -31,7 +25,7 @@ (target configDatetime.ml) (mode (promote (until-clean) (only configDatetime.ml))) ; replace existing file in source tree, even if releasing (only overrides) (deps (universe)) ; do not cache, always regenerate - (action (pipe-stdout (bash "date +\"%Y-%m-%dT%H:%M:%S\" || echo \"n/a\"") (with-stdout-to %{target} (bash "xargs printf '(* Automatically regenerated, changes do not persist! *)\nlet datetime = \"%s\"'"))))) + (action (pipe-stdout (bash "date +\"%Y-%m-%dT%H:%M:%S\" || echo \"n/a\"") (with-stdout-to %{target} (bash "xargs printf '(* Automatically regenerated, changes do not persist! *)\nlet datetime = Sys.opaque_identity \"%s\"'"))))) (env (_ diff --git a/src/build-info/build_info_dune/dune_build_info.ml b/src/build-info/dune_build_info.ml similarity index 100% rename from src/build-info/build_info_dune/dune_build_info.ml rename to src/build-info/dune_build_info.ml diff --git a/src/build-info/goblint_build_info.ml b/src/build-info/goblint_build_info.ml index 14e30a1b36..d2600c94ad 100644 --- a/src/build-info/goblint_build_info.ml +++ b/src/build-info/goblint_build_info.ml @@ -18,7 +18,7 @@ let release_commit = "%%VCS_COMMIT_ID%%" (** Goblint version. *) let version = let commit = ConfigVersion.version in - if BatString.starts_with release_version "%" then + if String.starts_with release_version ~prefix:"%" then commit else ( let commit = diff --git a/src/cdomain/value/cdomains/addressDomain.ml b/src/cdomain/value/cdomains/addressDomain.ml index 1a09aed026..8f5bb4db4d 100644 --- a/src/cdomain/value/cdomains/addressDomain.ml +++ b/src/cdomain/value/cdomains/addressDomain.ml @@ -326,7 +326,7 @@ struct let string_writing_defined dest = (* if the destination address set contains a StrPtr, writing to such a string literal is undefined behavior *) - if List.exists Option.is_some (List.map Addr.to_c_string (elements dest)) then + if exists (fun a -> Option.is_some (Addr.to_c_string a)) dest then (M.warn ~category:M.Category.Behavior.Undefined.other "May write to a string literal, which leads to a segmentation fault in most cases"; false) else diff --git a/src/cdomain/value/cdomains/arrayDomain.ml b/src/cdomain/value/cdomains/arrayDomain.ml index e1cfb96425..4192489c3a 100644 --- a/src/cdomain/value/cdomains/arrayDomain.ml +++ b/src/cdomain/value/cdomains/arrayDomain.ml @@ -834,7 +834,7 @@ end let array_oob_check ( type a ) (module Idx: IntDomain.Z with type t = a) (x, l) (e, v) = if GobConfig.get_bool "ana.arrayoob" then (* The purpose of the following 2 lines is to give the user extra info about the array oob *) let idx_before_end = Idx.to_bool (Idx.lt v l) (* check whether index is before the end of the array *) - and idx_after_start = Idx.to_bool (Idx.ge v (Idx.of_int Cil.ILong Z.zero)) in (* check whether the index is non-negative *) + and idx_after_start = Idx.to_bool (Idx.ge v (Idx.of_int (Cilfacade.ptrdiff_ikind ()) Z.zero)) in (* check whether the index is non-negative *) (* For an explanation of the warning types check the Pull Request #255 *) match(idx_after_start, idx_before_end) with | Some true, Some true -> (* Certainly in bounds on both sides.*) diff --git a/src/cdomain/value/cdomains/concDomain.ml b/src/cdomain/value/cdomains/concDomain.ml index 5f609a31d8..467159c9da 100644 --- a/src/cdomain/value/cdomains/concDomain.ml +++ b/src/cdomain/value/cdomains/concDomain.ml @@ -1,7 +1,7 @@ (** Domains for thread sets and their uniqueness. *) -module ThreadSet = -struct +module ThreadSet = +struct include SetDomain.Make (ThreadIdDomain.Thread) let is_top = mem UnknownThread @@ -27,10 +27,11 @@ module CreatedThreadSet = ThreadSet module ThreadCreation = struct module UNames = struct - let truename = "repeated" - let falsename = "unique" + let name = "unique" + let true_name = "repeated" + let false_name = "unique" end - module Uniqueness = IntDomain.MakeBooleans (UNames) + module Uniqueness = BoolDomain.MakeMayBool (UNames) module ParentThreadSet = struct include ThreadSet @@ -38,12 +39,13 @@ struct end module DirtyExitNames = struct - let truename = "dirty exit" - let falsename = "clean exit" + let name = "exit" + let true_name = "dirty exit" + let false_name = "clean exit" end (* A thread exits cleanly iff it joined all threads it started, and they also all exit cleanly *) - module DirtyExit = IntDomain.MakeBooleans (DirtyExitNames) + module DirtyExit = BoolDomain.MakeMayBool (DirtyExitNames) include Lattice.Prod3 (Uniqueness) (ParentThreadSet) (DirtyExit) end diff --git a/src/cdomain/value/cdomains/floatDomain.ml b/src/cdomain/value/cdomains/floatDomain.ml index c86770826c..7c77b954b4 100644 --- a/src/cdomain/value/cdomains/floatDomain.ml +++ b/src/cdomain/value/cdomains/floatDomain.ml @@ -241,12 +241,12 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct let minimal = function | Bot -> raise (ArithmeticOnFloatBot (Printf.sprintf "minimal %s" (show Bot))) - | Interval (l, _) -> Float_t.to_float l + | Interval (l, _) -> Some (Float_t.to_float l) | _ -> None let maximal = function | Bot -> raise (ArithmeticOnFloatBot (Printf.sprintf "maximal %s" (show Bot))) - | Interval (_, h) -> Float_t.to_float h + | Interval (_, h) -> Some (Float_t.to_float h) | _ -> None let is_exact = function @@ -1134,7 +1134,7 @@ module FloatDomTupleImpl = struct module F1 = FloatIntervalImplLifted open Batteries - type t = F1.t option [@@deriving to_yojson, eq, ord] + type t = F1.t option [@@deriving eq, ord, hash] let name () = "floatdomtuple" @@ -1181,10 +1181,6 @@ module FloatDomTupleImpl = struct Option.map_default identity "" (mapp { fp= (fun (type a) (module F : FloatDomain with type t = a) x -> F.name () ^ ":" ^ F.show x); } x) - let hash x = - Option.map_default identity 0 - (mapp { fp= (fun (type a) (module F : FloatDomain with type t = a) -> F.hash); } x) - let of_const fkind = create { fi= (fun (type a) (module F : FloatDomain with type t = a) -> F.of_const fkind); } diff --git a/src/cdomain/value/cdomains/int/congruenceDomain.ml b/src/cdomain/value/cdomains/int/congruenceDomain.ml new file mode 100644 index 0000000000..003e33a624 --- /dev/null +++ b/src/cdomain/value/cdomains/int/congruenceDomain.ml @@ -0,0 +1,496 @@ +open IntDomain0 +open GoblintCil + + +module Congruence : S with type int_t = Z.t and type t = (Z.t * Z.t) option = +struct + let name () = "congruences" + type int_t = Z.t + + (* represents congruence class of c mod m, None is bot *) + type t = (Z.t * Z.t) option [@@deriving eq, ord, hash] + + let ( *: ) = Z.mul + let (+:) = Z.add + let (-:) = Z.sub + let (%:) = Z.rem + let (/:) = Z.div + let (=:) = Z.equal + let (<:) x y = Z.compare x y < 0 + let (>:) x y = Z.compare x y > 0 + let (<=:) x y = Z.compare x y <= 0 + let (>=:) x y = Z.compare x y >= 0 + (* a divides b *) + let ( |: ) a b = + if a =: Z.zero then false else (b %: a) =: Z.zero + + let normalize ik x = + match x with + | None -> None + | Some (c, m) -> + if m =: Z.zero then + if should_wrap ik then + Some (Size.cast ik c, m) + else + Some (c, m) + else + let m' = Z.abs m in + let c' = c %: m' in + if c' <: Z.zero then + Some (c' +: m', m') + else + Some (c' %: m', m') + + let range ik = Size.range ik + + let top () = Some (Z.zero, Z.one) + let top_of ik = Some (Z.zero, Z.one) + let bot () = None + let bot_of ik = bot () + + let show = function ik -> match ik with + | None -> "⟂" + | Some (c, m) when (c, m) = (Z.zero, Z.zero) -> Z.to_string c + | Some (c, m) -> + let a = if c =: Z.zero then "" else Z.to_string c in + let b = if m =: Z.zero then "" else if m = Z.one then "ℤ" else Z.to_string m^"ℤ" in + let c = if a = "" || b = "" then "" else "+" in + a^c^b + + include Std (struct type nonrec t = t let name = name let top_of = top_of let bot_of = bot_of let show = show let equal = equal end) + + let is_top x = x = top () + + let equal_to i = function + | None -> failwith "unsupported: equal_to with bottom" + | Some (a, b) when b =: Z.zero -> if a =: i then `Eq else `Neq + | Some (a, b) -> if i %: b =: a then `Top else `Neq + + let leq (x:t) (y:t) = + match x, y with + | None, _ -> true + | Some _, None -> false + | Some (c1,m1), Some (c2,m2) when m2 =: Z.zero && m1 =: Z.zero -> c1 =: c2 + | Some (c1,m1), Some (c2,m2) when m2 =: Z.zero -> c1 =: c2 && m1 =: Z.zero + | Some (c1,m1), Some (c2,m2) -> m2 |: Z.gcd (c1 -: c2) m1 + (* Typo in original equation of P. Granger (m2 instead of m1): gcd (c1 -: c2) m2 + Reference: https://doi.org/10.1080/00207168908803778 Page 171 corollary 3.3*) + + let leq x y = + let res = leq x y in + if M.tracing then M.trace "congruence" "leq %a %a -> %a " pretty x pretty y pretty (Some (Z.of_int (Bool.to_int res), Z.zero)) ; + res + + let join ik (x:t) y = + match x, y with + | None, z | z, None -> z + | Some (c1,m1), Some (c2,m2) -> + let m3 = Z.gcd m1 (Z.gcd m2 (c1 -: c2)) in + normalize ik (Some (c1, m3)) + + let join ik (x:t) y = + let res = join ik x y in + if M.tracing then M.trace "congruence" "join %a %a -> %a" pretty x pretty y pretty res; + res + + + let meet ik x y = + (* if it exists, c2/a2 is solution to a*x ≡ c (mod m) *) + let congruence_series a c m = + let rec next a1 c1 a2 c2 = + if a2 |: a1 then (a2, c2) + else next a2 c2 (a1 %: a2) (c1 -: (c2 *: (a1 /: a2))) + in next m Z.zero a c + in + let simple_case i c m = + if m |: (i -: c) + then Some (i, Z.zero) else None + in + match x, y with + | Some (c1, m1), Some (c2, m2) when m1 =: Z.zero && m2 =: Z.zero -> if c1 =: c2 then Some (c1, Z.zero) else None + | Some (c1, m1), Some (c2, m2) when m1 =: Z.zero -> simple_case c1 c2 m2 + | Some (c1, m1), Some (c2, m2) when m2 =: Z.zero -> simple_case c2 c1 m1 + | Some (c1, m1), Some (c2, m2) when (Z.gcd m1 m2) |: (c1 -: c2) -> + let (c, m) = congruence_series m1 (c2 -: c1 ) m2 in + normalize ik (Some(c1 +: (m1 *: (m /: c)), m1 *: (m2 /: c))) + | _ -> None + + let meet ik x y = + let res = meet ik x y in + if M.tracing then M.trace "congruence" "meet %a %a -> %a" pretty x pretty y pretty res; + res + + let to_int = function Some (c, m) when m =: Z.zero -> Some c | _ -> None + let of_int ik (x: int_t) = normalize ik @@ Some (x, Z.zero) + let zero = Some (Z.zero, Z.zero) + let one = Some (Z.one, Z.zero) + let top_bool = top() + + let of_bool _ik = function true -> one | false -> zero + + let to_bool (a: t) = match a with + | None -> None + | x when equal zero x -> Some false + | x -> if leq zero x then None else Some true + + let starting ?(suppress_ovwarn=false) ik n = top() + + let ending = starting + + let of_congruence ik (c,m) = normalize ik @@ Some(c,m) + + let maximal t = match t with + | Some (x, y) when y =: Z.zero -> Some x + | _ -> None + + let minimal t = match t with + | Some (x,y) when y =: Z.zero -> Some x + | _ -> None + + (* cast from original type to ikind, set to top if the value doesn't fit into the new type *) + let cast_to ?(suppress_ovwarn=false) ?torg ?(no_ov=false) t x = + match x with + | None -> None + | Some (c, m) when m =: Z.zero -> + let c' = Size.cast t c in + (* When casting into a signed type and the result does not fit, the behavior is implementation-defined. (C90 6.2.1.2, C99 and C11 6.3.1.3) *) + (* We go with GCC behavior here: *) + (* For conversion to a type of width N, the value is reduced modulo 2^N to be within range of the type; no signal is raised. *) + (* (https://gcc.gnu.org/onlinedocs/gcc/Integers-implementation.html) *) + (* Clang behaves the same but they never document that anywhere *) + Some (c', m) + | _ -> + let (min_t, max_t) = range t in + let p ikorg = + let (min_ikorg, max_ikorg) = range ikorg in + ikorg = t || (max_t >=: max_ikorg && min_t <=: min_ikorg) + in + match torg with + | Some (Cil.TInt (ikorg, _)) when p ikorg -> + if M.tracing then M.trace "cong-cast" "some case"; + x + | _ -> top () + + + let cast_to ?(suppress_ovwarn=false) ?torg ?no_ov (t : Cil.ikind) x = + let pretty_bool _ x = Pretty.text (string_of_bool x) in + let res = cast_to ?torg ?no_ov t x in + if M.tracing then M.trace "cong-cast" "Cast %a to %a (no_ov: %a) = %a" pretty x Cil.d_ikind t (Pretty.docOpt (pretty_bool ())) no_ov pretty res; + res + + let widen = join + + let widen ik x y = + let res = widen ik x y in + if M.tracing then M.trace "congruence" "widen %a %a -> %a" pretty x pretty y pretty res; + res + + let narrow = meet + + let log f ik i1 i2 = + match is_bot i1, is_bot i2 with + | true, true -> bot_of ik + | true, _ + | _ , true -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show i1) (show i2))) + | _ -> + match to_bool i1, to_bool i2 with + | Some x, Some y -> of_bool ik (f x y) + | _ -> top_of ik + + let c_logor = log (||) + let c_logand = log (&&) + + let log1 f ik i1 = + if is_bot i1 then + bot_of ik + else + match to_bool i1 with + | Some x -> of_bool ik (f ik x) + | _ -> top_of ik + + let c_lognot = log1 (fun _ik -> not) + + let shift_right _ _ _ = top() + + let shift_right ik x y = + let res = shift_right ik x y in + if M.tracing then M.trace "congruence" "shift_right : %a %a becomes %a " pretty x pretty y pretty res; + res + + let shift_left ik x y = + (* Naive primality test *) + (* let is_prime n = + let n = Z.abs n in + let rec is_prime' d = + (d *: d >: n) || ((not ((n %: d) =: Z.zero)) && (is_prime' [@tailcall]) (d +: Z.one)) + in + not (n =: Z.one) && is_prime' (Z.of_int 2) + in *) + match x, y with + | None, None -> None + | None, _ + | _, None -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) + | Some (c, m), Some (c', m') when Cil.isSigned ik || c <: Z.zero || c' <: Z.zero -> top_of ik + | Some (c, m), Some (c', m') -> + let (_, max_ik) = range ik in + if m =: Z.zero && m' =: Z.zero then + normalize ik @@ Some (Z.logand max_ik (Z.shift_left c (Z.to_int c')), Z.zero) + else + let x = Z.logand max_ik (Z.shift_left Z.one (Z.to_int c')) in (* 2^c' *) + (* TODO: commented out because fails test with _Bool *) + (* if is_prime (m' +: Z.one) then + normalize ik @@ Some (x *: c, Z.gcd (x *: m) ((c *: x) *: (m' +: Z.one))) + else *) + normalize ik @@ Some (x *: c, Z.gcd (x *: m) (c *: x)) + + let shift_left ik x y = + let res = shift_left ik x y in + if M.tracing then M.trace "congruence" "shift_left : %a %a becomes %a " pretty x pretty y pretty res; + res + + (* Handle unsigned overflows. + From n === k mod (2^a * b), we conclude n === k mod 2^a, for a <= bitwidth. + The congruence modulo b may not persist on an overflow. *) + let handle_overflow ik (c, m) = + if m =: Z.zero then + normalize ik (Some (c, m)) + else + (* Find largest m'=2^k (for some k) such that m is divisible by m' *) + let tz = Z.trailing_zeros m in + let m' = Z.shift_left Z.one tz in + + let max = (snd (Size.range ik)) +: Z.one in + if m' >=: max then + (* if m' >= 2 ^ {bitlength}, there is only one value in range *) + let c' = c %: max in + Some (c', Z.zero) + else + normalize ik (Some (c, m')) + + let mul ?(no_ov=false) ik x y = + let no_ov_case (c1, m1) (c2, m2) = + c1 *: c2, Z.gcd (c1 *: m2) (Z.gcd (m1 *: c2) (m1 *: m2)) + in + match x, y with + | None, None -> bot () + | None, _ | _, None -> + raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) + | Some (c1, m1), Some (c2, m2) when no_ov -> + Some (no_ov_case (c1, m1) (c2, m2)) + | Some (c1, m1), Some (c2, m2) when m1 =: Z.zero && m2 =: Z.zero && not (Cil.isSigned ik) -> + let (_, max_ik) = range ik in + Some ((c1 *: c2) %: (max_ik +: Z.one), Z.zero) + | Some a, Some b when not (Cil.isSigned ik) -> + handle_overflow ik (no_ov_case a b ) + | _ -> top () + + let mul ?no_ov ik x y = + let res = mul ?no_ov ik x y in + if M.tracing then M.trace "congruence" "mul : %a %a -> %a " pretty x pretty y pretty res; + res + + let neg ?(no_ov=false) ik x = + match x with + | None -> bot() + | Some _ -> mul ~no_ov ik (of_int ik (Z.of_int (-1))) x + + let add ?(no_ov=false) ik x y = + let no_ov_case (c1, m1) (c2, m2) = + c1 +: c2, Z.gcd m1 m2 + in + match (x, y) with + | None, None -> bot () + | None, _ | _, None -> + raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) + | Some a, Some b when no_ov -> + normalize ik (Some (no_ov_case a b)) + | Some (c1, m1), Some (c2, m2) when m1 =: Z.zero && m2 =: Z.zero && not (Cil.isSigned ik) -> + let (_, max_ik) = range ik in + Some((c1 +: c2) %: (max_ik +: Z.one), Z.zero) + | Some a, Some b when not (Cil.isSigned ik) -> + handle_overflow ik (no_ov_case a b) + | _ -> top () + + + let add ?no_ov ik x y = + let res = add ?no_ov ik x y in + if M.tracing then + M.trace "congruence" "add : %a %a -> %a" pretty x pretty y + pretty res ; + res + + let sub ?(no_ov=false) ik x y = add ~no_ov ik x (neg ~no_ov ik y) + + + let sub ?no_ov ik x y = + let res = sub ?no_ov ik x y in + if M.tracing then + M.trace "congruence" "sub : %a %a -> %a" pretty x pretty y + pretty res ; + res + + let lognot ik x = match x with + | None -> None + | Some (c, m) -> + if (Cil.isSigned ik) then + sub ik (neg ik x) one + else + let (_, max_ik) = range ik in + Some (Z.sub max_ik c, m) + + (** The implementation of the bit operations could be improved based on the master’s thesis + 'Abstract Interpretation and Abstract Domains' written by Stefan Bygde. + see: http://www.es.mdh.se/pdf_publications/948.pdf *) + let bit2 f ik x y = match x, y with + | None, None -> None + | None, _ | _, None -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) + | Some (c, m), Some (c', m') -> + if m =: Z.zero && m' =: Z.zero then Some (f c c', Z.zero) + else top () + + let logor ik x y = bit2 Z.logor ik x y + + let logand ik x y = match x, y with + | None, None -> None + | None, _ | _, None -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) + | Some (c, m), Some (c', m') -> + if m =: Z.zero && m' =: Z.zero then + (* both arguments constant *) + Some (Z.logand c c', Z.zero) + else if m' =: Z.zero && c' =: Z.one && Z.rem m (Z.of_int 2) =: Z.zero then + (* x & 1 and x == c (mod 2*z) *) + (* Value is equal to LSB of c *) + Some (Z.logand c c', Z.zero) + else + top () + + let logxor ik x y = bit2 Z.logxor ik x y + + let rem ik x y = + match x, y with + | None, None -> bot() + | None, _ | _, None -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) + | Some (c1, m1), Some(c2, m2) -> + if m2 =: Z.zero then + if (c2 |: m1) && (c1 %: c2 =: Z.zero || m1 =: Z.zero || not (Cil.isSigned ik)) then + Some (c1 %: c2, Z.zero) + else + normalize ik (Some (c1, (Z.gcd m1 c2))) + else + normalize ik (Some (c1, Z.gcd m1 (Z.gcd c2 m2))) + + let rem ik x y = let res = rem ik x y in + if M.tracing then M.trace "congruence" "rem : %a %a -> %a " pretty x pretty y pretty res; + res + + let div ?(no_ov=false) ik x y = + match x,y with + | None, None -> bot () + | None, _ | _, None -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) + | _, x when leq zero x -> top () + | Some(c1, m1), Some(c2, m2) when not no_ov && m2 =: Z.zero && c2 =: Z.neg Z.one -> top () + | Some(c1, m1), Some(c2, m2) when m1 =: Z.zero && m2 =: Z.zero -> Some (c1 /: c2, Z.zero) + | Some(c1, m1), Some(c2, m2) when m2 =: Z.zero && c2 |: m1 && c2 |: c1 -> Some (c1 /: c2, m1 /: c2) + | _, _ -> top () + + + let div ?no_ov ik x y = + let res = div ?no_ov ik x y in + if M.tracing then + M.trace "congruence" "div : %a %a -> %a" pretty x pretty y pretty + res ; + res + + let ne ik (x: t) (y: t) = match x, y with + | Some (c1, m1), Some (c2, m2) when (m1 =: Z.zero) && (m2 =: Z.zero) -> of_bool ik (not (c1 =: c2 )) + | x, y -> if meet ik x y = None then of_bool ik true else top_bool + + let eq ik (x: t) (y: t) = match x, y with + | Some (c1, m1), Some (c2, m2) when (m1 =: Z.zero) && (m2 =: Z.zero) -> of_bool ik (c1 =: c2) + | x, y -> if meet ik x y <> None then top_bool else of_bool ik false + + let comparison ik op x y = match x, y with + | None, None -> bot_of ik + | None, _ | _, None -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) + | Some (c1, m1), Some (c2, m2) -> + if m1 =: Z.zero && m2 =: Z.zero then + if op c1 c2 then of_bool ik true else of_bool ik false + else + top_bool + + let ge ik x y = comparison ik (>=:) x y + + let ge ik x y = + let res = ge ik x y in + if M.tracing then M.trace "congruence" "greater or equal : %a %a -> %a " pretty x pretty y pretty res; + res + + let le ik x y = comparison ik (<=:) x y + + let le ik x y = + let res = le ik x y in + if M.tracing then M.trace "congruence" "less or equal : %a %a -> %a " pretty x pretty y pretty res; + res + + let gt ik x y = comparison ik (>:) x y + + + let gt ik x y = + let res = gt ik x y in + if M.tracing then M.trace "congruence" "greater than : %a %a -> %a " pretty x pretty y pretty res; + res + + let lt ik x y = comparison ik (<:) x y + + let lt ik x y = + let res = lt ik x y in + if M.tracing then M.trace "congruence" "less than : %a %a -> %a " pretty x pretty y pretty res; + res + + let invariant_ikind e ik x = + match x with + | x when is_top x -> Invariant.top () + | Some (c, m) when m =: Z.zero -> + IntInvariant.of_int e ik c + | Some (c, m) -> + let open Cil in + let (c, m) = BatTuple.Tuple2.mapn (fun a -> kintegerCilint ik a) (c, m) in + Invariant.of_exp (BinOp (Eq, (BinOp (Mod, e, m, TInt(ik,[]))), c, intType)) + | None -> Invariant.none + + let arbitrary ik = + let open QCheck in + let int_arb = map ~rev:Z.to_int64 Z.of_int64 GobQCheck.Arbitrary.int64 in + let cong_arb = pair int_arb int_arb in + let of_pair ik p = normalize ik (Some p) in + let to_pair = Option.get in + set_print show (map ~rev:to_pair (of_pair ik) cong_arb) + + let refine_with_interval ik (cong : t) (intv : (int_t * int_t ) option) : t = + match intv, cong with + | Some (x, y), Some (c, m) -> + if m =: Z.zero then + if c <: x || c >: y then None else Some (c, Z.zero) + else + let rcx = x +: ((c -: x) %: Z.abs m) in + let lcy = y -: ((y -: c) %: Z.abs m) in + if rcx >: lcy then None + else if rcx =: lcy then Some (rcx, Z.zero) + else cong + | _ -> None + + let refine_with_interval ik (cong : t) (intv : (int_t * int_t) option) : t = + let pretty_intv _ i = + match i with + | Some (l, u) -> Pretty.dprintf "[%a,%a]" GobZ.pretty l GobZ.pretty u + | _ -> Pretty.text ("Display Error") in + let refn = refine_with_interval ik cong intv in + if M.tracing then M.trace "refine" "cong_refine_with_interval %a %a -> %a" pretty cong pretty_intv intv pretty refn; + refn + + let refine_with_congruence ik a b = meet ik a b + let refine_with_excl_list ik a b = a + let refine_with_incl_list ik a b = a + + let project ik p t = t +end diff --git a/src/cdomain/value/cdomains/int/defExcDomain.ml b/src/cdomain/value/cdomains/int/defExcDomain.ml new file mode 100644 index 0000000000..89e4b399aa --- /dev/null +++ b/src/cdomain/value/cdomains/int/defExcDomain.ml @@ -0,0 +1,542 @@ +open IntDomain0 +open IntervalDomain +open GoblintCil + + +module BISet = struct + include SetDomain.Make (IntOps.BigIntOps) + let is_singleton s = cardinal s = 1 +end + +(* The module [Exclusion] constains common functionality about handling of exclusion sets between [DefExc] and [Enums] *) +module Exclusion = +struct + module R = Interval32 + (* We use these types for the functions in this module to make the intended meaning more explicit *) + type t = Exc of BISet.t * Interval32.t + type inc = Inc of BISet.t [@@unboxed] + let max_of_range r = Size.max_from_bit_range (Option.get (R.maximal r)) + let min_of_range r = Size.min_from_bit_range (Option.get (R.minimal r)) + let cardinality_of_range r = Z.succ (Z.add (Z.neg (min_of_range r)) (max_of_range r)) + + let cardinality_BISet s = + Z.of_int (BISet.cardinal s) + + let leq_excl_incl (Exc (xs, r)) (Inc ys) = + (* For a <= b to hold, the cardinalities must fit, i.e. |a| <= |b|, which implies |min_r, max_r| - |xs| <= |ys|. We check this first. *) + let lower_bound_cardinality_a = Z.sub (cardinality_of_range r) (cardinality_BISet xs) in + let card_b = cardinality_BISet ys in + if Z.compare lower_bound_cardinality_a card_b > 0 then + false + else (* The cardinality did fit, so we check for all elements that are represented by range r, whether they are in (xs union ys) *) + let min_a = min_of_range r in + let max_a = max_of_range r in + GobZ.for_all_range (fun el -> BISet.mem el xs || BISet.mem el ys) (min_a, max_a) + + let leq (Exc (xs, r)) (Exc (ys, s)) = + let min_a, max_a = min_of_range r, max_of_range r in + let excluded_check = BISet.for_all (fun y -> BISet.mem y xs || Z.compare y min_a < 0 || Z.compare y max_a > 0) ys in (* if true, then the values ys, that are not in b, also do not occur in a *) + if not excluded_check + then false + else begin (* Check whether all elements that are in the range r, but not in s, are in xs, i.e. excluded. *) + if R.leq r s then true + else begin if Z.compare (cardinality_BISet xs) (Z.sub (cardinality_of_range r) (cardinality_of_range s)) >= 0 (* Check whether the number of excluded elements in a is as least as big as |min_r, max_r| - |min_s, max_s| *) + then + let min_b, max_b = min_of_range s, max_of_range s in + let leq1 = (* check whether the elements in [r_l; s_l-1] are all in xs, i.e. excluded *) + if Z.compare min_a min_b < 0 then + GobZ.for_all_range (fun x -> BISet.mem x xs) (min_a, Z.pred min_b) + else + true + in + let leq2 () = (* check whether the elements in [s_u+1; r_u] are all in xs, i.e. excluded *) + if Z.compare max_b max_a < 0 then + GobZ.for_all_range (fun x -> BISet.mem x xs) (Z.succ max_b, max_a) + else + true + in + leq1 && (leq2 ()) + else + false + end + end +end + +module DefExc : S with type int_t = Z.t = (* definite or set of excluded values *) +struct + module S = BISet + module R = Interval32 (* range for exclusion *) + + (* Ikind used for intervals representing the domain *) + let range_ikind = Cil.IInt + let size t = R.of_interval range_ikind (let a,b = Size.bits_i64 t in Int64.neg a,b) + + + type t = [ + | `Excluded of S.t * R.t + | `Definite of Z.t + | `Bot + ] [@@deriving eq, ord, hash] + type int_t = Z.t + let name () = "def_exc" + + + let top_range = R.of_interval range_ikind (-99L, 99L) (* Since there is no top ikind we use a range that includes both ILongLong [-63,63] and IULongLong [0,64]. Only needed for intermediate range computation on longs. Correct range is set by cast. *) + let top () = `Excluded (S.empty (), top_range) + let bot () = `Bot + let top_of ik = `Excluded (S.empty (), size ik) + let bot_of ik = bot () + + let show x = + let short_size x = "("^R.show x^")" in + match x with + | `Bot -> "Error int" + | `Definite x -> Z.to_string x + (* Print the empty exclusion as if it was a distinct top element: *) + | `Excluded (s,l) when S.is_empty s -> "Unknown int" ^ short_size l + (* Prepend the exclusion sets with something: *) + | `Excluded (s,l) -> "Not " ^ S.show s ^ short_size l + + include Std (struct type nonrec t = t let name = name let top_of = top_of let bot_of = bot_of let show = show let equal = equal end) + + let maximal = function + | `Definite x -> Some x + | `Excluded (s,r) -> Some (Exclusion.max_of_range r) + | `Bot -> None + + let minimal = function + | `Definite x -> Some x + | `Excluded (s,r) -> Some (Exclusion.min_of_range r) + | `Bot -> None + + let in_range r i = + if Z.compare i Z.zero < 0 then + let lowerb = Exclusion.min_of_range r in + Z.compare lowerb i <= 0 + else + let upperb = Exclusion.max_of_range r in + Z.compare i upperb <= 0 + + let is_top x = x = top () + + let equal_to i = function + | `Bot -> failwith "unsupported: equal_to with bottom" + | `Definite x -> if i = x then `Eq else `Neq + | `Excluded (s,r) -> if S.mem i s then `Neq else `Top + + let cast_to ?(suppress_ovwarn=false) ?torg ?no_ov ik = function + | `Excluded (s,r) -> + let r' = size ik in + if R.leq r r' then (* upcast -> no change *) + `Excluded (s, r) + else if ik = IBool then (* downcast to bool *) + if S.mem Z.zero s then + `Definite Z.one + else + `Excluded (S.empty(), r') + else + (* downcast: may overflow *) + (* let s' = S.map (Size.cast ik) s in *) + (* We want to filter out all i in s' where (t)x with x in r could be i. *) + (* Since this is hard to compute, we just keep all i in s' which overflowed, since those are safe - all i which did not overflow may now be possible due to overflow of r. *) + (* S.diff s' s, r' *) + (* The above is needed for test 21/03, but not sound! See example https://github.com/goblint/analyzer/pull/95#discussion_r483023140 *) + `Excluded (S.empty (), r') + | `Definite x -> `Definite (Size.cast ik x) + | `Bot -> `Bot + + (* Wraps definite values and excluded values according to the ikind. + * For an `Excluded s,r , assumes that r is already an overapproximation of the range of possible values. + * r might be larger than the possible range of this type; the range of the returned `Excluded set will be within the bounds of the ikind. + *) + let norm ik v = + match v with + | `Excluded (s, r) -> + let possibly_overflowed = not (R.leq r (size ik)) || not (S.for_all (in_range (size ik)) s) in + (* If no overflow occurred, just return x *) + if not possibly_overflowed then ( + v + ) + (* Else, if an overflow might have occurred but we should just ignore it *) + else if should_ignore_overflow ik then ( + let r = size ik in + (* filter out excluded elements that are not in the range *) + let mapped_excl = S.filter (in_range r) s in + `Excluded (mapped_excl, r) + ) + (* Else, if an overflow occurred that we should not treat with wrap-around, go to top *) + else if not (should_wrap ik) then ( + top_of ik + ) else ( + (* Else an overflow occurred that we should treat with wrap-around *) + let r = size ik in + (* Perform a wrap-around for unsigned values and for signed values (if configured). *) + let mapped_excl = S.map (fun excl -> Size.cast ik excl) s in + match ik with + | IBool -> + begin match S.mem Z.zero mapped_excl, S.mem Z.one mapped_excl with + | false, false -> `Excluded (mapped_excl, r) (* Not {} -> Not {} *) + | true, false -> `Definite Z.one (* Not {0} -> 1 *) + | false, true -> `Definite Z.zero (* Not {1} -> 0 *) + | true, true -> `Bot (* Not {0, 1} -> bot *) + end + | ik -> + `Excluded (mapped_excl, r) + ) + | `Definite x -> + let min, max = Size.range ik in + (* Perform a wrap-around for unsigned values and for signed values (if configured). *) + if should_wrap ik then ( + cast_to ik v + ) + else if Z.compare min x <= 0 && Z.compare x max <= 0 then ( + v + ) + else if should_ignore_overflow ik then ( + M.warn ~category:M.Category.Integer.overflow "DefExc: Value was outside of range, indicating overflow, but 'sem.int.signed_overflow' is 'assume_none' -> Returned Bot"; + `Bot + ) + else ( + top_of ik + ) + | `Bot -> `Bot + + let leq x y = match (x,y) with + (* `Bot <= x is always true *) + | `Bot, _ -> true + (* Anything except bot <= bot is always false *) + | _, `Bot -> false + (* Two known values are leq whenever equal *) + | `Definite (x: int_t), `Definite y -> x = y + (* A definite value is leq all exclusion sets that don't contain it *) + | `Definite x, `Excluded (s,r) -> in_range r x && not (S.mem x s) + (* No finite exclusion set can be leq than a definite value *) + | `Excluded (xs, xr), `Definite d -> + Exclusion.(leq_excl_incl (Exc (xs, xr)) (Inc (S.singleton d))) + | `Excluded (xs,xr), `Excluded (ys,yr) -> + Exclusion.(leq (Exc (xs,xr)) (Exc (ys, yr))) + + let join' ?range ik x y = + match (x,y) with + (* The least upper bound with the bottom element: *) + | `Bot, x -> x + | x, `Bot -> x + (* The case for two known values: *) + | `Definite (x: int_t), `Definite y -> + (* If they're equal, it's just THAT value *) + if x = y then `Definite x + (* Unless one of them is zero, we can exclude it: *) + else + let a,b = Size.min_range_sign_agnostic x, Size.min_range_sign_agnostic y in + let r = R.join (R.of_interval range_ikind a) (R.of_interval range_ikind b) in + `Excluded ((if Z.equal x Z.zero || Z.equal y Z.zero then S.empty () else S.singleton Z.zero), r) + (* A known value and an exclusion set... the definite value should no + * longer be excluded: *) + | `Excluded (s,r), `Definite x + | `Definite x, `Excluded (s,r) -> + if not (in_range r x) then + let a = R.of_interval range_ikind (Size.min_range_sign_agnostic x) in + `Excluded (S.remove x s, R.join a r) + else + `Excluded (S.remove x s, r) + (* For two exclusion sets, only their intersection can be excluded: *) + | `Excluded (x,wx), `Excluded (y,wy) -> `Excluded (S.inter x y, range |? R.join wx wy) + + let join ik = join' ik + + + let widen ik x y = + if get_def_exc_widen_by_join () then + join' ik x y + else if equal x y then + x + else + join' ~range:(size ik) ik x y + + + let meet ik x y = + match (x,y) with + (* Greatest LOWER bound with the least element is trivial: *) + | `Bot, _ -> `Bot + | _, `Bot -> `Bot + (* Definite elements are either equal or the glb is bottom *) + | `Definite x, `Definite y -> if x = y then `Definite x else `Bot + (* The glb of a definite element and an exclusion set is either bottom or + * just the element itself, if it isn't in the exclusion set *) + | `Excluded (s,r), `Definite x + | `Definite x, `Excluded (s,r) -> if S.mem x s || not (in_range r x) then `Bot else `Definite x + (* The greatest lower bound of two exclusion sets is their union, this is + * just DeMorgans Law *) + | `Excluded (x,r1), `Excluded (y,r2) -> + let r' = R.meet r1 r2 in + let s' = S.union x y |> S.filter (in_range r') in + `Excluded (s', r') + + let narrow ik x y = x + + let of_int ik x = norm ik @@ `Definite x + let to_int x = match x with + | `Definite x -> Some x + | _ -> None + + let from_excl ikind (s: S.t) = norm ikind @@ `Excluded (s, size ikind) + + let of_bool_cmp ik x = of_int ik (if x then Z.one else Z.zero) + let of_bool = of_bool_cmp + let to_bool x = + match x with + | `Definite x -> Some (IntOps.BigIntOps.to_bool x) + | `Excluded (s,r) when S.mem Z.zero s -> Some true + | _ -> None + let top_bool = `Excluded (S.empty (), R.of_interval range_ikind (0L, 1L)) + + let of_interval ?(suppress_ovwarn=false) ik (x,y) = + if Z.compare x y = 0 then + of_int ik x + else + let a, b = Size.min_range_sign_agnostic x, Size.min_range_sign_agnostic y in + let r = R.join (R.of_interval ~suppress_ovwarn range_ikind a) (R.of_interval ~suppress_ovwarn range_ikind b) in + let ex = if Z.gt x Z.zero || Z.lt y Z.zero then S.singleton Z.zero else S.empty () in + norm ik @@ (`Excluded (ex, r)) + + let starting ?(suppress_ovwarn=false) ikind x = + let _,u_ik = Size.range ikind in + of_interval ~suppress_ovwarn ikind (x, u_ik) + + let ending ?(suppress_ovwarn=false) ikind x = + let l_ik,_ = Size.range ikind in + of_interval ~suppress_ovwarn ikind (l_ik, x) + + let of_excl_list t l = + let r = size t in (* elements in l are excluded from the full range of t! *) + `Excluded (List.fold_right S.add l (S.empty ()), r) + let is_excl_list l = match l with `Excluded _ -> true | _ -> false + let to_excl_list (x:t) = match x with + | `Definite _ -> None + | `Excluded (s,r) -> Some (S.elements s, (Option.get (R.minimal r), Option.get (R.maximal r))) + | `Bot -> None + + let to_incl_list x = match x with + | `Definite x -> Some [x] + | `Excluded _ -> None + | `Bot -> None + + let apply_range f r = (* apply f to the min/max of the old range r to get a new range *) + (* If the Int64 might overflow on us during computation, we instead go to top_range *) + match R.minimal r, R.maximal r with + | _ -> + let rf m = (size % Size.min_for % f) (m r) in + let r1, r2 = rf Exclusion.min_of_range, rf Exclusion.max_of_range in + R.join r1 r2 + + (* Default behaviour for unary operators, simply maps the function to the + * DefExc data structure. *) + let lift1 f ik x = norm ik @@ match x with + | `Excluded (s,r) -> + let s' = S.map f s in + `Excluded (s', apply_range f r) + | `Definite x -> `Definite (f x) + | `Bot -> `Bot + + let lift2 f ik x y = norm ik (match x,y with + (* We don't bother with exclusion sets: *) + | `Excluded _, `Definite _ + | `Definite _, `Excluded _ + | `Excluded _, `Excluded _ -> top () + (* The good case: *) + | `Definite x, `Definite y -> + (try `Definite (f x y) with | Division_by_zero -> top ()) + | `Bot, `Bot -> `Bot + | _ -> + (* If only one of them is bottom, we raise an exception that eval_rv will catch *) + raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y)))) + + (* Default behaviour for binary operators that are injective in either + * argument, so that Exclusion Sets can be used: *) + let lift2_inj f ik x y = + let def_exc f x s r = `Excluded (S.map (f x) s, apply_range (f x) r) in + norm ik @@ + match x,y with + (* If both are exclusion sets, there isn't anything we can do: *) + | `Excluded _, `Excluded _ -> top () + (* A definite value should be applied to all members of the exclusion set *) + | `Definite x, `Excluded (s,r) -> def_exc f x s r + (* Same thing here, but we should flip the operator to map it properly *) + | `Excluded (s,r), `Definite x -> def_exc (Batteries.flip f) x s r + (* The good case: *) + | `Definite x, `Definite y -> `Definite (f x y) + | `Bot, `Bot -> `Bot + | _ -> + (* If only one of them is bottom, we raise an exception that eval_rv will catch *) + raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) + + (* The equality check: *) + let eq ik x y = match x,y with + (* Not much to do with two exclusion sets: *) + | `Excluded _, `Excluded _ -> top () + (* Is x equal to an exclusion set, if it is a member then NO otherwise we + * don't know: *) + | `Definite x, `Excluded (s,r) -> if S.mem x s then of_bool IInt false else top () + | `Excluded (s,r), `Definite x -> if S.mem x s then of_bool IInt false else top () + (* The good case: *) + | `Definite x, `Definite y -> of_bool IInt (x = y) + | `Bot, `Bot -> `Bot + | _ -> + (* If only one of them is bottom, we raise an exception that eval_rv will catch *) + raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) + + (* The inequality check: *) + let ne ik x y = match x,y with + (* Not much to do with two exclusion sets: *) + | `Excluded _, `Excluded _ -> top () + (* Is x unequal to an exclusion set, if it is a member then Yes otherwise we + * don't know: *) + | `Definite x, `Excluded (s,r) -> if S.mem x s then of_bool IInt true else top () + | `Excluded (s,r), `Definite x -> if S.mem x s then of_bool IInt true else top () + (* The good case: *) + | `Definite x, `Definite y -> of_bool IInt (x <> y) + | `Bot, `Bot -> `Bot + | _ -> + (* If only one of them is bottom, we raise an exception that eval_rv will catch *) + raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) + + let neg ?no_ov ik (x :t) = norm ik @@ lift1 Z.neg ik x + let add ?no_ov ik x y = norm ik @@ lift2_inj Z.add ik x y + + let sub ?no_ov ik x y = norm ik @@ lift2_inj Z.sub ik x y + let mul ?no_ov ik x y = norm ik @@ match x, y with + | `Definite z, (`Excluded _ | `Definite _) when Z.equal z Z.zero -> x + | (`Excluded _ | `Definite _), `Definite z when Z.equal z Z.zero -> y + | `Definite a, `Excluded (s,r) + (* Integer multiplication with even numbers is not injective. *) + (* Thus we cannot exclude the values to which the exclusion set would be mapped to. *) + | `Excluded (s,r),`Definite a when Z.equal (Z.rem a (Z.of_int 2)) Z.zero -> `Excluded (S.empty (), apply_range (Z.mul a) r) + | _ -> lift2_inj Z.mul ik x y + let div ?no_ov ik x y = lift2 Z.div ik x y + let rem ik x y = lift2 Z.rem ik x y + + (* Comparison handling copied from Enums. *) + let handle_bot x y f = match x, y with + | `Bot, `Bot -> `Bot + | `Bot, _ + | _, `Bot -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) + | _, _ -> f () + + let lt ik x y = + handle_bot x y (fun () -> + match minimal x, maximal x, minimal y, maximal y with + | _, Some x2, Some y1, _ when Z.compare x2 y1 < 0 -> of_bool ik true + | Some x1, _, _, Some y2 when Z.compare x1 y2 >= 0 -> of_bool ik false + | _, _, _, _ -> top_bool) + + let gt ik x y = lt ik y x + + let le ik x y = + handle_bot x y (fun () -> + match minimal x, maximal x, minimal y, maximal y with + | _, Some x2, Some y1, _ when Z.compare x2 y1 <= 0 -> of_bool ik true + | Some x1, _, _, Some y2 when Z.compare x1 y2 > 0 -> of_bool ik false + | _, _, _, _ -> top_bool) + + let ge ik x y = le ik y x + + let lognot = lift1 Z.lognot + + let logand ik x y = norm ik (match x,y with + (* We don't bother with exclusion sets: *) + | `Excluded _, `Definite i -> + (* Except in two special cases *) + if Z.equal i Z.zero then + `Definite Z.zero + else if Z.equal i Z.one then + of_interval IBool (Z.zero, Z.one) + else + top () + | `Definite _, `Excluded _ + | `Excluded _, `Excluded _ -> top () + (* The good case: *) + | `Definite x, `Definite y -> + (try `Definite (Z.logand x y) with | Division_by_zero -> top ()) + | `Bot, `Bot -> `Bot + | _ -> + (* If only one of them is bottom, we raise an exception that eval_rv will catch *) + raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y)))) + + + let logor = lift2 Z.logor + let logxor = lift2 Z.logxor + + let shift (shift_op: int_t -> int -> int_t) (ik: Cil.ikind) (x: t) (y: t) = + (* BigInt only accepts int as second argument for shifts; perform conversion here *) + let shift_op_big_int a (b: int_t) = + let (b : int) = Z.to_int b in + shift_op a b + in + (* If one of the parameters of the shift is negative, the result is undefined *) + let is_negative = GobOption.for_all (fun x -> Z.lt x Z.zero) in + if is_negative (minimal x) || is_negative (minimal y) then + top_of ik + else + norm ik @@ lift2 shift_op_big_int ik x y + + let shift_left = + shift Z.shift_left + + let shift_right = + shift Z.shift_right + (* TODO: lift does not treat Not {0} as true. *) + let c_logand ik x y = + match to_bool x, to_bool y with + | Some false, _ + | _, Some false -> + of_bool ik false + | _, _ -> + lift2 IntOps.BigIntOps.c_logand ik x y + let c_logor ik x y = + match to_bool x, to_bool y with + | Some true, _ + | _, Some true -> + of_bool ik true + | _, _ -> + lift2 IntOps.BigIntOps.c_logor ik x y + let c_lognot ik = eq ik (of_int ik Z.zero) + + let invariant_ikind e ik (x:t) = + match x with + | `Definite x -> + IntInvariant.of_int e ik x + | `Excluded (s, r) -> + (* Emit range invariant if tighter than ikind bounds. + This can be more precise than interval, which has been widened. *) + let (rmin, rmax) = (Exclusion.min_of_range r, Exclusion.max_of_range r) in + let ri = IntInvariant.of_interval e ik (rmin, rmax) in + let si = IntInvariant.of_excl_list e ik (S.elements s) in + Invariant.(ri && si) + | `Bot -> Invariant.none + + let arbitrary ik = + let open QCheck.Iter in + let excluded s = from_excl ik s in + let definite x = of_int ik x in + let shrink = function + | `Excluded (s, _) -> GobQCheck.shrink (S.arbitrary ()) s >|= excluded (* S TODO: possibly shrink excluded to definite *) + | `Definite x -> (return `Bot) <+> (GobQCheck.shrink (IntOps.BigIntOps.arbitrary ()) x >|= definite) + | `Bot -> empty + in + QCheck.frequency ~shrink ~print:show [ + 20, QCheck.map excluded (S.arbitrary ()); + 10, QCheck.map definite (IntOps.BigIntOps.arbitrary ()); + 1, QCheck.always `Bot + ] (* S TODO: decide frequencies *) + + let refine_with_congruence ik a b = a + let refine_with_interval ik a b = match a, b with + | x, Some(i) -> meet ik x (of_interval ik i) + | _ -> a + let refine_with_excl_list ik a b = match a, b with + | `Excluded (s, r), Some(ls, _) -> meet ik (`Excluded (s, r)) (of_excl_list ik ls) (* TODO: refine with excl range? *) + | _ -> a + let refine_with_incl_list ik a b = a + + let project ik p t = t +end diff --git a/src/cdomain/value/cdomains/int/enumsDomain.ml b/src/cdomain/value/cdomains/int/enumsDomain.ml new file mode 100644 index 0000000000..d1020cfe2d --- /dev/null +++ b/src/cdomain/value/cdomains/int/enumsDomain.ml @@ -0,0 +1,372 @@ +open IntDomain0 +open IntervalDomain +open DefExcDomain +open GoblintCil + + +(* Inclusion/Exclusion sets. Go to top on arithmetic operations (except for some easy cases, e.g. multiplication with 0). Joins on widen, i.e. precise integers as long as not derived from arithmetic expressions. *) +module Enums : S with type int_t = Z.t = struct + module R = Interval32 (* range for exclusion *) + + let range_ikind = Cil.IInt + let size t = R.of_interval range_ikind (let a,b = Size.bits_i64 t in Int64.neg a,b) + + type t = Inc of BISet.t | Exc of BISet.t * R.t [@@deriving eq, ord, hash] (* inclusion/exclusion set *) + + type int_t = Z.t + let name () = "enums" + let bot () = failwith "bot () not implemented for Enums" + let top () = failwith "top () not implemented for Enums" + let bot_of ik = Inc (BISet.empty ()) + let top_bool = Inc (BISet.of_list [Z.zero; Z.one]) + let top_of ik = + match ik with + | IBool -> top_bool + | _ -> Exc (BISet.empty (), size ik) + + let range ik = Size.range ik + +(* + let max_of_range r = Size.max_from_bit_range (Option.get (R.maximal r)) + let min_of_range r = Size.min_from_bit_range (Option.get (R.minimal r)) + let cardinality_of_range r = Z.add (Z.neg (min_of_range r)) (max_of_range r) *) + let value_in_range (min, max) v = Z.compare min v <= 0 && Z.compare v max <= 0 + + let show = function + | Inc xs when BISet.is_empty xs -> "bot" + | Inc xs -> "{" ^ (String.concat ", " (List.map Z.to_string (BISet.elements xs))) ^ "}" + | Exc (xs,r) -> "not {" ^ (String.concat ", " (List.map Z.to_string (BISet.elements xs))) ^ "} " ^ "("^R.show r^")" + + include Std (struct type nonrec t = t let name = name let top_of = top_of let bot_of = bot_of let show = show let equal = equal end) + + (* Normalization function for enums, that handles overflows for Inc. + As we do not compute on Excl, we do not have to perform any overflow handling for it. *) + let norm ikind v = + let min, max = range ikind in + (* Whether the value v lies within the values of the specified ikind. *) + let value_in_ikind v = + Z.compare min v <= 0 && Z.compare v max <= 0 + in + match v with + | Inc xs when BISet.for_all value_in_ikind xs -> v + | Inc xs -> + if should_wrap ikind then + Inc (BISet.map (Size.cast ikind) xs) + else if should_ignore_overflow ikind then + Inc (BISet.filter value_in_ikind xs) + else + top_of ikind + | Exc (xs, r) -> + (* The following assert should hold for Exc, therefore we do not have to overflow handling / normalization for it: + let range_in_ikind r = + R.leq r (size ikind) + in + let r_min, r_max = min_of_range r, max_of_range r in + assert (range_in_ikind r && BISet.for_all (value_in_range (r_min, r_max)) xs); *) + begin match ikind with + | IBool -> + begin match BISet.mem Z.zero xs, BISet.mem Z.one xs with + | false, false -> top_bool (* Not {} -> {0, 1} *) + | true, false -> Inc (BISet.singleton Z.one) (* Not {0} -> {1} *) + | false, true -> Inc (BISet.singleton Z.zero) (* Not {1} -> {0} *) + | true, true -> bot_of ikind (* Not {0, 1} -> bot *) + end + | _ -> + v + end + + + let equal_to i = function + | Inc x -> + if BISet.mem i x then + if BISet.is_singleton x then `Eq + else `Top + else `Neq + | Exc (x, r) -> + if BISet.mem i x then `Neq + else `Top + + let cast_to ?(suppress_ovwarn=false) ?torg ?no_ov ik v = norm ik @@ match v with + | Exc (s,r) -> + let r' = size ik in + if R.leq r r' then (* upcast -> no change *) + Exc (s, r) + else if ik = IBool then (* downcast to bool *) + if BISet.mem Z.zero s then + Inc (BISet.singleton Z.one) + else + Exc (BISet.empty(), r') + else (* downcast: may overflow *) + Exc ((BISet.empty ()), r') + | Inc xs -> + let casted_xs = BISet.map (Size.cast ik) xs in + if Cil.isSigned ik && not (BISet.equal xs casted_xs) + then top_of ik (* When casting into a signed type and the result does not fit, the behavior is implementation-defined *) + else Inc casted_xs + + let of_int ikind x = cast_to ikind (Inc (BISet.singleton x)) + + let of_interval ?(suppress_ovwarn=false) ik (x, y) = + if Z.compare x y = 0 then + of_int ik x + else + let a, b = Size.min_range_sign_agnostic x, Size.min_range_sign_agnostic y in + let r = R.join (R.of_interval ~suppress_ovwarn range_ikind a) (R.of_interval ~suppress_ovwarn range_ikind b) in + let ex = if Z.gt x Z.zero || Z.lt y Z.zero then BISet.singleton Z.zero else BISet.empty () in + norm ik @@ (Exc (ex, r)) + + let join _ x y = + match x, y with + | Inc x, Inc y -> Inc (BISet.union x y) + | Exc (x,r1), Exc (y,r2) -> Exc (BISet.inter x y, R.join r1 r2) + | Exc (x,r), Inc y + | Inc y, Exc (x,r) -> + let r = if BISet.is_empty y + then r + else + let (min_el_range, max_el_range) = Batteries.Tuple2.mapn (fun x -> R.of_interval range_ikind (Size.min_range_sign_agnostic x)) (BISet.min_elt y, BISet.max_elt y) in + let range = R.join min_el_range max_el_range in + R.join r range + in + Exc (BISet.diff x y, r) + + let meet _ x y = + match x, y with + | Inc x, Inc y -> Inc (BISet.inter x y) + | Exc (x,r1), Exc (y,r2) -> + let r = R.meet r1 r2 in + let r_min, r_max = Exclusion.min_of_range r, Exclusion.max_of_range r in + let filter_by_range = BISet.filter (value_in_range (r_min, r_max)) in + (* We remove those elements from the exclusion set that do not fit in the range anyway *) + let excl = BISet.union (filter_by_range x) (filter_by_range y) in + Exc (excl, r) + | Inc x, Exc (y,r) + | Exc (y,r), Inc x -> Inc (BISet.diff x y) + + let widen = join + let narrow = meet + let leq a b = + match a, b with + | Inc xs, Exc (ys, r) -> + if BISet.is_empty xs + then true + else + let min_b, max_b = Exclusion.min_of_range r, Exclusion.max_of_range r in + let min_a, max_a = BISet.min_elt xs, BISet.max_elt xs in + (* Check that the xs fit into the range r *) + Z.compare min_b min_a <= 0 && Z.compare max_a max_b <= 0 && + (* && check that none of the values contained in xs is excluded, i.e. contained in ys. *) + BISet.for_all (fun x -> not (BISet.mem x ys)) xs + | Inc xs, Inc ys -> + BISet.subset xs ys + | Exc (xs, r), Exc (ys, s) -> + Exclusion.(leq (Exc (xs, r)) (Exc (ys, s))) + | Exc (xs, r), Inc ys -> + Exclusion.(leq_excl_incl (Exc (xs, r)) (Inc ys)) + + let handle_bot x y f = match is_bot x, is_bot y with + | false, false -> f () + | true, false + | false, true -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) + | true, true -> Inc (BISet.empty ()) + + let lift1 f ikind v = norm ikind @@ match v with + | Inc x when BISet.is_empty x -> v (* Return bottom when value is bottom *) + | Inc x when BISet.is_singleton x -> Inc (BISet.singleton (f (BISet.choose x))) + | _ -> top_of ikind + + let lift2 f (ikind: Cil.ikind) u v = + handle_bot u v (fun () -> + norm ikind @@ match u, v with + | Inc x,Inc y when BISet.is_singleton x && BISet.is_singleton y -> Inc (BISet.singleton (f (BISet.choose x) (BISet.choose y))) + | _,_ -> top_of ikind) + + let lift2 f ikind a b = + try lift2 f ikind a b with Division_by_zero -> top_of ikind + + let neg ?no_ov = lift1 Z.neg + let add ?no_ov ikind a b = + match a, b with + | Inc z,x when BISet.is_singleton z && BISet.choose z = Z.zero -> x + | x,Inc z when BISet.is_singleton z && BISet.choose z = Z.zero -> x + | x,y -> lift2 Z.add ikind x y + let sub ?no_ov = lift2 Z.sub + let mul ?no_ov ikind a b = + match a, b with + | Inc one,x when BISet.is_singleton one && BISet.choose one = Z.one -> x + | x,Inc one when BISet.is_singleton one && BISet.choose one = Z.one -> x + | Inc zero,_ when BISet.is_singleton zero && BISet.choose zero = Z.zero -> a + | _,Inc zero when BISet.is_singleton zero && BISet.choose zero = Z.zero -> b + | x,y -> lift2 Z.mul ikind x y + + let div ?no_ov ikind a b = match a, b with + | x,Inc one when BISet.is_singleton one && BISet.choose one = Z.one -> x + | _,Inc zero when BISet.is_singleton zero && BISet.choose zero = Z.zero -> top_of ikind + | Inc zero,_ when BISet.is_singleton zero && BISet.choose zero = Z.zero -> a + | x,y -> lift2 Z.div ikind x y + + let rem = lift2 Z.rem + + let lognot = lift1 Z.lognot + let logand = lift2 Z.logand + let logor = lift2 Z.logor + let logxor = lift2 Z.logxor + + let shift (shift_op: int_t -> int -> int_t) (ik: Cil.ikind) (x: t) (y: t) = + handle_bot x y (fun () -> + (* BigInt only accepts int as second argument for shifts; perform conversion here *) + let shift_op_big_int a (b: int_t) = + let (b : int) = Z.to_int b in + shift_op a b + in + (* If one of the parameters of the shift is negative, the result is undefined *) + let is_negative = GobOption.for_all (fun x -> Z.lt x Z.zero) in + if is_negative (minimal x) || is_negative (minimal y) then + top_of ik + else + lift2 shift_op_big_int ik x y) + + let shift_left = + shift Z.shift_left + + let shift_right = + shift Z.shift_right + + let of_bool ikind x = Inc (BISet.singleton (if x then Z.one else Z.zero)) + let to_bool = function + | Inc e when BISet.is_empty e -> None + | Exc (e,_) when BISet.is_empty e -> None + | Inc zero when BISet.is_singleton zero && BISet.choose zero = Z.zero -> Some false + | Inc xs when BISet.for_all ((<>) Z.zero) xs -> Some true + | Exc (xs,_) when BISet.exists ((=) Z.zero) xs -> Some true + | _ -> None + let to_int = function Inc x when BISet.is_singleton x -> Some (BISet.choose x) | _ -> None + + let to_excl_list = function Exc (x,r) when not (BISet.is_empty x) -> Some (BISet.elements x, (Option.get (R.minimal r), Option.get (R.maximal r))) | _ -> None + let of_excl_list ik xs = + let min_ik, max_ik = Size.range ik in + let exc = BISet.of_list @@ List.filter (value_in_range (min_ik, max_ik)) xs in + norm ik @@ Exc (exc, size ik) + let is_excl_list = BatOption.is_some % to_excl_list + let to_incl_list = function Inc s when not (BISet.is_empty s) -> Some (BISet.elements s) | _ -> None + + let starting ?(suppress_ovwarn=false) ikind x = + let _,u_ik = Size.range ikind in + of_interval ~suppress_ovwarn ikind (x, u_ik) + + let ending ?(suppress_ovwarn=false) ikind x = + let l_ik,_ = Size.range ikind in + of_interval ~suppress_ovwarn ikind (l_ik, x) + + let c_lognot ik x = + if is_bot x + then x + else + match to_bool x with + | Some b -> of_bool ik (not b) + | None -> top_bool + + let c_logand = lift2 IntOps.BigIntOps.c_logand + let c_logor = lift2 IntOps.BigIntOps.c_logor + let maximal = function + | Inc xs when not (BISet.is_empty xs) -> Some (BISet.max_elt xs) + | Exc (excl,r) -> + let rec decrement_while_contained v = + if BISet.mem v excl + then decrement_while_contained (Z.pred v) + else v + in + let range_max = Exclusion.max_of_range r in + Some (decrement_while_contained range_max) + | _ (* bottom case *) -> None + + let minimal = function + | Inc xs when not (BISet.is_empty xs) -> Some (BISet.min_elt xs) + | Exc (excl,r) -> + let rec increment_while_contained v = + if BISet.mem v excl + then increment_while_contained (Z.succ v) + else v + in + let range_min = Exclusion.min_of_range r in + Some (increment_while_contained range_min) + | _ (* bottom case *) -> None + + let lt ik x y = + handle_bot x y (fun () -> + match minimal x, maximal x, minimal y, maximal y with + | _, Some x2, Some y1, _ when Z.compare x2 y1 < 0 -> of_bool ik true + | Some x1, _, _, Some y2 when Z.compare x1 y2 >= 0 -> of_bool ik false + | _, _, _, _ -> top_bool) + + let gt ik x y = lt ik y x + + let le ik x y = + handle_bot x y (fun () -> + match minimal x, maximal x, minimal y, maximal y with + | _, Some x2, Some y1, _ when Z.compare x2 y1 <= 0 -> of_bool ik true + | Some x1, _, _, Some y2 when Z.compare x1 y2 > 0 -> of_bool ik false + | _, _, _, _ -> top_bool) + + let ge ik x y = le ik y x + + let eq ik x y = + handle_bot x y (fun () -> + match x, y with + | Inc xs, Inc ys when BISet.is_singleton xs && BISet.is_singleton ys -> of_bool ik (Z.equal (BISet.choose xs) (BISet.choose ys)) + | _, _ -> + if is_bot (meet ik x y) then + (* If the meet is empty, there is no chance that concrete values are equal *) + of_bool ik false + else + top_bool) + + let ne ik x y = c_lognot ik (eq ik x y) + + let invariant_ikind e ik x = + match x with + | Inc ps -> + IntInvariant.of_incl_list e ik (BISet.elements ps) + | Exc (ns, r) -> + (* Emit range invariant if tighter than ikind bounds. + This can be more precise than interval, which has been widened. *) + let (rmin, rmax) = (Exclusion.min_of_range r, Exclusion.max_of_range r) in + let ri = IntInvariant.of_interval e ik (rmin, rmax) in + let nsi = IntInvariant.of_excl_list e ik (BISet.elements ns) in + Invariant.(ri && nsi) + + + let arbitrary ik = + let open QCheck.Iter in + let neg s = of_excl_list ik (BISet.elements s) in + let pos s = norm ik (Inc s) in + let shrink = function + | Exc (s, _) -> GobQCheck.shrink (BISet.arbitrary ()) s >|= neg (* S TODO: possibly shrink neg to pos *) + | Inc s -> GobQCheck.shrink (BISet.arbitrary ()) s >|= pos + in + QCheck.frequency ~shrink ~print:show [ + 20, QCheck.map neg (BISet.arbitrary ()); + 10, QCheck.map pos (BISet.arbitrary ()); + ] (* S TODO: decide frequencies *) + + let refine_with_congruence ik a b = + let contains c m x = if Z.equal m Z.zero then Z.equal c x else Z.equal (Z.rem (Z.sub x c) m) Z.zero in + match a, b with + | Inc e, None -> bot_of ik + | Inc e, Some (c, m) -> Inc (BISet.filter (contains c m) e) + | _ -> a + + let refine_with_interval ik a b = a (* TODO: refine inclusion (exclusion?) set *) + + let refine_with_excl_list ik a b = + match b with + | Some (ls, _) -> meet ik a (of_excl_list ik ls) (* TODO: refine with excl range? *) + | _ -> a + + let refine_with_incl_list ik a b = + match a, b with + | Inc x, Some (ls) -> meet ik (Inc x) (Inc (BISet.of_list ls)) + | _ -> a + + let project ik p t = t +end diff --git a/src/cdomain/value/cdomains/int/intDomTuple.ml b/src/cdomain/value/cdomains/int/intDomTuple.ml new file mode 100644 index 0000000000..34647795d8 --- /dev/null +++ b/src/cdomain/value/cdomains/int/intDomTuple.ml @@ -0,0 +1,524 @@ +open IntDomain0 +open IntervalDomain +open IntervalSetDomain +open DefExcDomain +open EnumsDomain +open CongruenceDomain +open GoblintCil +open Pretty +open PrecisionUtil + + +(* The old IntDomList had too much boilerplate since we had to edit every function in S when adding a new domain. With the following, we only have to edit the places where fn are applied, i.e., create, mapp, map, map2. You can search for I3 below to see where you need to extend. *) +(* discussion: https://github.com/goblint/analyzer/pull/188#issuecomment-818928540 *) +module IntDomTupleImpl = struct + include Printable.Std (* for default invariant, tag, ... *) + + open Batteries + type int_t = Z.t + module I1 = SOverflowLifter (DefExc) + module I2 = Interval + module I3 = SOverflowLifter (Enums) + module I4 = SOverflowLifter (Congruence) + module I5 = IntervalSetFunctor (IntOps.BigIntOps) + + type t = I1.t option * I2.t option * I3.t option * I4.t option * I5.t option + [@@deriving eq, ord, hash] + + let name () = "intdomtuple" + + (* The Interval domain can lead to too many contexts for recursive functions (top is [min,max]), but we don't want to drop all ints as with `ana.base.context.int`. TODO better solution? *) + let no_interval = Tuple5.map2 (const None) + let no_intervalSet = Tuple5.map5 (const None) + + type 'a m = (module SOverflow with type t = 'a) + type 'a m2 = (module SOverflow with type t = 'a and type int_t = int_t ) + + (* only first-order polymorphism on functions -> use records to get around monomorphism restriction on arguments *) + type 'b poly_in = { fi : 'a. 'a m -> 'b -> 'a } [@@unboxed] (* inject *) + type 'b poly2_in = { fi2 : 'a. 'a m2 -> 'b -> 'a } [@@unboxed] (* inject for functions that depend on int_t *) + type 'b poly2_in_ovc = { fi2_ovc : 'a. 'a m2 -> 'b -> 'a * overflow_info} [@@unboxed] (* inject for functions that depend on int_t *) + + type 'b poly_pr = { fp : 'a. 'a m -> 'a -> 'b } [@@unboxed] (* project *) + type 'b poly_pr2 = { fp2 : 'a. 'a m2 -> 'a -> 'b } [@@unboxed] (* project for functions that depend on int_t *) + type 'b poly2_pr = {f2p: 'a. 'a m -> ?no_ov:bool -> 'a -> 'a -> 'b} [@@unboxed] + type poly1 = {f1: 'a. 'a m -> ?no_ov:bool -> 'a -> 'a} [@@unboxed] (* needed b/c above 'b must be different from 'a *) + type poly1_ovc = {f1_ovc: 'a. 'a m -> ?no_ov:bool -> 'a -> 'a * overflow_info } [@@unboxed] (* needed b/c above 'b must be different from 'a *) + type poly2 = {f2: 'a. 'a m -> ?no_ov:bool -> 'a -> 'a -> 'a} [@@unboxed] + type poly2_ovc = {f2_ovc: 'a. 'a m -> ?no_ov:bool -> 'a -> 'a -> 'a * overflow_info } [@@unboxed] + type 'b poly3 = { f3: 'a. 'a m -> 'a option } [@@unboxed] (* used for projection to given precision *) + let create r x ((p1, p2, p3, p4, p5): int_precision) = + let f b g = if b then Some (g x) else None in + f p1 @@ r.fi (module I1), f p2 @@ r.fi (module I2), f p3 @@ r.fi (module I3), f p4 @@ r.fi (module I4), f p5 @@ r.fi (module I5) + let create r x = (* use where values are introduced *) + create r x (int_precision_from_node_or_config ()) + let create2 r x ((p1, p2, p3, p4, p5): int_precision) = + let f b g = if b then Some (g x) else None in + f p1 @@ r.fi2 (module I1), f p2 @@ r.fi2 (module I2), f p3 @@ r.fi2 (module I3), f p4 @@ r.fi2 (module I4), f p5 @@ r.fi2 (module I5) + let create2 r x = (* use where values are introduced *) + create2 r x (int_precision_from_node_or_config ()) + + let no_overflow ik = function + | Some(_, {underflow; overflow}) -> not (underflow || overflow) + | _ -> false + + let check_ov ?(suppress_ovwarn = false) ~cast ik intv intv_set = + let no_ov = (no_overflow ik intv) || (no_overflow ik intv_set) in + if not no_ov && not suppress_ovwarn && ( BatOption.is_some intv || BatOption.is_some intv_set) then ( + let (_,{underflow=underflow_intv; overflow=overflow_intv}) = match intv with None -> (I2.bot (), {underflow= true; overflow = true}) | Some x -> x in + let (_,{underflow=underflow_intv_set; overflow=overflow_intv_set}) = match intv_set with None -> (I5.bot (), {underflow= true; overflow = true}) | Some x -> x in + let underflow = underflow_intv && underflow_intv_set in + let overflow = overflow_intv && overflow_intv_set in + set_overflow_flag ~cast ~underflow ~overflow ik; + ); + no_ov + + let create2_ovc ik r x ((p1, p2, p3, p4, p5): int_precision) = + let f b g = if b then Some (g x) else None in + let map x = Option.map fst x in + let intv = f p2 @@ r.fi2_ovc (module I2) in + let intv_set = f p5 @@ r.fi2_ovc (module I5) in + ignore (check_ov ~cast:false ik intv intv_set); + map @@ f p1 @@ r.fi2_ovc (module I1), map @@ f p2 @@ r.fi2_ovc (module I2), map @@ f p3 @@ r.fi2_ovc (module I3), map @@ f p4 @@ r.fi2_ovc (module I4), map @@ f p5 @@ r.fi2_ovc (module I5) + + let create2_ovc ik r x = (* use where values are introduced *) + create2_ovc ik r x (int_precision_from_node_or_config ()) + + + let opt_map2 f ?no_ov = + curry @@ function Some x, Some y -> Some (f ?no_ov x y) | _ -> None + + let to_list x = Tuple5.enum x |> List.of_enum |> List.filter_map identity (* contains only the values of activated domains *) + let to_list_some x = List.filter_map identity @@ to_list x (* contains only the Some-values of activated domains *) + + let exists = function + | (Some true, _, _, _, _) + | (_, Some true, _, _, _) + | (_, _, Some true, _, _) + | (_, _, _, Some true, _) + | (_, _, _, _, Some true) -> + true + | _ -> + false + + let for_all = function + | (Some false, _, _, _, _) + | (_, Some false, _, _, _) + | (_, _, Some false, _, _) + | (_, _, _, Some false, _) + | (_, _, _, _, Some false) -> + false + | _ -> + true + + (* f0: constructors *) + let top () = create { fi = fun (type a) (module I:SOverflow with type t = a) -> I.top } () + let bot () = create { fi = fun (type a) (module I:SOverflow with type t = a) -> I.bot } () + let top_of = create { fi = fun (type a) (module I:SOverflow with type t = a) -> I.top_of } + let bot_of = create { fi = fun (type a) (module I:SOverflow with type t = a) -> I.bot_of } + let of_bool ik = create { fi = fun (type a) (module I:SOverflow with type t = a) -> I.of_bool ik } + let of_excl_list ik = create2 { fi2 = fun (type a) (module I:SOverflow with type t = a and type int_t = int_t) -> I.of_excl_list ik} + let of_int ik = create2_ovc ik { fi2_ovc = fun (type a) (module I:SOverflow with type t = a and type int_t = int_t) -> I.of_int ik } + let starting ?(suppress_ovwarn=false) ik = create2_ovc ik { fi2_ovc = fun (type a) (module I:SOverflow with type t = a and type int_t = int_t) -> I.starting ~suppress_ovwarn ik } + let ending ?(suppress_ovwarn=false) ik = create2_ovc ik { fi2_ovc = fun (type a) (module I:SOverflow with type t = a and type int_t = int_t) -> I.ending ~suppress_ovwarn ik } + let of_interval ?(suppress_ovwarn=false) ik = create2_ovc ik { fi2_ovc = fun (type a) (module I:SOverflow with type t = a and type int_t = int_t) -> I.of_interval ~suppress_ovwarn ik } + let of_congruence ik = create2 { fi2 = fun (type a) (module I:SOverflow with type t = a and type int_t = int_t) -> I.of_congruence ik } + + let refine_with_congruence ik ((a, b, c, d, e) : t) (cong : (int_t * int_t) option) : t= + let opt f a = + curry @@ function Some x, y -> Some (f a x y) | _ -> None + in + ( opt I1.refine_with_congruence ik a cong + , opt I2.refine_with_congruence ik b cong + , opt I3.refine_with_congruence ik c cong + , opt I4.refine_with_congruence ik d cong + , opt I5.refine_with_congruence ik e cong) + + let refine_with_interval ik (a, b, c, d, e) intv = + let opt f a = + curry @@ function Some x, y -> Some (f a x y) | _ -> None + in + ( opt I1.refine_with_interval ik a intv + , opt I2.refine_with_interval ik b intv + , opt I3.refine_with_interval ik c intv + , opt I4.refine_with_interval ik d intv + , opt I5.refine_with_interval ik e intv ) + + let refine_with_excl_list ik (a, b, c, d, e) excl = + let opt f a = + curry @@ function Some x, y -> Some (f a x y) | _ -> None + in + ( opt I1.refine_with_excl_list ik a excl + , opt I2.refine_with_excl_list ik b excl + , opt I3.refine_with_excl_list ik c excl + , opt I4.refine_with_excl_list ik d excl + , opt I5.refine_with_excl_list ik e excl ) + + let refine_with_incl_list ik (a, b, c, d, e) incl = + let opt f a = + curry @@ function Some x, y -> Some (f a x y) | _ -> None + in + ( opt I1.refine_with_incl_list ik a incl + , opt I2.refine_with_incl_list ik b incl + , opt I3.refine_with_incl_list ik c incl + , opt I4.refine_with_incl_list ik d incl + , opt I5.refine_with_incl_list ik e incl ) + + + let mapp r (a, b, c, d, e) = + let map = BatOption.map in + ( map (r.fp (module I1)) a + , map (r.fp (module I2)) b + , map (r.fp (module I3)) c + , map (r.fp (module I4)) d + , map (r.fp (module I5)) e) + + + let mapp2 r (a, b, c, d, e) = + BatOption. + ( map (r.fp2 (module I1)) a + , map (r.fp2 (module I2)) b + , map (r.fp2 (module I3)) c + , map (r.fp2 (module I4)) d + , map (r.fp2 (module I5)) e) + + + (* exists/for_all *) + let is_bot = exists % mapp { fp = fun (type a) (module I:SOverflow with type t = a) -> I.is_bot } + let is_top = for_all % mapp { fp = fun (type a) (module I:SOverflow with type t = a) -> I.is_top } + let is_top_of ik = for_all % mapp { fp = fun (type a) (module I:SOverflow with type t = a) -> I.is_top_of ik } + let is_excl_list = exists % mapp { fp = fun (type a) (module I:SOverflow with type t = a) -> I.is_excl_list } + + let map2p r (xa, xb, xc, xd, xe) (ya, yb, yc, yd, ye) = + ( opt_map2 (r.f2p (module I1)) xa ya + , opt_map2 (r.f2p (module I2)) xb yb + , opt_map2 (r.f2p (module I3)) xc yc + , opt_map2 (r.f2p (module I4)) xd yd + , opt_map2 (r.f2p (module I5)) xe ye) + + (* f2p: binary projections *) + let (%%) f g x = f % (g x) (* composition for binary function g *) + + let leq = + for_all + %% map2p {f2p= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.leq)} + + let flat f x = match to_list_some x with [] -> None | xs -> Some (f xs) + + let to_excl_list x = + let merge ps = + let (vs, rs) = List.split ps in + let (mins, maxs) = List.split rs in + (List.concat vs |> List.sort_uniq Z.compare, (List.min mins, List.max maxs)) + in + mapp2 { fp2 = fun (type a) (module I:SOverflow with type t = a and type int_t = int_t) -> I.to_excl_list } x |> flat merge + + let to_incl_list x = + let hd l = match l with h::t -> h | _ -> [] in + let tl l = match l with h::t -> t | _ -> [] in + let a y = BatSet.of_list (hd y) in + let b y = BatList.map BatSet.of_list (tl y) in + let merge y = BatSet.elements @@ BatList.fold BatSet.intersect (a y) (b y) + in + mapp2 { fp2 = fun (type a) (module I:SOverflow with type t = a and type int_t = int_t) -> I.to_incl_list } x |> flat merge + + let same show x = let xs = to_list_some x in let us = List.unique xs in let n = List.length us in + if n = 1 then Some (List.hd xs) + else ( + if n>1 then Messages.info ~category:Unsound "Inconsistent state! %a" (Pretty.docList ~sep:(Pretty.text ",") (Pretty.text % show)) us; (* do not want to abort *) + None + ) + let to_int = same Z.to_string % mapp2 { fp2 = fun (type a) (module I:SOverflow with type t = a and type int_t = int_t) -> I.to_int } + + let pretty () x = + match to_int x with + | Some v when not (GobConfig.get_bool "dbg.full-output") -> Pretty.text (Z.to_string v) + | _ -> + mapp { fp = fun (type a) (module I:SOverflow with type t = a) -> (* assert sf==I.short; *) I.pretty () } x + |> to_list + |> (fun xs -> + text "(" ++ ( + try + List.reduce (fun a b -> a ++ text "," ++ b) xs + with Invalid_argument _ -> + nil) + ++ text ")") (* NOTE: the version above does something else. also, we ignore the sf-argument here. *) + + let refine_functions ik : (t -> t) list = + let maybe reffun ik domtup dom = + match dom with Some y -> reffun ik domtup y | _ -> domtup + in + [(fun (a, b, c, d, e) -> refine_with_excl_list ik (a, b, c, d, e) (to_excl_list (a, b, c, d, e))); + (fun (a, b, c, d, e) -> refine_with_incl_list ik (a, b, c, d, e) (to_incl_list (a, b, c, d, e))); + (fun (a, b, c, d, e) -> maybe refine_with_interval ik (a, b, c, d, e) b); (* TODO: get interval across all domains with minimal and maximal *) + (fun (a, b, c, d, e) -> maybe refine_with_congruence ik (a, b, c, d, e) d)] + + let refine ik ((a, b, c, d, e) : t ) : t = + let dt = ref (a, b, c, d, e) in + (match get_refinement () with + | "never" -> () + | "once" -> + List.iter (fun f -> dt := f !dt) (refine_functions ik); + | "fixpoint" -> + let quit_loop = ref false in + while not !quit_loop do + let old_dt = !dt in + List.iter (fun f -> dt := f !dt) (refine_functions ik); + quit_loop := equal old_dt !dt; + if is_bot !dt then dt := bot_of ik; quit_loop := true; + if M.tracing then M.trace "cong-refine-loop" "old: %a, new: %a" pretty old_dt pretty !dt; + done; + | _ -> () + ); !dt + + + (* map with overflow check *) + let mapovc ?(suppress_ovwarn=false) ?(cast=false) ik r (a, b, c, d, e) = + let map f ?no_ov = function Some x -> Some (f ?no_ov x) | _ -> None in + let intv = map (r.f1_ovc (module I2)) b in + let intv_set = map (r.f1_ovc (module I5)) e in + let no_ov = check_ov ~suppress_ovwarn ~cast ik intv intv_set in + let no_ov = no_ov || should_ignore_overflow ik in + refine ik + ( map (fun ?no_ov x -> r.f1_ovc ?no_ov (module I1) x |> fst) a + , BatOption.map fst intv + , map (fun ?no_ov x -> r.f1_ovc ?no_ov (module I3) x |> fst) c + , map (fun ?no_ov x -> r.f1_ovc ?no_ov (module I4) x |> fst) ~no_ov d + , BatOption.map fst intv_set ) + + (* map2 with overflow check *) + let map2ovc ?(cast=false) ik r (xa, xb, xc, xd, xe) (ya, yb, yc, yd, ye) = + let intv = opt_map2 (r.f2_ovc (module I2)) xb yb in + let intv_set = opt_map2 (r.f2_ovc (module I5)) xe ye in + let no_ov = check_ov ~cast ik intv intv_set in + let no_ov = no_ov || should_ignore_overflow ik in + refine ik + ( opt_map2 (fun ?no_ov x y -> r.f2_ovc ?no_ov (module I1) x y |> fst) xa ya + , BatOption.map fst intv + , opt_map2 (fun ?no_ov x y -> r.f2_ovc ?no_ov (module I3) x y |> fst) xc yc + , opt_map2 (fun ?no_ov x y -> r.f2_ovc ?no_ov (module I4) x y |> fst) ~no_ov:no_ov xd yd + , BatOption.map fst intv_set ) + + let map ik r (a, b, c, d, e) = + refine ik + BatOption. + ( map (r.f1 (module I1)) a + , map (r.f1 (module I2)) b + , map (r.f1 (module I3)) c + , map (r.f1 (module I4)) d + , map (r.f1 (module I5)) e) + + let map2 ?(norefine=false) ik r (xa, xb, xc, xd, xe) (ya, yb, yc, yd, ye) = + let r = + ( opt_map2 (r.f2 (module I1)) xa ya + , opt_map2 (r.f2 (module I2)) xb yb + , opt_map2 (r.f2 (module I3)) xc yc + , opt_map2 (r.f2 (module I4)) xd yd + , opt_map2 (r.f2 (module I5)) xe ye) + in + if norefine then r else refine ik r + + + (* f1: unary ops *) + let neg ?no_ov ik = + mapovc ik {f1_ovc = (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.neg ?no_ov ik)} + + let lognot ik = + map ik {f1 = (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.lognot ik)} + + let c_lognot ik = + map ik {f1 = (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.c_lognot ik)} + + let cast_to ?(suppress_ovwarn=false) ?torg ?no_ov t = + mapovc ~suppress_ovwarn ~cast:true t {f1_ovc = (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.cast_to ?torg ?no_ov t)} + + (* fp: projections *) + let equal_to i x = + let xs = mapp2 { fp2 = fun (type a) (module I:SOverflow with type t = a and type int_t = int_t) -> I.equal_to i } x |> Tuple5.enum |> List.of_enum |> List.filter_map identity in + if List.mem `Eq xs then `Eq else + if List.mem `Neq xs then `Neq else + `Top + + let to_bool = same string_of_bool % mapp { fp = fun (type a) (module I:SOverflow with type t = a) -> I.to_bool } + let minimal = flat (List.max ~cmp:Z.compare) % mapp2 { fp2 = fun (type a) (module I:SOverflow with type t = a and type int_t = int_t) -> I.minimal } + let maximal = flat (List.min ~cmp:Z.compare) % mapp2 { fp2 = fun (type a) (module I:SOverflow with type t = a and type int_t = int_t) -> I.maximal } + (* others *) + let show x = + match to_int x with + | Some v when not (GobConfig.get_bool "dbg.full-output") -> Z.to_string v + | _ -> mapp { fp = fun (type a) (module I:SOverflow with type t = a) x -> I.name () ^ ":" ^ (I.show x) } x + |> to_list + |> String.concat "; " + let to_yojson = [%to_yojson: Yojson.Safe.t list] % to_list % mapp { fp = fun (type a) (module I:SOverflow with type t = a) x -> I.to_yojson x } + + (* `map/opt_map` are used by `project` *) + let opt_map b f = + curry @@ function None, true -> f | x, y when y || b -> x | _ -> None + let map ~keep r (i1, i2, i3, i4, i5) (b1, b2, b3, b4, b5) = + ( opt_map keep (r.f3 (module I1)) i1 b1 + , opt_map keep (r.f3 (module I2)) i2 b2 + , opt_map keep (r.f3 (module I3)) i3 b3 + , opt_map keep (r.f3 (module I4)) i4 b4 + , opt_map keep (r.f3 (module I5)) i5 b5 ) + + (** Project tuple t to precision p + * We have to deactivate IntDomains after the refinement, since we might + * lose information if we do it before. E.g. only "Interval" is active + * and shall be projected to only "Def_Exc". By seting "Interval" to None + * before refinement we have no information for "Def_Exc". + * + * Thus we have 3 Steps: + * 1. Add padding to t by setting `None` to `I.top_of ik` if p is true for this element + * 2. Refine the padded t + * 3. Set elements of t to `None` if p is false for this element + * + * Side Note: + * ~keep is used to reuse `map/opt_map` for Step 1 and 3. + * ~keep:true will keep elements that are `Some x` but should be set to `None` by p. + * This way we won't loose any information for the refinement. + * ~keep:false will set the elements to `None` as defined by p *) + let project ik (p: int_precision) t = + let t_padded = map ~keep:true { f3 = fun (type a) (module I:SOverflow with type t = a) -> Some (I.top_of ik) } t p in + let t_refined = refine ik t_padded in + map ~keep:false { f3 = fun (type a) (module I:SOverflow with type t = a) -> None } t_refined p + + + (* f2: binary ops *) + let join ik = + map2 ~norefine:true ik {f2= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.join ik)} + + let meet ik = + map2 ik {f2= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.meet ik)} + + let widen ik = + map2 ~norefine:true ik {f2= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.widen ik)} + + let narrow ik = + map2 ik {f2= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.narrow ik)} + + let add ?no_ov ik = + map2ovc ik + {f2_ovc = (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.add ?no_ov ik)} + + let sub ?no_ov ik = + map2ovc ik + {f2_ovc = (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.sub ?no_ov ik)} + + let mul ?no_ov ik = + map2ovc ik + {f2_ovc = (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.mul ?no_ov ik)} + + let div ?no_ov ik = + map2ovc ik + {f2_ovc = (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.div ?no_ov ik)} + + let rem ik = + map2 ik {f2= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.rem ik)} + + let lt ik = + map2 ik {f2= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.lt ik)} + + let gt ik = + map2 ik {f2= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.gt ik)} + + let le ik = + map2 ik {f2= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.le ik)} + + let ge ik = + map2 ik {f2= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.ge ik)} + + let eq ik = + map2 ik {f2= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.eq ik)} + + let ne ik = + map2 ik {f2= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.ne ik)} + + let logand ik = + map2 ik {f2= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.logand ik)} + + let logor ik = + map2 ik {f2= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.logor ik)} + + let logxor ik = + map2 ik {f2= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.logxor ik)} + + let shift_left ik = + map2ovc ik {f2_ovc= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.shift_left ik)} + + let shift_right ik = + map2ovc ik {f2_ovc= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.shift_right ik)} + + let c_logand ik = + map2 ik {f2= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.c_logand ik)} + + let c_logor ik = + map2 ik {f2= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.c_logor ik)} + + + (* printing boilerplate *) + let pretty_diff () (x,y) = dprintf "%a instead of %a" pretty x pretty y + let printXml f x = + match to_int x with + | Some v when not (GobConfig.get_bool "dbg.full-output") -> BatPrintf.fprintf f "\n\n%s\n\n\n" (Z.to_string v) + | _ -> BatPrintf.fprintf f "\n\n%s\n\n\n" (show x) + + let invariant_ikind e ik ((_, _, _, x_cong, x_intset) as x) = + (* TODO: do refinement before to ensure incl_list being more precise than intervals, etc (https://github.com/goblint/analyzer/pull/1517#discussion_r1693998515), requires refine functions to actually refine that *) + let simplify_int fallback = + match to_int x with + | Some v -> + (* If definite, output single equality instead of every subdomain repeating same equality (or something less precise). *) + IntInvariant.of_int e ik v + | None -> + fallback () + in + let simplify_all () = + match to_incl_list x with + | Some ps -> + (* If inclusion set, output disjunction of equalities because it subsumes interval(s), exclusion set and congruence. *) + IntInvariant.of_incl_list e ik ps + | None -> + (* Get interval bounds from all domains (intervals and exclusion set ranges). *) + let min = minimal x in + let max = maximal x in + let ns = Option.map fst (to_excl_list x) |? [] in (* Ignore exclusion set bit range, known via interval bounds already. *) + (* "Refine" out-of-bounds exclusions for simpler output. *) + let ns = Option.map_default (fun min -> List.filter (Z.leq min) ns) ns min in + let ns = Option.map_default (fun max -> List.filter (Z.geq max) ns) ns max in + Invariant.( + IntInvariant.of_interval_opt e ik (min, max) && (* Output best interval bounds once instead of multiple subdomains repeating them (or less precise ones). *) + IntInvariant.of_excl_list e ik ns && + Option.map_default (I4.invariant_ikind e ik) Invariant.none x_cong && (* Output congruence as is. *) + Option.map_default (I5.invariant_ikind e ik) Invariant.none x_intset (* Output interval sets as is. *) + ) + in + let simplify_none () = + let is = to_list (mapp { fp = fun (type a) (module I:SOverflow with type t = a) -> I.invariant_ikind e ik } x) in + List.fold_left (fun a i -> + Invariant.(a && i) + ) (Invariant.top ()) is + in + match GobConfig.get_string "ana.base.invariant.int.simplify" with + | "none" -> simplify_none () + | "int" -> simplify_int simplify_none + | "all" -> simplify_int simplify_all + | _ -> assert false + + let arbitrary ik = QCheck.(set_print show @@ tup5 (option (I1.arbitrary ik)) (option (I2.arbitrary ik)) (option (I3.arbitrary ik)) (option (I4.arbitrary ik)) (option (I5.arbitrary ik))) + + let relift (a, b, c, d, e) = + (Option.map I1.relift a, Option.map I2.relift b, Option.map I3.relift c, Option.map I4.relift d, Option.map I5.relift e) +end + +module IntDomTuple = +struct + module I = IntDomLifter (IntDomTupleImpl) + include I + + let top () = failwith "top in IntDomTuple not supported. Use top_of instead." + let no_interval (x: I.t) = {x with v = IntDomTupleImpl.no_interval x.v} + + let no_intervalSet (x: I.t) = {x with v = IntDomTupleImpl.no_intervalSet x.v} +end + +let of_const (i, ik, str) = IntDomTuple.of_int ik i diff --git a/src/cdomain/value/cdomains/int/intervalDomain.ml b/src/cdomain/value/cdomains/int/intervalDomain.ml new file mode 100644 index 0000000000..7d7329e76a --- /dev/null +++ b/src/cdomain/value/cdomains/int/intervalDomain.ml @@ -0,0 +1,417 @@ +open IntDomain0 + + +module IntervalFunctor (Ints_t : IntOps.IntOps): SOverflow with type int_t = Ints_t.t and type t = (Ints_t.t * Ints_t.t) option = +struct + let name () = "intervals" + type int_t = Ints_t.t + type t = (Ints_t.t * Ints_t.t) option [@@deriving eq, ord, hash] + module IArith = IntervalArith (Ints_t) + + let range ik = BatTuple.Tuple2.mapn Ints_t.of_bigint (Size.range ik) + + let top () = failwith @@ "top () not implemented for " ^ (name ()) + let top_of ik = Some (range ik) + let bot () = None + let bot_of ik = bot () (* TODO: improve *) + + let show = function None -> "bottom" | Some (x,y) -> "["^Ints_t.to_string x^","^Ints_t.to_string y^"]" + + include Std (struct type nonrec t = t let name = name let top_of = top_of let bot_of = bot_of let show = show let equal = equal end) + + let equal_to i = function + | None -> failwith "unsupported: equal_to with bottom" + | Some (a, b) -> + if a = b && b = i then `Eq else if Ints_t.compare a i <= 0 && Ints_t.compare i b <=0 then `Top else `Neq + + let norm ?(suppress_ovwarn=false) ?(cast=false) ik : (t -> t * overflow_info) = function None -> (None, {underflow=false; overflow=false}) | Some (x,y) -> + if Ints_t.compare x y > 0 then + (None,{underflow=false; overflow=false}) + else ( + let (min_ik, max_ik) = range ik in + let underflow = Ints_t.compare min_ik x > 0 in + let overflow = Ints_t.compare max_ik y < 0 in + let ov_info = { underflow = underflow && not suppress_ovwarn; overflow = overflow && not suppress_ovwarn } in + let v = + if underflow || overflow then + if should_wrap ik then (* could add [|| cast], but that's GCC implementation-defined behavior: https://gcc.gnu.org/onlinedocs/gcc/Integers-implementation.html#Integers-implementation *) + (* We can only soundly wrap if at most one overflow occurred, otherwise the minimal and maximal values of the interval *) + (* on Z will not safely contain the minimal and maximal elements after the cast *) + let diff = Ints_t.abs (Ints_t.sub max_ik min_ik) in + let resdiff = Ints_t.abs (Ints_t.sub y x) in + if Ints_t.compare resdiff diff > 0 then + top_of ik + else + let l = Ints_t.of_bigint @@ Size.cast ik (Ints_t.to_bigint x) in + let u = Ints_t.of_bigint @@ Size.cast ik (Ints_t.to_bigint y) in + if Ints_t.compare l u <= 0 then + Some (l, u) + else + (* Interval that wraps around (begins to the right of its end). We can not represent such intervals *) + top_of ik + else if not cast && should_ignore_overflow ik then + let tl, tu = BatOption.get @@ top_of ik in + Some (Ints_t.max tl x, Ints_t.min tu y) + else + top_of ik + else + Some (x,y) + in + (v, ov_info) + ) + + let leq (x:t) (y:t) = + match x, y with + | None, _ -> true + | Some _, None -> false + | Some (x1,x2), Some (y1,y2) -> Ints_t.compare x1 y1 >= 0 && Ints_t.compare x2 y2 <= 0 + + let join ik (x:t) y = + match x, y with + | None, z | z, None -> z + | Some (x1,x2), Some (y1,y2) -> norm ik @@ Some (Ints_t.min x1 y1, Ints_t.max x2 y2) |> fst + + let meet ik (x:t) y = + match x, y with + | None, z | z, None -> None + | Some (x1,x2), Some (y1,y2) -> norm ik @@ Some (Ints_t.max x1 y1, Ints_t.min x2 y2) |> fst + + (* TODO: change to_int signature so it returns a big_int *) + let to_int x = Option.bind x (IArith.to_int) + let of_interval ?(suppress_ovwarn=false) ik (x,y) = norm ~suppress_ovwarn ik @@ Some (x,y) + let of_int ik (x: int_t) = of_interval ik (x,x) + let zero = Some IArith.zero + let one = Some IArith.one + let top_bool = Some IArith.top_bool + + let of_bool _ik = function true -> one | false -> zero + let to_bool (a: t) = match a with + | None -> None + | Some (l, u) when Ints_t.compare l Ints_t.zero = 0 && Ints_t.compare u Ints_t.zero = 0 -> Some false + | x -> if leq zero x then None else Some true + + let starting ?(suppress_ovwarn=false) ik n = + norm ~suppress_ovwarn ik @@ Some (n, snd (range ik)) + + let ending ?(suppress_ovwarn=false) ik n = + norm ~suppress_ovwarn ik @@ Some (fst (range ik), n) + + (* TODO: change signature of maximal, minimal to return big_int*) + let maximal = function None -> None | Some (x,y) -> Some y + let minimal = function None -> None | Some (x,y) -> Some x + + let cast_to ?(suppress_ovwarn=false) ?torg ?no_ov t = norm ~cast:true t (* norm does all overflow handling *) + + let widen ik x y = + match x, y with + | None, z | z, None -> z + | Some (l0,u0), Some (l1,u1) -> + let (min_ik, max_ik) = range ik in + let threshold = get_interval_threshold_widening () in + let l2 = + if Ints_t.compare l0 l1 = 0 then l0 + else if threshold then IArith.lower_threshold l1 min_ik + else min_ik + in + let u2 = + if Ints_t.compare u0 u1 = 0 then u0 + else if threshold then IArith.upper_threshold u1 max_ik + else max_ik + in + norm ik @@ Some (l2,u2) |> fst + let widen ik x y = + let r = widen ik x y in + if M.tracing && not (equal x y) then M.tracel "int" "interval widen %a %a -> %a" pretty x pretty y pretty r; + assert (leq x y); (* TODO: remove for performance reasons? *) + r + + let narrow ik x y = + match x, y with + | _,None | None, _ -> None + | Some (x1,x2), Some (y1,y2) -> + let threshold = get_interval_threshold_widening () in + let (min_ik, max_ik) = range ik in + let lr = if Ints_t.compare min_ik x1 = 0 || threshold && Ints_t.compare y1 x1 > 0 && IArith.is_lower_threshold x1 then y1 else x1 in + let ur = if Ints_t.compare max_ik x2 = 0 || threshold && Ints_t.compare y2 x2 < 0 && IArith.is_upper_threshold x2 then y2 else x2 in + norm ik @@ Some (lr,ur) |> fst + + + let narrow ik x y = + if get_interval_narrow_by_meet () then + meet ik x y + else + narrow ik x y + + let log f ~annihilator ik i1 i2 = + match is_bot i1, is_bot i2 with + | true, true -> bot_of ik + | true, _ + | _ , true -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show i1) (show i2))) + | _ -> + match to_bool i1, to_bool i2 with + | Some x, _ when x = annihilator -> of_bool ik annihilator + | _, Some y when y = annihilator -> of_bool ik annihilator + | Some x, Some y -> of_bool ik (f x y) + | _ -> top_of ik + + let c_logor = log (||) ~annihilator:true + let c_logand = log (&&) ~annihilator:false + + let log1 f ik i1 = + if is_bot i1 then + bot_of ik + else + match to_bool i1 with + | Some x -> of_bool ik (f ik x) + | _ -> top_of ik + + let c_lognot = log1 (fun _ik -> not) + + let bit f ik i1 i2 = + match is_bot i1, is_bot i2 with + | true, true -> bot_of ik + | true, _ + | _ , true -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show i1) (show i2))) + | _ -> + match to_int i1, to_int i2 with + | Some x, Some y -> (try of_int ik (f ik x y) |> fst with Division_by_zero -> top_of ik) + | _ -> top_of ik + + let bitcomp f ik i1 i2 = + match is_bot i1, is_bot i2 with + | true, true -> (bot_of ik,{underflow=false; overflow=false}) + | true, _ + | _ , true -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show i1) (show i2))) + | _ -> + match to_int i1, to_int i2 with + | Some x, Some y -> (try of_int ik (f ik x y) with Division_by_zero | Invalid_argument _ -> (top_of ik,{underflow=false; overflow=false})) + | _ -> (top_of ik,{underflow=true; overflow=true}) + + let logxor = bit (fun _ik -> Ints_t.logxor) + + let logand ik i1 i2 = + match is_bot i1, is_bot i2 with + | true, true -> bot_of ik + | true, _ + | _ , true -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show i1) (show i2))) + | _ -> + match to_int i1, to_int i2 with + | Some x, Some y -> (try of_int ik (Ints_t.logand x y) |> fst with Division_by_zero -> top_of ik) + | _, Some y when Ints_t.equal y Ints_t.zero -> of_int ik Ints_t.zero |> fst + | _, Some y when Ints_t.equal y Ints_t.one -> of_interval ik (Ints_t.zero, Ints_t.one) |> fst + | _ -> top_of ik + + let logor = bit (fun _ik -> Ints_t.logor) + + let bit1 f ik i1 = + if is_bot i1 then + bot_of ik + else + match to_int i1 with + | Some x -> of_int ik (f ik x) |> fst + | _ -> top_of ik + + let lognot = bit1 (fun _ik -> Ints_t.lognot) + let shift_right = bitcomp (fun _ik x y -> Ints_t.shift_right x (Ints_t.to_int y)) + + let neg ?no_ov ik = function None -> (None,{underflow=false; overflow=false}) | Some x -> norm ik @@ Some (IArith.neg x) + + let binary_op_with_norm ?no_ov op ik x y = match x, y with + | None, None -> (None, {overflow=false; underflow= false}) + | None, _ | _, None -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) + | Some x, Some y -> norm ik @@ Some (op x y) + + let add ?no_ov = binary_op_with_norm IArith.add + let mul ?no_ov = binary_op_with_norm IArith.mul + let sub ?no_ov = binary_op_with_norm IArith.sub + + let shift_left ik a b = + match is_bot a, is_bot b with + | true, true -> (bot_of ik,{underflow=false; overflow=false}) + | true, _ + | _ , true -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show a) (show b))) + | _ -> + match a, minimal b, maximal b with + | Some a, Some bl, Some bu when (Ints_t.compare bl Ints_t.zero >= 0) -> + (try + let r = IArith.shift_left a (Ints_t.to_int bl, Ints_t.to_int bu) in + norm ik @@ Some r + with Z.Overflow -> (top_of ik,{underflow=false; overflow=true})) + | _ -> (top_of ik,{underflow=true; overflow=true}) + + let rem ik x y = match x, y with + | None, None -> None + | None, _ | _, None -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) + | Some (xl, xu), Some (yl, yu) -> + if is_top_of ik x && is_top_of ik y then + (* This is needed to preserve soundness also on things bigger than int32 e.g. *) + (* x: 3803957176L -> T in Interval32 *) + (* y: 4209861404L -> T in Interval32 *) + (* x % y: 3803957176L -> T in Interval32 *) + (* T in Interval32 is [-2147483648,2147483647] *) + (* the code below computes [-2147483647,2147483647] for this though which is unsound *) + top_of ik + else + (* If we have definite values, Ints_t.rem will give a definite result. + * Otherwise we meet with a [range] the result can be in. + * This range is [0, min xu b] if x is positive, and [max xl -b, min xu b] if x can be negative. + * The precise bound b is one smaller than the maximum bound. Negative y give the same result as positive. *) + let pos x = if Ints_t.compare x Ints_t.zero < 0 then Ints_t.neg x else x in + let b = Ints_t.sub (Ints_t.max (pos yl) (pos yu)) Ints_t.one in + let range = if Ints_t.compare xl Ints_t.zero>= 0 then Some (Ints_t.zero, Ints_t.min xu b) else Some (Ints_t.max xl (Ints_t.neg b), Ints_t.min (Ints_t.max (pos xl) (pos xu)) b) in + meet ik (bit (fun _ik -> Ints_t.rem) ik x y) range + + let rec div ?no_ov ik x y = + match x, y with + | None, None -> (bot (),{underflow=false; overflow=false}) + | None, _ | _, None -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) + | (Some (x1,x2) as x), (Some (y1,y2) as y) -> + begin + let is_zero v = Ints_t.compare v Ints_t.zero = 0 in + match y1, y2 with + | l, u when is_zero l && is_zero u -> (top_of ik,{underflow=false; overflow=false}) (* TODO warn about undefined behavior *) + | l, _ when is_zero l -> div ik (Some (x1,x2)) (Some (Ints_t.one,y2)) + | _, u when is_zero u -> div ik (Some (x1,x2)) (Some (y1, Ints_t.(neg one))) + | _ when leq (of_int ik (Ints_t.zero) |> fst) (Some (y1,y2)) -> (top_of ik,{underflow=false; overflow=false}) + | _ -> binary_op_with_norm IArith.div ik x y + end + + let ne ik x y = + match x, y with + | None, None -> bot_of ik + | None, _ | _, None -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) + | Some (x1,x2), Some (y1,y2) -> + if Ints_t.compare y2 x1 < 0 || Ints_t.compare x2 y1 < 0 then + of_bool ik true + else if Ints_t.compare x2 y1 <= 0 && Ints_t.compare y2 x1 <= 0 then + of_bool ik false + else top_bool + + let eq ik x y = + match x, y with + | None, None -> bot_of ik + | None, _ | _, None -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) + | Some (x1,x2), Some (y1,y2) -> + if Ints_t.compare y2 x1 <= 0 && Ints_t.compare x2 y1 <= 0 then + of_bool ik true + else if Ints_t.compare y2 x1 < 0 || Ints_t.compare x2 y1 < 0 then + of_bool ik false + else top_bool + + let ge ik x y = + match x, y with + | None, None -> bot_of ik + | None, _ | _, None -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) + | Some (x1,x2), Some (y1,y2) -> + if Ints_t.compare y2 x1 <= 0 then of_bool ik true + else if Ints_t.compare x2 y1 < 0 then of_bool ik false + else top_bool + + let le ik x y = + match x, y with + | None, None -> bot_of ik + | None, _ | _, None -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) + | Some (x1,x2), Some (y1,y2) -> + if Ints_t.compare x2 y1 <= 0 then of_bool ik true + else if Ints_t.compare y2 x1 < 0 then of_bool ik false + else top_bool + + let gt ik x y = + match x, y with + | None, None -> bot_of ik + | None, _ | _, None -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) + | Some (x1,x2), Some (y1,y2) -> + if Ints_t.compare y2 x1 < 0 then of_bool ik true + else if Ints_t.compare x2 y1 <= 0 then of_bool ik false + else top_bool + + let lt ik x y = + match x, y with + | None, None -> bot_of ik + | None, _ | _, None -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) + | Some (x1,x2), Some (y1,y2) -> + if Ints_t.compare x2 y1 < 0 then of_bool ik true + else if Ints_t.compare y2 x1 <= 0 then of_bool ik false + else top_bool + + let invariant_ikind e ik = function + | Some (x1, x2) -> + let (x1', x2') = BatTuple.Tuple2.mapn Ints_t.to_bigint (x1, x2) in + IntInvariant.of_interval e ik (x1', x2') + | None -> Invariant.none + + let arbitrary ik = + let open QCheck.Iter in + (* let int_arb = QCheck.map ~rev:Ints_t.to_bigint Ints_t.of_bigint GobQCheck.Arbitrary.big_int in *) + (* TODO: apparently bigints are really slow compared to int64 for domaintest *) + let int_arb = QCheck.map ~rev:Ints_t.to_int64 Ints_t.of_int64 GobQCheck.Arbitrary.int64 in + let pair_arb = QCheck.pair int_arb int_arb in + let shrink = function + | Some (l, u) -> (return None) <+> (GobQCheck.shrink pair_arb (l, u) >|= of_interval ik >|= fst) + | None -> empty + in + QCheck.(set_shrink shrink @@ set_print show @@ map (*~rev:BatOption.get*) (fun x -> of_interval ik x |> fst ) pair_arb) + + let modulo n k = + let result = Ints_t.rem n k in + if Ints_t.compare result Ints_t.zero >= 0 then result + else Ints_t.add result k + + let refine_with_congruence ik (intv : t) (cong : (int_t * int_t ) option) : t = + match intv, cong with + | Some (x, y), Some (c, m) -> + if Ints_t.equal m Ints_t.zero && (Ints_t.compare c x < 0 || Ints_t.compare c y > 0) then None + else if Ints_t.equal m Ints_t.zero then + Some (c, c) + else + let (min_ik, max_ik) = range ik in + let rcx = + if Ints_t.equal x min_ik then x else + Ints_t.add x (modulo (Ints_t.sub c x) (Ints_t.abs m)) in + let lcy = + if Ints_t.equal y max_ik then y else + Ints_t.sub y (modulo (Ints_t.sub y c) (Ints_t.abs m)) in + if Ints_t.compare rcx lcy > 0 then None + else if Ints_t.equal rcx lcy then norm ik @@ Some (rcx, rcx) |> fst + else norm ik @@ Some (rcx, lcy) |> fst + | _ -> None + + let refine_with_congruence ik x y = + let refn = refine_with_congruence ik x y in + if M.tracing then M.trace "refine" "int_refine_with_congruence %a %a -> %a" pretty x pretty y pretty refn; + refn + + let refine_with_interval ik a b = meet ik a b + + let refine_with_excl_list ik (intv : t) (excl : (int_t list * (int64 * int64)) option) : t = + match intv, excl with + | None, _ | _, None -> intv + | Some(l, u), Some(ls, (rl, rh)) -> + let rec shrink op b = + let new_b = (op b (Ints_t.of_int(Bool.to_int(BatList.mem_cmp Ints_t.compare b ls)))) in + if not (Ints_t.equal b new_b) then shrink op new_b else new_b + in + let (min_ik, max_ik) = range ik in + let l' = if Ints_t.equal l min_ik then l else shrink Ints_t.add l in + let u' = if Ints_t.equal u max_ik then u else shrink Ints_t.sub u in + let intv' = norm ik @@ Some (l', u') |> fst in + let range = norm ~suppress_ovwarn:true ik (Some (Ints_t.of_bigint (Size.min_from_bit_range rl), Ints_t.of_bigint (Size.max_from_bit_range rh))) |> fst in + meet ik intv' range + + let refine_with_incl_list ik (intv: t) (incl : (int_t list) option) : t = + match intv, incl with + | None, _ | _, None -> intv + | Some(l, u), Some(ls) -> + let rec min m1 ms = match ms with | [] -> m1 | x::xs -> match m1 with + | None -> min (Some x) xs | Some m -> if Ints_t.compare m x < 0 then min (Some m) xs else min (Some x) xs in + let rec max m1 ms = match ms with | [] -> m1 | x::xs -> match m1 with + | None -> max (Some x) xs | Some m -> if Ints_t.compare m x > 0 then max (Some m) xs else max (Some x) xs in + match min None ls, max None ls with + | Some m1, Some m2 -> refine_with_interval ik (Some(l, u)) (Some (m1, m2)) + | _, _-> intv + + let project ik p t = t +end + +module Interval = IntervalFunctor (IntOps.BigIntOps) +module Interval32 = IntDomWithDefaultIkind (IntDomLifter (SOverflowUnlifter (IntervalFunctor (IntOps.Int64Ops)))) (IntIkind) diff --git a/src/cdomain/value/cdomains/int/intervalSetDomain.ml b/src/cdomain/value/cdomains/int/intervalSetDomain.ml new file mode 100644 index 0000000000..3e2bc847ee --- /dev/null +++ b/src/cdomain/value/cdomains/int/intervalSetDomain.ml @@ -0,0 +1,541 @@ +open IntDomain0 +open IntervalDomain +open GoblintCil + + +(** IntervalSetFunctor that is not just disjunctive completion of intervals, but attempts to be precise for wraparound arithmetic for unsigned types *) +module IntervalSetFunctor (Ints_t : IntOps.IntOps): SOverflow with type int_t = Ints_t.t and type t = (Ints_t.t * Ints_t.t) list = +struct + + module Interval = IntervalFunctor (Ints_t) + module IArith = IntervalArith (Ints_t) + + + let name () = "interval_sets" + + type int_t = Ints_t.t + + let (>.) a b = Ints_t.compare a b > 0 + let (=.) a b = Ints_t.compare a b = 0 + let (<.) a b = Ints_t.compare a b < 0 + let (>=.) a b = Ints_t.compare a b >= 0 + let (<=.) a b = Ints_t.compare a b <= 0 + let (+.) a b = Ints_t.add a b + let (-.) a b = Ints_t.sub a b + + (* + Each domain's element is guaranteed to be in canonical form. That is, each interval contained + inside the set does not overlap with each other and they are not adjacent. + *) + type t = (Ints_t.t * Ints_t.t) list [@@deriving eq, hash, ord] + + let range ik = BatTuple.Tuple2.mapn Ints_t.of_bigint (Size.range ik) + + let top () = failwith @@ "top () not implemented for " ^ (name ()) + + let top_of ik = [range ik] + + let bot () = [] + + let bot_of ik = bot () + + let show (x: t) = + let show_interval i = Printf.sprintf "[%s, %s]" (Ints_t.to_string (fst i)) (Ints_t.to_string (snd i)) in + List.fold_left (fun acc i -> (show_interval i) :: acc) [] x |> List.rev |> String.concat ", " |> Printf.sprintf "[%s]" + + (* New type definition for the sweeping line algorithm used for implementing join/meet functions. *) + type event = Enter of Ints_t.t | Exit of Ints_t.t + + let unbox_event = function Enter x -> x | Exit x -> x + + let cmp_events x y = + (* Deliberately comparing ints first => Cannot be derived *) + let res = Ints_t.compare (unbox_event x) (unbox_event y) in + if res <> 0 then res + else + begin + match (x, y) with + | (Enter _, Exit _) -> -1 + | (Exit _, Enter _) -> 1 + | (_, _) -> 0 + end + + let interval_set_to_events (xs: t) = + List.concat_map (fun (a, b) -> [Enter a; Exit b]) xs + + let two_interval_sets_to_events (xs: t) (ys: t) = + let xs = interval_set_to_events xs in + let ys = interval_set_to_events ys in + List.merge cmp_events xs ys + + (* Using the sweeping line algorithm, combined_event_list returns a new event list representing the intervals in which at least n intervals in xs overlap + This function is used for both join and meet operations with different parameter n: 1 for join, 2 for meet *) + let combined_event_list lattice_op (xs:event list) = + let l = match lattice_op with `Join -> 1 | `Meet -> 2 in + let aux (interval_count, acc) = function + | Enter x -> (interval_count + 1, if (interval_count + 1) >= l && interval_count < l then (Enter x)::acc else acc) + | Exit x -> (interval_count - 1, if interval_count >= l && (interval_count - 1) < l then (Exit x)::acc else acc) + in + List.fold_left aux (0, []) xs |> snd |> List.rev + + let rec events_to_intervals = function + | [] -> [] + | (Enter x)::(Exit y)::xs -> (x, y)::(events_to_intervals xs) + | _ -> failwith "Invalid events list" + + let remove_empty_gaps (xs: t) = + let aux acc (l, r) = match acc with + | ((a, b)::acc') when (b +. Ints_t.one) >=. l -> (a, r)::acc' + | _ -> (l, r)::acc + in + List.fold_left aux [] xs |> List.rev + + let canonize (xs: t) = + interval_set_to_events xs |> + List.sort cmp_events |> + combined_event_list `Join |> + events_to_intervals |> + remove_empty_gaps + + let unop (x: t) op = match x with + | [] -> [] + | _ -> canonize @@ List.concat_map op x + + let binop (x: t) (y: t) op : t = match x, y with + | [], _ -> [] + | _, [] -> [] + | _, _ -> canonize @@ List.concat_map op (BatList.cartesian_product x y) + + + include Std (struct type nonrec t = t let name = name let top_of = top_of let bot_of = bot_of let show = show let equal = equal end) + + let minimal = function + | [] -> None + | (x, _)::_ -> Some x + + let maximal = function + | [] -> None + | xs -> Some (BatList.last xs |> snd) + + let equal_to_interval i (a, b) = + if a =. b && b =. i then + `Eq + else if a <=. i && i <=. b then + `Top + else + `Neq + + let equal_to i xs = match List.map (equal_to_interval i) xs with + | [] -> failwith "unsupported: equal_to with bottom" + | [`Eq] -> `Eq + | ys when List.for_all ((=) `Neq) ys -> `Neq + | _ -> `Top + + let norm_interval ?(suppress_ovwarn=false) ?(cast=false) ik (x,y) : t*overflow_info = + if x >. y then + ([],{underflow=false; overflow=false}) + else + let (min_ik, max_ik) = range ik in + let underflow = min_ik >. x in + let overflow = max_ik <. y in + let v = if underflow || overflow then + begin + if should_wrap ik then (* could add [|| cast], but that's GCC implementation-defined behavior: https://gcc.gnu.org/onlinedocs/gcc/Integers-implementation.html#Integers-implementation *) + (* We can only soundly wrap if at most one overflow occurred, otherwise the minimal and maximal values of the interval *) + (* on Z will not safely contain the minimal and maximal elements after the cast *) + let diff = Ints_t.abs (max_ik -. min_ik) in + let resdiff = Ints_t.abs (y -. x) in + if resdiff >. diff then + [range ik] + else + let l = Ints_t.of_bigint @@ Size.cast ik (Ints_t.to_bigint x) in + let u = Ints_t.of_bigint @@ Size.cast ik (Ints_t.to_bigint y) in + if l <=. u then + [(l, u)] + else + (* Interval that wraps around (begins to the right of its end). We CAN represent such intervals *) + [(min_ik, u); (l, max_ik)] + else if not cast && should_ignore_overflow ik then + [Ints_t.max min_ik x, Ints_t.min max_ik y] + else + [range ik] + end + else + [(x,y)] + in + if suppress_ovwarn then (v, {underflow=false; overflow=false}) else (v, {underflow; overflow}) + + let norm_intvs ?(suppress_ovwarn=false) ?(cast=false) (ik:ikind) (xs: t) : t*overflow_info = + let res = List.map (norm_interval ~suppress_ovwarn ~cast ik) xs in + let intvs = List.concat_map fst res in + let underflow = List.exists (fun (_,{underflow; _}) -> underflow) res in + let overflow = List.exists (fun (_,{overflow; _}) -> underflow) res in + (canonize intvs,{underflow; overflow}) + + let binary_op_with_norm op (ik:ikind) (x: t) (y: t) : t*overflow_info = match x, y with + | [], _ -> ([],{overflow=false; underflow=false}) + | _, [] -> ([],{overflow=false; underflow=false}) + | _, _ -> norm_intvs ik @@ List.concat_map (fun (x,y) -> [op x y]) (BatList.cartesian_product x y) + + let binary_op_with_ovc (x: t) (y: t) op : t*overflow_info = match x, y with + | [], _ -> ([],{overflow=false; underflow=false}) + | _, [] -> ([],{overflow=false; underflow=false}) + | _, _ -> + let res = List.map op (BatList.cartesian_product x y) in + let intvs = List.concat_map fst res in + let underflow = List.exists (fun (_,{underflow; _}) -> underflow) res in + let overflow = List.exists (fun (_,{overflow; _}) -> underflow) res in + (canonize intvs,{underflow; overflow}) + + let unary_op_with_norm op (ik:ikind) (x: t) = match x with + | [] -> ([],{overflow=false; underflow=false}) + | _ -> norm_intvs ik @@ List.concat_map (fun x -> [op x]) x + + let rec leq (xs: t) (ys: t) = + let leq_interval (al, au) (bl, bu) = al >=. bl && au <=. bu in + match xs, ys with + | [], _ -> true + | _, [] -> false + | (xl,xr)::xs', (yl,yr)::ys' -> + if leq_interval (xl,xr) (yl,yr) then + leq xs' ys + else if xr <. yl then + false + else + leq xs ys' + + let join ik (x: t) (y: t): t = + two_interval_sets_to_events x y |> + combined_event_list `Join |> + events_to_intervals |> + remove_empty_gaps + + let meet ik (x: t) (y: t): t = + two_interval_sets_to_events x y |> + combined_event_list `Meet |> + events_to_intervals + + let to_int = function + | [x] -> IArith.to_int x + | _ -> None + + let zero = [IArith.zero] + let one = [IArith.one] + let top_bool = [IArith.top_bool] + + let not_bool (x:t) = + let is_false x = equal x zero in + let is_true x = equal x one in + if is_true x then zero else if is_false x then one else top_bool + + let to_bool = function + | [(l,u)] when l =. Ints_t.zero && u =. Ints_t.zero -> Some false + | x -> if leq zero x then None else Some true + + let of_bool _ = function true -> one | false -> zero + + let of_interval ?(suppress_ovwarn=false) ik (x,y) = norm_interval ~suppress_ovwarn ~cast:false ik (x,y) + + let of_int ik (x: int_t) = of_interval ik (x, x) + + let lt ik x y = + match x, y with + | [], [] -> bot_of ik + | [], _ | _, [] -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) + | _, _ -> + let (max_x, min_y) = (maximal x |> Option.get, minimal y |> Option.get) in + let (min_x, max_y) = (minimal x |> Option.get, maximal y |> Option.get) in + if max_x <. min_y then + of_bool ik true + else if min_x >=. max_y then + of_bool ik false + else + top_bool + + let le ik x y = + match x, y with + | [], [] -> bot_of ik + | [], _ | _, [] -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) + | _, _ -> + let (max_x, min_y) = (maximal x |> Option.get, minimal y |> Option.get) in + let (min_x, max_y) = (minimal x |> Option.get, maximal y |> Option.get) in + if max_x <=. min_y then + of_bool ik true + else if min_x >. max_y then + of_bool ik false + else + top_bool + + let gt ik x y = not_bool @@ le ik x y + + let ge ik x y = not_bool @@ lt ik x y + + let eq ik x y = match x, y with + | (a, b)::[], (c, d)::[] when a =. b && c =. d && a =. c -> + one + | _ -> + if is_bot (meet ik x y) then + zero + else + top_bool + + let ne ik x y = not_bool @@ eq ik x y + let interval_to_int i = Interval.to_int (Some i) + let interval_to_bool i = Interval.to_bool (Some i) + + let log f ik (i1, i2) = + match (interval_to_bool i1, interval_to_bool i2) with + | Some x, Some y -> of_bool ik (f x y) + | _ -> top_of ik + + + let bit f ik (i1, i2) = + match (interval_to_int i1), (interval_to_int i2) with + | Some x, Some y -> (try of_int ik (f x y) |> fst with Division_by_zero -> top_of ik) + | _ -> top_of ik + + + let bitcomp f ik (i1, i2) = + match (interval_to_int i1, interval_to_int i2) with + | Some x, Some y -> (try of_int ik (f x y) with Division_by_zero | Invalid_argument _ -> (top_of ik,{overflow=false; underflow=false})) + | _, _ -> (top_of ik,{overflow=false; underflow=false}) + + let logand ik x y = + let interval_logand = bit Ints_t.logand ik in + binop x y interval_logand + + let logor ik x y = + let interval_logor = bit Ints_t.logor ik in + binop x y interval_logor + + let logxor ik x y = + let interval_logxor = bit Ints_t.logxor ik in + binop x y interval_logxor + + let lognot ik x = + let interval_lognot i = + match interval_to_int i with + | Some x -> of_int ik (Ints_t.lognot x) |> fst + | _ -> top_of ik + in + unop x interval_lognot + + let shift_left ik x y = + let interval_shiftleft = bitcomp (fun x y -> Ints_t.shift_left x (Ints_t.to_int y)) ik in + binary_op_with_ovc x y interval_shiftleft + + let shift_right ik x y = + let interval_shiftright = bitcomp (fun x y -> Ints_t.shift_right x (Ints_t.to_int y)) ik in + binary_op_with_ovc x y interval_shiftright + + let c_lognot ik x = + let log1 f ik i1 = + match interval_to_bool i1 with + | Some x -> of_bool ik (f x) + | _ -> top_of ik + in + let interval_lognot = log1 not ik in + unop x interval_lognot + + let c_logand ik x y = + let interval_logand = log (&&) ik in + binop x y interval_logand + + let c_logor ik x y = + let interval_logor = log (||) ik in + binop x y interval_logor + + let add ?no_ov = binary_op_with_norm IArith.add + let sub ?no_ov = binary_op_with_norm IArith.sub + let mul ?no_ov = binary_op_with_norm IArith.mul + let neg ?no_ov = unary_op_with_norm IArith.neg + + let div ?no_ov ik x y = + let rec interval_div x (y1, y2) = begin + let top_of ik = top_of ik |> List.hd in + let is_zero v = v =. Ints_t.zero in + match y1, y2 with + | l, u when is_zero l && is_zero u -> top_of ik (* TODO warn about undefined behavior *) + | l, _ when is_zero l -> interval_div x (Ints_t.one,y2) + | _, u when is_zero u -> interval_div x (y1, Ints_t.(neg one)) + | _ when leq (of_int ik (Ints_t.zero) |> fst) ([(y1,y2)]) -> top_of ik + | _ -> IArith.div x (y1, y2) + end + in binary_op_with_norm interval_div ik x y + + let rem ik x y = + let interval_rem (x, y) = + if Interval.is_top_of ik (Some x) && Interval.is_top_of ik (Some y) then + top_of ik + else + let (xl, xu) = x in let (yl, yu) = y in + let pos x = if x <. Ints_t.zero then Ints_t.neg x else x in + let b = (Ints_t.max (pos yl) (pos yu)) -. Ints_t.one in + let range = if xl >=. Ints_t.zero then (Ints_t.zero, Ints_t.min xu b) else (Ints_t.max xl (Ints_t.neg b), Ints_t.min (Ints_t.max (pos xl) (pos xu)) b) in + meet ik (bit Ints_t.rem ik (x, y)) [range] + in + binop x y interval_rem + + let cast_to ?(suppress_ovwarn=false) ?torg ?no_ov ik x = norm_intvs ~cast:true ik x + + (* + narrows down the extremeties of xs if they are equal to boundary values of the ikind with (possibly) narrower values from ys + *) + let narrow ik xs ys = match xs ,ys with + | [], _ -> [] | _ ,[] -> xs + | _, _ -> + let min_xs = minimal xs |> Option.get in + let max_xs = maximal xs |> Option.get in + let min_ys = minimal ys |> Option.get in + let max_ys = maximal ys |> Option.get in + let min_range,max_range = range ik in + let threshold = get_interval_threshold_widening () in + let min = if min_xs =. min_range || threshold && min_ys >. min_xs && IArith.is_lower_threshold min_xs then min_ys else min_xs in + let max = if max_xs =. max_range || threshold && max_ys <. max_xs && IArith.is_upper_threshold max_xs then max_ys else max_xs in + xs + |> (function (_, y)::z -> (min, y)::z | _ -> []) + |> List.rev + |> (function (x, _)::z -> (x, max)::z | _ -> []) + |> List.rev + + (* + 1. partitions the intervals of xs by assigning each of them to the an interval in ys that includes it. + and joins all intervals in xs assigned to the same interval in ys as one interval. + 2. checks for every pair of adjacent pairs whether the pairs did approach (if you compare the intervals from xs and ys) and merges them if it is the case. + 3. checks whether partitions at the extremeties are approaching infinity (and expands them to infinity. in that case) + + The expansion (between a pair of adjacent partitions or at extremeties ) stops at a threshold. + *) + let widen ik xs ys = + let (min_ik,max_ik) = range ik in + let threshold = GobConfig.get_bool "ana.int.interval_threshold_widening" in + let upper_threshold (_,u) = IArith.upper_threshold u max_ik in + let lower_threshold (l,_) = IArith.lower_threshold l min_ik in + (*obtain partitioning of xs intervals according to the ys interval that includes them*) + let rec interval_sets_to_partitions (ik: ikind) (acc : (int_t * int_t) option) (xs: t) (ys: t)= + match xs,ys with + | _, [] -> [] + | [], (y::ys) -> (acc,y):: interval_sets_to_partitions ik None [] ys + | (x::xs), (y::ys) when Interval.leq (Some x) (Some y) -> interval_sets_to_partitions ik (Interval.join ik acc (Some x)) xs (y::ys) + | (x::xs), (y::ys) -> (acc,y) :: interval_sets_to_partitions ik None (x::xs) ys + in + let interval_sets_to_partitions ik xs ys = interval_sets_to_partitions ik None xs ys in + (*merge a pair of adjacent partitions*) + let merge_pair ik (a,b) (c,d) = + let new_a = function + | None -> Some (upper_threshold b, upper_threshold b) + | Some (ax,ay) -> Some (ax, upper_threshold b) + in + let new_c = function + | None -> Some (lower_threshold d, lower_threshold d) + | Some (cx,cy) -> Some (lower_threshold d, cy) + in + if threshold && (lower_threshold d +. Ints_t.one) >. (upper_threshold b) then + [(new_a a,(fst b, upper_threshold b)); (new_c c, (lower_threshold d, snd d))] + else + [(Interval.join ik a c, (Interval.join ik (Some b) (Some d) |> Option.get))] + in + let partitions_are_approaching part_left part_right = match part_left, part_right with + | (Some (_, left_x), (_, left_y)), (Some (right_x, _), (right_y, _)) -> (right_x -. left_x) >. (right_y -. left_y) + | _,_ -> false + in + (*merge all approaching pairs of adjacent partitions*) + let rec merge_list ik = function + | [] -> [] + | x::y::xs when partitions_are_approaching x y -> merge_list ik ((merge_pair ik x y) @ xs) + | x::xs -> x :: merge_list ik xs + in + (*expands left extremity*) + let widen_left = function + | [] -> [] + | (None,(lb,rb))::ts -> let lt = if threshold then lower_threshold (lb,lb) else min_ik in (None, (lt,rb))::ts + | (Some (la,ra), (lb,rb))::ts when lb <. la -> let lt = if threshold then lower_threshold (lb,lb) else min_ik in (Some (la,ra),(lt,rb))::ts + | x -> x + in + (*expands right extremity*) + let widen_right x = + let map_rightmost = function + | [] -> [] + | (None,(lb,rb))::ts -> let ut = if threshold then upper_threshold (rb,rb) else max_ik in (None, (lb,ut))::ts + | (Some (la,ra), (lb,rb))::ts when ra <. rb -> let ut = if threshold then upper_threshold (rb,rb) else max_ik in (Some (la,ra),(lb,ut))::ts + | x -> x + in + List.rev x |> map_rightmost |> List.rev + in + interval_sets_to_partitions ik xs ys |> merge_list ik |> widen_left |> widen_right |> List.map snd + + let starting ?(suppress_ovwarn=false) ik n = norm_interval ik ~suppress_ovwarn (n, snd (range ik)) + + let ending ?(suppress_ovwarn=false) ik n = norm_interval ik ~suppress_ovwarn (fst (range ik), n) + + let invariant_ikind e ik xs = + List.map (fun x -> Interval.invariant_ikind e ik (Some x)) xs |> + let open Invariant in List.fold_left (||) (bot ()) + + let modulo n k = + let result = Ints_t.rem n k in + if result >=. Ints_t.zero then result + else result +. k + + let refine_with_congruence ik (intvs: t) (cong: (int_t * int_t ) option): t = + let refine_with_congruence_interval ik (cong : (int_t * int_t ) option) (intv : (int_t * int_t ) option): t = + match intv, cong with + | Some (x, y), Some (c, m) -> + if m =. Ints_t.zero && (c <. x || c >. y) then [] + else if m =. Ints_t.zero then + [(c, c)] + else + let (min_ik, max_ik) = range ik in + let rcx = + if x =. min_ik then x else + x +. (modulo (c -. x) (Ints_t.abs m)) in + let lcy = + if y =. max_ik then y else + y -. (modulo (y -. c) (Ints_t.abs m)) in + if rcx >. lcy then [] + else if rcx =. lcy then norm_interval ik (rcx, rcx) |> fst + else norm_interval ik (rcx, lcy) |> fst + | _ -> [] + in + List.concat_map (fun x -> refine_with_congruence_interval ik cong (Some x)) intvs + + let refine_with_interval ik xs = function None -> [] | Some (a,b) -> meet ik xs [(a,b)] + + let refine_with_incl_list ik intvs = function + | None -> intvs + | Some xs -> meet ik intvs (List.map (fun x -> (x,x)) xs) + + let excl_range_to_intervalset (ik: ikind) ((min, max): int_t * int_t) (excl: int_t): t = + let intv1 = (min, excl -. Ints_t.one) in + let intv2 = (excl +. Ints_t.one, max) in + norm_intvs ik ~suppress_ovwarn:true [intv1 ; intv2] |> fst + + let of_excl_list ik (excls: int_t list) = + let excl_list = List.map (excl_range_to_intervalset ik (range ik)) excls in + let res = List.fold_left (meet ik) (top_of ik) excl_list in + res + + let refine_with_excl_list ik (intv : t) = function + | None -> intv + | Some (xs, range) -> + let excl_to_intervalset (ik: ikind) ((rl, rh): (int64 * int64)) (excl: int_t): t = + excl_range_to_intervalset ik (Ints_t.of_bigint (Size.min_from_bit_range rl),Ints_t.of_bigint (Size.max_from_bit_range rh)) excl + in + let excl_list = List.map (excl_to_intervalset ik range) xs in + List.fold_left (meet ik) intv excl_list + + let project ik p t = t + + let arbitrary ik = + let open QCheck.Iter in + (* let int_arb = QCheck.map ~rev:Ints_t.to_bigint Ints_t.of_bigint GobQCheck.Arbitrary.big_int in *) + (* TODO: apparently bigints are really slow compared to int64 for domaintest *) + let int_arb = QCheck.map ~rev:Ints_t.to_int64 Ints_t.of_int64 GobQCheck.Arbitrary.int64 in + let pair_arb = QCheck.pair int_arb int_arb in + let list_pair_arb = QCheck.small_list pair_arb in + let canonize_randomly_generated_list = (fun x -> norm_intvs ik x |> fst) in + let shrink xs = GobQCheck.shrink list_pair_arb xs >|= canonize_randomly_generated_list + in QCheck.(set_shrink shrink @@ set_print show @@ map (*~rev:BatOption.get*) canonize_randomly_generated_list list_pair_arb) +end + +module IntervalSet = IntervalSetFunctor (IntOps.BigIntOps) diff --git a/src/cdomain/value/cdomains/intDomain.ml b/src/cdomain/value/cdomains/intDomain.ml index d8fe147d67..c53367d278 100644 --- a/src/cdomain/value/cdomains/intDomain.ml +++ b/src/cdomain/value/cdomains/intDomain.ml @@ -1,3805 +1,8 @@ -open GobConfig -open GoblintCil -open Pretty -open PrecisionUtil - -module M = Messages - -let (%) = Batteries.(%) -let (|?) = Batteries.(|?) - -exception IncompatibleIKinds of string -exception Unknown -exception Error -exception ArithmeticOnIntegerBot of string - - - - -(** Define records that hold mutable variables representing different Configuration values. - * These values are used to keep track of whether or not the corresponding Config values are en-/disabled *) -type ana_int_config_values = { - mutable interval_threshold_widening : bool option; - mutable interval_narrow_by_meet : bool option; - mutable def_exc_widen_by_join : bool option; - mutable interval_threshold_widening_constants : string option; - mutable refinement : string option; -} - -let ana_int_config: ana_int_config_values = { - interval_threshold_widening = None; - interval_narrow_by_meet = None; - def_exc_widen_by_join = None; - interval_threshold_widening_constants = None; - refinement = None; -} - -let get_interval_threshold_widening () = - if ana_int_config.interval_threshold_widening = None then - ana_int_config.interval_threshold_widening <- Some (get_bool "ana.int.interval_threshold_widening"); - Option.get ana_int_config.interval_threshold_widening - -let get_interval_narrow_by_meet () = - if ana_int_config.interval_narrow_by_meet = None then - ana_int_config.interval_narrow_by_meet <- Some (get_bool "ana.int.interval_narrow_by_meet"); - Option.get ana_int_config.interval_narrow_by_meet - -let get_def_exc_widen_by_join () = - if ana_int_config.def_exc_widen_by_join = None then - ana_int_config.def_exc_widen_by_join <- Some (get_bool "ana.int.def_exc_widen_by_join"); - Option.get ana_int_config.def_exc_widen_by_join - -let get_interval_threshold_widening_constants () = - if ana_int_config.interval_threshold_widening_constants = None then - ana_int_config.interval_threshold_widening_constants <- Some (get_string "ana.int.interval_threshold_widening_constants"); - Option.get ana_int_config.interval_threshold_widening_constants - -let get_refinement () = - if ana_int_config.refinement = None then - ana_int_config.refinement <- Some (get_string "ana.int.refinement"); - Option.get ana_int_config.refinement - - - -(** Whether for a given ikind, we should compute with wrap-around arithmetic. - * Always for unsigned types, for signed types if 'sem.int.signed_overflow' is 'assume_wraparound' *) -let should_wrap ik = not (Cil.isSigned ik) || get_string "sem.int.signed_overflow" = "assume_wraparound" - -(** Whether for a given ikind, we should assume there are no overflows. - * Always false for unsigned types, true for signed types if 'sem.int.signed_overflow' is 'assume_none' *) -let should_ignore_overflow ik = Cil.isSigned ik && get_string "sem.int.signed_overflow" = "assume_none" - -let widening_thresholds = ResettableLazy.from_fun WideningThresholds.thresholds -let widening_thresholds_desc = ResettableLazy.from_fun (List.rev % WideningThresholds.thresholds) - -type overflow_info = { overflow: bool; underflow: bool;} - -let set_overflow_flag ~cast ~underflow ~overflow ik = - if !AnalysisState.executing_speculative_computations then - (* Do not produce warnings when the operations are not actually happening in code *) - () - else - let signed = Cil.isSigned ik in - if !AnalysisState.postsolving && signed && not cast then - AnalysisState.svcomp_may_overflow := true; - let sign = if signed then "Signed" else "Unsigned" in - match underflow, overflow with - | true, true -> - M.warn ~category:M.Category.Integer.overflow ~tags:[CWE 190; CWE 191] "%s integer overflow and underflow" sign - | true, false -> - M.warn ~category:M.Category.Integer.overflow ~tags:[CWE 191] "%s integer underflow" sign - | false, true -> - M.warn ~category:M.Category.Integer.overflow ~tags:[CWE 190] "%s integer overflow" sign - | false, false -> assert false - -let reset_lazy () = - ResettableLazy.reset widening_thresholds; - ResettableLazy.reset widening_thresholds_desc; - ana_int_config.interval_threshold_widening <- None; - ana_int_config.interval_narrow_by_meet <- None; - ana_int_config.def_exc_widen_by_join <- None; - ana_int_config.interval_threshold_widening_constants <- None; - ana_int_config.refinement <- None - -module type Arith = -sig - type t - val neg: t -> t - val add: t -> t -> t - val sub: t -> t -> t - val mul: t -> t -> t - val div: t -> t -> t - val rem: t -> t -> t - - val lt: t -> t -> t - val gt: t -> t -> t - val le: t -> t -> t - val ge: t -> t -> t - val eq: t -> t -> t - val ne: t -> t -> t - - val lognot: t -> t - val logand: t -> t -> t - val logor : t -> t -> t - val logxor: t -> t -> t - - val shift_left : t -> t -> t - val shift_right: t -> t -> t - - val c_lognot: t -> t - val c_logand: t -> t -> t - val c_logor : t -> t -> t - -end - -module type ArithIkind = -sig - type t - val neg: Cil.ikind -> t -> t - val add: Cil.ikind -> t -> t -> t - val sub: Cil.ikind -> t -> t -> t - val mul: Cil.ikind -> t -> t -> t - val div: Cil.ikind -> t -> t -> t - val rem: Cil.ikind -> t -> t -> t - - val lt: Cil.ikind -> t -> t -> t - val gt: Cil.ikind -> t -> t -> t - val le: Cil.ikind -> t -> t -> t - val ge: Cil.ikind -> t -> t -> t - val eq: Cil.ikind -> t -> t -> t - val ne: Cil.ikind -> t -> t -> t - - val lognot: Cil.ikind -> t -> t - val logand: Cil.ikind -> t -> t -> t - val logor : Cil.ikind -> t -> t -> t - val logxor: Cil.ikind -> t -> t -> t - - val shift_left : Cil.ikind -> t -> t -> t - val shift_right: Cil.ikind -> t -> t -> t - - val c_lognot: Cil.ikind -> t -> t - val c_logand: Cil.ikind -> t -> t -> t - val c_logor : Cil.ikind -> t -> t -> t - -end - -(* Shared functions between S and Z *) -module type B = -sig - include Lattice.S - type int_t - val bot_of: Cil.ikind -> t - val top_of: Cil.ikind -> t - val to_int: t -> int_t option - val equal_to: int_t -> t -> [`Eq | `Neq | `Top] - - val to_bool: t -> bool option - val to_excl_list: t -> (int_t list * (int64 * int64)) option - val of_excl_list: Cil.ikind -> int_t list -> t - val is_excl_list: t -> bool - - val to_incl_list: t -> int_t list option - - val maximal : t -> int_t option - val minimal : t -> int_t option - - val cast_to: ?suppress_ovwarn:bool -> ?torg:Cil.typ -> Cil.ikind -> t -> t -end - -(** Interface of IntDomain implementations that do not take ikinds for arithmetic operations yet. TODO: Should be ported to S in the future. *) -module type IkindUnawareS = -sig - include B - include Arith with type t := t - val starting : ?suppress_ovwarn:bool -> Cil.ikind -> int_t -> t - val ending : ?suppress_ovwarn:bool -> Cil.ikind -> int_t -> t - val of_int: int_t -> t - val of_bool: bool -> t - val of_interval: ?suppress_ovwarn:bool -> Cil.ikind -> int_t * int_t -> t - val of_congruence: Cil.ikind -> int_t * int_t -> t - val arbitrary: unit -> t QCheck.arbitrary - val invariant: Cil.exp -> t -> Invariant.t -end - -(** Interface of IntDomain implementations taking an ikind for arithmetic operations *) -module type S = -sig - include B - include ArithIkind with type t:= t - - val add : ?no_ov:bool -> Cil.ikind -> t -> t -> t - val sub : ?no_ov:bool -> Cil.ikind -> t -> t -> t - val mul : ?no_ov:bool -> Cil.ikind -> t -> t -> t - val div : ?no_ov:bool -> Cil.ikind -> t -> t -> t - val neg : ?no_ov:bool -> Cil.ikind -> t -> t - val cast_to : ?suppress_ovwarn:bool -> ?torg:Cil.typ -> ?no_ov:bool -> Cil.ikind -> t -> t - - val join: Cil.ikind -> t -> t -> t - val meet: Cil.ikind -> t -> t -> t - val narrow: Cil.ikind -> t -> t -> t - val widen: Cil.ikind -> t -> t -> t - val starting : ?suppress_ovwarn:bool -> Cil.ikind -> int_t -> t - val ending : ?suppress_ovwarn:bool -> Cil.ikind -> int_t -> t - val of_int: Cil.ikind -> int_t -> t - val of_bool: Cil.ikind -> bool -> t - val of_interval: ?suppress_ovwarn:bool -> Cil.ikind -> int_t * int_t -> t - val of_congruence: Cil.ikind -> int_t * int_t -> t - val is_top_of: Cil.ikind -> t -> bool - val invariant_ikind : Cil.exp -> Cil.ikind -> t -> Invariant.t - - val refine_with_congruence: Cil.ikind -> t -> (int_t * int_t) option -> t - val refine_with_interval: Cil.ikind -> t -> (int_t * int_t) option -> t - val refine_with_excl_list: Cil.ikind -> t -> (int_t list * (int64 * int64)) option -> t - val refine_with_incl_list: Cil.ikind -> t -> int_t list option -> t - - val project: Cil.ikind -> int_precision -> t -> t - val arbitrary: Cil.ikind -> t QCheck.arbitrary -end - -module type SOverflow = -sig - - include S - - val add : ?no_ov:bool -> Cil.ikind -> t -> t -> t * overflow_info - - val sub : ?no_ov:bool -> Cil.ikind -> t -> t -> t * overflow_info - - val mul : ?no_ov:bool -> Cil.ikind -> t -> t -> t * overflow_info - - val div : ?no_ov:bool -> Cil.ikind -> t -> t -> t * overflow_info - - val neg : ?no_ov:bool -> Cil.ikind -> t -> t * overflow_info - - val cast_to : ?suppress_ovwarn:bool -> ?torg:Cil.typ -> ?no_ov:bool -> Cil.ikind -> t -> t * overflow_info - - val of_int : Cil.ikind -> int_t -> t * overflow_info - - val of_interval: ?suppress_ovwarn:bool -> Cil.ikind -> int_t * int_t -> t * overflow_info - - val starting : ?suppress_ovwarn:bool -> Cil.ikind -> int_t -> t * overflow_info - val ending : ?suppress_ovwarn:bool -> Cil.ikind -> int_t -> t * overflow_info - - val shift_left : Cil.ikind -> t -> t -> t * overflow_info - - val shift_right : Cil.ikind -> t -> t -> t * overflow_info -end - -module type Y = -sig - (* include B *) - include B - include Arith with type t:= t - val of_int: Cil.ikind -> int_t -> t - val of_bool: Cil.ikind -> bool -> t - val of_interval: ?suppress_ovwarn:bool -> Cil.ikind -> int_t * int_t -> t - val of_congruence: Cil.ikind -> int_t * int_t -> t - - val starting : ?suppress_ovwarn:bool -> Cil.ikind -> int_t -> t - val ending : ?suppress_ovwarn:bool -> Cil.ikind -> int_t -> t - val is_top_of: Cil.ikind -> t -> bool - - val project: int_precision -> t -> t - val invariant: Cil.exp -> t -> Invariant.t -end - -module type Z = Y with type int_t = Z.t - - -module IntDomLifter (I : S) = -struct - open Cil - type int_t = I.int_t - type t = { v : I.t; ikind : CilType.Ikind.t } [@@deriving eq, ord, hash] - - let ikind {ikind; _} = ikind - - (* Helper functions *) - let check_ikinds x y = if x.ikind <> y.ikind then raise (IncompatibleIKinds (GobPretty.sprintf "ikinds %a and %a are incompatible. Values: %a and %a" CilType.Ikind.pretty x.ikind CilType.Ikind.pretty y.ikind I.pretty x.v I.pretty y.v)) - let lift op x = {x with v = op x.ikind x.v } - (* For logical operations the result is of type int *) - let lift_logical op x = {v = op x.ikind x.v; ikind = Cil.IInt} - let lift2 op x y = check_ikinds x y; {x with v = op x.ikind x.v y.v } - let lift2_cmp op x y = check_ikinds x y; {v = op x.ikind x.v y.v; ikind = Cil.IInt} - - let bot_of ikind = { v = I.bot_of ikind; ikind} - let bot () = failwith "bot () is not implemented for IntDomLifter." - let is_bot x = I.is_bot x.v - let top_of ikind = { v = I.top_of ikind; ikind} - let top () = failwith "top () is not implemented for IntDomLifter." - let is_top x = I.is_top x.v - - (* Leq does not check for ikind, because it is used in invariant with arguments of different type. - TODO: check ikinds here and fix invariant to work with right ikinds *) - let leq x y = I.leq x.v y.v - let join = lift2 I.join - let meet = lift2 I.meet - let widen = lift2 I.widen - let narrow = lift2 I.narrow - - let show x = - if not (GobConfig.get_bool "dbg.full-output") && I.is_top_of x.ikind x.v then - "⊤" - else - I.show x.v (* TODO add ikind to output *) - let pretty () x = - if not (GobConfig.get_bool "dbg.full-output") && I.is_top_of x.ikind x.v then - Pretty.text "⊤" - else - I.pretty () x.v (* TODO add ikind to output *) - let pretty_diff () (x, y) = I.pretty_diff () (x.v, y.v) (* TODO check ikinds, add them to output *) - let printXml o x = - if not (GobConfig.get_bool "dbg.full-output") && I.is_top_of x.ikind x.v then - BatPrintf.fprintf o "\n\n⊤\n\n\n" - else - I.printXml o x.v (* TODO add ikind to output *) - (* This is for debugging *) - let name () = "IntDomLifter(" ^ (I.name ()) ^ ")" - let to_yojson x = I.to_yojson x.v - let invariant e x = - let e' = Cilfacade.mkCast ~e ~newt:(TInt (x.ikind, [])) in - I.invariant_ikind e' x.ikind x.v - let tag x = I.tag x.v - let arbitrary ik = failwith @@ "Arbitrary not implement for " ^ (name ()) ^ "." - let to_int x = I.to_int x.v - let of_int ikind x = { v = I.of_int ikind x; ikind} - let equal_to i x = I.equal_to i x.v - let to_bool x = I.to_bool x.v - let of_bool ikind b = { v = I.of_bool ikind b; ikind} - let to_excl_list x = I.to_excl_list x.v - let of_excl_list ikind is = {v = I.of_excl_list ikind is; ikind} - let is_excl_list x = I.is_excl_list x.v - let to_incl_list x = I.to_incl_list x.v - let of_interval ?(suppress_ovwarn=false) ikind (lb,ub) = {v = I.of_interval ~suppress_ovwarn ikind (lb,ub); ikind} - let of_congruence ikind (c,m) = {v = I.of_congruence ikind (c,m); ikind} - let starting ?(suppress_ovwarn=false) ikind i = {v = I.starting ~suppress_ovwarn ikind i; ikind} - let ending ?(suppress_ovwarn=false) ikind i = {v = I.ending ~suppress_ovwarn ikind i; ikind} - let maximal x = I.maximal x.v - let minimal x = I.minimal x.v - - let neg = lift I.neg - let add = lift2 I.add - let sub = lift2 I.sub - let mul = lift2 I.mul - let div = lift2 I.div - let rem = lift2 I.rem - let lt = lift2_cmp I.lt - let gt = lift2_cmp I.gt - let le = lift2_cmp I.le - let ge = lift2_cmp I.ge - let eq = lift2_cmp I.eq - let ne = lift2_cmp I.ne - let lognot = lift I.lognot - let logand = lift2 I.logand - let logor = lift2 I.logor - let logxor = lift2 I.logxor - let shift_left x y = {x with v = I.shift_left x.ikind x.v y.v } (* TODO check ikinds*) - let shift_right x y = {x with v = I.shift_right x.ikind x.v y.v } (* TODO check ikinds*) - let c_lognot = lift_logical I.c_lognot - let c_logand = lift2 I.c_logand - let c_logor = lift2 I.c_logor - - let cast_to ?(suppress_ovwarn=false) ?torg ikind x = {v = I.cast_to ~suppress_ovwarn ~torg:(TInt(x.ikind,[])) ikind x.v; ikind} - - let is_top_of ik x = ik = x.ikind && I.is_top_of ik x.v - - let relift x = { v = I.relift x.v; ikind = x.ikind } - - let project p v = { v = I.project v.ikind p v.v; ikind = v.ikind } -end - -module type Ikind = -sig - val ikind: unit -> Cil.ikind -end - -module PtrDiffIkind : Ikind = -struct - let ikind = Cilfacade.ptrdiff_ikind -end - -module IntDomWithDefaultIkind (I: Y) (Ik: Ikind) : Y with type t = I.t and type int_t = I.int_t = -struct - include I - let top () = I.top_of (Ik.ikind ()) - let bot () = I.bot_of (Ik.ikind ()) -end - -module Size = struct (* size in bits as int, range as int64 *) - open Cil - let sign x = if Z.compare x Z.zero < 0 then `Signed else `Unsigned - - let top_typ = TInt (ILongLong, []) - let min_for x = intKindForValue x (sign x = `Unsigned) - let bit = function (* bits needed for representation *) - | IBool -> 1 - | ik -> bytesSizeOfInt ik * 8 - let is_int64_big_int x = Z.fits_int64 x - let card ik = (* cardinality *) - let b = bit ik in - Z.shift_left Z.one b - let bits ik = (* highest bits for neg/pos values *) - let s = bit ik in - if isSigned ik then s-1, s-1 else 0, s - let bits_i64 ik = BatTuple.Tuple2.mapn Int64.of_int (bits ik) - let range ik = - let a,b = bits ik in - let x = if isSigned ik then Z.neg (Z.shift_left Z.one a) (* -2^a *) else Z.zero in - let y = Z.pred (Z.shift_left Z.one b) in (* 2^b - 1 *) - x,y - - let is_cast_injective ~from_type ~to_type = - let (from_min, from_max) = range (Cilfacade.get_ikind from_type) in - let (to_min, to_max) = range (Cilfacade.get_ikind to_type) in - if M.tracing then M.trace "int" "is_cast_injective %a (%a, %a) -> %a (%a, %a)" CilType.Typ.pretty from_type GobZ.pretty from_min GobZ.pretty from_max CilType.Typ.pretty to_type GobZ.pretty to_min GobZ.pretty to_max; - Z.compare to_min from_min <= 0 && Z.compare from_max to_max <= 0 - - let cast t x = (* TODO: overflow is implementation-dependent! *) - if t = IBool then - (* C11 6.3.1.2 Boolean type *) - if Z.equal x Z.zero then Z.zero else Z.one - else - let a,b = range t in - let c = card t in - let y = Z.erem x c in - let y = if Z.gt y b then Z.sub y c - else if Z.lt y a then Z.add y c - else y - in - if M.tracing then M.tracel "cast" "Cast %a to range [%a, %a] (%a) = %a (%s in int64)" GobZ.pretty x GobZ.pretty a GobZ.pretty b GobZ.pretty c GobZ.pretty y (if is_int64_big_int y then "fits" else "does not fit"); - y - - let min_range_sign_agnostic x = - let size ik = - let a,b = bits_i64 ik in - Int64.neg a,b - in - if sign x = `Signed then - size (min_for x) - else - let a, b = size (min_for x) in - if b <= 64L then - let upper_bound_less = Int64.sub b 1L in - let max_one_less = Z.(pred @@ shift_left Z.one (Int64.to_int upper_bound_less)) in - if x <= max_one_less then - a, upper_bound_less - else - a,b - else - a, b - - (* From the number of bits used to represent a positive value, determines the maximal representable value *) - let max_from_bit_range pos_bits = Z.(pred @@ shift_left Z.one (to_int (Z.of_int64 pos_bits))) - - (* From the number of bits used to represent a non-positive value, determines the minimal representable value *) - let min_from_bit_range neg_bits = Z.(if neg_bits = 0L then Z.zero else neg @@ shift_left Z.one (to_int (neg (Z.of_int64 neg_bits)))) - -end - - -module StdTop (B: sig type t val top_of: Cil.ikind -> t end) = struct - open B - (* these should be overwritten for better precision if possible: *) - let to_excl_list x = None - let of_excl_list ik x = top_of ik - let is_excl_list x = false - let to_incl_list x = None - let of_interval ?(suppress_ovwarn=false) ik x = top_of ik - let of_congruence ik x = top_of ik - let starting ?(suppress_ovwarn=false) ik x = top_of ik - let ending ?(suppress_ovwarn=false) ik x = top_of ik - let maximal x = None - let minimal x = None -end - -module Std (B: sig - type t - val name: unit -> string - val top_of: Cil.ikind -> t - val bot_of: Cil.ikind -> t - val show: t -> string - val equal: t -> t -> bool - end) = struct - include Printable.StdLeaf - let name = B.name (* overwrite the one from Printable.Std *) - open B - let is_top x = failwith "is_top not implemented for IntDomain.Std" - let is_bot x = B.equal x (bot_of Cil.IInt) (* Here we assume that the representation of bottom is independent of the ikind - This may be true for intdomain implementations, but not e.g. for IntDomLifter. *) - let is_top_of ik x = B.equal x (top_of ik) - - (* all output is based on B.show *) - include Printable.SimpleShow ( - struct - type nonrec t = t - let show = show - end - ) - let pretty_diff () (x,y) = dprintf "%s: %a instead of %a" (name ()) pretty x pretty y - - include StdTop (B) -end - -(* Textbook interval arithmetic, without any overflow handling etc. *) -module IntervalArith (Ints_t : IntOps.IntOps) = struct - let min4 a b c d = Ints_t.min (Ints_t.min a b) (Ints_t.min c d) - let max4 a b c d = Ints_t.max (Ints_t.max a b) (Ints_t.max c d) - - let mul (x1, x2) (y1, y2) = - let x1y1 = (Ints_t.mul x1 y1) in - let x1y2 = (Ints_t.mul x1 y2) in - let x2y1 = (Ints_t.mul x2 y1) in - let x2y2 = (Ints_t.mul x2 y2) in - (min4 x1y1 x1y2 x2y1 x2y2, max4 x1y1 x1y2 x2y1 x2y2) - - let shift_left (x1,x2) (y1,y2) = - let y1p = Ints_t.shift_left Ints_t.one y1 in - let y2p = Ints_t.shift_left Ints_t.one y2 in - mul (x1, x2) (y1p, y2p) - - let div (x1, x2) (y1, y2) = - let x1y1n = (Ints_t.div x1 y1) in - let x1y2n = (Ints_t.div x1 y2) in - let x2y1n = (Ints_t.div x2 y1) in - let x2y2n = (Ints_t.div x2 y2) in - let x1y1p = (Ints_t.div x1 y1) in - let x1y2p = (Ints_t.div x1 y2) in - let x2y1p = (Ints_t.div x2 y1) in - let x2y2p = (Ints_t.div x2 y2) in - (min4 x1y1n x1y2n x2y1n x2y2n, max4 x1y1p x1y2p x2y1p x2y2p) - - let add (x1, x2) (y1, y2) = (Ints_t.add x1 y1, Ints_t.add x2 y2) - let sub (x1, x2) (y1, y2) = (Ints_t.sub x1 y2, Ints_t.sub x2 y1) - - let neg (x1, x2) = (Ints_t.neg x2, Ints_t.neg x1) - - let one = (Ints_t.one, Ints_t.one) - let zero = (Ints_t.zero, Ints_t.zero) - let top_bool = (Ints_t.zero, Ints_t.one) - - let to_int (x1, x2) = - if Ints_t.equal x1 x2 then Some x1 else None -end - -module IntervalFunctor (Ints_t : IntOps.IntOps): SOverflow with type int_t = Ints_t.t and type t = (Ints_t.t * Ints_t.t) option = -struct - let name () = "intervals" - type int_t = Ints_t.t - type t = (Ints_t.t * Ints_t.t) option [@@deriving eq, ord, hash] - module IArith = IntervalArith (Ints_t) - - let range ik = BatTuple.Tuple2.mapn Ints_t.of_bigint (Size.range ik) - - let top () = failwith @@ "top () not implemented for " ^ (name ()) - let top_of ik = Some (range ik) - let bot () = None - let bot_of ik = bot () (* TODO: improve *) - - let show = function None -> "bottom" | Some (x,y) -> "["^Ints_t.to_string x^","^Ints_t.to_string y^"]" - - include Std (struct type nonrec t = t let name = name let top_of = top_of let bot_of = bot_of let show = show let equal = equal end) - - let equal_to i = function - | None -> failwith "unsupported: equal_to with bottom" - | Some (a, b) -> - if a = b && b = i then `Eq else if Ints_t.compare a i <= 0 && Ints_t.compare i b <=0 then `Top else `Neq - - let norm ?(suppress_ovwarn=false) ?(cast=false) ik : (t -> t * overflow_info) = function None -> (None, {underflow=false; overflow=false}) | Some (x,y) -> - if Ints_t.compare x y > 0 then - (None,{underflow=false; overflow=false}) - else ( - let (min_ik, max_ik) = range ik in - let underflow = Ints_t.compare min_ik x > 0 in - let overflow = Ints_t.compare max_ik y < 0 in - let ov_info = { underflow = underflow && not suppress_ovwarn; overflow = overflow && not suppress_ovwarn } in - let v = - if underflow || overflow then - if should_wrap ik then (* could add [|| cast], but that's GCC implementation-defined behavior: https://gcc.gnu.org/onlinedocs/gcc/Integers-implementation.html#Integers-implementation *) - (* We can only soundly wrap if at most one overflow occurred, otherwise the minimal and maximal values of the interval *) - (* on Z will not safely contain the minimal and maximal elements after the cast *) - let diff = Ints_t.abs (Ints_t.sub max_ik min_ik) in - let resdiff = Ints_t.abs (Ints_t.sub y x) in - if Ints_t.compare resdiff diff > 0 then - top_of ik - else - let l = Ints_t.of_bigint @@ Size.cast ik (Ints_t.to_bigint x) in - let u = Ints_t.of_bigint @@ Size.cast ik (Ints_t.to_bigint y) in - if Ints_t.compare l u <= 0 then - Some (l, u) - else - (* Interval that wraps around (begins to the right of its end). We can not represent such intervals *) - top_of ik - else if not cast && should_ignore_overflow ik then - let tl, tu = BatOption.get @@ top_of ik in - Some (Ints_t.max tl x, Ints_t.min tu y) - else - top_of ik - else - Some (x,y) - in - (v, ov_info) - ) - - let leq (x:t) (y:t) = - match x, y with - | None, _ -> true - | Some _, None -> false - | Some (x1,x2), Some (y1,y2) -> Ints_t.compare x1 y1 >= 0 && Ints_t.compare x2 y2 <= 0 - - let join ik (x:t) y = - match x, y with - | None, z | z, None -> z - | Some (x1,x2), Some (y1,y2) -> norm ik @@ Some (Ints_t.min x1 y1, Ints_t.max x2 y2) |> fst - - let meet ik (x:t) y = - match x, y with - | None, z | z, None -> None - | Some (x1,x2), Some (y1,y2) -> norm ik @@ Some (Ints_t.max x1 y1, Ints_t.min x2 y2) |> fst - - (* TODO: change to_int signature so it returns a big_int *) - let to_int x = Option.bind x (IArith.to_int) - let of_interval ?(suppress_ovwarn=false) ik (x,y) = norm ~suppress_ovwarn ik @@ Some (x,y) - let of_int ik (x: int_t) = of_interval ik (x,x) - let zero = Some IArith.zero - let one = Some IArith.one - let top_bool = Some IArith.top_bool - - let of_bool _ik = function true -> one | false -> zero - let to_bool (a: t) = match a with - | None -> None - | Some (l, u) when Ints_t.compare l Ints_t.zero = 0 && Ints_t.compare u Ints_t.zero = 0 -> Some false - | x -> if leq zero x then None else Some true - - let starting ?(suppress_ovwarn=false) ik n = - norm ~suppress_ovwarn ik @@ Some (n, snd (range ik)) - - let ending ?(suppress_ovwarn=false) ik n = - norm ~suppress_ovwarn ik @@ Some (fst (range ik), n) - - (* TODO: change signature of maximal, minimal to return big_int*) - let maximal = function None -> None | Some (x,y) -> Some y - let minimal = function None -> None | Some (x,y) -> Some x - - let cast_to ?(suppress_ovwarn=false) ?torg ?no_ov t = norm ~cast:true t (* norm does all overflow handling *) - - let widen ik x y = - match x, y with - | None, z | z, None -> z - | Some (l0,u0), Some (l1,u1) -> - let (min_ik, max_ik) = range ik in - let threshold = get_interval_threshold_widening () in - let upper_threshold u = - let ts = if get_interval_threshold_widening_constants () = "comparisons" then WideningThresholds.upper_thresholds () else ResettableLazy.force widening_thresholds in - let u = Ints_t.to_bigint u in - let t = List.find_opt (fun x -> Z.compare u x <= 0) ts in - BatOption.map_default Ints_t.of_bigint max_ik t - in - let lower_threshold l = - let ts = if get_interval_threshold_widening_constants () = "comparisons" then WideningThresholds.lower_thresholds () else ResettableLazy.force widening_thresholds_desc in - let l = Ints_t.to_bigint l in - let t = List.find_opt (fun x -> Z.compare l x >= 0) ts in - BatOption.map_default Ints_t.of_bigint min_ik t - in - let lt = if threshold then lower_threshold l1 else min_ik in - let l2 = if Ints_t.compare l0 l1 = 0 then l0 else Ints_t.min l1 (Ints_t.max lt min_ik) in - let ut = if threshold then upper_threshold u1 else max_ik in - let u2 = if Ints_t.compare u0 u1 = 0 then u0 else Ints_t.max u1 (Ints_t.min ut max_ik) in - norm ik @@ Some (l2,u2) |> fst - let widen ik x y = - let r = widen ik x y in - if M.tracing && not (equal x y) then M.tracel "int" "interval widen %a %a -> %a" pretty x pretty y pretty r; - assert (leq x y); (* TODO: remove for performance reasons? *) - r - - let narrow ik x y = - match x, y with - | _,None | None, _ -> None - | Some (x1,x2), Some (y1,y2) -> - let (min_ik, max_ik) = range ik in - let lr = if Ints_t.compare min_ik x1 = 0 then y1 else x1 in - let ur = if Ints_t.compare max_ik x2 = 0 then y2 else x2 in - norm ik @@ Some (lr,ur) |> fst - - - let narrow ik x y = - if get_interval_narrow_by_meet () then - meet ik x y - else - narrow ik x y - - let log f ~annihilator ik i1 i2 = - match is_bot i1, is_bot i2 with - | true, true -> bot_of ik - | true, _ - | _ , true -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show i1) (show i2))) - | _ -> - match to_bool i1, to_bool i2 with - | Some x, _ when x = annihilator -> of_bool ik annihilator - | _, Some y when y = annihilator -> of_bool ik annihilator - | Some x, Some y -> of_bool ik (f x y) - | _ -> top_of ik - - let c_logor = log (||) ~annihilator:true - let c_logand = log (&&) ~annihilator:false - - let log1 f ik i1 = - if is_bot i1 then - bot_of ik - else - match to_bool i1 with - | Some x -> of_bool ik (f ik x) - | _ -> top_of ik - - let c_lognot = log1 (fun _ik -> not) - - let bit f ik i1 i2 = - match is_bot i1, is_bot i2 with - | true, true -> bot_of ik - | true, _ - | _ , true -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show i1) (show i2))) - | _ -> - match to_int i1, to_int i2 with - | Some x, Some y -> (try of_int ik (f ik x y) |> fst with Division_by_zero -> top_of ik) - | _ -> top_of ik - - let bitcomp f ik i1 i2 = - match is_bot i1, is_bot i2 with - | true, true -> (bot_of ik,{underflow=false; overflow=false}) - | true, _ - | _ , true -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show i1) (show i2))) - | _ -> - match to_int i1, to_int i2 with - | Some x, Some y -> (try of_int ik (f ik x y) with Division_by_zero | Invalid_argument _ -> (top_of ik,{underflow=false; overflow=false})) - | _ -> (top_of ik,{underflow=true; overflow=true}) - - let logxor = bit (fun _ik -> Ints_t.logxor) - - let logand ik i1 i2 = - match is_bot i1, is_bot i2 with - | true, true -> bot_of ik - | true, _ - | _ , true -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show i1) (show i2))) - | _ -> - match to_int i1, to_int i2 with - | Some x, Some y -> (try of_int ik (Ints_t.logand x y) |> fst with Division_by_zero -> top_of ik) - | _, Some y when Ints_t.equal y Ints_t.zero -> of_int ik Ints_t.zero |> fst - | _, Some y when Ints_t.equal y Ints_t.one -> of_interval ik (Ints_t.zero, Ints_t.one) |> fst - | _ -> top_of ik - - let logor = bit (fun _ik -> Ints_t.logor) - - let bit1 f ik i1 = - if is_bot i1 then - bot_of ik - else - match to_int i1 with - | Some x -> of_int ik (f ik x) |> fst - | _ -> top_of ik - - let lognot = bit1 (fun _ik -> Ints_t.lognot) - let shift_right = bitcomp (fun _ik x y -> Ints_t.shift_right x (Ints_t.to_int y)) - - let neg ?no_ov ik = function None -> (None,{underflow=false; overflow=false}) | Some x -> norm ik @@ Some (IArith.neg x) - - let binary_op_with_norm ?no_ov op ik x y = match x, y with - | None, None -> (None, {overflow=false; underflow= false}) - | None, _ | _, None -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) - | Some x, Some y -> norm ik @@ Some (op x y) - - let add ?no_ov = binary_op_with_norm IArith.add - let mul ?no_ov = binary_op_with_norm IArith.mul - let sub ?no_ov = binary_op_with_norm IArith.sub - - let shift_left ik a b = - match is_bot a, is_bot b with - | true, true -> (bot_of ik,{underflow=false; overflow=false}) - | true, _ - | _ , true -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show a) (show b))) - | _ -> - match a, minimal b, maximal b with - | Some a, Some bl, Some bu when (Ints_t.compare bl Ints_t.zero >= 0) -> - (try - let r = IArith.shift_left a (Ints_t.to_int bl, Ints_t.to_int bu) in - norm ik @@ Some r - with Z.Overflow -> (top_of ik,{underflow=false; overflow=true})) - | _ -> (top_of ik,{underflow=true; overflow=true}) - - let rem ik x y = match x, y with - | None, None -> None - | None, _ | _, None -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) - | Some (xl, xu), Some (yl, yu) -> - if is_top_of ik x && is_top_of ik y then - (* This is needed to preserve soundness also on things bigger than int32 e.g. *) - (* x: 3803957176L -> T in Interval32 *) - (* y: 4209861404L -> T in Interval32 *) - (* x % y: 3803957176L -> T in Interval32 *) - (* T in Interval32 is [-2147483648,2147483647] *) - (* the code below computes [-2147483647,2147483647] for this though which is unsound *) - top_of ik - else - (* If we have definite values, Ints_t.rem will give a definite result. - * Otherwise we meet with a [range] the result can be in. - * This range is [0, min xu b] if x is positive, and [max xl -b, min xu b] if x can be negative. - * The precise bound b is one smaller than the maximum bound. Negative y give the same result as positive. *) - let pos x = if Ints_t.compare x Ints_t.zero < 0 then Ints_t.neg x else x in - let b = Ints_t.sub (Ints_t.max (pos yl) (pos yu)) Ints_t.one in - let range = if Ints_t.compare xl Ints_t.zero>= 0 then Some (Ints_t.zero, Ints_t.min xu b) else Some (Ints_t.max xl (Ints_t.neg b), Ints_t.min (Ints_t.max (pos xl) (pos xu)) b) in - meet ik (bit (fun _ik -> Ints_t.rem) ik x y) range - - let rec div ?no_ov ik x y = - match x, y with - | None, None -> (bot (),{underflow=false; overflow=false}) - | None, _ | _, None -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) - | (Some (x1,x2) as x), (Some (y1,y2) as y) -> - begin - let is_zero v = Ints_t.compare v Ints_t.zero = 0 in - match y1, y2 with - | l, u when is_zero l && is_zero u -> (top_of ik,{underflow=false; overflow=false}) (* TODO warn about undefined behavior *) - | l, _ when is_zero l -> div ik (Some (x1,x2)) (Some (Ints_t.one,y2)) - | _, u when is_zero u -> div ik (Some (x1,x2)) (Some (y1, Ints_t.(neg one))) - | _ when leq (of_int ik (Ints_t.zero) |> fst) (Some (y1,y2)) -> (top_of ik,{underflow=false; overflow=false}) - | _ -> binary_op_with_norm IArith.div ik x y - end - - let ne ik x y = - match x, y with - | None, None -> bot_of ik - | None, _ | _, None -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) - | Some (x1,x2), Some (y1,y2) -> - if Ints_t.compare y2 x1 < 0 || Ints_t.compare x2 y1 < 0 then - of_bool ik true - else if Ints_t.compare x2 y1 <= 0 && Ints_t.compare y2 x1 <= 0 then - of_bool ik false - else top_bool - - let eq ik x y = - match x, y with - | None, None -> bot_of ik - | None, _ | _, None -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) - | Some (x1,x2), Some (y1,y2) -> - if Ints_t.compare y2 x1 <= 0 && Ints_t.compare x2 y1 <= 0 then - of_bool ik true - else if Ints_t.compare y2 x1 < 0 || Ints_t.compare x2 y1 < 0 then - of_bool ik false - else top_bool - - let ge ik x y = - match x, y with - | None, None -> bot_of ik - | None, _ | _, None -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) - | Some (x1,x2), Some (y1,y2) -> - if Ints_t.compare y2 x1 <= 0 then of_bool ik true - else if Ints_t.compare x2 y1 < 0 then of_bool ik false - else top_bool - - let le ik x y = - match x, y with - | None, None -> bot_of ik - | None, _ | _, None -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) - | Some (x1,x2), Some (y1,y2) -> - if Ints_t.compare x2 y1 <= 0 then of_bool ik true - else if Ints_t.compare y2 x1 < 0 then of_bool ik false - else top_bool - - let gt ik x y = - match x, y with - | None, None -> bot_of ik - | None, _ | _, None -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) - | Some (x1,x2), Some (y1,y2) -> - if Ints_t.compare y2 x1 < 0 then of_bool ik true - else if Ints_t.compare x2 y1 <= 0 then of_bool ik false - else top_bool - - let lt ik x y = - match x, y with - | None, None -> bot_of ik - | None, _ | _, None -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) - | Some (x1,x2), Some (y1,y2) -> - if Ints_t.compare x2 y1 < 0 then of_bool ik true - else if Ints_t.compare y2 x1 <= 0 then of_bool ik false - else top_bool - - let invariant_ikind e ik x = - match x with - | Some (x1, x2) when Ints_t.compare x1 x2 = 0 -> - if get_bool "witness.invariant.exact" then - let x1 = Ints_t.to_bigint x1 in - Invariant.of_exp Cil.(BinOp (Eq, e, kintegerCilint ik x1, intType)) - else - Invariant.top () - | Some (x1, x2) -> - let (min_ik, max_ik) = range ik in - let (x1', x2') = BatTuple.Tuple2.mapn (Ints_t.to_bigint) (x1, x2) in - let inexact_type_bounds = get_bool "witness.invariant.inexact-type-bounds" in - let i1 = if inexact_type_bounds || Ints_t.compare min_ik x1 <> 0 then Invariant.of_exp Cil.(BinOp (Le, kintegerCilint ik x1', e, intType)) else Invariant.none in - let i2 = if inexact_type_bounds || Ints_t.compare x2 max_ik <> 0 then Invariant.of_exp Cil.(BinOp (Le, e, kintegerCilint ik x2', intType)) else Invariant.none in - Invariant.(i1 && i2) - | None -> Invariant.none - - let arbitrary ik = - let open QCheck.Iter in - (* let int_arb = QCheck.map ~rev:Ints_t.to_bigint Ints_t.of_bigint GobQCheck.Arbitrary.big_int in *) - (* TODO: apparently bigints are really slow compared to int64 for domaintest *) - let int_arb = QCheck.map ~rev:Ints_t.to_int64 Ints_t.of_int64 GobQCheck.Arbitrary.int64 in - let pair_arb = QCheck.pair int_arb int_arb in - let shrink = function - | Some (l, u) -> (return None) <+> (GobQCheck.shrink pair_arb (l, u) >|= of_interval ik >|= fst) - | None -> empty - in - QCheck.(set_shrink shrink @@ set_print show @@ map (*~rev:BatOption.get*) (fun x -> of_interval ik x |> fst ) pair_arb) - - let modulo n k = - let result = Ints_t.rem n k in - if Ints_t.compare result Ints_t.zero >= 0 then result - else Ints_t.add result k - - let refine_with_congruence ik (intv : t) (cong : (int_t * int_t ) option) : t = - match intv, cong with - | Some (x, y), Some (c, m) -> - if Ints_t.equal m Ints_t.zero && (Ints_t.compare c x < 0 || Ints_t.compare c y > 0) then None - else if Ints_t.equal m Ints_t.zero then - Some (c, c) - else - let (min_ik, max_ik) = range ik in - let rcx = - if Ints_t.equal x min_ik then x else - Ints_t.add x (modulo (Ints_t.sub c x) (Ints_t.abs m)) in - let lcy = - if Ints_t.equal y max_ik then y else - Ints_t.sub y (modulo (Ints_t.sub y c) (Ints_t.abs m)) in - if Ints_t.compare rcx lcy > 0 then None - else if Ints_t.equal rcx lcy then norm ik @@ Some (rcx, rcx) |> fst - else norm ik @@ Some (rcx, lcy) |> fst - | _ -> None - - let refine_with_congruence ik x y = - let refn = refine_with_congruence ik x y in - if M.tracing then M.trace "refine" "int_refine_with_congruence %a %a -> %a" pretty x pretty y pretty refn; - refn - - let refine_with_interval ik a b = meet ik a b - - let refine_with_excl_list ik (intv : t) (excl : (int_t list * (int64 * int64)) option) : t = - match intv, excl with - | None, _ | _, None -> intv - | Some(l, u), Some(ls, (rl, rh)) -> - let rec shrink op b = - let new_b = (op b (Ints_t.of_int(Bool.to_int(BatList.mem_cmp Ints_t.compare b ls)))) in - if not (Ints_t.equal b new_b) then shrink op new_b else new_b - in - let (min_ik, max_ik) = range ik in - let l' = if Ints_t.equal l min_ik then l else shrink Ints_t.add l in - let u' = if Ints_t.equal u max_ik then u else shrink Ints_t.sub u in - let intv' = norm ik @@ Some (l', u') |> fst in - let range = norm ~suppress_ovwarn:true ik (Some (Ints_t.of_bigint (Size.min_from_bit_range rl), Ints_t.of_bigint (Size.max_from_bit_range rh))) |> fst in - meet ik intv' range - - let refine_with_incl_list ik (intv: t) (incl : (int_t list) option) : t = - match intv, incl with - | None, _ | _, None -> intv - | Some(l, u), Some(ls) -> - let rec min m1 ms = match ms with | [] -> m1 | x::xs -> match m1 with - | None -> min (Some x) xs | Some m -> if Ints_t.compare m x < 0 then min (Some m) xs else min (Some x) xs in - let rec max m1 ms = match ms with | [] -> m1 | x::xs -> match m1 with - | None -> max (Some x) xs | Some m -> if Ints_t.compare m x > 0 then max (Some m) xs else max (Some x) xs in - match min None ls, max None ls with - | Some m1, Some m2 -> refine_with_interval ik (Some(l, u)) (Some (m1, m2)) - | _, _-> intv - - let project ik p t = t -end - -(** IntervalSetFunctor that is not just disjunctive completion of intervals, but attempts to be precise for wraparound arithmetic for unsigned types *) -module IntervalSetFunctor (Ints_t : IntOps.IntOps): SOverflow with type int_t = Ints_t.t and type t = (Ints_t.t * Ints_t.t) list = -struct - - module Interval = IntervalFunctor (Ints_t) - module IArith = IntervalArith (Ints_t) - - - let name () = "interval_sets" - - type int_t = Ints_t.t - - let (>.) a b = Ints_t.compare a b > 0 - let (=.) a b = Ints_t.compare a b = 0 - let (<.) a b = Ints_t.compare a b < 0 - let (>=.) a b = Ints_t.compare a b >= 0 - let (<=.) a b = Ints_t.compare a b <= 0 - let (+.) a b = Ints_t.add a b - let (-.) a b = Ints_t.sub a b - - (* - Each domain's element is guaranteed to be in canonical form. That is, each interval contained - inside the set does not overlap with each other and they are not adjacent. - *) - type t = (Ints_t.t * Ints_t.t) list [@@deriving eq, hash, ord] - - let range ik = BatTuple.Tuple2.mapn Ints_t.of_bigint (Size.range ik) - - let top () = failwith @@ "top () not implemented for " ^ (name ()) - - let top_of ik = [range ik] - - let bot () = [] - - let bot_of ik = bot () - - let show (x: t) = - let show_interval i = Printf.sprintf "[%s, %s]" (Ints_t.to_string (fst i)) (Ints_t.to_string (snd i)) in - List.fold_left (fun acc i -> (show_interval i) :: acc) [] x |> List.rev |> String.concat ", " |> Printf.sprintf "[%s]" - - (* New type definition for the sweeping line algorithm used for implementing join/meet functions. *) - type event = Enter of Ints_t.t | Exit of Ints_t.t - - let unbox_event = function Enter x -> x | Exit x -> x - - let cmp_events x y = - (* Deliberately comparing ints first => Cannot be derived *) - let res = Ints_t.compare (unbox_event x) (unbox_event y) in - if res <> 0 then res - else - begin - match (x, y) with - | (Enter _, Exit _) -> -1 - | (Exit _, Enter _) -> 1 - | (_, _) -> 0 - end - - let interval_set_to_events (xs: t) = - List.concat_map (fun (a, b) -> [Enter a; Exit b]) xs - - let two_interval_sets_to_events (xs: t) (ys: t) = - let xs = interval_set_to_events xs in - let ys = interval_set_to_events ys in - List.merge cmp_events xs ys - - (* Using the sweeping line algorithm, combined_event_list returns a new event list representing the intervals in which at least n intervals in xs overlap - This function is used for both join and meet operations with different parameter n: 1 for join, 2 for meet *) - let combined_event_list lattice_op (xs:event list) = - let l = match lattice_op with `Join -> 1 | `Meet -> 2 in - let aux (interval_count, acc) = function - | Enter x -> (interval_count + 1, if (interval_count + 1) >= l && interval_count < l then (Enter x)::acc else acc) - | Exit x -> (interval_count - 1, if interval_count >= l && (interval_count - 1) < l then (Exit x)::acc else acc) - in - List.fold_left aux (0, []) xs |> snd |> List.rev - - let rec events_to_intervals = function - | [] -> [] - | (Enter x)::(Exit y)::xs -> (x, y)::(events_to_intervals xs) - | _ -> failwith "Invalid events list" - - let remove_empty_gaps (xs: t) = - let aux acc (l, r) = match acc with - | ((a, b)::acc') when (b +. Ints_t.one) >=. l -> (a, r)::acc' - | _ -> (l, r)::acc - in - List.fold_left aux [] xs |> List.rev - - let canonize (xs: t) = - interval_set_to_events xs |> - List.sort cmp_events |> - combined_event_list `Join |> - events_to_intervals |> - remove_empty_gaps - - let unop (x: t) op = match x with - | [] -> [] - | _ -> canonize @@ List.concat_map op x - - let binop (x: t) (y: t) op : t = match x, y with - | [], _ -> [] - | _, [] -> [] - | _, _ -> canonize @@ List.concat_map op (BatList.cartesian_product x y) - - - include Std (struct type nonrec t = t let name = name let top_of = top_of let bot_of = bot_of let show = show let equal = equal end) - - let minimal = function - | [] -> None - | (x, _)::_ -> Some x - - let maximal = function - | [] -> None - | xs -> Some (BatList.last xs |> snd) - - let equal_to_interval i (a, b) = - if a =. b && b =. i then - `Eq - else if a <=. i && i <=. b then - `Top - else - `Neq - - let equal_to i xs = match List.map (equal_to_interval i) xs with - | [] -> failwith "unsupported: equal_to with bottom" - | [`Eq] -> `Eq - | ys when List.for_all ((=) `Neq) ys -> `Neq - | _ -> `Top - - let norm_interval ?(suppress_ovwarn=false) ?(cast=false) ik (x,y) : t*overflow_info = - if x >. y then - ([],{underflow=false; overflow=false}) - else - let (min_ik, max_ik) = range ik in - let underflow = min_ik >. x in - let overflow = max_ik <. y in - let v = if underflow || overflow then - begin - if should_wrap ik then (* could add [|| cast], but that's GCC implementation-defined behavior: https://gcc.gnu.org/onlinedocs/gcc/Integers-implementation.html#Integers-implementation *) - (* We can only soundly wrap if at most one overflow occurred, otherwise the minimal and maximal values of the interval *) - (* on Z will not safely contain the minimal and maximal elements after the cast *) - let diff = Ints_t.abs (max_ik -. min_ik) in - let resdiff = Ints_t.abs (y -. x) in - if resdiff >. diff then - [range ik] - else - let l = Ints_t.of_bigint @@ Size.cast ik (Ints_t.to_bigint x) in - let u = Ints_t.of_bigint @@ Size.cast ik (Ints_t.to_bigint y) in - if l <=. u then - [(l, u)] - else - (* Interval that wraps around (begins to the right of its end). We CAN represent such intervals *) - [(min_ik, u); (l, max_ik)] - else if not cast && should_ignore_overflow ik then - [Ints_t.max min_ik x, Ints_t.min max_ik y] - else - [range ik] - end - else - [(x,y)] - in - if suppress_ovwarn then (v, {underflow=false; overflow=false}) else (v, {underflow; overflow}) - - let norm_intvs ?(suppress_ovwarn=false) ?(cast=false) (ik:ikind) (xs: t) : t*overflow_info = - let res = List.map (norm_interval ~suppress_ovwarn ~cast ik) xs in - let intvs = List.concat_map fst res in - let underflow = List.exists (fun (_,{underflow; _}) -> underflow) res in - let overflow = List.exists (fun (_,{overflow; _}) -> underflow) res in - (canonize intvs,{underflow; overflow}) - - let binary_op_with_norm op (ik:ikind) (x: t) (y: t) : t*overflow_info = match x, y with - | [], _ -> ([],{overflow=false; underflow=false}) - | _, [] -> ([],{overflow=false; underflow=false}) - | _, _ -> norm_intvs ik @@ List.concat_map (fun (x,y) -> [op x y]) (BatList.cartesian_product x y) - - let binary_op_with_ovc (x: t) (y: t) op : t*overflow_info = match x, y with - | [], _ -> ([],{overflow=false; underflow=false}) - | _, [] -> ([],{overflow=false; underflow=false}) - | _, _ -> - let res = List.map op (BatList.cartesian_product x y) in - let intvs = List.concat_map fst res in - let underflow = List.exists (fun (_,{underflow; _}) -> underflow) res in - let overflow = List.exists (fun (_,{overflow; _}) -> underflow) res in - (canonize intvs,{underflow; overflow}) - - let unary_op_with_norm op (ik:ikind) (x: t) = match x with - | [] -> ([],{overflow=false; underflow=false}) - | _ -> norm_intvs ik @@ List.concat_map (fun x -> [op x]) x - - let rec leq (xs: t) (ys: t) = - let leq_interval (al, au) (bl, bu) = al >=. bl && au <=. bu in - match xs, ys with - | [], _ -> true - | _, [] -> false - | (xl,xr)::xs', (yl,yr)::ys' -> - if leq_interval (xl,xr) (yl,yr) then - leq xs' ys - else if xr <. yl then - false - else - leq xs ys' - - let join ik (x: t) (y: t): t = - two_interval_sets_to_events x y |> - combined_event_list `Join |> - events_to_intervals |> - remove_empty_gaps - - let meet ik (x: t) (y: t): t = - two_interval_sets_to_events x y |> - combined_event_list `Meet |> - events_to_intervals - - let to_int = function - | [x] -> IArith.to_int x - | _ -> None - - let zero = [IArith.zero] - let one = [IArith.one] - let top_bool = [IArith.top_bool] - - let not_bool (x:t) = - let is_false x = equal x zero in - let is_true x = equal x one in - if is_true x then zero else if is_false x then one else top_bool - - let to_bool = function - | [(l,u)] when l =. Ints_t.zero && u =. Ints_t.zero -> Some false - | x -> if leq zero x then None else Some true - - let of_bool _ = function true -> one | false -> zero - - let of_interval ?(suppress_ovwarn=false) ik (x,y) = norm_interval ~suppress_ovwarn ~cast:false ik (x,y) - - let of_int ik (x: int_t) = of_interval ik (x, x) - - let lt ik x y = - match x, y with - | [], [] -> bot_of ik - | [], _ | _, [] -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) - | _, _ -> - let (max_x, min_y) = (maximal x |> Option.get, minimal y |> Option.get) in - let (min_x, max_y) = (minimal x |> Option.get, maximal y |> Option.get) in - if max_x <. min_y then - of_bool ik true - else if min_x >=. max_y then - of_bool ik false - else - top_bool - - let le ik x y = - match x, y with - | [], [] -> bot_of ik - | [], _ | _, [] -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) - | _, _ -> - let (max_x, min_y) = (maximal x |> Option.get, minimal y |> Option.get) in - let (min_x, max_y) = (minimal x |> Option.get, maximal y |> Option.get) in - if max_x <=. min_y then - of_bool ik true - else if min_x >. max_y then - of_bool ik false - else - top_bool - - let gt ik x y = not_bool @@ le ik x y - - let ge ik x y = not_bool @@ lt ik x y - - let eq ik x y = match x, y with - | (a, b)::[], (c, d)::[] when a =. b && c =. d && a =. c -> - one - | _ -> - if is_bot (meet ik x y) then - zero - else - top_bool - - let ne ik x y = not_bool @@ eq ik x y - let interval_to_int i = Interval.to_int (Some i) - let interval_to_bool i = Interval.to_bool (Some i) - - let log f ik (i1, i2) = - match (interval_to_bool i1, interval_to_bool i2) with - | Some x, Some y -> of_bool ik (f x y) - | _ -> top_of ik - - - let bit f ik (i1, i2) = - match (interval_to_int i1), (interval_to_int i2) with - | Some x, Some y -> (try of_int ik (f x y) |> fst with Division_by_zero -> top_of ik) - | _ -> top_of ik - - - let bitcomp f ik (i1, i2) = - match (interval_to_int i1, interval_to_int i2) with - | Some x, Some y -> (try of_int ik (f x y) with Division_by_zero | Invalid_argument _ -> (top_of ik,{overflow=false; underflow=false})) - | _, _ -> (top_of ik,{overflow=false; underflow=false}) - - let logand ik x y = - let interval_logand = bit Ints_t.logand ik in - binop x y interval_logand - - let logor ik x y = - let interval_logor = bit Ints_t.logor ik in - binop x y interval_logor - - let logxor ik x y = - let interval_logxor = bit Ints_t.logxor ik in - binop x y interval_logxor - - let lognot ik x = - let interval_lognot i = - match interval_to_int i with - | Some x -> of_int ik (Ints_t.lognot x) |> fst - | _ -> top_of ik - in - unop x interval_lognot - - let shift_left ik x y = - let interval_shiftleft = bitcomp (fun x y -> Ints_t.shift_left x (Ints_t.to_int y)) ik in - binary_op_with_ovc x y interval_shiftleft - - let shift_right ik x y = - let interval_shiftright = bitcomp (fun x y -> Ints_t.shift_right x (Ints_t.to_int y)) ik in - binary_op_with_ovc x y interval_shiftright - - let c_lognot ik x = - let log1 f ik i1 = - match interval_to_bool i1 with - | Some x -> of_bool ik (f x) - | _ -> top_of ik - in - let interval_lognot = log1 not ik in - unop x interval_lognot - - let c_logand ik x y = - let interval_logand = log (&&) ik in - binop x y interval_logand - - let c_logor ik x y = - let interval_logor = log (||) ik in - binop x y interval_logor - - let add ?no_ov = binary_op_with_norm IArith.add - let sub ?no_ov = binary_op_with_norm IArith.sub - let mul ?no_ov = binary_op_with_norm IArith.mul - let neg ?no_ov = unary_op_with_norm IArith.neg - - let div ?no_ov ik x y = - let rec interval_div x (y1, y2) = begin - let top_of ik = top_of ik |> List.hd in - let is_zero v = v =. Ints_t.zero in - match y1, y2 with - | l, u when is_zero l && is_zero u -> top_of ik (* TODO warn about undefined behavior *) - | l, _ when is_zero l -> interval_div x (Ints_t.one,y2) - | _, u when is_zero u -> interval_div x (y1, Ints_t.(neg one)) - | _ when leq (of_int ik (Ints_t.zero) |> fst) ([(y1,y2)]) -> top_of ik - | _ -> IArith.div x (y1, y2) - end - in binary_op_with_norm interval_div ik x y - - let rem ik x y = - let interval_rem (x, y) = - if Interval.is_top_of ik (Some x) && Interval.is_top_of ik (Some y) then - top_of ik - else - let (xl, xu) = x in let (yl, yu) = y in - let pos x = if x <. Ints_t.zero then Ints_t.neg x else x in - let b = (Ints_t.max (pos yl) (pos yu)) -. Ints_t.one in - let range = if xl >=. Ints_t.zero then (Ints_t.zero, Ints_t.min xu b) else (Ints_t.max xl (Ints_t.neg b), Ints_t.min (Ints_t.max (pos xl) (pos xu)) b) in - meet ik (bit Ints_t.rem ik (x, y)) [range] - in - binop x y interval_rem - - let cast_to ?(suppress_ovwarn=false) ?torg ?no_ov ik x = norm_intvs ~cast:true ik x - - (* - narrows down the extremeties of xs if they are equal to boundary values of the ikind with (possibly) narrower values from ys - *) - let narrow ik xs ys = match xs ,ys with - | [], _ -> [] | _ ,[] -> xs - | _, _ -> - let min_xs = minimal xs |> Option.get in - let max_xs = maximal xs |> Option.get in - let min_ys = minimal ys |> Option.get in - let max_ys = maximal ys |> Option.get in - let min_range,max_range = range ik in - let min = if min_xs =. min_range then min_ys else min_xs in - let max = if max_xs =. max_range then max_ys else max_xs in - xs - |> (function (_, y)::z -> (min, y)::z | _ -> []) - |> List.rev - |> (function (x, _)::z -> (x, max)::z | _ -> []) - |> List.rev - - (* - 1. partitions the intervals of xs by assigning each of them to the an interval in ys that includes it. - and joins all intervals in xs assigned to the same interval in ys as one interval. - 2. checks for every pair of adjacent pairs whether the pairs did approach (if you compare the intervals from xs and ys) and merges them if it is the case. - 3. checks whether partitions at the extremeties are approaching infinity (and expands them to infinity. in that case) - - The expansion (between a pair of adjacent partitions or at extremeties ) stops at a threshold. - *) - let widen ik xs ys = - let (min_ik,max_ik) = range ik in - let threshold = get_bool "ana.int.interval_threshold_widening" in - let upper_threshold (_,u) = - let ts = if GobConfig.get_string "ana.int.interval_threshold_widening_constants" = "comparisons" then WideningThresholds.upper_thresholds () else ResettableLazy.force widening_thresholds in - let u = Ints_t.to_bigint u in - let t = List.find_opt (fun x -> Z.compare u x <= 0) ts in - BatOption.map_default Ints_t.of_bigint max_ik t - in - let lower_threshold (l,_) = - let ts = if GobConfig.get_string "ana.int.interval_threshold_widening_constants" = "comparisons" then WideningThresholds.lower_thresholds () else ResettableLazy.force widening_thresholds_desc in - let l = Ints_t.to_bigint l in - let t = List.find_opt (fun x -> Z.compare l x >= 0) ts in - BatOption.map_default Ints_t.of_bigint min_ik t - in - (*obtain partitioning of xs intervals according to the ys interval that includes them*) - let rec interval_sets_to_partitions (ik: ikind) (acc : (int_t * int_t) option) (xs: t) (ys: t)= - match xs,ys with - | _, [] -> [] - | [], (y::ys) -> (acc,y):: interval_sets_to_partitions ik None [] ys - | (x::xs), (y::ys) when Interval.leq (Some x) (Some y) -> interval_sets_to_partitions ik (Interval.join ik acc (Some x)) xs (y::ys) - | (x::xs), (y::ys) -> (acc,y) :: interval_sets_to_partitions ik None (x::xs) ys - in - let interval_sets_to_partitions ik xs ys = interval_sets_to_partitions ik None xs ys in - (*merge a pair of adjacent partitions*) - let merge_pair ik (a,b) (c,d) = - let new_a = function - | None -> Some (upper_threshold b, upper_threshold b) - | Some (ax,ay) -> Some (ax, upper_threshold b) - in - let new_c = function - | None -> Some (lower_threshold d, lower_threshold d) - | Some (cx,cy) -> Some (lower_threshold d, cy) - in - if threshold && (lower_threshold d +. Ints_t.one) >. (upper_threshold b) then - [(new_a a,(fst b, upper_threshold b)); (new_c c, (lower_threshold d, snd d))] - else - [(Interval.join ik a c, (Interval.join ik (Some b) (Some d) |> Option.get))] - in - let partitions_are_approaching part_left part_right = match part_left, part_right with - | (Some (_, left_x), (_, left_y)), (Some (right_x, _), (right_y, _)) -> (right_x -. left_x) >. (right_y -. left_y) - | _,_ -> false - in - (*merge all approaching pairs of adjacent partitions*) - let rec merge_list ik = function - | [] -> [] - | x::y::xs when partitions_are_approaching x y -> merge_list ik ((merge_pair ik x y) @ xs) - | x::xs -> x :: merge_list ik xs - in - (*expands left extremity*) - let widen_left = function - | [] -> [] - | (None,(lb,rb))::ts -> let lt = if threshold then lower_threshold (lb,lb) else min_ik in (None, (lt,rb))::ts - | (Some (la,ra), (lb,rb))::ts when lb <. la -> let lt = if threshold then lower_threshold (lb,lb) else min_ik in (Some (la,ra),(lt,rb))::ts - | x -> x - in - (*expands right extremity*) - let widen_right x = - let map_rightmost = function - | [] -> [] - | (None,(lb,rb))::ts -> let ut = if threshold then upper_threshold (rb,rb) else max_ik in (None, (lb,ut))::ts - | (Some (la,ra), (lb,rb))::ts when ra <. rb -> let ut = if threshold then upper_threshold (rb,rb) else max_ik in (Some (la,ra),(lb,ut))::ts - | x -> x - in - List.rev x |> map_rightmost |> List.rev - in - interval_sets_to_partitions ik xs ys |> merge_list ik |> widen_left |> widen_right |> List.map snd - - let starting ?(suppress_ovwarn=false) ik n = norm_interval ik ~suppress_ovwarn (n, snd (range ik)) - - let ending ?(suppress_ovwarn=false) ik n = norm_interval ik ~suppress_ovwarn (fst (range ik), n) - - let invariant_ikind e ik xs = - List.map (fun x -> Interval.invariant_ikind e ik (Some x)) xs |> - let open Invariant in List.fold_left (||) (bot ()) - - let modulo n k = - let result = Ints_t.rem n k in - if result >=. Ints_t.zero then result - else result +. k - - let refine_with_congruence ik (intvs: t) (cong: (int_t * int_t ) option): t = - let refine_with_congruence_interval ik (cong : (int_t * int_t ) option) (intv : (int_t * int_t ) option): t = - match intv, cong with - | Some (x, y), Some (c, m) -> - if m =. Ints_t.zero && (c <. x || c >. y) then [] - else if m =. Ints_t.zero then - [(c, c)] - else - let (min_ik, max_ik) = range ik in - let rcx = - if x =. min_ik then x else - x +. (modulo (c -. x) (Ints_t.abs m)) in - let lcy = - if y =. max_ik then y else - y -. (modulo (y -. c) (Ints_t.abs m)) in - if rcx >. lcy then [] - else if rcx =. lcy then norm_interval ik (rcx, rcx) |> fst - else norm_interval ik (rcx, lcy) |> fst - | _ -> [] - in - List.concat_map (fun x -> refine_with_congruence_interval ik cong (Some x)) intvs - - let refine_with_interval ik xs = function None -> [] | Some (a,b) -> meet ik xs [(a,b)] - - let refine_with_incl_list ik intvs = function - | None -> intvs - | Some xs -> meet ik intvs (List.map (fun x -> (x,x)) xs) - - let excl_range_to_intervalset (ik: ikind) ((min, max): int_t * int_t) (excl: int_t): t = - let intv1 = (min, excl -. Ints_t.one) in - let intv2 = (excl +. Ints_t.one, max) in - norm_intvs ik ~suppress_ovwarn:true [intv1 ; intv2] |> fst - - let of_excl_list ik (excls: int_t list) = - let excl_list = List.map (excl_range_to_intervalset ik (range ik)) excls in - let res = List.fold_left (meet ik) (top_of ik) excl_list in - res - - let refine_with_excl_list ik (intv : t) = function - | None -> intv - | Some (xs, range) -> - let excl_to_intervalset (ik: ikind) ((rl, rh): (int64 * int64)) (excl: int_t): t = - excl_range_to_intervalset ik (Ints_t.of_bigint (Size.min_from_bit_range rl),Ints_t.of_bigint (Size.max_from_bit_range rh)) excl - in - let excl_list = List.map (excl_to_intervalset ik range) xs in - List.fold_left (meet ik) intv excl_list - - let project ik p t = t - - let arbitrary ik = - let open QCheck.Iter in - (* let int_arb = QCheck.map ~rev:Ints_t.to_bigint Ints_t.of_bigint GobQCheck.Arbitrary.big_int in *) - (* TODO: apparently bigints are really slow compared to int64 for domaintest *) - let int_arb = QCheck.map ~rev:Ints_t.to_int64 Ints_t.of_int64 GobQCheck.Arbitrary.int64 in - let pair_arb = QCheck.pair int_arb int_arb in - let list_pair_arb = QCheck.small_list pair_arb in - let canonize_randomly_generated_list = (fun x -> norm_intvs ik x |> fst) in - let shrink xs = GobQCheck.shrink list_pair_arb xs >|= canonize_randomly_generated_list - in QCheck.(set_shrink shrink @@ set_print show @@ map (*~rev:BatOption.get*) canonize_randomly_generated_list list_pair_arb) -end - -module SOverflowUnlifter (D : SOverflow) : S with type int_t = D.int_t and type t = D.t = struct - include D - - let add ?no_ov ik x y = fst @@ D.add ?no_ov ik x y - - let sub ?no_ov ik x y = fst @@ D.sub ?no_ov ik x y - - let mul ?no_ov ik x y = fst @@ D.mul ?no_ov ik x y - - let div ?no_ov ik x y = fst @@ D.div ?no_ov ik x y - - let neg ?no_ov ik x = fst @@ D.neg ?no_ov ik x - - let cast_to ?suppress_ovwarn ?torg ?no_ov ik x = fst @@ D.cast_to ?suppress_ovwarn ?torg ?no_ov ik x - - let of_int ik x = fst @@ D.of_int ik x - - let of_interval ?suppress_ovwarn ik x = fst @@ D.of_interval ?suppress_ovwarn ik x - - let starting ?suppress_ovwarn ik x = fst @@ D.starting ?suppress_ovwarn ik x - - let ending ?suppress_ovwarn ik x = fst @@ D.ending ?suppress_ovwarn ik x - - let shift_left ik x y = fst @@ D.shift_left ik x y - - let shift_right ik x y = fst @@ D.shift_right ik x y -end - -module IntIkind = struct let ikind () = Cil.IInt end -module Interval = IntervalFunctor (IntOps.BigIntOps) -module Interval32 = IntDomWithDefaultIkind (IntDomLifter (SOverflowUnlifter (IntervalFunctor (IntOps.Int64Ops)))) (IntIkind) -module IntervalSet = IntervalSetFunctor (IntOps.BigIntOps) -module Integers (Ints_t : IntOps.IntOps): IkindUnawareS with type t = Ints_t.t and type int_t = Ints_t.t = (* no top/bot, order is <= *) -struct - include Printable.Std - let name () = "integers" - type t = Ints_t.t [@@deriving eq, ord, hash] - type int_t = Ints_t.t - let top () = raise Unknown - let bot () = raise Error - let top_of ik = top () - let bot_of ik = bot () - let show (x: Ints_t.t) = Ints_t.to_string x - - include Std (struct type nonrec t = t let name = name let top_of = top_of let bot_of = bot_of let show = show let equal = equal end) - (* is_top and is_bot are never called, but if they were, the Std impl would raise their exception, so we overwrite them: *) - let is_top _ = false - let is_bot _ = false - - let equal_to i x = if i > x then `Neq else `Top - let leq x y = x <= y - let join x y = if Ints_t.compare x y > 0 then x else y - let widen = join - let meet x y = if Ints_t.compare x y > 0 then y else x - let narrow = meet - - let of_bool x = if x then Ints_t.one else Ints_t.zero - let to_bool' x = x <> Ints_t.zero - let to_bool x = Some (to_bool' x) - let of_int x = x - let to_int x = Some x - - let neg = Ints_t.neg - let add = Ints_t.add (* TODO: signed overflow is undefined behavior! *) - let sub = Ints_t.sub - let mul = Ints_t.mul - let div = Ints_t.div - let rem = Ints_t.rem - let lt n1 n2 = of_bool (n1 < n2) - let gt n1 n2 = of_bool (n1 > n2) - let le n1 n2 = of_bool (n1 <= n2) - let ge n1 n2 = of_bool (n1 >= n2) - let eq n1 n2 = of_bool (n1 = n2) - let ne n1 n2 = of_bool (n1 <> n2) - let lognot = Ints_t.lognot - let logand = Ints_t.logand - let logor = Ints_t.logor - let logxor = Ints_t.logxor - let shift_left n1 n2 = Ints_t.shift_left n1 (Ints_t.to_int n2) - let shift_right n1 n2 = Ints_t.shift_right n1 (Ints_t.to_int n2) - let c_lognot n1 = of_bool (not (to_bool' n1)) - let c_logand n1 n2 = of_bool ((to_bool' n1) && (to_bool' n2)) - let c_logor n1 n2 = of_bool ((to_bool' n1) || (to_bool' n2)) - let cast_to ?(suppress_ovwarn=false) ?torg t x = failwith @@ "Cast_to not implemented for " ^ (name ()) ^ "." - let arbitrary ik = QCheck.map ~rev:Ints_t.to_int64 Ints_t.of_int64 GobQCheck.Arbitrary.int64 (* TODO: use ikind *) - let invariant _ _ = Invariant.none (* TODO *) -end - -module FlatPureIntegers: IkindUnawareS with type t = int64 and type int_t = int64 = (* Integers, but raises Unknown/Error on join/meet *) -struct - include Integers(IntOps.Int64Ops) - let top () = raise Unknown - let bot () = raise Error - let leq = equal - let pretty_diff () (x,y) = Pretty.dprintf "Integer %a instead of %a" pretty x pretty y - let join x y = if equal x y then x else top () - let meet x y = if equal x y then x else bot () -end - -module Flat (Base: IkindUnawareS) = (* identical to Lift, but goes to `Top/`Bot if Base raises Unknown/Error *) -struct - type int_t = Base.int_t - include Lattice.FlatConf (struct - include Printable.DefaultConf - let top_name = "Unknown int" - let bot_name = "Error int" - end) (Base) - - let top_of ik = top () - let bot_of ik = bot () - - - let name () = "flat integers" - let cast_to ?(suppress_ovwarn=false) ?torg t = function - | `Lifted x -> `Lifted (Base.cast_to t x) - | x -> x - - let equal_to i = function - | `Bot -> failwith "unsupported: equal_to with bottom" - | `Top -> `Top - | `Lifted x -> Base.equal_to i x - - let of_int x = `Lifted (Base.of_int x) - let to_int x = match x with - | `Lifted x -> Base.to_int x - | _ -> None - - let of_bool x = `Lifted (Base.of_bool x) - let to_bool x = match x with - | `Lifted x -> Base.to_bool x - | _ -> None - - let to_excl_list x = None - let of_excl_list ik x = top_of ik - let is_excl_list x = false - let to_incl_list x = None - let of_interval ?(suppress_ovwarn=false) ik x = top_of ik - let of_congruence ik x = top_of ik - let starting ?(suppress_ovwarn=false) ikind x = top_of ikind - let ending ?(suppress_ovwarn=false) ikind x = top_of ikind - let maximal x = None - let minimal x = None - - let lift1 f x = match x with - | `Lifted x -> - (try `Lifted (f x) with Unknown -> `Top | Error -> `Bot) - | x -> x - let lift2 f x y = match x,y with - | `Lifted x, `Lifted y -> - (try `Lifted (f x y) with Unknown -> `Top | Error -> `Bot) - | `Bot, `Bot -> `Bot - | _ -> `Top - - let neg = lift1 Base.neg - let add = lift2 Base.add - let sub = lift2 Base.sub - let mul = lift2 Base.mul - let div = lift2 Base.div - let rem = lift2 Base.rem - let lt = lift2 Base.lt - let gt = lift2 Base.gt - let le = lift2 Base.le - let ge = lift2 Base.ge - let eq = lift2 Base.eq - let ne = lift2 Base.ne - let lognot = lift1 Base.lognot - let logand = lift2 Base.logand - let logor = lift2 Base.logor - let logxor = lift2 Base.logxor - let shift_left = lift2 Base.shift_left - let shift_right = lift2 Base.shift_right - let c_lognot = lift1 Base.c_lognot - let c_logand = lift2 Base.c_logand - let c_logor = lift2 Base.c_logor - - let invariant e = function - | `Lifted x -> Base.invariant e x - | `Top | `Bot -> Invariant.none -end - -module Lift (Base: IkindUnawareS) = (* identical to Flat, but does not go to `Top/Bot` if Base raises Unknown/Error *) -struct - include Lattice.LiftPO (struct - include Printable.DefaultConf - let top_name = "MaxInt" - let bot_name = "MinInt" - end) (Base) - type int_t = Base.int_t - let top_of ik = top () - let bot_of ik = bot () - include StdTop (struct type nonrec t = t let top_of = top_of end) - - let name () = "lifted integers" - let cast_to ?(suppress_ovwarn=false) ?torg t = function - | `Lifted x -> `Lifted (Base.cast_to t x) - | x -> x - - let equal_to i = function - | `Bot -> failwith "unsupported: equal_to with bottom" - | `Top -> `Top - | `Lifted x -> Base.equal_to i x - - let of_int x = `Lifted (Base.of_int x) - let to_int x = match x with - | `Lifted x -> Base.to_int x - | _ -> None - - let of_bool x = `Lifted (Base.of_bool x) - let to_bool x = match x with - | `Lifted x -> Base.to_bool x - | _ -> None - - let lift1 f x = match x with - | `Lifted x -> `Lifted (f x) - | x -> x - let lift2 f x y = match x,y with - | `Lifted x, `Lifted y -> `Lifted (f x y) - | `Bot, `Bot -> `Bot - | _ -> `Top - - let neg = lift1 Base.neg - let add = lift2 Base.add - let sub = lift2 Base.sub - let mul = lift2 Base.mul - let div = lift2 Base.div - let rem = lift2 Base.rem - let lt = lift2 Base.lt - let gt = lift2 Base.gt - let le = lift2 Base.le - let ge = lift2 Base.ge - let eq = lift2 Base.eq - let ne = lift2 Base.ne - let lognot = lift1 Base.lognot - let logand = lift2 Base.logand - let logor = lift2 Base.logor - let logxor = lift2 Base.logxor - let shift_left = lift2 Base.shift_left - let shift_right = lift2 Base.shift_right - let c_lognot = lift1 Base.c_lognot - let c_logand = lift2 Base.c_logand - let c_logor = lift2 Base.c_logor - - let invariant e = function - | `Lifted x -> Base.invariant e x - | `Top | `Bot -> Invariant.none -end - -module Flattened = Flat (Integers (IntOps.Int64Ops)) -module Lifted = Lift (Integers (IntOps.Int64Ops)) - -module Reverse (Base: IkindUnawareS) = -struct - include Base - include (Lattice.Reverse (Base) : Lattice.S with type t := Base.t) -end - -module BISet = struct - include SetDomain.Make (IntOps.BigIntOps) - let is_singleton s = cardinal s = 1 -end - -(* The module [Exclusion] constains common functionality about handling of exclusion sets between [DefExc] and [Enums] *) -module Exclusion = -struct - module R = Interval32 - (* We use these types for the functions in this module to make the intended meaning more explicit *) - type t = Exc of BISet.t * Interval32.t - type inc = Inc of BISet.t [@@unboxed] - let max_of_range r = Size.max_from_bit_range (Option.get (R.maximal r)) - let min_of_range r = Size.min_from_bit_range (Option.get (R.minimal r)) - let cardinality_of_range r = Z.succ (Z.add (Z.neg (min_of_range r)) (max_of_range r)) - - let cardinality_BISet s = - Z.of_int (BISet.cardinal s) - - let leq_excl_incl (Exc (xs, r)) (Inc ys) = - (* For a <= b to hold, the cardinalities must fit, i.e. |a| <= |b|, which implies |min_r, max_r| - |xs| <= |ys|. We check this first. *) - let lower_bound_cardinality_a = Z.sub (cardinality_of_range r) (cardinality_BISet xs) in - let card_b = cardinality_BISet ys in - if Z.compare lower_bound_cardinality_a card_b > 0 then - false - else (* The cardinality did fit, so we check for all elements that are represented by range r, whether they are in (xs union ys) *) - let min_a = min_of_range r in - let max_a = max_of_range r in - GobZ.for_all_range (fun el -> BISet.mem el xs || BISet.mem el ys) (min_a, max_a) - - let leq (Exc (xs, r)) (Exc (ys, s)) = - let min_a, max_a = min_of_range r, max_of_range r in - let excluded_check = BISet.for_all (fun y -> BISet.mem y xs || Z.compare y min_a < 0 || Z.compare y max_a > 0) ys in (* if true, then the values ys, that are not in b, also do not occur in a *) - if not excluded_check - then false - else begin (* Check whether all elements that are in the range r, but not in s, are in xs, i.e. excluded. *) - if R.leq r s then true - else begin if Z.compare (cardinality_BISet xs) (Z.sub (cardinality_of_range r) (cardinality_of_range s)) >= 0 (* Check whether the number of excluded elements in a is as least as big as |min_r, max_r| - |min_s, max_s| *) - then - let min_b, max_b = min_of_range s, max_of_range s in - let leq1 = (* check whether the elements in [r_l; s_l-1] are all in xs, i.e. excluded *) - if Z.compare min_a min_b < 0 then - GobZ.for_all_range (fun x -> BISet.mem x xs) (min_a, Z.pred min_b) - else - true - in - let leq2 () = (* check whether the elements in [s_u+1; r_u] are all in xs, i.e. excluded *) - if Z.compare max_b max_a < 0 then - GobZ.for_all_range (fun x -> BISet.mem x xs) (Z.succ max_b, max_a) - else - true - in - leq1 && (leq2 ()) - else - false - end - end -end - -module DefExc : S with type int_t = Z.t = (* definite or set of excluded values *) -struct - module S = BISet - module R = Interval32 (* range for exclusion *) - - (* Ikind used for intervals representing the domain *) - let range_ikind = Cil.IInt - let size t = R.of_interval range_ikind (let a,b = Size.bits_i64 t in Int64.neg a,b) - - - type t = [ - | `Excluded of S.t * R.t - | `Definite of Z.t - | `Bot - ] [@@deriving eq, ord, hash] - type int_t = Z.t - let name () = "def_exc" - - - let top_range = R.of_interval range_ikind (-99L, 99L) (* Since there is no top ikind we use a range that includes both ILongLong [-63,63] and IULongLong [0,64]. Only needed for intermediate range computation on longs. Correct range is set by cast. *) - let top () = `Excluded (S.empty (), top_range) - let bot () = `Bot - let top_of ik = `Excluded (S.empty (), size ik) - let bot_of ik = bot () - - let show x = - let short_size x = "("^R.show x^")" in - match x with - | `Bot -> "Error int" - | `Definite x -> Z.to_string x - (* Print the empty exclusion as if it was a distinct top element: *) - | `Excluded (s,l) when S.is_empty s -> "Unknown int" ^ short_size l - (* Prepend the exclusion sets with something: *) - | `Excluded (s,l) -> "Not " ^ S.show s ^ short_size l - - include Std (struct type nonrec t = t let name = name let top_of = top_of let bot_of = bot_of let show = show let equal = equal end) - - let maximal = function - | `Definite x -> Some x - | `Excluded (s,r) -> Some (Exclusion.max_of_range r) - | `Bot -> None - - let minimal = function - | `Definite x -> Some x - | `Excluded (s,r) -> Some (Exclusion.min_of_range r) - | `Bot -> None - - let in_range r i = - if Z.compare i Z.zero < 0 then - let lowerb = Exclusion.min_of_range r in - Z.compare lowerb i <= 0 - else - let upperb = Exclusion.max_of_range r in - Z.compare i upperb <= 0 - - let is_top x = x = top () - - let equal_to i = function - | `Bot -> failwith "unsupported: equal_to with bottom" - | `Definite x -> if i = x then `Eq else `Neq - | `Excluded (s,r) -> if S.mem i s then `Neq else `Top - - let cast_to ?(suppress_ovwarn=false) ?torg ?no_ov ik = function - | `Excluded (s,r) -> - let r' = size ik in - if R.leq r r' then (* upcast -> no change *) - `Excluded (s, r) - else if ik = IBool then (* downcast to bool *) - if S.mem Z.zero s then - `Definite Z.one - else - `Excluded (S.empty(), r') - else - (* downcast: may overflow *) - (* let s' = S.map (Size.cast ik) s in *) - (* We want to filter out all i in s' where (t)x with x in r could be i. *) - (* Since this is hard to compute, we just keep all i in s' which overflowed, since those are safe - all i which did not overflow may now be possible due to overflow of r. *) - (* S.diff s' s, r' *) - (* The above is needed for test 21/03, but not sound! See example https://github.com/goblint/analyzer/pull/95#discussion_r483023140 *) - `Excluded (S.empty (), r') - | `Definite x -> `Definite (Size.cast ik x) - | `Bot -> `Bot - - (* Wraps definite values and excluded values according to the ikind. - * For an `Excluded s,r , assumes that r is already an overapproximation of the range of possible values. - * r might be larger than the possible range of this type; the range of the returned `Excluded set will be within the bounds of the ikind. - *) - let norm ik v = - match v with - | `Excluded (s, r) -> - let possibly_overflowed = not (R.leq r (size ik)) || not (S.for_all (in_range (size ik)) s) in - (* If no overflow occurred, just return x *) - if not possibly_overflowed then ( - v - ) - (* Else, if an overflow might have occurred but we should just ignore it *) - else if should_ignore_overflow ik then ( - let r = size ik in - (* filter out excluded elements that are not in the range *) - let mapped_excl = S.filter (in_range r) s in - `Excluded (mapped_excl, r) - ) - (* Else, if an overflow occurred that we should not treat with wrap-around, go to top *) - else if not (should_wrap ik) then ( - top_of ik - ) else ( - (* Else an overflow occurred that we should treat with wrap-around *) - let r = size ik in - (* Perform a wrap-around for unsigned values and for signed values (if configured). *) - let mapped_excl = S.map (fun excl -> Size.cast ik excl) s in - match ik with - | IBool -> - begin match S.mem Z.zero mapped_excl, S.mem Z.one mapped_excl with - | false, false -> `Excluded (mapped_excl, r) (* Not {} -> Not {} *) - | true, false -> `Definite Z.one (* Not {0} -> 1 *) - | false, true -> `Definite Z.zero (* Not {1} -> 0 *) - | true, true -> `Bot (* Not {0, 1} -> bot *) - end - | ik -> - `Excluded (mapped_excl, r) - ) - | `Definite x -> - let min, max = Size.range ik in - (* Perform a wrap-around for unsigned values and for signed values (if configured). *) - if should_wrap ik then ( - cast_to ik v - ) - else if Z.compare min x <= 0 && Z.compare x max <= 0 then ( - v - ) - else if should_ignore_overflow ik then ( - M.warn ~category:M.Category.Integer.overflow "DefExc: Value was outside of range, indicating overflow, but 'sem.int.signed_overflow' is 'assume_none' -> Returned Bot"; - `Bot - ) - else ( - top_of ik - ) - | `Bot -> `Bot - - let leq x y = match (x,y) with - (* `Bot <= x is always true *) - | `Bot, _ -> true - (* Anything except bot <= bot is always false *) - | _, `Bot -> false - (* Two known values are leq whenever equal *) - | `Definite (x: int_t), `Definite y -> x = y - (* A definite value is leq all exclusion sets that don't contain it *) - | `Definite x, `Excluded (s,r) -> in_range r x && not (S.mem x s) - (* No finite exclusion set can be leq than a definite value *) - | `Excluded (xs, xr), `Definite d -> - Exclusion.(leq_excl_incl (Exc (xs, xr)) (Inc (S.singleton d))) - | `Excluded (xs,xr), `Excluded (ys,yr) -> - Exclusion.(leq (Exc (xs,xr)) (Exc (ys, yr))) - - let join' ?range ik x y = - match (x,y) with - (* The least upper bound with the bottom element: *) - | `Bot, x -> x - | x, `Bot -> x - (* The case for two known values: *) - | `Definite (x: int_t), `Definite y -> - (* If they're equal, it's just THAT value *) - if x = y then `Definite x - (* Unless one of them is zero, we can exclude it: *) - else - let a,b = Size.min_range_sign_agnostic x, Size.min_range_sign_agnostic y in - let r = R.join (R.of_interval range_ikind a) (R.of_interval range_ikind b) in - `Excluded ((if Z.equal x Z.zero || Z.equal y Z.zero then S.empty () else S.singleton Z.zero), r) - (* A known value and an exclusion set... the definite value should no - * longer be excluded: *) - | `Excluded (s,r), `Definite x - | `Definite x, `Excluded (s,r) -> - if not (in_range r x) then - let a = R.of_interval range_ikind (Size.min_range_sign_agnostic x) in - `Excluded (S.remove x s, R.join a r) - else - `Excluded (S.remove x s, r) - (* For two exclusion sets, only their intersection can be excluded: *) - | `Excluded (x,wx), `Excluded (y,wy) -> `Excluded (S.inter x y, range |? R.join wx wy) - - let join ik = join' ik - - - let widen ik x y = - if get_def_exc_widen_by_join () then - join' ik x y - else if equal x y then - x - else - join' ~range:(size ik) ik x y - - - let meet ik x y = - match (x,y) with - (* Greatest LOWER bound with the least element is trivial: *) - | `Bot, _ -> `Bot - | _, `Bot -> `Bot - (* Definite elements are either equal or the glb is bottom *) - | `Definite x, `Definite y -> if x = y then `Definite x else `Bot - (* The glb of a definite element and an exclusion set is either bottom or - * just the element itself, if it isn't in the exclusion set *) - | `Excluded (s,r), `Definite x - | `Definite x, `Excluded (s,r) -> if S.mem x s || not (in_range r x) then `Bot else `Definite x - (* The greatest lower bound of two exclusion sets is their union, this is - * just DeMorgans Law *) - | `Excluded (x,r1), `Excluded (y,r2) -> - let r' = R.meet r1 r2 in - let s' = S.union x y |> S.filter (in_range r') in - `Excluded (s', r') - - let narrow ik x y = x - - let of_int ik x = norm ik @@ `Definite x - let to_int x = match x with - | `Definite x -> Some x - | _ -> None - - let from_excl ikind (s: S.t) = norm ikind @@ `Excluded (s, size ikind) - - let of_bool_cmp ik x = of_int ik (if x then Z.one else Z.zero) - let of_bool = of_bool_cmp - let to_bool x = - match x with - | `Definite x -> Some (IntOps.BigIntOps.to_bool x) - | `Excluded (s,r) when S.mem Z.zero s -> Some true - | _ -> None - let top_bool = `Excluded (S.empty (), R.of_interval range_ikind (0L, 1L)) - - let of_interval ?(suppress_ovwarn=false) ik (x,y) = - if Z.compare x y = 0 then - of_int ik x - else - let a, b = Size.min_range_sign_agnostic x, Size.min_range_sign_agnostic y in - let r = R.join (R.of_interval ~suppress_ovwarn range_ikind a) (R.of_interval ~suppress_ovwarn range_ikind b) in - let ex = if Z.gt x Z.zero || Z.lt y Z.zero then S.singleton Z.zero else S.empty () in - norm ik @@ (`Excluded (ex, r)) - - let starting ?(suppress_ovwarn=false) ikind x = - let _,u_ik = Size.range ikind in - of_interval ~suppress_ovwarn ikind (x, u_ik) - - let ending ?(suppress_ovwarn=false) ikind x = - let l_ik,_ = Size.range ikind in - of_interval ~suppress_ovwarn ikind (l_ik, x) - - let of_excl_list t l = - let r = size t in (* elements in l are excluded from the full range of t! *) - `Excluded (List.fold_right S.add l (S.empty ()), r) - let is_excl_list l = match l with `Excluded _ -> true | _ -> false - let to_excl_list (x:t) = match x with - | `Definite _ -> None - | `Excluded (s,r) -> Some (S.elements s, (Option.get (R.minimal r), Option.get (R.maximal r))) - | `Bot -> None - - let to_incl_list x = match x with - | `Definite x -> Some [x] - | `Excluded _ -> None - | `Bot -> None - - let apply_range f r = (* apply f to the min/max of the old range r to get a new range *) - (* If the Int64 might overflow on us during computation, we instead go to top_range *) - match R.minimal r, R.maximal r with - | _ -> - let rf m = (size % Size.min_for % f) (m r) in - let r1, r2 = rf Exclusion.min_of_range, rf Exclusion.max_of_range in - R.join r1 r2 - - (* Default behaviour for unary operators, simply maps the function to the - * DefExc data structure. *) - let lift1 f ik x = norm ik @@ match x with - | `Excluded (s,r) -> - let s' = S.map f s in - `Excluded (s', apply_range f r) - | `Definite x -> `Definite (f x) - | `Bot -> `Bot - - let lift2 f ik x y = norm ik (match x,y with - (* We don't bother with exclusion sets: *) - | `Excluded _, `Definite _ - | `Definite _, `Excluded _ - | `Excluded _, `Excluded _ -> top () - (* The good case: *) - | `Definite x, `Definite y -> - (try `Definite (f x y) with | Division_by_zero -> top ()) - | `Bot, `Bot -> `Bot - | _ -> - (* If only one of them is bottom, we raise an exception that eval_rv will catch *) - raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y)))) - - (* Default behaviour for binary operators that are injective in either - * argument, so that Exclusion Sets can be used: *) - let lift2_inj f ik x y = - let def_exc f x s r = `Excluded (S.map (f x) s, apply_range (f x) r) in - norm ik @@ - match x,y with - (* If both are exclusion sets, there isn't anything we can do: *) - | `Excluded _, `Excluded _ -> top () - (* A definite value should be applied to all members of the exclusion set *) - | `Definite x, `Excluded (s,r) -> def_exc f x s r - (* Same thing here, but we should flip the operator to map it properly *) - | `Excluded (s,r), `Definite x -> def_exc (Batteries.flip f) x s r - (* The good case: *) - | `Definite x, `Definite y -> `Definite (f x y) - | `Bot, `Bot -> `Bot - | _ -> - (* If only one of them is bottom, we raise an exception that eval_rv will catch *) - raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) - - (* The equality check: *) - let eq ik x y = match x,y with - (* Not much to do with two exclusion sets: *) - | `Excluded _, `Excluded _ -> top () - (* Is x equal to an exclusion set, if it is a member then NO otherwise we - * don't know: *) - | `Definite x, `Excluded (s,r) -> if S.mem x s then of_bool IInt false else top () - | `Excluded (s,r), `Definite x -> if S.mem x s then of_bool IInt false else top () - (* The good case: *) - | `Definite x, `Definite y -> of_bool IInt (x = y) - | `Bot, `Bot -> `Bot - | _ -> - (* If only one of them is bottom, we raise an exception that eval_rv will catch *) - raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) - - (* The inequality check: *) - let ne ik x y = match x,y with - (* Not much to do with two exclusion sets: *) - | `Excluded _, `Excluded _ -> top () - (* Is x unequal to an exclusion set, if it is a member then Yes otherwise we - * don't know: *) - | `Definite x, `Excluded (s,r) -> if S.mem x s then of_bool IInt true else top () - | `Excluded (s,r), `Definite x -> if S.mem x s then of_bool IInt true else top () - (* The good case: *) - | `Definite x, `Definite y -> of_bool IInt (x <> y) - | `Bot, `Bot -> `Bot - | _ -> - (* If only one of them is bottom, we raise an exception that eval_rv will catch *) - raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) - - let neg ?no_ov ik (x :t) = norm ik @@ lift1 Z.neg ik x - let add ?no_ov ik x y = norm ik @@ lift2_inj Z.add ik x y - - let sub ?no_ov ik x y = norm ik @@ lift2_inj Z.sub ik x y - let mul ?no_ov ik x y = norm ik @@ match x, y with - | `Definite z, (`Excluded _ | `Definite _) when Z.equal z Z.zero -> x - | (`Excluded _ | `Definite _), `Definite z when Z.equal z Z.zero -> y - | `Definite a, `Excluded (s,r) - (* Integer multiplication with even numbers is not injective. *) - (* Thus we cannot exclude the values to which the exclusion set would be mapped to. *) - | `Excluded (s,r),`Definite a when Z.equal (Z.rem a (Z.of_int 2)) Z.zero -> `Excluded (S.empty (), apply_range (Z.mul a) r) - | _ -> lift2_inj Z.mul ik x y - let div ?no_ov ik x y = lift2 Z.div ik x y - let rem ik x y = lift2 Z.rem ik x y - - (* Comparison handling copied from Enums. *) - let handle_bot x y f = match x, y with - | `Bot, `Bot -> `Bot - | `Bot, _ - | _, `Bot -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) - | _, _ -> f () - - let lt ik x y = - handle_bot x y (fun () -> - match minimal x, maximal x, minimal y, maximal y with - | _, Some x2, Some y1, _ when Z.compare x2 y1 < 0 -> of_bool ik true - | Some x1, _, _, Some y2 when Z.compare x1 y2 >= 0 -> of_bool ik false - | _, _, _, _ -> top_bool) - - let gt ik x y = lt ik y x - - let le ik x y = - handle_bot x y (fun () -> - match minimal x, maximal x, minimal y, maximal y with - | _, Some x2, Some y1, _ when Z.compare x2 y1 <= 0 -> of_bool ik true - | Some x1, _, _, Some y2 when Z.compare x1 y2 > 0 -> of_bool ik false - | _, _, _, _ -> top_bool) - - let ge ik x y = le ik y x - - let lognot = lift1 Z.lognot - - let logand ik x y = norm ik (match x,y with - (* We don't bother with exclusion sets: *) - | `Excluded _, `Definite i -> - (* Except in two special cases *) - if Z.equal i Z.zero then - `Definite Z.zero - else if Z.equal i Z.one then - of_interval IBool (Z.zero, Z.one) - else - top () - | `Definite _, `Excluded _ - | `Excluded _, `Excluded _ -> top () - (* The good case: *) - | `Definite x, `Definite y -> - (try `Definite (Z.logand x y) with | Division_by_zero -> top ()) - | `Bot, `Bot -> `Bot - | _ -> - (* If only one of them is bottom, we raise an exception that eval_rv will catch *) - raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y)))) - - - let logor = lift2 Z.logor - let logxor = lift2 Z.logxor - - let shift (shift_op: int_t -> int -> int_t) (ik: Cil.ikind) (x: t) (y: t) = - (* BigInt only accepts int as second argument for shifts; perform conversion here *) - let shift_op_big_int a (b: int_t) = - let (b : int) = Z.to_int b in - shift_op a b - in - (* If one of the parameters of the shift is negative, the result is undefined *) - let x_min = minimal x in - let y_min = minimal y in - if x_min = None || y_min = None || Z.compare (Option.get x_min) Z.zero < 0 || Z.compare (Option.get y_min) Z.zero < 0 then - top_of ik - else - norm ik @@ lift2 shift_op_big_int ik x y - - let shift_left = - shift Z.shift_left - - let shift_right = - shift Z.shift_right - (* TODO: lift does not treat Not {0} as true. *) - let c_logand ik x y = - match to_bool x, to_bool y with - | Some false, _ - | _, Some false -> - of_bool ik false - | _, _ -> - lift2 IntOps.BigIntOps.c_logand ik x y - let c_logor ik x y = - match to_bool x, to_bool y with - | Some true, _ - | _, Some true -> - of_bool ik true - | _, _ -> - lift2 IntOps.BigIntOps.c_logor ik x y - let c_lognot ik = eq ik (of_int ik Z.zero) - - let invariant_ikind e ik (x:t) = - match x with - | `Definite x -> - if get_bool "witness.invariant.exact" then - Invariant.of_exp Cil.(BinOp (Eq, e, kintegerCilint ik x, intType)) - else - Invariant.top () - | `Excluded (s, r) -> - (* Emit range invariant if tighter than ikind bounds. - This can be more precise than interval, which has been widened. *) - let (rmin, rmax) = (Exclusion.min_of_range r, Exclusion.max_of_range r) in - let (ikmin, ikmax) = - let ikr = size ik in - (Exclusion.min_of_range ikr, Exclusion.max_of_range ikr) - in - let inexact_type_bounds = get_bool "witness.invariant.inexact-type-bounds" in - let imin = if inexact_type_bounds || Z.compare ikmin rmin <> 0 then Invariant.of_exp Cil.(BinOp (Le, kintegerCilint ik rmin, e, intType)) else Invariant.none in - let imax = if inexact_type_bounds || Z.compare rmax ikmax <> 0 then Invariant.of_exp Cil.(BinOp (Le, e, kintegerCilint ik rmax, intType)) else Invariant.none in - S.fold (fun x a -> - let i = Invariant.of_exp Cil.(BinOp (Ne, e, kintegerCilint ik x, intType)) in - Invariant.(a && i) - ) s Invariant.(imin && imax) - | `Bot -> Invariant.none - - let arbitrary ik = - let open QCheck.Iter in - let excluded s = from_excl ik s in - let definite x = of_int ik x in - let shrink = function - | `Excluded (s, _) -> GobQCheck.shrink (S.arbitrary ()) s >|= excluded (* S TODO: possibly shrink excluded to definite *) - | `Definite x -> (return `Bot) <+> (GobQCheck.shrink (IntOps.BigIntOps.arbitrary ()) x >|= definite) - | `Bot -> empty - in - QCheck.frequency ~shrink ~print:show [ - 20, QCheck.map excluded (S.arbitrary ()); - 10, QCheck.map definite (IntOps.BigIntOps.arbitrary ()); - 1, QCheck.always `Bot - ] (* S TODO: decide frequencies *) - - let refine_with_congruence ik a b = a - let refine_with_interval ik a b = match a, b with - | x, Some(i) -> meet ik x (of_interval ik i) - | _ -> a - let refine_with_excl_list ik a b = match a, b with - | `Excluded (s, r), Some(ls, _) -> meet ik (`Excluded (s, r)) (of_excl_list ik ls) (* TODO: refine with excl range? *) - | _ -> a - let refine_with_incl_list ik a b = a - - let project ik p t = t -end - -(* BOOLEAN DOMAINS *) - -module type BooleansNames = -sig - val truename: string - val falsename: string -end - -module MakeBooleans (N: BooleansNames) = -struct - type int_t = IntOps.Int64Ops.t - type t = bool [@@deriving eq, ord, hash, to_yojson] - let name () = "booleans" - let top () = true - let bot () = false - let top_of ik = top () - let bot_of ik = bot () - let show x = if x then N.truename else N.falsename - include Std (struct type nonrec t = t let name = name let top_of = top_of let bot_of = bot_of let show = show let equal = equal end) - let is_top x = x (* override Std *) - - let equal_to i x = if x then `Top else failwith "unsupported: equal_to with bottom" - let cast_to ?(suppress_ovwarn=false) ?torg _ x = x (* ok since there's no smaller ikind to cast to *) - - let leq x y = not x || y - let join = (||) - let widen = join - let meet = (&&) - let narrow = meet - - let of_bool x = x - let to_bool x = Some x - let of_int x = x = Int64.zero - let to_int x = if x then None else Some Int64.zero - - let neg x = x - let add x y = x || y - let sub x y = x || y - let mul x y = x && y - let div x y = true - let rem x y = true - let lt n1 n2 = true - let gt n1 n2 = true - let le n1 n2 = true - let ge n1 n2 = true - let eq n1 n2 = true - let ne n1 n2 = true - let lognot x = true - let logand x y = x && y - let logor x y = x || y - let logxor x y = x && not y || not x && y - let shift_left n1 n2 = n1 - let shift_right n1 n2 = n1 - let c_lognot = (not) - let c_logand = (&&) - let c_logor = (||) - let arbitrary () = QCheck.bool - let invariant _ _ = Invariant.none (* TODO *) -end - -module Booleans = MakeBooleans ( - struct - let truename = "True" - let falsename = "False" - end) - -(* Inclusion/Exclusion sets. Go to top on arithmetic operations (except for some easy cases, e.g. multiplication with 0). Joins on widen, i.e. precise integers as long as not derived from arithmetic expressions. *) -module Enums : S with type int_t = Z.t = struct - open Batteries - module R = Interval32 (* range for exclusion *) - - let range_ikind = Cil.IInt - let size t = R.of_interval range_ikind (let a,b = Size.bits_i64 t in Int64.neg a,b) - - type t = Inc of BISet.t | Exc of BISet.t * R.t [@@deriving eq, ord, hash] (* inclusion/exclusion set *) - - type int_t = Z.t - let name () = "enums" - let bot () = failwith "bot () not implemented for Enums" - let top () = failwith "top () not implemented for Enums" - let bot_of ik = Inc (BISet.empty ()) - let top_bool = Inc (BISet.of_list [Z.zero; Z.one]) - let top_of ik = - match ik with - | IBool -> top_bool - | _ -> Exc (BISet.empty (), size ik) - - let range ik = Size.range ik - -(* - let max_of_range r = Size.max_from_bit_range (Option.get (R.maximal r)) - let min_of_range r = Size.min_from_bit_range (Option.get (R.minimal r)) - let cardinality_of_range r = Z.add (Z.neg (min_of_range r)) (max_of_range r) *) - let value_in_range (min, max) v = Z.compare min v <= 0 && Z.compare v max <= 0 - - let show = function - | Inc xs when BISet.is_empty xs -> "bot" - | Inc xs -> "{" ^ (String.concat ", " (List.map Z.to_string (BISet.elements xs))) ^ "}" - | Exc (xs,r) -> "not {" ^ (String.concat ", " (List.map Z.to_string (BISet.elements xs))) ^ "} " ^ "("^R.show r^")" - - include Std (struct type nonrec t = t let name = name let top_of = top_of let bot_of = bot_of let show = show let equal = equal end) - - (* Normalization function for enums, that handles overflows for Inc. - As we do not compute on Excl, we do not have to perform any overflow handling for it. *) - let norm ikind v = - let min, max = range ikind in - (* Whether the value v lies within the values of the specified ikind. *) - let value_in_ikind v = - Z.compare min v <= 0 && Z.compare v max <= 0 - in - match v with - | Inc xs when BISet.for_all value_in_ikind xs -> v - | Inc xs -> - if should_wrap ikind then - Inc (BISet.map (Size.cast ikind) xs) - else if should_ignore_overflow ikind then - Inc (BISet.filter value_in_ikind xs) - else - top_of ikind - | Exc (xs, r) -> - (* The following assert should hold for Exc, therefore we do not have to overflow handling / normalization for it: - let range_in_ikind r = - R.leq r (size ikind) - in - let r_min, r_max = min_of_range r, max_of_range r in - assert (range_in_ikind r && BISet.for_all (value_in_range (r_min, r_max)) xs); *) - begin match ikind with - | IBool -> - begin match BISet.mem Z.zero xs, BISet.mem Z.one xs with - | false, false -> top_bool (* Not {} -> {0, 1} *) - | true, false -> Inc (BISet.singleton Z.one) (* Not {0} -> {1} *) - | false, true -> Inc (BISet.singleton Z.zero) (* Not {1} -> {0} *) - | true, true -> bot_of ikind (* Not {0, 1} -> bot *) - end - | _ -> - v - end - - - let equal_to i = function - | Inc x -> - if BISet.mem i x then - if BISet.is_singleton x then `Eq - else `Top - else `Neq - | Exc (x, r) -> - if BISet.mem i x then `Neq - else `Top - - let cast_to ?(suppress_ovwarn=false) ?torg ?no_ov ik v = norm ik @@ match v with - | Exc (s,r) -> - let r' = size ik in - if R.leq r r' then (* upcast -> no change *) - Exc (s, r) - else if ik = IBool then (* downcast to bool *) - if BISet.mem Z.zero s then - Inc (BISet.singleton Z.one) - else - Exc (BISet.empty(), r') - else (* downcast: may overflow *) - Exc ((BISet.empty ()), r') - | Inc xs -> - let casted_xs = BISet.map (Size.cast ik) xs in - if Cil.isSigned ik && not (BISet.equal xs casted_xs) - then top_of ik (* When casting into a signed type and the result does not fit, the behavior is implementation-defined *) - else Inc casted_xs - - let of_int ikind x = cast_to ikind (Inc (BISet.singleton x)) - - let of_interval ?(suppress_ovwarn=false) ik (x, y) = - if Z.compare x y = 0 then - of_int ik x - else - let a, b = Size.min_range_sign_agnostic x, Size.min_range_sign_agnostic y in - let r = R.join (R.of_interval ~suppress_ovwarn range_ikind a) (R.of_interval ~suppress_ovwarn range_ikind b) in - let ex = if Z.gt x Z.zero || Z.lt y Z.zero then BISet.singleton Z.zero else BISet.empty () in - norm ik @@ (Exc (ex, r)) - - let join ik = curry @@ function - | Inc x, Inc y -> Inc (BISet.union x y) - | Exc (x,r1), Exc (y,r2) -> Exc (BISet.inter x y, R.join r1 r2) - | Exc (x,r), Inc y - | Inc y, Exc (x,r) -> - let r = if BISet.is_empty y - then r - else - let (min_el_range, max_el_range) = Tuple2.mapn (fun x -> R.of_interval range_ikind (Size.min_range_sign_agnostic x)) (BISet.min_elt y, BISet.max_elt y) in - let range = R.join min_el_range max_el_range in - R.join r range - in - Exc (BISet.diff x y, r) - - let meet ikind = curry @@ function - | Inc x, Inc y -> Inc (BISet.inter x y) - | Exc (x,r1), Exc (y,r2) -> - let r = R.meet r1 r2 in - let r_min, r_max = Exclusion.min_of_range r, Exclusion.max_of_range r in - let filter_by_range = BISet.filter (value_in_range (r_min, r_max)) in - (* We remove those elements from the exclusion set that do not fit in the range anyway *) - let excl = BISet.union (filter_by_range x) (filter_by_range y) in - Exc (excl, r) - | Inc x, Exc (y,r) - | Exc (y,r), Inc x -> Inc (BISet.diff x y) - - let widen = join - let narrow = meet - let leq a b = - match a, b with - | Inc xs, Exc (ys, r) -> - if BISet.is_empty xs - then true - else - let min_b, max_b = Exclusion.min_of_range r, Exclusion.max_of_range r in - let min_a, max_a = BISet.min_elt xs, BISet.max_elt xs in - (* Check that the xs fit into the range r *) - Z.compare min_b min_a <= 0 && Z.compare max_a max_b <= 0 && - (* && check that none of the values contained in xs is excluded, i.e. contained in ys. *) - BISet.for_all (fun x -> not (BISet.mem x ys)) xs - | Inc xs, Inc ys -> - BISet.subset xs ys - | Exc (xs, r), Exc (ys, s) -> - Exclusion.(leq (Exc (xs, r)) (Exc (ys, s))) - | Exc (xs, r), Inc ys -> - Exclusion.(leq_excl_incl (Exc (xs, r)) (Inc ys)) - - let handle_bot x y f = match is_bot x, is_bot y with - | false, false -> f () - | true, false - | false, true -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) - | true, true -> Inc (BISet.empty ()) - - let lift1 f ikind v = norm ikind @@ match v with - | Inc x when BISet.is_empty x -> v (* Return bottom when value is bottom *) - | Inc x when BISet.is_singleton x -> Inc (BISet.singleton (f (BISet.choose x))) - | _ -> top_of ikind - - let lift2 f (ikind: Cil.ikind) u v = - handle_bot u v (fun () -> - norm ikind @@ match u, v with - | Inc x,Inc y when BISet.is_singleton x && BISet.is_singleton y -> Inc (BISet.singleton (f (BISet.choose x) (BISet.choose y))) - | _,_ -> top_of ikind) - - let lift2 f ikind a b = - try lift2 f ikind a b with Division_by_zero -> top_of ikind - - let neg ?no_ov = lift1 Z.neg - let add ?no_ov ikind = curry @@ function - | Inc z,x when BISet.is_singleton z && BISet.choose z = Z.zero -> x - | x,Inc z when BISet.is_singleton z && BISet.choose z = Z.zero -> x - | x,y -> lift2 Z.add ikind x y - let sub ?no_ov = lift2 Z.sub - let mul ?no_ov ikind a b = - match a, b with - | Inc one,x when BISet.is_singleton one && BISet.choose one = Z.one -> x - | x,Inc one when BISet.is_singleton one && BISet.choose one = Z.one -> x - | Inc zero,_ when BISet.is_singleton zero && BISet.choose zero = Z.zero -> a - | _,Inc zero when BISet.is_singleton zero && BISet.choose zero = Z.zero -> b - | x,y -> lift2 Z.mul ikind x y - - let div ?no_ov ikind a b = match a, b with - | x,Inc one when BISet.is_singleton one && BISet.choose one = Z.one -> x - | _,Inc zero when BISet.is_singleton zero && BISet.choose zero = Z.zero -> top_of ikind - | Inc zero,_ when BISet.is_singleton zero && BISet.choose zero = Z.zero -> a - | x,y -> lift2 Z.div ikind x y - - let rem = lift2 Z.rem - - let lognot = lift1 Z.lognot - let logand = lift2 Z.logand - let logor = lift2 Z.logor - let logxor = lift2 Z.logxor - - let shift (shift_op: int_t -> int -> int_t) (ik: Cil.ikind) (x: t) (y: t) = - handle_bot x y (fun () -> - (* BigInt only accepts int as second argument for shifts; perform conversion here *) - let shift_op_big_int a (b: int_t) = - let (b : int) = Z.to_int b in - shift_op a b - in - (* If one of the parameters of the shift is negative, the result is undefined *) - let x_min = minimal x in - let y_min = minimal y in - if x_min = None || y_min = None || Z.compare (Option.get x_min) Z.zero < 0 || Z.compare (Option.get y_min) Z.zero < 0 then - top_of ik - else - lift2 shift_op_big_int ik x y) - - let shift_left = - shift Z.shift_left - - let shift_right = - shift Z.shift_right - - let of_bool ikind x = Inc (BISet.singleton (if x then Z.one else Z.zero)) - let to_bool = function - | Inc e when BISet.is_empty e -> None - | Exc (e,_) when BISet.is_empty e -> None - | Inc zero when BISet.is_singleton zero && BISet.choose zero = Z.zero -> Some false - | Inc xs when BISet.for_all ((<>) Z.zero) xs -> Some true - | Exc (xs,_) when BISet.exists ((=) Z.zero) xs -> Some true - | _ -> None - let to_int = function Inc x when BISet.is_singleton x -> Some (BISet.choose x) | _ -> None - - let to_excl_list = function Exc (x,r) when not (BISet.is_empty x) -> Some (BISet.elements x, (Option.get (R.minimal r), Option.get (R.maximal r))) | _ -> None - let of_excl_list ik xs = - let min_ik, max_ik = Size.range ik in - let exc = BISet.of_list @@ List.filter (value_in_range (min_ik, max_ik)) xs in - norm ik @@ Exc (exc, size ik) - let is_excl_list = BatOption.is_some % to_excl_list - let to_incl_list = function Inc s when not (BISet.is_empty s) -> Some (BISet.elements s) | _ -> None - - let starting ?(suppress_ovwarn=false) ikind x = - let _,u_ik = Size.range ikind in - of_interval ~suppress_ovwarn ikind (x, u_ik) - - let ending ?(suppress_ovwarn=false) ikind x = - let l_ik,_ = Size.range ikind in - of_interval ~suppress_ovwarn ikind (l_ik, x) - - let c_lognot ik x = - if is_bot x - then x - else - match to_bool x with - | Some b -> of_bool ik (not b) - | None -> top_bool - - let c_logand = lift2 IntOps.BigIntOps.c_logand - let c_logor = lift2 IntOps.BigIntOps.c_logor - let maximal = function - | Inc xs when not (BISet.is_empty xs) -> Some (BISet.max_elt xs) - | Exc (excl,r) -> - let rec decrement_while_contained v = - if BISet.mem v excl - then decrement_while_contained (Z.pred v) - else v - in - let range_max = Exclusion.max_of_range r in - Some (decrement_while_contained range_max) - | _ (* bottom case *) -> None - - let minimal = function - | Inc xs when not (BISet.is_empty xs) -> Some (BISet.min_elt xs) - | Exc (excl,r) -> - let rec increment_while_contained v = - if BISet.mem v excl - then increment_while_contained (Z.succ v) - else v - in - let range_min = Exclusion.min_of_range r in - Some (increment_while_contained range_min) - | _ (* bottom case *) -> None - - let lt ik x y = - handle_bot x y (fun () -> - match minimal x, maximal x, minimal y, maximal y with - | _, Some x2, Some y1, _ when Z.compare x2 y1 < 0 -> of_bool ik true - | Some x1, _, _, Some y2 when Z.compare x1 y2 >= 0 -> of_bool ik false - | _, _, _, _ -> top_bool) - - let gt ik x y = lt ik y x - - let le ik x y = - handle_bot x y (fun () -> - match minimal x, maximal x, minimal y, maximal y with - | _, Some x2, Some y1, _ when Z.compare x2 y1 <= 0 -> of_bool ik true - | Some x1, _, _, Some y2 when Z.compare x1 y2 > 0 -> of_bool ik false - | _, _, _, _ -> top_bool) - - let ge ik x y = le ik y x - - let eq ik x y = - handle_bot x y (fun () -> - match x, y with - | Inc xs, Inc ys when BISet.is_singleton xs && BISet.is_singleton ys -> of_bool ik (Z.equal (BISet.choose xs) (BISet.choose ys)) - | _, _ -> - if is_bot (meet ik x y) then - (* If the meet is empty, there is no chance that concrete values are equal *) - of_bool ik false - else - top_bool) - - let ne ik x y = c_lognot ik (eq ik x y) - - let invariant_ikind e ik x = - let inexact_type_bounds = get_bool "witness.invariant.inexact-type-bounds" in - match x with - | Inc ps when not inexact_type_bounds && ik = IBool && is_top_of ik x -> - Invariant.none - | Inc ps -> - if BISet.cardinal ps > 1 || get_bool "witness.invariant.exact" then - BISet.fold (fun x a -> - let i = Invariant.of_exp Cil.(BinOp (Eq, e, kintegerCilint ik x, intType)) in - Invariant.(a || i) [@coverage off] (* bisect_ppx cannot handle redefined (||) *) - ) ps (Invariant.bot ()) - else - Invariant.top () - | Exc (ns, r) -> - (* Emit range invariant if tighter than ikind bounds. - This can be more precise than interval, which has been widened. *) - let (rmin, rmax) = (Exclusion.min_of_range r, Exclusion.max_of_range r) in - let (ikmin, ikmax) = - let ikr = size ik in - (Exclusion.min_of_range ikr, Exclusion.max_of_range ikr) - in - let imin = if inexact_type_bounds || Z.compare ikmin rmin <> 0 then Invariant.of_exp Cil.(BinOp (Le, kintegerCilint ik rmin, e, intType)) else Invariant.none in - let imax = if inexact_type_bounds || Z.compare rmax ikmax <> 0 then Invariant.of_exp Cil.(BinOp (Le, e, kintegerCilint ik rmax, intType)) else Invariant.none in - BISet.fold (fun x a -> - let i = Invariant.of_exp Cil.(BinOp (Ne, e, kintegerCilint ik x, intType)) in - Invariant.(a && i) - ) ns Invariant.(imin && imax) - - - let arbitrary ik = - let open QCheck.Iter in - let neg s = of_excl_list ik (BISet.elements s) in - let pos s = norm ik (Inc s) in - let shrink = function - | Exc (s, _) -> GobQCheck.shrink (BISet.arbitrary ()) s >|= neg (* S TODO: possibly shrink neg to pos *) - | Inc s -> GobQCheck.shrink (BISet.arbitrary ()) s >|= pos - in - QCheck.frequency ~shrink ~print:show [ - 20, QCheck.map neg (BISet.arbitrary ()); - 10, QCheck.map pos (BISet.arbitrary ()); - ] (* S TODO: decide frequencies *) - - let refine_with_congruence ik a b = - let contains c m x = if Z.equal m Z.zero then Z.equal c x else Z.equal (Z.rem (Z.sub x c) m) Z.zero in - match a, b with - | Inc e, None -> bot_of ik - | Inc e, Some (c, m) -> Inc (BISet.filter (contains c m) e) - | _ -> a - - let refine_with_interval ik a b = a - - let refine_with_excl_list ik a b = - match b with - | Some (ls, _) -> meet ik a (of_excl_list ik ls) (* TODO: refine with excl range? *) - | _ -> a - - let refine_with_incl_list ik a b = - match a, b with - | Inc x, Some (ls) -> meet ik (Inc x) (Inc (BISet.of_list ls)) - | _ -> a - - let project ik p t = t -end - -module Congruence : S with type int_t = Z.t and type t = (Z.t * Z.t) option = -struct - let name () = "congruences" - type int_t = Z.t - - (* represents congruence class of c mod m, None is bot *) - type t = (Z.t * Z.t) option [@@deriving eq, ord, hash] - - let ( *: ) = Z.mul - let (+:) = Z.add - let (-:) = Z.sub - let (%:) = Z.rem - let (/:) = Z.div - let (=:) = Z.equal - let (<:) x y = Z.compare x y < 0 - let (>:) x y = Z.compare x y > 0 - let (<=:) x y = Z.compare x y <= 0 - let (>=:) x y = Z.compare x y >= 0 - (* a divides b *) - let ( |: ) a b = - if a =: Z.zero then false else (b %: a) =: Z.zero - - let normalize ik x = - match x with - | None -> None - | Some (c, m) -> - if m =: Z.zero then - if should_wrap ik then - Some (Size.cast ik c, m) - else - Some (c, m) - else - let m' = Z.abs m in - let c' = c %: m' in - if c' <: Z.zero then - Some (c' +: m', m') - else - Some (c' %: m', m') - - let range ik = Size.range ik - - let top () = Some (Z.zero, Z.one) - let top_of ik = Some (Z.zero, Z.one) - let bot () = None - let bot_of ik = bot () - - let show = function ik -> match ik with - | None -> "⟂" - | Some (c, m) when (c, m) = (Z.zero, Z.zero) -> Z.to_string c - | Some (c, m) -> - let a = if c =: Z.zero then "" else Z.to_string c in - let b = if m =: Z.zero then "" else if m = Z.one then "ℤ" else Z.to_string m^"ℤ" in - let c = if a = "" || b = "" then "" else "+" in - a^c^b - - include Std (struct type nonrec t = t let name = name let top_of = top_of let bot_of = bot_of let show = show let equal = equal end) - - let is_top x = x = top () - - let equal_to i = function - | None -> failwith "unsupported: equal_to with bottom" - | Some (a, b) when b =: Z.zero -> if a =: i then `Eq else `Neq - | Some (a, b) -> if i %: b =: a then `Top else `Neq - - let leq (x:t) (y:t) = - match x, y with - | None, _ -> true - | Some _, None -> false - | Some (c1,m1), Some (c2,m2) when m2 =: Z.zero && m1 =: Z.zero -> c1 =: c2 - | Some (c1,m1), Some (c2,m2) when m2 =: Z.zero -> c1 =: c2 && m1 =: Z.zero - | Some (c1,m1), Some (c2,m2) -> m2 |: Z.gcd (c1 -: c2) m1 - (* Typo in original equation of P. Granger (m2 instead of m1): gcd (c1 -: c2) m2 - Reference: https://doi.org/10.1080/00207168908803778 Page 171 corollary 3.3*) - - let leq x y = - let res = leq x y in - if M.tracing then M.trace "congruence" "leq %a %a -> %a " pretty x pretty y pretty (Some (Z.of_int (Bool.to_int res), Z.zero)) ; - res - - let join ik (x:t) y = - match x, y with - | None, z | z, None -> z - | Some (c1,m1), Some (c2,m2) -> - let m3 = Z.gcd m1 (Z.gcd m2 (c1 -: c2)) in - normalize ik (Some (c1, m3)) - - let join ik (x:t) y = - let res = join ik x y in - if M.tracing then M.trace "congruence" "join %a %a -> %a" pretty x pretty y pretty res; - res - - - let meet ik x y = - (* if it exists, c2/a2 is solution to a*x ≡ c (mod m) *) - let congruence_series a c m = - let rec next a1 c1 a2 c2 = - if a2 |: a1 then (a2, c2) - else next a2 c2 (a1 %: a2) (c1 -: (c2 *: (a1 /: a2))) - in next m Z.zero a c - in - let simple_case i c m = - if m |: (i -: c) - then Some (i, Z.zero) else None - in - match x, y with - | Some (c1, m1), Some (c2, m2) when m1 =: Z.zero && m2 =: Z.zero -> if c1 =: c2 then Some (c1, Z.zero) else None - | Some (c1, m1), Some (c2, m2) when m1 =: Z.zero -> simple_case c1 c2 m2 - | Some (c1, m1), Some (c2, m2) when m2 =: Z.zero -> simple_case c2 c1 m1 - | Some (c1, m1), Some (c2, m2) when (Z.gcd m1 m2) |: (c1 -: c2) -> - let (c, m) = congruence_series m1 (c2 -: c1 ) m2 in - normalize ik (Some(c1 +: (m1 *: (m /: c)), m1 *: (m2 /: c))) - | _ -> None - - let meet ik x y = - let res = meet ik x y in - if M.tracing then M.trace "congruence" "meet %a %a -> %a" pretty x pretty y pretty res; - res - - let to_int = function Some (c, m) when m =: Z.zero -> Some c | _ -> None - let of_int ik (x: int_t) = normalize ik @@ Some (x, Z.zero) - let zero = Some (Z.zero, Z.zero) - let one = Some (Z.one, Z.zero) - let top_bool = top() - - let of_bool _ik = function true -> one | false -> zero - - let to_bool (a: t) = match a with - | None -> None - | x when equal zero x -> Some false - | x -> if leq zero x then None else Some true - - let starting ?(suppress_ovwarn=false) ik n = top() - - let ending = starting - - let of_congruence ik (c,m) = normalize ik @@ Some(c,m) - - let maximal t = match t with - | Some (x, y) when y =: Z.zero -> Some x - | _ -> None - - let minimal t = match t with - | Some (x,y) when y =: Z.zero -> Some x - | _ -> None - - (* cast from original type to ikind, set to top if the value doesn't fit into the new type *) - let cast_to ?(suppress_ovwarn=false) ?torg ?(no_ov=false) t x = - match x with - | None -> None - | Some (c, m) when m =: Z.zero -> - let c' = Size.cast t c in - (* When casting into a signed type and the result does not fit, the behavior is implementation-defined. (C90 6.2.1.2, C99 and C11 6.3.1.3) *) - (* We go with GCC behavior here: *) - (* For conversion to a type of width N, the value is reduced modulo 2^N to be within range of the type; no signal is raised. *) - (* (https://gcc.gnu.org/onlinedocs/gcc/Integers-implementation.html) *) - (* Clang behaves the same but they never document that anywhere *) - Some (c', m) - | _ -> - let (min_t, max_t) = range t in - let p ikorg = - let (min_ikorg, max_ikorg) = range ikorg in - ikorg = t || (max_t >=: max_ikorg && min_t <=: min_ikorg) - in - match torg with - | Some (Cil.TInt (ikorg, _)) when p ikorg -> - if M.tracing then M.trace "cong-cast" "some case"; - x - | _ -> top () - - - let cast_to ?(suppress_ovwarn=false) ?torg ?no_ov (t : Cil.ikind) x = - let pretty_bool _ x = Pretty.text (string_of_bool x) in - let res = cast_to ?torg ?no_ov t x in - if M.tracing then M.trace "cong-cast" "Cast %a to %a (no_ov: %a) = %a" pretty x Cil.d_ikind t (Pretty.docOpt (pretty_bool ())) no_ov pretty res; - res - - let widen = join - - let widen ik x y = - let res = widen ik x y in - if M.tracing then M.trace "congruence" "widen %a %a -> %a" pretty x pretty y pretty res; - res - - let narrow = meet - - let log f ik i1 i2 = - match is_bot i1, is_bot i2 with - | true, true -> bot_of ik - | true, _ - | _ , true -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show i1) (show i2))) - | _ -> - match to_bool i1, to_bool i2 with - | Some x, Some y -> of_bool ik (f x y) - | _ -> top_of ik - - let c_logor = log (||) - let c_logand = log (&&) - - let log1 f ik i1 = - if is_bot i1 then - bot_of ik - else - match to_bool i1 with - | Some x -> of_bool ik (f ik x) - | _ -> top_of ik - - let c_lognot = log1 (fun _ik -> not) - - let shift_right _ _ _ = top() - - let shift_right ik x y = - let res = shift_right ik x y in - if M.tracing then M.trace "congruence" "shift_right : %a %a becomes %a " pretty x pretty y pretty res; - res - - let shift_left ik x y = - (* Naive primality test *) - (* let is_prime n = - let n = Z.abs n in - let rec is_prime' d = - (d *: d >: n) || ((not ((n %: d) =: Z.zero)) && (is_prime' [@tailcall]) (d +: Z.one)) - in - not (n =: Z.one) && is_prime' (Z.of_int 2) - in *) - match x, y with - | None, None -> None - | None, _ - | _, None -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) - | Some (c, m), Some (c', m') when Cil.isSigned ik || c <: Z.zero || c' <: Z.zero -> top_of ik - | Some (c, m), Some (c', m') -> - let (_, max_ik) = range ik in - if m =: Z.zero && m' =: Z.zero then - normalize ik @@ Some (Z.logand max_ik (Z.shift_left c (Z.to_int c')), Z.zero) - else - let x = Z.logand max_ik (Z.shift_left Z.one (Z.to_int c')) in (* 2^c' *) - (* TODO: commented out because fails test with _Bool *) - (* if is_prime (m' +: Z.one) then - normalize ik @@ Some (x *: c, Z.gcd (x *: m) ((c *: x) *: (m' +: Z.one))) - else *) - normalize ik @@ Some (x *: c, Z.gcd (x *: m) (c *: x)) - - let shift_left ik x y = - let res = shift_left ik x y in - if M.tracing then M.trace "congruence" "shift_left : %a %a becomes %a " pretty x pretty y pretty res; - res - - (* Handle unsigned overflows. - From n === k mod (2^a * b), we conclude n === k mod 2^a, for a <= bitwidth. - The congruence modulo b may not persist on an overflow. *) - let handle_overflow ik (c, m) = - if m =: Z.zero then - normalize ik (Some (c, m)) - else - (* Find largest m'=2^k (for some k) such that m is divisible by m' *) - let tz = Z.trailing_zeros m in - let m' = Z.shift_left Z.one tz in - - let max = (snd (Size.range ik)) +: Z.one in - if m' >=: max then - (* if m' >= 2 ^ {bitlength}, there is only one value in range *) - let c' = c %: max in - Some (c', Z.zero) - else - normalize ik (Some (c, m')) - - let mul ?(no_ov=false) ik x y = - let no_ov_case (c1, m1) (c2, m2) = - c1 *: c2, Z.gcd (c1 *: m2) (Z.gcd (m1 *: c2) (m1 *: m2)) - in - match x, y with - | None, None -> bot () - | None, _ | _, None -> - raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) - | Some (c1, m1), Some (c2, m2) when no_ov -> - Some (no_ov_case (c1, m1) (c2, m2)) - | Some (c1, m1), Some (c2, m2) when m1 =: Z.zero && m2 =: Z.zero && not (Cil.isSigned ik) -> - let (_, max_ik) = range ik in - Some ((c1 *: c2) %: (max_ik +: Z.one), Z.zero) - | Some a, Some b when not (Cil.isSigned ik) -> - handle_overflow ik (no_ov_case a b ) - | _ -> top () - - let mul ?no_ov ik x y = - let res = mul ?no_ov ik x y in - if M.tracing then M.trace "congruence" "mul : %a %a -> %a " pretty x pretty y pretty res; - res - - let neg ?(no_ov=false) ik x = - match x with - | None -> bot() - | Some _ -> mul ~no_ov ik (of_int ik (Z.of_int (-1))) x - - let add ?(no_ov=false) ik x y = - let no_ov_case (c1, m1) (c2, m2) = - c1 +: c2, Z.gcd m1 m2 - in - match (x, y) with - | None, None -> bot () - | None, _ | _, None -> - raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) - | Some a, Some b when no_ov -> - normalize ik (Some (no_ov_case a b)) - | Some (c1, m1), Some (c2, m2) when m1 =: Z.zero && m2 =: Z.zero && not (Cil.isSigned ik) -> - let (_, max_ik) = range ik in - Some((c1 +: c2) %: (max_ik +: Z.one), Z.zero) - | Some a, Some b when not (Cil.isSigned ik) -> - handle_overflow ik (no_ov_case a b) - | _ -> top () - - - let add ?no_ov ik x y = - let res = add ?no_ov ik x y in - if M.tracing then - M.trace "congruence" "add : %a %a -> %a" pretty x pretty y - pretty res ; - res - - let sub ?(no_ov=false) ik x y = add ~no_ov ik x (neg ~no_ov ik y) - - - let sub ?no_ov ik x y = - let res = sub ?no_ov ik x y in - if M.tracing then - M.trace "congruence" "sub : %a %a -> %a" pretty x pretty y - pretty res ; - res - - let lognot ik x = match x with - | None -> None - | Some (c, m) -> - if (Cil.isSigned ik) then - sub ik (neg ik x) one - else - let (_, max_ik) = range ik in - Some (Z.sub max_ik c, m) - - (** The implementation of the bit operations could be improved based on the master’s thesis - 'Abstract Interpretation and Abstract Domains' written by Stefan Bygde. - see: http://www.es.mdh.se/pdf_publications/948.pdf *) - let bit2 f ik x y = match x, y with - | None, None -> None - | None, _ | _, None -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) - | Some (c, m), Some (c', m') -> - if m =: Z.zero && m' =: Z.zero then Some (f c c', Z.zero) - else top () - - let logor ik x y = bit2 Z.logor ik x y - - let logand ik x y = match x, y with - | None, None -> None - | None, _ | _, None -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) - | Some (c, m), Some (c', m') -> - if m =: Z.zero && m' =: Z.zero then - (* both arguments constant *) - Some (Z.logand c c', Z.zero) - else if m' =: Z.zero && c' =: Z.one && Z.rem m (Z.of_int 2) =: Z.zero then - (* x & 1 and x == c (mod 2*z) *) - (* Value is equal to LSB of c *) - Some (Z.logand c c', Z.zero) - else - top () - - let logxor ik x y = bit2 Z.logxor ik x y - - let rem ik x y = - match x, y with - | None, None -> bot() - | None, _ | _, None -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) - | Some (c1, m1), Some(c2, m2) -> - if m2 =: Z.zero then - if (c2 |: m1) && (c1 %: c2 =: Z.zero || m1 =: Z.zero || not (Cil.isSigned ik)) then - Some (c1 %: c2, Z.zero) - else - normalize ik (Some (c1, (Z.gcd m1 c2))) - else - normalize ik (Some (c1, Z.gcd m1 (Z.gcd c2 m2))) - - let rem ik x y = let res = rem ik x y in - if M.tracing then M.trace "congruence" "rem : %a %a -> %a " pretty x pretty y pretty res; - res - - let div ?(no_ov=false) ik x y = - match x,y with - | None, None -> bot () - | None, _ | _, None -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) - | _, x when leq zero x -> top () - | Some(c1, m1), Some(c2, m2) when not no_ov && m2 =: Z.zero && c2 =: Z.neg Z.one -> top () - | Some(c1, m1), Some(c2, m2) when m1 =: Z.zero && m2 =: Z.zero -> Some (c1 /: c2, Z.zero) - | Some(c1, m1), Some(c2, m2) when m2 =: Z.zero && c2 |: m1 && c2 |: c1 -> Some (c1 /: c2, m1 /: c2) - | _, _ -> top () - - - let div ?no_ov ik x y = - let res = div ?no_ov ik x y in - if M.tracing then - M.trace "congruence" "div : %a %a -> %a" pretty x pretty y pretty - res ; - res - - let ne ik (x: t) (y: t) = match x, y with - | Some (c1, m1), Some (c2, m2) when (m1 =: Z.zero) && (m2 =: Z.zero) -> of_bool ik (not (c1 =: c2 )) - | x, y -> if meet ik x y = None then of_bool ik true else top_bool - - let eq ik (x: t) (y: t) = match x, y with - | Some (c1, m1), Some (c2, m2) when (m1 =: Z.zero) && (m2 =: Z.zero) -> of_bool ik (c1 =: c2) - | x, y -> if meet ik x y <> None then top_bool else of_bool ik false - - let comparison ik op x y = match x, y with - | None, None -> bot_of ik - | None, _ | _, None -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) - | Some (c1, m1), Some (c2, m2) -> - if m1 =: Z.zero && m2 =: Z.zero then - if op c1 c2 then of_bool ik true else of_bool ik false - else - top_bool - - let ge ik x y = comparison ik (>=:) x y - - let ge ik x y = - let res = ge ik x y in - if M.tracing then M.trace "congruence" "greater or equal : %a %a -> %a " pretty x pretty y pretty res; - res - - let le ik x y = comparison ik (<=:) x y - - let le ik x y = - let res = le ik x y in - if M.tracing then M.trace "congruence" "less or equal : %a %a -> %a " pretty x pretty y pretty res; - res - - let gt ik x y = comparison ik (>:) x y - - - let gt ik x y = - let res = gt ik x y in - if M.tracing then M.trace "congruence" "greater than : %a %a -> %a " pretty x pretty y pretty res; - res - - let lt ik x y = comparison ik (<:) x y - - let lt ik x y = - let res = lt ik x y in - if M.tracing then M.trace "congruence" "less than : %a %a -> %a " pretty x pretty y pretty res; - res - - let invariant_ikind e ik x = - match x with - | x when is_top x -> Invariant.top () - | Some (c, m) when m =: Z.zero -> - if get_bool "witness.invariant.exact" then - Invariant.of_exp Cil.(BinOp (Eq, e, Cil.kintegerCilint ik c, intType)) - else - Invariant.top () - | Some (c, m) -> - let open Cil in - let (c, m) = BatTuple.Tuple2.mapn (fun a -> kintegerCilint ik a) (c, m) in - Invariant.of_exp (BinOp (Eq, (BinOp (Mod, e, m, TInt(ik,[]))), c, intType)) - | None -> Invariant.none - - let arbitrary ik = - let open QCheck in - let int_arb = map ~rev:Z.to_int64 Z.of_int64 GobQCheck.Arbitrary.int64 in - let cong_arb = pair int_arb int_arb in - let of_pair ik p = normalize ik (Some p) in - let to_pair = Option.get in - set_print show (map ~rev:to_pair (of_pair ik) cong_arb) - - let refine_with_interval ik (cong : t) (intv : (int_t * int_t ) option) : t = - match intv, cong with - | Some (x, y), Some (c, m) -> - if m =: Z.zero then - if c <: x || c >: y then None else Some (c, Z.zero) - else - let rcx = x +: ((c -: x) %: Z.abs m) in - let lcy = y -: ((y -: c) %: Z.abs m) in - if rcx >: lcy then None - else if rcx =: lcy then Some (rcx, Z.zero) - else cong - | _ -> None - - let refine_with_interval ik (cong : t) (intv : (int_t * int_t) option) : t = - let pretty_intv _ i = - match i with - | Some (l, u) -> Pretty.dprintf "[%a,%a]" GobZ.pretty l GobZ.pretty u - | _ -> Pretty.text ("Display Error") in - let refn = refine_with_interval ik cong intv in - if M.tracing then M.trace "refine" "cong_refine_with_interval %a %a -> %a" pretty cong pretty_intv intv pretty refn; - refn - - let refine_with_congruence ik a b = meet ik a b - let refine_with_excl_list ik a b = a - let refine_with_incl_list ik a b = a - - let project ik p t = t -end - -module SOverflowLifter (D : S) : SOverflow with type int_t = D.int_t and type t = D.t = struct - - include D - - let lift v = (v, {overflow=false; underflow=false}) - - let add ?no_ov ik x y = lift @@ D.add ?no_ov ik x y - - let sub ?no_ov ik x y = lift @@ D.sub ?no_ov ik x y - - let mul ?no_ov ik x y = lift @@ D.mul ?no_ov ik x y - - let div ?no_ov ik x y = lift @@ D.div ?no_ov ik x y - - let neg ?no_ov ik x = lift @@ D.neg ?no_ov ik x - - let cast_to ?suppress_ovwarn ?torg ?no_ov ik x = lift @@ D.cast_to ?suppress_ovwarn ?torg ?no_ov ik x - - let of_int ik x = lift @@ D.of_int ik x - - let of_interval ?suppress_ovwarn ik x = lift @@ D.of_interval ?suppress_ovwarn ik x - - let starting ?suppress_ovwarn ik x = lift @@ D.starting ?suppress_ovwarn ik x - - let ending ?suppress_ovwarn ik x = lift @@ D.ending ?suppress_ovwarn ik x - - let shift_left ik x y = lift @@ D.shift_left ik x y - - let shift_right ik x y = lift @@ D.shift_right ik x y - -end - - - -(* The old IntDomList had too much boilerplate since we had to edit every function in S when adding a new domain. With the following, we only have to edit the places where fn are applied, i.e., create, mapp, map, map2. You can search for I3 below to see where you need to extend. *) -(* discussion: https://github.com/goblint/analyzer/pull/188#issuecomment-818928540 *) -module IntDomTupleImpl = struct - include Printable.Std (* for default invariant, tag, ... *) - - open Batteries - type int_t = Z.t - module I1 = SOverflowLifter (DefExc) - module I2 = Interval - module I3 = SOverflowLifter (Enums) - module I4 = SOverflowLifter (Congruence) - module I5 = IntervalSetFunctor (IntOps.BigIntOps) - - type t = I1.t option * I2.t option * I3.t option * I4.t option * I5.t option - [@@deriving to_yojson, eq, ord] - - let name () = "intdomtuple" - - (* The Interval domain can lead to too many contexts for recursive functions (top is [min,max]), but we don't want to drop all ints as with `ana.base.context.int`. TODO better solution? *) - let no_interval = Tuple5.map2 (const None) - let no_intervalSet = Tuple5.map5 (const None) - - type 'a m = (module SOverflow with type t = 'a) - type 'a m2 = (module SOverflow with type t = 'a and type int_t = int_t ) - - (* only first-order polymorphism on functions -> use records to get around monomorphism restriction on arguments *) - type 'b poly_in = { fi : 'a. 'a m -> 'b -> 'a } [@@unboxed] (* inject *) - type 'b poly2_in = { fi2 : 'a. 'a m2 -> 'b -> 'a } [@@unboxed] (* inject for functions that depend on int_t *) - type 'b poly2_in_ovc = { fi2_ovc : 'a. 'a m2 -> 'b -> 'a * overflow_info} [@@unboxed] (* inject for functions that depend on int_t *) - - type 'b poly_pr = { fp : 'a. 'a m -> 'a -> 'b } [@@unboxed] (* project *) - type 'b poly_pr2 = { fp2 : 'a. 'a m2 -> 'a -> 'b } [@@unboxed] (* project for functions that depend on int_t *) - type 'b poly2_pr = {f2p: 'a. 'a m -> ?no_ov:bool -> 'a -> 'a -> 'b} [@@unboxed] - type poly1 = {f1: 'a. 'a m -> ?no_ov:bool -> 'a -> 'a} [@@unboxed] (* needed b/c above 'b must be different from 'a *) - type poly1_ovc = {f1_ovc: 'a. 'a m -> ?no_ov:bool -> 'a -> 'a * overflow_info } [@@unboxed] (* needed b/c above 'b must be different from 'a *) - type poly2 = {f2: 'a. 'a m -> ?no_ov:bool -> 'a -> 'a -> 'a} [@@unboxed] - type poly2_ovc = {f2_ovc: 'a. 'a m -> ?no_ov:bool -> 'a -> 'a -> 'a * overflow_info } [@@unboxed] - type 'b poly3 = { f3: 'a. 'a m -> 'a option } [@@unboxed] (* used for projection to given precision *) - let create r x ((p1, p2, p3, p4, p5): int_precision) = - let f b g = if b then Some (g x) else None in - f p1 @@ r.fi (module I1), f p2 @@ r.fi (module I2), f p3 @@ r.fi (module I3), f p4 @@ r.fi (module I4), f p5 @@ r.fi (module I5) - let create r x = (* use where values are introduced *) - create r x (int_precision_from_node_or_config ()) - let create2 r x ((p1, p2, p3, p4, p5): int_precision) = - let f b g = if b then Some (g x) else None in - f p1 @@ r.fi2 (module I1), f p2 @@ r.fi2 (module I2), f p3 @@ r.fi2 (module I3), f p4 @@ r.fi2 (module I4), f p5 @@ r.fi2 (module I5) - let create2 r x = (* use where values are introduced *) - create2 r x (int_precision_from_node_or_config ()) - - let no_overflow ik = function - | Some(_, {underflow; overflow}) -> not (underflow || overflow) - | _ -> false - - let check_ov ?(suppress_ovwarn = false) ~cast ik intv intv_set = - let no_ov = (no_overflow ik intv) || (no_overflow ik intv_set) in - if not no_ov && not suppress_ovwarn && ( BatOption.is_some intv || BatOption.is_some intv_set) then ( - let (_,{underflow=underflow_intv; overflow=overflow_intv}) = match intv with None -> (I2.bot (), {underflow= true; overflow = true}) | Some x -> x in - let (_,{underflow=underflow_intv_set; overflow=overflow_intv_set}) = match intv_set with None -> (I5.bot (), {underflow= true; overflow = true}) | Some x -> x in - let underflow = underflow_intv && underflow_intv_set in - let overflow = overflow_intv && overflow_intv_set in - set_overflow_flag ~cast ~underflow ~overflow ik; - ); - no_ov - - let create2_ovc ik r x ((p1, p2, p3, p4, p5): int_precision) = - let f b g = if b then Some (g x) else None in - let map x = Option.map fst x in - let intv = f p2 @@ r.fi2_ovc (module I2) in - let intv_set = f p5 @@ r.fi2_ovc (module I5) in - ignore (check_ov ~cast:false ik intv intv_set); - map @@ f p1 @@ r.fi2_ovc (module I1), map @@ f p2 @@ r.fi2_ovc (module I2), map @@ f p3 @@ r.fi2_ovc (module I3), map @@ f p4 @@ r.fi2_ovc (module I4), map @@ f p5 @@ r.fi2_ovc (module I5) - - let create2_ovc ik r x = (* use where values are introduced *) - create2_ovc ik r x (int_precision_from_node_or_config ()) - - - let opt_map2 f ?no_ov = - curry @@ function Some x, Some y -> Some (f ?no_ov x y) | _ -> None - - let to_list x = Tuple5.enum x |> List.of_enum |> List.filter_map identity (* contains only the values of activated domains *) - let to_list_some x = List.filter_map identity @@ to_list x (* contains only the Some-values of activated domains *) - - let exists = function - | (Some true, _, _, _, _) - | (_, Some true, _, _, _) - | (_, _, Some true, _, _) - | (_, _, _, Some true, _) - | (_, _, _, _, Some true) -> - true - | _ -> - false - - let for_all = function - | (Some false, _, _, _, _) - | (_, Some false, _, _, _) - | (_, _, Some false, _, _) - | (_, _, _, Some false, _) - | (_, _, _, _, Some false) -> - false - | _ -> - true - - (* f0: constructors *) - let top () = create { fi = fun (type a) (module I:SOverflow with type t = a) -> I.top } () - let bot () = create { fi = fun (type a) (module I:SOverflow with type t = a) -> I.bot } () - let top_of = create { fi = fun (type a) (module I:SOverflow with type t = a) -> I.top_of } - let bot_of = create { fi = fun (type a) (module I:SOverflow with type t = a) -> I.bot_of } - let of_bool ik = create { fi = fun (type a) (module I:SOverflow with type t = a) -> I.of_bool ik } - let of_excl_list ik = create2 { fi2 = fun (type a) (module I:SOverflow with type t = a and type int_t = int_t) -> I.of_excl_list ik} - let of_int ik = create2_ovc ik { fi2_ovc = fun (type a) (module I:SOverflow with type t = a and type int_t = int_t) -> I.of_int ik } - let starting ?(suppress_ovwarn=false) ik = create2_ovc ik { fi2_ovc = fun (type a) (module I:SOverflow with type t = a and type int_t = int_t) -> I.starting ~suppress_ovwarn ik } - let ending ?(suppress_ovwarn=false) ik = create2_ovc ik { fi2_ovc = fun (type a) (module I:SOverflow with type t = a and type int_t = int_t) -> I.ending ~suppress_ovwarn ik } - let of_interval ?(suppress_ovwarn=false) ik = create2_ovc ik { fi2_ovc = fun (type a) (module I:SOverflow with type t = a and type int_t = int_t) -> I.of_interval ~suppress_ovwarn ik } - let of_congruence ik = create2 { fi2 = fun (type a) (module I:SOverflow with type t = a and type int_t = int_t) -> I.of_congruence ik } - - let refine_with_congruence ik ((a, b, c, d, e) : t) (cong : (int_t * int_t) option) : t= - let opt f a = - curry @@ function Some x, y -> Some (f a x y) | _ -> None - in - ( opt I1.refine_with_congruence ik a cong - , opt I2.refine_with_congruence ik b cong - , opt I3.refine_with_congruence ik c cong - , opt I4.refine_with_congruence ik d cong - , opt I5.refine_with_congruence ik e cong) - - let refine_with_interval ik (a, b, c, d, e) intv = - let opt f a = - curry @@ function Some x, y -> Some (f a x y) | _ -> None - in - ( opt I1.refine_with_interval ik a intv - , opt I2.refine_with_interval ik b intv - , opt I3.refine_with_interval ik c intv - , opt I4.refine_with_interval ik d intv - , opt I5.refine_with_interval ik e intv ) - - let refine_with_excl_list ik (a, b, c, d, e) excl = - let opt f a = - curry @@ function Some x, y -> Some (f a x y) | _ -> None - in - ( opt I1.refine_with_excl_list ik a excl - , opt I2.refine_with_excl_list ik b excl - , opt I3.refine_with_excl_list ik c excl - , opt I4.refine_with_excl_list ik d excl - , opt I5.refine_with_excl_list ik e excl ) - - let refine_with_incl_list ik (a, b, c, d, e) incl = - let opt f a = - curry @@ function Some x, y -> Some (f a x y) | _ -> None - in - ( opt I1.refine_with_incl_list ik a incl - , opt I2.refine_with_incl_list ik b incl - , opt I3.refine_with_incl_list ik c incl - , opt I4.refine_with_incl_list ik d incl - , opt I5.refine_with_incl_list ik e incl ) - - - let mapp r (a, b, c, d, e) = - let map = BatOption.map in - ( map (r.fp (module I1)) a - , map (r.fp (module I2)) b - , map (r.fp (module I3)) c - , map (r.fp (module I4)) d - , map (r.fp (module I5)) e) - - - let mapp2 r (a, b, c, d, e) = - BatOption. - ( map (r.fp2 (module I1)) a - , map (r.fp2 (module I2)) b - , map (r.fp2 (module I3)) c - , map (r.fp2 (module I4)) d - , map (r.fp2 (module I5)) e) - - - (* exists/for_all *) - let is_bot = exists % mapp { fp = fun (type a) (module I:SOverflow with type t = a) -> I.is_bot } - let is_top = for_all % mapp { fp = fun (type a) (module I:SOverflow with type t = a) -> I.is_top } - let is_top_of ik = for_all % mapp { fp = fun (type a) (module I:SOverflow with type t = a) -> I.is_top_of ik } - let is_excl_list = exists % mapp { fp = fun (type a) (module I:SOverflow with type t = a) -> I.is_excl_list } - - let map2p r (xa, xb, xc, xd, xe) (ya, yb, yc, yd, ye) = - ( opt_map2 (r.f2p (module I1)) xa ya - , opt_map2 (r.f2p (module I2)) xb yb - , opt_map2 (r.f2p (module I3)) xc yc - , opt_map2 (r.f2p (module I4)) xd yd - , opt_map2 (r.f2p (module I5)) xe ye) - - (* f2p: binary projections *) - let (%%) f g x = f % (g x) (* composition for binary function g *) - - let leq = - for_all - %% map2p {f2p= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.leq)} - - let flat f x = match to_list_some x with [] -> None | xs -> Some (f xs) - - let to_excl_list x = - let merge ps = - let (vs, rs) = List.split ps in - let (mins, maxs) = List.split rs in - (List.concat vs, (List.min mins, List.max maxs)) - in - mapp2 { fp2 = fun (type a) (module I:SOverflow with type t = a and type int_t = int_t) -> I.to_excl_list } x |> flat merge - - let to_incl_list x = - let hd l = match l with h::t -> h | _ -> [] in - let tl l = match l with h::t -> t | _ -> [] in - let a y = BatSet.of_list (hd y) in - let b y = BatList.map BatSet.of_list (tl y) in - let merge y = BatSet.elements @@ BatList.fold BatSet.intersect (a y) (b y) - in - mapp2 { fp2 = fun (type a) (module I:SOverflow with type t = a and type int_t = int_t) -> I.to_incl_list } x |> flat merge - - let same show x = let xs = to_list_some x in let us = List.unique xs in let n = List.length us in - if n = 1 then Some (List.hd xs) - else ( - if n>1 then Messages.info ~category:Unsound "Inconsistent state! %a" (Pretty.docList ~sep:(Pretty.text ",") (Pretty.text % show)) us; (* do not want to abort *) - None - ) - let to_int = same Z.to_string % mapp2 { fp2 = fun (type a) (module I:SOverflow with type t = a and type int_t = int_t) -> I.to_int } - - let pretty () x = - match to_int x with - | Some v when not (GobConfig.get_bool "dbg.full-output") -> Pretty.text (Z.to_string v) - | _ -> - mapp { fp = fun (type a) (module I:SOverflow with type t = a) -> (* assert sf==I.short; *) I.pretty () } x - |> to_list - |> (fun xs -> - text "(" ++ ( - try - List.reduce (fun a b -> a ++ text "," ++ b) xs - with Invalid_argument _ -> - nil) - ++ text ")") (* NOTE: the version above does something else. also, we ignore the sf-argument here. *) - - let refine_functions ik : (t -> t) list = - let maybe reffun ik domtup dom = - match dom with Some y -> reffun ik domtup y | _ -> domtup - in - [(fun (a, b, c, d, e) -> refine_with_excl_list ik (a, b, c, d, e) (to_excl_list (a, b, c, d, e))); - (fun (a, b, c, d, e) -> refine_with_incl_list ik (a, b, c, d, e) (to_incl_list (a, b, c, d, e))); - (fun (a, b, c, d, e) -> maybe refine_with_interval ik (a, b, c, d, e) b); - (fun (a, b, c, d, e) -> maybe refine_with_congruence ik (a, b, c, d, e) d)] - - let refine ik ((a, b, c, d, e) : t ) : t = - let dt = ref (a, b, c, d, e) in - (match get_refinement () with - | "never" -> () - | "once" -> - List.iter (fun f -> dt := f !dt) (refine_functions ik); - | "fixpoint" -> - let quit_loop = ref false in - while not !quit_loop do - let old_dt = !dt in - List.iter (fun f -> dt := f !dt) (refine_functions ik); - quit_loop := equal old_dt !dt; - if is_bot !dt then dt := bot_of ik; quit_loop := true; - if M.tracing then M.trace "cong-refine-loop" "old: %a, new: %a" pretty old_dt pretty !dt; - done; - | _ -> () - ); !dt - - - (* map with overflow check *) - let mapovc ?(suppress_ovwarn=false) ?(cast=false) ik r (a, b, c, d, e) = - let map f ?no_ov = function Some x -> Some (f ?no_ov x) | _ -> None in - let intv = map (r.f1_ovc (module I2)) b in - let intv_set = map (r.f1_ovc (module I5)) e in - let no_ov = check_ov ~suppress_ovwarn ~cast ik intv intv_set in - let no_ov = no_ov || should_ignore_overflow ik in - refine ik - ( map (fun ?no_ov x -> r.f1_ovc ?no_ov (module I1) x |> fst) a - , BatOption.map fst intv - , map (fun ?no_ov x -> r.f1_ovc ?no_ov (module I3) x |> fst) c - , map (fun ?no_ov x -> r.f1_ovc ?no_ov (module I4) x |> fst) ~no_ov d - , BatOption.map fst intv_set ) - - (* map2 with overflow check *) - let map2ovc ?(cast=false) ik r (xa, xb, xc, xd, xe) (ya, yb, yc, yd, ye) = - let intv = opt_map2 (r.f2_ovc (module I2)) xb yb in - let intv_set = opt_map2 (r.f2_ovc (module I5)) xe ye in - let no_ov = check_ov ~cast ik intv intv_set in - let no_ov = no_ov || should_ignore_overflow ik in - refine ik - ( opt_map2 (fun ?no_ov x y -> r.f2_ovc ?no_ov (module I1) x y |> fst) xa ya - , BatOption.map fst intv - , opt_map2 (fun ?no_ov x y -> r.f2_ovc ?no_ov (module I3) x y |> fst) xc yc - , opt_map2 (fun ?no_ov x y -> r.f2_ovc ?no_ov (module I4) x y |> fst) ~no_ov:no_ov xd yd - , BatOption.map fst intv_set ) - - let map ik r (a, b, c, d, e) = - refine ik - BatOption. - ( map (r.f1 (module I1)) a - , map (r.f1 (module I2)) b - , map (r.f1 (module I3)) c - , map (r.f1 (module I4)) d - , map (r.f1 (module I5)) e) - - let map2 ?(norefine=false) ik r (xa, xb, xc, xd, xe) (ya, yb, yc, yd, ye) = - let r = - ( opt_map2 (r.f2 (module I1)) xa ya - , opt_map2 (r.f2 (module I2)) xb yb - , opt_map2 (r.f2 (module I3)) xc yc - , opt_map2 (r.f2 (module I4)) xd yd - , opt_map2 (r.f2 (module I5)) xe ye) - in - if norefine then r else refine ik r - - - (* f1: unary ops *) - let neg ?no_ov ik = - mapovc ik {f1_ovc = (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.neg ?no_ov ik)} - - let lognot ik = - map ik {f1 = (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.lognot ik)} - - let c_lognot ik = - map ik {f1 = (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.c_lognot ik)} - - let cast_to ?(suppress_ovwarn=false) ?torg ?no_ov t = - mapovc ~suppress_ovwarn ~cast:true t {f1_ovc = (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.cast_to ?torg ?no_ov t)} - - (* fp: projections *) - let equal_to i x = - let xs = mapp2 { fp2 = fun (type a) (module I:SOverflow with type t = a and type int_t = int_t) -> I.equal_to i } x |> Tuple5.enum |> List.of_enum |> List.filter_map identity in - if List.mem `Eq xs then `Eq else - if List.mem `Neq xs then `Neq else - `Top - - let to_bool = same string_of_bool % mapp { fp = fun (type a) (module I:SOverflow with type t = a) -> I.to_bool } - let minimal = flat (List.max ~cmp:Z.compare) % mapp2 { fp2 = fun (type a) (module I:SOverflow with type t = a and type int_t = int_t) -> I.minimal } - let maximal = flat (List.min ~cmp:Z.compare) % mapp2 { fp2 = fun (type a) (module I:SOverflow with type t = a and type int_t = int_t) -> I.maximal } - (* others *) - let show x = - match to_int x with - | Some v when not (GobConfig.get_bool "dbg.full-output") -> Z.to_string v - | _ -> mapp { fp = fun (type a) (module I:SOverflow with type t = a) x -> I.name () ^ ":" ^ (I.show x) } x - |> to_list - |> String.concat "; " - let to_yojson = [%to_yojson: Yojson.Safe.t list] % to_list % mapp { fp = fun (type a) (module I:SOverflow with type t = a) x -> I.to_yojson x } - let hash = List.fold_left (lxor) 0 % to_list % mapp { fp = fun (type a) (module I:SOverflow with type t = a) -> I.hash } - - (* `map/opt_map` are used by `project` *) - let opt_map b f = - curry @@ function None, true -> f | x, y when y || b -> x | _ -> None - let map ~keep r (i1, i2, i3, i4, i5) (b1, b2, b3, b4, b5) = - ( opt_map keep (r.f3 (module I1)) i1 b1 - , opt_map keep (r.f3 (module I2)) i2 b2 - , opt_map keep (r.f3 (module I3)) i3 b3 - , opt_map keep (r.f3 (module I4)) i4 b4 - , opt_map keep (r.f3 (module I5)) i5 b5 ) - - (** Project tuple t to precision p - * We have to deactivate IntDomains after the refinement, since we might - * lose information if we do it before. E.g. only "Interval" is active - * and shall be projected to only "Def_Exc". By seting "Interval" to None - * before refinement we have no information for "Def_Exc". - * - * Thus we have 3 Steps: - * 1. Add padding to t by setting `None` to `I.top_of ik` if p is true for this element - * 2. Refine the padded t - * 3. Set elements of t to `None` if p is false for this element - * - * Side Note: - * ~keep is used to reuse `map/opt_map` for Step 1 and 3. - * ~keep:true will keep elements that are `Some x` but should be set to `None` by p. - * This way we won't loose any information for the refinement. - * ~keep:false will set the elements to `None` as defined by p *) - let project ik (p: int_precision) t = - let t_padded = map ~keep:true { f3 = fun (type a) (module I:SOverflow with type t = a) -> Some (I.top_of ik) } t p in - let t_refined = refine ik t_padded in - map ~keep:false { f3 = fun (type a) (module I:SOverflow with type t = a) -> None } t_refined p - - - (* f2: binary ops *) - let join ik = - map2 ~norefine:true ik {f2= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.join ik)} - - let meet ik = - map2 ik {f2= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.meet ik)} - - let widen ik = - map2 ~norefine:true ik {f2= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.widen ik)} - - let narrow ik = - map2 ik {f2= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.narrow ik)} - - let add ?no_ov ik = - map2ovc ik - {f2_ovc = (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.add ?no_ov ik)} - - let sub ?no_ov ik = - map2ovc ik - {f2_ovc = (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.sub ?no_ov ik)} - - let mul ?no_ov ik = - map2ovc ik - {f2_ovc = (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.mul ?no_ov ik)} - - let div ?no_ov ik = - map2ovc ik - {f2_ovc = (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.div ?no_ov ik)} - - let rem ik = - map2 ik {f2= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.rem ik)} - - let lt ik = - map2 ik {f2= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.lt ik)} - - let gt ik = - map2 ik {f2= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.gt ik)} - - let le ik = - map2 ik {f2= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.le ik)} - - let ge ik = - map2 ik {f2= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.ge ik)} - - let eq ik = - map2 ik {f2= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.eq ik)} - - let ne ik = - map2 ik {f2= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.ne ik)} - - let logand ik = - map2 ik {f2= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.logand ik)} - - let logor ik = - map2 ik {f2= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.logor ik)} - - let logxor ik = - map2 ik {f2= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.logxor ik)} - - let shift_left ik = - map2ovc ik {f2_ovc= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.shift_left ik)} - - let shift_right ik = - map2ovc ik {f2_ovc= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.shift_right ik)} - - let c_logand ik = - map2 ik {f2= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.c_logand ik)} - - let c_logor ik = - map2 ik {f2= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.c_logor ik)} - - - (* printing boilerplate *) - let pretty_diff () (x,y) = dprintf "%a instead of %a" pretty x pretty y - let printXml f x = - match to_int x with - | Some v when not (GobConfig.get_bool "dbg.full-output") -> BatPrintf.fprintf f "\n\n%s\n\n\n" (Z.to_string v) - | _ -> BatPrintf.fprintf f "\n\n%s\n\n\n" (show x) - - let invariant_ikind e ik x = - match to_int x with - | Some v -> - if get_bool "witness.invariant.exact" then - (* If definite, output single equality instead of every subdomain repeating same equality *) - Invariant.of_exp Cil.(BinOp (Eq, e, kintegerCilint ik v, intType)) - else - Invariant.top () - | None -> - let is = to_list (mapp { fp = fun (type a) (module I:SOverflow with type t = a) -> I.invariant_ikind e ik } x) - in List.fold_left (fun a i -> - Invariant.(a && i) - ) (Invariant.top ()) is - - let arbitrary ik = QCheck.(set_print show @@ tup5 (option (I1.arbitrary ik)) (option (I2.arbitrary ik)) (option (I3.arbitrary ik)) (option (I4.arbitrary ik)) (option (I5.arbitrary ik))) - - let relift (a, b, c, d, e) = - (Option.map I1.relift a, Option.map I2.relift b, Option.map I3.relift c, Option.map I4.relift d, Option.map I5.relift e) -end - -module IntDomTuple = -struct - module I = IntDomLifter (IntDomTupleImpl) - include I - - let top () = failwith "top in IntDomTuple not supported. Use top_of instead." - let no_interval (x: I.t) = {x with v = IntDomTupleImpl.no_interval x.v} - - let no_intervalSet (x: I.t) = {x with v = IntDomTupleImpl.no_intervalSet x.v} -end - -let of_const (i, ik, str) = IntDomTuple.of_int ik i +include IntDomain0 + +include IntervalDomain +include IntervalSetDomain +include DefExcDomain +include EnumsDomain +include CongruenceDomain +include IntDomTuple diff --git a/src/cdomain/value/cdomains/intDomain.mli b/src/cdomain/value/cdomains/intDomain.mli index ca64692290..e7667c9b14 100644 --- a/src/cdomain/value/cdomains/intDomain.mli +++ b/src/cdomain/value/cdomains/intDomain.mli @@ -436,27 +436,3 @@ module Reverse (Base: IkindUnawareS): IkindUnawareS with type t = Base.t and typ (* module IncExcInterval : S with type t = [ | `Excluded of Interval.t| `Included of Interval.t ] *) (** Inclusive and exclusive intervals. Warning: NOT A LATTICE *) module Enums : S with type int_t = Z.t - -(** {b Boolean domains} *) - -module type BooleansNames = -sig - val truename: string - (** The name of the [true] abstract value *) - - val falsename: string - (** The name of the [false] abstract value *) -end -(** Parameter signature for the [MakeBooleans] functor. *) - -module MakeBooleans (Names: BooleansNames): IkindUnawareS with type t = bool -(** Creates an abstract domain for integers represented by boolean values. *) - -module Booleans: IkindUnawareS with type t = bool -(** Boolean abstract domain, where true is output "True" and false is output - * "False" *) - -(* -module None: S with type t = unit -(** Domain with nothing in it. *) -*) diff --git a/src/cdomain/value/cdomains/intDomain0.ml b/src/cdomain/value/cdomains/intDomain0.ml new file mode 100644 index 0000000000..61a5f2c19b --- /dev/null +++ b/src/cdomain/value/cdomains/intDomain0.ml @@ -0,0 +1,922 @@ +open GobConfig +open GoblintCil +open Pretty +open PrecisionUtil + +module M = Messages + +let (%) = Batteries.(%) +let (|?) = Batteries.(|?) + +exception IncompatibleIKinds of string +exception Unknown +exception Error +exception ArithmeticOnIntegerBot of string + + + + +(** Define records that hold mutable variables representing different Configuration values. + * These values are used to keep track of whether or not the corresponding Config values are en-/disabled *) +type ana_int_config_values = { + mutable interval_threshold_widening : bool option; + mutable interval_narrow_by_meet : bool option; + mutable def_exc_widen_by_join : bool option; + mutable interval_threshold_widening_constants : string option; + mutable refinement : string option; +} + +let ana_int_config: ana_int_config_values = { + interval_threshold_widening = None; + interval_narrow_by_meet = None; + def_exc_widen_by_join = None; + interval_threshold_widening_constants = None; + refinement = None; +} + +let get_interval_threshold_widening () = + if ana_int_config.interval_threshold_widening = None then + ana_int_config.interval_threshold_widening <- Some (get_bool "ana.int.interval_threshold_widening"); + Option.get ana_int_config.interval_threshold_widening + +let get_interval_narrow_by_meet () = + if ana_int_config.interval_narrow_by_meet = None then + ana_int_config.interval_narrow_by_meet <- Some (get_bool "ana.int.interval_narrow_by_meet"); + Option.get ana_int_config.interval_narrow_by_meet + +let get_def_exc_widen_by_join () = + if ana_int_config.def_exc_widen_by_join = None then + ana_int_config.def_exc_widen_by_join <- Some (get_bool "ana.int.def_exc_widen_by_join"); + Option.get ana_int_config.def_exc_widen_by_join + +let get_interval_threshold_widening_constants () = + if ana_int_config.interval_threshold_widening_constants = None then + ana_int_config.interval_threshold_widening_constants <- Some (get_string "ana.int.interval_threshold_widening_constants"); + Option.get ana_int_config.interval_threshold_widening_constants + +let get_refinement () = + if ana_int_config.refinement = None then + ana_int_config.refinement <- Some (get_string "ana.int.refinement"); + Option.get ana_int_config.refinement + + + +(** Whether for a given ikind, we should compute with wrap-around arithmetic. + * Always for unsigned types, for signed types if 'sem.int.signed_overflow' is 'assume_wraparound' *) +let should_wrap ik = not (Cil.isSigned ik) || get_string "sem.int.signed_overflow" = "assume_wraparound" + +(** Whether for a given ikind, we should assume there are no overflows. + * Always false for unsigned types, true for signed types if 'sem.int.signed_overflow' is 'assume_none' *) +let should_ignore_overflow ik = Cil.isSigned ik && get_string "sem.int.signed_overflow" = "assume_none" + +let widening_thresholds = ResettableLazy.from_fun WideningThresholds.thresholds +let widening_thresholds_desc = ResettableLazy.from_fun (List.rev % WideningThresholds.thresholds) + +type overflow_info = { overflow: bool; underflow: bool;} + +let set_overflow_flag ~cast ~underflow ~overflow ik = + if !AnalysisState.executing_speculative_computations then + (* Do not produce warnings when the operations are not actually happening in code *) + () + else + let signed = Cil.isSigned ik in + if !AnalysisState.postsolving && signed && not cast then + AnalysisState.svcomp_may_overflow := true; + let sign = if signed then "Signed" else "Unsigned" in + match underflow, overflow with + | true, true -> + M.warn ~category:M.Category.Integer.overflow ~tags:[CWE 190; CWE 191] "%s integer overflow and underflow" sign + | true, false -> + M.warn ~category:M.Category.Integer.overflow ~tags:[CWE 191] "%s integer underflow" sign + | false, true -> + M.warn ~category:M.Category.Integer.overflow ~tags:[CWE 190] "%s integer overflow" sign + | false, false -> assert false + +let reset_lazy () = + ResettableLazy.reset widening_thresholds; + ResettableLazy.reset widening_thresholds_desc; + ana_int_config.interval_threshold_widening <- None; + ana_int_config.interval_narrow_by_meet <- None; + ana_int_config.def_exc_widen_by_join <- None; + ana_int_config.interval_threshold_widening_constants <- None; + ana_int_config.refinement <- None + +module type Arith = +sig + type t + val neg: t -> t + val add: t -> t -> t + val sub: t -> t -> t + val mul: t -> t -> t + val div: t -> t -> t + val rem: t -> t -> t + + val lt: t -> t -> t + val gt: t -> t -> t + val le: t -> t -> t + val ge: t -> t -> t + val eq: t -> t -> t + val ne: t -> t -> t + + val lognot: t -> t + val logand: t -> t -> t + val logor : t -> t -> t + val logxor: t -> t -> t + + val shift_left : t -> t -> t + val shift_right: t -> t -> t + + val c_lognot: t -> t + val c_logand: t -> t -> t + val c_logor : t -> t -> t + +end + +module type ArithIkind = +sig + type t + val neg: Cil.ikind -> t -> t + val add: Cil.ikind -> t -> t -> t + val sub: Cil.ikind -> t -> t -> t + val mul: Cil.ikind -> t -> t -> t + val div: Cil.ikind -> t -> t -> t + val rem: Cil.ikind -> t -> t -> t + + val lt: Cil.ikind -> t -> t -> t + val gt: Cil.ikind -> t -> t -> t + val le: Cil.ikind -> t -> t -> t + val ge: Cil.ikind -> t -> t -> t + val eq: Cil.ikind -> t -> t -> t + val ne: Cil.ikind -> t -> t -> t + + val lognot: Cil.ikind -> t -> t + val logand: Cil.ikind -> t -> t -> t + val logor : Cil.ikind -> t -> t -> t + val logxor: Cil.ikind -> t -> t -> t + + val shift_left : Cil.ikind -> t -> t -> t + val shift_right: Cil.ikind -> t -> t -> t + + val c_lognot: Cil.ikind -> t -> t + val c_logand: Cil.ikind -> t -> t -> t + val c_logor : Cil.ikind -> t -> t -> t + +end + +(* Shared functions between S and Z *) +module type B = +sig + include Lattice.S + type int_t + val bot_of: Cil.ikind -> t + val top_of: Cil.ikind -> t + val to_int: t -> int_t option + val equal_to: int_t -> t -> [`Eq | `Neq | `Top] + + val to_bool: t -> bool option + val to_excl_list: t -> (int_t list * (int64 * int64)) option + val of_excl_list: Cil.ikind -> int_t list -> t + val is_excl_list: t -> bool + + val to_incl_list: t -> int_t list option + + val maximal : t -> int_t option + val minimal : t -> int_t option + + val cast_to: ?suppress_ovwarn:bool -> ?torg:Cil.typ -> Cil.ikind -> t -> t +end + +(** Interface of IntDomain implementations that do not take ikinds for arithmetic operations yet. TODO: Should be ported to S in the future. *) +module type IkindUnawareS = +sig + include B + include Arith with type t := t + val starting : ?suppress_ovwarn:bool -> Cil.ikind -> int_t -> t + val ending : ?suppress_ovwarn:bool -> Cil.ikind -> int_t -> t + val of_int: int_t -> t + val of_bool: bool -> t + val of_interval: ?suppress_ovwarn:bool -> Cil.ikind -> int_t * int_t -> t + val of_congruence: Cil.ikind -> int_t * int_t -> t + val arbitrary: unit -> t QCheck.arbitrary + val invariant: Cil.exp -> t -> Invariant.t +end + +(** Interface of IntDomain implementations taking an ikind for arithmetic operations *) +module type S = +sig + include B + include ArithIkind with type t:= t + + val add : ?no_ov:bool -> Cil.ikind -> t -> t -> t + val sub : ?no_ov:bool -> Cil.ikind -> t -> t -> t + val mul : ?no_ov:bool -> Cil.ikind -> t -> t -> t + val div : ?no_ov:bool -> Cil.ikind -> t -> t -> t + val neg : ?no_ov:bool -> Cil.ikind -> t -> t + val cast_to : ?suppress_ovwarn:bool -> ?torg:Cil.typ -> ?no_ov:bool -> Cil.ikind -> t -> t + + val join: Cil.ikind -> t -> t -> t + val meet: Cil.ikind -> t -> t -> t + val narrow: Cil.ikind -> t -> t -> t + val widen: Cil.ikind -> t -> t -> t + val starting : ?suppress_ovwarn:bool -> Cil.ikind -> int_t -> t + val ending : ?suppress_ovwarn:bool -> Cil.ikind -> int_t -> t + val of_int: Cil.ikind -> int_t -> t + val of_bool: Cil.ikind -> bool -> t + val of_interval: ?suppress_ovwarn:bool -> Cil.ikind -> int_t * int_t -> t + val of_congruence: Cil.ikind -> int_t * int_t -> t + val is_top_of: Cil.ikind -> t -> bool + val invariant_ikind : Cil.exp -> Cil.ikind -> t -> Invariant.t + + val refine_with_congruence: Cil.ikind -> t -> (int_t * int_t) option -> t + val refine_with_interval: Cil.ikind -> t -> (int_t * int_t) option -> t + val refine_with_excl_list: Cil.ikind -> t -> (int_t list * (int64 * int64)) option -> t + val refine_with_incl_list: Cil.ikind -> t -> int_t list option -> t + + val project: Cil.ikind -> int_precision -> t -> t + val arbitrary: Cil.ikind -> t QCheck.arbitrary +end + +module type SOverflow = +sig + + include S + + val add : ?no_ov:bool -> Cil.ikind -> t -> t -> t * overflow_info + + val sub : ?no_ov:bool -> Cil.ikind -> t -> t -> t * overflow_info + + val mul : ?no_ov:bool -> Cil.ikind -> t -> t -> t * overflow_info + + val div : ?no_ov:bool -> Cil.ikind -> t -> t -> t * overflow_info + + val neg : ?no_ov:bool -> Cil.ikind -> t -> t * overflow_info + + val cast_to : ?suppress_ovwarn:bool -> ?torg:Cil.typ -> ?no_ov:bool -> Cil.ikind -> t -> t * overflow_info + + val of_int : Cil.ikind -> int_t -> t * overflow_info + + val of_interval: ?suppress_ovwarn:bool -> Cil.ikind -> int_t * int_t -> t * overflow_info + + val starting : ?suppress_ovwarn:bool -> Cil.ikind -> int_t -> t * overflow_info + val ending : ?suppress_ovwarn:bool -> Cil.ikind -> int_t -> t * overflow_info + + val shift_left : Cil.ikind -> t -> t -> t * overflow_info + + val shift_right : Cil.ikind -> t -> t -> t * overflow_info +end + +module type Y = +sig + (* include B *) + include B + include Arith with type t:= t + val of_int: Cil.ikind -> int_t -> t + val of_bool: Cil.ikind -> bool -> t + val of_interval: ?suppress_ovwarn:bool -> Cil.ikind -> int_t * int_t -> t + val of_congruence: Cil.ikind -> int_t * int_t -> t + + val starting : ?suppress_ovwarn:bool -> Cil.ikind -> int_t -> t + val ending : ?suppress_ovwarn:bool -> Cil.ikind -> int_t -> t + val is_top_of: Cil.ikind -> t -> bool + + val project: int_precision -> t -> t + val invariant: Cil.exp -> t -> Invariant.t +end + +module type Z = Y with type int_t = Z.t + + +module IntDomLifter (I : S) = +struct + open Cil + type int_t = I.int_t + type t = { v : I.t; ikind : CilType.Ikind.t } [@@deriving eq, ord, hash] + + let ikind {ikind; _} = ikind + + (* Helper functions *) + let check_ikinds x y = if x.ikind <> y.ikind then raise (IncompatibleIKinds (GobPretty.sprintf "ikinds %a and %a are incompatible. Values: %a and %a" CilType.Ikind.pretty x.ikind CilType.Ikind.pretty y.ikind I.pretty x.v I.pretty y.v)) + let lift op x = {x with v = op x.ikind x.v } + (* For logical operations the result is of type int *) + let lift_logical op x = {v = op x.ikind x.v; ikind = Cil.IInt} + let lift2 op x y = check_ikinds x y; {x with v = op x.ikind x.v y.v } + let lift2_cmp op x y = check_ikinds x y; {v = op x.ikind x.v y.v; ikind = Cil.IInt} + + let bot_of ikind = { v = I.bot_of ikind; ikind} + let bot () = failwith "bot () is not implemented for IntDomLifter." + let is_bot x = I.is_bot x.v + let top_of ikind = { v = I.top_of ikind; ikind} + let top () = failwith "top () is not implemented for IntDomLifter." + let is_top x = I.is_top x.v + + (* Leq does not check for ikind, because it is used in invariant with arguments of different type. + TODO: check ikinds here and fix invariant to work with right ikinds *) + let leq x y = I.leq x.v y.v + let join = lift2 I.join + let meet = lift2 I.meet + let widen = lift2 I.widen + let narrow = lift2 I.narrow + + let show x = + if not (GobConfig.get_bool "dbg.full-output") && I.is_top_of x.ikind x.v then + "⊤" + else + I.show x.v (* TODO add ikind to output *) + let pretty () x = + if not (GobConfig.get_bool "dbg.full-output") && I.is_top_of x.ikind x.v then + Pretty.text "⊤" + else + I.pretty () x.v (* TODO add ikind to output *) + let pretty_diff () (x, y) = I.pretty_diff () (x.v, y.v) (* TODO check ikinds, add them to output *) + let printXml o x = + if not (GobConfig.get_bool "dbg.full-output") && I.is_top_of x.ikind x.v then + BatPrintf.fprintf o "\n\n⊤\n\n\n" + else + I.printXml o x.v (* TODO add ikind to output *) + (* This is for debugging *) + let name () = "IntDomLifter(" ^ (I.name ()) ^ ")" + let to_yojson x = I.to_yojson x.v + let invariant e x = + let e' = Cilfacade.mkCast ~e ~newt:(TInt (x.ikind, [])) in + I.invariant_ikind e' x.ikind x.v + let tag x = I.tag x.v + let arbitrary ik = failwith @@ "Arbitrary not implement for " ^ (name ()) ^ "." + let to_int x = I.to_int x.v + let of_int ikind x = { v = I.of_int ikind x; ikind} + let equal_to i x = I.equal_to i x.v + let to_bool x = I.to_bool x.v + let of_bool ikind b = { v = I.of_bool ikind b; ikind} + let to_excl_list x = I.to_excl_list x.v + let of_excl_list ikind is = {v = I.of_excl_list ikind is; ikind} + let is_excl_list x = I.is_excl_list x.v + let to_incl_list x = I.to_incl_list x.v + let of_interval ?(suppress_ovwarn=false) ikind (lb,ub) = {v = I.of_interval ~suppress_ovwarn ikind (lb,ub); ikind} + let of_congruence ikind (c,m) = {v = I.of_congruence ikind (c,m); ikind} + let starting ?(suppress_ovwarn=false) ikind i = {v = I.starting ~suppress_ovwarn ikind i; ikind} + let ending ?(suppress_ovwarn=false) ikind i = {v = I.ending ~suppress_ovwarn ikind i; ikind} + let maximal x = I.maximal x.v + let minimal x = I.minimal x.v + + let neg = lift I.neg + let add = lift2 I.add + let sub = lift2 I.sub + let mul = lift2 I.mul + let div = lift2 I.div + let rem = lift2 I.rem + let lt = lift2_cmp I.lt + let gt = lift2_cmp I.gt + let le = lift2_cmp I.le + let ge = lift2_cmp I.ge + let eq = lift2_cmp I.eq + let ne = lift2_cmp I.ne + let lognot = lift I.lognot + let logand = lift2 I.logand + let logor = lift2 I.logor + let logxor = lift2 I.logxor + let shift_left x y = {x with v = I.shift_left x.ikind x.v y.v } (* TODO check ikinds*) + let shift_right x y = {x with v = I.shift_right x.ikind x.v y.v } (* TODO check ikinds*) + let c_lognot = lift_logical I.c_lognot + let c_logand = lift2 I.c_logand + let c_logor = lift2 I.c_logor + + let cast_to ?(suppress_ovwarn=false) ?torg ikind x = {v = I.cast_to ~suppress_ovwarn ~torg:(TInt(x.ikind,[])) ikind x.v; ikind} + + let is_top_of ik x = ik = x.ikind && I.is_top_of ik x.v + + let relift x = { v = I.relift x.v; ikind = x.ikind } + + let project p v = { v = I.project v.ikind p v.v; ikind = v.ikind } +end + +module type Ikind = +sig + val ikind: unit -> Cil.ikind +end + +module PtrDiffIkind : Ikind = +struct + let ikind = Cilfacade.ptrdiff_ikind +end + +module IntDomWithDefaultIkind (I: Y) (Ik: Ikind) : Y with type t = I.t and type int_t = I.int_t = +struct + include I + let top () = I.top_of (Ik.ikind ()) + let bot () = I.bot_of (Ik.ikind ()) +end + +module Size = struct (* size in bits as int, range as int64 *) + open Cil + let sign x = if Z.compare x Z.zero < 0 then `Signed else `Unsigned + + let top_typ = TInt (ILongLong, []) + let min_for x = intKindForValue x (sign x = `Unsigned) + let bit = function (* bits needed for representation *) + | IBool -> 1 + | ik -> bytesSizeOfInt ik * 8 + let is_int64_big_int x = Z.fits_int64 x + let card ik = (* cardinality *) + let b = bit ik in + Z.shift_left Z.one b + let bits ik = (* highest bits for neg/pos values *) + let s = bit ik in + if isSigned ik then s-1, s-1 else 0, s + let bits_i64 ik = BatTuple.Tuple2.mapn Int64.of_int (bits ik) + let range ik = + let a,b = bits ik in + let x = if isSigned ik then Z.neg (Z.shift_left Z.one a) (* -2^a *) else Z.zero in + let y = Z.pred (Z.shift_left Z.one b) in (* 2^b - 1 *) + x,y + + let is_cast_injective ~from_type ~to_type = + let (from_min, from_max) = range (Cilfacade.get_ikind from_type) in + let (to_min, to_max) = range (Cilfacade.get_ikind to_type) in + if M.tracing then M.trace "int" "is_cast_injective %a (%a, %a) -> %a (%a, %a)" CilType.Typ.pretty from_type GobZ.pretty from_min GobZ.pretty from_max CilType.Typ.pretty to_type GobZ.pretty to_min GobZ.pretty to_max; + Z.compare to_min from_min <= 0 && Z.compare from_max to_max <= 0 + + let cast t x = (* TODO: overflow is implementation-dependent! *) + if t = IBool then + (* C11 6.3.1.2 Boolean type *) + if Z.equal x Z.zero then Z.zero else Z.one + else + let a,b = range t in + let c = card t in + let y = Z.erem x c in + let y = if Z.gt y b then Z.sub y c + else if Z.lt y a then Z.add y c + else y + in + if M.tracing then M.tracel "cast" "Cast %a to range [%a, %a] (%a) = %a (%s in int64)" GobZ.pretty x GobZ.pretty a GobZ.pretty b GobZ.pretty c GobZ.pretty y (if is_int64_big_int y then "fits" else "does not fit"); + y + + let min_range_sign_agnostic x = + let size ik = + let a,b = bits_i64 ik in + Int64.neg a,b + in + if sign x = `Signed then + size (min_for x) + else + let a, b = size (min_for x) in + if b <= 64L then + let upper_bound_less = Int64.sub b 1L in + let max_one_less = Z.(pred @@ shift_left Z.one (Int64.to_int upper_bound_less)) in + if x <= max_one_less then + a, upper_bound_less + else + a,b + else + a, b + + (* From the number of bits used to represent a positive value, determines the maximal representable value *) + let max_from_bit_range pos_bits = Z.(pred @@ shift_left Z.one (to_int (Z.of_int64 pos_bits))) + + (* From the number of bits used to represent a non-positive value, determines the minimal representable value *) + let min_from_bit_range neg_bits = Z.(if neg_bits = 0L then Z.zero else neg @@ shift_left Z.one (to_int (neg (Z.of_int64 neg_bits)))) + +end + + +module StdTop (B: sig type t val top_of: Cil.ikind -> t end) = struct + open B + (* these should be overwritten for better precision if possible: *) + let to_excl_list x = None + let of_excl_list ik x = top_of ik + let is_excl_list x = false + let to_incl_list x = None + let of_interval ?(suppress_ovwarn=false) ik x = top_of ik + let of_congruence ik x = top_of ik + let starting ?(suppress_ovwarn=false) ik x = top_of ik + let ending ?(suppress_ovwarn=false) ik x = top_of ik + let maximal x = None + let minimal x = None +end + +module Std (B: sig + type t + val name: unit -> string + val top_of: Cil.ikind -> t + val bot_of: Cil.ikind -> t + val show: t -> string + val equal: t -> t -> bool + end) = struct + include Printable.StdLeaf + let name = B.name (* overwrite the one from Printable.Std *) + open B + let is_top x = failwith "is_top not implemented for IntDomain.Std" + let is_bot x = B.equal x (bot_of Cil.IInt) (* Here we assume that the representation of bottom is independent of the ikind + This may be true for intdomain implementations, but not e.g. for IntDomLifter. *) + let is_top_of ik x = B.equal x (top_of ik) + + (* all output is based on B.show *) + include Printable.SimpleShow ( + struct + type nonrec t = t + let show = show + end + ) + let pretty_diff () (x,y) = dprintf "%s: %a instead of %a" (name ()) pretty x pretty y + + include StdTop (B) +end + +(* Textbook interval arithmetic, without any overflow handling etc. *) +module IntervalArith (Ints_t : IntOps.IntOps) = struct + let min4 a b c d = Ints_t.min (Ints_t.min a b) (Ints_t.min c d) + let max4 a b c d = Ints_t.max (Ints_t.max a b) (Ints_t.max c d) + + let mul (x1, x2) (y1, y2) = + let x1y1 = (Ints_t.mul x1 y1) in + let x1y2 = (Ints_t.mul x1 y2) in + let x2y1 = (Ints_t.mul x2 y1) in + let x2y2 = (Ints_t.mul x2 y2) in + (min4 x1y1 x1y2 x2y1 x2y2, max4 x1y1 x1y2 x2y1 x2y2) + + let shift_left (x1,x2) (y1,y2) = + let y1p = Ints_t.shift_left Ints_t.one y1 in + let y2p = Ints_t.shift_left Ints_t.one y2 in + mul (x1, x2) (y1p, y2p) + + let div (x1, x2) (y1, y2) = + let x1y1n = (Ints_t.div x1 y1) in + let x1y2n = (Ints_t.div x1 y2) in + let x2y1n = (Ints_t.div x2 y1) in + let x2y2n = (Ints_t.div x2 y2) in + let x1y1p = (Ints_t.div x1 y1) in + let x1y2p = (Ints_t.div x1 y2) in + let x2y1p = (Ints_t.div x2 y1) in + let x2y2p = (Ints_t.div x2 y2) in + (min4 x1y1n x1y2n x2y1n x2y2n, max4 x1y1p x1y2p x2y1p x2y2p) + + let add (x1, x2) (y1, y2) = (Ints_t.add x1 y1, Ints_t.add x2 y2) + let sub (x1, x2) (y1, y2) = (Ints_t.sub x1 y2, Ints_t.sub x2 y1) + + let neg (x1, x2) = (Ints_t.neg x2, Ints_t.neg x1) + + let one = (Ints_t.one, Ints_t.one) + let zero = (Ints_t.zero, Ints_t.zero) + let top_bool = (Ints_t.zero, Ints_t.one) + + let to_int (x1, x2) = + if Ints_t.equal x1 x2 then Some x1 else None + + let upper_threshold u max_ik = + let ts = if get_interval_threshold_widening_constants () = "comparisons" then WideningThresholds.upper_thresholds () else ResettableLazy.force widening_thresholds in + let u = Ints_t.to_bigint u in + let max_ik' = Ints_t.to_bigint max_ik in + let t = List.find_opt (fun x -> Z.compare u x <= 0 && Z.compare x max_ik' <= 0) ts in + BatOption.map_default Ints_t.of_bigint max_ik t + let lower_threshold l min_ik = + let ts = if get_interval_threshold_widening_constants () = "comparisons" then WideningThresholds.lower_thresholds () else ResettableLazy.force widening_thresholds_desc in + let l = Ints_t.to_bigint l in + let min_ik' = Ints_t.to_bigint min_ik in + let t = List.find_opt (fun x -> Z.compare l x >= 0 && Z.compare x min_ik' >= 0) ts in + BatOption.map_default Ints_t.of_bigint min_ik t + let is_upper_threshold u = + let ts = if get_interval_threshold_widening_constants () = "comparisons" then WideningThresholds.upper_thresholds () else ResettableLazy.force widening_thresholds in + let u = Ints_t.to_bigint u in + List.exists (Z.equal u) ts + let is_lower_threshold l = + let ts = if get_interval_threshold_widening_constants () = "comparisons" then WideningThresholds.lower_thresholds () else ResettableLazy.force widening_thresholds_desc in + let l = Ints_t.to_bigint l in + List.exists (Z.equal l) ts +end + +module IntInvariant = +struct + let of_int e ik x = + if get_bool "witness.invariant.exact" then + Invariant.of_exp Cil.(BinOp (Eq, e, kintegerCilint ik x, intType)) + else + Invariant.none + + let of_incl_list e ik ps = + match ps with + | [_; _] when ik = IBool && not (get_bool "witness.invariant.inexact-type-bounds") -> + assert (List.mem Z.zero ps); + assert (List.mem Z.one ps); + Invariant.none + | [_] when get_bool "witness.invariant.exact" -> + Invariant.none + | _ :: _ :: _ + | [_] | [] -> + List.fold_left (fun a x -> + let i = Invariant.of_exp Cil.(BinOp (Eq, e, kintegerCilint ik x, intType)) in + Invariant.(a || i) [@coverage off] (* bisect_ppx cannot handle redefined (||) *) + ) (Invariant.bot ()) ps + + let of_interval_opt e ik = function + | (Some x1, Some x2) when Z.equal x1 x2 -> + of_int e ik x1 + | x1_opt, x2_opt -> + let (min_ik, max_ik) = Size.range ik in + let inexact_type_bounds = get_bool "witness.invariant.inexact-type-bounds" in + let i1 = + match x1_opt, inexact_type_bounds with + | Some x1, false when Z.equal min_ik x1 -> Invariant.none + | Some x1, _ -> Invariant.of_exp Cil.(BinOp (Le, kintegerCilint ik x1, e, intType)) + | None, _ -> Invariant.none + in + let i2 = + match x2_opt, inexact_type_bounds with + | Some x2, false when Z.equal x2 max_ik -> Invariant.none + | Some x2, _ -> Invariant.of_exp Cil.(BinOp (Le, e, kintegerCilint ik x2, intType)) + | None, _ -> Invariant.none + in + Invariant.(i1 && i2) + + let of_interval e ik (x1, x2) = + of_interval_opt e ik (Some x1, Some x2) + + let of_excl_list e ik ns = + List.fold_left (fun a x -> + let i = Invariant.of_exp Cil.(BinOp (Ne, e, kintegerCilint ik x, intType)) in + Invariant.(a && i) + ) (Invariant.top ()) ns +end + +module SOverflowUnlifter (D : SOverflow) : S with type int_t = D.int_t and type t = D.t = struct + include D + + let add ?no_ov ik x y = fst @@ D.add ?no_ov ik x y + + let sub ?no_ov ik x y = fst @@ D.sub ?no_ov ik x y + + let mul ?no_ov ik x y = fst @@ D.mul ?no_ov ik x y + + let div ?no_ov ik x y = fst @@ D.div ?no_ov ik x y + + let neg ?no_ov ik x = fst @@ D.neg ?no_ov ik x + + let cast_to ?suppress_ovwarn ?torg ?no_ov ik x = fst @@ D.cast_to ?suppress_ovwarn ?torg ?no_ov ik x + + let of_int ik x = fst @@ D.of_int ik x + + let of_interval ?suppress_ovwarn ik x = fst @@ D.of_interval ?suppress_ovwarn ik x + + let starting ?suppress_ovwarn ik x = fst @@ D.starting ?suppress_ovwarn ik x + + let ending ?suppress_ovwarn ik x = fst @@ D.ending ?suppress_ovwarn ik x + + let shift_left ik x y = fst @@ D.shift_left ik x y + + let shift_right ik x y = fst @@ D.shift_right ik x y +end + +module IntIkind = struct let ikind () = Cil.IInt end + +module Integers (Ints_t : IntOps.IntOps): IkindUnawareS with type t = Ints_t.t and type int_t = Ints_t.t = (* no top/bot, order is <= *) +struct + include Printable.Std + let name () = "integers" + type t = Ints_t.t [@@deriving eq, ord, hash] + type int_t = Ints_t.t + let top () = raise Unknown + let bot () = raise Error + let top_of ik = top () + let bot_of ik = bot () + let show (x: Ints_t.t) = Ints_t.to_string x + + include Std (struct type nonrec t = t let name = name let top_of = top_of let bot_of = bot_of let show = show let equal = equal end) + (* is_top and is_bot are never called, but if they were, the Std impl would raise their exception, so we overwrite them: *) + let is_top _ = false + let is_bot _ = false + + let equal_to i x = if i > x then `Neq else `Top + let leq x y = x <= y + let join x y = if Ints_t.compare x y > 0 then x else y + let widen = join + let meet x y = if Ints_t.compare x y > 0 then y else x + let narrow = meet + + let of_bool x = if x then Ints_t.one else Ints_t.zero + let to_bool' x = x <> Ints_t.zero + let to_bool x = Some (to_bool' x) + let of_int x = x + let to_int x = Some x + + let neg = Ints_t.neg + let add = Ints_t.add (* TODO: signed overflow is undefined behavior! *) + let sub = Ints_t.sub + let mul = Ints_t.mul + let div = Ints_t.div + let rem = Ints_t.rem + let lt n1 n2 = of_bool (n1 < n2) + let gt n1 n2 = of_bool (n1 > n2) + let le n1 n2 = of_bool (n1 <= n2) + let ge n1 n2 = of_bool (n1 >= n2) + let eq n1 n2 = of_bool (n1 = n2) + let ne n1 n2 = of_bool (n1 <> n2) + let lognot = Ints_t.lognot + let logand = Ints_t.logand + let logor = Ints_t.logor + let logxor = Ints_t.logxor + let shift_left n1 n2 = Ints_t.shift_left n1 (Ints_t.to_int n2) + let shift_right n1 n2 = Ints_t.shift_right n1 (Ints_t.to_int n2) + let c_lognot n1 = of_bool (not (to_bool' n1)) + let c_logand n1 n2 = of_bool ((to_bool' n1) && (to_bool' n2)) + let c_logor n1 n2 = of_bool ((to_bool' n1) || (to_bool' n2)) + let cast_to ?(suppress_ovwarn=false) ?torg t x = failwith @@ "Cast_to not implemented for " ^ (name ()) ^ "." + let arbitrary ik = QCheck.map ~rev:Ints_t.to_int64 Ints_t.of_int64 GobQCheck.Arbitrary.int64 (* TODO: use ikind *) + let invariant _ _ = Invariant.none (* TODO *) +end + +module FlatPureIntegers: IkindUnawareS with type t = int64 and type int_t = int64 = (* Integers, but raises Unknown/Error on join/meet *) +struct + include Integers(IntOps.Int64Ops) + let top () = raise Unknown + let bot () = raise Error + let leq = equal + let pretty_diff () (x,y) = Pretty.dprintf "Integer %a instead of %a" pretty x pretty y + let join x y = if equal x y then x else top () + let meet x y = if equal x y then x else bot () +end + +module Flat (Base: IkindUnawareS): IkindUnawareS with type t = [ `Bot | `Lifted of Base.t | `Top ] and type int_t = Base.int_t = (* identical to Lift, but goes to `Top/`Bot if Base raises Unknown/Error *) +struct + type int_t = Base.int_t + include Lattice.FlatConf (struct + include Printable.DefaultConf + let top_name = "Unknown int" + let bot_name = "Error int" + end) (Base) + + let top_of ik = top () + let bot_of ik = bot () + + + let name () = "flat integers" + let cast_to ?(suppress_ovwarn=false) ?torg t = function + | `Lifted x -> `Lifted (Base.cast_to t x) + | x -> x + + let equal_to i = function + | `Bot -> failwith "unsupported: equal_to with bottom" + | `Top -> `Top + | `Lifted x -> Base.equal_to i x + + let of_int x = `Lifted (Base.of_int x) + let to_int x = match x with + | `Lifted x -> Base.to_int x + | _ -> None + + let of_bool x = `Lifted (Base.of_bool x) + let to_bool x = match x with + | `Lifted x -> Base.to_bool x + | _ -> None + + let to_excl_list x = None + let of_excl_list ik x = top_of ik + let is_excl_list x = false + let to_incl_list x = None + let of_interval ?(suppress_ovwarn=false) ik x = top_of ik + let of_congruence ik x = top_of ik + let starting ?(suppress_ovwarn=false) ikind x = top_of ikind + let ending ?(suppress_ovwarn=false) ikind x = top_of ikind + let maximal x = None + let minimal x = None + + let lift1 f x = match x with + | `Lifted x -> + (try `Lifted (f x) with Unknown -> `Top | Error -> `Bot) + | x -> x + let lift2 f x y = match x,y with + | `Lifted x, `Lifted y -> + (try `Lifted (f x y) with Unknown -> `Top | Error -> `Bot) + | `Bot, `Bot -> `Bot + | _ -> `Top + + let neg = lift1 Base.neg + let add = lift2 Base.add + let sub = lift2 Base.sub + let mul = lift2 Base.mul + let div = lift2 Base.div + let rem = lift2 Base.rem + let lt = lift2 Base.lt + let gt = lift2 Base.gt + let le = lift2 Base.le + let ge = lift2 Base.ge + let eq = lift2 Base.eq + let ne = lift2 Base.ne + let lognot = lift1 Base.lognot + let logand = lift2 Base.logand + let logor = lift2 Base.logor + let logxor = lift2 Base.logxor + let shift_left = lift2 Base.shift_left + let shift_right = lift2 Base.shift_right + let c_lognot = lift1 Base.c_lognot + let c_logand = lift2 Base.c_logand + let c_logor = lift2 Base.c_logor + + let invariant e = function + | `Lifted x -> Base.invariant e x + | `Top | `Bot -> Invariant.none +end + +module Lift (Base: IkindUnawareS): IkindUnawareS with type t = [ `Bot | `Lifted of Base.t | `Top ] and type int_t = Base.int_t = (* identical to Flat, but does not go to `Top/Bot` if Base raises Unknown/Error *) +struct + include Lattice.LiftPO (struct + include Printable.DefaultConf + let top_name = "MaxInt" + let bot_name = "MinInt" + end) (Base) + type int_t = Base.int_t + let top_of ik = top () + let bot_of ik = bot () + include StdTop (struct type nonrec t = t let top_of = top_of end) + + let name () = "lifted integers" + let cast_to ?(suppress_ovwarn=false) ?torg t = function + | `Lifted x -> `Lifted (Base.cast_to t x) + | x -> x + + let equal_to i = function + | `Bot -> failwith "unsupported: equal_to with bottom" + | `Top -> `Top + | `Lifted x -> Base.equal_to i x + + let of_int x = `Lifted (Base.of_int x) + let to_int x = match x with + | `Lifted x -> Base.to_int x + | _ -> None + + let of_bool x = `Lifted (Base.of_bool x) + let to_bool x = match x with + | `Lifted x -> Base.to_bool x + | _ -> None + + let lift1 f x = match x with + | `Lifted x -> `Lifted (f x) + | x -> x + let lift2 f x y = match x,y with + | `Lifted x, `Lifted y -> `Lifted (f x y) + | `Bot, `Bot -> `Bot + | _ -> `Top + + let neg = lift1 Base.neg + let add = lift2 Base.add + let sub = lift2 Base.sub + let mul = lift2 Base.mul + let div = lift2 Base.div + let rem = lift2 Base.rem + let lt = lift2 Base.lt + let gt = lift2 Base.gt + let le = lift2 Base.le + let ge = lift2 Base.ge + let eq = lift2 Base.eq + let ne = lift2 Base.ne + let lognot = lift1 Base.lognot + let logand = lift2 Base.logand + let logor = lift2 Base.logor + let logxor = lift2 Base.logxor + let shift_left = lift2 Base.shift_left + let shift_right = lift2 Base.shift_right + let c_lognot = lift1 Base.c_lognot + let c_logand = lift2 Base.c_logand + let c_logor = lift2 Base.c_logor + + let invariant e = function + | `Lifted x -> Base.invariant e x + | `Top | `Bot -> Invariant.none +end + +module Flattened = Flat (Integers (IntOps.Int64Ops)) +module Lifted = Lift (Integers (IntOps.Int64Ops)) + +module Reverse (Base: IkindUnawareS) = +struct + include Base + include (Lattice.Reverse (Base) : Lattice.S with type t := Base.t) +end + +module SOverflowLifter (D : S) : SOverflow with type int_t = D.int_t and type t = D.t = struct + + include D + + let lift v = (v, {overflow=false; underflow=false}) + + let add ?no_ov ik x y = lift @@ D.add ?no_ov ik x y + + let sub ?no_ov ik x y = lift @@ D.sub ?no_ov ik x y + + let mul ?no_ov ik x y = lift @@ D.mul ?no_ov ik x y + + let div ?no_ov ik x y = lift @@ D.div ?no_ov ik x y + + let neg ?no_ov ik x = lift @@ D.neg ?no_ov ik x + + let cast_to ?suppress_ovwarn ?torg ?no_ov ik x = lift @@ D.cast_to ?suppress_ovwarn ?torg ?no_ov ik x + + let of_int ik x = lift @@ D.of_int ik x + + let of_interval ?suppress_ovwarn ik x = lift @@ D.of_interval ?suppress_ovwarn ik x + + let starting ?suppress_ovwarn ik x = lift @@ D.starting ?suppress_ovwarn ik x + + let ending ?suppress_ovwarn ik x = lift @@ D.ending ?suppress_ovwarn ik x + + let shift_left ik x y = lift @@ D.shift_left ik x y + + let shift_right ik x y = lift @@ D.shift_right ik x y + +end diff --git a/src/cdomain/value/cdomains/mutexAttrDomain.ml b/src/cdomain/value/cdomains/mutexAttrDomain.ml index ea9696d26f..e12818c2c1 100644 --- a/src/cdomain/value/cdomains/mutexAttrDomain.ml +++ b/src/cdomain/value/cdomains/mutexAttrDomain.ml @@ -25,7 +25,7 @@ let recursive_int = lazy ( let res = ref (Z.of_int 2) in (* Use OS X as the default, it doesn't have the enum *) GoblintCil.iterGlobals !Cilfacade.current_file (function | GEnumTag (einfo, _) -> - List.iter (fun (name, exp, _) -> + List.iter (fun (name, _, exp, _) -> if name = "PTHREAD_MUTEX_RECURSIVE" then res := Option.get @@ GoblintCil.getInteger exp ) einfo.eitems diff --git a/src/cdomain/value/cdomains/mval.ml b/src/cdomain/value/cdomains/mval.ml index 2bc5658460..888ce22a86 100644 --- a/src/cdomain/value/cdomains/mval.ml +++ b/src/cdomain/value/cdomains/mval.ml @@ -36,13 +36,14 @@ struct let to_cil_exp lv = Lval (to_cil lv) let is_definite (_, o) = Offs.is_definite o - let top_indices (x, o) = (x, Offs.top_indices o) end module MakeLattice (Offs: Offset.Lattice): Lattice with type idx = Offs.idx = struct include MakePrintable (Offs) + let top_indices (x, o) = (x, Offs.top_indices o) + let semantic_equal (x, xoffs) (y, yoffs) = if CilType.Varinfo.equal x y then let typ1 = x.vtype in @@ -72,3 +73,4 @@ end module Unit = MakePrintable (Offset.Unit) module Exp = MakePrintable (Offset.Exp) +module Z = MakePrintable (Offset.Z) diff --git a/src/cdomain/value/cdomains/mval_intf.ml b/src/cdomain/value/cdomains/mval_intf.ml index 7b956bf8b8..542eb1a300 100644 --- a/src/cdomain/value/cdomains/mval_intf.ml +++ b/src/cdomain/value/cdomains/mval_intf.ml @@ -17,9 +17,6 @@ sig @return [Some o] if it is (such that the variables are equal and [add_offset m1 o = m2]), [None] if it is not. *) - val top_indices: t -> t - (** Change all indices to top indices. *) - val to_cil: t -> GoblintCil.lval (** Convert to CIL lvalue. *) @@ -35,6 +32,9 @@ sig include Printable (** @closed *) include Lattice.S with type t := t (** @closed *) + val top_indices: t -> t + (** Change all indices to top indices. *) + val semantic_equal: t -> t -> bool option (** Check semantic equality of two mvalues. @@ -57,4 +57,7 @@ sig (** Mvalue with {!Offset.Exp} indices in offset. *) module Exp: Printable with type idx = GoblintCil.exp + + (** Mvalue with {!Offset.Z} indices in offset. *) + module Z: Printable with type idx = Z.t end diff --git a/src/cdomain/value/cdomains/offset.ml b/src/cdomain/value/cdomains/offset.ml index 7166e0642e..e8cba0afc5 100644 --- a/src/cdomain/value/cdomains/offset.ml +++ b/src/cdomain/value/cdomains/offset.ml @@ -41,11 +41,29 @@ struct let equal_to _ _ = `Top (* TODO: more precise for definite indices *) let to_int _ = None (* TODO: more precise for definite indices *) - let top () = Lazy.force any + end + + module Z = + struct + include Printable.Z + let name () = "Z index" + let to_int z = Some z + let equal_to z1 z2 = + if Z.equal z2 z2 then + `Eq + else + `Neq end end +module Poly = +struct + let rec map_indices g: 'a t -> 'b t = function + | `NoOffset -> `NoOffset + | `Field (f, o) -> `Field (f, map_indices g o) + | `Index (i, o) -> `Index (g i, map_indices g o) +end module MakePrintable (Idx: Index.Printable): Printable with type idx = Idx.t = struct @@ -79,6 +97,8 @@ struct end ) + include Poly + let rec is_definite: t -> bool = function | `NoOffset -> true | `Field (f,o) -> is_definite o @@ -103,7 +123,7 @@ struct | `Field(f,o) -> Field(f, to_cil_offset o) | `Index(i,o) -> NoOffset (* array domain can not deal with this -> leads to being handeled as access to unknown part *) - let rec to_exp: t -> exp offs = function + let rec to_exp: t -> exp offs = function (* TODO: Poly.map_indices *) | `NoOffset -> `NoOffset | `Index (i,o) -> let i_exp = match Idx.to_int i with @@ -128,13 +148,6 @@ struct | `Field (_, os) -> contains_index os | `Index _ -> true - let rec map_indices g: t -> t = function - | `NoOffset -> `NoOffset - | `Field (f, o) -> `Field (f, map_indices g o) - | `Index (i, o) -> `Index (g i, map_indices g o) - - let top_indices = map_indices (fun _ -> Idx.top ()) - (* tries to follow o in t *) let rec type_of ~base:t o = match unrollType t, o with (* resolves TNamed *) | t, `NoOffset -> t @@ -179,9 +192,11 @@ struct let widen x y = merge `Widen x y let narrow x y = merge `Narrow x y + let top_indices = map_indices (fun _ -> Idx.top ()) + (* NB! Currently we care only about concrete indexes. Base (seeing only a int domain element) answers with any_index_exp on all non-concrete cases. *) - let rec of_exp: exp offs -> t = function + let rec of_exp: exp offs -> t = function (* TODO: Poly.map_indices *) | `NoOffset -> `NoOffset | `Index (Const (CInt (i,ik,s)),o) -> `Index (Idx.of_int ik i, of_exp o) | `Index (_,o) -> `Index (Idx.top (), of_exp o) @@ -198,7 +213,7 @@ struct let bits_offset, _size = GoblintCil.bitsOffset (TComp (field.fcomp, [])) field_as_offset in let bits_offset = idx_of_int bits_offset in let remaining_offset = offset_to_index_offset ~typ:field.ftype o in - Idx.add bits_offset remaining_offset + GobRef.wrap AnalysisState.executing_speculative_computations true @@ fun () -> Idx.add bits_offset remaining_offset | `Index (x, o) -> let (item_typ, item_size_in_bits) = match Option.map unrollType typ with @@ -208,9 +223,10 @@ struct | _ -> (None, Idx.top ()) in - let bits_offset = Idx.mul item_size_in_bits x in + (* Binary operations on offsets should not generate overflow warnings in SV-COMP *) + let bits_offset = GobRef.wrap AnalysisState.executing_speculative_computations true @@ fun () -> Idx.mul item_size_in_bits x in let remaining_offset = offset_to_index_offset ?typ:item_typ o in - Idx.add bits_offset remaining_offset + GobRef.wrap AnalysisState.executing_speculative_computations true @@ fun () -> Idx.add bits_offset remaining_offset in offset_to_index_offset ?typ offs @@ -231,7 +247,7 @@ struct include MakePrintable (Index.Unit) (* TODO: rename to of_poly? *) - let rec of_offs: 'i offs -> t = function + let rec of_offs: 'i offs -> t = function (* TODO: Poly.map_indices *) | `NoOffset -> `NoOffset | `Field (f,o) -> `Field (f, of_offs o) | `Index (i,o) -> `Index ((), of_offs o) @@ -246,6 +262,8 @@ module Exp = struct include MakePrintable (Index.Exp) + let top_indices = map_indices (fun _ -> Lazy.force Index.Exp.any) + let rec of_cil: offset -> t = function | NoOffset -> `NoOffset | Index (i,o) -> `Index (i, of_cil o) @@ -258,6 +276,13 @@ struct | `Field (f,o) -> Field (f, to_cil o) end +module Z = +struct + include MakePrintable (Index.Z) + + let is_definite _ = true (* override to avoid iterating over offset *) +end + let () = Printexc.register_printer (function | Type_of_error (t, o) -> Some (GobPretty.sprintf "Offset.Type_of_error(%a, %s)" d_plaintype t o) diff --git a/src/cdomain/value/cdomains/offset_intf.ml b/src/cdomain/value/cdomains/offset_intf.ml index 82117b7d9f..e858f72214 100644 --- a/src/cdomain/value/cdomains/offset_intf.ml +++ b/src/cdomain/value/cdomains/offset_intf.ml @@ -15,9 +15,6 @@ struct sig include Printable.S (** @closed *) - val top: unit -> t - (** Unknown index. *) - val equal_to: Z.t -> t -> [`Eq | `Neq | `Top] (** Check semantic equality of an integer and the index. @@ -59,9 +56,6 @@ sig val map_indices: (idx -> idx) -> t -> t (** Apply function to all indexing. *) - val top_indices: t -> t - (** Change all indices to top indices. *) - val to_cil: t -> GoblintCil.offset (** Convert to CIL offset. *) @@ -89,6 +83,9 @@ sig include Printable (** @closed *) include Lattice.S with type t := t (** @closed *) + val top_indices: t -> t + (** Change all indices to top indices. *) + val of_exp: GoblintCil.exp offs -> t (** Convert from Goblint offset with {!GoblintCil.exp} indices. *) @@ -136,11 +133,24 @@ sig Used for Goblint-specific witness invariants. *) val all: GoblintCil.exp Lazy.t end + + module Z: Printable with type t = Z.t + (** {!Z} index. + Represents a definite index. *) end exception Type_of_error of GoblintCil.typ * string (** {!Printable.type_of} could not follow offset completely. *) + (** Polymorphic offset operations. *) + module Poly: + sig + val map_indices: ('a -> 'b) -> 'a t -> 'b t + (** Apply function to all indexing. *) + + (* TODO: include more operations *) + end + module type Printable = Printable module type Lattice = Lattice @@ -166,10 +176,16 @@ sig sig include Printable with type idx = GoblintCil.exp + val top_indices: t -> t + (** Change all indices to top indices. *) + val of_cil : GoblintCil.offset -> t (** Convert from CIL offset. *) val to_cil : t -> GoblintCil.offset (** Convert to CIL offset. *) end + + (** Offset with {!Index.Z} indices. *) + module Z: Printable with type idx = Z.t end diff --git a/src/cdomain/value/cdomains/stringDomain.ml b/src/cdomain/value/cdomains/stringDomain.ml index 2b968b0321..35054590f9 100644 --- a/src/cdomain/value/cdomains/stringDomain.ml +++ b/src/cdomain/value/cdomains/stringDomain.ml @@ -20,12 +20,12 @@ let reset_lazy () = type t = string option [@@deriving eq, ord, hash] +(** [None] means top. *) let hash x = - if get_string_domain () = Disjoint then - hash x - else - 13859 + match get_string_domain () with + | Disjoint | Flat -> hash x + | Unit -> 13859 let show = function | Some x -> "\"" ^ x ^ "\"" @@ -39,10 +39,9 @@ include Printable.SimpleShow ( ) let of_string x = - if get_string_domain () = Unit then - None - else - Some x + match get_string_domain () with + | Unit -> None + | Disjoint | Flat -> Some x let to_string x = x (* only keep part before first null byte *) @@ -91,10 +90,10 @@ let join x y = | _, None -> None | Some a, Some b when a = b -> Some a | Some a, Some b (* when a <> b *) -> - if get_string_domain () = Disjoint then - raise Lattice.Uncomparable - else - None + match get_string_domain () with + | Disjoint -> raise Lattice.Uncomparable + | Flat -> None + | Unit -> assert false let meet x y = match x, y with @@ -102,13 +101,14 @@ let meet x y = | a, None -> a | Some a, Some b when a = b -> Some a | Some a, Some b (* when a <> b *) -> - if get_string_domain () = Disjoint then - raise Lattice.Uncomparable - else - raise Lattice.BotValue + match get_string_domain () with + | Disjoint -> raise Lattice.Uncomparable + | Flat -> raise Lattice.BotValue + | Unit -> assert false let repr x = - if get_string_domain () = Disjoint then + match get_string_domain () with + | Disjoint -> x (* everything else is kept separate, including strings if not limited *) - else + | Flat | Unit -> None (* all strings together if limited *) diff --git a/src/cdomain/value/cdomains/threadIdDomain.ml b/src/cdomain/value/cdomains/threadIdDomain.ml index fff6734f27..226905ed6f 100644 --- a/src/cdomain/value/cdomains/threadIdDomain.ml +++ b/src/cdomain/value/cdomains/threadIdDomain.ml @@ -83,10 +83,10 @@ struct (v, None) let is_main = function - | ({vname; _}, None) -> List.mem vname @@ GobConfig.get_string_list "mainfun" + | ({vname; _}, None) -> GobConfig.get_bool "ana.thread.include-node" && List.mem vname @@ GobConfig.get_string_list "mainfun" | _ -> false - let is_unique _ = false (* TODO: should this consider main unique? *) + let is_unique = is_main let may_create _ _ = true let is_must_parent _ _ = false end diff --git a/src/cdomain/value/cdomains/unionDomain.ml b/src/cdomain/value/cdomains/unionDomain.ml index ad5c531061..302875ea2e 100644 --- a/src/cdomain/value/cdomains/unionDomain.ml +++ b/src/cdomain/value/cdomains/unionDomain.ml @@ -23,10 +23,9 @@ module Field = struct end) (CilType.Fieldinfo) let meet f g = - if equal f g then - f - else - raise Lattice.Uncomparable + match meet f g with + | `Bot -> raise Lattice.Uncomparable + | m -> m end module Simple (Values: Arg) = diff --git a/src/cdomain/value/cdomains/valueDomain.ml b/src/cdomain/value/cdomains/valueDomain.ml index de64fde807..b126d712bf 100644 --- a/src/cdomain/value/cdomains/valueDomain.ml +++ b/src/cdomain/value/cdomains/valueDomain.ml @@ -49,19 +49,32 @@ module type Blob = sig type value type size - type origin - include Lattice.S with type t = value * size * origin + type zeroinit + include Lattice.S with type t = value * size * zeroinit val map: (value -> value) -> t -> t val value: t -> value val invalidate_value: VDQ.t -> typ -> t -> t end -(* ZeroInit is true if malloc was used to allocate memory and it's false if calloc was used *) -module ZeroInit = +module type ZeroInit = +sig + include Lattice.S + + val is_malloc : t -> bool + val malloc : t + val calloc : t +end + +(* ZeroInit is false if malloc was used to allocate memory and true if calloc was used *) +module ZeroInit : ZeroInit = struct - include Lattice.Fake(Basetype.RawBools) - let name () = "no zeroinit" + include Lattice.Fake (BoolDomain.Bool) + let name () = "zeroinit" + + let is_malloc x = not x + let malloc = false + let calloc = true end module Blob (Value: S) (Size: IntDomain.Z)= @@ -70,7 +83,7 @@ struct let name () = "blob" type value = Value.t type size = Size.t - type origin = ZeroInit.t + type zeroinit = ZeroInit.t let map f (v, s, o) = f v, s, o let value (a, b, c) = a @@ -120,7 +133,7 @@ struct | _ -> false let is_mutex_type (t: typ): bool = match t with - | TNamed (info, attr) -> info.tname = "pthread_mutex_t" || info.tname = "spinlock_t" || info.tname = "pthread_spinlock_t" || info.tname = "pthread_cond_t" + | TNamed (info, attr) -> info.tname = "pthread_mutex_t" || info.tname = "spinlock_t" || info.tname = "pthread_spinlock_t" || info.tname = "pthread_cond_t" || info.tname = "pthread_rwlock_t" | TInt (IInt, attr) -> hasAttribute "mutex" attr | _ -> false @@ -862,8 +875,8 @@ struct end | _, _ -> None - let zero_init_calloced_memory orig x t = - if orig then + let zero_init_calloced_memory zeroinit x t = + if ZeroInit.is_malloc zeroinit then (* This Blob came from malloc *) x else if x = Bot then @@ -878,23 +891,23 @@ struct if M.tracing then M.traceli "eval_offset" "do_eval_offset %a %a (%a)" pretty x Offs.pretty offs (Pretty.docOpt (CilType.Exp.pretty ())) exp; let r = match x, offs with - | Blob((va, _, orig) as c), `Index (_, ox) -> + | Blob((va, _, zeroinit) as c), `Index (_, ox) -> begin let l', o' = shift_one_over l o in let ev = do_eval_offset ask f (Blobs.value c) ox exp l' o' v t in - zero_init_calloced_memory orig ev t + zero_init_calloced_memory zeroinit ev t end - | Blob((va, _, orig) as c), `Field _ -> + | Blob((va, _, zeroinit) as c), `Field _ -> begin let l', o' = shift_one_over l o in let ev = do_eval_offset ask f (Blobs.value c) offs exp l' o' v t in - zero_init_calloced_memory orig ev t + zero_init_calloced_memory zeroinit ev t end - | Blob((va, _, orig) as c), `NoOffset -> + | Blob((va, _, zeroinit) as c), `NoOffset -> begin let l', o' = shift_one_over l o in let ev = do_eval_offset ask f (Blobs.value c) offs exp l' o' v t in - zero_init_calloced_memory orig ev t + zero_init_calloced_memory zeroinit ev t end | Bot, _ -> Bot | _ -> @@ -952,24 +965,24 @@ struct let update_offset ?(blob_destructive=false) (ask: VDQ.t) (x:t) (offs:offs) (value:t) (exp:exp option) (v:lval) (t:typ): t = let rec do_update_offset (ask:VDQ.t) (x:t) (offs:offs) (value:t) (exp:exp option) (l:lval option) (o:offset option) (v:lval) (t:typ):t = if M.tracing then M.traceli "update_offset" "do_update_offset %a %a (%a) %a" pretty x Offs.pretty offs (Pretty.docOpt (CilType.Exp.pretty ())) exp pretty value; - let mu = function Blob (Blob (y, s', orig), s, orig2) -> Blob (y, ID.join s s',orig) | x -> x in + let mu = function Blob (Blob (y, s', zeroinit), s, _) -> Blob (y, ID.join s s', zeroinit) | x -> x in let r = match x, offs with | Mutex, _ -> (* hide mutex structure contents, not updated anyway *) Mutex - | Blob (x,s,orig), `Index (_,ofs) -> + | Blob (x,s,zeroinit), `Index (_,ofs) -> begin let l', o' = shift_one_over l o in - let x = zero_init_calloced_memory orig x t in - mu (Blob (join x (do_update_offset ask x ofs value exp l' o' v t), s, orig)) + let x = zero_init_calloced_memory zeroinit x t in + mu (Blob (join x (do_update_offset ask x ofs value exp l' o' v t), s, zeroinit)) end - | Blob (x,s,orig), `Field(f, _) -> + | Blob (x,s,zeroinit), `Field(f, _) -> begin (* We only have Blob for dynamically allocated memory. In these cases t is the type of the lval used to access it, i.e. for a struct s {int x; int y;} a; accessed via a->x *) (* will be int. Here, we need a zero_init of the entire contents of the blob though, which we get by taking the associated f.fcomp. Putting [] for attributes is ok, as we don't *) (* consider them in VD *) let l', o' = shift_one_over l o in - let x = zero_init_calloced_memory orig x (TComp (f.fcomp, [])) in + let x = zero_init_calloced_memory zeroinit x (TComp (f.fcomp, [])) in (* Strong update of scalar variable is possible if the variable is unique and size of written value matches size of blob being written to. *) let do_strong_update = match v with @@ -978,37 +991,38 @@ struct let blob_size_opt = ID.to_int s in not @@ ask.is_multiple var && not @@ Cil.isVoidType t (* Size of value is known *) - && Option.is_some blob_size_opt (* Size of blob is known *) - && Z.equal (Option.get blob_size_opt) (Z.of_int @@ Cil.bitsSizeOf (TComp (toptype, []))/8) + && GobOption.exists (fun blob_size -> (* Size of blob is known *) + Z.equal blob_size (Z.of_int @@ Cil.bitsSizeOf (TComp (toptype, []))/8) + ) blob_size_opt | _ -> false in if do_strong_update then - Blob ((do_update_offset ask x offs value exp l' o' v t), s, orig) + Blob ((do_update_offset ask x offs value exp l' o' v t), s, zeroinit) else - mu (Blob (join x (do_update_offset ask x offs value exp l' o' v t), s, orig)) + mu (Blob (join x (do_update_offset ask x offs value exp l' o' v t), s, zeroinit)) end - | Blob (x,s,orig), _ -> + | Blob (x,s,zeroinit), _ -> begin let l', o' = shift_one_over l o in - let x = zero_init_calloced_memory orig x t in + let x = zero_init_calloced_memory zeroinit x t in (* Strong update of scalar variable is possible if the variable is unique and size of written value matches size of blob being written to. *) let do_strong_update = begin match v with | (Var var, _) -> let blob_size_opt = ID.to_int s in not @@ ask.is_multiple var - && Option.is_some blob_size_opt (* Size of blob is known *) - && (( - not @@ Cil.isVoidType t (* Size of value is known *) - && Z.equal (Option.get blob_size_opt) (Z.of_int @@ Cil.alignOf_int t) - ) || blob_destructive) + && GobOption.exists (fun blob_size -> (* Size of blob is known *) + (not @@ Cil.isVoidType t (* Size of value is known *) + && Z.equal blob_size (Z.of_int @@ Cil.alignOf_int t)) + || blob_destructive + ) blob_size_opt | _ -> false end in if do_strong_update then - Blob ((do_update_offset ask x offs value exp l' o' v t), s, orig) + Blob ((do_update_offset ask x offs value exp l' o' v t), s, zeroinit) else - mu (Blob (join x (do_update_offset ask x offs value exp l' o' v t), s, orig)) + mu (Blob (join x (do_update_offset ask x offs value exp l' o' v t), s, zeroinit)) end | Thread _, _ -> (* hack for pthread_t variables *) @@ -1036,7 +1050,7 @@ struct match offs with | `NoOffset -> begin match value with - | Blob (y, s, orig) -> mu (Blob (join x y, s, orig)) + | Blob (y, s, zeroinit) -> mu (Blob (join x y, s, zeroinit)) | Int _ -> cast t value | _ -> value end @@ -1289,7 +1303,7 @@ and Unions: UnionDomain.S with type t = UnionDomain.Field.t * Compound.t and typ and CArrays: ArrayDomain.StrWithDomain with type value = Compound.t and type idx = ArrIdxDomain.t = ArrayDomain.AttributeConfiguredAndNullByteArrayDomain(Compound)(ArrIdxDomain) -and Blobs: Blob with type size = ID.t and type value = Compound.t and type origin = ZeroInit.t = Blob (Compound) (ID) +and Blobs: Blob with type size = ID.t and type value = Compound.t and type zeroinit = ZeroInit.t = Blob (Compound) (ID) module type InvariantArg = diff --git a/src/cdomain/value/domains/invariantCil.ml b/src/cdomain/value/domains/invariantCil.ml index 813ec25818..f41d48ab61 100644 --- a/src/cdomain/value/domains/invariantCil.ml +++ b/src/cdomain/value/domains/invariantCil.ml @@ -88,7 +88,7 @@ class exp_contains_anon_type_visitor = object inherit nopCilVisitor method! vtype (t: typ) = match t with - | TComp ({cname; _}, _) when BatString.starts_with_stdlib ~prefix:"__anon" cname -> + | TComp ({cname; _}, _) when String.starts_with ~prefix:"__anon" cname -> raise Stdlib.Exit | _ -> DoChildren @@ -102,7 +102,7 @@ let exp_contains_anon_type = (* TODO: synchronize magic constant with BaseDomain *) -let var_is_heap {vname; _} = BatString.starts_with vname "(alloc@" +let var_is_heap {vname; _} = String.starts_with vname ~prefix:"(alloc@" let reset_lazy () = ResettableLazy.reset exclude_vars_regexp diff --git a/src/cdomain/value/util/wideningThresholds.ml b/src/cdomain/value/util/wideningThresholds.ml index 0d93be76ff..939ed9482f 100644 --- a/src/cdomain/value/util/wideningThresholds.ml +++ b/src/cdomain/value/util/wideningThresholds.ml @@ -121,6 +121,7 @@ class extractInvariantsVisitor (exps) = object method! vinst (i: instr) = match i with | Call (_, Lval (Var f, NoOffset), args, _, _) when LibraryFunctions.is_special f -> + Goblint_backtrace.wrap_val ~mark:(Cilfacade.FunVarinfo f) @@ fun () -> let desc = LibraryFunctions.find f in begin match desc.special args with | Assert { exp; _ } -> diff --git a/src/cdomains/apron/affineEqualityDomain.apron.ml b/src/cdomains/apron/affineEqualityDomain.apron.ml index 7e60cce74b..06268130b2 100644 --- a/src/cdomains/apron/affineEqualityDomain.apron.ml +++ b/src/cdomains/apron/affineEqualityDomain.apron.ml @@ -6,7 +6,6 @@ Matrices are modeled as proposed by Karr: Each variable is assigned to a column and each row represents a linear affine relationship that must hold at the corresponding program point. The apron environment is hereby used to organize the order of columns and variables. *) -open Batteries open GoblintCil open Pretty module M = Messages @@ -181,7 +180,7 @@ struct else if Z.lt coeff Z.minus_one then Z.to_string coeff else Format.asprintf "+%s" (Z.to_string coeff) in - coeff_str ^ Var.to_string var + coeff_str ^ Var.show var in let const_to_str vl = if Z.equal vl Z.zero then @@ -193,7 +192,7 @@ struct in let res = (String.concat "" @@ Array.to_list @@ Array.map dim_to_str vars) ^ (const_to_str arr.(Array.length arr - 1)) ^ "=0" in - if String.starts_with res "+" then + if String.starts_with res ~prefix:"+" then Str.string_after res 1 else res @@ -203,11 +202,10 @@ struct | Some m when Matrix.is_empty m -> "⊤" | Some m -> let constraint_list = List.init (Matrix.num_rows m) (fun i -> vec_to_constraint (conv_to_ints @@ Matrix.get_row m i) t.env) in - Format.asprintf "%s" ("[|"^ (String.concat "; " constraint_list) ^"|]") + "[|"^ (String.concat "; " constraint_list) ^"|]" let pretty () (x:t) = text (show x) - let printXml f x = BatPrintf.fprintf f "\n\n\nmatrix\n\n\n%s\n\nenv\n\n\n%s\n\n\n" (XmlUtil.escape (Format.asprintf "%s" (show x) )) (XmlUtil.escape (Format.asprintf "%a" (Environment.print: Format.formatter -> Environment.t -> unit) (x.env))) - + let printXml f x = BatPrintf.fprintf f "\n\n\nmatrix\n\n\n%s\n\nenv\n\n\n%a\n\n\n" (XmlUtil.escape (show x)) Environment.printXml x.env let eval_interval ask = Bounds.bound_texpr let name () = "affeq" @@ -257,7 +255,7 @@ struct let meet t1 t2 = timing_wrap "meet" (meet t1) t2 let leq t1 t2 = - let env_comp = Environment.compare t1.env t2.env in (* Apron's Environment.compare has defined return values. *) + let env_comp = Environment.cmp t1.env t2.env in (* Apron's Environment.cmp has defined return values. *) if env_comp = -2 || env_comp > 0 then (* -2: environments are not compatible (a variable has different types in the 2 environements *) (* -1: if env1 is a subset of env2, (OK) *) @@ -334,7 +332,7 @@ struct else match Option.get a.d, Option.get b.d with | x, y when is_top_env a || is_top_env b -> {d = Some (Matrix.empty ()); env = Environment.lce a.env b.env} - | x, y when (Environment.compare a.env b.env <> 0) -> + | x, y when (Environment.cmp a.env b.env <> 0) -> let sup_env = Environment.lce a.env b.env in let mod_x = dim_add (Environment.dimchange a.env sup_env) x in let mod_y = dim_add (Environment.dimchange b.env sup_env) y in @@ -370,7 +368,7 @@ struct let remove_rels_with_var x var env inplace = timing_wrap "remove_rels_with_var" (remove_rels_with_var x var env) inplace let forget_vars t vars = - if is_bot t || is_top_env t || List.is_empty vars then + if is_bot t || is_top_env t || vars = [] then t else let m = Option.get t.d in @@ -430,8 +428,8 @@ struct let assign_exp ask t var exp no_ov = let res = assign_exp ask t var exp no_ov in - if M.tracing then M.tracel "ops" "assign_exp t:\n %s \n var: %s \n exp: %a\n no_ov: %b -> \n %s" - (show t) (Var.to_string var) d_exp exp (Lazy.force no_ov) (show res) ; + if M.tracing then M.tracel "ops" "assign_exp t:\n %s \n var: %a \n exp: %a\n no_ov: %b -> \n %s" + (show t) Var.pretty var d_exp exp (Lazy.force no_ov) (show res); res let assign_var (t: VarManagement(Vc)(Mx).t) v v' = @@ -441,7 +439,7 @@ struct let assign_var t v v' = let res = assign_var t v v' in - if M.tracing then M.tracel "ops" "assign_var t:\n %s \n v: %s \n v': %s\n -> %s" (show t) (Var.to_string v) (Var.to_string v') (show res) ; + if M.tracing then M.tracel "ops" "assign_var t:\n %s \n v: %a \n v': %a\n -> %s" (show t) Var.pretty v Var.pretty v' (show res); res let assign_var_parallel t vv's = @@ -499,7 +497,7 @@ struct let substitute_exp ask t var exp no_ov = let res = substitute_exp ask t var exp no_ov in - if M.tracing then M.tracel "ops" "Substitute_expr t: \n %s \n var: %s \n exp: %a \n -> \n %s" (show t) (Var.to_string var) d_exp exp (show res); + if M.tracing then M.tracel "ops" "Substitute_expr t: \n %s \n var: %a \n exp: %a \n -> \n %s" (show t) Var.pretty var d_exp exp (show res); res let substitute_exp ask t var exp no_ov = timing_wrap "substitution" (substitute_exp ask t var exp) no_ov diff --git a/src/cdomains/apron/apronDomain.apron.ml b/src/cdomains/apron/apronDomain.apron.ml index d0ef268ca6..043b728799 100644 --- a/src/cdomains/apron/apronDomain.apron.ml +++ b/src/cdomains/apron/apronDomain.apron.ml @@ -19,7 +19,7 @@ module M = Messages let widening_thresholds_apron = ResettableLazy.from_fun (fun () -> let t = if GobConfig.get_string "ana.apron.threshold_widening_constants" = "comparisons" then WideningThresholds.octagon_thresholds () else WideningThresholds.thresholds_incl_mul2 () in - let r = List.map (fun x -> Apron.Scalar.of_mpqf @@ Mpqf.of_mpz @@ Z_mlgmpidl.mpz_of_z x) t in + let r = List.map Scalar.of_z t in Array.of_list r ) @@ -283,7 +283,7 @@ struct let assign_exp_with ask nd v e no_ov = match Convert.texpr1_of_cil_exp ask nd (A.env nd) e no_ov with | texpr1 -> - if M.tracing then M.trace "apron" "assign_exp converted: %s" (Format.asprintf "%a" Texpr1.print texpr1); + if M.tracing then M.trace "apron" "assign_exp converted: %a" Texpr1.pretty texpr1; A.assign_texpr_with Man.mgr nd v texpr1 None | exception Convert.Unsupported_CilExp _ -> if M.tracing then M.trace "apron" "assign_exp unsupported"; @@ -442,7 +442,7 @@ struct let invariant _ = [] let show (x:t) = - Format.asprintf "%a (env: %a)" A.print x (Environment.print: Format.formatter -> Environment.t -> unit) (A.env x) + GobFormat.asprintf "%a (env: %a)" A.print x Environment.pp (A.env x) let pretty () (x:t) = text (show x) let equal x y = @@ -451,10 +451,10 @@ struct let hash (x:t) = A.hash Man.mgr x - let compare (x:t) y: int = - (* there is no A.compare, but polymorphic compare should delegate to Abstract0 and Environment compare's implemented in Apron's C *) - Stdlib.compare x y - let printXml f x = BatPrintf.fprintf f "\n\n\nconstraints\n\n\n%s\n\nenv\n\n\n%s\n\n\n" (XmlUtil.escape (Format.asprintf "%a" A.print x)) (XmlUtil.escape (Format.asprintf "%a" (Environment.print: Format.formatter -> Environment.t -> unit) (A.env x))) + let compare (x: t) (y: t): int = + failwith "Apron.Abstract1 doesn't have total order" (* https://github.com/antoinemine/apron/issues/99 *) + + let printXml f x = BatPrintf.fprintf f "\n\n\nconstraints\n\n\n%s\n\nenv\n\n\n%a\n\n\n" (XmlUtil.escape (GobFormat.asprint A.print x)) Environment.printXml (A.env x) let to_yojson (x: t) = let constraints = @@ -463,11 +463,9 @@ struct |> Lincons1Set.elements |> List.map (fun lincons1 -> `String (Lincons1.show lincons1)) in - let env = `String (Format.asprintf "%a" (Environment.print: Format.formatter -> Environment.t -> unit) (A.env x)) - in `Assoc [ ("constraints", `List constraints); - ("env", env); + ("env", Environment.to_yojson (A.env x)); ] let unify x y = @@ -533,9 +531,9 @@ struct | _ -> begin match Convert.tcons1_of_cil_exp ask d (A.env d) e negate no_ov with | tcons1 -> - if M.tracing then M.trace "apron" "assert_constraint %a %s" d_exp e (Format.asprintf "%a" Tcons1.print tcons1); + if M.tracing then M.trace "apron" "assert_constraint %a %a" d_exp e Tcons1.pretty tcons1; if M.tracing then M.trace "apron" "assert_constraint st: %a" D.pretty d; - if M.tracing then M.trace "apron" "assert_constraint tcons1: %s" (Format.asprintf "%a" Tcons1.print tcons1); + if M.tracing then M.trace "apron" "assert_constraint tcons1: %a" Tcons1.pretty tcons1; let r = meet_tcons ask d tcons1 e in if M.tracing then M.trace "apron" "assert_constraint r: %a" D.pretty r; r @@ -598,7 +596,7 @@ struct let x_cons = A.to_lincons_array Man.mgr x_j in let y_cons = A.to_lincons_array Man.mgr y_j in let try_add_con j con1 = - if M.tracing then M.tracei "apron" "try_add_con %s" (Format.asprintf "%a" (Lincons1.print: Format.formatter -> Lincons1.t -> unit) con1); + if M.tracing then M.tracei "apron" "try_add_con %a" Lincons1.pretty con1; let t = meet_lincons j con1 in let t_x = A.change_environment Man.mgr t x_env false in let t_y = A.change_environment Man.mgr t y_env false in @@ -637,7 +635,7 @@ struct in let env_exists_mem_con1 env con1 = let r = env_exists_mem_con1 env con1 in - if M.tracing then M.trace "apron" "env_exists_mem_con1 %s %s -> %B" (Format.asprintf "%a" (Environment.print: Format.formatter -> Environment.t -> unit) env) (Lincons1.show con1) r; + if M.tracing then M.trace "apron" "env_exists_mem_con1 %a %a -> %B" Environment.pretty env Lincons1.pretty con1 r; r in (* Heuristically reorder constraints to pass 36/12 with singlethreaded->multithreaded mode switching. *) diff --git a/src/cdomains/apron/gobApron.apron.ml b/src/cdomains/apron/gobApron.apron.ml index c39a3e42db..327e43e321 100644 --- a/src/cdomains/apron/gobApron.apron.ml +++ b/src/cdomains/apron/gobApron.apron.ml @@ -1,9 +1,40 @@ open Batteries include Apron +module Scalar = +struct + include Scalar + + let pp = print + include Printable.SimpleFormat ( + struct + type nonrec t = t + let pp = pp + end + ) + + let of_z z = of_mpqf (Mpqf.of_mpz (Z_mlgmpidl.mpz_of_z z)) +end + +module Coeff = +struct + include Coeff + + let s_of_z z = Coeff.s_of_mpqf (Mpqf.of_mpz (Z_mlgmpidl.mpz_of_z z)) +end + module Var = struct include Var + + let pp = print + include Printable.SimpleFormat ( + struct + type nonrec t = t + let pp = pp + end + ) + let equal x y = Var.compare x y = 0 end @@ -11,8 +42,17 @@ module Lincons1 = struct include Lincons1 - let show = Format.asprintf "%a" print - let compare x y = String.compare (show x) (show y) (* HACK *) + let pp = print + include Printable.SimpleFormat ( + struct + type nonrec t = t + let pp = pp + end + ) + + let compare x y = + (* TODO: implement proper total Lincons1 order *) + String.compare (show x) (show y) (* HACK *) let num_vars x = (* Apron.Linexpr0.get_size returns some internal nonsense, so we count ourselves. *) @@ -36,12 +76,63 @@ struct |> of_enum end +module Texpr1 = +struct + include Texpr1 + + let pp = print + include Printable.SimpleFormat ( + struct + type nonrec t = t + let pp = pp + end + ) + + module Expr = + struct + type t = expr + + let pp = print_expr + include Printable.SimpleFormat ( + struct + type nonrec t = t + let pp = pp + end + ) + end +end + +module Tcons1 = +struct + include Tcons1 + + let pp = print + include Printable.SimpleFormat ( + struct + type nonrec t = t + let pp = pp + end + ) +end + (** A few code elements for environment changes from functions as remove_vars etc. have been moved to sharedFunctions as they are needed in a similar way inside affineEqualityDomain. A module that includes various methods used by variable handling operations such as add_vars, remove_vars etc. in apronDomain and affineEqualityDomain. *) module Environment = struct include Environment + let pp: Format.formatter -> Environment.t -> unit = Environment.print + include Printable.SimpleFormat ( + struct + type nonrec t = t + let pp = pp + end + ) + + let compare (x: t) (y: t): int = + (* TODO: implement total Environment order in OCaml *) + failwith "Apron.Environment doesn't have total order" (* https://github.com/antoinemine/apron/issues/99 *) + let ivars_only env = let ivs, fvs = Environment.vars env in assert (Array.length fvs = 0); (* shouldn't ever contain floats *) diff --git a/src/cdomains/apron/linearTwoVarEqualityDomain.apron.ml b/src/cdomains/apron/linearTwoVarEqualityDomain.apron.ml index 67bd67f4e5..6af7030a51 100644 --- a/src/cdomains/apron/linearTwoVarEqualityDomain.apron.ml +++ b/src/cdomains/apron/linearTwoVarEqualityDomain.apron.ml @@ -11,21 +11,44 @@ open Batteries open GoblintCil open Pretty module M = Messages -open Apron +open GobApron open VectorMatrix module Mpqf = SharedFunctions.Mpqf module Rhs = struct - (* (Some i, k) represents a sum of a variable with index i and the number k. - (None, k) represents the number k. *) - type t = (int option * GobZ.t) [@@deriving eq, ord, hash] - let var_zero i = (Some i, Z.zero) - let show_formatted formatter = function - | (Some v, o) when Z.equal o Z.zero -> formatter v - | (Some v, o) -> Printf.sprintf "%s%+Ld" (formatter v) (Z.to_int64 o) - | (None, o) -> Printf.sprintf "%Ld" (Z.to_int64 o) - let show rhs = show_formatted (Printf.sprintf "var_%d") rhs + (* Rhs represents coefficient*var_i + offset / divisor + depending on whether coefficient is 0, the monomial term may disappear completely, not refering to any var_i, thus: + (Some (coefficient, i), offset, divisor ) with coefficient != 0 , or + (None , offset, divisor ) *) + type t = ((GobZ.t * int) option * GobZ.t * GobZ.t) [@@deriving eq, ord, hash] + let var_zero i = (Some (Z.one,i), Z.zero, Z.one) + let show_coeff c = + if Z.equal c Z.one then "" + else if Z.equal c Z.minus_one then "-" + else (Z.to_string c) ^"·" + let show_rhs_formatted formatter = let ztostring n = (if Z.(geq n zero) then "+" else "") ^ Z.to_string n in + function + | (Some (coeff,v), o,_) when Z.equal o Z.zero -> Printf.sprintf "%s%s" (show_coeff coeff) (formatter v) + | (Some (coeff,v), o,_) -> Printf.sprintf "%s%s %s" (show_coeff coeff) (formatter v) (ztostring o) + | (None, o,_) -> Printf.sprintf "%s" (Z.to_string o) + let show (v,o,d) = + let rhs=show_rhs_formatted (Printf.sprintf "var_%d") (v,o,d) in + if not (Z.equal d Z.one) then "(" ^ rhs ^ ")/" ^ (Z.to_string d) else rhs + + (** factor out gcd from all terms, i.e. ax=by+c with a positive is the canonical form for adx+bdy+cd *) + let canonicalize (v,o,d) = + let gcd = Z.gcd o d in (* gcd of coefficients *) + let gcd = Option.map_default (fun (c,_) -> Z.gcd c gcd) gcd v in (* include monomial in gcd computation *) + let commondivisor = if Z.(lt d zero) then Z.neg gcd else gcd in (* canonical form dictates d being positive *) + (BatOption.map (fun (coeff,i) -> (Z.div coeff commondivisor,i)) v, Z.div o commondivisor, Z.div d commondivisor) + + (** Substitute rhs for varx in rhs' *) + let subst rhs varx rhs' = + match rhs,rhs' with + | (monom, o, d), (Some (c', x'), o', d') when x'=varx -> canonicalize (Option.map (fun (c,x) -> (Z.mul c c',x)) monom, Z.((o*c')+(d*o')), Z.mul d d') + | _ -> rhs' + end module EqualitiesConjunction = struct @@ -36,7 +59,7 @@ module EqualitiesConjunction = struct let show_formatted formatter econ = if IntMap.is_empty econ then "{}" else - let str = IntMap.fold (fun i (refvar,off) acc -> Printf.sprintf "%s=%s ∧ %s" (formatter i) (Rhs.show_formatted formatter (refvar,off)) acc) econ "" in + let str = IntMap.fold (fun i (refmonom,off,divi) acc -> Printf.sprintf "%s%s=%s ∧ %s" (Rhs.show_coeff divi) (formatter i) (Rhs.show_rhs_formatted formatter (refmonom,off,divi)) acc) econ "" in "{" ^ String.sub str 0 (String.length str - 4) ^ "}" let show econ = show_formatted (Printf.sprintf "var_%d") econ @@ -52,16 +75,23 @@ module EqualitiesConjunction = struct (** trivial equalities are of the form var_i = var_i and are not kept explicitely in the sparse representation of EquanlitiesConjunction *) let nontrivial (_,econmap) lhs = IntMap.mem lhs econmap + (** turn x = (cy+o)/d into y = (dx-o)/c*) + let inverse x (c,y,o,d) = (y, (Some (d, x), Z.neg o, c)) + (** sparse implementation of get rhs for lhs, but will default to no mapping for sparse entries *) let get_rhs (_,econmap) lhs = IntMap.find_default (Rhs.var_zero lhs) lhs econmap - (** set_rhs, staying loyal to immutable, sparse map underneath *) + (** set_rhs, staying loyal to immutable, sparse map underneath; do not attempt any normalization *) let set_rhs (dim,map) lhs rhs = (dim, if Rhs.equal rhs Rhs.(var_zero lhs) then IntMap.remove lhs map else IntMap.add lhs rhs map ) + + (** canonicalize equation, and set_rhs, staying loyal to immutable, sparse map underneath *) + let canonicalize_and_set (dim,map) lhs rhs = set_rhs (dim,map) lhs (Rhs.canonicalize rhs) + let copy = identity @@ -86,9 +116,10 @@ module EqualitiesConjunction = struct IntHashtbl.add h x r; r) in - let rec bumpentry k (refvar,offset) = function (* directly bumps lhs-variable during a run through indexes, bumping refvar explicitely with a new lookup in indexes *) - | (tbl,delta,head::rest) when k>=head -> bumpentry k (refvar,offset) (tbl,delta+1,rest) (* rec call even when =, in order to correctly interpret double bumps *) - | (tbl,delta,lyst) (* k (IntMap.add (op k delta) (BatOption.map (memobumpvar) refvar, offset) tbl, delta, lyst) + let rec bumpentry k (refvar,offset,divi) = function (* directly bumps lhs-variable during a run through indexes, bumping refvar explicitly with a new lookup in indexes *) + + | (tbl,delta,head::rest) when k>=head -> bumpentry k (refvar,offset,divi) (tbl,delta+1,rest) (* rec call even when =, in order to correctly interpret double bumps *) + | (tbl,delta,lyst) (* k (IntMap.add (op k delta) (BatOption.map (fun (c,v) -> (c,memobumpvar v)) refvar,offset,divi) tbl, delta, lyst) in let (a,_,_) = IntMap.fold bumpentry map (IntMap.empty,0,offsetlist) in (* Build new map during fold with bumped key/vals *) (op dim (Array.length indexes), a) @@ -111,17 +142,28 @@ module EqualitiesConjunction = struct (* Forget information about variable i *) let forget_variable d var = let res = - (let ref_var_opt = fst (get_rhs d var) in + (let ref_var_opt = Tuple3.first (get_rhs d var) in match ref_var_opt with - | Some ref_var when ref_var = var -> + | Some (_,ref_var) when ref_var = var -> + if M.tracing then M.trace "forget" "headvar var_%d" var; (* var is the reference variable of its connected component *) - (let cluster = IntMap.fold - (fun i (ref, offset) l -> if ref = ref_var_opt then i::l else l) (snd d) [] in + (let cluster = List.sort (Int.compare) @@ IntMap.fold + (fun i (refe,_,_) l -> BatOption.map_default (fun (coeff,refe) -> if (refe=ref_var) then i::l else l) l refe) (snd d) [] in + if M.tracing then M.trace "forget" "cluster varindices: [%s]" (String.concat ", " (List.map (string_of_int) cluster)); (* obtain cluster with common reference variable ref_var*) match cluster with (* new ref_var is taken from head of the cluster *) - | head :: tail -> - let headconst = snd (get_rhs d head) in (* take offset between old and new reference variable *) - List.fold (fun map i -> set_rhs map i Z.(Some head, snd (get_rhs d i) - headconst)) d cluster (* shift offset to match new reference variable *) + | head :: clusterrest -> + (* head: divi*x = coeff*y + offs *) + (* divi*x = coeff*y + offs =inverse=> y =( divi*x - offs)/coeff *) + let (newref,offs,divi) = (get_rhs d head) in + let (coeff,y) = BatOption.get newref in + let (y,yrhs) = inverse head (coeff,y,offs,divi) in (* reassemble yrhs out of components *) + let shifted_cluster = (List.fold (fun map i -> + let irhs = (get_rhs d i) in (* old entry is i = irhs *) + Rhs.subst yrhs y irhs |> (* new entry for i is irhs [yrhs/y] *) + set_rhs map i + ) d clusterrest) in + set_rhs shifted_cluster head (Rhs.var_zero head) (* finally make sure that head is now trivial *) | [] -> d) (* empty cluster means no work for us *) | _ -> d) (* variable is either a constant or expressed by another refvar *) in let res = (fst res, IntMap.remove var (snd res)) in (* set d(var) to unknown, finally *) @@ -153,31 +195,68 @@ module EqualitiesConjunction = struct exception Contradiction - let meet_with_one_conj ts i (var, b) = + let meet_with_one_conj ts i (var, offs, divi) = + let (var,offs,divi) = Rhs.canonicalize (var,offs,divi) in (* make sure that the one new conj is properly canonicalized *) let res = - let subst_var tsi x (vart, bt) = + let subst_var (dim,econj) x (vary, o, d) = + (* [[x substby (cy+o)/d ]] ((c'x+o')/d') *) + (* =====> (c'cy + c'o+o'd)/(dd') *) let adjust = function - | (Some vare, b') when vare = x -> (vart, Z.(b' + bt)) + | (Some (c',varx), o',d') when varx = x -> + let open Z in Rhs.canonicalize (BatOption.map (fun (c, y)-> (c * c', y)) vary, c'*o + o'*d, d'*d) | e -> e in - (fst tsi, IntMap.add x (vart, bt) @@ IntMap.map adjust (snd tsi)) (* in case of sparse representation, make sure that the equality is now included in the conjunction *) + (dim, IntMap.add x (vary, o, d) @@ IntMap.map adjust econj) (* in case of sparse representation, make sure that the equality is now included in the conjunction *) in - let (var1, b1) = get_rhs ts i in - (match var, var1 with - | None , None -> if not @@ Z.equal b b1 then raise Contradiction else ts - | None , Some h1 -> subst_var ts h1 (None, Z.(b - b1)) - | Some j, None -> subst_var ts j (None, Z.(b1 - b)) - | Some j, Some h1 -> + (match var, (get_rhs ts i) with + (*| new conj , old conj *) + | None , (None , o1, divi1) -> if not @@ (Z.equal offs o1 && Z.equal divi divi1) then raise Contradiction else ts + (* o/d = x_i = (c1*x_h1+o1)/d1 *) + (* ======> x_h1 = (o*d1-o1*d)/(d*c1) /\ x_i = o/d *) + | None , (Some (coeff1,h1), o1, divi1) -> subst_var ts h1 (None, Z.(offs*divi1 - o1*divi),Z.(divi*coeff1)) + (* (c*x_j+o)/d = x_i = o1/d1 *) + (* ======> x_j = (o1*d-o*d1)/(d1*c) /\ x_i = o1/d1 *) + | Some (coeff,j), (None , o1, divi1) -> subst_var ts j (None, Z.(o1*divi - offs*divi1),Z.(divi1*coeff)) + (* (c*x_j+o)/d = x_i = (c1*x_h1+o1)/d1 *) + (* ======> x_j needs normalization wrt. ts *) + | Some (coeff,j), ((Some (coeff1,h1), o1, divi1) as oldi)-> (match get_rhs ts j with - | (None, b2) -> subst_var ts i (None, Z.(b2 + b)) - | (Some h2, b2) -> - if h1 = h2 then - (if not @@ Z.equal b1 Z.(b2 + b) then raise Contradiction else ts) - else if h1 < h2 then subst_var ts h2 (Some h1, Z.(b1 - (b + b2))) - else subst_var ts h1 (Some h2, Z.(b + (b2 - b1))))) in - if M.tracing then M.trace "meet" "meet_with_one_conj conj: { %s } eq: var_%d=%s -> { %s } " (show (snd ts)) i (Rhs.show (var,b)) (show (snd ts)) + (* ts[x_j]=o2/d2 ========> ... *) + | (None , o2, divi2) -> + let newxi = Rhs.subst (None,o2,divi2) j (Some (coeff,j),offs,divi) in + let newxh1 = snd @@ inverse i (coeff1,h1,o1,divi1) in + let newxh1 = Rhs.subst newxi i newxh1 in + subst_var ts h1 newxh1 + (* ts[x_j]=(c2*x_h2+o2)/d2 ========> ... *) + | (Some (coeff2,h2), o2, divi2) as normalizedj -> + if h1 = h2 then (* this is the case where x_i and x_j already where in the same equivalence class; let's see whether the new equality contradicts the old one *) + let normalizedi= Rhs.subst normalizedj j (Some(coeff,j),offs,divi) in + if not @@ Rhs.equal normalizedi oldi then raise Contradiction else ts + else if h1 < h2 (* good, we now unite the two equvalence classes; let's decide upon the representative *) + then (* express h2 in terms of h1: *) + let (_,newh2)= inverse j (coeff2,h2,o2,divi2) in + let newh2 = Rhs.subst oldi i (Rhs.subst (snd @@ inverse i (coeff,j,offs,divi)) j newh2) in + subst_var ts h2 newh2 + else (* express h1 in terms of h2: *) + let (_,newh1)= inverse i (coeff1,h1,o1,divi1) in + let newh1 = Rhs.subst normalizedj j (Rhs.subst (Some(coeff,j),offs,divi) i newh1) in + subst_var ts h1 newh1)) in + if M.tracing then M.tracel "meet_with_one_conj" "meet_with_one_conj conj: %s eq: var_%d=%s -> %s " (show (snd ts)) i (Rhs.show (var,offs,divi)) (show (snd res)) ; res + (** affine transform variable i allover conj with transformer (Some (coeff,i)+offs)/divi *) + let affine_transform econ i (coeff, j, offs, divi) = + if nontrivial econ i then (* i cannot occur on any other rhs apart from itself *) + set_rhs econ i (Rhs.subst (get_rhs econ i) i (Some (coeff,j), offs, divi)) + else (* var_i = var_i, i.e. it may occur on the rhs of other equalities *) + (* so now, we transform with the inverse of the transformer: *) + let inv = snd (inverse i (coeff,j,offs,divi)) in + IntMap.fold (fun k v acc -> + match v with + | (Some (c,x),o,d) when x=i-> set_rhs acc k (Rhs.subst inv i v) + | _ -> acc + ) (snd econ) econ + end (** [VarManagement] defines the type t of the affine equality domain (a record that contains an optional matrix and an apron environment) and provides the functions needed for handling variables (which are defined by [RelationDomain.D2]) such as [add_vars], [remove_vars]. @@ -195,32 +274,32 @@ struct let open Apron.Texpr1 in let exception NotLinearExpr in let exception ScalarIsInfinity in - let negate coeff_var_list = List.map (fun (coeff, var) -> (Z.(-coeff), var)) coeff_var_list in - let multiply_with_Z number coeff_var_list = - List.map (fun (coeff, var) -> (Z.(number * coeff, var))) coeff_var_list in + let negate coeff_var_list = + List.map (fun (monom, offs, divi) -> Z.(BatOption.map (fun (coeff,i) -> (neg coeff, i)) monom, neg offs, divi)) coeff_var_list in + let multiply_with_Q dividend divisor coeff_var_list = + List.map (fun (monom, offs, divi) -> Rhs.canonicalize Z.(BatOption.map (fun (coeff,i) -> (dividend*coeff,i)) monom, dividend*offs, divi*divisor) ) coeff_var_list in let multiply a b = (* if one of them is a constant, then multiply. Otherwise, the expression is not linear *) match a, b with - | [(a_coeff, None)], b -> multiply_with_Z a_coeff b - | a, [(b_coeff, None)] -> multiply_with_Z b_coeff a + | [(None,coeff, divi)], c + | c, [(None,coeff, divi)] -> multiply_with_Q coeff divi c | _ -> raise NotLinearExpr in let rec convert_texpr texp = begin match texp with - (* If x is a constant, replace it with its const. val. immediately *) | Cst (Interval _) -> failwith "constant was an interval; this is not supported" | Cst (Scalar x) -> begin match SharedFunctions.int_of_scalar ?round:None x with - | Some x -> [(x, None)] + | Some x -> [(None,x,Z.one)] | None -> raise ScalarIsInfinity end | Var x -> let var_dim = Environment.dim_of_var t.env x in begin match t.d with - | None -> [(Z.one, Some var_dim)] + | None -> [(Some (Z.one,var_dim),Z.zero,Z.one)] | Some d -> (match (EConj.get_rhs d var_dim) with - | (Some i, k) -> [(Z.one, Some i); (k, None)] - | (None, k) -> [(k, None)]) + | (Some (coeff,i), k,divi) -> [(Some (coeff,i),Z.zero,divi); (None,k,divi)] + | (None, k,divi) -> [ (None,k,divi)]) end | Unop (Neg, e, _, _) -> negate (convert_texpr e) | Unop (Cast, e, _, _) -> convert_texpr e (* Ignore since casts in apron are used for floating point nums and rounding in contrast to CIL casts *) @@ -234,59 +313,41 @@ struct | exception ScalarIsInfinity -> None | x -> Some(x) - (** convert and simplify (wrt. reference variables) a texpr into a tuple of a list of monomials and a constant *) + (** convert and simplify (wrt. reference variables) a texpr into a tuple of a list of monomials (coeff,varidx,divi) and a (constant/divi) *) let simplified_monomials_from_texp (t: t) texp = BatOption.bind (monomials_from_texp t texp) (fun monomiallist -> let d = Option.get t.d in - let expr = Array.make (Environment.size t.env) Z.zero in - let accumulate_constants a (c, v) = match v with - | None -> Z.(a + c) - | Some idx -> let (term,con) = (EConj.get_rhs d idx) in - (Option.may (fun ter -> expr.(ter) <- Z.(expr.(ter) + c)) term; - Z.(a + c * con)) + let module IMap = EConj.IntMap in + let accumulate_constants (exprcache,(aconst,adiv)) (v,offs,divi) = match v with + | None -> let gcdee = Z.gcd adiv divi in exprcache,(Z.(aconst*divi/gcdee + offs*adiv/gcdee),Z.lcm adiv divi) + | Some (coeff,idx) -> let (somevar,someoffs,somedivi)=Rhs.subst (EConj.get_rhs d idx) idx (v,offs,divi) in (* normalize! *) + let newcache = Option.map_default (fun (coef,ter) -> IMap.add ter Q.((IMap.find_default zero ter exprcache) + make coef somedivi) exprcache) exprcache somevar in + let gcdee = Z.gcd adiv divi in + (newcache,(Z.(aconst*divi/gcdee + offs*adiv/gcdee),Z.lcm adiv divi)) in - let constant = List.fold_left accumulate_constants Z.zero monomiallist in (* abstract simplification of the guard wrt. reference variables *) - Some (Array.fold_lefti (fun list v (c) -> if Z.equal c Z.zero then list else (c,v)::list) [] expr, constant) ) + let (expr,constant) = List.fold_left accumulate_constants (IMap.empty,(Z.zero,Z.one)) monomiallist in (* abstract simplification of the guard wrt. reference variables *) + Some (IMap.fold (fun v c acc -> if Q.equal c Q.zero then acc else (Q.num c,v,Q.den c)::acc) expr [], constant) ) + + let simplified_monomials_from_texp (t: t) texp = + let res = simplified_monomials_from_texp t texp in + if M.tracing then M.tracel "from_texp" "%s %a -> %s" (EConj.show @@ snd @@ BatOption.get t.d) Texpr1.Expr.pretty texp + (BatOption.map_default (fun (l,(o,d)) -> List.fold_right (fun (a,x,b) acc -> Printf.sprintf "%s*var_%d/%s + %s" (Z.to_string a) x (Z.to_string b) acc) l ((Z.to_string o)^"/"^(Z.to_string d))) "" res); + res let simplify_to_ref_and_offset (t: t) texp = BatOption.bind (simplified_monomials_from_texp t texp ) - (fun (sum_of_terms, constant) -> + (fun (sum_of_terms, (constant,divisor)) -> (match sum_of_terms with - | [] -> Some (None, constant) - | [(coeff,var)] when Z.equal coeff Z.one -> Some (Some var, constant) + | [] -> Some (None, constant,divisor) + | [(coeff,var,divi)] -> Some (Rhs.canonicalize (Some (Z.mul divisor coeff,var), Z.mul constant divi,Z.mul divisor divi)) |_ -> None)) let simplify_to_ref_and_offset t texp = timing_wrap "coeff_vec" (simplify_to_ref_and_offset t) texp - (* Copy because function is not "with" so should not mutate inputs *) - let assign_const t var const = match t.d with + let assign_const t var const divi = match t.d with | None -> t - | Some t_d -> {d = Some (EConj.set_rhs t_d var (None, const)); env = t.env} - - let subtract_const_from_var t var const = - match t.d with - | None -> t - | Some t_d -> - let subtract_const_from_var_for_single_equality index (eq_var_opt, off2) econ = - if index <> var then - begin match eq_var_opt with - | Some eq_var when eq_var = var -> - EConj.set_rhs econ index (eq_var_opt, Z.(off2 - const)) - | _ -> econ - end - else econ - in - let d = - if not @@ EConj.nontrivial t_d var - (* var is a reference variable -> it can appear on the right-hand side of an equality *) - then - (EConj.IntMap.fold (subtract_const_from_var_for_single_equality) (snd t_d) t_d) - else - (* var never appears on the right hand side-> we only need to modify the array entry at index var *) - EConj.set_rhs t_d var (Tuple2.map2 (Z.add const) (EConj.get_rhs t_d var)) - in - {d = Some d; env = t.env} + | Some t_d -> {d = Some (EConj.set_rhs t_d var (None, const, divi)); env = t.env} end @@ -299,9 +360,9 @@ struct if t.d = None then None, None else match simplify_to_ref_and_offset t (Texpr1.to_expr texpr) with - | Some (None, offset) -> - (if M.tracing then M.tracel "bounds" "min: %s max: %s" (IntOps.BigIntOps.to_string offset) (IntOps.BigIntOps.to_string offset); - Some offset, Some offset) + | Some (None, offset, divisor) when Z.equal (Z.rem offset divisor) Z.zero -> let res = Z.div offset divisor in + (if M.tracing then M.tracel "bounds" "min: %a max: %a" GobZ.pretty res GobZ.pretty res; + Some res, Some res) | _ -> None, None let bound_texpr d texpr1 = timing_wrap "bounds calculation" (bound_texpr d) texpr1 @@ -336,11 +397,23 @@ struct let top () = {d = Some (EConj.empty()); env = empty_env} (** is_top returns true for top_of array and empty array; precondition: t.env and t.d are of same size *) - let is_top t = Environment.equal empty_env t.env && GobOption.exists EConj.is_top_con t.d + let is_top t = GobOption.exists EConj.is_top_con t.d + + let to_subscript i = + let transl = [|"₀";"₁";"₂";"₃";"₄";"₅";"₆";"₇";"₈";"₉"|] in + let rec subscr i = + if i = 0 then "" + else (subscr (i/10)) ^ transl.(i mod 10) in + subscr i + + let show_var env i = + let res = Var.to_string (Environment.var_of_dim env i) in + match String.split_on_char '#' res with + | varname::rest::[] -> varname ^ (try to_subscript @@ int_of_string rest with _ -> "#" ^ rest) + | _ -> res (** prints the current variable equalities with resolved variable names *) let show varM = - let lookup i = Var.to_string (Environment.var_of_dim varM.env i) in match varM.d with | None -> "⊥\n" | Some arr when EConj.is_top_con arr -> "⊤\n" @@ -348,25 +421,25 @@ struct if is_bot varM then "Bot \n" else - EConj.show_formatted lookup (snd arr) ^ (" with dimension " ^ (string_of_int @@ fst arr)) + EConj.show_formatted (show_var varM.env) (snd arr) ^ (to_subscript @@ fst arr) let pretty () (x:t) = text (show x) - let printXml f x = BatPrintf.fprintf f "\n\n\nequalities\n\n\n%s\n\nenv\n\n\n%s\n\n\n" (XmlUtil.escape (Format.asprintf "%s" (show x) )) (XmlUtil.escape (Format.asprintf "%a" (Environment.print: Format.formatter -> Environment.t -> unit) (x.env))) + let printXml f x = BatPrintf.fprintf f "\n\n\nequalities\n\n\n%s\n\nenv\n\n\n%a\n\n\n" (XmlUtil.escape (show x)) Environment.printXml x.env let eval_interval ask = Bounds.bound_texpr - let meet_with_one_conj t i (var, b) = + let meet_with_one_conj t i (var, o, divi) = match t.d with | None -> t | Some d -> try - { d = Some (EConj.meet_with_one_conj d i (var, b)); env = t.env} + { d = Some (EConj.meet_with_one_conj d i (var, o, divi)); env = t.env} with EConj.Contradiction -> if M.tracing then M.trace "meet" " -> Contradiction\n"; { d = None; env = t.env} let meet_with_one_conj t i e = let res = meet_with_one_conj t i e in - if M.tracing then M.trace "meet" "meet_with_one_conj %s with var_%d=%s -> %s" (show t) i (Rhs.show e) (show res); + if M.tracing then M.tracel "meet" "%s with single eq %s=%s -> %s" (show t) (Z.(to_string @@ Tuple3.third e)^ show_var t.env i) (Rhs.show_rhs_formatted (show_var t.env) e) (show res); res let meet t1 t2 = @@ -374,7 +447,7 @@ struct let t1 = change_d t1 sup_env ~add:true ~del:false in let t2 = change_d t2 sup_env ~add:true ~del:false in match t1.d, t2.d with - | Some d1', Some d2' -> + | Some d1', Some d2' -> EConj.IntMap.fold (fun lhs rhs map -> meet_with_one_conj map lhs rhs) (snd d2') t1 (* even on sparse d2, this will chose the relevant conjs to meet with*) | _ -> {d = None; env = sup_env} @@ -386,16 +459,18 @@ struct let meet t1 t2 = timing_wrap "meet" (meet t1) t2 let leq t1 t2 = - let env_comp = Environment.compare t1.env t2.env in (* Apron's Environment.compare has defined return values. *) - let implies ts i (var, b) = - let tuple_cmp = Tuple2.eq (Option.eq ~eq:Int.equal) (Z.equal) in + let env_comp = Environment.cmp t1.env t2.env in (* Apron's Environment.cmp has defined return values. *) + let implies ts i (var, offs, divi) = + let tuple_cmp = Tuple3.eq (Option.eq ~eq:(Tuple2.eq (Z.equal) (Int.equal))) (Z.equal) (Z.equal) in match var with - | None -> tuple_cmp (var, b) (EConj.get_rhs ts i) - | Some j -> tuple_cmp (EConj.get_rhs ts i) @@ Tuple2.map2 (Z.add b) (EConj.get_rhs ts j) + (* directly compare in case of constant value *) + | None -> tuple_cmp (var, offs, divi) (EConj.get_rhs ts i) + (* normalize in case of a full blown equality *) + | Some (coeffj,j) -> tuple_cmp (EConj.get_rhs ts i) @@ Rhs.subst (EConj.get_rhs ts j) j (var, offs, divi) in if env_comp = -2 || env_comp > 0 then false else - if is_bot_env t1 || is_top t2 then true else - if is_bot_env t2 || is_top t1 then false else + if is_bot_env t1 || is_top t2 then true + else if is_bot_env t2 || is_top t1 then false else let m1, m2 = Option.get t1.d, Option.get t2.d in let m1' = if env_comp = 0 then m1 else VarManagement.dim_add (Environment.dimchange t1.env t2.env) m1 in EConj.IntMap.for_all (implies m1') (snd m2) (* even on sparse m2, it suffices to check the non-trivial equalities, still present in sparse m2 *) @@ -412,32 +487,50 @@ struct (* joinfunction handles the dirty details of performing an "inner join" on the lhs of both bindings; in the resulting binding, the lhs is then mapped to values that are later relevant for sorting/grouping, i.e. - lhs itself - - the difference of both offsets + - criteria A and B that characterize equivalence class, depending on the reference variable and the affine expression parameters wrt. each EConj - rhs1 - - rhs2 + - rhs2 however, we have to account for the sparseity of EConj maps by manually patching holes with default values *) - let joinfunction lhs rhs1 rhs2 = match rhs1, rhs2 with - | Some (ai,aj),Some (bi,bj) -> Some (lhs,Z.(aj - bj),(ai,aj),(bi,bj)) (* this is explicitely what we want *) - | None, Some (bi,bj) -> Some (lhs,Z.neg bj ,Rhs.var_zero lhs,(bi,bj)) (* account for the sparseity of binding 1 *) - | Some (ai,aj), None -> Some (lhs,aj ,(ai,aj),Rhs.var_zero lhs) (* account for the sparseity of binding 2 *) - | _,_ -> None (* no binding for lhs in both maps is replicated implicitely in a sparse result map *) + let joinfunction lhs rhs1 rhs2 = + ( + let e = Option.default (Rhs.var_zero lhs) in + match rhs1,rhs2 with (* first of all re-instantiate implicit sparse elements *) + | None, None -> None + | a, b -> Some (e a, e b)) + |> + BatOption.map (fun (r1,r2) -> match (r1,r2) with (* criterion A , criterion B *) + | (Some (c1,_),o1,d1), (Some (c2,_),o2,d2)-> lhs, Q.make Z.((o1*d2)-(o2*d1)) Z.(c1*d2), Q.make Z.(c2*d2) Z.(c1*d1), r1, r2 + | (None, oc,dc), (Some (cv,_),ov,dv) + | (Some (cv,_),ov,dv), (None ,oc,dc)-> lhs, Q.make Z.((oc*dv)-(ov*dc)) Z.(dc*cv), Q.one , r1, r2 (* equivalence class defined by (oc/dc-ov/dv)/(cv/dv) *) + | (None, o1,d1), (None ,o2,d2)-> lhs, (if Z.(zero = ((o1*d2)-(o2*d1))) then Q.one else Q.zero), Q.zero, r1, r2 (* only two equivalence classes: constants with matching values or constants with different values *) + ) in let table = List.of_enum @@ EConj.IntMap.values @@ EConj.IntMap.merge joinfunction (snd ad) (snd bd) in - (*compare two variables for grouping depending on delta function and reference index*) - let cmp_z (_, t0i, t1i, t2i) (_, t0j, t1j, t2j) = - let cmp_ref = Option.compare ~cmp:Int.compare in - Tuple3.compare ~cmp1:cmp_ref ~cmp2:cmp_ref ~cmp3:Z.compare (fst t1i, fst t2i, t0i) (fst t1j, fst t2j, t0j) + (* compare two variables for grouping depending on affine function parameters a, b and reference variable indices *) + let cmp_z (_, ai, bi, t1i, t2i) (_, aj, bj, t1j, t2j) = + let cmp_ref = Option.compare ~cmp:(fun x y -> Int.compare (snd x) (snd y)) in + Tuple4.compare ~cmp1:cmp_ref ~cmp2:cmp_ref ~cmp3:Q.compare ~cmp4:Q.compare (Tuple3.first t1i, Tuple3.first t2i, ai, bi) (Tuple3.first t1j, Tuple3.first t2j, aj, bj) in - (*Calculate new components as groups*) + (* Calculate new components as groups *) let new_components = BatList.group cmp_z table in - (*Adjust the domain array to represent the new components*) - let modify map idx_h b_h (idx, _, (opt1, z1), (opt2, z2)) = EConj.set_rhs map (idx) - (if opt1 = opt2 && Z.equal z1 z2 then (opt1, z1) - else (Some idx_h, Z.(z1 - b_h))) + let varentry ci offi ch offh xh = + let (coeff,off,d) = Q.(ci,(offi*ch)-(ci*offh),ch) in (* compute new rhs in Q *) + let (coeff,off,d) = Z.(coeff.num*d.den*off.den,off.num*d.den*coeff.den,d. num*coeff.den*off.den) in (* convert that back into Z *) + Rhs.canonicalize (Some(coeff,xh),off,d) in + (* ci1 = a*ch1+b /\ ci2 = a*ch2+b *) + (* ===> a = (ci1-ci2)/(ch1-ch2) b = ci2-a*ch2 *) + let constentry ci1 ci2 ch1 ch2 xh = + let a = Q.((ci1-ci2) / (ch1-ch2)) in + let b = Q.(ci2 - a*ch2) in + Rhs.canonicalize (Some (Z.(a.num*b.den),xh),Z.(b.num*a.den) ,Z.(a.den*b.den) ) in let iterate map l = match l with - | (idx_h, _, (_, b_h), _) :: t -> List.fold (fun map' e -> modify map' idx_h b_h e) map l + | (_, _, _, rhs , rhs' ) :: t when Rhs.equal rhs rhs' -> List.fold (fun acc (x,_,_,rh,_) -> EConj.set_rhs acc x rh) map l + | (h, _, _, ((Some (ch,_),oh,dh)), ((Some _,_,_) )) :: t -> List.fold (fun acc (i,_,_,(monom,oi,di),_) -> EConj.set_rhs acc i (varentry Q.(make (fst@@Option.get monom) di) Q.(make oi di) Q.(make ch dh) Q.(make oh dh) h)) map t + | (h, _, _, ((Some (ch,_),oh,dh)), ((None,_,_) )) :: t -> List.fold (fun acc (i,_,_,(monom,oi,di),_) -> EConj.set_rhs acc i (varentry Q.(make (fst@@Option.get monom) di) Q.(make oi di) Q.(make ch dh) Q.(make oh dh) h)) map t + | (h, _, _, ((None,_,_) ), ((Some (ch,_),oh,dh))) :: t -> List.fold (fun acc (i,_,_,_,(monom,oi,di)) -> EConj.set_rhs acc i (varentry Q.(make (fst@@Option.get monom) di) Q.(make oi di) Q.(make ch dh) Q.(make oh dh) h)) map t + | (h, _, _, ((None,oh1,dh1) ), ((None),oh2,dh2) ) :: t -> List.fold (fun acc (i,_,_,(_,oi1,di1),(_,oi2,di2)) -> EConj.set_rhs acc i (constentry Q.(make oi1 di1) Q.(make oi2 di2) Q.(make oh1 dh1) Q.(make oh2 dh2) h)) map t | [] -> let exception EmptyComponent in raise EmptyComponent in Some (List.fold iterate (EConj.make_empty_conj @@ fst ad) new_components) @@ -450,7 +543,7 @@ struct | Some x, Some y when is_top a || is_top b -> let new_env = Environment.lce a.env b.env in top_of new_env - | Some x, Some y when (Environment.compare a.env b.env <> 0) -> + | Some x, Some y when (Environment.cmp a.env b.env <> 0) -> let sup_env = Environment.lce a.env b.env in let mod_x = dim_add (Environment.dimchange a.env sup_env) x in let mod_y = dim_add (Environment.dimchange b.env sup_env) y in @@ -512,15 +605,15 @@ struct | None -> (* Statement "assigned_var = ?" (non-linear assignment) *) forget_var t var - | Some (None, off) -> + | Some (None, off, divi) -> (* Statement "assigned_var = off" (constant assignment) *) - assign_const (forget_var t var) var_i off - | Some (Some exp_var, off) when var_i = exp_var -> - (* Statement "assigned_var = assigned_var + off" *) - subtract_const_from_var t var_i off - | Some (Some exp_var, off) -> - (* Statement "assigned_var = exp_var + off" (assigned_var is not the same as exp_var) *) - meet_with_one_conj (forget_var t var) var_i (Some exp_var, off) + assign_const (forget_var t var) var_i off divi + | Some (Some (coeff_var,exp_var), off, divi) when var_i = exp_var -> + (* Statement "assigned_var = (coeff_var*assigned_var + off) / divi" *) + {d=Some (EConj.affine_transform d var_i (coeff_var, var_i, off, divi)); env=t.env } + | Some (Some monomial, off, divi) -> + (* Statement "assigned_var = (monomial) + off / divi" (assigned_var is not the same as exp_var) *) + meet_with_one_conj (forget_var t var) var_i (Some (monomial), off, divi) end | None -> bot_env @@ -537,8 +630,8 @@ struct let assign_exp ask t var exp no_ov = let res = assign_exp ask t var exp no_ov in - if M.tracing then M.tracel "ops" "assign_exp t:\n %s \n var: %s \n exp: %a\n no_ov: %b -> \n %s" - (show t) (Var.to_string var) d_exp exp (Lazy.force no_ov) (show res) ; + if M.tracing then M.tracel "ops" "assign_exp t:\n %s \n var: %a \n exp: %a\n no_ov: %b -> \n %s" + (show t) Var.pretty var d_exp exp (Lazy.force no_ov) (show res); res let assign_var (t: VarManagement.t) v v' = @@ -547,7 +640,7 @@ struct let assign_var t v v' = let res = assign_var t v v' in - if M.tracing then M.tracel "ops" "assign_var t:\n %s \n v: %s \n v': %s\n -> %s" (show t) (Var.to_string v) (Var.to_string v') (show res) ; + if M.tracing then M.tracel "ops" "assign_var t:\n %s \n v: %a \n v': %a\n -> %s" (show t) Var.pretty v Var.pretty v' (show res); res (** Parallel assignment of variables. @@ -600,7 +693,7 @@ struct let substitute_exp ask t var exp no_ov = let res = substitute_exp ask t var exp no_ov in - if M.tracing then M.tracel "ops" "Substitute_expr t: \n %s \n var: %s \n exp: %a \n -> \n %s" (show t) (Var.to_string var) d_exp exp (show res); + if M.tracing then M.tracel "ops" "Substitute_expr t: \n %s \n var: %a \n exp: %a \n -> \n %s" (show t) Var.pretty var d_exp exp (show res); res let substitute_exp ask t var exp no_ov = timing_wrap "substitution" (substitute_exp ask t var exp) no_ov @@ -624,9 +717,9 @@ struct | Some d -> match simplified_monomials_from_texp t (Texpr1.to_expr @@ Tcons1.get_texpr1 tcons) with | None -> t - | Some (sum_of_terms, constant) ->( + | Some (sum_of_terms, (constant,divisor)) ->( match sum_of_terms with - | [] -> (* no reference variables in the guard *) + | [] -> (* no reference variables in the guard, so check constant for zero *) begin match Tcons1.get_typ tcons with | EQ when Z.equal constant Z.zero -> t | SUPEQ when Z.geq constant Z.zero -> t @@ -635,15 +728,21 @@ struct | EQMOD _ -> t | _ -> bot_env (* all other results are violating the guard *) end - | [(varexpr, index)] -> (* guard has a single reference variable only *) - if Tcons1.get_typ tcons = EQ && Z.divisible constant varexpr then - meet_with_one_conj t index (None, (Z.(-(constant) / varexpr))) + | [(coeff, index, divi)] -> (* guard has a single reference variable only *) + if Tcons1.get_typ tcons = EQ then + meet_with_one_conj t index (Rhs.canonicalize (None, Z.neg @@ Z.(divi*constant),Z.(coeff*divisor))) else t (* only EQ is supported in equality based domains *) - | [(a1,var1); (a2,var2)] -> (* two variables in relation needs a little sorting out *) + | [(c1,var1,d1); (c2,var2,d2)] -> (* two variables in relation needs a little sorting out *) begin match Tcons1.get_typ tcons with - | EQ when Z.(a1 * a2 = -one) -> (* var1-var1 or var2-var1 *) - meet_with_one_conj t var2 (Some var1, Z.mul a1 constant) + | EQ -> (* c1*var1/d1 + c2*var2/d2 +constant/divisor = 0*) + (* ======> c1*divisor*d2 * var1 = -c2*divisor*d1 * var2 +constant*-d1*d2*) + (* \/ c2*divisor*d1 * var2 = -c1*divisor*d2 * var1 +constant*-d1*d2*) + let open Z in + if var1 < var2 then + meet_with_one_conj t var2 (Rhs.canonicalize (Some (neg @@ c1*divisor,var1),neg @@ constant*d2*d1,c2*divisor*d1)) + else + meet_with_one_conj t var1 (Rhs.canonicalize (Some (neg @@ c2*divisor,var2),neg @@ constant*d2*d1,c1*divisor*d2)) | _-> t (* Not supported in equality based 2vars without coeffiients *) end | _ -> t (* For equalities of more then 2 vars we just return t *)) @@ -691,20 +790,20 @@ struct let of_coeff xi coeffs o = let typ = (Option.get @@ V.to_cil_varinfo xi).vtype in let ikind = Cilfacade.get_ikind typ in - let cst = Coeff.s_of_mpqf @@ Mpqf.of_mpz (Z_mlgmpidl.mpz_of_z @@ IntDomain.Size.cast ikind o) in + let cst = Coeff.s_of_z (IntDomain.Size.cast ikind o) in let lincons = Lincons1.make (Linexpr1.make t.env) Lincons1.EQ in Lincons1.set_list lincons coeffs (Some cst); lincons in let get_const acc i = function - | (None, o) -> + | (None, o, d) -> let xi = Environment.var_of_dim t.env i in - of_coeff xi [(Coeff.s_of_int (-1), xi)] o :: acc - | (Some r, _) when r = i -> acc - | (Some r, o) -> + of_coeff xi [(GobApron.Coeff.s_of_z @@ Z.neg d, xi)] o :: acc + | (Some (c,r), _,_) when r = i -> acc + | (Some (c,r), o, d) -> let xi = Environment.var_of_dim t.env i in let ri = Environment.var_of_dim t.env r in - of_coeff xi [(Coeff.s_of_int (-1), xi); (Coeff.s_of_int 1, ri)] o :: acc + of_coeff xi [(GobApron.Coeff.s_of_z @@ Z.neg d, xi); (GobApron.Coeff.s_of_z c, ri)] o :: acc in BatOption.get t.d |> fun (_,map) -> EConj.IntMap.fold (fun lhs rhs list -> get_const list lhs rhs) map [] diff --git a/src/cdomains/apron/relationDomain.apron.ml b/src/cdomains/apron/relationDomain.apron.ml index 9b1eff0c64..3b7226889f 100644 --- a/src/cdomains/apron/relationDomain.apron.ml +++ b/src/cdomains/apron/relationDomain.apron.ml @@ -146,7 +146,7 @@ end type ('a, 'b) relcomponents_t = { rel: 'a; priv: 'b; -} [@@deriving eq, ord, hash, to_yojson] +} [@@deriving eq, ord, hash, to_yojson, relift, lattice] module RelComponents (D3: S3) (PrivD: Lattice.S): sig @@ -155,13 +155,11 @@ sig end = struct module RD = D3 - type t = (RD.t, PrivD.t) relcomponents_t [@@deriving eq, ord, hash, to_yojson] + type t = (RD.t, PrivD.t) relcomponents_t [@@deriving eq, ord, hash, to_yojson, relift, lattice] include Printable.Std open Pretty - let relift {rel; priv} = {rel = RD.relift rel; priv = PrivD.relift priv} - let show r = let first = RD.show r.rel in let third = PrivD.show r.priv in @@ -185,26 +183,11 @@ struct let tr = QCheck.pair (RD.arbitrary ()) (PrivD.arbitrary ()) in QCheck.map ~rev:to_tuple of_tuple tr - let bot () = {rel = RD.bot (); priv = PrivD.bot ()} - let is_bot {rel; priv} = RD.is_bot rel && PrivD.is_bot priv - let top () = {rel = RD.top (); priv = PrivD.bot ()} - let is_top {rel; priv} = RD.is_top rel && PrivD.is_top priv - - let leq {rel=x1; priv=x3 } {rel=y1; priv=y3} = - RD.leq x1 y1 && PrivD.leq x3 y3 - let pretty_diff () (({rel=x1; priv=x3}:t),({rel=y1; priv=y3}:t)): Pretty.doc = if not (RD.leq x1 y1) then RD.pretty_diff () (x1,y1) else PrivD.pretty_diff () (x3,y3) - - let op_scheme op1 op3 {rel=x1; priv=x3} {rel=y1; priv=y3}: t = - {rel = op1 x1 y1; priv = op3 x3 y3 } - let join = op_scheme RD.join PrivD.join - let meet = op_scheme RD.meet PrivD.meet - let widen = op_scheme RD.widen PrivD.widen - let narrow = op_scheme RD.narrow PrivD.narrow end diff --git a/src/cdomains/apron/sharedFunctions.apron.ml b/src/cdomains/apron/sharedFunctions.apron.ml index 827dc252fc..b9d93bfd99 100644 --- a/src/cdomains/apron/sharedFunctions.apron.ml +++ b/src/cdomains/apron/sharedFunctions.apron.ml @@ -6,7 +6,8 @@ open GobApron module M = Messages -let int_of_scalar ?round (scalar: Scalar.t) = + +let int_of_scalar ?(scalewith=Z.one) ?round (scalar: Scalar.t) = if Scalar.is_infty scalar <> 0 then (* infinity means unbounded *) None else @@ -20,23 +21,26 @@ let int_of_scalar ?round (scalar: Scalar.t) = | None when Stdlib.Float.is_integer f -> Some f | None -> None in - Z.of_float f + Z.(of_float f * scalewith) | Mpqf scalar -> (* octMPQ, boxMPQ, polkaMPQ *) let n = Mpqf.get_num scalar in let d = Mpqf.get_den scalar in + let scale = Z_mlgmpidl.mpz_of_z scalewith in let+ z = if Mpzf.cmp_int d 1 = 0 then (* exact integer (denominator 1) *) - Some n + Some (Mpzf.mul scale n) else begin match round with - | Some `Floor -> Some (Mpzf.fdiv_q n d) (* floor division *) - | Some `Ceil -> Some (Mpzf.cdiv_q n d) (* ceiling division *) - | None -> None + | Some `Floor -> Some (Mpzf.mul scale (Mpzf.fdiv_q n d)) (* floor division *) + | Some `Ceil -> Some (Mpzf.mul scale (Mpzf.cdiv_q n d)) (* ceiling division *) + | None -> let product = Mpzf.mul scale n in if Mpz.divisible_p product d then + Some (Mpzf.divexact product d) (* scale, preferably with common denominator *) + else None end in Z_mlgmpidl.z_of_mpzf z | _ -> - failwith ("int_of_scalar: unsupported: " ^ Scalar.to_string scalar) + failwith ("int_of_scalar: unsupported: " ^ Scalar.show scalar) module type ConvertArg = @@ -109,7 +113,7 @@ struct | `Bot -> raise (Unsupported_CilExp Exp_not_supported) (* This should never happen according to Michael Schwarz *) | `Top -> IntDomain.IntDomTuple.top_of ik | `Lifted x -> x (* Cast should be unnecessary because it should be taken care of by EvalInt. *) in - if M.tracing then M.trace "relation" "texpr1_expr_of_cil_exp/query: %a -> %a" d_plainexp e IntDomain.IntDomTuple.pretty res; + if M.tracing then M.trace "relation-query" "texpr1_expr_of_cil_exp/query: %a -> %a" d_plainexp e IntDomain.IntDomTuple.pretty res; res in (* recurse without env and ask arguments *) @@ -129,15 +133,22 @@ struct else failwith "texpr1_expr_of_cil_exp: globals must be replaced with temporary locals" | Const (CInt (i, _, _)) -> - Cst (Coeff.s_of_mpqf (Mpqf.of_mpz (Z_mlgmpidl.mpz_of_z i))) + Cst (Coeff.s_of_z i) | exp -> match Cilfacade.get_ikind_exp exp with | ik -> let expr = (** simplify asks for a constant value of some subexpression e, similar to a constant fold. In particular but not exclusively this query is answered by the 2 var equalities domain itself. This normalizes arbitrary expressions to a point where they - might be able to be represented by means of 2 var equalities *) + might be able to be represented by means of 2 var equalities + + This simplification happens during a time, when there are temporary variables a#in and a#out part of the expression, + but are not represented in the ctx, thus queries may result in top for these variables. Wrapping this in speculative + mode is a stop-gap measure to avoid flagging overflows. We however should address simplification in a more generally useful way. + outside of the apron-related expression conversion. + *) let simplify e = + GobRef.wrap AnalysisState.executing_speculative_computations true @@ fun () -> let ikind = try (Cilfacade.get_ikind_exp e) with Invalid_argument _ -> raise (Unsupported_CilExp Exp_not_supported) in let simp = query e ikind in let const = IntDomain.IntDomTuple.to_int @@ IntDomain.IntDomTuple.cast_to ikind simp in @@ -164,7 +175,7 @@ struct (* convert response to a constant *) let const = IntDomain.IntDomTuple.to_int @@ IntDomain.IntDomTuple.cast_to t_ik res in match const with - | Some c -> Cst (Coeff.s_of_mpqf (Mpqf.of_mpz (Z_mlgmpidl.mpz_of_z c))) (* Got a constant value -> use it straight away *) + | Some c -> Cst (Coeff.s_of_z c) (* Got a constant value -> use it straight away *) (* I gotten top, we can not guarantee injectivity *) | None -> if IntDomain.IntDomTuple.is_top_of t_ik res then raise (Unsupported_CilExp (Cast_not_injective t)) else ( (* Got a ranged value different from top, so let's check bounds manually *) @@ -189,7 +200,7 @@ struct in let exp = Cil.constFold false exp in let res = conv exp in - if M.tracing then M.trace "relation" "texpr1_expr_of_cil_exp: %a -> %s (%b)" d_plainexp exp (Format.asprintf "%a" Texpr1.print_expr res) (Lazy.force no_ov); + if M.tracing then M.trace "relation" "texpr1_expr_of_cil_exp: %a -> %a (%b)" d_plainexp exp Texpr1.Expr.pretty res (Lazy.force no_ov); res let texpr1_of_cil_exp ask d env e no_ov = @@ -238,11 +249,11 @@ module CilOfApron (V: SV) = struct exception Unsupported_Linexpr1 - let cil_exp_of_linexpr1 (linexpr1:Linexpr1.t) = + let cil_exp_of_linexpr1 ?scalewith (linexpr1:Linexpr1.t) = let longlong = TInt(ILongLong,[]) in let coeff_to_const consider_flip (c:Coeff.union_5) = match c with | Scalar c -> - (match int_of_scalar c with + (match int_of_scalar ?scalewith c with | Some i -> let ci,truncation = truncateCilint ILongLong i in if truncation = NoTruncation then @@ -256,15 +267,14 @@ struct else Const (CInt(i,ILongLong,None)), false else - (M.warn ~category:Analyzer "Invariant Apron: coefficient is not int: %s" (Scalar.to_string c); raise Unsupported_Linexpr1) + (M.warn ~category:Analyzer "Invariant Apron: coefficient is not int: %a" Scalar.pretty c; raise Unsupported_Linexpr1) | None -> raise Unsupported_Linexpr1) | _ -> raise Unsupported_Linexpr1 in let expr = ref (fst @@ coeff_to_const false (Linexpr1.get_cst linexpr1)) in let append_summand (c:Coeff.union_5) v = match V.to_cil_varinfo v with - | Some vinfo -> - (* TODO: What to do with variables that have a type that cannot be stored into ILongLong to avoid overflows? *) + | Some vinfo when IntDomain.Size.is_cast_injective ~from_type:vinfo.vtype ~to_type:(TInt(ILongLong,[])) -> let var = Cilfacade.mkCast ~e:(Lval(Var vinfo,NoOffset)) ~newt:longlong in let coeff, flip = coeff_to_const true c in let prod = BinOp(Mult, coeff, var, longlong) in @@ -272,17 +282,45 @@ struct expr := BinOp(MinusA,!expr,prod,longlong) else expr := BinOp(PlusA,!expr,prod,longlong) - | None -> M.warn ~category:Analyzer "Invariant Apron: cannot convert to cil var: %s" (Var.to_string v); raise Unsupported_Linexpr1 + | None -> M.warn ~category:Analyzer "Invariant Apron: cannot convert to cil var: %a" Var.pretty v; raise Unsupported_Linexpr1 + | _ -> M.warn ~category:Analyzer "Invariant Apron: cannot convert to cil var in overflow preserving manner: %a" Var.pretty v; raise Unsupported_Linexpr1 in Linexpr1.iter append_summand linexpr1; !expr + let lcm_den linexpr1 = + let exception UnsupportedScalar + in + let frac_of_scalar scalar = + if Scalar.is_infty scalar <> 0 then (* infinity means unbounded *) + None + else match scalar with + | Float f -> if Stdlib.Float.is_integer f then Some (Q.of_float f) else None + | Mpqf f -> Some (Z_mlgmpidl.q_of_mpqf f) + | _ -> raise UnsupportedScalar + in + let extract_den (c:Coeff.union_5) = + match c with + | Scalar c -> BatOption.map Q.den (frac_of_scalar c) + | _ -> None + in + let lcm_denom = ref (BatOption.default Z.one (extract_den (Linexpr1.get_cst linexpr1))) in + let lcm_coeff (c:Coeff.union_5) _ = + match (extract_den c) with + | Some z -> lcm_denom := Z.lcm z !lcm_denom + | _ -> () + in + try + Linexpr1.iter lcm_coeff linexpr1; !lcm_denom + with UnsupportedScalar -> Z.one + let cil_exp_of_lincons1 (lincons1:Lincons1.t) = let zero = Cil.kinteger ILongLong 0 in try let linexpr1 = Lincons1.get_linexpr1 lincons1 in - let cilexp = cil_exp_of_linexpr1 linexpr1 in + let common_denominator = lcm_den linexpr1 in + let cilexp = cil_exp_of_linexpr1 ~scalewith:common_denominator linexpr1 in match Lincons1.get_typ lincons1 with | EQ -> Some (Cil.constFold false @@ BinOp(Eq, cilexp, zero, TInt(IInt,[]))) | SUPEQ -> Some (Cil.constFold false @@ BinOp(Ge, cilexp, zero, TInt(IInt,[]))) diff --git a/src/cdomains/baseDomain.ml b/src/cdomains/baseDomain.ml index 1be0fb8c1e..64b5a174e8 100644 --- a/src/cdomains/baseDomain.ml +++ b/src/cdomains/baseDomain.ml @@ -38,7 +38,7 @@ type 'a basecomponents_t = { deps: PartDeps.t; weak: WeakUpdates.t; priv: 'a; -} [@@deriving eq, ord, hash] +} [@@deriving eq, ord, hash, relift, lattice] module BaseComponents (PrivD: Lattice.S): @@ -47,7 +47,7 @@ sig val op_scheme: (CPA.t -> CPA.t -> CPA.t) -> (PartDeps.t -> PartDeps.t -> PartDeps.t) -> (WeakUpdates.t -> WeakUpdates.t -> WeakUpdates.t) -> (PrivD.t -> PrivD.t -> PrivD.t) -> t -> t -> t end = struct - type t = PrivD.t basecomponents_t [@@deriving eq, ord, hash] + type t = PrivD.t basecomponents_t [@@deriving eq, ord, hash, relift, lattice] include Printable.Std open Pretty @@ -90,14 +90,6 @@ struct let tr = QCheck.quad (CPA.arbitrary ()) (PartDeps.arbitrary ()) (WeakUpdates.arbitrary ()) (PrivD.arbitrary ()) in QCheck.map ~rev:to_tuple of_tuple tr - let bot () = { cpa = CPA.bot (); deps = PartDeps.bot (); weak = WeakUpdates.bot (); priv = PrivD.bot ()} - let is_bot {cpa; deps; weak; priv} = CPA.is_bot cpa && PartDeps.is_bot deps && WeakUpdates.is_bot weak && PrivD.is_bot priv - let top () = {cpa = CPA.top (); deps = PartDeps.top (); weak = WeakUpdates.top () ; priv = PrivD.top ()} - let is_top {cpa; deps; weak; priv} = CPA.is_top cpa && PartDeps.is_top deps && WeakUpdates.is_top weak && PrivD.is_top priv - - let leq {cpa=x1; deps=x2; weak=x3; priv=x4 } {cpa=y1; deps=y2; weak=y3; priv=y4} = - CPA.leq x1 y1 && PartDeps.leq x2 y2 && WeakUpdates.leq x3 y3 && PrivD.leq x4 y4 - let pretty_diff () (({cpa=x1; deps=x2; weak=x3; priv=x4}:t),({cpa=y1; deps=y2; weak=y3; priv=y4}:t)): Pretty.doc = if not (CPA.leq x1 y1) then CPA.pretty_diff () (x1,y1) @@ -110,13 +102,6 @@ struct let op_scheme op1 op2 op3 op4 {cpa=x1; deps=x2; weak=x3; priv=x4} {cpa=y1; deps=y2; weak=y3; priv=y4}: t = {cpa = op1 x1 y1; deps = op2 x2 y2; weak = op3 x3 y3; priv = op4 x4 y4 } - let join = op_scheme CPA.join PartDeps.join WeakUpdates.join PrivD.join - let meet = op_scheme CPA.meet PartDeps.meet WeakUpdates.meet PrivD.meet - let widen = op_scheme CPA.widen PartDeps.widen WeakUpdates.widen PrivD.widen - let narrow = op_scheme CPA.narrow PartDeps.narrow WeakUpdates.narrow PrivD.narrow - - let relift {cpa; deps; weak; priv} = - {cpa = CPA.relift cpa; deps = PartDeps.relift deps; weak = WeakUpdates.relift weak; priv = PrivD.relift priv} end module type ExpEvaluator = diff --git a/src/cdomains/lockDomain.ml b/src/cdomains/lockDomain.ml index baee8a2ce6..b71573d6f6 100644 --- a/src/cdomains/lockDomain.ml +++ b/src/cdomains/lockDomain.ml @@ -1,126 +1,163 @@ (** Lockset domains. *) -module Addr = ValueDomain.Addr -module Offs = ValueDomain.Offs -module Exp = CilType.Exp -module IdxDom = ValueDomain.IndexDomain +open struct + module MvalZ = Mval.Z (* So we can define MustLock using Mval.Z after redefining Mval *) +end -open GoblintCil +module IndexDomain = ValueDomain.IndexDomain +module Mval = ValueDomain.Mval +module Addr = ValueDomain.Addr -module Mutexes = SetDomain.ToppedSet (Addr) (struct let topname = "All mutexes" end) (* TODO: AD? *) -module Simple = Lattice.Reverse (Mutexes) -module Priorities = IntDomain.Lifted +module MustLock = +struct + include MvalZ + + let semantic_equal_mval ((v, o): t) ((v', o'): Mval.t): bool option = + if CilType.Varinfo.equal v v' then ( + let (index1, _) = GoblintCil.bitsOffset v.vtype (Offset.Z.to_cil o) in (* TODO: better way to compute this? as Z.t not int *) + let index2: IndexDomain.t = ValueDomain.Offs.to_index ~typ:v.vtype o' in (* TODO: is this bits or bytes? *) + match IndexDomain.equal_to (Z.of_int index1) index2 with + | `Eq -> Some true + | `Neq -> Some false + | `Top -> None + ) + else + Some false + + let of_var v: t = (v, `NoOffset) + + let of_mval ((v, o): Mval.t): t = + (v, Offset.Poly.map_indices (fun i -> IndexDomain.to_int i |> Option.get) o) + + let to_mval ((v, o): t): Mval.t = + (v, Offset.Poly.map_indices (IndexDomain.of_int (Cilfacade.ptrdiff_ikind ())) o) +end -module Lockset = +module MustLockset = struct + include SetDomain.Reverse (SetDomain.ToppedSet (MustLock) (struct let topname = "All mutexes" end)) - (* true means exclusive lock and false represents reader lock*) - module RW = IntDomain.Booleans + let all (): t = `Top + let is_all (set: t) = set = `Top +end - (* pair Addr and RW; also change pretty printing*) - module Lock = - struct - include Printable.Prod (Addr) (RW) +(* true means exclusive lock and false represents reader lock*) +module RW = BoolDomain.MayBool (* TODO: name booleans? *) - let pretty () (a, write) = - if write then - Addr.pretty () a - else - Pretty.dprintf "read lock %a" Addr.pretty a +(* pair Addr and RW; also change pretty printing*) +module MakeRW (P: Printable.S) = +struct + include Printable.Prod (P) (RW) + + let pretty () (a, write) = + if write then + P.pretty () a + else + GoblintCil.Pretty.dprintf "read lock %a" P.pretty a + + include Printable.SimplePretty ( + struct + type nonrec t = t + let pretty = pretty + end + ) +end - include Printable.SimplePretty ( - struct - type nonrec t = t - let pretty = pretty - end - ) - end +module MvalRW = MakeRW (Mval) +module AddrRW = MakeRW (Addr) +module MustLockRW = MakeRW (MustLock) - include SetDomain.Reverse(SetDomain.ToppedSet (Lock) (struct let topname = "All mutexes" end)) +module MustLocksetRW = +struct + include SetDomain.Reverse (SetDomain.ToppedSet (MustLockRW) (struct let topname = "All mutexes" end)) let name () = "lockset" - let add ((addr, _) as lock) set = - match addr with - | Addr.Addr mv when Addr.Mval.is_definite mv -> (* avoids NULL *) - add lock set - | _ -> + let add_mval_rw ((mv, rw): MvalRW.t) (set: t) = + if Addr.Mval.is_definite mv then + add (MustLock.of_mval mv, rw) set + else set - let remove ((addr, _) as lock) set = - match addr with - | Addr.Addr mv when Addr.Mval.is_definite mv -> (* avoids NULL *) - remove lock set - | _ -> - filter (fun (addr', _) -> - Addr.semantic_equal addr addr' = Some false + let remove_mval_rw ((mv, rw): MvalRW.t) (set: t) = + if Addr.Mval.is_definite mv then + remove (MustLock.of_mval mv, rw) set + else + filter (fun (ml, _) -> + MustLock.semantic_equal_mval ml mv = Some false ) set - let export_locks ls = - let f (x,_) set = Mutexes.add x set in - fold f ls (Mutexes.empty ()) + let mem_mval (mv: Mval.t) (set: t) = + if Mval.is_definite mv then ( + let ml = MustLock.of_mval mv in + mem (ml, true) set || mem (ml, false) set + ) + else + false + + let remove_mval (mv: Mval.t) set = + remove_mval_rw (mv, true) (remove_mval_rw (mv, false) set) + + let all (): t = `Top + let is_all (set: t) = set = `Top + + let to_must_lockset (ls: t): MustLockset.t = + let f (ml, _) set = MustLockset.add ml set in + fold f ls (MustLockset.empty ()) end -module MayLockset = -struct - include Lockset - let leq x y = leq y x - let join = Lockset.meet - let meet = Lockset.join - let top = Lockset.bot - let bot = Lockset.top +module MustMultiplicity = struct + (* the maximum multiplicity which we keep track of precisely *) + let max_count () = 4 + + module Count = Lattice.Reverse ( + Lattice.Chain ( + struct + let n () = max_count () + 1 + let names x = if x = max_count () then Format.asprintf ">= %d" x else Format.asprintf "%d" x + end + ) + ) + + include MapDomain.MapTop_LiftBot (MustLock) (Count) + + let name () = "multiplicity" + + let increment v x = + let current = find v x in + if current = max_count () then + x + else + add v (current + 1) x + + let increment mv m = + if Addr.Mval.is_definite mv then + increment (MustLock.of_mval mv) m + else + m + + let decrement v x = + let current = find v x in + if current = 0 then + (x, true) + else + (add v (current - 1) x, current - 1 = 0) (* TODO: remove if 0? *) + + let decrement mv m = + if Addr.Mval.is_definite mv then + decrement (MustLock.of_mval mv) m + else + (* TODO: non-definite should also decrement (to 0)? *) + fold (fun ml _ (m, rmed) -> + if MustLock.semantic_equal_mval ml mv = Some false then + (m, rmed) + else ( + let (m', rmed') = decrement ml m in + (m', rmed || rmed') + ) + ) m (m, false) end module MayLocksetNoRW = struct include PreValueDomain.AD end - -module Symbolic = -struct - (* TODO: use SetDomain.Reverse *) - module S = SetDomain.ToppedSet (Exp) (struct let topname = "All mutexes" end) - include Lattice.Reverse (S) - - let rec eq_set (ask: Queries.ask) e = - S.union - (match ask.f (Queries.EqualSet e) with - | es when not (Queries.ES.is_bot es) -> - Queries.ES.fold S.add es (S.empty ()) - | _ -> S.empty ()) - (match e with - | SizeOf _ - | SizeOfE _ - | SizeOfStr _ - | AlignOf _ - | Const _ - | AlignOfE _ - | UnOp _ - | BinOp _ - | Question _ - | Real _ - | Imag _ - | AddrOfLabel _ -> S.empty () - | AddrOf (Var _,_) - | StartOf (Var _,_) - | Lval (Var _,_) -> S.singleton e - | AddrOf (Mem e,ofs) -> S.map (fun e -> AddrOf (Mem e,ofs)) (eq_set ask e) - | StartOf (Mem e,ofs) -> S.map (fun e -> StartOf (Mem e,ofs)) (eq_set ask e) - | Lval (Mem e,ofs) -> S.map (fun e -> Lval (Mem e,ofs)) (eq_set ask e) - | CastE (_,e) -> eq_set ask e - ) - - let add (ask: Queries.ask) e st = - let no_casts = S.map Expcompare.stripCastsDeepForPtrArith (eq_set ask e) in - let addrs = S.filter (function AddrOf _ -> true | _ -> false) no_casts in - S.union addrs st - let remove ask e st = - (* TODO: Removing based on must-equality sets is not sound! *) - let no_casts = S.map Expcompare.stripCastsDeepForPtrArith (eq_set ask e) in - let addrs = S.filter (function AddrOf _ -> true | _ -> false) no_casts in - S.diff st addrs - let remove_var v st = S.filter (fun x -> not (SymbLocksDomain.Exp.contains_var v x)) st - - let filter = S.filter - let fold = S.fold - -end diff --git a/src/cdomains/mHP.ml b/src/cdomains/mHP.ml index f58b7d2e92..433486d4e0 100644 --- a/src/cdomains/mHP.ml +++ b/src/cdomains/mHP.ml @@ -11,10 +11,7 @@ type t = { tid: ThreadIdDomain.ThreadLifted.t; created: ConcDomain.ThreadSet.t; must_joined: ConcDomain.ThreadSet.t; -} [@@deriving eq, ord, hash] - -let relift {tid; created; must_joined} = - {tid = ThreadIdDomain.ThreadLifted.relift tid; created = ConcDomain.ThreadSet.relift created; must_joined = ConcDomain.ThreadSet.relift must_joined} +} [@@deriving eq, ord, hash, relift] let current (ask:Queries.ask) = { diff --git a/src/cdomains/pthreadDomain.ml b/src/cdomains/pthreadDomain.ml index 2c12689dcf..8862a768bc 100644 --- a/src/cdomains/pthreadDomain.ml +++ b/src/cdomains/pthreadDomain.ml @@ -25,7 +25,7 @@ end module D = struct include Printable.StdLeaf - type t = { tid : Tid.t; pred : Pred.t; ctx : Ctx.t } [@@deriving eq, ord, hash, to_yojson] + type t = { tid : Tid.t; pred : Pred.t; ctx : Ctx.t } [@@deriving eq, ord, hash, relift, to_yojson, lattice] (** printing *) let show x = @@ -35,26 +35,15 @@ module D = struct (Pred.show x.pred) (Ctx.show x.ctx) - include Printable.SimpleShow(struct type nonrec t = t let show = show end) (* TODO: overrides derived to_yojson *) + include Printable.SimpleShow (struct + type nonrec t = t + let show = show + end) (* TODO: overrides derived to_yojson *) let name () = "pthread state" let make tid pred ctx = { tid; pred; ctx } - let bot () = { tid = Tid.bot (); pred = Pred.bot (); ctx = Ctx.bot () } - let is_bot x = Tid.is_bot x.tid && Pred.is_bot x.pred && Ctx.is_bot x.ctx let any_is_bot x = Tid.is_bot x.tid || Pred.is_bot x.pred - let top () = { tid = Tid.top (); pred = Pred.top (); ctx = Ctx.top () } - let is_top x = Tid.is_top x.tid && Pred.is_top x.pred && Ctx.is_top x.ctx - - let leq x y = Tid.leq x.tid y.tid && Pred.leq x.pred y.pred && Ctx.leq x.ctx y.ctx - - let op_scheme op1 op2 op3 x y : t = - { tid = op1 x.tid y.tid; pred = op2 x.pred y.pred; ctx = op3 x.ctx y.ctx } - - let join = op_scheme Tid.join Pred.join Ctx.join - let widen = join - let meet = op_scheme Tid.meet Pred.meet Ctx.meet - let narrow = meet let pretty_diff () (x,y) = if not (Tid.leq x.tid y.tid) then diff --git a/src/cdomains/symbLocksDomain.ml b/src/cdomains/symbLocksDomain.ml index ba2b96e8d0..10ea70da1d 100644 --- a/src/cdomains/symbLocksDomain.ml +++ b/src/cdomains/symbLocksDomain.ml @@ -317,3 +317,54 @@ struct let of_mval (v, o) = of_mval (v, conv_const_offset o) end + + +module Symbolic = +struct + (* TODO: use SetDomain.Reverse *) + module S = SetDomain.ToppedSet (CilType.Exp) (struct let topname = "All mutexes" end) + include Lattice.Reverse (S) + + let rec eq_set (ask: Queries.ask) e = + S.union + (match ask.f (Queries.EqualSet e) with + | es when not (Queries.ES.is_bot es) -> + Queries.ES.fold S.add es (S.empty ()) + | _ -> S.empty ()) + (match e with + | SizeOf _ + | SizeOfE _ + | SizeOfStr _ + | AlignOf _ + | Const _ + | AlignOfE _ + | UnOp _ + | BinOp _ + | Question _ + | Real _ + | Imag _ + | AddrOfLabel _ -> S.empty () + | AddrOf (Var _,_) + | StartOf (Var _,_) + | Lval (Var _,_) -> S.singleton e + | AddrOf (Mem e,ofs) -> S.map (fun e -> AddrOf (Mem e,ofs)) (eq_set ask e) + | StartOf (Mem e,ofs) -> S.map (fun e -> StartOf (Mem e,ofs)) (eq_set ask e) + | Lval (Mem e,ofs) -> S.map (fun e -> Lval (Mem e,ofs)) (eq_set ask e) + | CastE (_,e) -> eq_set ask e + ) + + let add (ask: Queries.ask) e st = + let no_casts = S.map Expcompare.stripCastsDeepForPtrArith (eq_set ask e) in + let addrs = S.filter (function AddrOf _ -> true | _ -> false) no_casts in + S.union addrs st + let remove ask e st = + (* TODO: Removing based on must-equality sets is not sound! *) + let no_casts = S.map Expcompare.stripCastsDeepForPtrArith (eq_set ask e) in + let addrs = S.filter (function AddrOf _ -> true | _ -> false) no_casts in + S.diff st addrs + let remove_var v st = S.filter (fun x -> not (Exp.contains_var v x)) st + + let filter = S.filter + let fold = S.fold + +end diff --git a/src/cdomains/threadFlagDomain.ml b/src/cdomains/threadFlagDomain.ml index 80ba9b7a52..42571656e7 100644 --- a/src/cdomains/threadFlagDomain.ml +++ b/src/cdomains/threadFlagDomain.ml @@ -15,10 +15,11 @@ module Trivial: S = struct module TrivialNames = struct - let truename = "Multithreaded" - let falsename = "Singlethreaded" + let name = "MT mode" + let true_name = "Multithreaded" + let false_name = "Singlethreaded" end - include IntDomain.MakeBooleans (TrivialNames) + include BoolDomain.MakeMayBool (TrivialNames) let is_multi x = x let is_not_main x = x diff --git a/src/common/cdomains/basetype.ml b/src/common/cdomains/basetype.ml index 1b846309aa..bf832b1c3c 100644 --- a/src/common/cdomains/basetype.ml +++ b/src/common/cdomains/basetype.ml @@ -33,17 +33,6 @@ struct let printXml f x = BatPrintf.fprintf f "\n\n%s\n\n\n" (XmlUtil.escape (show x)) end -module RawBools: Printable.S with type t = bool = -struct - include Printable.StdLeaf - open Pretty - type t = bool [@@deriving eq, ord, hash, to_yojson] - let show (x:t) = if x then "true" else "false" - let pretty () x = text (show x) - let name () = "raw bools" - let printXml f x = BatPrintf.fprintf f "\n\n%s\n\n\n" (show x) -end - module CilExp = struct include CilType.Exp diff --git a/src/common/cdomains/floatOps/floatOps.ml b/src/common/cdomains/floatOps/floatOps.ml index 80946b1749..eced6af248 100644 --- a/src/common/cdomains/floatOps/floatOps.ml +++ b/src/common/cdomains/floatOps/floatOps.ml @@ -16,7 +16,7 @@ module type CFloatType = sig val pi : t val of_float: round_mode -> float -> t - val to_float: t -> float option + val to_float: t -> float val to_big_int: t -> Z.t val is_finite: t -> bool @@ -68,7 +68,7 @@ module CDouble = struct let pi = Float.pi let of_float _ x = x - let to_float x = Some x + let to_float x = x let to_big_int = big_int_of_float let is_finite = Float.is_finite @@ -112,7 +112,7 @@ module CFloat = struct let smallest = smallest' () let pi = pi' () - let to_float x = Some x + let to_float x = x let to_big_int = big_int_of_float let is_finite x = Float.is_finite x && x >= lower_bound && x <= upper_bound diff --git a/src/common/cdomains/floatOps/floatOps.mli b/src/common/cdomains/floatOps/floatOps.mli index 7cd74f7e7d..a07582f442 100644 --- a/src/common/cdomains/floatOps/floatOps.mli +++ b/src/common/cdomains/floatOps/floatOps.mli @@ -17,7 +17,7 @@ module type CFloatType = sig val pi : t val of_float: round_mode -> float -> t - val to_float: t -> float option + val to_float: t -> float val to_big_int: t -> Z.t val is_finite: t -> bool diff --git a/src/common/domains/printable.ml b/src/common/domains/printable.ml index 24b30ce8e3..93d3f99edc 100644 --- a/src/common/domains/printable.ml +++ b/src/common/domains/printable.ml @@ -88,6 +88,20 @@ struct let to_yojson x = `String (show x) end +module type Formatable = +sig + type t + val pp: Format.formatter -> t -> unit +end + +module SimpleFormat (P: Formatable) = +struct + let show x = GobFormat.asprint P.pp x + let pretty () x = text (show x) + let printXml f x = BatPrintf.fprintf f "\n\n%s\n\n\n" (XmlUtil.escape (show x)) + let to_yojson x = `String (show x) +end + module type Name = sig val name: string end module UnitConf (N: Name) = @@ -465,7 +479,7 @@ module ProdConf (C: ProdConfiguration) (Base1: S) (Base2: S)= struct include C - type t = Base1.t * Base2.t [@@deriving eq, ord, hash] + type t = Base1.t * Base2.t [@@deriving eq, ord, hash, relift] include Std @@ -504,8 +518,6 @@ struct `Assoc [ (Base1.name (), Base1.to_yojson x); (Base2.name (), Base2.to_yojson y) ] let arbitrary () = QCheck.pair (Base1.arbitrary ()) (Base2.arbitrary ()) - - let relift (x,y) = (Base1.relift x, Base2.relift y) end module Prod = ProdConf (struct let expand_fst = true let expand_snd = true end) @@ -513,7 +525,7 @@ module ProdSimple = ProdConf (struct let expand_fst = false let expand_snd = fal module Prod3 (Base1: S) (Base2: S) (Base3: S) = struct - type t = Base1.t * Base2.t * Base3.t [@@deriving eq, ord, hash] + type t = Base1.t * Base2.t * Base3.t [@@deriving eq, ord, hash, relift] include Std let show (x,y,z) = @@ -555,40 +567,10 @@ struct let name () = Base1.name () ^ " * " ^ Base2.name () ^ " * " ^ Base3.name () - let relift (x,y,z) = (Base1.relift x, Base2.relift y, Base3.relift z) let arbitrary () = QCheck.triple (Base1.arbitrary ()) (Base2.arbitrary ()) (Base3.arbitrary ()) end -module Prod4 (Base1: S) (Base2: S) (Base3: S) (Base4: S) = struct - type t = Base1.t * Base2.t * Base3.t * Base4.t [@@deriving eq, ord, hash] - include Std - - let show (x,y,z,w) = "(" ^ Base1.show x ^ ", " ^ Base2.show y ^ ", " ^ Base3.show z ^ ", " ^ Base4.show w ^ ")" - - let pretty () (x,y,z,w) = - text "(" ++ - Base1.pretty () x - ++ text ", " ++ - Base2.pretty () y - ++ text ", " ++ - Base3.pretty () z - ++ text ", " ++ - Base4.pretty () w - ++ text ")" - - let printXml f (x,y,z,w) = - BatPrintf.fprintf f "\n\n\n%s\n\n%a\n%s\n\n%a\n%s\n\n%a\n%s\n\n%a\n\n" (XmlUtil.escape (Base1.name ())) Base1.printXml x (XmlUtil.escape (Base2.name ())) Base2.printXml y (XmlUtil.escape (Base3.name ())) Base3.printXml z (XmlUtil.escape (Base4.name ())) Base4.printXml w - - let to_yojson (x, y, z, w) = - `Assoc [ (Base1.name (), Base1.to_yojson x); (Base2.name (), Base2.to_yojson y); (Base3.name (), Base3.to_yojson z); (Base4.name (), Base4.to_yojson w) ] - - let name () = Base1.name () ^ " * " ^ Base2.name () ^ " * " ^ Base3.name () ^ " * " ^ Base4.name () - - let relift (x,y,z,w) = (Base1.relift x, Base2.relift y, Base3.relift z, Base4.relift w) - let arbitrary () = QCheck.quad (Base1.arbitrary ()) (Base2.arbitrary ()) (Base3.arbitrary ()) (Base4.arbitrary ()) -end - -module PQueue (Base: S) = +module PQueue (Base: S) = struct type t = Base.t BatDeque.dq include Std @@ -604,22 +586,22 @@ struct let rec loop n q = match BatDeque.front q with | None -> () - | Some (x, xs) -> (BatPrintf.fprintf f "%d\n%a\n" n Base.printXml x; + | Some (x, xs) -> (BatPrintf.fprintf f "%d\n%a\n" n Base.printXml x; loop (n+1) (xs)) in BatPrintf.fprintf f "\n\n"; - loop 0 xs; + loop 0 xs; BatPrintf.fprintf f "\n\n" let to_yojson q = `List (BatDeque.to_list @@ BatDeque.map (Base.to_yojson) q) let hash q = BatDeque.fold_left (fun acc x -> (acc + 71) * (Base.hash x)) 11 q let equal q1 q2 = BatDeque.eq ~eq:Base.equal q1 q2 - let compare q1 q2 = + let compare q1 q2 = match BatDeque.front q1, BatDeque.front q2 with | None, None -> 0 | None, Some(_, _) -> -1 | Some(_, _), None -> 1 - | Some(a1, q1'), Some(a2, q2') -> + | Some(a1, q1'), Some(a2, q2') -> let c = Base.compare a1 a2 in if c <> 0 then c else compare q1' q2' @@ -840,3 +822,17 @@ struct let to_yojson x = x (* override SimplePretty *) end + +module Z: S with type t = Z.t = +struct + include StdLeaf + include GobZ + let name () = "Z" + + include SimplePretty ( + struct + type nonrec t = t + let pretty = pretty + end + ) +end diff --git a/src/common/dune b/src/common/dune index 0cded0bdf9..e5aeddde7f 100644 --- a/src/common/dune +++ b/src/common/dune @@ -11,6 +11,7 @@ goblint_logs goblint_config goblint_tracing + goblint_backtrace goblint-cil fpath yojson @@ -23,7 +24,8 @@ (pps ppx_deriving.std ppx_deriving_hash - ppx_deriving_yojson)) + ppx_deriving_yojson + ppx_deriving_printable)) (instrumentation (backend bisect_ppx))) (documentation) diff --git a/src/common/framework/analysisState.ml b/src/common/framework/analysisState.ml index d1764c839f..96816b8529 100644 --- a/src/common/framework/analysisState.ml +++ b/src/common/framework/analysisState.ml @@ -36,3 +36,8 @@ let postsolving = ref false (* None if verification is disabled, Some true if verification succeeded, Some false if verification failed *) let verified : bool option ref = ref None + +let unsound_both_branches_dead: bool option ref = ref None +(** [Some true] if unsound both branches dead occurs in analysis results. + [Some false] if it doesn't occur. + [None] if [ana.dead-code.branches] option is disabled and this isn't checked. *) \ No newline at end of file diff --git a/src/common/util/cilType.ml b/src/common/util/cilType.ml index a484a228bd..91368052b3 100644 --- a/src/common/util/cilType.ml +++ b/src/common/util/cilType.ml @@ -79,8 +79,8 @@ struct let of_yojson = function | `Assoc l -> - begin match List.assoc_opt "file" l, List.assoc_opt "line" l, List.assoc_opt "column" l, List.assoc_opt "byte" l with - | Some (`String file), Some (`Int line), Some (`Int column), Some (`Int byte) -> + begin match List.assoc_opt "file" l, List.assoc_opt "line" l, List.assoc_opt "column" l, Option.value ~default:(`Int (-1)) (List.assoc_opt "byte" l) with + | Some (`String file), Some (`Int line), Some (`Int column), `Int byte -> let loc = {file; line; column; byte; endLine = -1; endColumn = -1; endByte = -1; synthetic = false} in begin match List.assoc_opt "endLine" l, List.assoc_opt "endColumn" l, List.assoc_opt "endByte" l with | Some (`Int endLine), Some (`Int endColumn), Some (`Int endByte) -> @@ -698,6 +698,7 @@ struct | AAddrOf of t | AIndex of t * t | AQuestion of t * t * t + | AAssign of t * t [@@deriving eq, ord, hash] let name () = "attrparam" diff --git a/src/common/util/cilfacade.ml b/src/common/util/cilfacade.ml index 99430ee8b6..6e86701858 100644 --- a/src/common/util/cilfacade.ml +++ b/src/common/util/cilfacade.ml @@ -6,6 +6,14 @@ module E = Errormsg include Cilfacade0 +type Goblint_backtrace.mark += FunVarinfo of varinfo + +let () = Goblint_backtrace.register_mark_printer (function + | FunVarinfo var -> + Some ("function " ^ CilType.Varinfo.show var) + | _ -> None (* for other marks *) + ) + (** Command for assigning an id to a varinfo. All varinfos directly created by Goblint should be modified by this method *) let create_var (var: varinfo) = (* Hack: using negative integers should preempt conflicts with ids generated by CIL *) @@ -39,7 +47,20 @@ let init_options () = Mergecil.merge_inlines := get_bool "cil.merge.inlines"; Cil.cstd := Cil.cstd_of_string (get_string "cil.cstd"); Cil.gnu89inline := get_bool "cil.gnu89inline"; - Cabs2cil.addNestedScopeAttr := get_bool "cil.addNestedScopeAttr" + Cabs2cil.addNestedScopeAttr := get_bool "cil.addNestedScopeAttr"; + + if get_bool "ana.sv-comp.enabled" then ( + let machine = match get_string "exp.architecture" with + | "32bit" -> Machdep.gcc32 + | "64bit" -> Machdep.gcc64 + | _ -> assert false + in + match machine with + | Some _ -> Cil.envMachine := machine + | None -> + GobRef.wrap AnalysisState.should_warn true (fun () -> Messages.msg_final Error ~category:Unsound "Machine definition not available for selected architecture"); + Logs.error "Machine definition not available for selected architecture, defaulting to host" + ) let init () = initCIL (); @@ -453,8 +474,8 @@ let rec pretty_typsig_like_typ (nameOpt: Pretty.doc option) () ts = (* ignore the const attribute for arrays *) let a' = dropAttributes [ "pconst" ] a in let name' = - if a' == [] then name else - if nameOpt == None then printAttributes a' else + if a' = [] then name else + if nameOpt = None then printAttributes a' else text "(" ++ printAttributes a' ++ name ++ text ")" in pretty_typsig_like_typ @@ -467,8 +488,8 @@ let rec pretty_typsig_like_typ (nameOpt: Pretty.doc option) () ts = | TSFun (restyp, args, isvararg, a) -> let name' = - if a == [] then name else - if nameOpt == None then printAttributes a else + if a = [] then name else + if nameOpt = None then printAttributes a else text "(" ++ printAttributes a ++ name ++ text ")" in pretty_typsig_like_typ @@ -734,7 +755,7 @@ let add_function_declarations (file: Cil.file): unit = let functions, non_functions = List.partition (fun g -> match g with GFun _ -> true | _ -> false) globals in let upto_last_type, non_types = GobList.until_last_with (fun g -> match g with GType _ -> true | _ -> false) non_functions in let declaration_from_GFun f = match f with - | GFun (f, _) when BatString.starts_with_stdlib ~prefix:"__builtin" f.svar.vname -> + | GFun (f, _) when String.starts_with ~prefix:"__builtin" f.svar.vname -> (* Builtin functions should not occur in asserts generated, so there is no need to add declarations for them.*) None | GFun (f, _) -> diff --git a/src/common/util/gobFormat.ml b/src/common/util/gobFormat.ml index 3cda0a4758..8f26ff0087 100644 --- a/src/common/util/gobFormat.ml +++ b/src/common/util/gobFormat.ml @@ -19,3 +19,12 @@ let pp_set_ansi_color_tags ppf = Format.pp_set_mark_tags ppf true let pp_print_nothing (ppf: Format.formatter) () = () + +let pp_infinity = 1000000001 (* Exact value not exposed before OCaml 5.2, but use the smallest value permitted by documentation. *) + +let pp_set_infinite_geometry = Format.pp_set_geometry ~max_indent:(pp_infinity - 2) ~margin:(pp_infinity - 1) + +let asprintf (fmt: ('a, Format.formatter, unit, string) format4): 'a = + Format.asprintf ("%t" ^^ fmt) pp_set_infinite_geometry + +let asprint pp x = asprintf "%a" pp x (* eta-expanded to bypass value restriction *) diff --git a/src/common/util/richVarinfo.ml b/src/common/util/richVarinfo.ml index d1918c40a6..6a27339eed 100644 --- a/src/common/util/richVarinfo.ml +++ b/src/common/util/richVarinfo.ml @@ -1,9 +1,9 @@ open GoblintCil -let create_var name = Cilfacade.create_var @@ makeGlobalVar name voidType +let create_var name typ = Cilfacade.create_var @@ makeGlobalVar name typ -let single ~name = - let vi = lazy (create_var name) in +let single ~name ~typ = + let vi = lazy (create_var name typ) in fun () -> Lazy.force vi @@ -21,6 +21,7 @@ module type G = sig include Hashtbl.HashedType val name_varinfo: t -> string + val typ: t -> typ end module type H = @@ -47,7 +48,7 @@ struct try XH.find !xh x with Not_found -> - let vi = create_var (X.name_varinfo x) in + let vi = create_var (X.name_varinfo x) (X.typ x) in store_f x vi; vi diff --git a/src/common/util/richVarinfo.mli b/src/common/util/richVarinfo.mli index 4e682734ee..d1c002bf84 100644 --- a/src/common/util/richVarinfo.mli +++ b/src/common/util/richVarinfo.mli @@ -2,7 +2,7 @@ open GoblintCil -val single: name:string -> (unit -> varinfo) +val single: name:string -> typ:typ -> (unit -> varinfo) module type VarinfoMap = sig @@ -18,6 +18,7 @@ module type G = sig include Hashtbl.HashedType val name_varinfo: t -> string + val typ: t -> typ end module type H = diff --git a/src/config/options.schema.json b/src/config/options.schema.json index 99c887ca53..43e2ad1d59 100644 --- a/src/config/options.schema.json +++ b/src/config/options.schema.json @@ -79,9 +79,9 @@ "result": { "title": "result", "description": - "Result style: none, fast_xml, json, pretty, json-messages, sarif.", + "Result style: none, fast_xml, json, pretty, pretty-deterministic, json-messages, sarif.", "type": "string", - "enum": ["none", "fast_xml", "json", "pretty", "json-messages", "sarif"], + "enum": ["none", "fast_xml", "json", "pretty", "pretty-deterministic", "json-messages", "sarif"], "default": "none" }, "solver": { @@ -344,7 +344,7 @@ "default": [ "expRelation", "base", "threadid", "threadflag", "threadreturn", "escape", "mutexEvents", "mutex", "access", "race", "mallocWrapper", "mhp", - "assert" + "assert", "pthreadMutexType" ] }, "path_sens": { @@ -535,7 +535,6 @@ "enum": [ "congruence", "singleThreaded", - "specification", "mallocWrappers", "noRecursiveIntervals", "enums", @@ -545,6 +544,8 @@ "octagon", "wideningThresholds", "memsafetySpecification", + "concurrencySafetySpecification", + "noOverflows", "termination", "tmpSpecialAnalysis" ] @@ -552,7 +553,6 @@ "default": [ "congruence", "singleThreaded", - "specification", "mallocWrappers", "noRecursiveIntervals", "enums", @@ -561,6 +561,8 @@ "octagon", "wideningThresholds", "memsafetySpecification", + "concurrencySafetySpecification", + "noOverflows", "termination", "tmpSpecialAnalysis" ] @@ -795,6 +797,18 @@ "type": "boolean", "default": true }, + "local": { + "title": "ana.base.invariant.local", + "description": "Keep local variables in invariants", + "type": "boolean", + "default": true + }, + "global": { + "title": "ana.base.invariant.global", + "description": "Keep global variables in invariants", + "type": "boolean", + "default": true + }, "blobs": { "title": "ana.base.invariant.blobs", "description": "Whether to dump assertions about all blobs. Enabling this option may lead to unsound asserts.", @@ -807,6 +821,20 @@ "type": "string", "enum": ["once", "fixpoint"], "default": "once" + }, + "int": { + "title": "ana.base.invariant.int", + "type": "object", + "properties": { + "simplify": { + "title": "ana.base.invariant.int.simplify", + "description": "How much to simplify int domain invariants. Value \"int\" only simplifies definite integers. Without int domain refinement \"all\" might not be maximally precise.", + "type": "string", + "enum": ["none", "int", "all"], + "default": "all" + } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -980,10 +1008,18 @@ }, "gas_value": { "title": "ana.context.gas_value", - "description": "Denotes the gas value x for the ContextGasLifter. Negative values deactivate the context gas, zero yields a context-insensitve analysis. If enabled, the first x recursive calls of the call stack are analyzed context-sensitively. Any calls deeper in the call stack are analyzed with the same (empty) context.", + "description": "Denotes the gas value x for the ContextGasLifter. Negative values deactivate the context gas, zero yields a context-insensitive analysis. If enabled, the first x recursive calls of the call stack are analyzed context-sensitively. Any calls deeper in the call stack are analyzed with the same (empty) context.", "type": "integer", "default": -1 }, + "gas_scope": { + "title": "ana.context.gas_scope", + "description": + "Whether the gas should be 'global' (default) or per 'function'", + "type": "string", + "enum": ["global","function"], + "default": "global" + }, "callString_length": { "title": "ana.context.callString_length", "description": "Length of the call string that should be used as context for the call_string and/or call_site analyses. In case the value is zero, the analysis is context-insensitive. For a negative value, an infinite call string is used! For this option to have an effect, one of the analyses in `callstring.ml` must be activated.", @@ -1142,6 +1178,26 @@ } }, "additionalProperties": false + }, + "var_eq": { + "title": "ana.var_eq", + "type": "object", + "properties": { + "invariant": { + "title": "ana.var_eq.invariant", + "type": "object", + "properties": { + "enabled": { + "title": "ana.var_eq.invariant.enabled", + "description": "Generate var_eq analysis invariants", + "type": "boolean", + "default": true + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -1344,7 +1400,8 @@ "zstd", "pcre", "zlib", - "liblzma" + "liblzma", + "legacy" ] }, "default": [ @@ -1355,7 +1412,8 @@ "glibc", "linux-userspace", "goblint", - "ncurses" + "ncurses", + "legacy" ] } }, @@ -1520,6 +1578,19 @@ } }, "additionalProperties": false + }, + "atexit": { + "title": "sem.atexit", + "type": "object", + "properties": { + "ignore": { + "title": "sem.atexit.ignore", + "description": "Ignore atexit callbacks (unsound).", + "type": "boolean", + "default": false + } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -1559,6 +1630,26 @@ "description": "Output filename for transformations that output a transformed file.", "type":"string", "default": "transformed.c" + }, + "assert" : { + "title": "trans.assert", + "type": "object", + "properties": { + "function": { + "title": "trans.assert.function", + "description": "Function to use for assertions in output.", + "type": "string", + "enum": ["assert", "__goblint_check", "__VERIFIER_assert"], + "default": "__VERIFIER_assert" + }, + "wrap-atomic": { + "title": "trans.assert.wrap-atomic", + "description": "Wrap assertions in __VERIFIER_atomic_begin and __VERIFIER_atomic_end.", + "type": "boolean", + "default": true + } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -1817,15 +1908,38 @@ }, "arg": { "title": "exp.arg", - "description": "Construct abstract reachability graph (ARG).", - "type": "boolean", - "default": false - }, - "argdot": { - "title": "exp.argdot", - "description": "Output ARG as dot file.", - "type": "boolean", - "default": false + "description": "Abstract reachability graph (ARG) options", + "type": "object", + "properties": { + "enabled": { + "title": "exp.arg.enabled", + "description": "Construct ARG.", + "type": "boolean", + "default": false + }, + "dot": { + "title": "exp.arg.dot", + "description": "ARG output as .dot file", + "type": "object", + "properties": { + "path": { + "title": "exp.arg.dot.path", + "description": "ARG .dot file output path. Disabled if empty.", + "type": "string", + "default": "" + }, + "node-label": { + "title": "exp.arg.dot.node-label", + "description": "Which ARG node labels to use? node/empty", + "type": "string", + "enum": ["node", "empty"], + "default": "node" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -2512,6 +2626,13 @@ "description": "Emit invariants with typedef-ed types (e.g. in casts). Our validator cannot parse these.", "type": "boolean", "default": true + }, + "flow_insensitive-as": { + "title": "witness.invariant.flow_insensitive-as", + "description": "Emit flow-insensitive invariants as location invariants at certain locations.", + "type": "string", + "enum": ["flow_insensitive_invariant", "location_invariant", "invariant_set-location_invariant"], + "default": "flow_insensitive_invariant" } }, "additionalProperties": false @@ -2532,7 +2653,8 @@ "type": "string", "enum": [ "0.1", - "2.0" + "2.0", + "2.1" ], "default": "0.1" }, @@ -2549,7 +2671,9 @@ "precondition_loop_invariant", "loop_invariant_certificate", "precondition_loop_invariant_certificate", - "invariant_set" + "invariant_set", + "violation_sequence", + "ghost_instrumentation" ] }, "default": [ @@ -2591,7 +2715,7 @@ }, "strict": { "title": "witness.yaml.strict", - "description": "", + "description": "Fail YAML witness validation if there's an error/unsupported/disabled entry.", "type": "boolean", "default": false }, diff --git a/src/domain/boolDomain.ml b/src/domain/boolDomain.ml index d92d716d7a..77fcf7e108 100644 --- a/src/domain/boolDomain.ml +++ b/src/domain/boolDomain.ml @@ -1,31 +1,65 @@ (** Boolean domains. *) -module Bool = +module type Names = +sig + val name: string + val true_name: string + val false_name: string +end + +module MakeBool (Names: Names) = struct - include Basetype.RawBools - (* type t = bool - let equal = Bool.equal - let compare = Bool.compare - let relift x = x - let arbitrary () = QCheck.bool *) + include Printable.StdLeaf + type t = bool [@@deriving eq, ord, hash] + let name () = Names.name + + let show x = if x then Names.true_name else Names.false_name + include Printable.SimpleShow (struct + type nonrec t = t + let show = show + end) + + let arbitrary () = QCheck.bool + (* For Lattice.S *) let pretty_diff () (x,y) = GoblintCil.Pretty.dprintf "%s: %a not leq %a" (name ()) pretty x pretty y end -module MayBool: Lattice.S with type t = bool = +module StdNames: Names = struct - include Bool + let name = "bool" + let true_name = "true" + let false_name = "false" +end + +module Bool = +struct + include MakeBool (StdNames) + let to_yojson = [%to_yojson: bool] (* override to_yojson from SimpleShow *) +end + +module MakeMayBool (Names: Names): Lattice.S with type t = bool = +struct + include MakeBool (Names) let bot () = false let is_bot x = x = false let top () = true let is_top x = x = true - let leq x y = x == y || y + let leq x y = (x = y) || y let join = (||) let widen = (||) let meet = (&&) let narrow = (&&) end +module MayBool: Lattice.S with type t = bool = +struct + include MakeMayBool (StdNames) + let to_yojson = [%to_yojson: bool] (* override to_yojson from SimpleShow *) +end + +(* TODO: MakeMustBool? *) + module MustBool: Lattice.S with type t = bool = struct include Bool @@ -33,7 +67,7 @@ struct let is_bot x = x = true let top () = false let is_top x = x = false - let leq x y = x == y || x + let leq x y = (x = y) || x let join = (&&) let widen = (&&) let meet = (||) diff --git a/src/domain/dune b/src/domain/dune index 85e69a6246..ce4cdcb1a5 100644 --- a/src/domain/dune +++ b/src/domain/dune @@ -14,7 +14,8 @@ (pps ppx_deriving.std ppx_deriving_hash - ppx_deriving_yojson)) + ppx_deriving_yojson + ppx_deriving_lattice)) (instrumentation (backend bisect_ppx))) (documentation) diff --git a/src/domain/lattice.ml b/src/domain/lattice.ml index 99322c09d8..37a4a2fef5 100644 --- a/src/domain/lattice.ml +++ b/src/domain/lattice.ml @@ -153,11 +153,11 @@ struct include Printable.HConsed (Base) let lift_f2 f x y = f (unlift x) (unlift y) - let narrow x y = if Arg.assume_idempotent && x.BatHashcons.tag == y.BatHashcons.tag then x else lift (lift_f2 Base.narrow x y) - let widen x y = if x.BatHashcons.tag == y.BatHashcons.tag then x else lift (lift_f2 Base.widen x y) - let meet x y = if Arg.assume_idempotent && x.BatHashcons.tag == y.BatHashcons.tag then x else lift (lift_f2 Base.meet x y) - let join x y = if x.BatHashcons.tag == y.BatHashcons.tag then x else lift (lift_f2 Base.join x y) - let leq x y = (x.BatHashcons.tag == y.BatHashcons.tag) || lift_f2 Base.leq x y + let narrow x y = if Arg.assume_idempotent && x.BatHashcons.tag = y.BatHashcons.tag then x else lift (lift_f2 Base.narrow x y) + let widen x y = if x.BatHashcons.tag = y.BatHashcons.tag then x else lift (lift_f2 Base.widen x y) + let meet x y = if Arg.assume_idempotent && x.BatHashcons.tag = y.BatHashcons.tag then x else lift (lift_f2 Base.meet x y) + let join x y = if x.BatHashcons.tag = y.BatHashcons.tag then x else lift (lift_f2 Base.join x y) + let leq x y = (x.BatHashcons.tag = y.BatHashcons.tag) || lift_f2 Base.leq x y let is_top = lift_f Base.is_top let is_bot = lift_f Base.is_bot let top () = lift (Base.top ()) @@ -277,6 +277,8 @@ struct let narrow x y = match (x,y) with | (`Lifted x, `Lifted y) -> `Lifted (Base.narrow x y) + | (_, `Bot) -> `Bot + | (`Top, y) -> y | _ -> x end @@ -337,6 +339,8 @@ struct | (`Lifted x, `Lifted y) -> (try `Lifted (Base.narrow x y) with Uncomparable -> `Bot) + | (_, `Bot) -> `Bot + | (`Top, y) -> y | _ -> x end @@ -408,6 +412,8 @@ struct match (x,y) with | (`Lifted1 x, `Lifted1 y) -> `Lifted1 (Base1.narrow x y) | (`Lifted2 x, `Lifted2 y) -> `Lifted2 (Base2.narrow x y) + | (_, `Bot) -> `Bot + | (`Top, y) -> y | _ -> x end @@ -416,27 +422,17 @@ module Lift2 = Lift2Conf (Printable.DefaultConf) module ProdConf (C: Printable.ProdConfiguration) (Base1: S) (Base2: S) = struct - include Printable.ProdConf (C) (Base1) (Base2) - - let bot () = (Base1.bot (), Base2.bot ()) - let is_bot (x1,x2) = Base1.is_bot x1 && Base2.is_bot x2 - let top () = (Base1.top (), Base2.top ()) - let is_top (x1,x2) = Base1.is_top x1 && Base2.is_top x2 - - let leq (x1,x2) (y1,y2) = Base1.leq x1 y1 && Base2.leq x2 y2 + open struct (* open to avoid leaking P and causing conflicts *) + module P = Printable.ProdConf (C) (Base1) (Base2) + end + type t = Base1.t * Base2.t [@@deriving lattice] + include (P: module type of P with type t := t) let pretty_diff () ((x1,x2:t),(y1,y2:t)): Pretty.doc = if Base1.leq x1 y1 then Base2.pretty_diff () (x2,y2) else Base1.pretty_diff () (x1,y1) - - let op_scheme op1 op2 (x1,x2) (y1,y2): t = (op1 x1 y1, op2 x2 y2) - let join = op_scheme Base1.join Base2.join - let meet = op_scheme Base1.meet Base2.meet - let narrow = op_scheme Base1.narrow Base2.narrow - let widen = op_scheme Base1.widen Base2.widen - end @@ -445,14 +441,11 @@ module ProdSimple = ProdConf (struct let expand_fst = false let expand_snd = fal module Prod3 (Base1: S) (Base2: S) (Base3: S) = struct - include Printable.Prod3 (Base1) (Base2) (Base3) - - let bot () = (Base1.bot (), Base2.bot (), Base3.bot ()) - let is_bot (x1,x2,x3) = Base1.is_bot x1 && Base2.is_bot x2 && Base3.is_bot x3 - let top () = (Base1.top (), Base2.top (), Base3.top ()) - let is_top (x1,x2,x3) = Base1.is_top x1 && Base2.is_top x2 && Base3.is_top x3 - - let leq (x1,x2,x3) (y1,y2,y3) = Base1.leq x1 y1 && Base2.leq x2 y2 && Base3.leq x3 y3 + open struct (* open to avoid leaking P and causing conflicts *) + module P = Printable.Prod3 (Base1) (Base2) (Base3) + end + type t = Base1.t * Base2.t * Base3.t [@@deriving lattice] + include (P: module type of P with type t := t) let pretty_diff () ((x1,x2,x3:t),(y1,y2,y3:t)): Pretty.doc = if not (Base1.leq x1 y1) then @@ -461,39 +454,6 @@ struct Base2.pretty_diff () (x2,y2) else Base3.pretty_diff () (x3,y3) - - let op_scheme op1 op2 op3 (x1,x2,x3) (y1,y2,y3): t = (op1 x1 y1, op2 x2 y2, op3 x3 y3) - let join = op_scheme Base1.join Base2.join Base3.join - let meet = op_scheme Base1.meet Base2.meet Base3.meet - let widen = op_scheme Base1.widen Base2.widen Base3.widen - let narrow = op_scheme Base1.narrow Base2.narrow Base3.narrow -end - -module Prod4 (Base1: S) (Base2: S) (Base3: S) (Base4: S) = -struct - include Printable.Prod4 (Base1) (Base2) (Base3) (Base4) - - let bot () = (Base1.bot (), Base2.bot (), Base3.bot (), Base4.bot ()) - let is_bot (x1,x2,x3,x4) = Base1.is_bot x1 && Base2.is_bot x2 && Base3.is_bot x3 && Base4.is_bot x4 - let top () = (Base1.top (), Base2.top (), Base3.top (), Base4.top ()) - let is_top (x1,x2,x3,x4) = Base1.is_top x1 && Base2.is_top x2 && Base3.is_top x3 && Base4.is_top x4 - let leq (x1,x2,x3,x4) (y1,y2,y3,y4) = Base1.leq x1 y1 && Base2.leq x2 y2 && Base3.leq x3 y3 && Base4.leq x4 y4 - - let pretty_diff () ((x1,x2,x3,x4:t),(y1,y2,y3,y4:t)): Pretty.doc = - if not (Base1.leq x1 y1) then - Base1.pretty_diff () (x1,y1) - else if not (Base2.leq x2 y2) then - Base2.pretty_diff () (x2,y2) - else if not (Base3.leq x3 y3) then - Base3.pretty_diff () (x3,y3) - else - Base4.pretty_diff () (x4,y4) - - let op_scheme op1 op2 op3 op4 (x1,x2,x3,x4) (y1,y2,y3,y4): t = (op1 x1 y1, op2 x2 y2, op3 x3 y3, op4 x4 y4) - let join = op_scheme Base1.join Base2.join Base3.join Base4.join - let meet = op_scheme Base1.meet Base2.meet Base3.meet Base4.meet - let widen = op_scheme Base1.widen Base2.widen Base3.widen Base4.widen - let narrow = op_scheme Base1.narrow Base2.narrow Base3.narrow Base4.narrow end module LiftBot (Base : S) = @@ -539,6 +499,7 @@ struct let narrow x y = match (x,y) with | (`Lifted x, `Lifted y) -> `Lifted (Base.narrow x y) + | (_, `Bot) -> `Bot | _ -> x end @@ -580,6 +541,7 @@ struct let narrow x y = match (x,y) with | (`Lifted x, `Lifted y) -> `Lifted (Base.narrow x y) + | (`Top, y) -> y | _ -> x let pretty_diff () (x,y) = diff --git a/src/domain/partitionDomain.ml b/src/domain/partitionDomain.ml index 9675e9bfce..e97946e463 100644 --- a/src/domain/partitionDomain.ml +++ b/src/domain/partitionDomain.ml @@ -31,10 +31,10 @@ struct let meet _ _ = failwith "PartitonDomain.Set.meet: unsound" let collapse (s1:t) (s2:t): bool = - let f vf2 res = - res || exists (fun vf1 -> S.collapse vf1 vf2) s1 + let f vf2 = + exists (fun vf1 -> S.collapse vf1 vf2) s1 in - fold f s2 false + exists f s2 let add e s = join s (singleton e) @@ -106,6 +106,9 @@ struct let show _ = "Partitions" + (* Top and bottom are reversed: + Bottom will be All (equations), i.e. contradiction, + Top will be empty set, i.e. no equations. *) let top = E.bot let bot = E.top let is_top = E.is_bot diff --git a/src/domain/setDomain.ml b/src/domain/setDomain.ml index 1b5239de80..c552363f3d 100644 --- a/src/domain/setDomain.ml +++ b/src/domain/setDomain.ml @@ -184,7 +184,7 @@ struct end ) - let hash x = fold (fun x y -> y + Base.hash x) x 0 + let hash x = fold (fun x y -> 13 * y + Base.hash x) x 0 let relift x = map Base.relift x @@ -436,23 +436,23 @@ struct include Lattice.Reverse (Base) end -module type FiniteSetElems = +module type FiniteSetElem = sig - type t + include Printable.S val elems: t list + (** List of all possible elements. *) end -(* TODO: put elems into E *) -module FiniteSet (E:Printable.S) (Elems:FiniteSetElems with type t = E.t) = +module FiniteSet (E: FiniteSetElem) = struct module E = struct include E - let arbitrary () = QCheck.oneofl Elems.elems + let arbitrary () = QCheck.oneofl E.elems end include Make (E) - let top () = of_list Elems.elems + let top () = of_list E.elems let is_top x = equal x (top ()) end diff --git a/src/domains/access.ml b/src/domains/access.ml index c35fb3a16d..f7ce68a18b 100644 --- a/src/domains/access.ml +++ b/src/domains/access.ml @@ -1,6 +1,5 @@ (** Memory accesses and their manipulation. *) -open Batteries open GoblintCil open Pretty open GobConfig @@ -12,7 +11,7 @@ module M = Messages let is_ignorable_comp_name = function | "__pthread_mutex_s" | "__pthread_rwlock_arch_t" | "__jmp_buf_tag" | "_pthread_cleanup_buffer" | "__pthread_cleanup_frame" | "__cancel_jmp_buf_tag" | "_IO_FILE" -> true - | cname when String.starts_with_stdlib ~prefix:"__anon" cname -> + | cname when String.starts_with ~prefix:"__anon" cname -> begin match Cilfacade.split_anoncomp_name cname with | (true, Some ("__once_flag" | "__pthread_unwind_buf_t" | "__cancel_jmp_buf"), _) -> true (* anonstruct *) | (false, Some ("pthread_mutexattr_t" | "pthread_condattr_t" | "pthread_barrierattr_t"), _) -> true (* anonunion *) @@ -385,7 +384,7 @@ and distribute_access_exp f = function and distribute_access_type f = function | TArray (et, len, _) -> - Option.may (distribute_access_exp f) len; + Option.iter (distribute_access_exp f) len; distribute_access_type f et | TVoid _ @@ -434,7 +433,7 @@ struct include SetDomain.Make (A) let max_conf accs = - accs |> elements |> List.map (fun {A.conf; _} -> conf) |> (List.max ~cmp:Int.compare) + accs |> elements |> List.map (fun {A.conf; _} -> conf) |> (BatList.max ~cmp:Int.compare) end @@ -583,7 +582,7 @@ let incr_summary ~safe ~vulnerable ~unsafe grouped_accs = |> List.filter_map race_conf |> (function | [] -> None - | confs -> Some (List.max confs) + | confs -> Some (BatList.max confs) ) in match safety with diff --git a/src/domains/events.ml b/src/domains/events.ml index 06561bddbe..cf12900c98 100644 --- a/src/domains/events.ml +++ b/src/domains/events.ml @@ -4,7 +4,7 @@ open GoblintCil open Pretty type t = - | Lock of LockDomain.Lockset.Lock.t + | Lock of LockDomain.AddrRW.t | Unlock of LockDomain.Addr.t | Escape of EscapeDomain.EscapedVars.t | EnterMultiThreaded @@ -14,7 +14,7 @@ type t = | Assign of {lval: CilType.Lval.t; exp: CilType.Exp.t} (** Used to simulate old [ctx.assign]. *) (* TODO: unused *) | UpdateExpSplit of exp (** Used by expsplit analysis to evaluate [exp] on post-state. *) | Assert of exp - | Unassume of {exp: CilType.Exp.t; uuids: string list} + | Unassume of {exp: CilType.Exp.t; tokens: WideningToken.t list} | Longjmped of {lval: CilType.Lval.t option} (** Should event be emitted after transfer function raises [Deadcode]? *) @@ -35,7 +35,7 @@ let emit_on_deadcode = function false let pretty () = function - | Lock m -> dprintf "Lock %a" LockDomain.Lockset.Lock.pretty m + | Lock m -> dprintf "Lock %a" LockDomain.AddrRW.pretty m | Unlock m -> dprintf "Unlock %a" LockDomain.Addr.pretty m | Escape escaped -> dprintf "Escape %a" EscapeDomain.EscapedVars.pretty escaped | EnterMultiThreaded -> text "EnterMultiThreaded" @@ -45,5 +45,5 @@ let pretty () = function | Assign {lval; exp} -> dprintf "Assign {lval=%a, exp=%a}" CilType.Lval.pretty lval CilType.Exp.pretty exp | UpdateExpSplit exp -> dprintf "UpdateExpSplit %a" d_exp exp | Assert exp -> dprintf "Assert %a" d_exp exp - | Unassume {exp; uuids} -> dprintf "Unassume {exp=%a; uuids=%a}" d_exp exp (docList Pretty.text) uuids + | Unassume {exp; tokens} -> dprintf "Unassume {exp=%a; tokens=%a}" d_exp exp (d_list ", " WideningToken.pretty) tokens | Longjmped {lval} -> dprintf "Longjmped {lval=%a}" (docOpt (CilType.Lval.pretty ())) lval diff --git a/src/domains/queries.ml b/src/domains/queries.ml index f5d7be21a4..fee44a6b24 100644 --- a/src/domains/queries.ml +++ b/src/domains/queries.ml @@ -10,6 +10,7 @@ module LS = VDQ.LS module TS = SetDomain.ToppedSet (CilType.Typ) (struct let topname = "All" end) module ES = SetDomain.Reverse (SetDomain.ToppedSet (CilType.Exp) (struct let topname = "All" end)) module VS = SetDomain.ToppedSet (CilType.Varinfo) (struct let topname = "All" end) +module NS = SetDomain.ToppedSet (Node) (struct let topname = "All" end) module NFL = WrapperFunctionAnalysis0.NodeFlatLattice module TC = WrapperFunctionAnalysis0.ThreadCreateUniqueCount @@ -49,9 +50,9 @@ end (* Helper definitions for deriving complex parts of Any.compare below. *) type maybepublic = {global: CilType.Varinfo.t; write: bool; protection: Protection.t} [@@deriving ord, hash] -type maybepublicwithout = {global: CilType.Varinfo.t; write: bool; without_mutex: PreValueDomain.Addr.t; protection: Protection.t} [@@deriving ord, hash] -type mustbeprotectedby = {mutex: PreValueDomain.Addr.t; global: CilType.Varinfo.t; write: bool; protection: Protection.t} [@@deriving ord, hash] -type mustprotectedvars = {mutex: PreValueDomain.Addr.t; write: bool} [@@deriving ord, hash] +type maybepublicwithout = {global: CilType.Varinfo.t; write: bool; without_mutex: LockDomain.MustLock.t; protection: Protection.t} [@@deriving ord, hash] +type mustbeprotectedby = {mutex: LockDomain.MustLock.t; global: CilType.Varinfo.t; write: bool; protection: Protection.t} [@@deriving ord, hash] +type mustprotectedvars = {mutex: LockDomain.MustLock.t; write: bool} [@@deriving ord, hash] type access = | Memory of {exp: CilType.Exp.t; var_opt: CilType.Varinfo.t option; kind: AccessKind.t} (** Memory location access (race). *) | Point (** Program point and state access (MHP), independent of memory location. *) @@ -62,6 +63,8 @@ type invariant_context = Invariant.context = { } [@@deriving ord, hash] +module YS = SetDomain.ToppedSet (YamlWitnessType.Entry) (struct let topname = "Top" end) + (** GADT for queries with specific result type. *) type _ t = @@ -74,7 +77,7 @@ type _ t = | MayBePublic: maybepublic -> MayBool.t t (* old behavior with write=false *) | MayBePublicWithout: maybepublicwithout -> MayBool.t t | MustBeProtectedBy: mustbeprotectedby -> MustBool.t t - | MustLockset: AD.t t + | MustLockset: LockDomain.MustLockset.t t | MustBeAtomic: MustBool.t t | MustBeSingleThreaded: {since_start: bool} -> MustBool.t t | MustBeUniqueThread: MustBool.t t @@ -114,6 +117,7 @@ type _ t = | MustJoinedThreads: ConcDomain.MustThreadSet.t t | ThreadsJoinedCleanly: MustBool.t t | MustProtectedVars: mustprotectedvars -> VS.t t + | MustProtectingLocks: CilType.Varinfo.t -> LockDomain.MustLockset.t t | Invariant: invariant_context -> Invariant.t t | InvariantGlobal: Obj.t -> Invariant.t t (** Argument must be of corresponding [Spec.V.t]. *) | WarnGlobal: Obj.t -> Unit.t t (** Argument must be of corresponding [Spec.V.t]. *) @@ -124,8 +128,12 @@ type _ t = | MustTermLoop: stmt -> MustBool.t t | MustTermAllLoops: MustBool.t t | IsEverMultiThreaded: MayBool.t t - | TmpSpecial: Mval.Exp.t -> ML.t t + | TmpSpecial: Mval.Exp.t -> ML.t t | MaySignedOverflow: exp -> MayBool.t t + | GasExhausted: CilType.Fundec.t -> MustBool.t t + | YamlEntryGlobal: Obj.t * YamlWitnessType.Task.t -> YS.t t (** YAML witness entries for a global unknown ([Obj.t] represents [Spec.V.t]) and YAML witness task. *) + | GhostVarAvailable: WitnessGhostVar.t -> MayBool.t t + | InvariantGlobalNodes: NS.t t (** Nodes where YAML witness flow-insensitive invariants should be emitted as location invariants (if [witness.invariant.flow_insensitive-as] is configured to do so). *) (* [Spec.V.t] argument (as [Obj.t]) could be added, if this should be different for different flow-insensitive invariants. *) type 'a result = 'a @@ -147,7 +155,7 @@ struct | MayPointTo _ -> (module AD) | ReachableFrom _ -> (module AD) | Regions _ -> (module LS) - | MustLockset -> (module AD) + | MustLockset -> (module LockDomain.MustLockset) | EvalFunvar _ -> (module AD) | ReachableUkTypes _ -> (module TS) | MayEscape _ -> (module MayBool) @@ -184,6 +192,7 @@ struct | MustJoinedThreads -> (module ConcDomain.MustThreadSet) | ThreadsJoinedCleanly -> (module MustBool) | MustProtectedVars _ -> (module VS) + | MustProtectingLocks _ -> (module LockDomain.MustLockset) | Invariant _ -> (module Invariant) | InvariantGlobal _ -> (module Invariant) | WarnGlobal _ -> (module Unit) @@ -196,6 +205,10 @@ struct | IsEverMultiThreaded -> (module MayBool) | TmpSpecial _ -> (module ML) | MaySignedOverflow _ -> (module MayBool) + | GasExhausted _ -> (module MustBool) + | YamlEntryGlobal _ -> (module YS) + | GhostVarAvailable _ -> (module MayBool) + | InvariantGlobalNodes -> (module NS) (** Get bottom result for query. *) let bot (type a) (q: a t): a result = @@ -216,7 +229,7 @@ struct | MayPointTo _ -> AD.top () | ReachableFrom _ -> AD.top () | Regions _ -> LS.top () - | MustLockset -> AD.top () + | MustLockset -> LockDomain.MustLockset.top () | EvalFunvar _ -> AD.top () | ReachableUkTypes _ -> TS.top () | MayEscape _ -> MayBool.top () @@ -253,6 +266,7 @@ struct | MustJoinedThreads -> ConcDomain.MustThreadSet.top () | ThreadsJoinedCleanly -> MustBool.top () | MustProtectedVars _ -> VS.top () + | MustProtectingLocks _ -> LockDomain.MustLockset.top () | Invariant _ -> Invariant.top () | InvariantGlobal _ -> Invariant.top () | WarnGlobal _ -> Unit.top () @@ -265,6 +279,10 @@ struct | IsEverMultiThreaded -> MayBool.top () | TmpSpecial _ -> ML.top () | MaySignedOverflow _ -> MayBool.top () + | GasExhausted _ -> MustBool.top () + | YamlEntryGlobal _ -> YS.top () + | GhostVarAvailable _ -> MayBool.top () + | InvariantGlobalNodes -> NS.top () end (* The type any_query can't be directly defined in Any as t, @@ -331,6 +349,11 @@ struct | Any (TmpSpecial _) -> 56 | Any (IsAllocVar _) -> 57 | Any (MaySignedOverflow _) -> 58 + | Any (GasExhausted _) -> 59 + | Any (YamlEntryGlobal _) -> 60 + | Any (MustProtectingLocks _) -> 61 + | Any (GhostVarAvailable _) -> 62 + | Any InvariantGlobalNodes -> 63 let rec compare a b = let r = Stdlib.compare (order a) (order b) in @@ -378,13 +401,17 @@ struct | Any (WarnGlobal vi1), Any (WarnGlobal vi2) -> Stdlib.compare (Hashtbl.hash vi1) (Hashtbl.hash vi2) | Any (Invariant i1), Any (Invariant i2) -> compare_invariant_context i1 i2 | Any (InvariantGlobal vi1), Any (InvariantGlobal vi2) -> Stdlib.compare (Hashtbl.hash vi1) (Hashtbl.hash vi2) + | Any (YamlEntryGlobal (vi1, task1)), Any (YamlEntryGlobal (vi2, task2)) -> Stdlib.compare (Hashtbl.hash vi1) (Hashtbl.hash vi2) (* TODO: compare task *) | Any (IterSysVars (vq1, vf1)), Any (IterSysVars (vq2, vf2)) -> VarQuery.compare vq1 vq2 (* not comparing fs *) | Any (MutexType m1), Any (MutexType m2) -> Mval.Unit.compare m1 m2 | Any (MustProtectedVars m1), Any (MustProtectedVars m2) -> compare_mustprotectedvars m1 m2 + | Any (MustProtectingLocks g1), Any (MustProtectingLocks g2) -> CilType.Varinfo.compare g1 g2 | Any (MayBeModifiedSinceSetjmp e1), Any (MayBeModifiedSinceSetjmp e2) -> JmpBufDomain.BufferEntry.compare e1 e2 | Any (MustBeSingleThreaded {since_start=s1;}), Any (MustBeSingleThreaded {since_start=s2;}) -> Stdlib.compare s1 s2 | Any (TmpSpecial lv1), Any (TmpSpecial lv2) -> Mval.Exp.compare lv1 lv2 | Any (MaySignedOverflow e1), Any (MaySignedOverflow e2) -> CilType.Exp.compare e1 e2 + | Any (GasExhausted f1), Any (GasExhausted f2) -> CilType.Fundec.compare f1 f2 + | Any (GhostVarAvailable v1), Any (GhostVarAvailable v2) -> WitnessGhostVar.compare v1 v2 (* only argumentless queries should remain *) | _, _ -> Stdlib.compare (order a) (order b) @@ -422,11 +449,15 @@ struct | Any (Invariant i) -> hash_invariant_context i | Any (MutexType m) -> Mval.Unit.hash m | Any (InvariantGlobal vi) -> Hashtbl.hash vi + | Any (YamlEntryGlobal (vi, task)) -> Hashtbl.hash vi (* TODO: hash task *) | Any (MustProtectedVars m) -> hash_mustprotectedvars m + | Any (MustProtectingLocks g) -> CilType.Varinfo.hash g | Any (MayBeModifiedSinceSetjmp e) -> JmpBufDomain.BufferEntry.hash e | Any (MustBeSingleThreaded {since_start}) -> Hashtbl.hash since_start | Any (TmpSpecial lv) -> Mval.Exp.hash lv | Any (MaySignedOverflow e) -> CilType.Exp.hash e + | Any (GasExhausted f) -> CilType.Fundec.hash f + | Any (GhostVarAvailable v) -> WitnessGhostVar.hash v (* IterSysVars: *) (* - argument is a function and functions cannot be compared in any meaningful way. *) (* - doesn't matter because IterSysVars is always queried from outside of the analysis, so MCP's query caching is not done for it. *) @@ -475,10 +506,12 @@ struct | Any MustJoinedThreads -> Pretty.dprintf "MustJoinedThreads" | Any ThreadsJoinedCleanly -> Pretty.dprintf "ThreadsJoinedCleanly" | Any (MustProtectedVars m) -> Pretty.dprintf "MustProtectedVars _" + | Any (MustProtectingLocks g) -> Pretty.dprintf "MustProtectingLocks _" | Any (Invariant i) -> Pretty.dprintf "Invariant _" | Any (WarnGlobal vi) -> Pretty.dprintf "WarnGlobal _" | Any (IterSysVars _) -> Pretty.dprintf "IterSysVars _" | Any (InvariantGlobal i) -> Pretty.dprintf "InvariantGlobal _" + | Any (YamlEntryGlobal (i, task)) -> Pretty.dprintf "YamlEntryGlobal _" | Any (MutexType (v,o)) -> Pretty.dprintf "MutexType _" | Any (EvalMutexAttr a) -> Pretty.dprintf "EvalMutexAttr _" | Any MayAccessed -> Pretty.dprintf "MayAccessed" @@ -490,6 +523,9 @@ struct | Any IsEverMultiThreaded -> Pretty.dprintf "IsEverMultiThreaded" | Any (TmpSpecial lv) -> Pretty.dprintf "TmpSpecial %a" Mval.Exp.pretty lv | Any (MaySignedOverflow e) -> Pretty.dprintf "MaySignedOverflow %a" CilType.Exp.pretty e + | Any (GasExhausted f) -> Pretty.dprintf "GasExhausted %a" CilType.Fundec.pretty f + | Any (GhostVarAvailable v) -> Pretty.dprintf "GhostVarAvailable %a" WitnessGhostVar.pretty v + | Any InvariantGlobalNodes -> Pretty.dprintf "InvariantGlobalNodes" end let to_value_domain_ask (ask: ask) = diff --git a/src/dune b/src/dune index 5265821b5a..10996e7ee8 100644 --- a/src/dune +++ b/src/dune @@ -71,7 +71,7 @@ (flags :standard -open Goblint_std -open Goblint_logs) (ocamlopt_flags :standard -no-float-const-prop) (preprocess - (pps ppx_deriving.std ppx_deriving_hash ppx_deriving_yojson ppx_blob)) + (pps ppx_deriving.std ppx_deriving_hash ppx_deriving_yojson ppx_blob ppx_deriving_printable ppx_deriving_lattice)) (instrumentation (backend bisect_ppx)) ) @@ -88,7 +88,7 @@ (public_names goblint) (modes byte native) ; https://dune.readthedocs.io/en/stable/dune-files.html#linking-modes (modules goblint) - (libraries goblint.lib goblint.sites.dune goblint.build-info.dune goblint_std) + (libraries goblint.lib goblint_std) (preprocess (pps ppx_deriving.std ppx_deriving_hash ppx_deriving_yojson)) (flags :standard -linkall -open Goblint_std) ) @@ -96,7 +96,7 @@ (executable (name privPrecCompare) (modules privPrecCompare) - (libraries goblint.lib goblint.sites.dune goblint.build-info.dune goblint_std) + (libraries goblint.lib goblint_std) (preprocess (pps ppx_deriving.std ppx_deriving_hash ppx_deriving_yojson)) (flags :standard -linkall -open Goblint_std) ) @@ -104,7 +104,7 @@ (executable (name apronPrecCompare) (modules apronPrecCompare) - (libraries goblint.lib goblint.sites.dune goblint.build-info.dune goblint_std) + (libraries goblint.lib goblint_std) (preprocess (pps ppx_deriving.std ppx_deriving_hash ppx_deriving_yojson)) (flags :standard -linkall -open Goblint_std) ) @@ -112,7 +112,7 @@ (executable (name messagesCompare) (modules messagesCompare) - (libraries goblint.lib goblint.sites.dune goblint.build-info.dune goblint_std) + (libraries goblint.lib goblint_std) (preprocess (pps ppx_deriving.std ppx_deriving_hash ppx_deriving_yojson)) (flags :standard -linkall -open Goblint_std) ) diff --git a/src/framework/analyses.ml b/src/framework/analyses.ml index c8464f6e84..ab41335944 100644 --- a/src/framework/analyses.ml +++ b/src/framework/analyses.ml @@ -14,8 +14,7 @@ type fundecs = fundec list * fundec list * fundec list module Var = struct - type t = Node.t [@@deriving eq, ord, hash] - let relift = Node.relift + type t = Node.t [@@deriving eq, ord, hash, relift] let printXml f n = let l = Node.location n in @@ -206,9 +205,10 @@ sig val morphstate : varinfo -> D.t -> D.t val exitstate : varinfo -> D.t - val context : fundec -> D.t -> C.t + val context: (D.t, G.t, C.t, V.t) ctx -> fundec -> D.t -> C.t + val startcontext: unit -> C.t - val sync : (D.t, G.t, C.t, V.t) ctx -> [`Normal | `Join | `Return] -> D.t + val sync : (D.t, G.t, C.t, V.t) ctx -> [`Normal | `Join | `JoinCall of CilType.Fundec.t | `Return] -> D.t val query : (D.t, G.t, C.t, V.t) ctx -> 'a Queries.t -> 'a Queries.result (** A transfer function which handles the assignment of a rval to a lval, i.e., @@ -274,6 +274,8 @@ sig val event : (D.t, G.t, C.t, V.t) ctx -> Events.t -> (D.t, G.t, C.t, V.t) ctx -> D.t end +module type Spec2Spec = functor (S: Spec) -> Spec + module type MCPA = sig include Printable.S @@ -375,7 +377,7 @@ struct let sync ctx _ = ctx.local (* Most domains do not have a global part. *) - let context fd x = x + let context ctx fd x = x (* Everything is context sensitive --- override in MCP and maybe elsewhere*) let paths_as_set ctx = [ctx.local] @@ -420,7 +422,13 @@ module IdentityUnitContextsSpec = struct include IdentitySpec module C = Printable.Unit - let context _ _ = () + let context ctx _ _ = () + let startcontext () = () +end + +module ValueContexts (D:Lattice.S) = struct + module C = D + let startcontext () = D.bot () end module type SpecSys = diff --git a/src/framework/analysisResult.ml b/src/framework/analysisResult.ml index 41c2bbd2c7..c1d7fde8a5 100644 --- a/src/framework/analysisResult.ml +++ b/src/framework/analysisResult.ml @@ -45,6 +45,18 @@ struct let defline () = dprintf "OTHERS -> Not available\n" in dprintf "@[Mapping {\n @[%t%t@]}@]" content defline + let pretty_deterministic () mapping = + let bindings = + to_list mapping + |> List.sort [%ord: ResultNode.t * Range.t] + in + let f dok (key, st) = + dok ++ dprintf "%a ->@? @[%a@]\n" ResultNode.pretty key Range.pretty st + in + let content () = List.fold_left f nil bindings in + let defline () = dprintf "OTHERS -> Not available\n" in + dprintf "@[Mapping {\n @[%t%t@]}@]" content defline + include C let printXml f xs = @@ -91,6 +103,7 @@ struct let out = Messages.get_out result_name !Messages.out in match get_string "result" with | "pretty" -> ignore (fprintf out "%a\n" pretty (Lazy.force table)) + | "pretty-deterministic" -> ignore (fprintf out "%a\n" pretty_deterministic (Lazy.force table)) | "fast_xml" -> let module SH = BatHashtbl.Make (Basetype.RawStrings) in let file2funs = SH.create 100 in diff --git a/src/framework/compareConstraints.ml b/src/framework/compareConstraints.ml new file mode 100644 index 0000000000..5197ac8e4e --- /dev/null +++ b/src/framework/compareConstraints.ml @@ -0,0 +1,217 @@ +open Batteries +open Analyses +open ConstrSys +open GobConfig + + +module CompareGlobSys (SpecSys: SpecSys) = +struct + open SpecSys + module Sys = EQSys + module LH = LHT + module GH = GHT + + open Spec + module G = Sys.G + + module PP = Hashtbl.Make (Node) + + let compare_globals g1 g2 = + let eq, le, gr, uk = 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 + let f_uk () = incr uk in + let f k v1 = + let v2 = try GH.find g2 k with Not_found -> G.bot () in + let b1 = G.leq v1 v2 in + let b2 = G.leq v2 v1 in + if b1 && b2 then + f_eq () + else if b1 then begin + if get_bool "dbg.compare_runs.diff" then + Logs.info "Global %a is more precise using left:\n%a" 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 + Logs.info "Global %a is more precise using right:\n%a" Sys.GVar.pretty_trace k G.pretty_diff (v1,v2); + f_gr () + end else begin + if get_bool "dbg.compare_runs.diff" then ( + Logs.info "Global %a is incomparable (diff):\n%a" Sys.GVar.pretty_trace k G.pretty_diff (v1,v2); + Logs.info "Global %a is incomparable (reverse diff):\n%a" Sys.GVar.pretty_trace k G.pretty_diff (v2,v1); + ); + f_uk () + end + in + GH.iter f g1; + Logs.info "globals:\tequal = %d\tleft = %d\tright = %d\tincomparable = %d" !eq !le !gr !uk + + let compare_locals h1 h2 = + let eq, le, gr, uk = ref 0, ref 0, ref 0, ref 0 in + let f k v1 = + if PP.mem h2 k then + let v2 = PP.find h2 k in + let b1 = D.leq v1 v2 in + let b2 = D.leq v2 v1 in + if b1 && b2 then + incr eq + else if b1 then begin + if get_bool "dbg.compare_runs.diff" then + Logs.info "%a @@ %a is more precise using left:\n%a" 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 + Logs.info "%a @@ %a is more precise using right:\n%a" Node.pretty_plain k CilType.Location.pretty (Node.location k) D.pretty_diff (v1,v2); + incr gr + end else begin + if get_bool "dbg.compare_runs.diff" then ( + Logs.info "%a @@ %a is incomparable (diff):\n%a" Node.pretty_plain k CilType.Location.pretty (Node.location k) D.pretty_diff (v1,v2); + Logs.info "%a @@ %a is incomparable (reverse diff):\n%a" 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 + 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 + Logs.info "locals: \tequal = %d\tleft = %d[%d]\tright = %d[%d]\tincomparable = %d" !eq !le o1 !gr o2 !uk *) + Logs.info "locals: \tequal = %d\tleft = %d\tright = %d\tincomparable = %d" !eq !le !gr !uk + + let compare_locals_ctx h1 h2 = + 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 + let f_uk () = incr uk in + let f k v1 = + if not (LH.mem h2 k) then incr no2 else + let v2 = LH.find h2 k in + let b1 = D.leq v1 v2 in + let b2 = D.leq v2 v1 in + if b1 && b2 then + f_eq () + else if b1 then begin + if get_bool "dbg.compare_runs.diff" then + Logs.info "%a is more precise using left:\n%a" 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 + Logs.info "%a is more precise using right:\n%a" Sys.LVar.pretty_trace k D.pretty_diff (v1,v2); + f_gr () + end else begin + if get_bool "dbg.compare_runs.diff" then ( + Logs.info "%a is incomparable (diff):\n%a" Sys.LVar.pretty_trace k D.pretty_diff (v1,v2); + Logs.info "%a is incomparable (reverse diff):\n%a" 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 *) + Logs.info "locals_ctx:\tequal = %d\tleft = %d\tright = %d\tincomparable = %d\tno_ctx_in_right = %d\tno_ctx_in_left = %d" !eq !le !gr !uk !no2 !no1 + + let compare (name1,name2) (l1,g1) (l2,g2) = + let one_ctx (n,_) v h = + PP.replace h n (try D.join v (PP.find h n) with Not_found -> v); + h + in + (* these contain results where the contexts per node have been joined *) + let h1 = PP.create 113 in + let h2 = PP.create 113 in + let _ = LH.fold one_ctx l1 h1 in + let _ = LH.fold one_ctx l2 h2 in + Logs.newline (); + Logs.info "Comparing GlobConstrSys precision of %s (left) with %s (right):" name1 name2; + compare_globals g1 g2; + compare_locals h1 h2; + compare_locals_ctx l1 l2; + Logs.newline (); +end + +module CompareHashtbl (Var: VarType) (Dom: Lattice.S) (VH: Hashtbl.S with type key = Var.t) = +struct + module Var = + struct + include Printable.Std + include Var + let name () = "var" + + let pretty = pretty_trace + include Printable.SimplePretty ( + struct + type nonrec t = t + let pretty = pretty + end + ) + end + + include PrecCompare.MakeHashtbl (Var) (Dom) (VH) +end + +module CompareEqSys (Sys: EqConstrSys) (VH: Hashtbl.S with type key = Sys.Var.t) = +struct + module Compare = CompareHashtbl (Sys.Var) (Sys.Dom) (VH) + + let compare (name1, name2) vh1 vh2 = + Logs.newline (); + Logs.info "Comparing EqConstrSys precision of %s (left) with %s (right):" name1 name2; + let verbose = get_bool "dbg.compare_runs.diff" in + let (_, msg) = Compare.compare ~verbose ~name1 vh1 ~name2 vh2 in + Logs.info "EqConstrSys comparison summary: %t" (fun () -> msg); + Logs.newline (); +end + +module CompareGlobal (GVar: VarType) (G: Lattice.S) (GH: Hashtbl.S with type key = GVar.t) = +struct + module Compare = CompareHashtbl (GVar) (G) (GH) + + let compare (name1, name2) vh1 vh2 = + Logs.newline (); + Logs.info "Comparing globals precision of %s (left) with %s (right):" name1 name2; + let verbose = get_bool "dbg.compare_runs.diff" in + let (_, msg) = Compare.compare ~verbose ~name1 vh1 ~name2 vh2 in + Logs.info "Globals comparison summary: %t" (fun () -> msg); + Logs.newline (); +end + +module CompareNode (C: Printable.S) (D: Lattice.S) (LH: Hashtbl.S with type key = VarF (C).t) = +struct + module Node = + struct + include Node + let var_id _ = "nodes" + let node x = x + let is_write_only _ = false + end + module NH = Hashtbl.Make (Node) + + module Compare = CompareHashtbl (Node) (D) (NH) + + let join_contexts (lh: D.t LH.t): D.t NH.t = + let nh = NH.create 113 in + LH.iter (fun (n, _) d -> + let d' = try D.join (NH.find nh n) d with Not_found -> d in + NH.replace nh n d' + ) lh; + nh + + let compare (name1, name2) vh1 vh2 = + Logs.newline (); + Logs.info "Comparing nodes precision of %s (left) with %s (right):" name1 name2; + let vh1' = join_contexts vh1 in + let vh2' = join_contexts vh2 in + let verbose = get_bool "dbg.compare_runs.diff" in + let (_, msg) = Compare.compare ~verbose ~name1 vh1' ~name2 vh2' in + Logs.info "Nodes comparison summary: %t" (fun () -> msg); + Logs.newline (); +end diff --git a/src/framework/constraints.ml b/src/framework/constraints.ml index 2d2fa80f8d..fb4b5081e8 100644 --- a/src/framework/constraints.ml +++ b/src/framework/constraints.ml @@ -8,579 +8,6 @@ open Analyses open ConstrSys open GobConfig -module M = Messages - - -(** Lifts a [Spec] so that the domain is [Hashcons]d *) -module HashconsLifter (S:Spec) - : Spec with module G = S.G - and module C = S.C -= -struct - module HConsedArg = - struct - (* We do refine int values on narrow and meet {!IntDomain.IntDomTupleImpl}, which can lead to fixpoint issues if we assume x op x = x *) - (* see https://github.com/goblint/analyzer/issues/1005 *) - let assume_idempotent = GobConfig.get_string "ana.int.refinement" = "never" - end - module D = Lattice.HConsed (S.D) (HConsedArg) - module G = S.G - module C = S.C - module V = S.V - module P = - struct - include S.P - let of_elt x = of_elt (D.unlift x) - end - - let name () = S.name () ^" hashconsed" - - type marshal = S.marshal (* TODO: should hashcons table be in here to avoid relift altogether? *) - let init = S.init - let finalize = S.finalize - - let startstate v = D.lift (S.startstate v) - let exitstate v = D.lift (S.exitstate v) - let morphstate v d = D.lift (S.morphstate v (D.unlift d)) - - let context fd = S.context fd % D.unlift - - let conv ctx = - { ctx with local = D.unlift ctx.local - ; split = (fun d es -> ctx.split (D.lift d) es ) - } - - let sync ctx reason = - D.lift @@ S.sync (conv ctx) reason - - let query ctx = - S.query (conv ctx) - - let assign ctx lv e = - D.lift @@ S.assign (conv ctx) lv e - - let vdecl ctx v = - D.lift @@ S.vdecl (conv ctx) v - - let branch ctx e tv = - D.lift @@ S.branch (conv ctx) e tv - - let body ctx f = - D.lift @@ S.body (conv ctx) f - - let return ctx r f = - D.lift @@ S.return (conv ctx) r f - - let asm ctx = - D.lift @@ S.asm (conv ctx) - - let skip ctx = - D.lift @@ S.skip (conv ctx) - - let enter ctx r f args = - List.map (fun (x,y) -> D.lift x, D.lift y) @@ S.enter (conv ctx) r f args - - let special ctx r f args = - D.lift @@ S.special (conv ctx) r f args - - let combine_env ctx r fe f args fc es f_ask = - D.lift @@ S.combine_env (conv ctx) r fe f args fc (D.unlift es) f_ask - - let combine_assign ctx r fe f args fc es f_ask = - D.lift @@ S.combine_assign (conv ctx) r fe f args fc (D.unlift es) f_ask - - let threadenter ctx ~multiple lval f args = - List.map D.lift @@ S.threadenter (conv ctx) ~multiple lval f args - - let threadspawn ctx ~multiple lval f args fctx = - D.lift @@ S.threadspawn (conv ctx) ~multiple lval f args (conv fctx) - - let paths_as_set ctx = - List.map (fun x -> D.lift x) @@ S.paths_as_set (conv ctx) - - let event ctx e octx = - D.lift @@ S.event (conv ctx) e (conv octx) -end - -(** Lifts a [Spec] so that the context is [Hashcons]d. *) -module HashconsContextLifter (S:Spec) - : Spec with module D = S.D - and module G = S.G - and module C = Printable.HConsed (S.C) -= -struct - module D = S.D - module G = S.G - module C = Printable.HConsed (S.C) - module V = S.V - module P = S.P - - let name () = S.name () ^" context hashconsed" - - type marshal = S.marshal (* TODO: should hashcons table be in here to avoid relift altogether? *) - let init = S.init - let finalize = S.finalize - - let startstate = S.startstate - let exitstate = S.exitstate - let morphstate = S.morphstate - - let context fd = C.lift % S.context fd - - let conv ctx = - { ctx with context = (fun () -> C.unlift (ctx.context ())) } - - let sync ctx reason = - S.sync (conv ctx) reason - - let query ctx (type a) (q: a Queries.t): a Queries.result = - match q with - | Queries.IterPrevVars f -> - let g i (n, c, j) e = f i (n, Obj.repr (C.lift (Obj.obj c)), j) e in - S.query (conv ctx) (Queries.IterPrevVars g) - | _ -> S.query (conv ctx) q - - let assign ctx lv e = - S.assign (conv ctx) lv e - - let vdecl ctx v = - S.vdecl (conv ctx) v - - let branch ctx e tv = - S.branch (conv ctx) e tv - - let body ctx f = - S.body (conv ctx) f - - let return ctx r f = - S.return (conv ctx) r f - - let asm ctx = - S.asm (conv ctx) - - let skip ctx = - S.skip (conv ctx) - - let enter ctx r f args = - S.enter (conv ctx) r f args - - let special ctx r f args = - S.special (conv ctx) r f args - - let combine_env ctx r fe f args fc es f_ask = - S.combine_env (conv ctx) r fe f args (Option.map C.unlift fc) es f_ask - - let combine_assign ctx r fe f args fc es f_ask = - S.combine_assign (conv ctx) r fe f args (Option.map C.unlift fc) es f_ask - - let threadenter ctx ~multiple lval f args = - S.threadenter (conv ctx) ~multiple lval f args - - let threadspawn ctx ~multiple lval f args fctx = - S.threadspawn (conv ctx) ~multiple lval f args (conv fctx) - - let paths_as_set ctx = S.paths_as_set (conv ctx) - let event ctx e octx = S.event (conv ctx) e (conv octx) -end - -(* see option ana.opt.equal *) -module OptEqual (S: Spec) = struct - module D = struct include S.D let equal x y = x == y || equal x y end - module G = struct include S.G let equal x y = x == y || equal x y end - module C = struct include S.C let equal x y = x == y || equal x y end - include (S : Spec with module D := D and module G := G and module C := C) -end - -(** If dbg.slice.on, stops entering functions after dbg.slice.n levels. *) -module LevelSliceLifter (S:Spec) - : Spec with module D = Lattice.Prod (S.D) (Lattice.Reverse (IntDomain.Lifted)) - and module G = S.G - and module C = S.C -= -struct - module D = Lattice.Prod (S.D) (Lattice.Reverse (IntDomain.Lifted)) - module G = S.G - module C = S.C - module V = S.V - module P = - struct - include S.P - let of_elt (x, _) = of_elt x - end - - let name () = S.name ()^" level sliced" - - let start_level = ref (`Top) - - type marshal = S.marshal (* TODO: should hashcons table be in here to avoid relift altogether? *) - let init marshal = - if get_bool "dbg.slice.on" then - start_level := `Lifted (Int64.of_int (get_int "dbg.slice.n")); - S.init marshal - - let finalize = S.finalize - - let startstate v = (S.startstate v, !start_level) - let exitstate v = (S.exitstate v, !start_level) - let morphstate v (d,l) = (S.morphstate v d, l) - - let context fd (d,_) = S.context fd d - - let conv ctx = - { ctx with local = fst ctx.local - ; split = (fun d es -> ctx.split (d, snd ctx.local) es ) - } - - let lift_fun ctx f g h = - f @@ h (g (conv ctx)) - - let enter' ctx r f args = - let liftmap = List.map (fun (x,y) -> (x, snd ctx.local), (y, snd ctx.local)) in - lift_fun ctx liftmap S.enter ((|>) args % (|>) f % (|>) r) - - let lift ctx d = (d, snd ctx.local) - let lift_start_level d = (d, !start_level) - - let sync ctx reason = lift_fun ctx (lift ctx) S.sync ((|>) reason) - let query' ctx (type a) (q: a Queries.t): a Queries.result = - lift_fun ctx identity S.query (fun x -> x q) - let assign ctx lv e = lift_fun ctx (lift ctx) S.assign ((|>) e % (|>) lv) - let vdecl ctx v = lift_fun ctx (lift ctx) S.vdecl ((|>) v) - let branch ctx e tv = lift_fun ctx (lift ctx) S.branch ((|>) tv % (|>) e) - let body ctx f = lift_fun ctx (lift ctx) S.body ((|>) f) - let return ctx r f = lift_fun ctx (lift ctx) S.return ((|>) f % (|>) r) - let asm ctx = lift_fun ctx (lift ctx) S.asm identity - let skip ctx = lift_fun ctx (lift ctx) S.skip identity - let special ctx r f args = lift_fun ctx (lift ctx) S.special ((|>) args % (|>) f % (|>) r) - let combine_env' ctx r fe f args fc es f_ask = lift_fun ctx (lift ctx) S.combine_env (fun p -> p r fe f args fc (fst es) f_ask) - let combine_assign' ctx r fe f args fc es f_ask = lift_fun ctx (lift ctx) S.combine_assign (fun p -> p r fe f args fc (fst es) f_ask) - - let threadenter ctx ~multiple lval f args = lift_fun ctx (List.map lift_start_level) (S.threadenter ~multiple) ((|>) args % (|>) f % (|>) lval) - let threadspawn ctx ~multiple lval f args fctx = lift_fun ctx (lift ctx) (S.threadspawn ~multiple) ((|>) (conv fctx) % (|>) args % (|>) f % (|>) lval) - - let leq0 = function - | `Top -> false - | `Lifted x -> x <= 0L - | `Bot -> true - - let sub1 = function - | `Lifted x -> `Lifted (Int64.sub x 1L) - | x -> x - - let add1 = function - | `Lifted x -> `Lifted (Int64.add x 1L) - | x -> x - - let paths_as_set ctx = - let liftmap = List.map (fun x -> (x, snd ctx.local)) in - lift_fun ctx liftmap S.paths_as_set (Fun.id) - - let event ctx e octx = - lift_fun ctx (lift ctx) S.event ((|>) (conv octx) % (|>) e) - - let enter ctx r f args = - let (d,l) = ctx.local in - if leq0 l then - [ctx.local, D.bot ()] - else - enter' {ctx with local=(d, sub1 l)} r f args - - let combine_env ctx r fe f args fc es f_ask = - let (d,l) = ctx.local in - let l = add1 l in - if leq0 l then - (d, l) - else - let d',_ = combine_env' ctx r fe f args fc es f_ask in - (d', l) - - let combine_assign ctx r fe f args fc es f_ask = - let (d,l) = ctx.local in - (* No need to add1 here, already done in combine_env. *) - if leq0 l then - (d, l) - else - let d',_ = combine_assign' ctx r fe f args fc es f_ask in - (d', l) - - let query ctx (type a) (q: a Queries.t): a Queries.result = - match q with - | Queries.EvalFunvar e -> - let (d,l) = ctx.local in - if leq0 l then - Queries.AD.empty () - else - query' ctx (Queries.EvalFunvar e) - | q -> query' ctx q -end - - -(** Limits the number of widenings per node. *) -module LimitLifter (S:Spec) = -struct - include (S : module type of S with module D := S.D and type marshal = S.marshal) - - let name () = S.name ()^" limited" - - let limit = ref 0 - - let init marshal = - limit := get_int "dbg.limit.widen"; - S.init marshal - - module H = MyCFG.NodeH - let h = H.create 13 - let incr k = - H.modify_def 1 k (fun v -> - if v >= !limit then failwith (GobPretty.sprintf "LimitLifter: Reached limit (%d) for node %a" !limit Node.pretty_plain_short (Option.get !MyCFG.current_node)); - v+1 - ) h; - module D = struct - include S.D - let widen x y = Option.may incr !MyCFG.current_node; widen x y (* when is this None? *) - end -end - - -(* widening on contexts, keeps contexts for calls only in D *) -module WidenContextLifterSide (S:Spec) -= -struct - module DD = - struct - include S.D - let printXml f d = BatPrintf.fprintf f "%a" printXml d - end - module M = MapDomain.MapBot (Basetype.Variables) (DD) (* should be CilFun -> S.C, but CilFun is not Groupable, and S.C is no Lattice *) - - module D = struct - include Lattice.Prod (S.D) (M) - let printXml f (d,m) = BatPrintf.fprintf f "\n%a\n%a\n" S.D.printXml d M.printXml m - end - module G = S.G - module C = S.C - module V = S.V - module P = - struct - include S.P - let of_elt (x, _) = of_elt x - end - - - let name () = S.name ()^" with widened contexts" - - type marshal = S.marshal - let init = S.init - let finalize = S.finalize - - let inj f x = f x, M.bot () - - let startstate = inj S.startstate - let exitstate = inj S.exitstate - let morphstate v (d,m) = S.morphstate v d, m - - let context fd (d,m) = S.context fd d (* just the child analysis' context *) - - let conv ctx = - { ctx with local = fst ctx.local - ; split = (fun d es -> ctx.split (d, snd ctx.local) es ) - } - let lift_fun ctx f g = g (f (conv ctx)), snd ctx.local - - let sync ctx reason = lift_fun ctx S.sync ((|>) reason) - let query ctx = S.query (conv ctx) - let assign ctx lv e = lift_fun ctx S.assign ((|>) e % (|>) lv) - let vdecl ctx v = lift_fun ctx S.vdecl ((|>) v) - let branch ctx e tv = lift_fun ctx S.branch ((|>) tv % (|>) e) - let body ctx f = lift_fun ctx S.body ((|>) f) - let return ctx r f = lift_fun ctx S.return ((|>) f % (|>) r) - let asm ctx = lift_fun ctx S.asm identity - let skip ctx = lift_fun ctx S.skip identity - let special ctx r f args = lift_fun ctx S.special ((|>) args % (|>) f % (|>) r) - - let event ctx e octx = lift_fun ctx S.event ((|>) (conv octx) % (|>) e) - - let threadenter ctx ~multiple lval f args = S.threadenter (conv ctx) ~multiple lval f args |> List.map (fun d -> (d, snd ctx.local)) - let threadspawn ctx ~multiple lval f args fctx = lift_fun ctx (S.threadspawn ~multiple) ((|>) (conv fctx) % (|>) args % (|>) f % (|>) lval) - - let enter ctx r f args = - let m = snd ctx.local in - let d' v_cur = - if ContextUtil.should_keep ~isAttr:GobContext ~keepOption:"ana.context.widen" ~keepAttr:"widen" ~removeAttr:"no-widen" f then ( - let v_old = M.find f.svar m in (* S.D.bot () if not found *) - let v_new = S.D.widen v_old (S.D.join v_old v_cur) in - Messages.(if tracing && not (S.D.equal v_old v_new) then tracel "widen-context" "enter results in new context for function %s" f.svar.vname); - v_new, M.add f.svar v_new m - ) - else - v_cur, m - in - S.enter (conv ctx) r f args - |> List.map (fun (c,v) -> (c,m), d' v) (* c: caller, v: callee *) - - let paths_as_set ctx = - let m = snd ctx.local in - S.paths_as_set (conv ctx) |> List.map (fun v -> (v,m)) - - let combine_env ctx r fe f args fc es f_ask = lift_fun ctx S.combine_env (fun p -> p r fe f args fc (fst es) f_ask) - let combine_assign ctx r fe f args fc es f_ask = lift_fun ctx S.combine_assign (fun p -> p r fe f args fc (fst es) f_ask) -end - - -(** Lifts a [Spec] with a special bottom element that represent unreachable code. *) -module DeadCodeLifter (S:Spec) - : Spec with module D = Dom (S.D) - and module G = S.G - and module C = S.C -= -struct - module D = Dom (S.D) - module G = S.G - module C = S.C - module V = S.V - module P = - struct - include Printable.Option (S.P) (struct let name = "None" end) - - let of_elt = function - | `Lifted x -> Some (S.P.of_elt x) - | _ -> None - end - - let name () = S.name ()^" lifted" - - type marshal = S.marshal - let init = S.init - let finalize = S.finalize - - let startstate v = `Lifted (S.startstate v) - let exitstate v = `Lifted (S.exitstate v) - let morphstate v d = try `Lifted (S.morphstate v (D.unlift d)) with Deadcode -> d - - let context fd = S.context fd % D.unlift - - let conv ctx = - { ctx with local = D.unlift ctx.local - ; split = (fun d es -> ctx.split (D.lift d) es ) - } - - let lift_fun ctx f g h b = - try f @@ h (g (conv ctx)) - with Deadcode -> b - - let sync ctx reason = lift_fun ctx D.lift S.sync ((|>) reason) `Bot - - let enter ctx r f args = - let liftmap = List.map (fun (x,y) -> D.lift x, D.lift y) in - lift_fun ctx liftmap S.enter ((|>) args % (|>) f % (|>) r) [] - - let paths_as_set ctx = - let liftmap = List.map (fun x -> D.lift x) in - lift_fun ctx liftmap S.paths_as_set (Fun.id) [D.bot ()] (* One dead path instead of none, such that combine_env gets called for functions with dead normal return (and thus longjmpy returns can be correctly handled by lifter). *) - - let query ctx (type a) (q: a Queries.t): a Queries.result = - lift_fun ctx identity S.query (fun (x) -> x q) (Queries.Result.bot q) - let assign ctx lv e = lift_fun ctx D.lift S.assign ((|>) e % (|>) lv) `Bot - let vdecl ctx v = lift_fun ctx D.lift S.vdecl ((|>) v) `Bot - let branch ctx e tv = lift_fun ctx D.lift S.branch ((|>) tv % (|>) e) `Bot - let body ctx f = lift_fun ctx D.lift S.body ((|>) f) `Bot - let return ctx r f = lift_fun ctx D.lift S.return ((|>) f % (|>) r) `Bot - let asm ctx = lift_fun ctx D.lift S.asm identity `Bot - let skip ctx = lift_fun ctx D.lift S.skip identity `Bot - let special ctx r f args = lift_fun ctx D.lift S.special ((|>) args % (|>) f % (|>) r) `Bot - let combine_env ctx r fe f args fc es f_ask = lift_fun ctx D.lift S.combine_env (fun p -> p r fe f args fc (D.unlift es) f_ask) `Bot - let combine_assign ctx r fe f args fc es f_ask = lift_fun ctx D.lift S.combine_assign (fun p -> p r fe f args fc (D.unlift es) f_ask) `Bot - - let threadenter ctx ~multiple lval f args = lift_fun ctx (List.map D.lift) (S.threadenter ~multiple) ((|>) args % (|>) f % (|>) lval) [] - let threadspawn ctx ~multiple lval f args fctx = lift_fun ctx D.lift (S.threadspawn ~multiple) ((|>) (conv fctx) % (|>) args % (|>) f % (|>) lval) `Bot - - let event (ctx:(D.t,G.t,C.t,V.t) ctx) (e:Events.t) (octx:(D.t,G.t,C.t,V.t) ctx):D.t = lift_fun ctx D.lift S.event ((|>) (conv octx) % (|>) e) `Bot -end - -module NoContext = struct let name = "no context" end -module IntConf = -struct - let n () = max_int - let names x = Format.asprintf "%d" x -end - -(** Lifts a [Spec] with the context gas variable. The gas variable limits the number of context-sensitively analyzed function calls in a call stack. - For every function call the gas is reduced. If the gas is zero, the remaining function calls are analyzed without context-information *) -module ContextGasLifter (S:Spec) - : Spec with module D = Lattice.Prod (S.D) (Lattice.Chain (IntConf)) - and module C = Printable.Option (S.C) (NoContext) - and module G = S.G -= -struct - include S - - module Context_Gas_Prod (Base1: Lattice.S) (Base2: Lattice.S) = - struct - include Lattice.Prod (Base1) (Base2) - let printXml f (x,y) = - BatPrintf.fprintf f "\n%a\n%a\n" Base1.printXml x Base2.printXml y - end - module D = Context_Gas_Prod (S.D) (Lattice.Chain (IntConf)) (* Product of S.D and an integer, tracking the context gas value *) - module C = Printable.Option (S.C) (NoContext) - module G = S.G - module V = S.V - module P = - struct - include S.P - let of_elt (x, _) = of_elt x - end - - (* returns context gas value of the given ctx *) - let cg_val ctx = snd ctx.local - - type marshal = S.marshal - let init = S.init - let finalize = S.finalize - - let name () = S.name ()^" with context gas" - let startstate v = S.startstate v, get_int "ana.context.gas_value" - let exitstate v = S.exitstate v, get_int "ana.context.gas_value" - let morphstate v (d,i) = S.morphstate v d, i - - let context fd (d,i) = - (* only keep context if the context gas is greater zero *) - if i <= 0 then None else Some (S.context fd d) - - let conv (ctx:(D.t,G.t,C.t,V.t) ctx): (S.D.t,G.t,S.C.t,V.t)ctx = - if (cg_val ctx <= 0) - then {ctx with local = fst ctx.local - ; split = (fun d es -> ctx.split (d, cg_val ctx) es) - ; context = (fun () -> ctx_failwith "no context (contextGas = 0)")} - else {ctx with local = fst ctx.local - ; split = (fun d es -> ctx.split (d, cg_val ctx) es) - ; context = (fun () -> Option.get (ctx.context ()))} - - let enter ctx r f args = - (* callee gas = caller gas - 1 *) - let liftmap_tup = List.map (fun (x,y) -> (x, cg_val ctx), (y, max 0 (cg_val ctx - 1))) in - liftmap_tup (S.enter (conv ctx) r f args) - - let threadenter ctx ~multiple lval f args = - let liftmap f = List.map (fun (x) -> (x, max 0 (cg_val ctx - 1))) f in - liftmap (S.threadenter (conv ctx) ~multiple lval f args) - - let sync ctx reason = S.sync (conv ctx) reason, cg_val ctx - let query ctx q = S.query (conv ctx) q - let assign ctx lval expr = S.assign (conv ctx) lval expr, cg_val ctx - let vdecl ctx v = S.vdecl (conv ctx) v, cg_val ctx - let body ctx fundec = S.body (conv ctx) fundec, cg_val ctx - let branch ctx e tv = S.branch (conv ctx) e tv, cg_val ctx - let return ctx r f = S.return (conv ctx) r f, cg_val ctx - let asm ctx = S.asm (conv ctx), cg_val ctx - let skip ctx = S.skip (conv ctx), cg_val ctx - let special ctx r f args = S.special (conv ctx) r f args, cg_val ctx - let combine_env ctx r fe f args fc es f_ask = S.combine_env (conv ctx) r fe f args (Option.bind fc Fun.id) (fst es) f_ask, cg_val ctx - let combine_assign ctx r fe f args fc es f_ask = S.combine_assign (conv ctx) r fe f args (Option.bind fc Fun.id) (fst es) f_ask, cg_val ctx - let paths_as_set ctx = List.map (fun (x) -> (x, cg_val ctx)) @@ S.paths_as_set (conv ctx) - let threadspawn ctx ~multiple lval f args fctx = S.threadspawn (conv ctx) ~multiple lval f args (conv fctx), cg_val ctx - let event ctx e octx = S.event (conv ctx) e (conv octx), cg_val ctx -end - module type Increment = sig @@ -613,9 +40,10 @@ struct let sync ctx = match ctx.prev_node, Cfg.prev ctx.prev_node with - | _, _ :: _ :: _ (* Join in CFG. *) - | FunctionEntry _, _ -> (* Function entry, also needs sync because partial contexts joined by solver, see 00-sanity/35-join-contexts. *) + | _, _ :: _ :: _ -> (* Join in CFG. *) S.sync ctx `Join + | FunctionEntry f, _ -> (* Function entry, also needs sync because partial contexts joined by solver, see 00-sanity/35-join-contexts. *) + S.sync ctx (`JoinCall f) | _, _ -> S.sync ctx `Normal let side_context sideg f c = @@ -648,7 +76,7 @@ struct spawns := (lval, f, args, d, multiple) :: !spawns; match Cilfacade.find_varinfo_fundec f with | fd -> - let c = S.context fd d in + let c = S.context ctx fd d in sidel (FunctionEntry fd, c) d; ignore (getl (Function fd, c)) | exception Not_found -> @@ -695,11 +123,13 @@ struct let tf_assign var edge prev_node lv e getl sidel getg sideg d = let ctx, r, spawns = common_ctx var edge prev_node d getl sidel getg sideg in - common_join ctx (S.assign ctx lv e) !r !spawns + let d = S.assign ctx lv e in (* Force transfer function to be evaluated before dereferencing in common_join argument. *) + common_join ctx d !r !spawns let tf_vdecl var edge prev_node v getl sidel getg sideg d = let ctx, r, spawns = common_ctx var edge prev_node d getl sidel getg sideg in - common_join ctx (S.vdecl ctx v) !r !spawns + let d = S.vdecl ctx v in (* Force transfer function to be evaluated before dereferencing in common_join argument. *) + common_join ctx d !r !spawns let normal_return r fd ctx sideg = let spawning_return = S.return ctx r fd in @@ -714,7 +144,7 @@ struct let tf_ret var edge prev_node ret fd getl sidel getg sideg d = let ctx, r, spawns = common_ctx var edge prev_node d getl sidel getg sideg in - let d = + let d = (* Force transfer function to be evaluated before dereferencing in common_join argument. *) if (CilType.Fundec.equal fd MyCFG.dummy_func || List.mem fd.svar.vname (get_string_list "mainfun")) && get_bool "kernel" @@ -729,11 +159,13 @@ struct 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 + let d = S.body ctx fd in (* Force transfer function to be evaluated before dereferencing in common_join argument. *) + common_join ctx d !r !spawns let tf_test var edge prev_node e tv getl sidel getg sideg d = let ctx, r, spawns = common_ctx var edge prev_node d getl sidel getg sideg in - common_join ctx (S.branch ctx e tv) !r !spawns + let d = S.branch ctx e tv in (* Force transfer function to be evaluated before dereferencing in common_join argument. *) + common_join ctx d !r !spawns let tf_normal_call ctx lv e (f:fundec) args getl sidel getg sideg = let combine (cd, fc, fd) = @@ -789,7 +221,7 @@ struct r in let paths = S.enter ctx lv f args in - let paths = List.map (fun (c,v) -> (c, S.context f v, v)) paths in + let paths = List.map (fun (c,v) -> (c, S.context ctx f v, v)) paths in List.iter (fun (c,fc,v) -> if not (S.D.is_bot v) then sidel (FunctionEntry f, fc) v) paths; let paths = List.map (fun (c,fc,v) -> (c, fc, if S.D.is_bot v then v else getl (Function f, fc))) paths in (* Don't filter bot paths, otherwise LongjmpLifter is not called. *) @@ -852,11 +284,13 @@ struct let tf_asm var edge prev_node getl sidel getg sideg d = let ctx, r, spawns = common_ctx var edge prev_node d getl sidel getg sideg in - common_join ctx (S.asm ctx) !r !spawns + let d = S.asm ctx in (* Force transfer function to be evaluated before dereferencing in common_join argument. *) + common_join ctx d !r !spawns let tf_skip var edge prev_node getl sidel getg sideg d = let ctx, r, spawns = common_ctx var edge prev_node d getl sidel getg sideg in - common_join ctx (S.skip ctx) !r !spawns + let d = S.skip ctx in (* Force transfer function to be evaluated before dereferencing in common_join argument. *) + common_join ctx d !r !spawns let tf var getl sidel getg sideg prev_node edge d = begin match edge with @@ -1106,880 +540,3 @@ struct {obsolete; delete; reluctant; restart} end - - -(** Add path sensitivity to a analysis *) -module PathSensitive2 (Spec:Spec) - : Spec - with module G = Spec.G - and module C = Spec.C - and module V = Spec.V -= -struct - module D = - struct - (* TODO is it really worth it to check every time instead of just using sets and joining later? *) - module R = - struct - include Spec.P - type elt = Spec.D.t - end - module J = SetDomain.Joined (Spec.D) - include DisjointDomain.ProjectiveSet (Spec.D) (J) (R) - let name () = "PathSensitive (" ^ name () ^ ")" - - let printXml f x = - let print_one x = - BatPrintf.fprintf f "\n%a" Spec.D.printXml x - in - iter print_one x - end - - module G = Spec.G - module C = Spec.C - module V = Spec.V - module P = UnitP - - let name () = "PathSensitive2("^Spec.name ()^")" - - type marshal = Spec.marshal - let init = Spec.init - let finalize = Spec.finalize - - let exitstate v = D.singleton (Spec.exitstate v) - let startstate v = D.singleton (Spec.startstate v) - let morphstate v d = D.map (Spec.morphstate v) d - - let context fd l = - if D.cardinal l <> 1 then - failwith "PathSensitive2.context must be called with a singleton set." - else - Spec.context fd @@ D.choose l - - let conv ctx x = - let rec ctx' = { ctx with ask = (fun (type a) (q: a Queries.t) -> Spec.query ctx' q) - ; local = x - ; split = (ctx.split % D.singleton) } - in - ctx' - - let map ctx f g = - let h x xs = - try D.add (g (f (conv ctx x))) xs - with Deadcode -> xs - in - let d = D.fold h ctx.local (D.empty ()) in - if D.is_bot d then raise Deadcode else d - - let fold' ctx f g h a = - let k x a = - try h a @@ g @@ f @@ conv ctx x - with Deadcode -> a - in - D.fold k ctx.local a - - let assign ctx l e = map ctx Spec.assign (fun h -> h l e ) - let vdecl ctx v = map ctx Spec.vdecl (fun h -> h v) - let body ctx f = map ctx Spec.body (fun h -> h f ) - let return ctx e f = map ctx Spec.return (fun h -> h e f ) - let branch ctx e tv = map ctx Spec.branch (fun h -> h e tv) - let asm ctx = map ctx Spec.asm identity - let skip ctx = map ctx Spec.skip identity - let special ctx l f a = map ctx Spec.special (fun h -> h l f a) - - let event ctx e octx = - let fd1 = D.choose octx.local in - map ctx Spec.event (fun h -> h e (conv octx fd1)) - - let threadenter ctx ~multiple lval f args = - let g xs ys = (List.map (fun y -> D.singleton y) ys) @ xs in - fold' ctx (Spec.threadenter ~multiple) (fun h -> h lval f args) g [] - - let threadspawn ctx ~multiple lval f args fctx = - let fd1 = D.choose fctx.local in - map ctx (Spec.threadspawn ~multiple) (fun h -> h lval f args (conv fctx fd1)) - - let sync ctx reason = map ctx Spec.sync (fun h -> h reason) - - let query ctx (type a) (q: a Queries.t): a Queries.result = - (* TODO: handle Invariant path like PathSensitive3? *) - (* join results so that they are sound for all paths *) - let module Result = (val Queries.Result.lattice q) in - fold' ctx Spec.query identity (fun x f -> Result.join x (f q)) (Result.bot ()) - - let enter ctx l f a = - let g xs ys = (List.map (fun (x,y) -> D.singleton x, D.singleton y) ys) @ xs in - fold' ctx Spec.enter (fun h -> h l f a) g [] - - let paths_as_set ctx = - (* Path-sensitivity is only here, not below! *) - let elems = D.elements ctx.local in - List.map (D.singleton) elems - - let combine_env ctx l fe f a fc d f_ask = - assert (D.cardinal ctx.local = 1); - let cd = D.choose ctx.local in - let k x y = - if M.tracing then M.traceli "combine" "function: %a" Spec.D.pretty x; - try - let r = Spec.combine_env (conv ctx cd) l fe f a fc x f_ask in - if M.tracing then M.traceu "combine" "combined function: %a" Spec.D.pretty r; - D.add r y - with Deadcode -> - if M.tracing then M.traceu "combine" "combined function: dead"; - y - in - let d = D.fold k d (D.bot ()) in - if D.is_bot d then raise Deadcode else d - - let combine_assign ctx l fe f a fc d f_ask = - assert (D.cardinal ctx.local = 1); - let cd = D.choose ctx.local in - let k x y = - if M.tracing then M.traceli "combine" "function: %a" Spec.D.pretty x; - try - let r = Spec.combine_assign (conv ctx cd) l fe f a fc x f_ask in - if M.tracing then M.traceu "combine" "combined function: %a" Spec.D.pretty r; - D.add r y - with Deadcode -> - if M.tracing then M.traceu "combine" "combined function: dead"; - y - in - let d = D.fold k d (D.bot ()) in - if D.is_bot d then raise Deadcode else d -end - -module DeadBranchLifter (S: Spec): Spec = -struct - include S - - let name () = "DeadBranch (" ^ S.name () ^ ")" - - (* Two global invariants: - 1. S.V -> S.G -- used for S - 2. node -> (exp -> flat bool) -- used for warnings *) - - module V = - struct - include Printable.EitherConf (struct let expand1 = false let expand2 = true end) (S.V) (Node) - let name () = "DeadBranch" - 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 = - struct - include MapDomain.MapBot (Basetype.CilExp) (BoolDomain.FlatBool) - let name () = "branches" - end - - module G = - struct - include Lattice.Lift2 (S.G) (EM) - let name () = "deadbranch" - - 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 when not (CilType.Exp.equal exp one) -> (* all branches dead *) - M.msg_final Error ~category:Analyzer ~tags:[Category Unsound] "Both branches dead"; - M.error ~loc:(Node g) ~category:Analyzer ~tags:[Category Unsound] "both branches over condition '%a' are dead" d_exp exp - | `Bot (* all branches dead, fine at our inserted Neg(1)-s because no Pos(1) *) - | `Top -> (* may be both true and false *) - () - ) em; - end - | InvariantGlobal g -> - let g: V.t = Obj.obj g in - begin match g with - | `Left g -> - S.query (conv ctx) (InvariantGlobal (Obj.repr g)) - | `Right g -> - Queries.Result.top q - 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 !AnalysisState.postsolving then ( - try - let r = branch ctx exp tv in - (* branch is live *) - 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 *) - ctx.sideg (V.node ctx.prev_node) (G.create_node (EM.singleton exp `Bot)); (* record expression without reached tv *) - raise Deadcode - ) - 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 paths_as_set ctx = S.paths_as_set (conv ctx) - let body ctx = S.body (conv ctx) - let return ctx = S.return (conv ctx) - let combine_env ctx = S.combine_env (conv ctx) - let combine_assign ctx = S.combine_assign (conv ctx) - let special ctx = S.special (conv ctx) - let threadenter ctx = S.threadenter (conv ctx) - let threadspawn ctx ~multiple lv f args fctx = S.threadspawn (conv ctx) ~multiple 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) - let event ctx e octx = S.event (conv ctx) e (conv octx) -end - -module LongjmpLifter (S: Spec): Spec = -struct - include S - - let name () = "Longjmp (" ^ S.name () ^ ")" - - module V = - struct - include Printable.Either3Conf (struct let expand1 = false let expand2 = true let expand3 = true end) (S.V) (Printable.Prod (Node) (C)) (Printable.Prod (CilType.Fundec) (C)) - let name () = "longjmp" - let s x = `Left x - let longjmpto x = `Middle x - let longjmpret x = `Right x - let is_write_only = function - | `Left x -> S.V.is_write_only x - | _ -> false - end - - module G = - struct - include Lattice.Lift2 (S.G) (S.D) - - let s = function - | `Bot -> S.G.bot () - | `Lifted1 x -> x - | _ -> failwith "LongjmpLifter.s" - let local = function - | `Bot -> S.D.bot () - | `Lifted2 x -> x - | _ -> failwith "LongjmpLifter.local" - let create_s s = `Lifted1 s - let create_local local = `Lifted2 local - - let printXml f = function - | `Lifted1 x -> S.G.printXml f x - | `Lifted2 x -> BatPrintf.fprintf f "%a" S.D.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)) - | _ -> - Queries.Result.top q - end - | InvariantGlobal g -> - let g: V.t = Obj.obj g in - begin match g with - | `Left g -> - S.query (conv ctx) (InvariantGlobal (Obj.repr g)) - | _ -> - Queries.Result.top q - 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')); - (* TODO: vars? *) - | _ -> - S.query (conv ctx) q - - - let branch ctx = S.branch (conv ctx) - let assign ctx = S.assign (conv ctx) - let vdecl ctx = S.vdecl (conv ctx) - let enter ctx = S.enter (conv ctx) - let paths_as_set ctx = S.paths_as_set (conv ctx) - let body ctx = S.body (conv ctx) - let return ctx = S.return (conv ctx) - - let combine_env ctx lv e f args fc fd f_ask = - let conv_ctx = conv ctx in - let current_fundec = Node.find_fundec ctx.node in - let handle_longjmp (cd, fc, longfd) = - (* This is called per-path. *) - let rec cd_ctx = - { conv_ctx with - ask = (fun (type a) (q: a Queries.t) -> S.query cd_ctx q); - local = cd; - } - in - let longfd_ctx = - (* Inner scope to prevent unsynced longfd_ctx from being used. *) - (* Extra sync like with normal combine. *) - let rec sync_ctx = - { conv_ctx with - ask = (fun (type a) (q: a Queries.t) -> S.query sync_ctx q); - local = longfd; - prev_node = Function f; - } - in - let synced = S.sync sync_ctx `Join in - let rec longfd_ctx = - { sync_ctx with - ask = (fun (type a) (q: a Queries.t) -> S.query longfd_ctx q); - local = synced; - } - in - longfd_ctx - in - let combined = lazy ( (* does not depend on target, do at most once *) - (* Globals are non-problematic here, as they are always carried around without any issues! *) - (* A combine call is mostly needed to ensure locals have appropriate values. *) - (* Using f from called function on purpose here! Needed? *) - S.combine_env cd_ctx None e f args fc longfd_ctx.local (Analyses.ask_of_ctx longfd_ctx) (* no lval because longjmp return skips return value assignment *) - ) - in - let returned = lazy ( (* does not depend on target, do at most once *) - let rec combined_ctx = - { cd_ctx with - ask = (fun (type a) (q: a Queries.t) -> S.query combined_ctx q); - local = Lazy.force combined; - } - in - S.return combined_ctx None current_fundec - ) - in - let (active_targets, _) = longfd_ctx.ask ActiveJumpBuf in - let valid_targets = cd_ctx.ask ValidLongJmp in - let handle_target target = match target with - | JmpBufDomain.BufferEntryOrTop.AllTargets -> () (* The warning is already emitted at the point where the longjmp happens *) - | Target (target_node, target_context) -> - let target_fundec = Node.find_fundec target_node in - if CilType.Fundec.equal target_fundec current_fundec && ControlSpecC.equal target_context (ctx.control_context ()) then ( - if M.tracing then Messages.tracel "longjmp" "Fun: Potentially from same context, side-effect to %a" Node.pretty target_node; - ctx.sideg (V.longjmpto (target_node, ctx.context ())) (G.create_local (Lazy.force combined)) - (* No need to propagate this outwards here, the set of valid longjumps is part of the context, we can never have the same context setting the longjmp multiple times *) - ) - (* Appropriate setjmp is not in current function & current context *) - else if JmpBufDomain.JmpBufSet.mem target valid_targets then - ctx.sideg (V.longjmpret (current_fundec, ctx.context ())) (G.create_local (Lazy.force returned)) - else - (* It actually is not handled here but was propagated here spuriously, we already warned at the location where this issue is caused *) - (* As the validlongjumps inside the callee is a a superset of the ones inside the caller *) - () - in - JmpBufDomain.JmpBufSet.iter handle_target active_targets - in - if M.tracing then M.tracel "longjmp" "longfd getg %a" CilType.Fundec.pretty f; - let longfd = G.local (ctx.global (V.longjmpret (f, Option.get fc))) in - if M.tracing then M.tracel "longjmp" "longfd %a" D.pretty longfd; - if not (D.is_bot longfd) then - handle_longjmp (ctx.local, fc, longfd); - S.combine_env (conv_ctx) lv e f args fc fd f_ask - - let combine_assign ctx lv e f args fc fd f_ask = - S.combine_assign (conv ctx) lv e f args fc fd f_ask - - let special ctx lv f args = - let conv_ctx = conv ctx in - match (LibraryFunctions.find f).special args with - | Setjmp {env} -> - (* Handling of returning for the first time *) - let normal_return = S.special conv_ctx lv f args in - let jmp_return = G.local (ctx.global (V.longjmpto (ctx.prev_node, ctx.context ()))) in - if S.D.is_bot jmp_return then - normal_return - else ( - let rec jmp_ctx = - { conv_ctx with - ask = (fun (type a) (q: a Queries.t) -> S.query jmp_ctx q); - local = jmp_return; - } - in - let longjmped = S.event jmp_ctx (Events.Longjmped {lval=lv}) jmp_ctx in - S.D.join normal_return longjmped - ) - | Longjmp {env; value} -> - let current_fundec = Node.find_fundec ctx.node in - let handle_path path = ( - let rec path_ctx = - { conv_ctx with - ask = (fun (type a) (q: a Queries.t) -> S.query path_ctx q); - local = path; - } - in - let specialed = lazy ( (* does not depend on target, do at most once *) - S.special path_ctx lv f args - ) - in - let returned = lazy ( (* does not depend on target, do at most once *) - let rec specialed_ctx = - { path_ctx with - ask = (fun (type a) (q: a Queries.t) -> S.query specialed_ctx q); - local = Lazy.force specialed; - } - in - S.return specialed_ctx None current_fundec - ) - in - (* Eval `env` again to avoid having to construct bespoke ctx to ask *) - let targets = path_ctx.ask (EvalJumpBuf env) in - let valid_targets = path_ctx.ask ValidLongJmp in - if M.tracing then Messages.tracel "longjmp" "Jumping to %a" JmpBufDomain.JmpBufSet.pretty targets; - let handle_target target = match target with - | JmpBufDomain.BufferEntryOrTop.AllTargets -> - M.warn ~category:Imprecise "Longjmp to potentially invalid target, as contents of buffer %a may be unknown! (imprecision due to heap?)" d_exp env; - M.msg_final Error ~category:Unsound ~tags:[Category Imprecise; Category Call] "Longjmp to unknown target ignored" - | Target (target_node, target_context) -> - let target_fundec = Node.find_fundec target_node in - if CilType.Fundec.equal target_fundec current_fundec && ControlSpecC.equal target_context (ctx.control_context ()) then ( - if M.tracing then Messages.tracel "longjmp" "Potentially from same context, side-effect to %a" Node.pretty target_node; - ctx.sideg (V.longjmpto (target_node, ctx.context ())) (G.create_local (Lazy.force specialed)) - ) - else if JmpBufDomain.JmpBufSet.mem target valid_targets then ( - if M.tracing then Messages.tracel "longjmp" "Longjmp to somewhere else, side-effect to %i" (S.C.hash (ctx.context ())); - ctx.sideg (V.longjmpret (current_fundec, ctx.context ())) (G.create_local (Lazy.force returned)) - ) - else - M.warn ~category:(Behavior (Undefined Other)) "Longjmp to potentially invalid target! (Target %a in Function %a which may have already returned or is in a different thread)" Node.pretty target_node CilType.Fundec.pretty target_fundec - in - if JmpBufDomain.JmpBufSet.is_empty targets then - M.warn ~category:(Behavior (Undefined Other)) "Longjmp to potentially invalid target (%a is bot?!)" d_exp env - else - JmpBufDomain.JmpBufSet.iter handle_target targets - ) - in - List.iter handle_path (S.paths_as_set conv_ctx); - if !AnalysisState.should_warn && List.mem "termination" @@ get_string_list "ana.activated" then ( - AnalysisState.svcomp_may_not_terminate := true; - M.warn ~category:Termination "The program might not terminate! (Longjmp)" - ); - S.D.bot () - | _ -> S.special conv_ctx lv f args - let threadenter ctx = S.threadenter (conv ctx) - let threadspawn ctx ~multiple lv f args fctx = S.threadspawn (conv ctx) ~multiple 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) - let event ctx e octx = S.event (conv ctx) e (conv octx) -end - - -(** Add cycle detection in the context-sensitive dynamic function call graph to an analysis *) -module RecursionTermLifter (S: Spec) - : Spec with module D = S.D - and module C = S.C -= -(* two global invariants: - - S.V -> S.G - Needed to store the previously built global invariants - - fundec * S.C -> (Set (fundec * S.C)) - The second global invariant maps from the callee fundec and context to a set of caller fundecs and contexts. - This structure therefore stores the context-sensitive call graph. - For example: - let the function f in context c call function g in context c'. - In the global invariant structure it would be stored like this: (g,c') -> {(f, c)} -*) - -struct - include S - - (* contains all the callee fundecs and contexts *) - module V = GVarFC(S.V)(S.C) - - (* Tuple containing the fundec and context of a caller *) - module Call = Printable.Prod (CilType.Fundec) (S.C) - - (* Set containing multiple caller tuples *) - module CallerSet = SetDomain.Make (Call) - - module G = - struct - include Lattice.Lift2 (G) (CallerSet) - - let spec = function - | `Bot -> G.bot () - | `Lifted1 x -> x - | _ -> failwith "RecursionTermLifter.spec" - - let callers = function - | `Bot -> CallerSet.bot () - | `Lifted2 x -> x - | _ -> failwith "RecursionTermLifter.callGraph" - - let create_spec spec = `Lifted1 spec - let create_singleton_caller caller = `Lifted2 (CallerSet.singleton caller) - - let printXml f = function - | `Lifted1 x -> G.printXml f x - | `Lifted2 x -> BatPrintf.fprintf f "%a" CallerSet.printXml x - | x -> BatPrintf.fprintf f "%a" printXml x - - end - - let name () = "RecursionTermLifter (" ^ S.name () ^ ")" - - let conv (ctx: (_, G.t, _, V.t) ctx): (_, S.G.t, _, S.V.t) ctx = - { ctx with - global = (fun v -> G.spec (ctx.global (V.spec v))); - sideg = (fun v g -> ctx.sideg (V.spec v) (G.create_spec g)); - } - - let cycleDetection ctx call = - let module LH = Hashtbl.Make (Printable.Prod (CilType.Fundec) (S.C)) in - let module LS = Set.Make (Printable.Prod (CilType.Fundec) (S.C)) in - (* find all cycles/SCCs *) - let global_visited_calls = LH.create 100 in - - (* DFS *) - let rec iter_call (path_visited_calls: LS.t) ((fundec, _) as call) = - if LS.mem call path_visited_calls then ( - AnalysisState.svcomp_may_not_terminate := true; (*set the indicator for a non-terminating program for the sv comp*) - (*Cycle found*) - let loc = M.Location.CilLocation fundec.svar.vdecl in - M.warn ~loc ~category:Termination "The program might not terminate! (Fundec %a is contained in a call graph cycle)" CilType.Fundec.pretty fundec) (* output a warning for non-termination*) - else if not (LH.mem global_visited_calls call) then begin - LH.replace global_visited_calls call (); - let new_path_visited_calls = LS.add call path_visited_calls in - let gvar = V.call call in - let callers = G.callers (ctx.global gvar) in - CallerSet.iter (fun to_call -> - iter_call new_path_visited_calls to_call - ) callers; - end - in - iter_call LS.empty call - - let query ctx (type a) (q: a Queries.t): a Queries.result = - match q with - | WarnGlobal v -> - (* check result of loop analysis *) - if not (ctx.ask Queries.MustTermAllLoops) then - AnalysisState.svcomp_may_not_terminate := true; - let v: V.t = Obj.obj v in - begin match v with - | `Left v' -> - S.query (conv ctx) (WarnGlobal (Obj.repr v')) - | `Right call -> cycleDetection ctx call (* Note: to make it more efficient, one could only execute the cycle detection in case the loop analysis returns true, because otherwise the program will probably not terminate anyway*) - end - | InvariantGlobal v -> - let v: V.t = Obj.obj v in - begin match v with - | `Left v -> - S.query (conv ctx) (InvariantGlobal (Obj.repr v)) - | `Right v -> - Queries.Result.top q - end - | _ -> S.query (conv ctx) q - - let branch ctx = S.branch (conv ctx) - let assign ctx = S.assign (conv ctx) - let vdecl ctx = S.vdecl (conv ctx) - - - let record_call sideg callee caller = - sideg (V.call callee) (G.create_singleton_caller caller) - - let enter ctx = S.enter (conv ctx) - let paths_as_set ctx = S.paths_as_set (conv ctx) - let body ctx = S.body (conv ctx) - let return ctx = S.return (conv ctx) - let combine_env ctx r fe f args fc es f_ask = - if !AnalysisState.postsolving then ( - let c_r: S.C.t = ctx.context () in (* Caller context *) - let nodeF = ctx.node in - let fd_r : fundec = Node.find_fundec nodeF in (* Caller fundec *) - let caller: (fundec * S.C.t) = (fd_r, c_r) in - let c_e: S.C.t = Option.get fc in (* Callee context *) - let fd_e : fundec = f in (* Callee fundec *) - let callee = (fd_e, c_e) in - record_call ctx.sideg callee caller - ); - S.combine_env (conv ctx) r fe f args fc es f_ask - - let combine_assign ctx = S.combine_assign (conv ctx) - let special ctx = S.special (conv ctx) - let threadenter ctx = S.threadenter (conv ctx) - let threadspawn ctx ~multiple lv f args fctx = S.threadspawn (conv ctx) ~multiple 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) - let event ctx e octx = S.event (conv ctx) e (conv octx) -end - -module CompareGlobSys (SpecSys: SpecSys) = -struct - open SpecSys - module Sys = EQSys - module LH = LHT - module GH = GHT - - open Spec - module G = Sys.G - - module PP = Hashtbl.Make (Node) - - let compare_globals g1 g2 = - let eq, le, gr, uk = 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 - let f_uk () = incr uk in - let f k v1 = - let v2 = try GH.find g2 k with Not_found -> G.bot () in - let b1 = G.leq v1 v2 in - let b2 = G.leq v2 v1 in - if b1 && b2 then - f_eq () - else if b1 then begin - if get_bool "dbg.compare_runs.diff" then - Logs.info "Global %a is more precise using left:\n%a" 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 - Logs.info "Global %a is more precise using right:\n%a" Sys.GVar.pretty_trace k G.pretty_diff (v1,v2); - f_gr () - end else begin - if get_bool "dbg.compare_runs.diff" then ( - Logs.info "Global %a is incomparable (diff):\n%a" Sys.GVar.pretty_trace k G.pretty_diff (v1,v2); - Logs.info "Global %a is incomparable (reverse diff):\n%a" Sys.GVar.pretty_trace k G.pretty_diff (v2,v1); - ); - f_uk () - end - in - GH.iter f g1; - Logs.info "globals:\tequal = %d\tleft = %d\tright = %d\tincomparable = %d" !eq !le !gr !uk - - let compare_locals h1 h2 = - let eq, le, gr, uk = ref 0, ref 0, ref 0, ref 0 in - let f k v1 = - if PP.mem h2 k then - let v2 = PP.find h2 k in - let b1 = D.leq v1 v2 in - let b2 = D.leq v2 v1 in - if b1 && b2 then - incr eq - else if b1 then begin - if get_bool "dbg.compare_runs.diff" then - Logs.info "%a @@ %a is more precise using left:\n%a" 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 - Logs.info "%a @@ %a is more precise using right:\n%a" Node.pretty_plain k CilType.Location.pretty (Node.location k) D.pretty_diff (v1,v2); - incr gr - end else begin - if get_bool "dbg.compare_runs.diff" then ( - Logs.info "%a @@ %a is incomparable (diff):\n%a" Node.pretty_plain k CilType.Location.pretty (Node.location k) D.pretty_diff (v1,v2); - Logs.info "%a @@ %a is incomparable (reverse diff):\n%a" 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 - 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 - Logs.info "locals: \tequal = %d\tleft = %d[%d]\tright = %d[%d]\tincomparable = %d" !eq !le o1 !gr o2 !uk *) - Logs.info "locals: \tequal = %d\tleft = %d\tright = %d\tincomparable = %d" !eq !le !gr !uk - - let compare_locals_ctx h1 h2 = - 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 - let f_uk () = incr uk in - let f k v1 = - if not (LH.mem h2 k) then incr no2 else - let v2 = LH.find h2 k in - let b1 = D.leq v1 v2 in - let b2 = D.leq v2 v1 in - if b1 && b2 then - f_eq () - else if b1 then begin - if get_bool "dbg.compare_runs.diff" then - Logs.info "%a is more precise using left:\n%a" 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 - Logs.info "%a is more precise using right:\n%a" Sys.LVar.pretty_trace k D.pretty_diff (v1,v2); - f_gr () - end else begin - if get_bool "dbg.compare_runs.diff" then ( - Logs.info "%a is incomparable (diff):\n%a" Sys.LVar.pretty_trace k D.pretty_diff (v1,v2); - Logs.info "%a is incomparable (reverse diff):\n%a" 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 *) - Logs.info "locals_ctx:\tequal = %d\tleft = %d\tright = %d\tincomparable = %d\tno_ctx_in_right = %d\tno_ctx_in_left = %d" !eq !le !gr !uk !no2 !no1 - - let compare (name1,name2) (l1,g1) (l2,g2) = - let one_ctx (n,_) v h = - PP.replace h n (try D.join v (PP.find h n) with Not_found -> v); - h - in - (* these contain results where the contexts per node have been joined *) - let h1 = PP.create 113 in - let h2 = PP.create 113 in - let _ = LH.fold one_ctx l1 h1 in - let _ = LH.fold one_ctx l2 h2 in - Logs.newline (); - Logs.info "Comparing GlobConstrSys precision of %s (left) with %s (right):" name1 name2; - compare_globals g1 g2; - compare_locals h1 h2; - compare_locals_ctx l1 l2; - Logs.newline (); -end - -module CompareHashtbl (Var: VarType) (Dom: Lattice.S) (VH: Hashtbl.S with type key = Var.t) = -struct - module Var = - struct - include Printable.Std - include Var - let name () = "var" - - let pretty = pretty_trace - include Printable.SimplePretty ( - struct - type nonrec t = t - let pretty = pretty - end - ) - end - - include PrecCompare.MakeHashtbl (Var) (Dom) (VH) -end - -module CompareEqSys (Sys: EqConstrSys) (VH: Hashtbl.S with type key = Sys.Var.t) = -struct - module Compare = CompareHashtbl (Sys.Var) (Sys.Dom) (VH) - - let compare (name1, name2) vh1 vh2 = - Logs.newline (); - Logs.info "Comparing EqConstrSys precision of %s (left) with %s (right):" name1 name2; - let verbose = get_bool "dbg.compare_runs.diff" in - let (_, msg) = Compare.compare ~verbose ~name1 vh1 ~name2 vh2 in - Logs.info "EqConstrSys comparison summary: %t" (fun () -> msg); - Logs.newline (); -end - -module CompareGlobal (GVar: VarType) (G: Lattice.S) (GH: Hashtbl.S with type key = GVar.t) = -struct - module Compare = CompareHashtbl (GVar) (G) (GH) - - let compare (name1, name2) vh1 vh2 = - Logs.newline (); - Logs.info "Comparing globals precision of %s (left) with %s (right):" name1 name2; - let verbose = get_bool "dbg.compare_runs.diff" in - let (_, msg) = Compare.compare ~verbose ~name1 vh1 ~name2 vh2 in - Logs.info "Globals comparison summary: %t" (fun () -> msg); - Logs.newline (); -end - -module CompareNode (C: Printable.S) (D: Lattice.S) (LH: Hashtbl.S with type key = VarF (C).t) = -struct - module Node = - struct - include Node - let var_id _ = "nodes" - let node x = x - let is_write_only _ = false - end - module NH = Hashtbl.Make (Node) - - module Compare = CompareHashtbl (Node) (D) (NH) - - let join_contexts (lh: D.t LH.t): D.t NH.t = - let nh = NH.create 113 in - LH.iter (fun (n, _) d -> - let d' = try D.join (NH.find nh n) d with Not_found -> d in - NH.replace nh n d' - ) lh; - nh - - let compare (name1, name2) vh1 vh2 = - Logs.newline (); - Logs.info "Comparing nodes precision of %s (left) with %s (right):" name1 name2; - let vh1' = join_contexts vh1 in - let vh2' = join_contexts vh2 in - let verbose = get_bool "dbg.compare_runs.diff" in - let (_, msg) = Compare.compare ~verbose ~name1 vh1' ~name2 vh2' in - Logs.info "Nodes comparison summary: %t" (fun () -> msg); - Logs.newline (); -end diff --git a/src/framework/control.ml b/src/framework/control.ml index 714a83b236..2566939817 100644 --- a/src/framework/control.ml +++ b/src/framework/control.ml @@ -9,13 +9,14 @@ open Analyses open ConstrSys open GobConfig open Constraints +open SpecLifters -module type S2S = functor (X : Spec) -> Spec +module type S2S = Spec2Spec (* spec is lazy, so HConsed table in Hashcons lifters is preserved between analyses in server mode *) let spec_module: (module Spec) Lazy.t = lazy ( GobConfig.building_spec := true; - let arg_enabled = get_bool "witness.graphml.enabled" || get_bool "exp.arg" in + let arg_enabled = get_bool "witness.graphml.enabled" || get_bool "exp.arg.enabled" in let termination_enabled = List.mem "termination" (get_string_list "ana.activated") in (* check if loop termination analysis is enabled*) let open Batteries in (* apply functor F on module X if opt is true *) @@ -23,7 +24,7 @@ let spec_module: (module Spec) Lazy.t = lazy ( let module S1 = (val (module MCP.MCP2 : Spec) - |> lift (get_int "ana.context.gas_value" >= 0) (module ContextGasLifter) + |> lift (get_int "ana.context.gas_value" >= 0) (ContextGasLifter.get_gas_lifter ()) |> lift true (module WidenContextLifterSide) (* option checked in functor *) (* hashcons before witness to reduce duplicates, because witness re-uses contexts in domain and requires tag for PathSensitive3 *) |> lift (get_bool "ana.opt.hashcons" || arg_enabled) (module HashconsContextLifter) @@ -38,9 +39,9 @@ let spec_module: (module Spec) Lazy.t = lazy ( |> lift (get_bool "ana.opt.hashcons") (module HashconsLifter) (* Widening tokens must be outside of hashcons, because widening token domain ignores token sets for identity, so hashcons doesn't allow adding tokens. Also must be outside of deadcode, because deadcode splits (like mutex lock event) don't pass on tokens. *) - |> lift (get_bool "ana.widen.tokens") (module WideningTokens.Lifter) - |> lift true (module LongjmpLifter) - |> lift termination_enabled (module RecursionTermLifter) (* Always activate the recursion termination analysis, when the loop termination analysis is activated*) + |> lift (get_bool "ana.widen.tokens") (module WideningTokenLifter.Lifter) + |> lift true (module LongjmpLifter.Lifter) + |> lift termination_enabled (module RecursionTermLifter.Lifter) (* Always activate the recursion termination analysis, when the loop termination analysis is activated*) ) in GobConfig.building_spec := false; @@ -90,7 +91,7 @@ struct end module Slvr = (GlobSolverFromEqSolver (Goblint_solver.Selector.Make (PostSolverArg))) (EQSys) (LHT) (GHT) (* The comparator *) - module CompareGlobSys = Constraints.CompareGlobSys (SpecSys) + module CompareGlobSys = CompareConstraints.CompareGlobSys (SpecSys) (* Triple of the function, context, and the local value. *) module RT = AnalysisResult.ResultType2 (Spec) @@ -357,6 +358,7 @@ struct (* real beginning of the [analyze] function *) if get_bool "ana.sv-comp.enabled" then Witness.init (module FileCfg); (* TODO: move this out of analyze_loop *) + YamlWitness.init (); AnalysisState.global_initialization := true; GobConfig.earlyglobs := get_bool "exp.earlyglobs"; @@ -404,8 +406,8 @@ struct ; emit = (fun _ -> failwith "Cannot \"emit\" in enter_with context.") ; node = MyCFG.dummy_node ; prev_node = MyCFG.dummy_node - ; control_context = (fun () -> ctx_failwith "enter_func has no context.") - ; context = (fun () -> ctx_failwith "enter_func has no context.") + ; control_context = (fun () -> ctx_failwith "enter_with has no control_context.") + ; context = Spec.startcontext ; edge = MyCFG.Skip ; local = st ; global = (fun g -> EQSys.G.spec (getg (EQSys.GVar.spec g))) @@ -458,14 +460,29 @@ struct AnalysisState.global_initialization := false; + let ctx e = + { ask = (fun (type a) (q: a Queries.t) -> Queries.Result.top q) + ; emit = (fun _ -> failwith "Cannot \"emit\" in enter_with context.") + ; node = MyCFG.dummy_node + ; prev_node = MyCFG.dummy_node + ; control_context = (fun () -> ctx_failwith "enter_with has no control_context.") + ; context = Spec.startcontext + ; edge = MyCFG.Skip + ; local = e + ; global = (fun g -> EQSys.G.spec (getg (EQSys.GVar.spec g))) + ; spawn = (fun ?(multiple=false) _ -> failwith "Bug1: Using enter_func for toplevel functions with 'otherstate'.") + ; split = (fun _ -> failwith "Bug2: Using enter_func for toplevel functions with 'otherstate'.") + ; sideg = (fun g d -> sideg (EQSys.GVar.spec g) (EQSys.G.create_spec d)) + } + in let startvars' = if get_bool "exp.forward" then - List.map (fun (n,e) -> (MyCFG.FunctionEntry n, Spec.context n e)) startvars + List.map (fun (n,e) -> (MyCFG.FunctionEntry n, Spec.context (ctx e) n e)) startvars else - List.map (fun (n,e) -> (MyCFG.Function n, Spec.context n e)) startvars + List.map (fun (n,e) -> (MyCFG.Function n, Spec.context (ctx e) n e)) startvars in - let entrystates = List.map (fun (n,e) -> (MyCFG.FunctionEntry n, Spec.context n e), e) startvars in + let entrystates = List.map (fun (n,e) -> (MyCFG.FunctionEntry n, Spec.context (ctx e) n e), e) startvars in let entrystates_global = GHT.to_list gh in let uncalled_dead = ref 0 in @@ -505,15 +522,15 @@ struct if get_bool "dbg.compare_runs.globsys" then CompareGlobSys.compare (d1, d2) r1 r2; - let module CompareEqSys = Constraints.CompareEqSys (S2) (VH) in + let module CompareEqSys = CompareConstraints.CompareEqSys (S2) (VH) in if get_bool "dbg.compare_runs.eqsys" then CompareEqSys.compare (d1, d2) r1' r2'; - let module CompareGlobal = Constraints.CompareGlobal (EQSys.GVar) (EQSys.G) (GHT) in + let module CompareGlobal = CompareConstraints.CompareGlobal (EQSys.GVar) (EQSys.G) (GHT) in if get_bool "dbg.compare_runs.global" then CompareGlobal.compare (d1, d2) (snd r1) (snd r2); - let module CompareNode = Constraints.CompareNode (Spec.C) (EQSys.D) (LHT) in + let module CompareNode = CompareConstraints.CompareNode (Spec.C) (EQSys.D) (LHT) in if get_bool "dbg.compare_runs.node" then CompareNode.compare (d1, d2) (fst r1) (fst r2); @@ -745,12 +762,23 @@ struct in Timing.wrap "warn_global" (GHT.iter warn_global) gh; - if get_bool "exp.arg" then ( + if get_bool "exp.arg.enabled" then ( let module ArgTool = ArgTools.Make (R) in let module Arg = (val ArgTool.create entrystates) in - if get_bool "exp.argdot" then ( - let module ArgDot = ArgTools.Dot (Arg) in - let oc = Batteries.open_out "arg.dot" in + let arg_dot_path = get_string "exp.arg.dot.path" in + if arg_dot_path <> "" then ( + let module NoLabelNodeStyle = + struct + type node = Arg.Node.t + let extra_node_styles node = + match GobConfig.get_string "exp.arg.dot.node-label" with + | "node" -> [] + | "empty" -> ["label=\"_\""] (* can't have empty string because graph-easy will default to node ID then... *) + | _ -> assert false + end + in + let module ArgDot = ArgTools.Dot (Arg) (NoLabelNodeStyle) in + let oc = Batteries.open_out arg_dot_path in Fun.protect (fun () -> let ppf = Format.formatter_of_out_channel oc in ArgDot.dot ppf; @@ -763,15 +791,19 @@ struct ); (* Before SV-COMP, so result can depend on YAML witness validation. *) - if get_string "witness.yaml.validate" <> "" then ( - let module YWitness = YamlWitness.Validator (R) in - YWitness.validate () - ); + let yaml_validate_result = + if get_string "witness.yaml.validate" <> "" then ( + let module YWitness = YamlWitness.Validator (R) in + Some (YWitness.validate ()) + ) + else + None + in if get_bool "ana.sv-comp.enabled" then ( (* SV-COMP and witness generation *) let module WResult = Witness.Result (R) in - WResult.write entrystates + WResult.write yaml_validate_result entrystates ); if get_bool "witness.yaml.enabled" then ( diff --git a/src/goblint.ml b/src/goblint.ml index 24afae1597..2a0ab3ce0f 100644 --- a/src/goblint.ml +++ b/src/goblint.ml @@ -5,8 +5,8 @@ open Maingoblint (** the main function *) let main () = try - Cilfacade.init (); Maingoblint.parse_arguments (); + Cilfacade.init (); (* Timing. *) Maingoblint.reset_stats (); @@ -36,7 +36,8 @@ let main () = Logs.debug "%s" (GobUnix.localtime ()); Logs.debug "%s" GobSys.command_line; (* When analyzing a termination specification, activate the termination analysis before pre-processing. *) - if get_bool "ana.autotune.enabled" && AutoTune.specificationTerminationIsActivated () then AutoTune.focusOnTermination (); + if get_string "ana.specification" <> "" then AutoSoundConfig.enableAnalysesForTerminationSpecification (); + if AutoTune.isActivated "termination" then AutoTune.focusOnTermination (); let file = lazy (Fun.protect ~finally:GoblintDir.finalize preprocess_parse_merge) in if get_bool "server.enabled" then ( let file = @@ -55,14 +56,16 @@ let main () = else None in - (* This is run independant of the autotuner being enabled or not be sound for programs with longjmp *) - AutoTune.activateLongjmpAnalysesWhenRequired (); + (* This is run independant of the autotuner being enabled or not to be sound for programs with longjmp *) + AutoSoundConfig.activateLongjmpAnalysesWhenRequired (); + if get_string "ana.specification" <> "" then AutoSoundConfig.enableAnalysesForSpecification (); if get_bool "ana.autotune.enabled" then AutoTune.chooseConfig file; file |> do_analyze changeInfo; do_html_output (); do_gobview file; do_stats (); Goblint_timing.teardown_tef (); + (* TODO: generalize exit codes for AnalysisState.unsound_both_branches_dead? *) if !AnalysisState.verified = Some false then exit 3 (* verifier failed! *) ) with @@ -81,6 +84,11 @@ let main () = Logs.error "%s" (MessageUtil.colorize ~fd:Unix.stderr ("{RED}Analysis was aborted because it reached the set timeout of " ^ get_string "dbg.timeout" ^ " or was signalled SIGPROF!")); Goblint_timing.teardown_tef (); exit 124 + | Svcomp.Error msg -> + do_stats (); + Witness.print_svcomp_result ("ERROR (" ^ msg ^ ")"); + Goblint_timing.teardown_tef (); + exit 1 (* We do this since the evaluation order of top-level bindings is not defined, but we want `main` to run after all the other side-effects (e.g. registering analyses/solvers) have happened. *) let () = at_exit main diff --git a/src/goblint_lib.ml b/src/goblint_lib.ml index 32e6fa0dbf..415fb21605 100644 --- a/src/goblint_lib.ml +++ b/src/goblint_lib.ml @@ -23,6 +23,7 @@ module CfgTools = CfgTools module Analyses = Analyses module ConstrSys = ConstrSys module Constraints = Constraints +module CompareConstraints = CompareConstraints module AnalysisState = AnalysisState module AnalysisStateUtil = AnalysisStateUtil module ControlSpecC = ControlSpecC @@ -59,6 +60,7 @@ module GobConfig = GobConfig module AfterConfig = AfterConfig module AutoTune = AutoTune +module AutoSoundConfig = AutoSoundConfig module JsonSchema = JsonSchema module Options = Options @@ -107,6 +109,7 @@ module MutexAnalysis = MutexAnalysis module MayLocks = MayLocks module SymbLocks = SymbLocks module Deadlock = Deadlock +module MutexGhosts = MutexGhosts (** {3 Threads} @@ -170,6 +173,20 @@ module AbortUnless = AbortUnless module PtranalAnalysis = PtranalAnalysis +(** {1 Analysis lifters} + + Transformations of analyses into extended analyses. *) + +module SpecLifters = SpecLifters +module LongjmpLifter = LongjmpLifter +module RecursionTermLifter = RecursionTermLifter +module ContextGasLifter = ContextGasLifter +module WideningToken = WideningToken +module WideningTokenLifter = WideningTokenLifter + +module WitnessConstraints = WitnessConstraints + + (** {1 Domains} Domains used by analysis specifications and constraint systems are {{!Lattice.S} lattices}. @@ -325,7 +342,6 @@ module WitnessUtil = WitnessUtil Automaton-based GraphML witnesses used in SV-COMP. *) module MyARG = MyARG -module WitnessConstraints = WitnessConstraints module ArgTools = ArgTools module Witness = Witness module Graphml = Graphml @@ -336,7 +352,7 @@ module Graphml = Graphml module YamlWitness = YamlWitness module YamlWitnessType = YamlWitnessType -module WideningTokens = WideningTokens +module WitnessGhost = WitnessGhost (** {3 Violation} diff --git a/src/incremental/compareAST.ml b/src/incremental/compareAST.ml index f3de153658..59adfe00be 100644 --- a/src/incremental/compareAST.ml +++ b/src/incremental/compareAST.ml @@ -73,7 +73,7 @@ let forward_list_equal ?(propF = (&&>>)) f l1 l2 ~(rename_mapping: rename_mappin let compare_name (a: string) (b: string) = let anon_struct = "__anonstruct_" in let anon_union = "__anonunion_" in - if a = b then true else BatString.(starts_with a anon_struct && starts_with b anon_struct || starts_with a anon_union && starts_with b anon_union) + if a = b then true else String.(starts_with a ~prefix:anon_struct && starts_with b ~prefix:anon_struct || starts_with a ~prefix:anon_union && starts_with b ~prefix:anon_union) let rec eq_constant ~(rename_mapping: rename_mapping) ~(acc: (typ * typ) list) (a: constant) (b: constant) : bool * rename_mapping = match a, b with @@ -91,7 +91,7 @@ and eq_exp (a: exp) (b: exp) ~(rename_mapping: rename_mapping) ~(acc: (typ * typ | AlignOf typ1, AlignOf typ2 -> eq_typ_acc typ1 typ2 ~rename_mapping ~acc | AlignOfE exp1, AlignOfE exp2 -> eq_exp exp1 exp2 ~rename_mapping ~acc | UnOp (op1, exp1, typ1), UnOp (op2, exp2, typ2) -> - ((op1 == op2), rename_mapping) &&>> eq_exp exp1 exp2 ~acc &&>> eq_typ_acc typ1 typ2 ~acc + (CilType.Unop.equal op1 op2, rename_mapping) &&>> eq_exp exp1 exp2 ~acc &&>> eq_typ_acc typ1 typ2 ~acc | BinOp (op1, left1, right1, typ1), BinOp (op2, left2, right2, typ2) -> (op1 = op2, rename_mapping) &&>> eq_exp left1 left2 ~acc &&>> eq_exp right1 right2 ~acc &&>> eq_typ_acc typ1 typ2 ~acc | CastE (typ1, exp1), CastE (typ2, exp2) -> eq_typ_acc typ1 typ2 ~rename_mapping ~acc &&>> eq_exp exp1 exp2 ~acc | AddrOf lv1, AddrOf lv2 -> eq_lval lv1 lv2 ~rename_mapping ~acc @@ -178,8 +178,8 @@ and eq_typ_acc ?(fun_parameter_name_comparison_enabled: bool = true) (a: typ) (b if Messages.tracing then Messages.traceu "compareast" "eq_typ_acc %a vs %a" d_type a d_type b; (r, updated_rename_mapping) -and eq_eitems (a: string * exp * location) (b: string * exp * location) ~(rename_mapping: rename_mapping) ~(acc: (typ * typ) list) = match a, b with - (name1, exp1, _l1), (name2, exp2, _l2) -> (name1 = name2, rename_mapping) &&>> eq_exp exp1 exp2 ~acc +and eq_eitems (a: string * attributes * exp * location) (b: string * attributes * exp * location) ~(rename_mapping: rename_mapping) ~(acc: (typ * typ) list) = match a, b with + (name1, attr1, exp1, _l1), (name2, attr2, exp2, _l2) -> (name1 = name2, rename_mapping) &&>> forward_list_equal (eq_attribute ~acc) attr1 attr2 &&>> eq_exp exp1 exp2 ~acc (* Ignore location *) and eq_enuminfo (a: enuminfo) (b: enuminfo) ~(rename_mapping: rename_mapping) ~(acc: (typ * typ) list) = @@ -210,6 +210,7 @@ and eq_attrparam (a: attrparam) (b: attrparam) ~(rename_mapping: rename_mapping) | AAddrOf attrparam1, AAddrOf attrparam2 -> eq_attrparam attrparam1 attrparam2 ~rename_mapping ~acc | AIndex (left1, right1), AIndex (left2, right2) -> eq_attrparam left1 left2 ~rename_mapping ~acc &&>> eq_attrparam right1 right2 ~acc | AQuestion (left1, middle1, right1), AQuestion (left2, middle2, right2) -> eq_attrparam left1 left2 ~rename_mapping ~acc &&>> eq_attrparam middle1 middle2 ~acc &&>> eq_attrparam right1 right2 ~acc + | AAssign (left1, right1), AAssign (left2, right2) -> eq_attrparam left1 left2 ~rename_mapping ~acc &&>> eq_attrparam right1 right2 ~acc | a, b -> a = b, rename_mapping and eq_attribute (a: attribute) (b: attribute) ~(acc: (typ * typ) list) ~(rename_mapping: rename_mapping) = match a, b with diff --git a/src/incremental/compareCFG.ml b/src/incremental/compareCFG.ml index 84b120b8e3..a663b80833 100644 --- a/src/incremental/compareCFG.ml +++ b/src/incremental/compareCFG.ml @@ -18,7 +18,7 @@ let (&&<>) (prev_result: bool * rename_mapping) f : bool * rename_mapping = let eq_node (x, fun1) (y, fun2) ~rename_mapping = let isPseudoReturn f sid = let pid = Cilfacade.get_pseudo_return_id f in - sid == pid in + sid = pid in match x,y with | Statement s1, Statement s2 -> let p1 = isPseudoReturn fun1 s1.sid in @@ -131,7 +131,7 @@ let reexamine f1 f2 (same : biDirectionNodeMap) (diffNodes1 : unit NH.t) (module false end in let cond n2 = Node.equal n2 (FunctionEntry f2) || check_all_nodes_in_same (List.map snd (CfgNew.prev n2)) n2 in - let forall = NH.fold (fun n2 n1 acc -> acc && cond n2) same.node2to1 true in + let forall = NH.fold (fun n2 n1 acc -> acc && cond n2) same.node2to1 true in (* nosemgrep: fold-for_all *) (* cond does side effects *) if not forall then repeat () in repeat (); NH.to_seq same.node1to2, NH.to_seq_keys diffNodes1 diff --git a/src/incremental/compareCIL.ml b/src/incremental/compareCIL.ml index ea22e02a56..837bd65589 100644 --- a/src/incremental/compareCIL.ml +++ b/src/incremental/compareCIL.ml @@ -19,7 +19,7 @@ let name_of_global_col gc = match gc.def with | None -> raise (Failure "empty global record") let compare_global_col gc1 gc2 = compare (name_of_global_col gc1) (name_of_global_col gc2) -let equal_name_global_col gc1 gc2 = compare_global_col gc1 gc2 == 0 +let equal_name_global_col gc1 gc2 = compare_global_col gc1 gc2 = 0 let get_varinfo gc = match gc.decls, gc.def with | _, Some (Var v) -> v diff --git a/src/incremental/makefileUtil.ml b/src/incremental/makefileUtil.ml index 9a17122f5e..33cd8f64c9 100644 --- a/src/incremental/makefileUtil.ml +++ b/src/incremental/makefileUtil.ml @@ -39,7 +39,7 @@ let find_file_by_suffix (dir: Fpath.t) (file_name_suffix: string) = | (h::t) -> let f = Fpath.to_string h in if Sys.file_exists f && Sys.is_directory f then (Queue.add h dirs; search dir t) - else if Batteries.String.ends_with (Fpath.filename h) file_name_suffix then h else search dir t + else if String.ends_with (Fpath.filename h) ~suffix:file_name_suffix then h else search dir t | [] -> if Queue.is_empty dirs then failwith ("find_file_by_suffix found no files with suffix "^file_name_suffix^" in "^ Fpath.to_string dir) else let d = Queue.take dirs in search d (list_files d) diff --git a/src/index.mld b/src/index.mld index f0d63a0fc7..a2ef15482e 100644 --- a/src/index.mld +++ b/src/index.mld @@ -44,15 +44,9 @@ The following libraries provide [goblint] package metadata for executables. {2 Library goblint.build-info} {!modules:Goblint_build_info} -This library is virtual and has the following implementations -- goblint.build-info.dune for native executables, -- goblint.build-info.js for js_of_ocaml executables. {2 Library goblint.sites} {!modules:Goblint_sites} -This library is virtual and has the following implementations -- goblint.sites.dune for native executables, -- goblint.sites.js for js_of_ocaml executables. {1 Independent utilities} diff --git a/src/lifters/contextGasLifter.ml b/src/lifters/contextGasLifter.ml new file mode 100644 index 0000000000..75bd9f7641 --- /dev/null +++ b/src/lifters/contextGasLifter.ml @@ -0,0 +1,142 @@ +(** Lifts a [Spec] with the context gas variable. The gas variable limits the number of context-sensitively analyzed function calls in a call stack. + For every function call the gas is reduced. If the gas is zero, the remaining function calls are analyzed without context-information *) + +open Batteries +open GoblintCil +open Analyses +open GobConfig + +module M = Messages + +module NoContext = struct let name = "no context" end + +module type Gas = sig + module M:Lattice.S + val startgas: unit -> M.t + val is_exhausted: fundec -> M.t -> bool + val callee_gas: fundec -> M.t -> M.t + val thread_gas: varinfo -> M.t -> M.t +end + +module ContextGasLifter (Gas:Gas) (S:Spec) + : Spec with module D = Lattice.Prod (S.D) (Gas.M) + and module C = Printable.Option (S.C) (NoContext) + and module G = S.G += +struct + include S + + module Context_Gas_Prod (Base1: Lattice.S) (Base2: Lattice.S) = + struct + include Lattice.Prod (Base1) (Base2) + let printXml f (x,y) = + BatPrintf.fprintf f "\n%a\n%a\n" Base1.printXml x Base2.printXml y + end + module D = Context_Gas_Prod (S.D) (Gas.M) (* Product of S.D and a value from the gas module, tracking the context gas value *) + module C = Printable.Option (S.C) (NoContext) + module G = S.G + module V = S.V + module P = + struct + include S.P + let of_elt (x, _) = of_elt x + end + + (* returns context gas value of the given ctx *) + let cg_val ctx = snd ctx.local + + type marshal = S.marshal + let init = S.init + let finalize = S.finalize + + + let startcontext () = Some (S.startcontext ()) + let name () = S.name ()^" with context gas" + let startstate v = S.startstate v, Gas.startgas () + let exitstate v = S.exitstate v, Gas.startgas () + let morphstate v (d,i) = S.morphstate v d, i + + let conv (ctx:(D.t,G.t,C.t,V.t) ctx): (S.D.t,G.t,S.C.t,V.t)ctx = + {ctx with local = fst ctx.local + ; split = (fun d es -> ctx.split (d, cg_val ctx) es) + ; context = (fun () -> match ctx.context () with Some c -> c | None -> ctx_failwith "no context (contextGas = 0)")} + + let context ctx fd (d,i) = + (* only keep context if the context gas is greater zero *) + if Gas.is_exhausted fd i then + None + else + Some (S.context (conv ctx) fd d) + + let enter ctx r f args = + let liftmap_tup = List.map (fun (x,y) -> (x, cg_val ctx), (y, Gas.callee_gas f (cg_val ctx))) in + liftmap_tup (S.enter (conv ctx) r f args) + + let threadenter ctx ~multiple lval f args = + let liftmap d = List.map (fun (x) -> (x, Gas.thread_gas f (cg_val ctx))) d in + liftmap (S.threadenter (conv ctx) ~multiple lval f args) + + let query ctx (type a) (q: a Queries.t):a Queries.result = + match q with + | Queries.GasExhausted f -> + let (d,i) = ctx.local in + Gas.is_exhausted f i + | _ -> S.query (conv ctx) q + + let sync ctx reason = S.sync (conv ctx) reason, cg_val ctx + let assign ctx lval expr = S.assign (conv ctx) lval expr, cg_val ctx + let vdecl ctx v = S.vdecl (conv ctx) v, cg_val ctx + let body ctx fundec = S.body (conv ctx) fundec, cg_val ctx + let branch ctx e tv = S.branch (conv ctx) e tv, cg_val ctx + let return ctx r f = S.return (conv ctx) r f, cg_val ctx + let asm ctx = S.asm (conv ctx), cg_val ctx + let skip ctx = S.skip (conv ctx), cg_val ctx + let special ctx r f args = S.special (conv ctx) r f args, cg_val ctx + let combine_env ctx r fe f args fc es f_ask = S.combine_env (conv ctx) r fe f args (Option.bind fc Fun.id) (fst es) f_ask, cg_val ctx + let combine_assign ctx r fe f args fc es f_ask = S.combine_assign (conv ctx) r fe f args (Option.bind fc Fun.id) (fst es) f_ask, cg_val ctx + let paths_as_set ctx = List.map (fun (x) -> (x, cg_val ctx)) @@ S.paths_as_set (conv ctx) + let threadspawn ctx ~multiple lval f args fctx = S.threadspawn (conv ctx) ~multiple lval f args (conv fctx), cg_val ctx + let event ctx e octx = S.event (conv ctx) e (conv octx), cg_val ctx +end + +let get_gas_lifter () = + let module GasChain = Lattice.Chain (struct + (* Chain lattice has elements [0,n-1], but we want [0,gas_value] *) + let n () = get_int "ana.context.gas_value" + 1 + let names x = Format.asprintf "%d" x + end) + in + if get_string "ana.context.gas_scope" = "global" then + let module GlobalGas:Gas = struct + module M = GasChain + let startgas () = M.top () (* M.top () yields maximal gas value *) + + let is_exhausted _ v = v <= 0 + + (* callee gas = caller gas - 1 *) + let callee_gas f v = max 0 (v - 1) + let thread_gas f v = max 0 (v - 1) + end + in + (module ContextGasLifter(GlobalGas):Spec2Spec) + else + let module PerFunctionGas:Gas = struct + (* The order is reversed here to ensure that the minimum is used *) + (* 5 join 4 = 4 *) + module G = Lattice.Reverse(GasChain) + (* Missing bindings are bot, i.e., have maximal gas for this function *) + module M = MapDomain.MapBot_LiftTop(CilType.Fundec)(G) + let startgas () = M.empty () + let is_exhausted f v = GobOption.exists (fun g -> g <= 0) (M.find_opt f v) (* v <= 0 *) + let callee_gas f v = + let c = Option.default (G.bot ()) (M.find_opt f v) in + M.add f (max 0 c-1) v + let thread_gas f v = + match Cilfacade.find_varinfo_fundec f with + | fd -> + callee_gas fd v + | exception Not_found -> + callee_gas Cil.dummyFunDec v + end + in + (module ContextGasLifter(PerFunctionGas):Spec2Spec) diff --git a/src/lifters/contextGasLifter.mli b/src/lifters/contextGasLifter.mli new file mode 100644 index 0000000000..f5b07e2b35 --- /dev/null +++ b/src/lifters/contextGasLifter.mli @@ -0,0 +1,5 @@ +(** Lifts a [Spec] with the context gas variable. The gas variable limits the number of context-sensitively analyzed function calls in a call stack. + For every function call the gas is reduced. If the gas is zero, the remaining function calls are analyzed without context-information *) + +(** Gets the appropriate lifter (either local or per-function). Should only be called when context gas is active. *) +val get_gas_lifter : unit -> (module Analyses.Spec2Spec) diff --git a/src/lifters/longjmpLifter.ml b/src/lifters/longjmpLifter.ml new file mode 100644 index 0000000000..a093f8c703 --- /dev/null +++ b/src/lifters/longjmpLifter.ml @@ -0,0 +1,255 @@ +open GoblintCil +open Analyses +open GobConfig + + +module Lifter (S: Spec): Spec = +struct + include S + + let name () = "Longjmp (" ^ S.name () ^ ")" + + module V = + struct + include Printable.Either3Conf (struct let expand1 = false let expand2 = true let expand3 = true end) (S.V) (Printable.Prod (Node) (C)) (Printable.Prod (CilType.Fundec) (C)) + let name () = "longjmp" + let s x = `Left x + let longjmpto x = `Middle x + let longjmpret x = `Right x + let is_write_only = function + | `Left x -> S.V.is_write_only x + | _ -> false + end + + module G = + struct + include Lattice.Lift2 (S.G) (S.D) + + let s = function + | `Bot -> S.G.bot () + | `Lifted1 x -> x + | _ -> failwith "LongjmpLifter.s" + let local = function + | `Bot -> S.D.bot () + | `Lifted2 x -> x + | _ -> failwith "LongjmpLifter.local" + let create_s s = `Lifted1 s + let create_local local = `Lifted2 local + + let printXml f = function + | `Lifted1 x -> S.G.printXml f x + | `Lifted2 x -> BatPrintf.fprintf f "%a" S.D.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)) + | _ -> + Queries.Result.top q + end + | InvariantGlobal g -> + let g: V.t = Obj.obj g in + begin match g with + | `Left g -> + S.query (conv ctx) (InvariantGlobal (Obj.repr g)) + | _ -> + Queries.Result.top q + end + | YamlEntryGlobal (g, task) -> + let g: V.t = Obj.obj g in + begin match g with + | `Left g -> + S.query (conv ctx) (YamlEntryGlobal (Obj.repr g, task)) + | _ -> + Queries.Result.top q + 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')); + (* TODO: vars? *) + | _ -> + S.query (conv ctx) q + + + let branch ctx = S.branch (conv ctx) + let assign ctx = S.assign (conv ctx) + let vdecl ctx = S.vdecl (conv ctx) + let enter ctx = S.enter (conv ctx) + let paths_as_set ctx = S.paths_as_set (conv ctx) + let body ctx = S.body (conv ctx) + let return ctx = S.return (conv ctx) + let context ctx = S.context (conv ctx) + + let combine_env ctx lv e f args fc fd f_ask = + let conv_ctx = conv ctx in + let current_fundec = Node.find_fundec ctx.node in + let handle_longjmp (cd, fc, longfd) = + (* This is called per-path. *) + let rec cd_ctx = + { conv_ctx with + ask = (fun (type a) (q: a Queries.t) -> S.query cd_ctx q); + local = cd; + } + in + let longfd_ctx = + (* Inner scope to prevent unsynced longfd_ctx from being used. *) + (* Extra sync like with normal combine. *) + let rec sync_ctx = + { conv_ctx with + ask = (fun (type a) (q: a Queries.t) -> S.query sync_ctx q); + local = longfd; + prev_node = Function f; + } + in + let synced = S.sync sync_ctx `Join in + let rec longfd_ctx = + { sync_ctx with + ask = (fun (type a) (q: a Queries.t) -> S.query longfd_ctx q); + local = synced; + } + in + longfd_ctx + in + let combined = lazy ( (* does not depend on target, do at most once *) + (* Globals are non-problematic here, as they are always carried around without any issues! *) + (* A combine call is mostly needed to ensure locals have appropriate values. *) + (* Using f from called function on purpose here! Needed? *) + S.combine_env cd_ctx None e f args fc longfd_ctx.local (Analyses.ask_of_ctx longfd_ctx) (* no lval because longjmp return skips return value assignment *) + ) + in + let returned = lazy ( (* does not depend on target, do at most once *) + let rec combined_ctx = + { cd_ctx with + ask = (fun (type a) (q: a Queries.t) -> S.query combined_ctx q); + local = Lazy.force combined; + } + in + S.return combined_ctx None current_fundec + ) + in + let (active_targets, _) = longfd_ctx.ask ActiveJumpBuf in + let valid_targets = cd_ctx.ask ValidLongJmp in + let handle_target target = match target with + | JmpBufDomain.BufferEntryOrTop.AllTargets -> () (* The warning is already emitted at the point where the longjmp happens *) + | Target (target_node, target_context) -> + let target_fundec = Node.find_fundec target_node in + if CilType.Fundec.equal target_fundec current_fundec && ControlSpecC.equal target_context (ctx.control_context ()) then ( + if M.tracing then Messages.tracel "longjmp" "Fun: Potentially from same context, side-effect to %a" Node.pretty target_node; + ctx.sideg (V.longjmpto (target_node, ctx.context ())) (G.create_local (Lazy.force combined)) + (* No need to propagate this outwards here, the set of valid longjumps is part of the context, we can never have the same context setting the longjmp multiple times *) + ) + (* Appropriate setjmp is not in current function & current context *) + else if JmpBufDomain.JmpBufSet.mem target valid_targets then + ctx.sideg (V.longjmpret (current_fundec, ctx.context ())) (G.create_local (Lazy.force returned)) + else + (* It actually is not handled here but was propagated here spuriously, we already warned at the location where this issue is caused *) + (* As the validlongjumps inside the callee is a a superset of the ones inside the caller *) + () + in + JmpBufDomain.JmpBufSet.iter handle_target active_targets + in + if M.tracing then M.tracel "longjmp" "longfd getg %a" CilType.Fundec.pretty f; + let longfd = G.local (ctx.global (V.longjmpret (f, Option.get fc))) in + if M.tracing then M.tracel "longjmp" "longfd %a" D.pretty longfd; + if not (D.is_bot longfd) then + handle_longjmp (ctx.local, fc, longfd); + S.combine_env (conv_ctx) lv e f args fc fd f_ask + + let combine_assign ctx lv e f args fc fd f_ask = + S.combine_assign (conv ctx) lv e f args fc fd f_ask + + let special ctx lv f args = + let conv_ctx = conv ctx in + match (LibraryFunctions.find f).special args with + | Setjmp {env} -> + (* Handling of returning for the first time *) + let normal_return = S.special conv_ctx lv f args in + let jmp_return = G.local (ctx.global (V.longjmpto (ctx.prev_node, ctx.context ()))) in + if S.D.is_bot jmp_return then + normal_return + else ( + let rec jmp_ctx = + { conv_ctx with + ask = (fun (type a) (q: a Queries.t) -> S.query jmp_ctx q); + local = jmp_return; + } + in + let longjmped = S.event jmp_ctx (Events.Longjmped {lval=lv}) jmp_ctx in + S.D.join normal_return longjmped + ) + | Longjmp {env; value} -> + let current_fundec = Node.find_fundec ctx.node in + let handle_path path = ( + let rec path_ctx = + { conv_ctx with + ask = (fun (type a) (q: a Queries.t) -> S.query path_ctx q); + local = path; + } + in + let specialed = lazy ( (* does not depend on target, do at most once *) + S.special path_ctx lv f args + ) + in + let returned = lazy ( (* does not depend on target, do at most once *) + let rec specialed_ctx = + { path_ctx with + ask = (fun (type a) (q: a Queries.t) -> S.query specialed_ctx q); + local = Lazy.force specialed; + } + in + S.return specialed_ctx None current_fundec + ) + in + (* Eval `env` again to avoid having to construct bespoke ctx to ask *) + let targets = path_ctx.ask (EvalJumpBuf env) in + let valid_targets = path_ctx.ask ValidLongJmp in + if M.tracing then Messages.tracel "longjmp" "Jumping to %a" JmpBufDomain.JmpBufSet.pretty targets; + let handle_target target = match target with + | JmpBufDomain.BufferEntryOrTop.AllTargets -> + M.warn ~category:Imprecise "Longjmp to potentially invalid target, as contents of buffer %a may be unknown! (imprecision due to heap?)" d_exp env; + M.msg_final Error ~category:Unsound ~tags:[Category Imprecise; Category Call] "Longjmp to unknown target ignored" + | Target (target_node, target_context) -> + let target_fundec = Node.find_fundec target_node in + if CilType.Fundec.equal target_fundec current_fundec && ControlSpecC.equal target_context (ctx.control_context ()) then ( + if M.tracing then Messages.tracel "longjmp" "Potentially from same context, side-effect to %a" Node.pretty target_node; + ctx.sideg (V.longjmpto (target_node, ctx.context ())) (G.create_local (Lazy.force specialed)) + ) + else if JmpBufDomain.JmpBufSet.mem target valid_targets then ( + if M.tracing then Messages.tracel "longjmp" "Longjmp to somewhere else, side-effect to %i" (S.C.hash (ctx.context ())); + ctx.sideg (V.longjmpret (current_fundec, ctx.context ())) (G.create_local (Lazy.force returned)) + ) + else + M.warn ~category:(Behavior (Undefined Other)) "Longjmp to potentially invalid target! (Target %a in Function %a which may have already returned or is in a different thread)" Node.pretty target_node CilType.Fundec.pretty target_fundec + in + if JmpBufDomain.JmpBufSet.is_empty targets then + M.warn ~category:(Behavior (Undefined Other)) "Longjmp to potentially invalid target (%a is bot?!)" d_exp env + else + JmpBufDomain.JmpBufSet.iter handle_target targets + ) + in + List.iter handle_path (S.paths_as_set conv_ctx); + if !AnalysisState.should_warn && List.mem "termination" @@ get_string_list "ana.activated" then ( + AnalysisState.svcomp_may_not_terminate := true; + M.warn ~category:Termination "The program might not terminate! (Longjmp)" + ); + S.D.bot () + | _ -> S.special conv_ctx lv f args + let threadenter ctx = S.threadenter (conv ctx) + let threadspawn ctx ~multiple lv f args fctx = S.threadspawn (conv ctx) ~multiple 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) + let event ctx e octx = S.event (conv ctx) e (conv octx) +end diff --git a/src/lifters/longjmpLifter.mli b/src/lifters/longjmpLifter.mli new file mode 100644 index 0000000000..5fd74907bc --- /dev/null +++ b/src/lifters/longjmpLifter.mli @@ -0,0 +1,3 @@ +(** Analysis lifter for [longjmp] and [setjmp] support. *) + +module Lifter: Analyses.Spec2Spec diff --git a/src/lifters/recursionTermLifter.ml b/src/lifters/recursionTermLifter.ml new file mode 100644 index 0000000000..37522305b9 --- /dev/null +++ b/src/lifters/recursionTermLifter.ml @@ -0,0 +1,153 @@ +open GoblintCil +open Analyses + + +module Lifter (S: Spec) + : Spec with module D = S.D + and module C = S.C += +(* two global invariants: + - S.V -> S.G + Needed to store the previously built global invariants + - fundec * S.C -> (Set (fundec * S.C)) + The second global invariant maps from the callee fundec and context to a set of caller fundecs and contexts. + This structure therefore stores the context-sensitive call graph. + For example: + let the function f in context c call function g in context c'. + In the global invariant structure it would be stored like this: (g,c') -> {(f, c)} +*) + +struct + include S + + (* contains all the callee fundecs and contexts *) + module V = GVarFC(S.V)(S.C) + + (* Tuple containing the fundec and context of a caller *) + module Call = Printable.Prod (CilType.Fundec) (S.C) + + (* Set containing multiple caller tuples *) + module CallerSet = SetDomain.Make (Call) + + module G = + struct + include Lattice.Lift2 (G) (CallerSet) + + let spec = function + | `Bot -> G.bot () + | `Lifted1 x -> x + | _ -> failwith "RecursionTermLifter.spec" + + let callers = function + | `Bot -> CallerSet.bot () + | `Lifted2 x -> x + | _ -> failwith "RecursionTermLifter.callGraph" + + let create_spec spec = `Lifted1 spec + let create_singleton_caller caller = `Lifted2 (CallerSet.singleton caller) + + let printXml f = function + | `Lifted1 x -> G.printXml f x + | `Lifted2 x -> BatPrintf.fprintf f "%a" CallerSet.printXml x + | x -> BatPrintf.fprintf f "%a" printXml x + + end + + let name () = "RecursionTermLifter (" ^ S.name () ^ ")" + + let conv (ctx: (_, G.t, _, V.t) ctx): (_, S.G.t, _, S.V.t) ctx = + { ctx with + global = (fun v -> G.spec (ctx.global (V.spec v))); + sideg = (fun v g -> ctx.sideg (V.spec v) (G.create_spec g)); + } + + let cycleDetection ctx call = + let module LH = Hashtbl.Make (Printable.Prod (CilType.Fundec) (S.C)) in + let module LS = Set.Make (Printable.Prod (CilType.Fundec) (S.C)) in + (* find all cycles/SCCs *) + let global_visited_calls = LH.create 100 in + + (* DFS *) + let rec iter_call (path_visited_calls: LS.t) ((fundec, _) as call) = + if LS.mem call path_visited_calls then ( + AnalysisState.svcomp_may_not_terminate := true; (*set the indicator for a non-terminating program for the sv comp*) + (*Cycle found*) + let loc = M.Location.CilLocation fundec.svar.vdecl in + M.warn ~loc ~category:Termination "The program might not terminate! (Fundec %a is contained in a call graph cycle)" CilType.Fundec.pretty fundec) (* output a warning for non-termination*) + else if not (LH.mem global_visited_calls call) then begin + LH.replace global_visited_calls call (); + let new_path_visited_calls = LS.add call path_visited_calls in + let gvar = V.call call in + let callers = G.callers (ctx.global gvar) in + CallerSet.iter (fun to_call -> + iter_call new_path_visited_calls to_call + ) callers; + end + in + iter_call LS.empty call + + let query ctx (type a) (q: a Queries.t): a Queries.result = + match q with + | WarnGlobal v -> + (* check result of loop analysis *) + if not (ctx.ask Queries.MustTermAllLoops) then + AnalysisState.svcomp_may_not_terminate := true; + let v: V.t = Obj.obj v in + begin match v with + | `Left v' -> + S.query (conv ctx) (WarnGlobal (Obj.repr v')) + | `Right call -> cycleDetection ctx call (* Note: to make it more efficient, one could only execute the cycle detection in case the loop analysis returns true, because otherwise the program will probably not terminate anyway*) + end + | InvariantGlobal v -> + let v: V.t = Obj.obj v in + begin match v with + | `Left v -> + S.query (conv ctx) (InvariantGlobal (Obj.repr v)) + | `Right v -> + Queries.Result.top q + end + | YamlEntryGlobal (v, task) -> + let v: V.t = Obj.obj v in + begin match v with + | `Left v -> + S.query (conv ctx) (YamlEntryGlobal (Obj.repr v, task)) + | `Right v -> + Queries.Result.top q + end + | _ -> S.query (conv ctx) q + + let branch ctx = S.branch (conv ctx) + let assign ctx = S.assign (conv ctx) + let vdecl ctx = S.vdecl (conv ctx) + + + let record_call sideg callee caller = + sideg (V.call callee) (G.create_singleton_caller caller) + + let enter ctx = S.enter (conv ctx) + let context ctx = S.context (conv ctx) + let paths_as_set ctx = S.paths_as_set (conv ctx) + let body ctx = S.body (conv ctx) + let return ctx = S.return (conv ctx) + let combine_env ctx r fe f args fc es f_ask = + if !AnalysisState.postsolving then ( + let c_r: S.C.t = ctx.context () in (* Caller context *) + let nodeF = ctx.node in + let fd_r : fundec = Node.find_fundec nodeF in (* Caller fundec *) + let caller: (fundec * S.C.t) = (fd_r, c_r) in + let c_e: S.C.t = Option.get fc in (* Callee context *) + let fd_e : fundec = f in (* Callee fundec *) + let callee = (fd_e, c_e) in + record_call ctx.sideg callee caller + ); + S.combine_env (conv ctx) r fe f args fc es f_ask + + let combine_assign ctx = S.combine_assign (conv ctx) + let special ctx = S.special (conv ctx) + let threadenter ctx = S.threadenter (conv ctx) + let threadspawn ctx ~multiple lv f args fctx = S.threadspawn (conv ctx) ~multiple 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) + let event ctx e octx = S.event (conv ctx) e (conv octx) +end diff --git a/src/lifters/recursionTermLifter.mli b/src/lifters/recursionTermLifter.mli new file mode 100644 index 0000000000..059cea658f --- /dev/null +++ b/src/lifters/recursionTermLifter.mli @@ -0,0 +1,3 @@ +(** Cycle detection in the context-sensitive dynamic function call graph of an analysis. *) + +module Lifter: Analyses.Spec2Spec diff --git a/src/lifters/specLifters.ml b/src/lifters/specLifters.ml new file mode 100644 index 0000000000..39c7799395 --- /dev/null +++ b/src/lifters/specLifters.ml @@ -0,0 +1,799 @@ +(** Various simple and old analysis lifters. *) + +open Batteries +open GoblintCil +open Analyses +open GobConfig + + +(** Lifts a [Spec] so that the domain is [Hashcons]d *) +module HashconsLifter (S:Spec) + : Spec with module G = S.G + and module C = S.C += +struct + module HConsedArg = + struct + (* We do refine int values on narrow and meet {!IntDomain.IntDomTupleImpl}, which can lead to fixpoint issues if we assume x op x = x *) + (* see https://github.com/goblint/analyzer/issues/1005 *) + let assume_idempotent = GobConfig.get_string "ana.int.refinement" = "never" + end + module D = Lattice.HConsed (S.D) (HConsedArg) + module G = S.G + module C = S.C + module V = S.V + module P = + struct + include S.P + let of_elt x = of_elt (D.unlift x) + end + + let name () = S.name () ^" hashconsed" + + type marshal = S.marshal (* TODO: should hashcons table be in here to avoid relift altogether? *) + let init = S.init + let finalize = S.finalize + + let startstate v = D.lift (S.startstate v) + let exitstate v = D.lift (S.exitstate v) + let morphstate v d = D.lift (S.morphstate v (D.unlift d)) + + let conv ctx = + { ctx with local = D.unlift ctx.local + ; split = (fun d es -> ctx.split (D.lift d) es ) + } + + let context ctx fd = S.context (conv ctx) fd % D.unlift + let startcontext () = S.startcontext () + + let sync ctx reason = + D.lift @@ S.sync (conv ctx) reason + + let query ctx = + S.query (conv ctx) + + let assign ctx lv e = + D.lift @@ S.assign (conv ctx) lv e + + let vdecl ctx v = + D.lift @@ S.vdecl (conv ctx) v + + let branch ctx e tv = + D.lift @@ S.branch (conv ctx) e tv + + let body ctx f = + D.lift @@ S.body (conv ctx) f + + let return ctx r f = + D.lift @@ S.return (conv ctx) r f + + let asm ctx = + D.lift @@ S.asm (conv ctx) + + let skip ctx = + D.lift @@ S.skip (conv ctx) + + let enter ctx r f args = + List.map (fun (x,y) -> D.lift x, D.lift y) @@ S.enter (conv ctx) r f args + + let special ctx r f args = + D.lift @@ S.special (conv ctx) r f args + + let combine_env ctx r fe f args fc es f_ask = + D.lift @@ S.combine_env (conv ctx) r fe f args fc (D.unlift es) f_ask + + let combine_assign ctx r fe f args fc es f_ask = + D.lift @@ S.combine_assign (conv ctx) r fe f args fc (D.unlift es) f_ask + + let threadenter ctx ~multiple lval f args = + List.map D.lift @@ S.threadenter (conv ctx) ~multiple lval f args + + let threadspawn ctx ~multiple lval f args fctx = + D.lift @@ S.threadspawn (conv ctx) ~multiple lval f args (conv fctx) + + let paths_as_set ctx = + List.map (fun x -> D.lift x) @@ S.paths_as_set (conv ctx) + + let event ctx e octx = + D.lift @@ S.event (conv ctx) e (conv octx) +end + +(** Lifts a [Spec] so that the context is [Hashcons]d. *) +module HashconsContextLifter (S:Spec) + : Spec with module D = S.D + and module G = S.G + and module C = Printable.HConsed (S.C) += +struct + module D = S.D + module G = S.G + module C = Printable.HConsed (S.C) + module V = S.V + module P = S.P + + let name () = S.name () ^" context hashconsed" + + type marshal = S.marshal (* TODO: should hashcons table be in here to avoid relift altogether? *) + let init = S.init + let finalize = S.finalize + + let startstate = S.startstate + let exitstate = S.exitstate + let morphstate = S.morphstate + + let conv ctx = + { ctx with context = (fun () -> C.unlift (ctx.context ())) } + + let context ctx fd = C.lift % S.context (conv ctx) fd + let startcontext () = C.lift @@ S.startcontext () + + let sync ctx reason = + S.sync (conv ctx) reason + + let query ctx (type a) (q: a Queries.t): a Queries.result = + match q with + | Queries.IterPrevVars f -> + let g i (n, c, j) e = f i (n, Obj.repr (C.lift (Obj.obj c)), j) e in + S.query (conv ctx) (Queries.IterPrevVars g) + | _ -> S.query (conv ctx) q + + let assign ctx lv e = + S.assign (conv ctx) lv e + + let vdecl ctx v = + S.vdecl (conv ctx) v + + let branch ctx e tv = + S.branch (conv ctx) e tv + + let body ctx f = + S.body (conv ctx) f + + let return ctx r f = + S.return (conv ctx) r f + + let asm ctx = + S.asm (conv ctx) + + let skip ctx = + S.skip (conv ctx) + + let enter ctx r f args = + S.enter (conv ctx) r f args + + let special ctx r f args = + S.special (conv ctx) r f args + + let combine_env ctx r fe f args fc es f_ask = + S.combine_env (conv ctx) r fe f args (Option.map C.unlift fc) es f_ask + + let combine_assign ctx r fe f args fc es f_ask = + S.combine_assign (conv ctx) r fe f args (Option.map C.unlift fc) es f_ask + + let threadenter ctx ~multiple lval f args = + S.threadenter (conv ctx) ~multiple lval f args + + let threadspawn ctx ~multiple lval f args fctx = + S.threadspawn (conv ctx) ~multiple lval f args (conv fctx) + + let paths_as_set ctx = S.paths_as_set (conv ctx) + let event ctx e octx = S.event (conv ctx) e (conv octx) +end + +(* see option ana.opt.equal *) +module OptEqual (S: Spec) = struct + module D = struct include S.D let equal x y = x == y || equal x y end + module G = struct include S.G let equal x y = x == y || equal x y end + module C = struct include S.C let equal x y = x == y || equal x y end + include (S : Spec with module D := D and module G := G and module C := C) +end + +(** If dbg.slice.on, stops entering functions after dbg.slice.n levels. *) +module LevelSliceLifter (S:Spec) + : Spec with module D = Lattice.Prod (S.D) (Lattice.Reverse (IntDomain.Lifted)) + and module G = S.G + and module C = S.C += +struct + module D = Lattice.Prod (S.D) (Lattice.Reverse (IntDomain.Lifted)) + module G = S.G + module C = S.C + module V = S.V + module P = + struct + include S.P + let of_elt (x, _) = of_elt x + end + + let name () = S.name ()^" level sliced" + + let start_level = ref (`Top) + + type marshal = S.marshal (* TODO: should hashcons table be in here to avoid relift altogether? *) + let init marshal = + if get_bool "dbg.slice.on" then + start_level := `Lifted (Int64.of_int (get_int "dbg.slice.n")); + S.init marshal + + let finalize = S.finalize + + let startstate v = (S.startstate v, !start_level) + let exitstate v = (S.exitstate v, !start_level) + let morphstate v (d,l) = (S.morphstate v d, l) + + let conv ctx = + { ctx with local = fst ctx.local + ; split = (fun d es -> ctx.split (d, snd ctx.local) es ) + } + + let context ctx fd (d,_) = S.context (conv ctx) fd d + let startcontext () = S.startcontext () + + let lift_fun ctx f g h = + f @@ h (g (conv ctx)) + + let enter' ctx r f args = + let liftmap = List.map (fun (x,y) -> (x, snd ctx.local), (y, snd ctx.local)) in + lift_fun ctx liftmap S.enter ((|>) args % (|>) f % (|>) r) + + let lift ctx d = (d, snd ctx.local) + let lift_start_level d = (d, !start_level) + + let sync ctx reason = lift_fun ctx (lift ctx) S.sync ((|>) reason) + let query' ctx (type a) (q: a Queries.t): a Queries.result = + lift_fun ctx identity S.query (fun x -> x q) + let assign ctx lv e = lift_fun ctx (lift ctx) S.assign ((|>) e % (|>) lv) + let vdecl ctx v = lift_fun ctx (lift ctx) S.vdecl ((|>) v) + let branch ctx e tv = lift_fun ctx (lift ctx) S.branch ((|>) tv % (|>) e) + let body ctx f = lift_fun ctx (lift ctx) S.body ((|>) f) + let return ctx r f = lift_fun ctx (lift ctx) S.return ((|>) f % (|>) r) + let asm ctx = lift_fun ctx (lift ctx) S.asm identity + let skip ctx = lift_fun ctx (lift ctx) S.skip identity + let special ctx r f args = lift_fun ctx (lift ctx) S.special ((|>) args % (|>) f % (|>) r) + let combine_env' ctx r fe f args fc es f_ask = lift_fun ctx (lift ctx) S.combine_env (fun p -> p r fe f args fc (fst es) f_ask) + let combine_assign' ctx r fe f args fc es f_ask = lift_fun ctx (lift ctx) S.combine_assign (fun p -> p r fe f args fc (fst es) f_ask) + + let threadenter ctx ~multiple lval f args = lift_fun ctx (List.map lift_start_level) (S.threadenter ~multiple) ((|>) args % (|>) f % (|>) lval) + let threadspawn ctx ~multiple lval f args fctx = lift_fun ctx (lift ctx) (S.threadspawn ~multiple) ((|>) (conv fctx) % (|>) args % (|>) f % (|>) lval) + + let leq0 = function + | `Top -> false + | `Lifted x -> x <= 0L + | `Bot -> true + + let sub1 = function + | `Lifted x -> `Lifted (Int64.sub x 1L) + | x -> x + + let add1 = function + | `Lifted x -> `Lifted (Int64.add x 1L) + | x -> x + + let paths_as_set ctx = + let liftmap = List.map (fun x -> (x, snd ctx.local)) in + lift_fun ctx liftmap S.paths_as_set (Fun.id) + + let event ctx e octx = + lift_fun ctx (lift ctx) S.event ((|>) (conv octx) % (|>) e) + + let enter ctx r f args = + let (d,l) = ctx.local in + if leq0 l then + [ctx.local, D.bot ()] + else + enter' {ctx with local=(d, sub1 l)} r f args + + let combine_env ctx r fe f args fc es f_ask = + let (d,l) = ctx.local in + let l = add1 l in + if leq0 l then + (d, l) + else + let d',_ = combine_env' ctx r fe f args fc es f_ask in + (d', l) + + let combine_assign ctx r fe f args fc es f_ask = + let (d,l) = ctx.local in + (* No need to add1 here, already done in combine_env. *) + if leq0 l then + (d, l) + else + let d',_ = combine_assign' ctx r fe f args fc es f_ask in + (d', l) + + let query ctx (type a) (q: a Queries.t): a Queries.result = + match q with + | Queries.EvalFunvar e -> + let (d,l) = ctx.local in + if leq0 l then + Queries.AD.empty () + else + query' ctx (Queries.EvalFunvar e) + | q -> query' ctx q +end + + +(** Limits the number of widenings per node. *) +module LimitLifter (S:Spec) = +struct + include (S : module type of S with module D := S.D and type marshal = S.marshal) + + let name () = S.name ()^" limited" + + let limit = ref 0 + + let init marshal = + limit := get_int "dbg.limit.widen"; + S.init marshal + + module H = MyCFG.NodeH + let h = H.create 13 + let incr k = + H.modify_def 1 k (fun v -> + if v >= !limit then failwith (GobPretty.sprintf "LimitLifter: Reached limit (%d) for node %a" !limit Node.pretty_plain_short (Option.get !MyCFG.current_node)); + v+1 + ) h; + module D = struct + include S.D + let widen x y = Option.may incr !MyCFG.current_node; widen x y (* when is this None? *) + end +end + + +(* widening on contexts, keeps contexts for calls only in D *) +module WidenContextLifterSide (S:Spec) += +struct + module DD = + struct + include S.D + let printXml f d = BatPrintf.fprintf f "%a" printXml d + end + module M = MapDomain.MapBot (Basetype.Variables) (DD) (* should be CilFun -> S.C, but CilFun is not Groupable, and S.C is no Lattice *) + + module D = struct + include Lattice.Prod (S.D) (M) + let printXml f (d,m) = BatPrintf.fprintf f "\n%a\n%a\n" S.D.printXml d M.printXml m + end + module G = S.G + module C = S.C + module V = S.V + module P = + struct + include S.P + let of_elt (x, _) = of_elt x + end + + + let name () = S.name ()^" with widened contexts" + + type marshal = S.marshal + let init = S.init + let finalize = S.finalize + + let inj f x = f x, M.bot () + + let startcontext () = S.startcontext () + let startstate = inj S.startstate + let exitstate = inj S.exitstate + let morphstate v (d,m) = S.morphstate v d, m + + + let conv ctx = + { ctx with local = fst ctx.local + ; split = (fun d es -> ctx.split (d, snd ctx.local) es ) + } + + let context ctx fd (d,m) = S.context (conv ctx) fd d (* just the child analysis' context *) + + let lift_fun ctx f g = g (f (conv ctx)), snd ctx.local + + let sync ctx reason = lift_fun ctx S.sync ((|>) reason) + let query ctx = S.query (conv ctx) + let assign ctx lv e = lift_fun ctx S.assign ((|>) e % (|>) lv) + let vdecl ctx v = lift_fun ctx S.vdecl ((|>) v) + let branch ctx e tv = lift_fun ctx S.branch ((|>) tv % (|>) e) + let body ctx f = lift_fun ctx S.body ((|>) f) + let return ctx r f = lift_fun ctx S.return ((|>) f % (|>) r) + let asm ctx = lift_fun ctx S.asm identity + let skip ctx = lift_fun ctx S.skip identity + let special ctx r f args = lift_fun ctx S.special ((|>) args % (|>) f % (|>) r) + + let event ctx e octx = lift_fun ctx S.event ((|>) (conv octx) % (|>) e) + + let threadenter ctx ~multiple lval f args = S.threadenter (conv ctx) ~multiple lval f args |> List.map (fun d -> (d, snd ctx.local)) + let threadspawn ctx ~multiple lval f args fctx = lift_fun ctx (S.threadspawn ~multiple) ((|>) (conv fctx) % (|>) args % (|>) f % (|>) lval) + + let enter ctx r f args = + let m = snd ctx.local in + let d' v_cur = + if ContextUtil.should_keep ~isAttr:GobContext ~keepOption:"ana.context.widen" ~keepAttr:"widen" ~removeAttr:"no-widen" f then ( + let v_old = M.find f.svar m in (* S.D.bot () if not found *) + let v_new = S.D.widen v_old (S.D.join v_old v_cur) in + Messages.(if tracing && not (S.D.equal v_old v_new) then tracel "widen-context" "enter results in new context for function %s" f.svar.vname); + v_new, M.add f.svar v_new m + ) + else + v_cur, m + in + S.enter (conv ctx) r f args + |> List.map (fun (c,v) -> (c,m), d' v) (* c: caller, v: callee *) + + let paths_as_set ctx = + let m = snd ctx.local in + S.paths_as_set (conv ctx) |> List.map (fun v -> (v,m)) + + let combine_env ctx r fe f args fc es f_ask = lift_fun ctx S.combine_env (fun p -> p r fe f args fc (fst es) f_ask) + let combine_assign ctx r fe f args fc es f_ask = lift_fun ctx S.combine_assign (fun p -> p r fe f args fc (fst es) f_ask) +end + + +(** Lifts a [Spec] with a special bottom element that represent unreachable code. *) +module DeadCodeLifter (S:Spec) + : Spec with module D = Dom (S.D) + and module G = S.G + and module C = S.C += +struct + module D = Dom (S.D) + module G = S.G + module C = S.C + module V = S.V + module P = + struct + include Printable.Option (S.P) (struct let name = "None" end) + + let of_elt = function + | `Lifted x -> Some (S.P.of_elt x) + | _ -> None + end + + let name () = S.name ()^" lifted" + + type marshal = S.marshal + let init = S.init + let finalize = S.finalize + + + let startcontext () = S.startcontext () + let startstate v = `Lifted (S.startstate v) + let exitstate v = `Lifted (S.exitstate v) + let morphstate v d = try `Lifted (S.morphstate v (D.unlift d)) with Deadcode -> d + + + let conv ctx = + { ctx with local = D.unlift ctx.local + ; split = (fun d es -> ctx.split (D.lift d) es ) + } + + let context ctx fd = S.context (conv ctx) fd % D.unlift + + let lift_fun ctx f g h b = + try f @@ h (g (conv ctx)) + with Deadcode -> b + + let sync ctx reason = lift_fun ctx D.lift S.sync ((|>) reason) `Bot + + let enter ctx r f args = + let liftmap = List.map (fun (x,y) -> D.lift x, D.lift y) in + lift_fun ctx liftmap S.enter ((|>) args % (|>) f % (|>) r) [] + + let paths_as_set ctx = + let liftmap = List.map (fun x -> D.lift x) in + lift_fun ctx liftmap S.paths_as_set (Fun.id) [D.bot ()] (* One dead path instead of none, such that combine_env gets called for functions with dead normal return (and thus longjmpy returns can be correctly handled by lifter). *) + + let query ctx (type a) (q: a Queries.t): a Queries.result = + lift_fun ctx identity S.query (fun (x) -> x q) (Queries.Result.bot q) + let assign ctx lv e = lift_fun ctx D.lift S.assign ((|>) e % (|>) lv) `Bot + let vdecl ctx v = lift_fun ctx D.lift S.vdecl ((|>) v) `Bot + let branch ctx e tv = lift_fun ctx D.lift S.branch ((|>) tv % (|>) e) `Bot + let body ctx f = lift_fun ctx D.lift S.body ((|>) f) `Bot + let return ctx r f = lift_fun ctx D.lift S.return ((|>) f % (|>) r) `Bot + let asm ctx = lift_fun ctx D.lift S.asm identity `Bot + let skip ctx = lift_fun ctx D.lift S.skip identity `Bot + let special ctx r f args = lift_fun ctx D.lift S.special ((|>) args % (|>) f % (|>) r) `Bot + let combine_env ctx r fe f args fc es f_ask = lift_fun ctx D.lift S.combine_env (fun p -> p r fe f args fc (D.unlift es) f_ask) `Bot + let combine_assign ctx r fe f args fc es f_ask = lift_fun ctx D.lift S.combine_assign (fun p -> p r fe f args fc (D.unlift es) f_ask) `Bot + + let threadenter ctx ~multiple lval f args = lift_fun ctx (List.map D.lift) (S.threadenter ~multiple) ((|>) args % (|>) f % (|>) lval) [] + let threadspawn ctx ~multiple lval f args fctx = lift_fun ctx D.lift (S.threadspawn ~multiple) ((|>) (conv fctx) % (|>) args % (|>) f % (|>) lval) `Bot + + let event (ctx:(D.t,G.t,C.t,V.t) ctx) (e:Events.t) (octx:(D.t,G.t,C.t,V.t) ctx):D.t = lift_fun ctx D.lift S.event ((|>) (conv octx) % (|>) e) `Bot +end + + +(** Add path sensitivity to a analysis *) +module PathSensitive2 (Spec:Spec) + : Spec + with module G = Spec.G + and module C = Spec.C + and module V = Spec.V += +struct + module D = + struct + (* TODO is it really worth it to check every time instead of just using sets and joining later? *) + module R = + struct + include Spec.P + type elt = Spec.D.t + end + module J = SetDomain.Joined (Spec.D) + include DisjointDomain.ProjectiveSet (Spec.D) (J) (R) + let name () = "PathSensitive (" ^ name () ^ ")" + + let printXml f x = + let print_one x = + BatPrintf.fprintf f "\n%a" Spec.D.printXml x + in + iter print_one x + end + + module G = Spec.G + module C = Spec.C + module V = Spec.V + module P = UnitP + + let name () = "PathSensitive2("^Spec.name ()^")" + + type marshal = Spec.marshal + let init = Spec.init + let finalize = Spec.finalize + + let startcontext () = Spec.startcontext () + let exitstate v = D.singleton (Spec.exitstate v) + let startstate v = D.singleton (Spec.startstate v) + let morphstate v d = D.map (Spec.morphstate v) d + + let conv ctx x = + let rec ctx' = { ctx with ask = (fun (type a) (q: a Queries.t) -> Spec.query ctx' q) + ; local = x + ; split = (ctx.split % D.singleton) } + in + ctx' + + let context ctx fd l = + if D.cardinal l <> 1 then + failwith "PathSensitive2.context must be called with a singleton set." + else + let x = D.choose l in + Spec.context (conv ctx x) fd x + + + let map ctx f g = + let h x xs = + try D.add (g (f (conv ctx x))) xs + with Deadcode -> xs + in + let d = D.fold h ctx.local (D.empty ()) in + if D.is_bot d then raise Deadcode else d + + let fold' ctx f g h a = + let k x a = + try h a @@ g @@ f @@ conv ctx x + with Deadcode -> a + in + D.fold k ctx.local a + + let assign ctx l e = map ctx Spec.assign (fun h -> h l e ) + let vdecl ctx v = map ctx Spec.vdecl (fun h -> h v) + let body ctx f = map ctx Spec.body (fun h -> h f ) + let return ctx e f = map ctx Spec.return (fun h -> h e f ) + let branch ctx e tv = map ctx Spec.branch (fun h -> h e tv) + let asm ctx = map ctx Spec.asm identity + let skip ctx = map ctx Spec.skip identity + let special ctx l f a = map ctx Spec.special (fun h -> h l f a) + + let event ctx e octx = + let fd1 = D.choose octx.local in + map ctx Spec.event (fun h -> h e (conv octx fd1)) + + let threadenter ctx ~multiple lval f args = + let g xs ys = (List.map (fun y -> D.singleton y) ys) @ xs in + fold' ctx (Spec.threadenter ~multiple) (fun h -> h lval f args) g [] + + let threadspawn ctx ~multiple lval f args fctx = + let fd1 = D.choose fctx.local in + map ctx (Spec.threadspawn ~multiple) (fun h -> h lval f args (conv fctx fd1)) + + let sync ctx reason = map ctx Spec.sync (fun h -> h reason) + + let query ctx (type a) (q: a Queries.t): a Queries.result = + (* TODO: handle Invariant path like PathSensitive3? *) + (* join results so that they are sound for all paths *) + let module Result = (val Queries.Result.lattice q) in + fold' ctx Spec.query identity (fun x f -> Result.join x (f q)) (Result.bot ()) + + let enter ctx l f a = + let g xs ys = (List.map (fun (x,y) -> D.singleton x, D.singleton y) ys) @ xs in + fold' ctx Spec.enter (fun h -> h l f a) g [] + + let paths_as_set ctx = + (* Path-sensitivity is only here, not below! *) + let elems = D.elements ctx.local in + List.map (D.singleton) elems + + let combine_env ctx l fe f a fc d f_ask = + assert (D.cardinal ctx.local = 1); + let cd = D.choose ctx.local in + let k x y = + if M.tracing then M.traceli "combine" "function: %a" Spec.D.pretty x; + try + let r = Spec.combine_env (conv ctx cd) l fe f a fc x f_ask in + if M.tracing then M.traceu "combine" "combined function: %a" Spec.D.pretty r; + D.add r y + with Deadcode -> + if M.tracing then M.traceu "combine" "combined function: dead"; + y + in + let d = D.fold k d (D.bot ()) in + if D.is_bot d then raise Deadcode else d + + let combine_assign ctx l fe f a fc d f_ask = + assert (D.cardinal ctx.local = 1); + let cd = D.choose ctx.local in + let k x y = + if M.tracing then M.traceli "combine" "function: %a" Spec.D.pretty x; + try + let r = Spec.combine_assign (conv ctx cd) l fe f a fc x f_ask in + if M.tracing then M.traceu "combine" "combined function: %a" Spec.D.pretty r; + D.add r y + with Deadcode -> + if M.tracing then M.traceu "combine" "combined function: dead"; + y + in + let d = D.fold k d (D.bot ()) in + if D.is_bot d then raise Deadcode else d +end + +module DeadBranchLifter (S: Spec): Spec = +struct + include S + + let name () = "DeadBranch (" ^ S.name () ^ ")" + + (* Two global invariants: + 1. S.V -> S.G -- used for S + 2. node -> (exp -> flat bool) -- used for warnings *) + + module V = + struct + include Printable.EitherConf (struct let expand1 = false let expand2 = true end) (S.V) (Node) + let name () = "DeadBranch" + 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 = + struct + include MapDomain.MapBot (Basetype.CilExp) (BoolDomain.FlatBool) + let name () = "branches" + end + + module G = + struct + include Lattice.Lift2 (S.G) (EM) + let name () = "deadbranch" + + 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 init marshal = + init marshal; + AnalysisState.unsound_both_branches_dead := Some false + + 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 when not (CilType.Exp.equal exp one) -> (* all branches dead *) + AnalysisState.unsound_both_branches_dead := Some true; + M.msg_final Error ~category:Analyzer ~tags:[Category Unsound] "Both branches dead"; + M.error ~loc:(Node g) ~category:Analyzer ~tags:[Category Unsound] "both branches over condition '%a' are dead" d_exp exp + | `Bot (* all branches dead, fine at our inserted Neg(1)-s because no Pos(1) *) + | `Top -> (* may be both true and false *) + () + ) em; + end + | InvariantGlobal g -> + let g: V.t = Obj.obj g in + begin match g with + | `Left g -> + S.query (conv ctx) (InvariantGlobal (Obj.repr g)) + | `Right g -> + Queries.Result.top q + end + | YamlEntryGlobal (g, task) -> + let g: V.t = Obj.obj g in + begin match g with + | `Left g -> + S.query (conv ctx) (YamlEntryGlobal (Obj.repr g, task)) + | `Right g -> + Queries.Result.top q + 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 context ctx = S.context (conv ctx) + + let branch ctx exp tv = + if !AnalysisState.postsolving then ( + try + let r = branch ctx exp tv in + (* branch is live *) + 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 *) + ctx.sideg (V.node ctx.prev_node) (G.create_node (EM.singleton exp `Bot)); (* record expression without reached tv *) + raise Deadcode + ) + 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 paths_as_set ctx = S.paths_as_set (conv ctx) + let body ctx = S.body (conv ctx) + let return ctx = S.return (conv ctx) + let combine_env ctx = S.combine_env (conv ctx) + let combine_assign ctx = S.combine_assign (conv ctx) + let special ctx = S.special (conv ctx) + let threadenter ctx = S.threadenter (conv ctx) + let threadspawn ctx ~multiple lv f args fctx = S.threadspawn (conv ctx) ~multiple 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) + let event ctx e octx = S.event (conv ctx) e (conv octx) +end diff --git a/src/lifters/wideningToken.ml b/src/lifters/wideningToken.ml new file mode 100644 index 0000000000..0639521038 --- /dev/null +++ b/src/lifters/wideningToken.ml @@ -0,0 +1,16 @@ +(** Widening token for {!WideningTokenLifter}. *) + +module Uuid = +struct + include Basetype.RawStrings + let name () = "uuid" +end + +module Index = +struct + include Printable.Option (IntDomain.Integers (IntOps.NIntOps)) (struct let name = "None" end) + let name () = "index" +end + +(* Change to variant type if need other tokens than witness UUIDs. *) +include Printable.Prod (Uuid) (Index) diff --git a/src/util/wideningTokens.ml b/src/lifters/wideningTokenLifter.ml similarity index 97% rename from src/util/wideningTokens.ml rename to src/lifters/wideningTokenLifter.ml index 1816de90c7..634468a9ca 100644 --- a/src/util/wideningTokens.ml +++ b/src/lifters/wideningTokenLifter.ml @@ -6,8 +6,7 @@ @see Mihaila, B., Sepp, A. & Simon, A. Widening as Abstract Domain. *) -(** Widening token. *) -module Token = Basetype.RawStrings (* Change to variant type if need other tokens than witness UUIDs. *) +module Token = WideningToken (** Widening token set. *) module TS = SetDomain.ToppedSet (Token) (struct let topname = "Top" end) @@ -126,8 +125,6 @@ struct let exitstate v = (S.exitstate v, TS.bot ()) let morphstate v (d, t) = (S.morphstate v d, t) - let context fd = S.context fd % D.unlift - let conv (ctx: (D.t, G.t, C.t, V.t) ctx): (S.D.t, S.G.t, S.C.t, S.V.t) ctx = { ctx with local = D.unlift ctx.local ; split = (fun d es -> ctx.split (d, snd ctx.local) es) (* Split keeps local widening tokens. *) @@ -135,6 +132,9 @@ struct ; sideg = (fun v g -> ctx.sideg v (g, !side_tokens)) (* Using side_tokens for side effect. *) } + let context ctx fd = S.context (conv ctx) fd % D.unlift + let startcontext () = S.startcontext () + let lift_fun ctx f g h = let new_tokens = ref (snd ctx.local) in (* New tokens not yet used during this transfer function, such that it is deterministic. *) let old_add = !add_ref in diff --git a/src/maingoblint.ml b/src/maingoblint.ml index 10983c2b2b..cb81ea0b86 100644 --- a/src/maingoblint.ml +++ b/src/maingoblint.ml @@ -201,7 +201,9 @@ let handle_options () = Logs.Level.current := Logs.Level.of_string (get_string "dbg.level"); check_arguments (); Sys.set_signal (GobSys.signal_of_string (get_string "dbg.solver-signal")) Signal_ignore; (* Ignore solver-signal before solving (e.g. MyCFG), otherwise exceptions self-signal the default, which crashes instead of printing backtrace. *) - if AutoTune.isActivated "memsafetySpecification" && get_string "ana.specification" <> "" then + if get_string "ana.specification" <> "" then + AutoSoundConfig.enableAnalysesForMemSafetySpecification (); + if AutoTune.isActivated "memsafetySpecification" then AutoTune.focusOnMemSafetySpecification (); AfterConfig.run (); Cilfacade.init_options (); diff --git a/src/ppx/dune b/src/ppx/dune new file mode 100644 index 0000000000..ff757cb8ca --- /dev/null +++ b/src/ppx/dune @@ -0,0 +1 @@ +(include_subdirs no) diff --git a/src/ppx/lattice/dune b/src/ppx/lattice/dune new file mode 100644 index 0000000000..862298be13 --- /dev/null +++ b/src/ppx/lattice/dune @@ -0,0 +1,5 @@ +(library + (name ppx_deriving_lattice) + (kind ppx_deriver) + (libraries ppxlib ppx_easy_deriving) + (preprocess (pps ppxlib.metaquot))) diff --git a/src/ppx/lattice/ppx_deriving_lattice.ml b/src/ppx/lattice/ppx_deriving_lattice.ml new file mode 100644 index 0000000000..e78e88644a --- /dev/null +++ b/src/ppx/lattice/ppx_deriving_lattice.ml @@ -0,0 +1,97 @@ +open Ppxlib +open Ppx_easy_deriving + +module LeqArg: Product.Reduce.Conjunctive.S = +struct + let name = "leq" +end + +module LeqDeriver = Deriver.Make (Product.Reduce2.Make (Product.Reduce.Conjunctive.Make (LeqArg))) +let leq_deriving = LeqDeriver.register () + + +module JoinArg: Product.Map2.S = +struct + let name = "join" +end + +module JoinDeriver = Deriver.Make (Product.Map2.Make (JoinArg)) +let join_deriving = JoinDeriver.register () + + +module MeetArg: Product.Map2.S = +struct + let name = "meet" +end + +module MeetDeriver = Deriver.Make (Product.Map2.Make (MeetArg)) +let meet_deriving = MeetDeriver.register () + + +module WidenArg: Product.Map2.S = +struct + let name = "widen" +end + +module WidenDeriver = Deriver.Make (Product.Map2.Make (WidenArg)) +let widen_deriving = WidenDeriver.register () + + +module NarrowArg: Product.Map2.S = +struct + let name = "narrow" +end + +module NarrowDeriver = Deriver.Make (Product.Map2.Make (NarrowArg)) +let narrow_deriving = NarrowDeriver.register () + + +module BotArg: Product.Create.S = +struct + let name = "bot" + let typ ~loc _ = [%type: unit] +end + +module BotDeriver = Deriver.Make (Product.Create.Make (BotArg)) +let bot_deriving = BotDeriver.register () + + +module IsBotArg: Product.Reduce.Conjunctive.S = +struct + let name = "is_bot" +end + +module IsBotDeriver = Deriver.Make (Product.Reduce1.Make (Product.Reduce.Conjunctive.Make (IsBotArg))) +let is_bot_deriving = IsBotDeriver.register () + + +module TopArg: Product.Create.S = +struct + let name = "top" + let typ ~loc _ = [%type: unit] +end + +module TopDeriver = Deriver.Make (Product.Create.Make (TopArg)) +let top_deriving = TopDeriver.register () + + +module IsTopArg: Product.Reduce.Conjunctive.S = +struct + let name = "is_top" +end + +module IsTopDeriver = Deriver.Make (Product.Reduce1.Make (Product.Reduce.Conjunctive.Make (IsTopArg))) +let is_top_deriving = IsTopDeriver.register () + + +let _ = Ppxlib.Deriving.add_alias "lattice" [ + leq_deriving; + join_deriving; + meet_deriving; + widen_deriving; + narrow_deriving; + bot_deriving; + is_bot_deriving; + top_deriving; + is_top_deriving; + ] diff --git a/src/ppx/printable/dune b/src/ppx/printable/dune new file mode 100644 index 0000000000..8e08232de6 --- /dev/null +++ b/src/ppx/printable/dune @@ -0,0 +1,5 @@ +(library + (name ppx_deriving_printable) + (kind ppx_deriver) + (libraries ppxlib ppx_easy_deriving) + (preprocess (pps ppxlib.metaquot))) diff --git a/src/ppx/printable/ppx_deriving_printable.ml b/src/ppx/printable/ppx_deriving_printable.ml new file mode 100644 index 0000000000..75fd5044fe --- /dev/null +++ b/src/ppx/printable/ppx_deriving_printable.ml @@ -0,0 +1,16 @@ +open Ppx_easy_deriving + +module ReliftArg: Product.Map1.S = +struct + let name = "relift" +end + +(* TODO: Map1 should also do variants *) +module ReliftDeriver = Deriver.Make (Product.Map1.Make (ReliftArg)) +let relift_deriving = ReliftDeriver.register () + + +(* TODO: needs https://github.com/ocaml-ppx/ppxlib/pull/124 to include eq, ord, hash *) +(* let _ = Ppxlib.Deriving.add_alias "printable" [ + relift_deriving; + ] *) diff --git a/src/sites/dune b/src/sites/dune index d8663e37fe..6de3a5a32a 100644 --- a/src/sites/dune +++ b/src/sites/dune @@ -1,12 +1,10 @@ (include_subdirs no) -; virtual library to allow js build (for gobview) without dune-site -; dune-site seems to be incompatible with js_of_ocaml -; File "gobview/src/.App.eobjs/dune_site_data.ml-gen", line 1: -; Error: Could not find the .cmi file for interface -; gobview/src/.App.eobjs/dune_site_data.ml-gen. (library (name goblint_sites) (public_name goblint.sites) - (virtual_modules goblint_sites) - (libraries fpath)) + (libraries dune-site fpath)) + +(generate_sites_module + (module dunesite) + (sites goblint)) diff --git a/src/sites/sites_dune/goblint_sites.ml b/src/sites/goblint_sites.ml similarity index 100% rename from src/sites/sites_dune/goblint_sites.ml rename to src/sites/goblint_sites.ml diff --git a/src/sites/sites_dune/dune b/src/sites/sites_dune/dune deleted file mode 100644 index b7f90a8892..0000000000 --- a/src/sites/sites_dune/dune +++ /dev/null @@ -1,12 +0,0 @@ -; goblint.sites implementation which properly uses dune-site -(library - (name goblint_sites_dune) - (public_name goblint.sites.dune) - (implements goblint.sites) - (modules goblint_sites dunesite) - (private_modules dunesite) ; must also be in modules - (libraries dune-site)) - -(generate_sites_module - (module dunesite) - (sites goblint)) diff --git a/src/sites/sites_js/dune b/src/sites/sites_js/dune deleted file mode 100644 index 4e20871974..0000000000 --- a/src/sites/sites_js/dune +++ /dev/null @@ -1,6 +0,0 @@ -; goblint.sites implementation which works with js_of_ocaml and doesn't use dune-site -(library - (name goblint_sites_js) - (public_name goblint.sites.js) - (implements goblint.sites) - (modules goblint_sites)) diff --git a/src/sites/sites_js/goblint_sites.ml b/src/sites/sites_js/goblint_sites.ml deleted file mode 100644 index 3a7b353064..0000000000 --- a/src/sites/sites_js/goblint_sites.ml +++ /dev/null @@ -1,6 +0,0 @@ -let lib = [] -let lib_stub_include = [] -let lib_stub_src = [] -let lib_runtime_include = [] -let lib_runtime_src = [] -let conf = [] diff --git a/src/solver/generic.ml b/src/solver/generic.ml index 1f0df57843..737aba6762 100644 --- a/src/solver/generic.ml +++ b/src/solver/generic.ml @@ -44,7 +44,7 @@ struct let histo = HM.create 1024 let increase (v:Var.t) = let set v c = - if not full_trace && (c > start_c && c > !max_c && (Option.is_none !max_var || not (Var.equal (Option.get !max_var) v))) then begin + if not full_trace && (c > start_c && c > !max_c && not (GobOption.exists (Var.equal v) !max_var)) then begin if tracing then trace "sol" "Switched tracing to %a" Var.pretty_trace v; max_c := c; max_var := Some v @@ -75,7 +75,7 @@ struct let update_var_event x o n = if tracing then increase x; - if full_trace || ((not (Dom.is_bot o)) && Option.is_some !max_var && Var.equal (Option.get !max_var) x) then begin + if full_trace || (not (Dom.is_bot o) && GobOption.exists (Var.equal x) !max_var) then begin if tracing then tracei "sol_max" "(%d) Update to %a" !max_c Var.pretty_trace x; if tracing then traceu "sol_max" "%a" Dom.pretty_diff (n, o) end diff --git a/src/solver/sLR.ml b/src/solver/sLR.ml index 3b46f36f5e..03ba9307aa 100644 --- a/src/solver/sLR.ml +++ b/src/solver/sLR.ml @@ -66,7 +66,7 @@ module SLR3 = if tracing then trace "sol" "Contrib:%a" S.Dom.pretty tmp; let tmp = if wpx then - if HM.mem globals x then S.Dom.widen old tmp + if HM.mem globals x then S.Dom.widen old (S.Dom.join old tmp) else box old tmp else tmp in @@ -527,7 +527,7 @@ let _ = Selector.add_solver ("widen2", (module PostSolver.EqIncrSolverFromEqSolver (W2))); Selector.add_solver ("widen3", (module PostSolver.EqIncrSolverFromEqSolver (W3))); let module S2 = TwoPhased (struct let ver = 1 end) in - Selector.add_solver ("two", (module PostSolver.EqIncrSolverFromEqSolver (S2))); + (* Selector.add_solver ("two", (module PostSolver.EqIncrSolverFromEqSolver (S2))); (* TODO: broken even on 00-sanity/01-assert *) *) let module S1 = Make (struct let ver = 1 end) in Selector.add_solver ("new", (module PostSolver.EqIncrSolverFromEqSolver (S1))); Selector.add_solver ("slr+", (module PostSolver.EqIncrSolverFromEqSolver (S1))) diff --git a/src/solver/td3.ml b/src/solver/td3.ml index 7e2a18b042..3cab3cf7f7 100644 --- a/src/solver/td3.ml +++ b/src/solver/td3.ml @@ -49,7 +49,7 @@ module Base = open SolverBox.Warrow (S.Dom) include Generic.SolverStats (S) (HM) module VS = Set.Make (S.Var) - let exists_key f hm = HM.fold (fun k _ a -> a || f k) hm false + let exists_key f hm = HM.exists (fun k _ -> f k) hm type solver_data = { st: (S.Var.t * S.Dom.t) list; (* needed to destabilize start functions if their start state changed because of some changed global initializer *) @@ -284,8 +284,12 @@ module Base = 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_cmp S.Var.compare y vs - ) w false + Hooks.stable_remove y; + if not (HM.mem called y) then + destabilize_vs y || b || was_stable && List.mem_cmp S.Var.compare y vs + else + true + ) w false (* nosemgrep: fold-exists *) (* does side effects *) and solve ?reuse_eq x phase = if tracing then trace "sol2" "solve %a, phase: %s, called: %b, stable: %b, wpoint: %b" S.Var.pretty_trace x (show_phase phase) (HM.mem called x) (HM.mem stable x) (HM.mem wpoint x); init x; diff --git a/src/solver/topDown_deprecated.ml b/src/solver/topDown_deprecated.ml index 16c45fcd16..a46da9e441 100644 --- a/src/solver/topDown_deprecated.ml +++ b/src/solver/topDown_deprecated.ml @@ -4,8 +4,6 @@ open Batteries open ConstrSys open Messages -exception SolverCannotDoGlobals - (** modified SLR3 as top down solver *) module TD3 = diff --git a/src/solver/topDown_space_cache_term.ml b/src/solver/topDown_space_cache_term.ml index 0022756a31..df44c376e1 100644 --- a/src/solver/topDown_space_cache_term.ml +++ b/src/solver/topDown_space_cache_term.ml @@ -170,7 +170,7 @@ module WP = ) in (* restore values for non-widening-points *) - if GobConfig.get_bool "solvers.wp.restore" then ( + if GobConfig.get_bool "solvers.td3.space_restore" then ( Logs.debug "Restoring missing values."; let restore () = let get x = diff --git a/src/solver/topDown_term.ml b/src/solver/topDown_term.ml index 9d89a42898..5560c50f4f 100644 --- a/src/solver/topDown_term.ml +++ b/src/solver/topDown_term.ml @@ -24,8 +24,8 @@ module WP = let stable = HM.create 10 in let infl = HM.create 10 in (* y -> xs *) let called = HM.create 10 in - let rho = HM.create 10 in - let rho' = HM.create 10 in + let rho = HM.create 10 in (* rho for right-hand side values *) + let rho' = HM.create 10 in (* rho for start and side effect values *) let wpoint = HM.create 10 in let add_infl y x = @@ -85,7 +85,7 @@ module WP = and side y d = let old = try HM.find rho' y with Not_found -> S.Dom.bot () in if not (S.Dom.leq d old) then ( - HM.replace rho' y (S.Dom.widen old d); + HM.replace rho' y (S.Dom.widen old (S.Dom.join old d)); HM.remove stable y; init y; solve y Widen; @@ -101,7 +101,7 @@ module WP = let set_start (x,d) = if tracing then trace "sol2" "set_start %a ## %a" S.Var.pretty_trace x S.Dom.pretty d; init x; - HM.replace rho x d; + HM.replace rho' x d; solve x Widen in diff --git a/src/transform/evalAssert.ml b/src/transform/evalAssert.ml index eab06222ef..8f858d09df 100644 --- a/src/transform/evalAssert.ml +++ b/src/transform/evalAssert.ml @@ -22,12 +22,8 @@ open Formatcil will be removed and they will fail on the next iteration *) -module EvalAssert = struct - (* should asserts be surrounded by __VERIFIER_atomic_{begin,end}? *) - let surroundByAtomic = true - - (* Cannot use Cilfacade.name_fundecs as assert() is external and has no fundec *) - let ass = makeVarinfo true "__VERIFIER_assert" (TVoid []) +module EvalAssert = +struct let atomicBegin = makeVarinfo true "__VERIFIER_atomic_begin" (TVoid []) let atomicEnd = makeVarinfo true "__VERIFIER_atomic_end" (TVoid []) @@ -39,10 +35,16 @@ module EvalAssert = struct val emit_after_lock = GobConfig.get_bool "witness.invariant.after-lock" val emit_other = GobConfig.get_bool "witness.invariant.other" + (* Cannot use Cilfacade.name_fundecs as assert() is external and has no fundec *) + val assert_function = makeVarinfo true (GobConfig.get_string "trans.assert.function") (TVoid []) + (* should asserts be surrounded by __VERIFIER_atomic_{begin,end}? *) + val surroundByAtomic = GobConfig.get_bool "trans.assert.wrap-atomic" + method! vstmt s = let is_lock exp args = match exp with | Lval(Var v,_) when LibraryFunctions.is_special v -> + Goblint_backtrace.wrap_val ~mark:(Cilfacade.FunVarinfo v) @@ fun () -> let desc = LibraryFunctions.find v in (match desc.special args with | Lock _ -> true @@ -59,7 +61,7 @@ module EvalAssert = struct match (ask ~node loc).f (Queries.Invariant context) with | `Lifted e -> let es = WitnessUtil.InvariantExp.process_exp e in - let asserts = List.map (fun e -> cInstr ("%v:assert (%e:exp);") loc [("assert", Fv ass); ("exp", Fe e)]) es in + let asserts = List.map (fun e -> cInstr ("%v:assert (%e:exp);") loc [("assert", Fv assert_function); ("exp", Fe e)]) es in if surroundByAtomic then let abegin = (cInstr ("%v:__VERIFIER_atomic_begin();") loc [("__VERIFIER_atomic_begin", Fv atomicBegin)]) in let aend = (cInstr ("%v:__VERIFIER_atomic_end();") loc [("__VERIFIER_atomic_end", Fv atomicEnd)]) in diff --git a/src/util/autoSoundConfig.ml b/src/util/autoSoundConfig.ml new file mode 100644 index 0000000000..0bb67e768e --- /dev/null +++ b/src/util/autoSoundConfig.ml @@ -0,0 +1,71 @@ +(** Automatically turning on analyses required to ensure soundness + based on a given specification (e.g., SV-COMP specification) + or programming idioms (e.g., longjmp) in the analyzed code, + but only when it is possible to do so automatically. + This does not fully exempt from the need for manual configuration. +*) + +open GobConfig +open AutoTune + +let enableSpecAnalyses spec analyses = + Logs.info "Specification: %s -> enabling soundness analyses \"%s\"" (Svcomp.Specification.to_string [spec]) (String.concat ", " analyses); + enableAnalyses analyses + +let enableOptions options = + let enableOpt option = + Logs.info "Setting \"%s\" to true" option; + set_bool option true + in + List.iter enableOpt options + +(** Selecting sound configurations based on SV-COMP specification. + Activating the analyses and enabling options that are + required for analyzing the property defined in the specification. + TODO: have only one function for matching all specifications and find a place where it can be called. +*) +let enableAnalysesForMemSafetySpecification (spec: Svcomp.Specification.t) = + match spec with + | ValidFree -> enableSpecAnalyses spec ["base"; "useAfterFree"]; + | ValidDeref -> + enableSpecAnalyses spec ["base"; "memOutOfBounds"]; + enableOptions ["ana.arrayoob"; "cil.addNestedScopeAttr"] + | ValidMemtrack + | ValidMemcleanup -> enableSpecAnalyses spec ["memLeak"]; + | _ -> () + +let enableAnalysesForMemSafetySpecification () = + List.iter enableAnalysesForMemSafetySpecification (Svcomp.Specification.of_option ()) + +let enableAnalysesForTerminationSpecification (spec: Svcomp.Specification.t) = + match spec with + | Termination -> enableSpecAnalyses spec ["termination"]; + | _ -> () + +let enableAnalysesForTerminationSpecification () = + List.iter enableAnalysesForTerminationSpecification (Svcomp.Specification.of_option ()) + +let enableAnalysesForSpecification (spec: Svcomp.Specification.t) = + match spec with + | UnreachCall s -> () + | NoDataRace -> enableSpecAnalyses spec ["access"; "race"]; + | NoOverflow -> enableOptions ["ana.int.interval"] + | _ -> () + +let enableAnalysesForSpecification () = + List.iter enableAnalysesForSpecification (Svcomp.Specification.of_option ()) + +(* This is run independent of the autotuner being enabled or not to be sound in the presence of setjmp/longjmp *) +(* It is done this way around to allow enabling some of these analyses also for programs without longjmp *) +let longjmpAnalyses = ["activeLongjmp"; "activeSetjmp"; "taintPartialContexts"; "modifiedSinceSetjmp"; "poisonVariables"; "expsplit"; "vla"] + +let activateLongjmpAnalysesWhenRequired () = + let isLongjmp (desc: LibraryDesc.t) args = + match desc.special args with + | LibraryDesc.Longjmp _ -> true + | _ -> false + in + if hasFunction isLongjmp then ( + Logs.info "longjmp -> enabling longjmp analyses \"%s\"" (String.concat ", " longjmpAnalyses); + enableAnalyses longjmpAnalyses; + ) diff --git a/src/util/backtrace/goblint_backtrace.ml b/src/util/backtrace/goblint_backtrace.ml index 29198c27ea..21d150a470 100644 --- a/src/util/backtrace/goblint_backtrace.ml +++ b/src/util/backtrace/goblint_backtrace.ml @@ -36,6 +36,15 @@ let protect ~(mark: unit -> mark) ~(finally: unit -> unit) work = add_mark work_exn (mark ()); Printexc.raise_with_backtrace work_exn work_bt +(* Copied & modified from protect. *) +let wrap_val ~(mark:mark) work = + try + work () + with work_exn -> + let work_bt = Printexc.get_raw_backtrace () in + add_mark work_exn mark; + Printexc.raise_with_backtrace work_exn work_bt + let mark_printers: (mark -> string option) list ref = ref [] @@ -67,12 +76,19 @@ let print_marktrace oc e = Printf.fprintf oc "Marked with %s\n" (mark_to_string m) ) ms +let print_innermost_mark oc e = + match find_marks e with + | m :: _ -> Printf.fprintf oc "Marked with %s\n" (mark_to_string m) + | [] -> () + let () = Printexc.set_uncaught_exception_handler (fun e bt -> (* Copied & modified from Printexc.default_uncaught_exception_handler. *) Printf.eprintf "Fatal error: exception %s\n" (Printexc.to_string e); (* nosemgrep: print-not-logging *) if Printexc.backtrace_status () then - print_marktrace stderr e; + print_marktrace stderr e + else + print_innermost_mark stderr e; Printexc.print_raw_backtrace stderr bt; flush stderr ) diff --git a/src/util/backtrace/goblint_backtrace.mli b/src/util/backtrace/goblint_backtrace.mli index e2b6ed2913..ee7052122e 100644 --- a/src/util/backtrace/goblint_backtrace.mli +++ b/src/util/backtrace/goblint_backtrace.mli @@ -24,10 +24,18 @@ val add_mark: exn -> mark -> unit val protect: mark:(unit -> mark) -> finally:(unit -> unit) -> (unit -> 'a) -> 'a (** {!Fun.protect} with additional [~mark] addition to all exceptions. *) +val wrap_val: mark:mark -> (unit -> 'a) -> 'a +(** {!protect} with [~mark] value and without [~finally]. *) + val print_marktrace: out_channel -> exn -> unit (** Print trace of marks of an exception. Used by default for uncaught exceptions. *) +val print_innermost_mark: out_channel -> exn -> unit +(** Print innermost mark of an exception. + + Used by default for uncaught exceptions. *) + val find_marks: exn -> mark list (** Find all marks of an exception. *) diff --git a/src/util/library/libraryDesc.ml b/src/util/library/libraryDesc.ml index ef22dc0df0..6f34de1864 100644 --- a/src/util/library/libraryDesc.ml +++ b/src/util/library/libraryDesc.ml @@ -56,6 +56,7 @@ type special = | ThreadCreate of { thread: Cil.exp; start_routine: Cil.exp; arg: Cil.exp; multiple: bool } | ThreadJoin of { thread: Cil.exp; ret_var: Cil.exp; } | ThreadExit of { ret_val: Cil.exp; } + | ThreadSelf | Globalize of Cil.exp | Signal of Cil.exp | Broadcast of Cil.exp @@ -91,16 +92,6 @@ module Accesses = struct type t = Cil.exp list -> (Access.t * Cil.exp list) list - (* TODO: remove after migration *) - type old = AccessKind.t -> Cil.exp list -> Cil.exp list - let of_old (f: old): t = fun args -> - [ - ({ kind = Read; deep = true; }, f Read args); - ({ kind = Write; deep = true; }, f Write args); - ({ kind = Free; deep = true; }, f Free args); - ({ kind = Spawn; deep = true; }, f Spawn args); - ] - (* TODO: remove/rename after migration? *) let find (accs: t): Access.t -> Cil.exp list -> Cil.exp list = fun acc args -> BatOption.(List.assoc_opt acc (accs args) |? []) @@ -136,12 +127,6 @@ type t = { attrs: attr list; (** Attributes of function. *) } -let of_old ?(attrs: attr list=[]) (old_accesses: Accesses.old): t = { - attrs; - accs = Accesses.of_old old_accesses; - special = fun _ -> Unknown; -} - module MathPrintable = struct include Printable.StdLeaf type t = math [@@deriving eq, ord, hash] diff --git a/src/util/library/libraryDsl.ml b/src/util/library/libraryDsl.ml index 49aac8ce9b..a380714dc0 100644 --- a/src/util/library/libraryDsl.ml +++ b/src/util/library/libraryDsl.ml @@ -30,8 +30,20 @@ struct | [] -> fail "^::" end +type access = + | Access of LibraryDesc.Access.t + | If of (unit -> bool) * access + +let rec eval_access = function + | Access acc -> Some acc + | If (p, access) -> + if p () then + eval_access access + else + None + type ('k, 'l, 'r) arg_desc = { - accesses: Access.t list; + accesses: access list; match_arg: (Cil.exp, 'k, 'r) Pattern.t; match_var_args: (Cil.exp list, 'l, 'r) Pattern.t; } @@ -51,15 +63,21 @@ let rec accs: type k r. (k, r) args_desc -> Accesses.t = fun args_desc args -> match args_desc, args with | [], [] -> [] | VarArgs arg_desc, args -> - List.map (fun acc -> - (acc, args) + List.filter_map (fun access -> + match eval_access access with + | Some acc -> Some (acc, args) + | None -> None ) arg_desc.accesses | arg_desc :: args_desc, arg :: args -> let accs'' = accs args_desc args in - List.fold_left (fun (accs'': (Access.t * Cil.exp list) list) (acc: Access.t) -> - match List.assoc_opt acc accs'' with - | Some args -> (acc, arg :: args) :: List.remove_assoc acc accs'' - | None -> (acc, [arg]) :: accs'' + List.fold_left (fun (accs'': (Access.t * Cil.exp list) list) (access: access) -> + match eval_access access with + | Some acc -> + begin match List.assoc_opt acc accs'' with + | Some args -> (acc, arg :: args) :: List.remove_assoc acc accs'' + | None -> (acc, [arg]) :: accs'' + end + | None -> accs'' ) accs'' arg_desc.accesses | _, _ -> invalid_arg "accs" @@ -94,11 +112,15 @@ let drop (_name: string) accesses = { empty_drop_desc with accesses; } let drop' accesses = { empty_drop_desc with accesses; } -let r = Access.{ kind = Read; deep = false; } -let r_deep = Access.{ kind = Read; deep = true; } -let w = Access.{ kind = Write; deep = false; } -let w_deep = Access.{ kind = Write; deep = true; } -let f = Access.{ kind = Free; deep = false; } -let f_deep = Access.{ kind = Free; deep = true; } -let s = Access.{ kind = Spawn; deep = false; } -let s_deep = Access.{ kind = Spawn; deep = true; } +let r = Access { kind = Read; deep = false; } +let r_deep = Access { kind = Read; deep = true; } +let w = Access { kind = Write; deep = false; } +let w_deep = Access { kind = Write; deep = true; } +let f = Access { kind = Free; deep = false; } +let f_deep = Access { kind = Free; deep = true; } +let s = Access { kind = Spawn; deep = false; } +let s_deep = Access { kind = Spawn; deep = true; } +let c = Access { kind = Spawn; deep = false; } (* TODO: Sound, but very imprecise hack for calls to function pointers given as arguments. *) +let c_deep = Access { kind = Spawn; deep = true; } + +let if_ p access = If (p, access) diff --git a/src/util/library/libraryDsl.mli b/src/util/library/libraryDsl.mli index fd0bc45c26..42c300af8e 100644 --- a/src/util/library/libraryDsl.mli +++ b/src/util/library/libraryDsl.mli @@ -28,46 +28,57 @@ val special': ?attrs:LibraryDesc.attr list -> (LibraryDesc.special, LibraryDesc. (** Create unknown library function descriptor from arguments descriptor, which must {!drop} all arguments. *) val unknown: ?attrs:LibraryDesc.attr list -> (LibraryDesc.special, LibraryDesc.special) args_desc -> LibraryDesc.t +(** Argument access descriptor. *) +type access (** Argument descriptor, which captures the named argument with accesses for continuation function of {!special}. *) -val __: string -> LibraryDesc.Access.t list -> (Cil.exp -> 'r, Cil.exp list -> 'r, 'r) arg_desc +val __: string -> access list -> (Cil.exp -> 'r, Cil.exp list -> 'r, 'r) arg_desc (** Argument descriptor, which captures an unnamed argument with accesses for continuation function of {!special}. *) -val __': LibraryDesc.Access.t list -> (Cil.exp -> 'r, Cil.exp list -> 'r, 'r) arg_desc +val __': access list -> (Cil.exp -> 'r, Cil.exp list -> 'r, 'r) arg_desc (** Argument descriptor, which drops (does not capture) the named argument with accesses. *) -val drop: string -> LibraryDesc.Access.t list -> ('r, 'r, 'r) arg_desc +val drop: string -> access list -> ('r, 'r, 'r) arg_desc (** Argument descriptor, which drops (does not capture) an unnamed argument with accesses. *) -val drop': LibraryDesc.Access.t list -> ('r, 'r, 'r) arg_desc +val drop': access list -> ('r, 'r, 'r) arg_desc (** Shallow {!AccessKind.Read} access. All immediate arguments of function calls are always read, this specifies the reading of pointed-to values. *) -val r: LibraryDesc.Access.t +val r: access (** Deep {!AccessKind.Read} access. All immediate arguments of function calls are always read, this specifies the reading of pointed-to values. Rarely needed. *) -val r_deep: LibraryDesc.Access.t +val r_deep: access (** Shallow {!AccessKind.Write} access. *) -val w: LibraryDesc.Access.t +val w: access (** Deep {!AccessKind.Write} access. Rarely needed. *) -val w_deep: LibraryDesc.Access.t +val w_deep: access (** Shallow {!AccessKind.Free} access. *) -val f: LibraryDesc.Access.t +val f: access (** Deep {!AccessKind.Free} access. Rarely needed. *) -val f_deep: LibraryDesc.Access.t +val f_deep: access (** Shallow {!AccessKind.Spawn} access. *) -val s: LibraryDesc.Access.t +val s: access (** Deep {!AccessKind.Spawn} access. Rarely needed. *) -val s_deep: LibraryDesc.Access.t +val s_deep: access + +(** Shallow {!AccessKind.Spawn} access, substituting function pointer calls for now (TODO). *) +val c: access + +(** Deep {!AccessKind.Spawn} access, substituting deep function pointer calls for now (TODO) *) +val c_deep: access + +(** Conditional access, e.g. on an option. *) +val if_: (unit -> bool) -> access -> access diff --git a/src/util/library/libraryFunctions.ml b/src/util/library/libraryFunctions.ml index e27b8ce8fd..6643b58eef 100644 --- a/src/util/library/libraryFunctions.ml +++ b/src/util/library/libraryFunctions.ml @@ -6,6 +6,16 @@ open GobConfig module M = Messages +let intmax_t = lazy ( + let res = ref None in + GoblintCil.iterGlobals !Cilfacade.current_file (function + | GType ({tname = "intmax_t"; ttype; _}, _) -> + res := Some ttype; + | _ -> () + ); + !res +) + (** C standard library functions. These are specified by the C standard. *) let c_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ @@ -63,6 +73,7 @@ let c_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ ("localeconv", unknown ~attrs:[ThreadUnsafe] []); ("localtime", unknown ~attrs:[ThreadUnsafe] [drop "time" [r]]); ("strlen", special [__ "s" [r]] @@ fun s -> Strlen s); + ("_strlen", special [__ "s" [r]] @@ fun s -> Strlen s); ("__builtin_strlen", special [__ "s" [r]] @@ fun s -> Strlen s); ("strstr", special [__ "haystack" [r]; __ "needle" [r]] @@ fun haystack needle -> Strstr { haystack; needle; }); ("strcmp", special [__ "s1" [r]; __ "s2" [r]] @@ fun s1 s2 -> Strcmp { s1; s2; n = None; }); @@ -108,7 +119,9 @@ let c_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ ("strtoul", unknown [drop "nptr" [r]; drop "endptr" [w]; drop "base" []]); ("strtoull", unknown [drop "nptr" [r]; drop "endptr" [w]; drop "base" []]); ("tolower", unknown [drop "ch" []]); + ("__tolower", unknown [drop "ch" []]); ("toupper", unknown [drop "ch" []]); + ("__toupper", unknown [drop "ch" []]); ("time", unknown [drop "arg" [w]]); ("tmpnam", unknown ~attrs:[ThreadUnsafe] [drop "filename" [w]]); ("vprintf", unknown [drop "format" [r]; drop "vlist" [r_deep]]); (* TODO: what to do with a va_list type? is r_deep correct? *) @@ -136,13 +149,13 @@ let c_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ ("abs", special [__ "j" []] @@ fun j -> Math { fun_args = (Abs (IInt, j)) }); ("labs", special [__ "j" []] @@ fun j -> Math { fun_args = (Abs (ILong, j)) }); ("llabs", special [__ "j" []] @@ fun j -> Math { fun_args = (Abs (ILongLong, j)) }); - ("imaxabs", unknown [drop "j" []]); + ("imaxabs", special [__ "j" []] @@ fun j -> Math { fun_args = (Abs (Cilfacade.get_ikind (Option.get (Lazy.force intmax_t)), j)) }); ("localtime_r", unknown [drop "timep" [r]; drop "result" [w]]); ("strpbrk", unknown [drop "s" [r]; drop "accept" [r]]); ("_setjmp", special [__ "env" [w]] @@ fun env -> Setjmp { env }); (* only has one underscore *) ("setjmp", special [__ "env" [w]] @@ fun env -> Setjmp { env }); ("longjmp", special [__ "env" [r]; __ "value" []] @@ fun env value -> Longjmp { env; value }); - ("atexit", unknown [drop "function" [s]]); + ("atexit", unknown [drop "function" [if_ (fun () -> not (get_bool "sem.atexit.ignore")) s]]); ("atoi", unknown [drop "nptr" [r]]); ("atol", unknown [drop "nptr" [r]]); ("atoll", unknown [drop "nptr" [r]]); @@ -163,6 +176,9 @@ let c_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ ("raise", unknown [drop "sig" []]); (* safe-ish, we don't handle signal handlers for now *) ("timespec_get", unknown [drop "ts" [w]; drop "base" []]); ("signal", unknown [drop "signum" []; drop "handler" [s]]); + ("va_arg", unknown [drop "ap" [r]; drop "T" []]); + ("va_start", unknown [drop "ap" [r_deep]; drop "parmN" []]); + ("va_end", unknown [drop "ap" [r_deep]]); ] [@@coverage off] @@ -312,6 +328,7 @@ let posix_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ ("send", unknown [drop "sockfd" []; drop "buf" [r]; drop "len" []; drop "flags" []]); ("sendto", unknown [drop "sockfd" []; drop "buf" [r]; drop "len" []; drop "flags" []; drop "dest_addr" [r_deep]; drop "addrlen" []]); ("strdup", unknown [drop "s" [r]]); + ("__strdup", unknown [drop "s" [r]]); ("strndup", unknown [drop "s" [r]; drop "n" []]); ("__strndup", unknown [drop "s" [r]; drop "n" []]); ("syscall", unknown (drop "number" [] :: VarArgs (drop' [r; w]))); @@ -425,6 +442,7 @@ let posix_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ ("stpcpy", unknown [drop "dest" [w]; drop "src" [r]]); ("dup", unknown [drop "oldfd" []]); ("readdir_r", unknown [drop "dirp" [r_deep]; drop "entry" [r_deep]; drop "result" [w]]); + ("scandir", unknown [drop "dirp" [r]; drop "namelist" [w]; drop "filter" [r; c]; drop "compar" [r; c]]); ("pipe", unknown [drop "pipefd" [w_deep]]); ("waitpid", unknown [drop "pid" []; drop "wstatus" [w]; drop "options" []]); ("strerror_r", unknown [drop "errnum" []; drop "buff" [w]; drop "buflen" []]); @@ -433,6 +451,11 @@ let posix_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ ("times", unknown [drop "buf" [w]]); ("mmap", unknown [drop "addr" []; drop "length" []; drop "prot" []; drop "flags" []; drop "fd" []; drop "offset" []]); ("munmap", unknown [drop "addr" []; drop "length" []]); + ("getline", unknown [drop "lineptr" [r_deep; w_deep]; drop "n" [r; w]; drop "stream" [r_deep; w_deep]]); + ("getwline", unknown [drop "lineptr" [r_deep; w_deep]; drop "n" [r; w]; drop "stream" [r_deep; w_deep]]); + ("getdelim", unknown [drop "lineptr" [r_deep; w_deep]; drop "n" [r; w]; drop "delimiter" []; drop "stream" [r_deep; w_deep]]); + ("__getdelim", unknown [drop "lineptr" [r_deep; w_deep]; drop "n" [r; w]; drop "delimiter" []; drop "stream" [r_deep; w_deep]]); + ("getwdelim", unknown [drop "lineptr" [r_deep; w_deep]; drop "n" [r; w]; drop "delimiter" []; drop "stream" [r_deep; w_deep]]); ] [@@coverage off] @@ -491,7 +514,7 @@ let pthread_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ ("pthread_attr_setstacksize", unknown [drop "attr" [w]; drop "stacksize" []]); ("pthread_attr_getscope", unknown [drop "attr" [r]; drop "scope" [w]]); ("pthread_attr_setscope", unknown [drop "attr" [w]; drop "scope" []]); - ("pthread_self", unknown []); + ("pthread_self", special [] ThreadSelf); ("pthread_sigmask", unknown [drop "how" []; drop "set" [r]; drop "oldset" [w]]); ("pthread_setspecific", unknown ~attrs:[InvalidateGlobals] [drop "key" []; drop "value" [w_deep]]); ("pthread_getspecific", unknown ~attrs:[InvalidateGlobals] [drop "key" []]); @@ -540,6 +563,13 @@ let gcc_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ ("__assert", special [drop "assertion" [r]; drop "file" [r]; drop "line" []] @@ Abort); (* header says: The following is not at all used here but needed for standard compliance. *) ("__builtin_return_address", unknown [drop "level" []]); ("__builtin___sprintf_chk", unknown (drop "s" [w] :: drop "flag" [] :: drop "os" [] :: drop "fmt" [r] :: VarArgs (drop' [r]))); + ("__builtin___snprintf_chk", unknown (drop "s" [w] :: drop "maxlen" [] :: drop "flag" [] :: drop "os" [] :: drop "fmt" [r] :: VarArgs (drop' [r]))); + ("__builtin___vsprintf_chk", unknown [drop "s" [w]; drop "flag" []; drop "os" []; drop "fmt" [r]; drop "ap" [r_deep]]); (* TODO: what to do with a va_list type? is r_deep correct? *) + ("__builtin___vsnprintf_chk", unknown [drop "s" [w]; drop "maxlen" []; drop "flag" []; drop "os" []; drop "fmt" [r]; drop "ap" [r_deep]]); (* TODO: what to do with a va_list type? is r_deep correct? *) + ("__builtin___printf_chk", unknown (drop "flag" [] :: drop "format" [r] :: VarArgs (drop' [r]))); + ("__builtin___vprintf_chk", unknown [drop "flag" []; drop "format" [r]; drop "ap" [r_deep]]); (* TODO: what to do with a va_list type? is r_deep correct? *) + ("__builtin___fprintf_chk", unknown (drop "stream" [r_deep; w_deep] :: drop "flag" [] :: drop "format" [r] :: VarArgs (drop' [r]))); + ("__builtin___vfprintf_chk", unknown [drop "stream" [r_deep; w_deep]; drop "flag" []; drop "format" [r]; drop "ap" [r_deep]]); ("__builtin_add_overflow", unknown [drop "a" []; drop "b" []; drop "c" [w]]); ("__builtin_sadd_overflow", unknown [drop "a" []; drop "b" []; drop "c" [w]]); ("__builtin_saddl_overflow", unknown [drop "a" []; drop "b" []; drop "c" [w]]); @@ -594,6 +624,12 @@ let gcc_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ ("__builtin_va_copy", unknown [drop "dest" [w]; drop "src" [r]]); ("alloca", special [__ "size" []] @@ fun size -> Alloca size); ("__builtin_alloca", special [__ "size" []] @@ fun size -> Alloca size); + ("__builtin_vsnprintf", unknown [drop "str" [w]; drop "size" []; drop "format" [r]; drop "ap" [r_deep]]); + ("__builtin___vsnprintf", unknown [drop "str" [w]; drop "size" []; drop "format" [r]; drop "ap" [r_deep]]); (* TODO: does this actually exist?! *) + ("__builtin_va_arg", unknown [drop "ap" [r_deep]; drop "T" []; drop "lhs" [w]]); (* cil: "__builtin_va_arg is special: in CIL, the left hand side is stored as the last argument" *) + ("__builtin_va_start", unknown [drop "ap" [r_deep]]); (* cil: "When we parse builtin_{va,stdarg}_start, we drop the second argument" *) + ("__builtin_va_end", unknown [drop "ap" [r_deep]]); + ("__builtin_va_arg_pack_len", unknown []); ] [@@coverage off] @@ -625,6 +661,7 @@ let glibc_desc_list: (string * LibraryDesc.t) list = LibraryDsl.[ ("__readlink_alias", unknown [drop "path" [r]; drop "buf" [w]; drop "len" []]); ("__overflow", unknown [drop "f" [r]; drop "ch" []]); ("__ctype_get_mb_cur_max", unknown []); + ("__maskrune", unknown [drop "c" []; drop "f" []]); ("__xmknod", unknown [drop "ver" []; drop "path" [r]; drop "mode" []; drop "dev" [r; w]]); ("yp_get_default_domain", unknown [drop "outdomain" [w]]); ("__nss_configure_lookup", unknown [drop "db" [r]; drop "service_line" [r]]); @@ -657,6 +694,18 @@ let glibc_desc_list: (string * LibraryDesc.t) list = LibraryDsl.[ ("getdtablesize", unknown []); ("daemon", unknown [drop "nochdir" []; drop "noclose" []]); ("putw", unknown [drop "w" []; drop "stream" [r_deep; w_deep]]); + (* RPC library start *) + ("clntudp_create", unknown [drop "addr" [r]; drop "prognum" []; drop "versnum" []; drop "wait" [r]; drop "sockp" [w]]); + ("clntudp_bufcreate", unknown [drop "addr" [r]; drop "prognum" []; drop "versnum" []; drop "wait" [r]; drop "sockp" [w]; drop "sendsize" []; drop "recosize" []]); + ("svctcp_create", unknown [drop "sock" []; drop "send_buf_size" []; drop "recv_buf_size" []]); + ("authunix_create_default", unknown []); + ("clnt_broadcast", unknown [drop "prognum" []; drop "versnum" []; drop "procnum" []; drop "inproc" [r; c]; drop "in" [w]; drop "outproc" [r; c]; drop "out" [w]; drop "eachresult" [c]]); + ("clnt_sperrno", unknown [drop "stat" []]); + ("pmap_unset", unknown [drop "prognum" []; drop "versnum" []]); + ("svcudp_create", unknown [drop "sock" []]); + ("svc_register", unknown [drop "xprt" [r_deep; w_deep]; drop "prognum" []; drop "versnum" []; drop "dispatch" [r; w; c]; drop "protocol" []]); + ("svc_run", unknown []); (* TODO: make new special kind "NoReturn" for this: the following node will be dead (like Abort), but the program doesn't exit (so it shouldn't be Abort) *) + (* RPC library end *) ] [@@coverage off] @@ -669,7 +718,13 @@ let linux_userspace_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ ("epoll_create", unknown [drop "size" []]); ("epoll_ctl", unknown [drop "epfd" []; drop "op" []; drop "fd" []; drop "event" [w]]); ("epoll_wait", unknown [drop "epfd" []; drop "events" [w]; drop "maxevents" []; drop "timeout" []]); + ("__error", unknown []); + ("__errno", unknown []); + ("__errno_location", unknown []); + ("__h_errno_location", unknown []); + ("__printf_chk", unknown (drop "flag" [] :: drop "format" [r] :: VarArgs (drop' [r]))); ("__fprintf_chk", unknown (drop "stream" [r_deep; w_deep] :: drop "flag" [] :: drop "format" [r] :: VarArgs (drop' [r]))); + ("__vfprintf_chk", unknown [drop "stream" [r_deep; w_deep]; drop "flag" []; drop "format" [r]; drop "ap" [r_deep]]); ("sysinfo", unknown [drop "info" [w_deep]]); ("__xpg_basename", unknown [drop "path" [r]]); ("ptrace", unknown (drop "request" [] :: VarArgs (drop' [r_deep; w_deep]))); (* man page has 4 arguments, but header has varargs and real-world programs may call with <4 *) @@ -691,6 +746,11 @@ let linux_userspace_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ ("process_vm_readv", unknown [drop "pid" []; drop "local_iov" [w_deep]; drop "liovcnt" []; drop "remote_iov" []; drop "riovcnt" []; drop "flags" []]); ("__libc_current_sigrtmax", unknown []); ("__libc_current_sigrtmin", unknown []); + ("__xstat", unknown [drop "ver" []; drop "path" [r]; drop "stat_buf" [w]]); + ("__lxstat", unknown [drop "ver" []; drop "path" [r]; drop "stat_buf" [w]]); + ("__fxstat", unknown [drop "ver" []; drop "fildes" []; drop "stat_buf" [w]]); + ("__ctype_b_loc", unknown []); + ("_IO_getc", unknown [drop "f" [r_deep; w_deep]]); ] [@@coverage off] @@ -706,11 +766,13 @@ let linux_kernel_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ ("up_read", special [__ "sem" []] @@ fun sem -> Unlock sem); ("up_write", special [__ "sem" []] @@ fun sem -> Unlock sem); ("mutex_init", unknown [drop "mutex" []]); + ("__mutex_init", unknown [drop "lock" []; drop "name" [r]; drop "key" [r]]); ("mutex_lock", special [__ "lock" []] @@ fun lock -> Lock { lock = lock; try_ = get_bool "sem.lock.fail"; write = true; return_on_success = true }); ("mutex_trylock", special [__ "lock" []] @@ fun lock -> Lock { lock = lock; try_ = true; write = true; return_on_success = true }); ("mutex_lock_interruptible", special [__ "lock" []] @@ fun lock -> Lock { lock = lock; try_ = get_bool "sem.lock.fail"; write = true; return_on_success = true }); ("mutex_unlock", special [__ "lock" []] @@ fun lock -> Unlock lock); ("spin_lock_init", unknown [drop "lock" []]); + ("__spin_lock_init", unknown [drop "lock" []]); ("spin_lock", special [__ "lock" []] @@ fun lock -> Lock { lock = lock; try_ = get_bool "sem.lock.fail"; write = true; return_on_success = true }); ("_spin_lock", special [__ "lock" []] @@ fun lock -> Lock { lock = lock; try_ = get_bool "sem.lock.fail"; write = true; return_on_success = true }); ("_spin_lock_bh", special [__ "lock" []] @@ fun lock -> Lock { lock = lock; try_ = get_bool "sem.lock.fail"; write = true; return_on_success = true }); @@ -756,7 +818,12 @@ let linux_kernel_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ ("__kmalloc", special [__ "size" []; drop "flags" []] @@ fun size -> Malloc size); ("kzalloc", special [__ "size" []; drop "flags" []] @@ fun size -> Calloc {count = Cil.one; size}); ("usb_alloc_urb", special [__ "iso_packets" []; drop "mem_flags" []] @@ fun iso_packets -> Malloc MyCFG.unknown_exp); + ("usb_submit_urb", unknown [drop "urb" [r_deep; w_deep; c_deep]; drop "mem_flags" []]); (* old comment: first argument is written to but according to specification must not be read from anymore *) + ("dev_driver_string", unknown [drop "dev" [r_deep]]); ("ioctl", unknown (drop "fd" [] :: drop "request" [] :: VarArgs (drop' [r_deep; w_deep]))); + ("idr_pre_get", unknown [drop "idp" [r_deep]; drop "gfp_mask" []]); + ("printk", unknown (drop "fmt" [r] :: VarArgs (drop' [r]))); + ("kmem_cache_create", unknown [drop "name" [r]; drop "size" []; drop "align" []; drop "flags" []; drop "ctor" [r; c]]); ] [@@coverage off] @@ -770,6 +837,7 @@ let goblint_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ ("__goblint_split_begin", unknown [drop "exp" []]); ("__goblint_split_end", unknown [drop "exp" []]); ("__goblint_bounded", special [__ "exp"[]] @@ fun exp -> Bounded { exp }); + ("__goblint_assume_join", unknown [drop "tid" []]); ] [@@coverage off] @@ -1029,6 +1097,7 @@ let svcomp_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ ("__VERIFIER_nondet_int", unknown []); (* declare invalidate actions to prevent invalidating globals when extern in regression tests *) ("__VERIFIER_nondet_size_t", unknown []); (* cannot give it in sv-comp.c without including stdlib or similar *) ("__VERIFIER_assert", special [__ "exp" []] @@ fun exp -> Assert { exp; check = true; refine = get_bool "sem.assert.refine" }); (* only used if definition missing (e.g. in evalAssert transformed output) or extraspecial *) + ("reach_error", special [] @@ Abort); (* only used if definition missing (e.g. in evalAssert transformed output) or extraspecial *) ] [@@coverage off] @@ -1045,6 +1114,8 @@ let klever_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ ("rtnl_lock", special [] @@ Lock { lock = rtnl_lock; try_ = false; write = true; return_on_success = true }); ("rtnl_unlock", special [] @@ Unlock rtnl_lock); ("__rtnl_unlock", special [] @@ Unlock rtnl_lock); + (* ddverify *) + ("sema_init", unknown [drop "sem" []; drop "val" []]); ] [@@coverage off] @@ -1111,6 +1182,8 @@ let zlib_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ ("gzdopen", unknown [drop "fd" []; drop "mode" [r]]); ("gzread", unknown [drop "file" [r_deep; w_deep]; drop "buf" [w]; drop "len" []]); ("gzclose", unknown [drop "file" [f_deep]]); + ("uncompress", unknown [drop "dest" [w]; drop "destLen" [r; w]; drop "source" [r]; drop "sourceLen" []]); + ("compress2", unknown [drop "dest" [w]; drop "destLen" [r; w]; drop "source" [r]; drop "sourceLen" []; drop "level" []]); ] [@@coverage off] @@ -1127,6 +1200,32 @@ let liblzma_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ ] [@@coverage off] +let legacy_libs_misc_list: (string * LibraryDesc.t) list = LibraryDsl.[ + ("__open_alias", unknown (drop "path" [r] :: drop "oflag" [] :: VarArgs (drop' [r]))); + ("__open_2", unknown [drop "file" [r]; drop "oflag" []]); + ("__open_too_many_args", unknown []); + (* bzlib *) + ("BZ2_bzBuffToBuffCompress", unknown [drop "dest" [w]; drop "destLen" [r; w]; drop "source" [r]; drop "sourceLen" []; drop "blockSize100k" []; drop "verbosity" []; drop "workFactor" []]); + ("BZ2_bzBuffToBuffDecompress", unknown [drop "dest" [w]; drop "destLen" [r; w]; drop "source" [r]; drop "sourceLen" []; drop "small" []; drop "verbosity" []]); + (* opensssl blowfish *) + ("BF_cfb64_encrypt", unknown [drop "in" [r]; drop "out" [w]; drop "length" []; drop "schedule" [r]; drop "ivec" [r; w]; drop "num" [r; w]; drop "enc" []]); + ("BF_set_key", unknown [drop "key" [w]; drop "len" []; drop "data" [r]]); + (* libintl *) + ("textdomain", unknown [drop "domainname" [r]]); + ("bindtextdomain", unknown [drop "domainname" [r]; drop "dirname" [r]]); + ("dcgettext", unknown [drop "domainname" [r]; drop "msgid" [r]; drop "category" []]); + (* TODO: the __extinline suffix was added by CIL in the old times in some cases, but is now switched off for like 10 years *) + ("strtoul__extinline", unknown [drop "nptr" [r]; drop "endptr" [w]; drop "base" []]); + ("atoi__extinline", unknown [drop "nptr" [r]]); + ("stat__extinline", unknown [drop "pathname" [r]; drop "statbuf" [w]]); + ("lstat__extinline", unknown [drop "pathname" [r]; drop "statbuf" [w]]); + ("fstat__extinline", unknown [drop "fd" []; drop "buf" [w]]); + (* only in knot *) + ("PL_NewHashTable", unknown [drop "n" []; drop "keyHash" [r]; drop "keyCompare" [r]; drop "valueCompare" [r]; drop "allocOps" [r]; drop "allocPriv" [r]]); (* TODO: should have call instead of read *) + ("assert_failed", unknown [drop "file" [r]; drop "line" []; drop "func" [r]; drop "exp" [r]]); + ] +[@@coverage off] + let libraries = Hashtbl.of_list [ ("c", c_descs_list @ math_descs_list); ("posix", posix_descs_list); @@ -1143,6 +1242,7 @@ let libraries = Hashtbl.of_list [ ("pcre", pcre_descs_list); ("zlib", zlib_descs_list); ("liblzma", liblzma_descs_list); + ("legacy", legacy_libs_misc_list); ] let libraries = @@ -1157,7 +1257,7 @@ let libraries = descs_tbl ) libraries -let all_library_descs: (string, LibraryDesc.t) Hashtbl.t = +let _all_library_descs: (string, LibraryDesc.t) Hashtbl.t = Hashtbl.fold (fun _ descs_tbl acc -> Hashtbl.merge (fun name desc1 desc2 -> match desc1, desc2 with @@ -1187,183 +1287,6 @@ let activated_library_descs: (string, LibraryDesc.t) Hashtbl.t ResettableLazy.t let reset_lazy () = ResettableLazy.reset activated_library_descs -module Invalidate = -struct - [@@@warning "-unused-value-declaration"] (* some functions are not used below *) - open AccessKind - - let drop = List.drop - let keep ns = List.filteri (fun i _ -> List.mem i ns) - - let partition ns x = - let rec go n = - function - | [] -> ([],[]) - | y :: ys -> - let (i,o) = go (n + 1) ys in - if List.mem n ns - then (y::i, o) - else ( i,y::o) - in - go 1 x - - let writesAllButFirst n f a x = - match a with - | Write | Call | Spawn -> f a x @ drop n x - | Read -> f a x - | Free -> [] - - let readsAllButFirst n f a x = - match a with - | Write | Call | Spawn -> f a x - | Read -> f a x @ drop n x - | Free -> [] - - let reads ns a x = - let i, o = partition ns x in - match a with - | Write | Call | Spawn -> o - | Read -> i - | Free -> [] - - let writes ns a x = - let i, o = partition ns x in - match a with - | Write | Call | Spawn -> i - | Read -> o - | Free -> [] - - let frees ns a x = - let i, o = partition ns x in - match a with - | Write | Call | Spawn -> [] - | Read -> o - | Free -> i - - let readsFrees rs fs a x = - match a with - | Write | Call | Spawn -> [] - | Read -> keep rs x - | Free -> keep fs x - - let onlyReads ns a x = - match a with - | Write | Call | Spawn -> [] - | Read -> keep ns x - | Free -> [] - - let onlyWrites ns a x = - match a with - | Write | Call | Spawn -> keep ns x - | Read -> [] - | Free -> [] - - let readsWrites rs ws a x = - match a with - | Write | Call | Spawn -> keep ws x - | Read -> keep rs x - | Free -> [] - - let readsAll a x = - match a with - | Write | Call | Spawn -> [] - | Read -> x - | Free -> [] - - let writesAll a x = - match a with - | Write | Call | Spawn -> x - | Read -> [] - | Free -> [] -end - -open Invalidate - -(* Data races: which arguments are read/written? - * We assume that no known functions that are reachable are executed/spawned. For that we use ThreadCreate above. *) -(* WTF: why are argument numbers 1-indexed (in partition)? *) -let invalidate_actions = [ - "__printf_chk", readsAll;(*safe*) - "printk", readsAll;(*safe*) - "__mutex_init", readsAll;(*safe*) - "__builtin___snprintf_chk", writes [1];(*keep [1]*) - "__vfprintf_chk", writes [1];(*keep [1]*) - "__builtin_va_arg", readsAll;(*safe*) - "__builtin_va_end", readsAll;(*safe*) - "__builtin_va_start", readsAll;(*safe*) - "__ctype_b_loc", readsAll;(*safe*) - "__errno", readsAll;(*safe*) - "__errno_location", readsAll;(*safe*) - "__strdup", readsAll;(*safe*) - "strtoul__extinline", readsAll;(*safe*) - "atoi__extinline", readsAll;(*safe*) - "_IO_getc", writesAll;(*unsafe*) - "_strlen", readsAll;(*safe*) - "stat__extinline", writesAllButFirst 1 readsAll;(*drop 1*) - "lstat__extinline", writesAllButFirst 1 readsAll;(*drop 1*) - "__open_alias", readsAll;(*safe*) - "__open_2", readsAll;(*safe*) - "fstat__extinline", writesAll;(*unsafe*) - "scandir", writes [1;3;4];(*keep [1;3;4]*) - "bindtextdomain", readsAll;(*safe*) - "textdomain", readsAll;(*safe*) - "dcgettext", readsAll;(*safe*) - "__getdelim", writes [3];(*keep [3]*) - "__h_errno_location", readsAll;(*safe*) - "__fxstat", readsAll;(*safe*) - (* RPC library start *) - "clntudp_create", writesAllButFirst 3 readsAll;(*drop 3*) - "svctcp_create", readsAll;(*safe*) - "clntudp_bufcreate", writesAll;(*unsafe*) - "authunix_create_default", readsAll;(*safe*) - "clnt_broadcast", writesAll;(*unsafe*) - "clnt_sperrno", readsAll;(*safe*) - "pmap_unset", writesAll;(*unsafe*) - "svcudp_create", readsAll;(*safe*) - "svc_register", writesAll;(*unsafe*) - "svc_run", writesAll;(*unsafe*) - (* RPC library end *) - "__builtin___vsnprintf", writesAllButFirst 3 readsAll; (*drop 3*) - "__builtin___vsnprintf_chk", writesAllButFirst 3 readsAll; (*drop 3*) - "__error", readsAll; (*safe*) - "__maskrune", writesAll; (*unsafe*) - "__tolower", readsAll; (*safe*) - "BF_cfb64_encrypt", writes [1;3;4;5]; (*keep [1;3;4,5]*) - "BZ2_bzBuffToBuffDecompress", writes [3;4]; (*keep [3;4]*) - "uncompress", writes [3;4]; (*keep [3;4]*) - "__xstat", writes [3]; (*keep [1]*) - "__lxstat", writes [3]; (*keep [1]*) - "BZ2_bzBuffToBuffCompress", writes [3;4]; (*keep [3;4]*) - "compress2", writes [3]; (*keep [3]*) - "__toupper", readsAll; (*safe*) - "BF_set_key", writes [3]; (*keep [3]*) - "PL_NewHashTable", readsAll; (*safe*) - "assert_failed", readsAll; (*safe*) - "__builtin_va_arg_pack_len", readsAll; - "__open_too_many_args", readsAll; - "usb_submit_urb", readsAll; (* first argument is written to but according to specification must not be read from anymore *) - "dev_driver_string", readsAll; - "__spin_lock_init", writes [1]; - "kmem_cache_create", readsAll; - "idr_pre_get", readsAll; - "zil_replay", writes [1;2;3;5]; - (* ddverify *) - "sema_init", readsAll; - "__goblint_assume_join", readsAll; -] - -let invalidate_actions = - let tbl = Hashtbl.create 113 in - List.iter (fun (name, old_accesses) -> - Hashtbl.modify_opt name (function - | None when Hashtbl.mem all_library_descs name -> failwith (Format.sprintf "Library function %s specified both in libraries and invalidate actions" name) - | None -> Some old_accesses - | Some _ -> failwith (Format.sprintf "Library function %s specified multiple times in invalidate actions" name) - ) tbl - ) invalidate_actions; - tbl - - let lib_funs = ref (Set.String.of_list ["__raw_read_unlock"; "__raw_write_unlock"; "spin_trylock"]) let add_lib_funs funs = lib_funs := List.fold_right Set.String.add funs !lib_funs let use_special fn_name = Set.String.mem fn_name !lib_funs @@ -1375,17 +1298,14 @@ let is_safe_uncalled fn_name = List.exists (fun r -> Str.string_match r fn_name 0) kernel_safe_uncalled_regex -let unknown_desc f = - let old_accesses (kind: AccessKind.t) args = match kind with - | Write when GobConfig.get_bool "sem.unknown_function.invalidate.args" -> args - | Write -> [] - | Read when GobConfig.get_bool "sem.unknown_function.read.args" -> args - | Read -> [] - | Free -> [] - | Call when get_bool "sem.unknown_function.call.args" -> args - | Call -> [] - | Spawn when get_bool "sem.unknown_function.spawn" -> args - | Spawn -> [] +let unknown_desc f : LibraryDesc.t = + let accs args : (LibraryDesc.Access.t * 'a list) list = [ + ({ kind = Read; deep = true; }, if GobConfig.get_bool "sem.unknown_function.read.args" then args else []); + ({ kind = Write; deep = true; }, if GobConfig.get_bool "sem.unknown_function.invalidate.args" then args else []); + ({ kind = Free; deep = true; }, []); (* TODO: why no option? *) + ({ kind = Call; deep = true; }, if get_bool "sem.unknown_function.call" then args else []); + ({ kind = Spawn; deep = true; }, if get_bool "sem.unknown_function.spawn" then args else []); + ] in let attrs: LibraryDesc.attr list = if GobConfig.get_bool "sem.unknown_function.invalidate.globals" then @@ -1398,18 +1318,17 @@ let unknown_desc f = M.msg_final Error ~category:Imprecise ~tags:[Category Unsound] "Function definition missing"; M.error ~category:Imprecise ~tags:[Category Unsound] "Function definition missing for %s" f.vname ); - LibraryDesc.of_old ~attrs old_accesses + { + attrs; + accs; + special = fun _ -> Unknown; + } let find f = let name = f.vname in match Hashtbl.find_option (ResettableLazy.force activated_library_descs) name with | Some desc -> desc - | None -> - match Hashtbl.find_option invalidate_actions name with - | Some old_accesses -> - LibraryDesc.of_old old_accesses - | None -> - unknown_desc f + | None -> unknown_desc f let is_special fv = diff --git a/src/util/loopUnrolling.ml b/src/util/loopUnrolling.ml index e433c34b4a..506337ac1b 100644 --- a/src/util/loopUnrolling.ml +++ b/src/util/loopUnrolling.ml @@ -68,7 +68,7 @@ class isPointedAtVisitor(var) = object inherit nopCilVisitor method! vexpr = function - | AddrOf (Var info, NoOffset) when info.vid == var.vid -> raise Found + | AddrOf (Var info, NoOffset) when CilType.Varinfo.equal info var -> raise Found | _ -> DoChildren end @@ -76,7 +76,7 @@ class hasAssignmentVisitor(var) = object inherit nopCilVisitor method! vinst = function - | Set ((Var info, NoOffset),_,_,_) when info.vid == var.vid -> raise Found + | Set ((Var info, NoOffset),_,_,_) when CilType.Varinfo.equal info var -> raise Found | _ -> SkipChildren end @@ -119,6 +119,20 @@ class findAssignmentConstDiff((diff: Z.t option ref), var) = object | _ -> SkipChildren end +class findStmtContainsInstructions = object + inherit nopCilVisitor + method! vinst = function + | Set _ + | Call _ -> raise Found + | _ -> DoChildren +end + +let containsInstructions stmt = + try + ignore @@ visitCilStmt (new findStmtContainsInstructions) stmt; false + with Found -> + true + let isCompare = function | Lt | Gt | Le | Ge | Ne -> true (*an loop that test for equality can not be of the type we look for*) | _ -> false @@ -154,7 +168,16 @@ let constBefore var loop f = let targetLocation = loopLocation loop in let rec lastAssignmentToVarBeforeLoop (current: (Z.t option)) (statements: stmt list) = match statements with | st::stmts -> ( - let current' = if st.labels <> [] then (Logs.debug "has Label"; (None)) else current in + let current' = + (* If there exists labels that are not the ones inserted by loop unrolling, forget the found assigned constant value *) + if List.exists (function + | Label (s,_,_) -> not (String.starts_with ~prefix:"loop_continue" s || String.starts_with ~prefix:"loop_end" s) + | _ -> true) st.labels + then + (Logs.debug "has Label"; (None)) + else + current + in match st.skind with | Instr list -> ( match lastAssignToVar var list with @@ -210,89 +233,105 @@ let constBefore var loop f = in fst @@ lastAssignmentToVarBeforeLoop (Some Z.zero) f.sbody.bstmts (*the top level call should never return false*) -let rec loopIterations start diff comp = - let flip = function - | Lt -> Gt - | Gt -> Lt - | Ge -> Le - | Le -> Ge - | s -> s - in let loopIterations' goal shouldBeExact = - let range = Z.sub goal start in - if Z.equal diff Z.zero || Z.equal range Z.zero || (Z.gt diff Z.zero && Z.lt range Z.zero) || (Z.lt diff Z.zero && Z.gt range Z.zero) then - None (*unfitting parameters*) - else ( - let roundedDown = Z.div range diff in - let isExact = Z.equal (Z.mul roundedDown diff) range in - if isExact then - Some roundedDown - else if shouldBeExact then - None - else - Some (Z.succ roundedDown) - ) - in - match comp with - | BinOp (op, (Const _ as c), var, t) -> loopIterations start diff (BinOp (flip op, var, c, t)) - | BinOp (Lt, _, (Const (CInt (cint,_,_) )), _) -> if Z.lt diff Z.zero then None else loopIterations' cint false - | BinOp (Gt, _, (Const (CInt (cint,_,_) )), _) -> if Z.gt diff Z.zero then None else loopIterations' cint false - | BinOp (Le, _, (Const (CInt (cint,_,_) )), _) -> if Z.lt diff Z.zero then None else loopIterations' (Z.succ cint) false - | BinOp (Ge, _, (Const (CInt (cint,_,_) )), _) -> if Z.gt diff Z.zero then None else loopIterations' (Z.pred cint) false - | BinOp (Ne, _, (Const (CInt (cint,_,_) )), _) -> loopIterations' cint true +(*find a single break in the else branch of a toplevel if*) +let findBreakComparison loopStatement = + try + let compOption = ref None in + let visitor = new findBreakVisitor (compOption) in + ignore @@ visitCilBlock visitor (loopBody loopStatement); + !compOption + with WrongOrMultiple -> + None + +let findLoopVarAndGoal loopStatement func (op, exp1, exp2) = + match Cil.stripCasts exp1, Cil.stripCasts exp2 with + | Const (CInt (goal, _, _)), Lval (Var varinfo, NoOffset) when not varinfo.vglob -> + (* TODO: define isCompare and flip in cilfacade and refactor to use instead of the many separately defined similar functions *) + let flip = function | Lt -> Gt | Gt -> Lt | Ge -> Le | Le -> Ge | s -> s in + Some (flip op, varinfo, goal) + | Lval (Var varinfo, NoOffset), Const (CInt (goal, _, _)) when not varinfo.vglob -> + Some (op, varinfo, goal) + | Lval (Var varinfo, NoOffset), Lval (Var varinfo2, NoOffset) when not varinfo.vglob && not varinfo2.vglob -> + (* When loop condition has a comparison between variables, we assume that the left one is the counter and right one is the bound. + TODO: can we do something more meaningful instead of this assumption? *) + begin match constBefore varinfo2 loopStatement func with + | Some goal -> Logs.debug "const: %a %a" CilType.Varinfo.pretty varinfo2 GobZ.pretty goal; Some (op, varinfo, goal) + | None -> None + end; + | _ -> None + +let getLoopVar loopStatement func = function + | BinOp (op, exp1, exp2, TInt _) when isCompare op -> findLoopVarAndGoal loopStatement func (op, exp1, exp2) + | _ -> None + +let getsPointedAt var func = + try + let visitor = new isPointedAtVisitor (var) in + ignore @@ visitCilFunction visitor func; + false + with Found -> + true + +let assignmentDifference loop var = + try + let diff = ref None in + let visitor = new findAssignmentConstDiff (diff, var) in + ignore @@ visitCilBlock visitor loop; + !diff + with WrongOrMultiple -> + None + +let adjustGoal diff goal op = + match op with + | Lt -> if Z.lt diff Z.zero then None else Some goal + | Gt -> if Z.gt diff Z.zero then None else Some goal + | Le -> if Z.lt diff Z.zero then None else Some (Z.succ goal) + | Ge -> if Z.gt diff Z.zero then None else Some (Z.pred goal) + | Ne -> Some goal | _ -> failwith "unexpected comparison in loopIterations" -let ( >>= ) = Option.bind -let fixedLoopSize loopStatement func = - let findBreakComparison = try (*find a single break in the else branch of a toplevel if*) - let compOption = ref None in - let visitor = new findBreakVisitor(compOption) in - ignore @@ visitCilBlock visitor (loopBody loopStatement); - !compOption - with | WrongOrMultiple -> None - in let getLoopVar = function - | BinOp (op, (Const (CInt _ )), Lval ((Var info), NoOffset), (TInt _)) - | BinOp (op, Lval ((Var info), NoOffset), (Const (CInt _ )), (TInt _)) when isCompare op && not info.vglob-> - Some info - | _ -> None - in let getsPointedAt var = try - let visitor = new isPointedAtVisitor(var) in - ignore @@ visitCilFunction visitor func; - false - with | Found -> true - in let assignmentDifference loop var = try - let diff = ref None in - let visitor = new findAssignmentConstDiff(diff, var) in - ignore @@ visitCilStmt visitor loop; - !diff - with | WrongOrMultiple -> None - in +let loopIterations start diff goal shouldBeExact = + let range = Z.sub goal start in + if Z.equal diff Z.zero || Z.equal range Z.zero || (Z.gt diff Z.zero && Z.lt range Z.zero) || (Z.lt diff Z.zero && Z.gt range Z.zero) then + None (*unfitting parameters*) + else ( + let roundedDown = Z.div range diff in + let isExact = Z.equal (Z.mul roundedDown diff) range in + if isExact then + Some roundedDown + else if shouldBeExact then + None + else + Some (Z.succ roundedDown) + ) - findBreakComparison >>= fun comparison -> - getLoopVar comparison >>= fun var -> - if getsPointedAt var then +let fixedLoopSize loopStatement func = + let open GobOption.Syntax in + let* comparison = findBreakComparison loopStatement in + let* op, var, goal = getLoopVar loopStatement func comparison in + if getsPointedAt var func then None else - constBefore var loopStatement func >>= fun start -> - assignmentDifference loopStatement var >>= fun diff -> - Logs.debug "comparison: "; - Pretty.fprint stderr (dn_exp () comparison) ~width:max_int; - Logs.debug ""; - Logs.debug "variable: "; - Logs.debug "%s" var.vname; - Logs.debug "start:"; - Logs.debug "%s" @@ Z.to_string start; - Logs.debug "diff:"; - Logs.debug "%s" @@ Z.to_string diff; - let iterations = loopIterations start diff comparison in + let diff = Option.value (assignmentDifference (loopBody loopStatement) var) ~default:Z.one in + (* Assume var start value if there was no constant assignment to the var before loop; + Assume it to be 0, if loop is increasing and 11 (TODO: can we do better than just 11?) if loop is decreasing *) + let start = Option.value (constBefore var loopStatement func) ~default:(if diff < Z.zero then Z.of_int 11 else Z.zero) in + let* goal = adjustGoal diff goal op in + let iterations = loopIterations start diff goal (op=Ne) in + Logs.debug "comparison: %a" CilType.Exp.pretty comparison; + Logs.debug "variable: %s" var.vname; + Logs.debug "start: %a" GobZ.pretty start; + Logs.debug "diff: %a" GobZ.pretty diff; match iterations with | None -> Logs.debug "iterations failed"; None | Some s -> try let s' = Z.to_int s in - Logs.debug "iterations:"; - Logs.debug "%d" s'; + Logs.debug "iterations: %d" s'; Some s' - with Z.Overflow -> Logs.debug "iterations too big for integer"; None + with Z.Overflow -> + Logs.debug "iterations too big for integer"; + None class arrayVisitor = object @@ -305,61 +344,26 @@ class arrayVisitor = object end let annotateArrays loopBody = ignore @@ visitCilBlock (new arrayVisitor) loopBody -(*unroll loops that handle locks, threads and mallocs, asserts and reach_error*) -class loopUnrollingCallVisitor = object - inherit nopCilVisitor - - method! vinst = function - | Call (_,Lval ((Var info), NoOffset),args,_,_) when LibraryFunctions.is_special info -> ( - let desc = LibraryFunctions.find info in - match desc.special args with - | Malloc _ - | Calloc _ - | Realloc _ - | Alloca _ - | Lock _ - | Unlock _ - | ThreadCreate _ - | Assert _ - | Bounded _ - | ThreadJoin _ -> - raise Found; - | _ -> - if List.mem "specification" @@ get_string_list "ana.autotune.activated" && get_string "ana.specification" <> "" then ( - if Svcomp.is_error_function' info (SvcompSpec.of_option ()) then - raise Found - ); - DoChildren - ) - | _ -> DoChildren - -end +let max_default_unrolls_per_spec (spec: Svcomp.Specification.t) = + match spec with + | NoDataRace -> 0 + | NoOverflow -> 2 + | _ -> 4 let loop_unrolling_factor loopStatement func totalLoops = let configFactor = get_int "exp.unrolling-factor" in - if AutoTune0.isActivated "loopUnrollHeuristic" then - let unrollFunctionCalled = try - let thisVisitor = new loopUnrollingCallVisitor in - ignore (visitCilStmt thisVisitor loopStatement); - false; - with - Found -> true - in - (*unroll up to near an instruction count, higher if the loop uses malloc/lock/threads *) - let targetInstructions = if unrollFunctionCalled then 50 else 25 in - let loopStats = AutoTune0.collectFactors visitCilStmt loopStatement in - if loopStats.instructions > 0 then - let fixedLoop = fixedLoopSize loopStatement func in - (* Unroll at least 10 times if there are only few (17?) loops *) - let unroll_min = if totalLoops < 17 && AutoTune0.isActivated "forceLoopUnrollForFewLoops" then 10 else 0 in - match fixedLoop with - | Some i -> if i * loopStats.instructions < 100 then (Logs.debug "fixed loop size"; i) else max unroll_min (100 / loopStats.instructions) - | _ -> max unroll_min (targetInstructions / loopStats.instructions) + if containsInstructions loopStatement then + if AutoTune0.isActivated "loopUnrollHeuristic" then + match fixedLoopSize loopStatement func with + | Some i when i <= 20 -> Logs.debug "fixed loop size %d" i; i + | _ -> + match Svcomp.Specification.of_option () with + | [] -> 4 + | specs -> BatList.max @@ List.map max_default_unrolls_per_spec specs else - (* Don't unroll empty (= while(1){}) loops*) - 0 - else - configFactor + configFactor + else (* Don't unroll empty (= while(1){}) loops*) + 0 (*actual loop unrolling*) @@ -438,16 +442,19 @@ let copy_and_patch_labels break_target current_continue_target stmts = let patchLabelsVisitor = new patchLabelsGotosVisitor(StatementHashTable.find_opt gotos) in List.map (visitCilStmt patchLabelsVisitor) stmts' -class loopUnrollingVisitor(func, totalLoops) = object +class loopUnrollingVisitor (func, totalLoops) = object (* Labels are simply handled by giving them a fresh name. Jumps coming from outside will still always go to the original label! *) inherit nopCilVisitor - method! vstmt s = - let duplicate_and_rem_labels s = - match s.skind with - | Loop (b,loc, loc2, break , continue) -> - let factor = loop_unrolling_factor s func totalLoops in - if(factor > 0) then ( + val mutable nests = 0 + + method! vstmt stmt = + let duplicate_and_rem_labels stmt = + match stmt.skind with + | Loop (b, loc, loc2, break, continue) -> + nests <- nests - 1; Logs.debug "nests: %i" nests; + let factor = loop_unrolling_factor stmt func totalLoops in + if factor > 0 then ( Logs.info "unrolling loop at %a with factor %d" CilType.Location.pretty loc factor; annotateArrays b; (* top-level breaks should immediately go to the end of the loop, and not just break out of the current iteration *) @@ -459,14 +466,19 @@ class loopUnrollingVisitor(func, totalLoops) = object one_copy_stmts @ [current_continue_target] ) in - mkStmt (Block (mkBlock (List.flatten copies @ [s; break_target]))) - ) else s (*no change*) - | _ -> s + mkStmt (Block (mkBlock (List.flatten copies @ [stmt; break_target]))) + ) else stmt (*no change*) + | _ -> stmt in - ChangeDoChildrenPost(s, duplicate_and_rem_labels) + match stmt.skind with + | Loop _ when nests + 1 < 4 -> nests <- nests + 1; ChangeDoChildrenPost(stmt, duplicate_and_rem_labels) + | Loop _ -> SkipChildren + | _ -> ChangeDoChildrenPost(stmt, duplicate_and_rem_labels) end let unroll_loops fd totalLoops = - Cil.populateLabelAlphaTable fd; - let thisVisitor = new loopUnrollingVisitor(fd, totalLoops) in - ignore (visitCilFunction thisVisitor fd) + if not (Cil.hasAttribute "goblint_stub" fd.svar.vattr) then ( + Cil.populateLabelAlphaTable fd; + let thisVisitor = new loopUnrollingVisitor(fd, totalLoops) in + ignore (visitCilFunction thisVisitor fd) + ) diff --git a/src/util/server.ml b/src/util/server.ml index 80ddc81d31..1bdc3e1ea9 100644 --- a/src/util/server.ml +++ b/src/util/server.ml @@ -320,6 +320,7 @@ let () = let process { reset } serve = try analyze serve ~reset; + (* TODO: generalize VerifyError for AnalysisState.unsound_both_branches_dead *) {status = if !AnalysisState.verified = Some false then VerifyError else Success} with | Sys.Break -> @@ -554,7 +555,13 @@ let () = } [@@deriving to_yojson] let process () serv = let module ArgWrapper = (val (ResettableLazy.force serv.arg_wrapper)) in - let module ArgDot = ArgTools.Dot (ArgWrapper.Arg) in + let module NoExtraNodeStyle = + struct + type node = ArgWrapper.Arg.Node.t + let extra_node_styles node = [] + end + in + let module ArgDot = ArgTools.Dot (ArgWrapper.Arg) (NoExtraNodeStyle) in let arg = Format.asprintf "%t" ArgDot.dot in {arg} end); diff --git a/src/util/std/gobYaml.ml b/src/util/std/gobYaml.ml index 624cdbf1fa..4c8576ade2 100644 --- a/src/util/std/gobYaml.ml +++ b/src/util/std/gobYaml.ml @@ -44,3 +44,5 @@ let list = function let entries = function | `O assoc -> Ok assoc | _ -> Error (`Msg "Failed to get entries from non-object value") + +let int i = float (float_of_int i) diff --git a/src/vendor/ppx_blob/LICENSE.txt b/src/vendor/ppx_blob/LICENSE.txt deleted file mode 100644 index 00d2e135a7..0000000000 --- a/src/vendor/ppx_blob/LICENSE.txt +++ /dev/null @@ -1,24 +0,0 @@ -This is free and unencumbered software released into the public domain. - -Anyone is free to copy, modify, publish, use, compile, sell, or -distribute this software, either in source code form or as a compiled -binary, for any purpose, commercial or non-commercial, and by any -means. - -In jurisdictions that recognize copyright laws, the author or authors -of this software dedicate any and all copyright interest in the -software to the public domain. We make this dedication for the benefit -of the public at large and to the detriment of our heirs and -successors. We intend this dedication to be an overt act of -relinquishment in perpetuity of all present and future rights to this -software under copyright law. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR -OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. - -For more information, please refer to \ No newline at end of file diff --git a/src/vendor/ppx_blob/README.md b/src/vendor/ppx_blob/README.md deleted file mode 100644 index fdd7448a32..0000000000 --- a/src/vendor/ppx_blob/README.md +++ /dev/null @@ -1 +0,0 @@ -ppx_blob fix for `(lang dune 3.0)` vendored from . diff --git a/src/vendor/ppx_blob/src/dune b/src/vendor/ppx_blob/src/dune deleted file mode 100644 index 112b1d07c0..0000000000 --- a/src/vendor/ppx_blob/src/dune +++ /dev/null @@ -1,5 +0,0 @@ -(library - (name ppx_blob) - ; (public_name ppx_blob) - (kind ppx_rewriter) - (libraries ppxlib)) \ No newline at end of file diff --git a/src/vendor/ppx_blob/src/ppx_blob.ml b/src/vendor/ppx_blob/src/ppx_blob.ml deleted file mode 100644 index 622a99f8f6..0000000000 --- a/src/vendor/ppx_blob/src/ppx_blob.ml +++ /dev/null @@ -1,35 +0,0 @@ -open Ppxlib - -let location_errorf ~loc = - Format.ksprintf (fun err -> - raise (Ocaml_common.Location.Error (Ocaml_common.Location.error ~loc err)) - ) - -let find_file_path ~loc file_name = - let dirname = loc.Ocaml_common.Location.loc_start.pos_fname |> Filename.dirname in - let relative_path = Filename.concat dirname file_name in - List.find Sys.file_exists [relative_path; file_name] - -let get_blob ~loc file_name = - try - let file_path = find_file_path ~loc file_name in - let c = open_in_bin file_path in - let s = String.init (in_channel_length c) (fun _ -> input_char c) in - close_in c; - s - with _ -> - location_errorf ~loc "[%%blob] could not find or load file %s" file_name - -let expand ~ctxt file_name = - let loc = Expansion_context.Extension.extension_point_loc ctxt in - Ast_builder.Default.estring ~loc (get_blob ~loc file_name) - -let extension = - Extension.V3.declare "blob" Extension.Context.expression - Ast_pattern.(single_expr_payload (estring __)) - expand - -let rule = Ppxlib.Context_free.Rule.extension extension - -;; -Driver.register_transformation ~rules:[rule] "ppx_blob" \ No newline at end of file diff --git a/src/vendor/ppx_easy_deriving/LICENSE.md b/src/vendor/ppx_easy_deriving/LICENSE.md new file mode 100644 index 0000000000..ae6337b11b --- /dev/null +++ b/src/vendor/ppx_easy_deriving/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Simmo Saan + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/vendor/ppx_easy_deriving/README.md b/src/vendor/ppx_easy_deriving/README.md new file mode 100644 index 0000000000..99a5e7e140 --- /dev/null +++ b/src/vendor/ppx_easy_deriving/README.md @@ -0,0 +1,4 @@ +# ppx_easy_deriving + +Goblint vendors a subset of the unreleased [ppx_easy_deriving](https://github.com/sim642/ppx_easy_deriving) library. +It only includes products, excluding simples and variants. diff --git a/src/vendor/ppx_easy_deriving/deriver.ml b/src/vendor/ppx_easy_deriving/deriver.ml new file mode 100644 index 0000000000..533a77959c --- /dev/null +++ b/src/vendor/ppx_easy_deriving/deriver.ml @@ -0,0 +1,100 @@ +open Ppxlib +open Ast_builder.Default + +include Deriver_intf + +module Make (Arg: Intf.S): S = +struct + let attr = Attribute.declare (Printf.sprintf "deriving.%s.%s" Arg.name Arg.name) Attribute.Context.core_type Ast_pattern.(single_expr_payload __) (fun expr -> expr) + + let unit ~loc = Arg.tuple ~loc [] + + let rec expr ~loc ~quoter ct = + match Attribute.get attr ct with + | Some expr -> + Expansion_helpers.Quoter.quote quoter expr + | None -> + match ct with + | [%type: unit] -> + unit ~loc + | {ptyp_desc = Ptyp_constr ({txt = lid; loc}, args); _} -> + let ident = pexp_ident ~loc {loc; txt = Expansion_helpers.mangle_lid (Prefix Arg.name) lid} in + let ident = Expansion_helpers.Quoter.quote quoter ident in + let apply_args = List.map (fun ct -> + (Nolabel, expr ~loc ~quoter ct) + ) args + in + pexp_apply ~loc ident apply_args + | {ptyp_desc = Ptyp_tuple elems; _} -> + expr_tuple ~loc ~quoter elems + | {ptyp_desc = Ptyp_var name; _} -> + evar ~loc ("poly_" ^ name) + | _ -> + pexp_extension ~loc (Location.error_extensionf ~loc "unsupported core type") + + and expr_record ~loc ~quoter (lds: label_declaration list) = + let les = List.map (fun {pld_name = {txt = label; _}; pld_type; _} -> + (Lident label, expr ~loc ~quoter pld_type) + ) lds + in + Arg.record ~loc les + + and expr_tuple ~loc ~quoter elems = + let es = List.map (expr ~loc ~quoter) elems in + Arg.tuple ~loc es + + let expr_declaration ~loc ~quoter = function + | {ptype_kind = Ptype_abstract; ptype_manifest = Some ct; _} -> + expr ~loc ~quoter ct + | {ptype_kind = Ptype_abstract; _} -> + pexp_extension ~loc (Location.error_extensionf ~loc "unsupported abstract type") + | {ptype_kind = Ptype_variant constrs; _} -> + pexp_extension ~loc (Location.error_extensionf ~loc "unsupported variant type") + | {ptype_kind = Ptype_open; _} -> + pexp_extension ~loc (Location.error_extensionf ~loc "unsupported open type") + | {ptype_kind = Ptype_record fields; _} -> + expr_record ~loc ~quoter fields + + let typ ~loc td = + let ct = Ppx_deriving.core_type_of_type_decl td in + Ppx_deriving.poly_arrow_of_type_decl + (Arg.typ ~loc) + td + (Arg.typ ~loc ct) + + let generate_impl ~ctxt (_rec_flag, type_declarations) = + let loc = Expansion_context.Deriver.derived_item_loc ctxt in + let vbs = List.map (fun td -> + let quoter = Expansion_helpers.Quoter.create () in + let expr = expr_declaration ~loc ~quoter td in + let expr = Expansion_helpers.Quoter.sanitize quoter expr in + let expr = Ppx_deriving.poly_fun_of_type_decl td expr in + let ct = typ ~loc td in + let pat = ppat_var ~loc {loc; txt = Expansion_helpers.mangle_type_decl (Prefix Arg.name) td} in + let pat = ppat_constraint ~loc pat ct in + Ast_helper.Vb.mk ~loc ~attrs:[Ppx_deriving.attr_warning [%expr "-39"]] pat expr + ) type_declarations + in + [Ast_helper.Str.value ~loc Recursive vbs] + + let generate_intf ~ctxt (_rec_flag, type_declarations) = + let loc = Expansion_context.Deriver.derived_item_loc ctxt in + List.map (fun td -> + let ct = typ ~loc td in + let val_ = Ast_helper.Val.mk ~loc {loc; txt = Expansion_helpers.mangle_type_decl (Prefix Arg.name) td} ct in + Ast_helper.Sig.value ~loc val_ + ) type_declarations + + let impl_generator = Deriving.Generator.V2.make_noarg generate_impl + let intf_generator = Deriving.Generator.V2.make_noarg generate_intf + let extension ~loc ~path:_ ct = + let quoter = Expansion_helpers.Quoter.create () in + let expr = expr ~loc ~quoter ct in + Expansion_helpers.Quoter.sanitize quoter expr + + let register () = + Deriving.add Arg.name + ~sig_type_decl:intf_generator + ~str_type_decl:impl_generator + ~extension +end diff --git a/src/vendor/ppx_easy_deriving/deriver.mli b/src/vendor/ppx_easy_deriving/deriver.mli new file mode 100644 index 0000000000..a8238f6f6f --- /dev/null +++ b/src/vendor/ppx_easy_deriving/deriver.mli @@ -0,0 +1,3 @@ +(** Registerable deriver. *) + +include Deriver_intf.Deriver (** @inline *) diff --git a/src/vendor/ppx_easy_deriving/deriver_intf.ml b/src/vendor/ppx_easy_deriving/deriver_intf.ml new file mode 100644 index 0000000000..565a6aef38 --- /dev/null +++ b/src/vendor/ppx_easy_deriving/deriver_intf.ml @@ -0,0 +1,13 @@ +module type S = +sig + val register: unit -> Ppxlib.Deriving.t + (** Register deriver with ppxlb. *) +end + +module type Deriver = +sig + module type S = S + + module Make (_: Intf.S): S + (** Make registerable deriver. *) +end diff --git a/src/vendor/ppx_easy_deriving/dune b/src/vendor/ppx_easy_deriving/dune new file mode 100644 index 0000000000..7d50a56e43 --- /dev/null +++ b/src/vendor/ppx_easy_deriving/dune @@ -0,0 +1,4 @@ +(library + (name ppx_easy_deriving) + (libraries ppxlib ppx_deriving.api) + (preprocess (pps ppxlib.metaquot))) diff --git a/src/vendor/ppx_easy_deriving/intf.ml b/src/vendor/ppx_easy_deriving/intf.ml new file mode 100644 index 0000000000..b30c7c36a0 --- /dev/null +++ b/src/vendor/ppx_easy_deriving/intf.ml @@ -0,0 +1,64 @@ +(** Main interfaces. *) + +open Ppxlib + +(** Deriver name interface. *) +module type Name = +sig + val name: string + (** Deriver name. + + For example, with the name "equal": + + Use [[@@deriving equal]] after a type definition. + + The derived value/function is [val equal: ...] (if the type is named [t]) or [val equal_ty: ...] (otherwise if the type is named [ty]). + + Use [[@equal ...]] after a type expression to override the underlying value/function used for it. + + Use [[%equal: ty]] as an expression for the value/function of type [ty]. *) +end + +(** Deriver base interface. *) +module type Base = +sig + include Name + val typ: loc:location -> core_type -> core_type + (** Derived value/function type for a given type. + + For example, "equal" deriver would map [t] to [t -> t -> bool]. *) +end + +module Tuple = +struct + + (** Tuple deriver interface. *) + module type S = + sig + include Base + val tuple: loc:location -> expression list -> expression + (** Compose derived values/functions for tuple elements into derived value/function for the tuple. *) + end +end + +module Record = +struct + + (** Record deriver interface. *) + module type S = + sig + include Base + val record: loc:location -> (longident * expression) list -> expression + (** Compose derived values/functions for record fields into derived value/function for the record. *) + end +end + +module Full = +struct + + (** Full deriver interface. *) + module type S = + sig + include Tuple.S + include Record.S + end +end + +module type S = Full.S +(** Deriver interface. *) diff --git a/src/vendor/ppx_easy_deriving/pat_exp.ml b/src/vendor/ppx_easy_deriving/pat_exp.ml new file mode 100644 index 0000000000..a9d43d07e6 --- /dev/null +++ b/src/vendor/ppx_easy_deriving/pat_exp.ml @@ -0,0 +1,46 @@ +open Ppxlib +open Ast_builder.Default + +type t = + | Record of (longident * t) list + | Tuple of t list + | Unit + | Base of string +let create_record ~prefix ls = + Record (List.mapi (fun i l -> (l, Base (prefix ^ string_of_int (i + 1)))) ls) +let create_tuple ~prefix n = + match n with + | 0 -> Unit + | 1 -> Base (prefix ^ "1") + | n -> Tuple (List.init n (fun i -> Base (prefix ^ string_of_int (i + 1)))) +let rec to_pat ~loc = function + | Record xs -> + ppat_record ~loc (List.map (fun (l, x) -> + (Located.mk ~loc l, to_pat ~loc x) + ) xs) Closed + | Tuple xs -> + ppat_tuple ~loc (List.map (to_pat ~loc) xs) + | Unit -> + [%pat? ()] + | Base s -> + ppat_var ~loc (Located.mk ~loc s) +let rec to_exps ~loc = function + | Record xs -> + List.flatten (List.map (fun (_, x) -> to_exps ~loc x) xs) + | Tuple xs -> + List.flatten (List.map (to_exps ~loc) xs) + | Unit -> + [] + | Base s -> + [pexp_ident ~loc {loc; txt = Lident s}] +let rec to_exp ~loc = function + | Record xs -> + pexp_record ~loc (List.map (fun (l, x) -> + (Located.mk ~loc l, to_exp ~loc x) + ) xs) None + | Tuple xs -> + pexp_tuple ~loc (List.map (to_exp ~loc) xs) + | Unit -> + [%expr ()] + | Base s -> + pexp_ident ~loc {loc; txt = Lident s} diff --git a/src/vendor/ppx_easy_deriving/pat_exp.mli b/src/vendor/ppx_easy_deriving/pat_exp.mli new file mode 100644 index 0000000000..a4878afa81 --- /dev/null +++ b/src/vendor/ppx_easy_deriving/pat_exp.mli @@ -0,0 +1,16 @@ +(** Common representation of patterns and expressions. *) + +open Ppxlib + +type t = + | Record of (longident * t) list + | Tuple of t list + | Unit + | Base of string + +val create_record: prefix:string -> longident list -> t +val create_tuple: prefix:string -> int -> t + +val to_pat: loc:location -> t -> pattern +val to_exps: loc:location -> t -> expression list +val to_exp: loc:location -> t -> expression diff --git a/src/vendor/ppx_easy_deriving/ppx_easy_deriving.ml b/src/vendor/ppx_easy_deriving/ppx_easy_deriving.ml new file mode 100644 index 0000000000..a490f33557 --- /dev/null +++ b/src/vendor/ppx_easy_deriving/ppx_easy_deriving.ml @@ -0,0 +1,15 @@ +(** Library for easily defining PPX derivers without boilerplate and runtime overhead. *) + +(** {1 Interfaces} *) + +include Intf (** @inline *) + + +(** {1 Deriver} *) + +module Deriver = Deriver + + +(** {1 Easier constructs} *) + +module Product = Product diff --git a/src/vendor/ppx_easy_deriving/product.ml b/src/vendor/ppx_easy_deriving/product.ml new file mode 100644 index 0000000000..2135552b63 --- /dev/null +++ b/src/vendor/ppx_easy_deriving/product.ml @@ -0,0 +1,226 @@ +open Ppxlib +open Ast_builder.Default + +include Product_intf + +module type Product_S = S + +module Make (P: S): Intf.S = +struct + include P + + let tuple ~loc es = + let n = List.length es in + let pe_create = Pat_exp.create_tuple n in + P.product ~loc ~pe_create es + + let record ~loc les = + let ls = List.map fst les in + let pe_create = Pat_exp.create_record ls in + let es = List.map snd les in + P.product ~loc ~pe_create es +end + +module Reduce = +struct + include Reduce + + module Conjunctive = + struct + include Conjunctive + + module Make (C: S): Reduce.S = + struct + let name = C.name + let typ ~loc _ = [%type: bool] + let unit ~loc = [%expr true] + let both ~loc e1 e2 = [%expr [%e e1] && [%e e2]] + end + end +end + +module Reduce1 = +struct + include Reduce1 + + module Make (R1: S): Intf.S = + struct + module P: Product_S = + struct + let name = R1.name + let typ ~loc t = [%type: [%t t] -> [%t R1.typ ~loc t]] + + let product ~loc ~pe_create es = + let pe = pe_create ~prefix:"x" in + let body = + let es = List.map2 (fun e x -> + [%expr [%e e] [%e x]] + ) es (Pat_exp.to_exps ~loc pe) + in + Util.reduce ~unit:(R1.unit ~loc) ~both:(R1.both ~loc) es + in + [%expr fun [%p Pat_exp.to_pat ~loc pe] -> [%e body]] + end + + include Make (P) + end +end + +module Reduce2 = +struct + include Reduce2 + + module Make (R2: S): Intf.S = + struct + module P: Product_S = + struct + let name = R2.name + let typ ~loc t = [%type: [%t t] -> [%t t] -> [%t R2.typ ~loc t]] + + let product ~loc ~pe_create es = + let pel = pe_create ~prefix:"l" in + let per = pe_create ~prefix:"r" in + let body = + let esl = Pat_exp.to_exps ~loc pel in + let esr = Pat_exp.to_exps ~loc per in + let es = Util.map3 (fun e l r -> + [%expr [%e e] [%e l] [%e r]] + ) es esl esr + in + Util.reduce ~unit:(R2.unit ~loc) ~both:(R2.both ~loc) es + in + let pl = Pat_exp.to_pat ~loc pel in + let pr = Pat_exp.to_pat ~loc per in + [%expr fun [%p pl] [%p pr] -> [%e body]] + end + + include Make (P) + end +end + +module Create = +struct + include Create + + module Make (C: S): Intf.S = + struct + let name = C.name + let typ ~loc t = [%type: [%t C.typ ~loc t] -> [%t t]] + + let tuple ~loc es = + match es with + | [] -> [%expr fun _ -> ()] + | [e] -> e + | _ :: _ -> + let elems = List.map (fun e -> + [%expr [%e e] x] + ) es + in + let body = pexp_tuple ~loc elems in + [%expr fun x -> [%e body]] + + let record ~loc les = + let fields = List.map (fun (l, e) -> + (Located.mk ~loc l, [%expr [%e e] x]) + ) les + in + let body = pexp_record ~loc fields None in + [%expr fun x -> [%e body]] + end +end + +module Map1 = +struct + include Map1 + + module Make (M1: S): Intf.S = + struct + let name = M1.name + let typ ~loc t = [%type: [%t t] -> [%t t]] + + let tuple ~loc es = + let n = List.length es in + let pe = Pat_exp.create_tuple ~prefix:"x" n in + let elems = + List.map2 (fun e x -> + [%expr [%e e] [%e x]] + ) es (Pat_exp.to_exps ~loc pe) + in + let body = + match elems with + | [] -> [%expr ()] + | [elem] -> elem + | _ :: _ -> pexp_tuple ~loc elems + in + [%expr fun [%p Pat_exp.to_pat ~loc pe] -> [%e body]] + + let record ~loc les = + let ls = List.map fst les in + let pe = Pat_exp.create_record ~prefix:"x" ls in + let es = List.map snd les in + let elems = + List.map2 (fun e x -> + [%expr [%e e] [%e x]] + ) es (Pat_exp.to_exps ~loc pe) + in + let fields = List.map2 (fun l elem -> + (Located.mk ~loc l, elem) + ) ls elems + in + let body = pexp_record ~loc fields None in + [%expr fun [%p Pat_exp.to_pat ~loc pe] -> [%e body]] + end +end + +module Map2 = +struct + include Map2 + + module Make (M2: S): Intf.S = + struct + let name = M2.name + let typ ~loc t = [%type: [%t t] -> [%t t] -> [%t t]] + + let tuple ~loc es = + let n = List.length es in + let pel = Pat_exp.create_tuple ~prefix:"l" n in + let per = Pat_exp.create_tuple ~prefix:"r" n in + let elems = + let esl = Pat_exp.to_exps ~loc pel in + let esr = Pat_exp.to_exps ~loc per in + Util.map3 (fun e l r -> + [%expr [%e e] [%e l] [%e r]] + ) es esl esr + in + let body = + match elems with + | [] -> [%expr ()] + | [elem] -> elem + | _ :: _ -> pexp_tuple ~loc elems + in + let pl = Pat_exp.to_pat ~loc pel in + let pr = Pat_exp.to_pat ~loc per in + [%expr fun [%p pl] [%p pr] -> [%e body]] + + let record ~loc les = + let ls = List.map fst les in + let pel = Pat_exp.create_record ~prefix:"l" ls in + let per = Pat_exp.create_record ~prefix:"r" ls in + let es = List.map snd les in + let elems = + let esl = Pat_exp.to_exps ~loc pel in + let esr = Pat_exp.to_exps ~loc per in + Util.map3 (fun e l r -> + [%expr [%e e] [%e l] [%e r]] + ) es esl esr + in + let fields = List.map2 (fun l elem -> + (Located.mk ~loc l, elem) + ) ls elems + in + let body = pexp_record ~loc fields None in + let pl = Pat_exp.to_pat ~loc pel in + let pr = Pat_exp.to_pat ~loc per in + [%expr fun [%p pl] [%p pr] -> [%e body]] + end +end diff --git a/src/vendor/ppx_easy_deriving/product.mli b/src/vendor/ppx_easy_deriving/product.mli new file mode 100644 index 0000000000..d0b45f106c --- /dev/null +++ b/src/vendor/ppx_easy_deriving/product.mli @@ -0,0 +1,3 @@ +(** Product derivers which unify tuple and record derivers. *) + +include Product_intf.Product (** @inline *) diff --git a/src/vendor/ppx_easy_deriving/product_intf.ml b/src/vendor/ppx_easy_deriving/product_intf.ml new file mode 100644 index 0000000000..14964bbee7 --- /dev/null +++ b/src/vendor/ppx_easy_deriving/product_intf.ml @@ -0,0 +1,132 @@ +open Ppxlib + +module type S = +sig + include Intf.Base + val product: loc:location -> pe_create:(prefix:string -> Pat_exp.t) -> expression list -> expression + (** Compose derived values/functions for product elements into derived value/function for the product. + + @param pe_create factory for patterns/expressions of the actual type. *) +end + +module Reduce = +struct + module type S = + sig + include Intf.Base + val unit: loc:location -> expression + (** Derived value/function for [unit] type. *) + + val both: loc:location -> expression -> expression -> expression + (** Compose derived values/functions in a product into derived value/function for the pair. *) + end + + module Conjunctive = + struct + module type S = Intf.Name + end +end + +module Reduce1 = +struct + module type S = Reduce.S +end + +module Reduce2 = +struct + module type S = Reduce.S +end + +module Create = +struct + module type S = + sig + include Intf.Base + end +end + +module Map1 = +struct + module type S = Intf.Name +end + +module Map2 = +struct + module type S = Intf.Name +end + +module type Product = +sig + module type S = S + (** Product deriver interface. *) + + module Make (P: S): Intf.S + (** Make deriver from product deriver. *) + + (** Reductions for reducing derivers. *) + module Reduce : + sig + module type S = Reduce.S + (** Reduction interface. *) + + (** Conjunctive reduction. *) + module Conjunctive : + sig + module type S = Reduce.Conjunctive.S + (** Conjunctive reduction interface. *) + + module Make (C: S): Reduce.S + (** Make reduction from conjunctive reduction. *) + end + end + + (** Unary reducing deriver. *) + module Reduce1 : + sig + module type S = Reduce1.S + (** Unary reducing deriver interface. *) + + module Make (R1: S): Intf.S + (** Make deriver from unary reducing deriver. *) + end + + (** Binary reducing deriver. *) + module Reduce2 : + sig + module type S = Reduce2.S + (** Binary reducing deriver interface. *) + + module Make (R2: S): Intf.S + (** Make deriver from binary reducing deriver. *) + end + + (** Unary creating deriver. *) + module Create : + sig + module type S = Create.S + (** Unary creating deriver interface. *) + + module Make (C: S): Intf.S + (** Make deriver from unary creating deriver. *) + end + + (** Unary mapping deriver. *) + module Map1 : + sig + module type S = Map1.S + (** Unary mapping deriver interface. *) + + module Make (M1: S): Intf.S + (** Make deriver from unary mapping deriver. *) + end + + (** Binary mapping deriver. *) + module Map2 : + sig + module type S = Map2.S + (** Binary mapping deriver interface. *) + + module Make (M2: S): Intf.S + (** Make deriver from binary mapping deriver. *) + end +end diff --git a/src/vendor/ppx_easy_deriving/util.ml b/src/vendor/ppx_easy_deriving/util.ml new file mode 100644 index 0000000000..fbb9d3f642 --- /dev/null +++ b/src/vendor/ppx_easy_deriving/util.ml @@ -0,0 +1,12 @@ +let reduce ~unit ~both = function + | [] -> unit + | [x] -> x + | xs -> + let xs = List.rev xs in + match xs with + | x :: xs -> + List.fold_right both (List.rev xs) x (* omits hash_empty *) + | [] -> assert false + +let map3 f l1 l2 l3 = + List.map2 (fun x1 (x2, x3) -> f x1 x2 x3) l1 (List.combine l2 l3) (* TODO: optimize *) diff --git a/src/vendor/ppx_easy_deriving/util.mli b/src/vendor/ppx_easy_deriving/util.mli new file mode 100644 index 0000000000..0f896e7efc --- /dev/null +++ b/src/vendor/ppx_easy_deriving/util.mli @@ -0,0 +1,8 @@ +(** Utility functions. *) + +val reduce: unit:'a -> both:('a -> 'a -> 'a) -> 'a list -> 'a +(** Reduce a list of values "optimally", + i.e. without unnecessary [~unit] and [~both]. *) + +val map3: ('a -> 'b -> 'c -> 'd) -> 'a list -> 'b list -> 'c list -> 'd list +(** Map three lists into one. *) diff --git a/src/witness/argTools.ml b/src/witness/argTools.ml index 8f440247f0..ae9276be97 100644 --- a/src/witness/argTools.ml +++ b/src/witness/argTools.ml @@ -14,22 +14,35 @@ sig val query: Node.t -> 'a Queries.t -> 'a Queries.result end -module Dot (Arg: BiArg) = +module type NodeStyles = +sig + type node + val extra_node_styles: node -> string list +end + +module Dot (Arg: BiArg) (NodeStyles: NodeStyles with type node = Arg.Node.t) = struct let dot_node_name ppf node = Format.fprintf ppf "\"%s\"" (Arg.Node.to_string node) let dot_edge ppf from_node (edge, to_node) = - Format.fprintf ppf "@,%a -> %a [label=\"%s\"];" dot_node_name from_node dot_node_name to_node (String.escaped (Arg.Edge.to_string edge)) + let label = [Format.sprintf "label=\"%s\"" (String.escaped (Arg.Edge.to_string edge))] in + let style = match edge with + | MyARG.InlinedEdge _ -> ["style=dotted"] + | _ -> [] + in + let styles = String.concat "," (label @ style) in + Format.fprintf ppf "@,%a -> %a [%s];" dot_node_name from_node dot_node_name to_node styles let dot_node ppf node = let shape = match Arg.Node.cfgnode node with - | Statement {skind=If (_,_,_,_,_); _} -> "diamond" - | Statement _ -> "oval" + | Statement {skind=If (_,_,_,_,_); _} -> ["shape=diamond"] + | Statement _ -> [] (* use default shape *) | Function _ - | FunctionEntry _ -> "box" + | FunctionEntry _ -> ["shape=box"] in - Format.fprintf ppf "@,%a [shape=%s];" dot_node_name node shape; + let styles = String.concat "," (shape @ NodeStyles.extra_node_styles node) in + Format.fprintf ppf "@,%a [%s];" dot_node_name node styles; List.iter (dot_edge ppf node) (Arg.next node) let dot_nodes ppf = @@ -149,11 +162,23 @@ struct include Intra (ArgIntra) (Arg) let prev = witness_prev - let iter_nodes f = - f main_entry; - NHT.iter (fun n _ -> - f n - ) witness_prev_map + + (** Iterate over {e reachable} nodes. *) + let iter_nodes (f: Node.t -> unit): unit = + let reachable = NHT.create (NHT.length witness_prev_map) in + + (* DFS *) + let rec iter_node node = + if not (NHT.mem reachable node) then ( + NHT.replace reachable node (); + f node; + List.iter (fun (edge, to_node) -> + iter_node to_node + ) (next node) (* use included next, not Arg.next, to prune uncilled nodes *) + ) + in + + iter_node main_entry let query ((n, c, i): Node.t) q = R.ask_local (n, c) (PathQuery (i, q)) diff --git a/src/witness/myARG.ml b/src/witness/myARG.ml index 373a66d3d6..6273ecdbd5 100644 --- a/src/witness/myARG.ml +++ b/src/witness/myARG.ml @@ -283,6 +283,7 @@ let partition_if_next if_next_n = module UnCilLogicIntra (Arg: SIntraOpt): SIntraOpt = struct open Cil + (* TODO: questionable (=) and (==) use here *) let is_equiv_stmtkind sk1 sk2 = match sk1, sk2 with | Instr is1, Instr is2 -> GobList.equal (=) is1 is2 @@ -305,14 +306,15 @@ struct let rec next_opt' n = match n with - | Statement {sid; skind=If (_, _, _, loc, eloc); _} when GobConfig.get_bool "witness.graphml.uncil" -> (* TODO: use elocs instead? *) + | Statement {sid; skind=If _; _} when GobConfig.get_bool "witness.graphml.uncil" -> let (e, if_true_next_n, if_false_next_n) = partition_if_next (Arg.next n) in (* avoid infinite recursion with sid <> sid2 in if_nondet_var *) (* TODO: why physical comparison if_false_next_n != n doesn't work? *) (* TODO: need to handle longer loops? *) + let loc = Node.location n in begin match if_true_next_n, if_false_next_n with (* && *) - | Statement {sid=sid2; skind=If (_, _, _, loc2, eloc2); _}, _ when sid <> sid2 && loc = loc2 -> + | Statement {sid=sid2; skind=If _; _}, _ when sid <> sid2 && CilType.Location.equal loc (Node.location if_true_next_n) -> (* get e2 from edge because recursive next returns it there *) let (e2, if_true_next_true_next_n, if_true_next_false_next_n) = partition_if_next (next if_true_next_n) in if is_equiv_chain if_false_next_n if_true_next_false_next_n then @@ -324,7 +326,7 @@ struct else None (* || *) - | _, Statement {sid=sid2; skind=If (_, _, _, loc2, eloc2); _} when sid <> sid2 && loc = loc2 -> + | _, Statement {sid=sid2; skind=If _; _} when sid <> sid2 && CilType.Location.equal loc (Node.location if_false_next_n) -> (* get e2 from edge because recursive next returns it there *) let (e2, if_false_next_true_next_n, if_false_next_false_next_n) = partition_if_next (next if_false_next_n) in if is_equiv_chain if_true_next_n if_false_next_true_next_n then @@ -358,9 +360,10 @@ struct Question(e_cond, e_true, e_false, Cilfacade.typeOf e_false) let next_opt' n = match n with - | Statement {skind=If (_, _, _, loc, eloc); _} when GobConfig.get_bool "witness.graphml.uncil" -> (* TODO: use eloc instead? *) + | Statement {skind=If _; _} when GobConfig.get_bool "witness.graphml.uncil" -> let (e_cond, if_true_next_n, if_false_next_n) = partition_if_next (Arg.next n) in - if Node.location if_true_next_n = loc && Node.location if_false_next_n = loc then + let loc = Node.location n in + if CilType.Location.equal (Node.location if_true_next_n) loc && CilType.Location.equal (Node.location if_false_next_n) loc then match Arg.next if_true_next_n, Arg.next if_false_next_n with | [(Assign (v_true, e_true), if_true_next_next_n)], [(Assign (v_false, e_false), if_false_next_next_n)] when v_true = v_false && Node.equal if_true_next_next_n if_false_next_next_n -> let exp = ternary e_cond e_true e_false in diff --git a/src/witness/observerAnalysis.ml b/src/witness/observerAnalysis.ml index 58b5b31fe4..490a51021a 100644 --- a/src/witness/observerAnalysis.ml +++ b/src/witness/observerAnalysis.ml @@ -30,7 +30,7 @@ struct let names x = "state " ^ string_of_int x end module D = Lattice.Flat (Printable.Chain (ChainParams)) - module C = D + include Analyses.ValueContexts(D) module P = IdentityP (D) (* fully path-sensitive *) let step d prev_node node = diff --git a/src/witness/svcomp.ml b/src/witness/svcomp.ml index bb887e6cb1..81dbc53760 100644 --- a/src/witness/svcomp.ml +++ b/src/witness/svcomp.ml @@ -1,7 +1,6 @@ (** SV-COMP tasks and results. *) open GoblintCil -open Batteries module Specification = SvcompSpec @@ -27,9 +26,9 @@ let is_error_function f = (* TODO: unused, but should be used? *) let is_special_function f = let loc = f.vdecl in - let is_svcomp = String.ends_with loc.file "sv-comp.c" in (* only includes/sv-comp.c functions, not __VERIFIER_assert in benchmark *) + let is_svcomp = String.ends_with loc.file ~suffix:"sv-comp.c" in (* only includes/sv-comp.c functions, not __VERIFIER_assert in benchmark *) let is_verifier = match f.vname with - | fname when String.starts_with fname "__VERIFIER" -> true + | fname when String.starts_with fname ~prefix:"__VERIFIER" -> true | fname -> is_error_function f in is_svcomp && is_verifier @@ -60,6 +59,11 @@ struct | Unknown -> "unknown" end +exception Error of string + +let errorwith s = raise (Error s) + + module type TaskResult = sig module Arg: MyARG.S diff --git a/src/witness/witness.ml b/src/witness/witness.ml index fb88c2ce7b..bb70c3319f 100644 --- a/src/witness/witness.ml +++ b/src/witness/witness.ml @@ -342,14 +342,14 @@ struct | UnreachCall _ -> (* error function name is globally known through Svcomp.task *) let is_unreach_call = - LHT.fold (fun (n, c) v acc -> + LHT.for_all (fun (n, c) v -> match n with (* FunctionEntry isn't used for extern __VERIFIER_error... *) | FunctionEntry f when Svcomp.is_error_function f.svar -> let is_dead = Spec.D.is_bot v in - acc && is_dead - | _ -> acc - ) lh true + is_dead + | _ -> true + ) lh in if is_unreach_call then ( @@ -690,25 +690,17 @@ struct Timing.wrap "graphml witness" (write_file witness_path (module Task)) (module TaskResult) ) - let write entrystates = - match !AnalysisState.verified with - | Some false -> print_svcomp_result "ERROR (verify)" - | _ -> - if get_string "witness.yaml.validate" <> "" then ( - match get_bool "witness.yaml.strict" with - | true when !YamlWitness.cnt_error > 0 -> - print_svcomp_result "ERROR (witness error)" - | true when !YamlWitness.cnt_unsupported > 0 -> - print_svcomp_result "ERROR (witness unsupported)" - | true when !YamlWitness.cnt_disabled > 0 -> - print_svcomp_result "ERROR (witness disabled)" - | _ when !YamlWitness.cnt_refuted > 0 -> - print_svcomp_result (Result.to_string (False None)) - | _ when !YamlWitness.cnt_unconfirmed > 0 -> - print_svcomp_result (Result.to_string Unknown) - | _ -> - write entrystates - ) - else + let write yaml_validate_result entrystates = + match !AnalysisState.verified, !AnalysisState.unsound_both_branches_dead with + | _, Some true -> print_svcomp_result "ERROR (both branches dead)" + | Some false, _ -> print_svcomp_result "ERROR (verify)" + | _, _ -> + match yaml_validate_result with + | Some (Stdlib.Error msg) -> + print_svcomp_result ("ERROR (" ^ msg ^ ")") + | Some (Ok (Svcomp.Result.False _ | Unknown as result)) -> + print_svcomp_result (Result.to_string result) + | Some (Ok True) + | None -> write entrystates end diff --git a/src/witness/witnessConstraints.ml b/src/witness/witnessConstraints.ml index 4af9dd898d..e361dbaa54 100644 --- a/src/witness/witnessConstraints.ml +++ b/src/witness/witnessConstraints.ml @@ -107,12 +107,6 @@ struct let startstate v = (Dom.singleton (Spec.startstate v) (R.bot ()), Sync.bot ()) let morphstate v (d, _) = (Dom.map_keys (Spec.morphstate v) d, Sync.bot ()) - let context fd (l, _) = - if Dom.cardinal l <> 1 then - failwith "PathSensitive3.context must be called with a singleton set." - else - Spec.context fd @@ Dom.choose_key l - let step n c i e = R.singleton ((n, c, i), e) let step n c i e sync = match Sync.find i sync with @@ -146,6 +140,15 @@ struct in ctx' + let context ctx fd (l, _) = + if Dom.cardinal l <> 1 then + failwith "PathSensitive3.context must be called with a singleton set." + else + let x = Dom.choose_key l in + Spec.context (conv ctx x) fd @@ x + + let startcontext = Spec.startcontext + let map ctx f g = (* we now use Sync for every tf such that threadspawn after tf could look up state before tf *) let h x (xs, sync) = @@ -278,7 +281,15 @@ struct R.bot () in (* keep left syncs so combine gets them for no-inline case *) - ((Dom.singleton x (R.bot ()), snd ctx.local), (Dom.singleton y yr, Sync.bot ())) + (* must lookup and short-circuit because enter may modify first in pair (e.g. abortUnless) *) + let syncs = + match Sync.find x' (snd ctx.local) with + | syncs -> syncs + | exception Not_found -> + M.debug ~category:Witness ~tags:[Category Analyzer] "PathSensitive3 sync predecessor not found"; + SyncSet.bot () + in + ((Dom.singleton x (R.bot ()), Sync.singleton x syncs), (Dom.singleton y yr, Sync.bot ())) ) ys in ys' @ xs diff --git a/src/witness/witnessGhost.ml b/src/witness/witnessGhost.ml new file mode 100644 index 0000000000..3eaa8ef69b --- /dev/null +++ b/src/witness/witnessGhost.ml @@ -0,0 +1,32 @@ +(** Ghost variables for YAML witnesses. *) + +let enabled () = + YamlWitness.entry_type_enabled YamlWitnessType.GhostInstrumentation.entry_type + +module Var = WitnessGhostVar + +include Var + +module Map = RichVarinfo.BiVarinfoMap.Make (Var) + +include Map + +let variable' x = + let variable = name_varinfo x in + let type_ = String.trim (CilType.Typ.show (typ x)) in (* CIL printer puts space at the end of some types *) + let initial = CilType.Exp.show (initial x) in + YamlWitness.Entry.ghost_variable' ~variable ~type_ ~initial + +let update' x e = + let variable = name_varinfo x in + let expression = CilType.Exp.show e in + YamlWitness.Entry.ghost_update' ~variable ~expression + +let location_update' ~node ~updates = + let loc = Node.location node in + let location_function = (Node.find_fundec node).svar.vname in + let location = YamlWitness.Entry.location ~location:loc ~location_function in + YamlWitness.Entry.ghost_location_update' ~location ~updates + +let instrumentation_entry ~task ~variables ~location_updates = + YamlWitness.Entry.ghost_instrumentation ~task ~variables ~location_updates diff --git a/src/witness/witnessGhostVar.ml b/src/witness/witnessGhostVar.ml new file mode 100644 index 0000000000..0d71909ba0 --- /dev/null +++ b/src/witness/witnessGhostVar.ml @@ -0,0 +1,43 @@ +(** Ghost variables for YAML witnesses. *) + +type t = + | Locked of LockDomain.MustLock.t + | Multithreaded +[@@deriving eq, ord, hash] + +let name_varinfo = function + | Locked (v, os) -> + let name = + if CilType.Varinfo.equal v LibraryFunctions.verifier_atomic_var then + invalid_arg "__VERIFIER_atomic" + else + if RichVarinfo.BiVarinfoMap.Collection.mem_varinfo v then + Printf.sprintf "alloc_%s%d" (if v.vid < 0 then "m" else "") (abs v.vid) (* turn minus into valid C name *) + else + Basetype.Variables.show v + in + let rec offs: Offset.Z.t -> string = function + | `NoOffset -> "" + | `Field (f, os') -> "_" ^ f.fname ^ offs os' + | `Index (i, os') -> + assert Z.Compare.(i >= Z.zero); "_" ^ Z.to_string i + in + name ^ offs os ^ "_locked" + | Multithreaded -> "multithreaded" + +let show = name_varinfo + +include Printable.SimpleShow (struct + type nonrec t = t + let show = show + end) + +let describe_varinfo _ _ = "" + +let typ = function + | Locked _ + | Multithreaded -> GoblintCil.intType + +let initial = function + | Locked _ + | Multithreaded -> GoblintCil.zero diff --git a/src/witness/witnessUtil.ml b/src/witness/witnessUtil.ml index 5d22fd1e83..390e893987 100644 --- a/src/witness/witnessUtil.ml +++ b/src/witness/witnessUtil.ml @@ -63,6 +63,7 @@ struct List.exists (fun (_, edge) -> match edge with | Proc (_, Lval (Var fv, NoOffset), args) when LibraryFunctions.is_special fv -> + Goblint_backtrace.wrap_val ~mark:(Cilfacade.FunVarinfo fv) @@ fun () -> let desc = LibraryFunctions.find fv in begin match desc.special args with | Lock _ -> true @@ -209,7 +210,7 @@ struct let typ = TEnum (e, []) in let name = "enum " ^ ename in Hashtbl.replace genv name (Cabs2cil.EnvTyp typ, loc); - List.iter (fun (name, exp, loc) -> + List.iter (fun (name, _, exp, loc) -> Hashtbl.replace genv name (Cabs2cil.EnvEnum (exp, typ), loc) ) eitems | Cil.GVar (v, _, loc) diff --git a/src/witness/yamlWitness.ml b/src/witness/yamlWitness.ml index 42254f30de..9d04b597fa 100644 --- a/src/witness/yamlWitness.ml +++ b/src/witness/yamlWitness.ml @@ -43,13 +43,20 @@ struct specification } - let location ~location:(loc: Cil.location) ~(location_function): Location.t = { - file_name = loc.file; - file_hash = sha256_file loc.file; - line = loc.line; - column = loc.column; - function_ = location_function; - } + let location ~location:(loc: Cil.location) ~(location_function): Location.t = + let file_hash = + match GobConfig.get_string "witness.yaml.format-version" with + | "0.1" -> Some (sha256_file loc.file) + | "2.0" -> None + | _ -> assert false + in + { + file_name = loc.file; + file_hash; + line = loc.line; + column = Some loc.column; + function_ = Some location_function; + } let invariant invariant: Invariant.t = { string = invariant; @@ -141,6 +148,35 @@ struct }; metadata = metadata (); } + + let ghost_variable' ~variable ~type_ ~(initial): GhostInstrumentation.Variable.t = { + name = variable; + scope = "global"; + type_; + initial = { + value = initial; + format = "c_expression"; + }; + } + + let ghost_update' ~variable ~(expression): GhostInstrumentation.Update.t = { + variable; + value = expression; + format = "c_expression"; + } + + let ghost_location_update' ~location ~(updates): GhostInstrumentation.LocationUpdate.t = { + location; + updates; + } + + let ghost_instrumentation ~task ~variables ~(location_updates): Entry.t = { + entry_type = GhostInstrumentation { + ghost_variables = variables; + ghost_updates = location_updates; + }; + metadata = metadata ~task (); + } end let yaml_entries_to_file yaml_entries file = @@ -242,6 +278,11 @@ struct let entries = [] in + let cnt_loop_invariant = ref 0 in + let cnt_location_invariant = ref 0 in + let cnt_flow_insensitive_invariant = ref 0 in + (* TODO: precondition invariants? *) + (* Generate location invariants (without precondition) *) let entries = if entry_type_enabled YamlWitnessType.LocationInvariant.entry_type then ( @@ -261,6 +302,7 @@ struct List.fold_left (fun acc inv -> let invariant = Entry.invariant (CilType.Exp.show inv) in let entry = Entry.location_invariant ~task ~location ~invariant in + incr cnt_location_invariant; entry :: acc ) acc invs | `Bot | `Top -> (* TODO: 0 for bot (dead code)? *) @@ -290,6 +332,7 @@ struct List.fold_left (fun acc inv -> let invariant = Entry.invariant (CilType.Exp.show inv) in let entry = Entry.loop_invariant ~task ~location ~invariant in + incr cnt_loop_invariant; entry :: acc ) acc invs | `Bot | `Top -> (* TODO: 0 for bot (dead code)? *) @@ -303,24 +346,53 @@ struct entries in + let invariant_global_nodes = lazy (R.ask_global InvariantGlobalNodes) in + + let fold_flow_insensitive_as_location ~inv f acc = + (* Currently same invariants (from InvariantGlobal query) for all nodes (from InvariantGlobalNodes query). + The alternative would be querying InvariantGlobal per local unknown when looping over them to generate location invariants. + See: https://github.com/goblint/analyzer/pull/1394#discussion_r1698149514. *) + let invs = WitnessUtil.InvariantExp.process_exp inv in + Queries.NS.fold (fun n acc -> + let fundec = Node.find_fundec n in + match WitnessInvariant.location_location n with (* Not just using Node.location because it returns expression location which may be invalid for location invariant (e.g. inside conditional). *) + | Some loc -> + let location_function = fundec.svar.vname in + let location = Entry.location ~location:loc ~location_function in + List.fold_left (fun acc inv -> + f ~location ~inv acc + ) acc invs + | None -> acc + ) (Lazy.force invariant_global_nodes) acc + in + (* Generate flow-insensitive invariants *) let entries = if entry_type_enabled YamlWitnessType.FlowInsensitiveInvariant.entry_type then ( GHT.fold (fun g v acc -> match g with - | `Left g -> (* Spec global *) - begin match R.ask_global (InvariantGlobal (Obj.repr g)) with - | `Lifted inv -> + | `Left g -> (* global unknown from analysis Spec *) + begin match R.ask_global (InvariantGlobal (Obj.repr g)), GobConfig.get_string "witness.invariant.flow_insensitive-as" with + | `Lifted inv, "flow_insensitive_invariant" -> let invs = WitnessUtil.InvariantExp.process_exp inv in List.fold_left (fun acc inv -> let invariant = Entry.invariant (CilType.Exp.show inv) in let entry = Entry.flow_insensitive_invariant ~task ~invariant in + incr cnt_flow_insensitive_invariant; entry :: acc ) acc invs - | `Bot | `Top -> (* global bot might only be possible for alloc variables, if at all, so emit nothing *) + | `Lifted inv, "location_invariant" -> + fold_flow_insensitive_as_location ~inv (fun ~location ~inv acc -> + let invariant = Entry.invariant (CilType.Exp.show inv) in + let entry = Entry.location_invariant ~task ~location ~invariant in + incr cnt_location_invariant; + entry :: acc + ) acc + | `Lifted _, _ + | `Bot, _ | `Top, _ -> (* global bot might only be possible for alloc variables, if at all, so emit nothing *) acc end - | `Right _ -> (* contexts global *) + | `Right _ -> (* global unknown for FromSpec contexts *) acc ) gh entries ) @@ -328,6 +400,33 @@ struct entries in + (* Generate flow-insensitive entries (ghost instrumentation) *) + let entries = + if entry_type_enabled YamlWitnessType.GhostInstrumentation.entry_type then ( + (* TODO: only at most one ghost_instrumentation entry can ever be produced, so this fold and deduplication is overkill *) + let module EntrySet = Queries.YS in + fst @@ GHT.fold (fun g v accs -> + match g with + | `Left g -> (* global unknown from analysis Spec *) + begin match R.ask_global (YamlEntryGlobal (Obj.repr g, task)) with + | `Lifted _ as inv -> + Queries.YS.fold (fun entry (acc, acc') -> + if EntrySet.mem entry acc' then (* deduplicate only with other global entries because local ones have different locations anyway *) + accs + else + (entry :: acc, EntrySet.add entry acc') + ) inv accs + | `Top -> + accs + end + | `Right _ -> (* global unknown for FromSpec contexts *) + accs + ) gh (entries, EntrySet.empty ()) + ) + else + entries + in + (* Generate precondition loop invariants. We do this in three steps: 1. Collect contexts for each function @@ -430,12 +529,12 @@ struct (* Generate invariant set *) let entries = - if entry_type_enabled YamlWitnessType.InvariantSet.entry_type then ( + if entry_type_enabled YamlWitnessType.InvariantSet.entry_type || entry_type_enabled YamlWitnessType.FlowInsensitiveInvariant.entry_type && GobConfig.get_string "witness.invariant.flow_insensitive-as" = "invariant_set-location_invariant" then ( let invariants = [] in (* Generate location invariants *) let invariants = - if invariant_type_enabled YamlWitnessType.InvariantSet.LocationInvariant.invariant_type then ( + if entry_type_enabled YamlWitnessType.InvariantSet.entry_type && invariant_type_enabled YamlWitnessType.InvariantSet.LocationInvariant.invariant_type then ( LH.fold (fun loc ns acc -> let inv = List.fold_left (fun acc n -> let local = try NH.find (Lazy.force nh) n with Not_found -> Spec.D.bot () in @@ -452,6 +551,7 @@ struct List.fold_left (fun acc inv -> let invariant = CilType.Exp.show inv in let invariant = Entry.location_invariant' ~location ~invariant in + incr cnt_location_invariant; invariant :: acc ) acc invs | `Bot | `Top -> (* TODO: 0 for bot (dead code)? *) @@ -464,7 +564,7 @@ struct (* Generate loop invariants *) let invariants = - if invariant_type_enabled YamlWitnessType.InvariantSet.LoopInvariant.invariant_type then ( + if entry_type_enabled YamlWitnessType.InvariantSet.entry_type && invariant_type_enabled YamlWitnessType.InvariantSet.LoopInvariant.invariant_type then ( LH.fold (fun loc ns acc -> if WitnessInvariant.emit_loop_head then ( (* TODO: remove double condition? *) let inv = List.fold_left (fun acc n -> @@ -481,6 +581,7 @@ struct List.fold_left (fun acc inv -> let invariant = CilType.Exp.show inv in let invariant = Entry.loop_invariant' ~location ~invariant in + incr cnt_loop_invariant; invariant :: acc ) acc invs | `Bot | `Top -> (* TODO: 0 for bot (dead code)? *) @@ -494,6 +595,31 @@ struct invariants in + (* Generate flow-insensitive invariants as location invariants *) + let invariants = + if entry_type_enabled YamlWitnessType.FlowInsensitiveInvariant.entry_type && GobConfig.get_string "witness.invariant.flow_insensitive-as" = "invariant_set-location_invariant" then ( + GHT.fold (fun g v acc -> + match g with + | `Left g -> (* global unknown from analysis Spec *) + begin match R.ask_global (InvariantGlobal (Obj.repr g)) with + | `Lifted inv -> + fold_flow_insensitive_as_location ~inv (fun ~location ~inv acc -> + let invariant = CilType.Exp.show inv in + let invariant = Entry.location_invariant' ~location ~invariant in + incr cnt_location_invariant; + invariant :: acc + ) acc + | `Bot | `Top -> (* global bot might only be possible for alloc variables, if at all, so emit nothing *) + acc + end + | `Right _ -> (* global unknown for FromSpec contexts *) + acc + ) gh invariants + ) + else + invariants + in + let invariants = List.rev invariants in let entry = Entry.invariant_set ~task ~invariants in entry :: entries @@ -505,6 +631,9 @@ struct let yaml_entries = List.rev_map YamlWitnessType.Entry.to_yaml entries in (* reverse to make entries in file in the same order as generation messages *) M.msg_group Info ~category:Witness "witness generation summary" [ + (Pretty.dprintf "location invariants: %d" !cnt_location_invariant, None); + (Pretty.dprintf "loop invariants: %d" !cnt_loop_invariant, None); + (Pretty.dprintf "flow-insensitive invariants: %d" !cnt_flow_insensitive_invariant, None); (Pretty.dprintf "total generation entries: %d" (List.length yaml_entries), None); ]; @@ -514,6 +643,26 @@ struct Timing.wrap "yaml witness" write () end +let init () = + match GobConfig.get_string "witness.yaml.validate" with + | "" -> () + | path -> + (* Check witness existence before doing the analysis. *) + if not (Sys.file_exists path) then ( + Logs.error "witness.yaml.validate: %s not found" path; + Svcomp.errorwith "witness missing" + ) + +let loc_of_location (location: YamlWitnessType.Location.t): Cil.location = { + file = location.file_name; + line = location.line; + column = Option.value location.column ~default:1; + byte = -1; + endLine = -1; + endColumn = -1; + endByte = -1; + synthetic = false; +} module ValidationResult = struct @@ -553,17 +702,6 @@ struct module InvariantParser = WitnessUtil.InvariantParser module VR = ValidationResult - let loc_of_location (location: YamlWitnessType.Location.t): Cil.location = { - file = location.file_name; - line = location.line; - column = location.column; - byte = -1; - endLine = -1; - endColumn = -1; - endByte = -1; - synthetic = false; - } - let validate () = let location_locator = Locator.create () in let loop_locator = Locator.create () in @@ -579,9 +717,11 @@ struct let inv_parser = InvariantParser.create FileCfg.file in - let yaml = match Yaml_unix.of_file (Fpath.v (GobConfig.get_string "witness.yaml.validate")) with + let yaml = match GobResult.Syntax.(Fpath.of_string (GobConfig.get_string "witness.yaml.validate") >>= Yaml_unix.of_file) with | Ok yaml -> yaml - | Error (`Msg m) -> failwith ("Yaml_unix.of_file: " ^ m) + | Error (`Msg m) -> + Logs.error "Yaml_unix.of_file: %s" m; + Svcomp.errorwith "witness missing" in let yaml_entries = yaml |> GobYaml.list |> BatResult.get_ok in @@ -795,6 +935,15 @@ struct None in + let validate_violation_sequence (violation_sequence: YamlWitnessType.ViolationSequence.t) = + (* TODO: update cnt-s appropriately (needs access to SV-COMP result pre-witness validation) *) + (* Nothing needs to be checked here! + If program is correct and we can prove it, we output true, which counts as refutation of violation witness. + If program is correct and we cannot prove it, we output unknown. + If program is incorrect, we output unknown. *) + None + in + match entry_type_enabled target_type, entry.entry_type with | true, LocationInvariant x -> validate_location_invariant x @@ -804,13 +953,15 @@ struct validate_precondition_loop_invariant x | true, InvariantSet x -> validate_invariant_set x - | false, (LocationInvariant _ | LoopInvariant _ | PreconditionLoopInvariant _ | InvariantSet _) -> + | true, ViolationSequence x -> + validate_violation_sequence x + | false, (LocationInvariant _ | LoopInvariant _ | PreconditionLoopInvariant _ | InvariantSet _ | ViolationSequence _) -> incr cnt_disabled; M.info_noloc ~category:Witness "disabled entry of type %s" target_type; None | _ -> incr cnt_unsupported; - M.info_noloc ~category:Witness "cannot validate entry of type %s" target_type; + M.warn_noloc ~category:Witness "cannot validate entry of type %s" target_type; None in @@ -822,7 +973,7 @@ struct Option.to_list yaml_certificate_entry @ yaml_entry :: yaml_entries' | Error (`Msg e) -> incr cnt_error; - M.info_noloc ~category:Witness "couldn't parse entry: %s" e; + M.error_noloc ~category:Witness "couldn't parse entry: %s" e; yaml_entry :: yaml_entries' ) [] yaml_entries in @@ -840,5 +991,21 @@ struct let certificate_path = GobConfig.get_string "witness.yaml.certificate" in if certificate_path <> "" then - yaml_entries_to_file (List.rev yaml_entries') (Fpath.v certificate_path) + yaml_entries_to_file (List.rev yaml_entries') (Fpath.v certificate_path); + + match GobConfig.get_bool "witness.yaml.strict" with + | true when !cnt_error > 0 -> + Error "witness error" + | true when !cnt_unsupported > 0 -> + Error "witness unsupported" + | true when !cnt_disabled > 0 -> + Error "witness disabled" + | _ when !cnt_refuted > 0 -> + (* Refuted only when assuming the invariant is reachable. *) + (* Ok (Svcomp.Result.False None) *) (* Wasn't a problem because valid*->correctness->false gave 0 points under old validator track scoring schema: https://doi.org/10.1007/978-3-031-22308-2_8. *) + Ok Svcomp.Result.Unknown (* Now valid*->correctness->false gives 1p (negative) points under new validator track scoring schema: https://doi.org/10.1007/978-3-031-57256-2_15. *) + | _ when !cnt_unconfirmed > 0 -> + Ok Unknown + | _ -> + Ok True end diff --git a/src/witness/yamlWitnessType.ml b/src/witness/yamlWitnessType.ml index 1630e05b69..7a57197a6f 100644 --- a/src/witness/yamlWitnessType.ml +++ b/src/witness/yamlWitnessType.ml @@ -9,6 +9,7 @@ struct command_line: string option; (* TODO: description *) } + [@@deriving eq, ord, hash] let to_yaml {name; version; command_line} = `O ([ @@ -39,6 +40,7 @@ struct language: string; specification: string option; } + [@@deriving eq, ord, hash] let to_yaml {input_files; input_file_hashes; data_model; language; specification} = `O ([ @@ -78,6 +80,7 @@ struct producer: Producer.t; task: Task.t option; } + [@@deriving eq, ord, hash] let to_yaml {format_version; uuid; creation_time; producer; task} = `O ([ @@ -106,29 +109,45 @@ module Location = struct type t = { file_name: string; - file_hash: string; + file_hash: string option; line: int; - column: int; - function_: string; + column: int option; + function_: string option; } - [@@deriving ord] + [@@deriving eq, ord, hash] let to_yaml {file_name; file_hash; line; column; function_} = - `O [ - ("file_name", `String file_name); - ("file_hash", `String file_hash); - ("line", `Float (float_of_int line)); - ("column", `Float (float_of_int column)); - ("function", `String function_); - ] + `O ([ + ("file_name", `String file_name); + ] @ (match file_hash with + | Some file_hash -> [ + ("file_hash", `String file_hash); + ] + | None -> + [] + ) @ [ + ("line", `Float (float_of_int line)); + ] @ (match column with + | Some column -> [ + ("column", `Float (float_of_int column)); + ] + | None -> + [] + ) @ (match function_ with + | Some function_ -> [ + ("function", `String function_); + ] + | None -> + [] + )) let of_yaml y = let open GobYaml in let+ file_name = y |> find "file_name" >>= to_string - and+ file_hash = y |> find "file_hash" >>= to_string + and+ file_hash = y |> Yaml.Util.find "file_hash" >>= option_map to_string and+ line = y |> find "line" >>= to_int - and+ column = y |> find "column" >>= to_int - and+ function_ = y |> find "function" >>= to_string in + and+ column = y |> Yaml.Util.find "column" >>= option_map to_int + and+ function_ = y |> Yaml.Util.find "function" >>= option_map to_string in {file_name; file_hash; line; column; function_} end @@ -139,7 +158,7 @@ struct type_: string; format: string; } - [@@deriving ord] + [@@deriving eq, ord, hash] let to_yaml {string; type_; format} = `O [ @@ -162,7 +181,7 @@ struct location: Location.t; loop_invariant: Invariant.t; } - [@@deriving ord] + [@@deriving eq, ord, hash] let entry_type = "loop_invariant" @@ -185,7 +204,7 @@ struct location: Location.t; location_invariant: Invariant.t; } - [@@deriving ord] + [@@deriving eq, ord, hash] let entry_type = "location_invariant" @@ -207,7 +226,7 @@ struct type t = { flow_insensitive_invariant: Invariant.t; } - [@@deriving ord] + [@@deriving eq, ord, hash] let entry_type = "flow_insensitive_invariant" @@ -229,7 +248,7 @@ struct loop_invariant: Invariant.t; precondition: Invariant.t; } - [@@deriving ord] + [@@deriving eq, ord, hash] let entry_type = "precondition_loop_invariant" @@ -257,7 +276,7 @@ struct value: string; format: string; } - [@@deriving ord] + [@@deriving eq, ord, hash] let invariant_type = "loop_invariant" @@ -289,7 +308,7 @@ struct type t = | LocationInvariant of LocationInvariant.t | LoopInvariant of LoopInvariant.t - [@@deriving ord] + [@@deriving eq, ord, hash] let invariant_type = function | LocationInvariant _ -> LocationInvariant.invariant_type @@ -317,7 +336,7 @@ struct type t = { invariant_type: InvariantType.t; } - [@@deriving ord] + [@@deriving eq, ord, hash] let to_yaml {invariant_type} = `O [ @@ -336,7 +355,7 @@ struct type t = { content: Invariant.t list; } - [@@deriving ord] + [@@deriving eq, ord, hash] let entry_type = "invariant_set" @@ -356,7 +375,7 @@ struct type_: string; file_hash: string; } - [@@deriving ord] + [@@deriving eq, ord, hash] let to_yaml {uuid; type_; file_hash} = `O [ @@ -380,7 +399,7 @@ struct type_: string; format: string; } - [@@deriving ord] + [@@deriving eq, ord, hash] let to_yaml {string; type_; format} = `O [ @@ -403,7 +422,7 @@ struct target: Target.t; certification: Certification.t; } - [@@deriving ord] + [@@deriving eq, ord, hash] let entry_type = "loop_invariant_certificate" @@ -426,6 +445,345 @@ struct let entry_type = "precondition_loop_invariant_certificate" end +module ViolationSequence = +struct + + module Constraint = + struct + + module Value = + struct + type t = + | String of string + | Int of int (* Why doesn't format consider ints (for switch branches) as strings here, like everywhere else? *) + [@@deriving eq, ord, hash] + + let to_yaml = function + | String s -> GobYaml.string s + | Int i -> GobYaml.int i + + let of_yaml y = + let open GobYaml in + match y with + | `String s -> Ok (String s) + | `Float f -> Ok (Int (int_of_float f)) + | _ -> Error (`Msg "Expected a string or integer value") + end + + type t = { + value: Value.t; + format: string option; + } + [@@deriving eq, ord, hash] + + let to_yaml {value; format} = + `O ([ + ("value", Value.to_yaml value); + ] @ (match format with + | Some format -> [ + ("format", `String format); + ] + | None -> + [] + )) + + let of_yaml y = + let open GobYaml in + let+ value = y |> find "value" >>= Value.of_yaml + and+ format = y |> Yaml.Util.find "format" >>= option_map to_string in + {value; format} + end + + module Assumption = + struct + type t = { + location: Location.t; + action: string; + constraint_: Constraint.t; + } + [@@deriving eq, ord, hash] + + let waypoint_type = "assumption" + + let to_yaml' {location; action; constraint_} = + [ + ("location", Location.to_yaml location); + ("action", `String action); + ("constraint", Constraint.to_yaml constraint_); + ] + + let of_yaml y = + let open GobYaml in + let+ location = y |> find "location" >>= Location.of_yaml + and+ action = y |> find "action" >>= to_string + and+ constraint_ = y |> find "constraint" >>= Constraint.of_yaml in + {location; action; constraint_} + end + + module Target = + struct + type t = { + location: Location.t; + action: string; + } + [@@deriving eq, ord, hash] + + let waypoint_type = "target" + + let to_yaml' {location; action} = + [ + ("location", Location.to_yaml location); + ("action", `String action); + ] + + let of_yaml y = + let open GobYaml in + let+ location = y |> find "location" >>= Location.of_yaml + and+ action = y |> find "action" >>= to_string in + {location; action} + end + + module FunctionEnter = + struct + include Target + + let waypoint_type = "function_enter" + end + + module FunctionReturn = + struct + include Assumption + + let waypoint_type = "function_return" + end + + module Branching = + struct + include Assumption + + let waypoint_type = "branching" + end + + (* TODO: could maybe use GADT, but adds ugly existential layer to entry type pattern matching *) + module WaypointType = + struct + type t = + | Assumption of Assumption.t + | Target of Target.t + | FunctionEnter of FunctionEnter.t + | FunctionReturn of FunctionReturn.t + | Branching of Branching.t + [@@deriving eq, ord, hash] + + let waypoint_type = function + | Assumption _ -> Assumption.waypoint_type + | Target _ -> Target.waypoint_type + | FunctionEnter _ -> FunctionEnter.waypoint_type + | FunctionReturn _ -> FunctionReturn.waypoint_type + | Branching _ -> Branching.waypoint_type + + let to_yaml' = function + | Assumption x -> Assumption.to_yaml' x + | Target x -> Target.to_yaml' x + | FunctionEnter x -> FunctionEnter.to_yaml' x + | FunctionReturn x -> FunctionReturn.to_yaml' x + | Branching x -> Branching.to_yaml' x + + let of_yaml y = + let open GobYaml in + let* waypoint_type = y |> find "type" >>= to_string in + if waypoint_type = Assumption.waypoint_type then + let+ x = y |> Assumption.of_yaml in + Assumption x + else if waypoint_type = Target.waypoint_type then + let+ x = y |> Target.of_yaml in + Target x + else if waypoint_type = FunctionEnter.waypoint_type then + let+ x = y |> FunctionEnter.of_yaml in + FunctionEnter x + else if waypoint_type = FunctionReturn.waypoint_type then + let+ x = y |> FunctionReturn.of_yaml in + FunctionReturn x + else if waypoint_type = Branching.waypoint_type then + let+ x = y |> Branching.of_yaml in + Branching x + else + Error (`Msg "type") + end + + module Waypoint = + struct + type t = { + waypoint_type: WaypointType.t; + } + [@@deriving eq, ord, hash] + + let to_yaml {waypoint_type} = + `O [ + ("waypoint", `O ([ + ("type", `String (WaypointType.waypoint_type waypoint_type)); + ] @ WaypointType.to_yaml' waypoint_type) + ) + ] + + let of_yaml y = + let open GobYaml in + let+ waypoint_type = y |> find "waypoint" >>= WaypointType.of_yaml in + {waypoint_type} + end + + module Segment = + struct + type t = { + segment: Waypoint.t list; + } + [@@deriving eq, ord, hash] + + let to_yaml {segment} = + `O [("segment", `A (List.map Waypoint.to_yaml segment))] + + let of_yaml y = + let open GobYaml in + let+ segment = y |> find "segment" >>= list >>= list_map Waypoint.of_yaml in + {segment} + end + + type t = { + content: Segment.t list; + } + [@@deriving eq, ord, hash] + + let entry_type = "violation_sequence" + + let to_yaml' {content} = + [("content", `A (List.map Segment.to_yaml content))] + + let of_yaml y = + let open GobYaml in + let+ content = y |> find "content" >>= list >>= list_map Segment.of_yaml in + {content} +end + +module GhostInstrumentation = +struct + + module Initial = + struct + type t = { + value: string; + format: string; + } + [@@deriving eq, ord, hash] + + let to_yaml {value; format} = + `O [ + ("value", `String value); + ("format", `String format); + ] + + let of_yaml y = + let open GobYaml in + let+ value = y |> find "value" >>= to_string + and+ format = y |> find "format" >>= to_string in + {value; format} + end + + module Variable = + struct + type t = { + name: string; + scope: string; + type_: string; + initial: Initial.t; + } + [@@deriving eq, ord, hash] + + let to_yaml {name; scope; type_; initial} = + `O [ + ("name", `String name); + ("scope", `String scope); + ("type", `String type_); + ("initial", Initial.to_yaml initial); + ] + + let of_yaml y = + let open GobYaml in + let+ name = y |> find "name" >>= to_string + and+ scope = y |> find "scope" >>= to_string + and+ type_ = y |> find "type" >>= to_string + and+ initial = y |> find "initial" >>= Initial.of_yaml in + {name; scope; type_; initial} + end + + module Update = + struct + type t = { + variable: string; + value: string; + format: string; + } + [@@deriving eq, ord, hash] + + let to_yaml {variable; value; format} = + `O [ + ("variable", `String variable); + ("value", `String value); + ("format", `String format); + ] + + let of_yaml y = + let open GobYaml in + let+ variable = y |> find "variable" >>= to_string + and+ value = y |> find "value" >>= to_string + and+ format = y |> find "format" >>= to_string in + {variable; value; format} + end + + module LocationUpdate = + struct + type t = { + location: Location.t; + updates: Update.t list; + } + [@@deriving eq, ord, hash] + + let to_yaml {location; updates} = + `O [ + ("location", Location.to_yaml location); + ("updates", `A (List.map Update.to_yaml updates)); + ] + + let of_yaml y = + let open GobYaml in + let+ location = y |> find "location" >>= Location.of_yaml + and+ updates = y |> find "updates" >>= list >>= list_map Update.of_yaml in + {location; updates} + end + + type t = { + ghost_variables: Variable.t list; + ghost_updates: LocationUpdate.t list; + } + [@@deriving eq, ord, hash] + + let entry_type = "ghost_instrumentation" + + let to_yaml' {ghost_variables; ghost_updates} = + [("content", + `O [ + ("ghost_variables", `A (List.map Variable.to_yaml ghost_variables)); + ("ghost_updates", `A (List.map LocationUpdate.to_yaml ghost_updates)); + ]) + ] + + let of_yaml y = + let open GobYaml in + let* content = y |> find "content" in + let+ ghost_variables = content |> find "ghost_variables" >>= list >>= list_map Variable.of_yaml + and+ ghost_updates = content |> find "ghost_updates" >>= list >>= list_map LocationUpdate.of_yaml in + {ghost_variables; ghost_updates} +end + (* TODO: could maybe use GADT, but adds ugly existential layer to entry type pattern matching *) module EntryType = struct @@ -437,7 +795,9 @@ struct | LoopInvariantCertificate of LoopInvariantCertificate.t | PreconditionLoopInvariantCertificate of PreconditionLoopInvariantCertificate.t | InvariantSet of InvariantSet.t - [@@deriving ord] + | ViolationSequence of ViolationSequence.t + | GhostInstrumentation of GhostInstrumentation.t + [@@deriving eq, ord, hash] let entry_type = function | LocationInvariant _ -> LocationInvariant.entry_type @@ -447,6 +807,8 @@ struct | LoopInvariantCertificate _ -> LoopInvariantCertificate.entry_type | PreconditionLoopInvariantCertificate _ -> PreconditionLoopInvariantCertificate.entry_type | InvariantSet _ -> InvariantSet.entry_type + | ViolationSequence _ -> ViolationSequence.entry_type + | GhostInstrumentation _ -> GhostInstrumentation.entry_type let to_yaml' = function | LocationInvariant x -> LocationInvariant.to_yaml' x @@ -456,6 +818,8 @@ struct | LoopInvariantCertificate x -> LoopInvariantCertificate.to_yaml' x | PreconditionLoopInvariantCertificate x -> PreconditionLoopInvariantCertificate.to_yaml' x | InvariantSet x -> InvariantSet.to_yaml' x + | ViolationSequence x -> ViolationSequence.to_yaml' x + | GhostInstrumentation x -> GhostInstrumentation.to_yaml' x let of_yaml y = let open GobYaml in @@ -481,16 +845,27 @@ struct else if entry_type = InvariantSet.entry_type then let+ x = y |> InvariantSet.of_yaml in InvariantSet x + else if entry_type = ViolationSequence.entry_type then + let+ x = y |> ViolationSequence.of_yaml in + ViolationSequence x + else if entry_type = GhostInstrumentation.entry_type then + let+ x = y |> GhostInstrumentation.of_yaml in + GhostInstrumentation x else - Error (`Msg "entry_type") + Error (`Msg ("entry_type " ^ entry_type)) end module Entry = struct + include Printable.StdLeaf + type t = { entry_type: EntryType.t; - metadata: Metadata.t; + metadata: Metadata.t [@equal fun _ _ -> true] [@compare fun _ _ -> 0] [@hash fun _ -> 1]; } + [@@deriving eq, ord, hash] + + let name () = "YAML entry" let to_yaml {entry_type; metadata} = `O ([ @@ -503,4 +878,10 @@ struct let+ metadata = y |> find "metadata" >>= Metadata.of_yaml and+ entry_type = y |> EntryType.of_yaml in {entry_type; metadata} + + let pp ppf x = Yaml.pp ppf (to_yaml x) + include Printable.SimpleFormat (struct + type nonrec t = t + let pp = pp + end) end diff --git a/tests/incremental/00-basic/01-global.c b/tests/incremental/00-basic/01-global.c index 8eac5b92a1..5b3a1feb6a 100644 --- a/tests/incremental/00-basic/01-global.c +++ b/tests/incremental/00-basic/01-global.c @@ -1,5 +1,6 @@ // Previosuly, the function was erroneously not reanalyzed when the global initializer/the start state changed // when hash-consing is activated. +// NOCHECK int g = 0; int main(){ diff --git a/tests/incremental/00-basic/01-global.patch b/tests/incremental/00-basic/01-global.patch index 2b86496f5a..9c74751174 100644 --- a/tests/incremental/00-basic/01-global.patch +++ b/tests/incremental/00-basic/01-global.patch @@ -3,6 +3,7 @@ @@ -1,6 +1,6 @@ // Previosuly, the function was erroneously not reanalyzed when the global initializer/the start state changed // when hash-consing is activated. + // NOCHECK -int g = 0; +int g = 35; diff --git a/tests/incremental/00-basic/04-rename_ids.c b/tests/incremental/00-basic/04-rename_ids.c index e5c9727edc..7f2884854d 100644 --- a/tests/incremental/00-basic/04-rename_ids.c +++ b/tests/incremental/00-basic/04-rename_ids.c @@ -1,3 +1,4 @@ +// NOCHECK int a; void b(); void c(); diff --git a/tests/incremental/00-basic/08-refine_interval_with_def_exc.c b/tests/incremental/00-basic/08-refine_interval_with_def_exc.c index da6cb9dd65..3fcf60e83a 100644 --- a/tests/incremental/00-basic/08-refine_interval_with_def_exc.c +++ b/tests/incremental/00-basic/08-refine_interval_with_def_exc.c @@ -1,3 +1,4 @@ +// NOCHECK struct input_state; typedef struct input_state input_state; struct input_state { diff --git a/tests/incremental/00-basic/12-rec-type.c b/tests/incremental/00-basic/12-rec-type.c index 7c862a107a..1c92e6f94b 100644 --- a/tests/incremental/00-basic/12-rec-type.c +++ b/tests/incremental/00-basic/12-rec-type.c @@ -4,7 +4,7 @@ typedef struct s_t s_t; union union_t { int i; - s_t *s[sizeof(s_t *)]; // This caused problems + s_t *s[sizeof(s_t *)]; // NOCRASH: This caused problems }; struct s_t { diff --git a/tests/incremental/00-basic/12-rec-type.patch b/tests/incremental/00-basic/12-rec-type.patch index 2a5bd8f478..5d00033464 100644 --- a/tests/incremental/00-basic/12-rec-type.patch +++ b/tests/incremental/00-basic/12-rec-type.patch @@ -5,8 +5,8 @@ index 7c862a107..a043e249c 100644 @@ -2,6 +2,7 @@ struct s_t ; typedef struct s_t s_t; - + +// Dummy change union union_t { int i; - s_t *s[sizeof(s_t *)]; // This caused problems + s_t *s[sizeof(s_t *)]; // NOCRASH: This caused problems diff --git a/tests/incremental/00-basic/15-reluctant-test.c b/tests/incremental/00-basic/15-reluctant-test.c index 6328061b29..a0c96977b7 100644 --- a/tests/incremental/00-basic/15-reluctant-test.c +++ b/tests/incremental/00-basic/15-reluctant-test.c @@ -1,5 +1,5 @@ #include -// This test used to resulted in an unreached fixpoint in the incremental implementation. +// FIXPOINT: This test used to resulted in an unreached fixpoint in the incremental implementation. int g = 3; diff --git a/tests/incremental/04-var-rename/01-rename_and_shuffle.c b/tests/incremental/04-var-rename/01-rename_and_shuffle.c index 7d6ea81e6f..0acafcae90 100644 --- a/tests/incremental/04-var-rename/01-rename_and_shuffle.c +++ b/tests/incremental/04-var-rename/01-rename_and_shuffle.c @@ -1,5 +1,5 @@ #include - +// CRAM // a is renamed to c, but the usage of a is replaced by b (semantic changes) int main() { int a = 0; diff --git a/tests/incremental/04-var-rename/01-rename_and_shuffle.patch b/tests/incremental/04-var-rename/01-rename_and_shuffle.patch index 94e27d9a80..fa6342b0ef 100644 --- a/tests/incremental/04-var-rename/01-rename_and_shuffle.patch +++ b/tests/incremental/04-var-rename/01-rename_and_shuffle.patch @@ -1,7 +1,7 @@ --- tests/incremental/04-var-rename/01-rename_and_shuffle.c +++ tests/incremental/04-var-rename/01-rename_and_shuffle.c @@ -2,10 +2,10 @@ - + // CRAM // a is renamed to c, but the usage of a is replaced by b (semantic changes) int main() { - int a = 0; diff --git a/tests/incremental/04-var-rename/02-rename_with_usage.c b/tests/incremental/04-var-rename/02-rename_with_usage.c index 2c93c487d8..928a8014d1 100644 --- a/tests/incremental/04-var-rename/02-rename_with_usage.c +++ b/tests/incremental/04-var-rename/02-rename_with_usage.c @@ -1,5 +1,5 @@ #include - +// CRAM //a is renamed to c, but its usages stay the same int main() { int a = 0; diff --git a/tests/incremental/04-var-rename/02-rename_with_usage.patch b/tests/incremental/04-var-rename/02-rename_with_usage.patch index 6cfe41bbb1..b6d0e4cb19 100644 --- a/tests/incremental/04-var-rename/02-rename_with_usage.patch +++ b/tests/incremental/04-var-rename/02-rename_with_usage.patch @@ -1,7 +1,7 @@ --- tests/incremental/04-var-rename/02-rename_with_usage.c +++ tests/incremental/04-var-rename/02-rename_with_usage.c @@ -2,10 +2,10 @@ - + // CRAM //a is renamed to c, but its usages stay the same int main() { - int a = 0; diff --git a/tests/incremental/04-var-rename/04-renamed_param.c b/tests/incremental/04-var-rename/04-renamed_param.c index 770af2683c..1d45695b1b 100644 --- a/tests/incremental/04-var-rename/04-renamed_param.c +++ b/tests/incremental/04-var-rename/04-renamed_param.c @@ -1,4 +1,5 @@ // function param is renamed (no semantic changes) +// CRAM void method(int a) { int c = a; } diff --git a/tests/incremental/04-var-rename/04-renamed_param.patch b/tests/incremental/04-var-rename/04-renamed_param.patch index 50a9b69f6a..7753ded6df 100644 --- a/tests/incremental/04-var-rename/04-renamed_param.patch +++ b/tests/incremental/04-var-rename/04-renamed_param.patch @@ -1,6 +1,7 @@ --- tests/incremental/04-var-rename/04-renamed_param.c +++ tests/incremental/04-var-rename/04-renamed_param.c @@ -2,5 +2,5 @@ + // CRAM -void method(int a) { - int c = a; +void method(int b) { diff --git a/tests/incremental/04-var-rename/05-renamed_param_usage_changed.c b/tests/incremental/04-var-rename/05-renamed_param_usage_changed.c index aed642566c..1453e5707a 100644 --- a/tests/incremental/04-var-rename/05-renamed_param_usage_changed.c +++ b/tests/incremental/04-var-rename/05-renamed_param_usage_changed.c @@ -1,5 +1,5 @@ //This test should mark foo and main as changed - +// CRAM void foo(int a, int b) { int x = a; int y = b; diff --git a/tests/incremental/04-var-rename/05-renamed_param_usage_changed.patch b/tests/incremental/04-var-rename/05-renamed_param_usage_changed.patch index 9ffc2c1cea..7e46542911 100644 --- a/tests/incremental/04-var-rename/05-renamed_param_usage_changed.patch +++ b/tests/incremental/04-var-rename/05-renamed_param_usage_changed.patch @@ -2,7 +2,7 @@ +++ tests/incremental/04-var-rename/05-renamed_param_usage_changed.c @@ -1,6 +1,6 @@ //This test should mark foo and main as changed - + // CRAM -void foo(int a, int b) { +void foo(int b, int a) { int x = a; diff --git a/tests/incremental/05-method-rename/00-simple_rename.c b/tests/incremental/05-method-rename/00-simple_rename.c index 5d1e6fe872..0dd7b4ba1c 100644 --- a/tests/incremental/05-method-rename/00-simple_rename.c +++ b/tests/incremental/05-method-rename/00-simple_rename.c @@ -1,5 +1,5 @@ #include - +// CRAM void foo() { printf("foo"); } diff --git a/tests/incremental/05-method-rename/00-simple_rename.patch b/tests/incremental/05-method-rename/00-simple_rename.patch index ed7b40014c..31f0bf18cc 100644 --- a/tests/incremental/05-method-rename/00-simple_rename.patch +++ b/tests/incremental/05-method-rename/00-simple_rename.patch @@ -2,7 +2,7 @@ +++ tests/incremental/05-method-rename/00-simple_rename.c @@ -1,10 +1,10 @@ #include - + // CRAM -void foo() { +void bar() { printf("foo"); diff --git a/tests/incremental/05-method-rename/01-dependent_rename.c b/tests/incremental/05-method-rename/01-dependent_rename.c index 66c1a5a634..c5c43d5cde 100644 --- a/tests/incremental/05-method-rename/01-dependent_rename.c +++ b/tests/incremental/05-method-rename/01-dependent_rename.c @@ -1,5 +1,5 @@ #include - +// CRAM void fun1() { printf("fun1"); } diff --git a/tests/incremental/05-method-rename/01-dependent_rename.patch b/tests/incremental/05-method-rename/01-dependent_rename.patch index f3a4a9a3f8..3712f75059 100644 --- a/tests/incremental/05-method-rename/01-dependent_rename.patch +++ b/tests/incremental/05-method-rename/01-dependent_rename.patch @@ -2,7 +2,7 @@ +++ tests/incremental/05-method-rename/01-dependent_rename.c @@ -1,14 +1,14 @@ #include - + // CRAM -void fun1() { +void bar1() { printf("fun1"); diff --git a/tests/incremental/05-method-rename/02-cyclic_rename_dependency.c b/tests/incremental/05-method-rename/02-cyclic_rename_dependency.c index 331a5e25cb..41813818cb 100644 --- a/tests/incremental/05-method-rename/02-cyclic_rename_dependency.c +++ b/tests/incremental/05-method-rename/02-cyclic_rename_dependency.c @@ -1,5 +1,5 @@ #include - +// CRAM void foo1(int c) { if (c < 10) foo2(c + 1); } diff --git a/tests/incremental/05-method-rename/02-cyclic_rename_dependency.patch b/tests/incremental/05-method-rename/02-cyclic_rename_dependency.patch index 7f15d88c3a..3b62220898 100644 --- a/tests/incremental/05-method-rename/02-cyclic_rename_dependency.patch +++ b/tests/incremental/05-method-rename/02-cyclic_rename_dependency.patch @@ -1,7 +1,7 @@ --- tests/incremental/05-method-rename/02-cyclic_rename_dependency.c +++ tests/incremental/05-method-rename/02-cyclic_rename_dependency.c @@ -2,14 +2,14 @@ - + // CRAM -void foo1(int c) { - if (c < 10) foo2(c + 1); +void bar1(int c) { diff --git a/tests/incremental/05-method-rename/03-cyclic_with_swap.c b/tests/incremental/05-method-rename/03-cyclic_with_swap.c index 34026afa92..898f15fe6d 100644 --- a/tests/incremental/05-method-rename/03-cyclic_with_swap.c +++ b/tests/incremental/05-method-rename/03-cyclic_with_swap.c @@ -1,5 +1,5 @@ #include - +// CRAM void foo1(int c) { if (c < 10) foo2(c + 1); } diff --git a/tests/incremental/05-method-rename/03-cyclic_with_swap.patch b/tests/incremental/05-method-rename/03-cyclic_with_swap.patch index 0886106162..3373449875 100644 --- a/tests/incremental/05-method-rename/03-cyclic_with_swap.patch +++ b/tests/incremental/05-method-rename/03-cyclic_with_swap.patch @@ -1,7 +1,7 @@ --- tests/incremental/05-method-rename/03-cyclic_with_swap.c +++ tests/incremental/05-method-rename/03-cyclic_with_swap.c @@ -2,13 +2,17 @@ - + // CRAM -void foo1(int c) { - if (c < 10) foo2(c + 1); +void newFun(int c) { diff --git a/tests/incremental/05-method-rename/04-deep_change.c b/tests/incremental/05-method-rename/04-deep_change.c index 80037f934d..4258329f2e 100644 --- a/tests/incremental/05-method-rename/04-deep_change.c +++ b/tests/incremental/05-method-rename/04-deep_change.c @@ -1,5 +1,5 @@ #include - +// CRAM void zap() { printf("zap"); } diff --git a/tests/incremental/05-method-rename/04-deep_change.patch b/tests/incremental/05-method-rename/04-deep_change.patch index 687b8f74bc..6d7ec21bf8 100644 --- a/tests/incremental/05-method-rename/04-deep_change.patch +++ b/tests/incremental/05-method-rename/04-deep_change.patch @@ -2,7 +2,7 @@ +++ tests/incremental/05-method-rename/04-deep_change.c @@ -1,7 +1,7 @@ #include - + // CRAM void zap() { - printf("zap"); + printf("drap"); diff --git a/tests/incremental/05-method-rename/05-common_rename.c b/tests/incremental/05-method-rename/05-common_rename.c index ce72a6dda1..643bba554a 100644 --- a/tests/incremental/05-method-rename/05-common_rename.c +++ b/tests/incremental/05-method-rename/05-common_rename.c @@ -1,5 +1,5 @@ #include - +// CRAM void foo() { printf("foo"); } diff --git a/tests/incremental/05-method-rename/05-common_rename.patch b/tests/incremental/05-method-rename/05-common_rename.patch index 93904d5780..dcf6d15719 100644 --- a/tests/incremental/05-method-rename/05-common_rename.patch +++ b/tests/incremental/05-method-rename/05-common_rename.patch @@ -2,7 +2,7 @@ +++ tests/incremental/05-method-rename/05-common_rename.c @@ -1,20 +1,20 @@ #include - + // CRAM -void foo() { +void bar() { printf("foo"); diff --git a/tests/incremental/05-method-rename/06-recursive_rename.c b/tests/incremental/05-method-rename/06-recursive_rename.c index dc9ac72e94..3d1dd2b9d1 100644 --- a/tests/incremental/05-method-rename/06-recursive_rename.c +++ b/tests/incremental/05-method-rename/06-recursive_rename.c @@ -1,3 +1,4 @@ +// CRAM void foo(int x) { if(x > 1) foo(x - 1); } diff --git a/tests/incremental/05-method-rename/06-recursive_rename.patch b/tests/incremental/05-method-rename/06-recursive_rename.patch index 356f959256..e29be521be 100644 --- a/tests/incremental/05-method-rename/06-recursive_rename.patch +++ b/tests/incremental/05-method-rename/06-recursive_rename.patch @@ -1,6 +1,7 @@ --- tests/incremental/05-method-rename/06-recursive_rename.c +++ tests/incremental/05-method-rename/06-recursive_rename.c -@@ -1,7 +1,7 @@ +@@ -1,8 +1,8 @@ + // CRAM -void foo(int x) { - if(x > 1) foo(x - 1); +void bar(int x) { diff --git a/tests/incremental/06-glob-var-rename/00-simple_rename.c b/tests/incremental/06-glob-var-rename/00-simple_rename.c index 56650e98ed..c06ba3a85a 100644 --- a/tests/incremental/06-glob-var-rename/00-simple_rename.c +++ b/tests/incremental/06-glob-var-rename/00-simple_rename.c @@ -1,5 +1,5 @@ #include - +// CRAM int foo = 1; int main() { diff --git a/tests/incremental/06-glob-var-rename/00-simple_rename.patch b/tests/incremental/06-glob-var-rename/00-simple_rename.patch index 1e0f3b2565..b9d20af358 100644 --- a/tests/incremental/06-glob-var-rename/00-simple_rename.patch +++ b/tests/incremental/06-glob-var-rename/00-simple_rename.patch @@ -2,7 +2,7 @@ +++ tests/incremental/06-glob-var-rename/00-simple_rename.c @@ -1,9 +1,9 @@ #include - + // CRAM -int foo = 1; +int bar = 1; diff --git a/tests/incremental/06-glob-var-rename/01-duplicate_local_global.c b/tests/incremental/06-glob-var-rename/01-duplicate_local_global.c index 9ad715e50d..35e527225c 100644 --- a/tests/incremental/06-glob-var-rename/01-duplicate_local_global.c +++ b/tests/incremental/06-glob-var-rename/01-duplicate_local_global.c @@ -1,5 +1,5 @@ #include - +// CRAM int foo = 1; int main() { diff --git a/tests/incremental/06-glob-var-rename/01-duplicate_local_global.patch b/tests/incremental/06-glob-var-rename/01-duplicate_local_global.patch index 1d65c5672a..2c44da4cce 100644 --- a/tests/incremental/06-glob-var-rename/01-duplicate_local_global.patch +++ b/tests/incremental/06-glob-var-rename/01-duplicate_local_global.patch @@ -2,7 +2,7 @@ +++ tests/incremental/06-glob-var-rename/01-duplicate_local_global.c @@ -1,14 +1,14 @@ #include - + // CRAM -int foo = 1; +int bar = 1; diff --git a/tests/incremental/06-glob-var-rename/02-add_new_gvar.c b/tests/incremental/06-glob-var-rename/02-add_new_gvar.c index 5efe319981..c399e1f784 100644 --- a/tests/incremental/06-glob-var-rename/02-add_new_gvar.c +++ b/tests/incremental/06-glob-var-rename/02-add_new_gvar.c @@ -1,5 +1,5 @@ #include - +// CRAM int myVar = 1; int main() { diff --git a/tests/incremental/06-glob-var-rename/02-add_new_gvar.patch b/tests/incremental/06-glob-var-rename/02-add_new_gvar.patch index f0c145107a..0ca97da64f 100644 --- a/tests/incremental/06-glob-var-rename/02-add_new_gvar.patch +++ b/tests/incremental/06-glob-var-rename/02-add_new_gvar.patch @@ -2,7 +2,7 @@ +++ tests/incremental/06-glob-var-rename/02-add_new_gvar.c @@ -1,8 +1,9 @@ #include - + // CRAM int myVar = 1; +int foo = 1; diff --git a/tests/incremental/11-restart/00-justglob.c b/tests/incremental/11-restart/00-justglob.c index 61b059f8ea..1e35604d3f 100644 --- a/tests/incremental/11-restart/00-justglob.c +++ b/tests/incremental/11-restart/00-justglob.c @@ -1,2 +1,3 @@ int max_domains = 0; int main() {} +// CRAM diff --git a/tests/regression/00-sanity/01-assert.t b/tests/regression/00-sanity/01-assert.t index 9142f805f9..9b3b55f530 100644 --- a/tests/regression/00-sanity/01-assert.t +++ b/tests/regression/00-sanity/01-assert.t @@ -9,3 +9,275 @@ live: 7 dead: 2 total lines: 9 + + +Test ancient solvers: + + $ goblint --enable warn.deterministic --set solver WL 01-assert.c + [Error][Assert] Assertion "fail" will fail. (01-assert.c:12:3-12:25) + [Warning][Assert] Assertion "unknown == 4" is unknown. (01-assert.c:11:3-11:33) + [Success][Assert] Assertion "success" will succeed (01-assert.c:10:3-10:28) + [Warning][Deadcode] Function 'main' does not return + [Warning][Deadcode] Function 'main' has dead code: + on lines 13..14 (01-assert.c:13-14) + [Warning][Deadcode] Logical lines of code (LLoC) summary: + live: 7 + dead: 2 + total lines: 9 + + $ goblint --enable warn.deterministic --set solver effectWConEq 01-assert.c + [Error][Assert] Assertion "fail" will fail. (01-assert.c:12:3-12:25) + [Warning][Assert] Assertion "unknown == 4" is unknown. (01-assert.c:11:3-11:33) + [Success][Assert] Assertion "success" will succeed (01-assert.c:10:3-10:28) + [Warning][Deadcode] Function 'main' does not return + [Warning][Deadcode] Function 'main' has dead code: + on lines 13..14 (01-assert.c:13-14) + [Warning][Deadcode] Logical lines of code (LLoC) summary: + live: 7 + dead: 2 + total lines: 9 + + +Test topdown solvers: + + $ goblint --enable warn.deterministic --set solver topdown_deprecated 01-assert.c + [Error][Assert] Assertion "fail" will fail. (01-assert.c:12:3-12:25) + [Warning][Assert] Assertion "unknown == 4" is unknown. (01-assert.c:11:3-11:33) + [Success][Assert] Assertion "success" will succeed (01-assert.c:10:3-10:28) + [Warning][Deadcode] Function 'main' does not return + [Warning][Deadcode] Function 'main' has dead code: + on lines 13..14 (01-assert.c:13-14) + [Warning][Deadcode] Logical lines of code (LLoC) summary: + live: 7 + dead: 2 + total lines: 9 + + $ goblint --enable warn.deterministic --set solver topdown 01-assert.c + [Error][Assert] Assertion "fail" will fail. (01-assert.c:12:3-12:25) + [Warning][Assert] Assertion "unknown == 4" is unknown. (01-assert.c:11:3-11:33) + [Success][Assert] Assertion "success" will succeed (01-assert.c:10:3-10:28) + [Warning][Deadcode] Function 'main' does not return + [Warning][Deadcode] Function 'main' has dead code: + on lines 13..14 (01-assert.c:13-14) + [Warning][Deadcode] Logical lines of code (LLoC) summary: + live: 7 + dead: 2 + total lines: 9 + + $ goblint --enable warn.deterministic --set solver topdown_term 01-assert.c + [Error][Assert] Assertion "fail" will fail. (01-assert.c:12:3-12:25) + [Warning][Assert] Assertion "unknown == 4" is unknown. (01-assert.c:11:3-11:33) + [Success][Assert] Assertion "success" will succeed (01-assert.c:10:3-10:28) + [Warning][Deadcode] Function 'main' does not return + [Warning][Deadcode] Function 'main' has dead code: + on lines 13..14 (01-assert.c:13-14) + [Warning][Deadcode] Logical lines of code (LLoC) summary: + live: 7 + dead: 2 + total lines: 9 + + $ goblint --enable warn.deterministic --set solver topdown_space_cache_term 01-assert.c + [Error][Assert] Assertion "fail" will fail. (01-assert.c:12:3-12:25) + [Warning][Assert] Assertion "unknown == 4" is unknown. (01-assert.c:11:3-11:33) + [Success][Assert] Assertion "success" will succeed (01-assert.c:10:3-10:28) + [Warning][Deadcode] Function 'main' does not return + [Warning][Deadcode] Function 'main' has dead code: + on lines 13..14 (01-assert.c:13-14) + [Warning][Deadcode] Logical lines of code (LLoC) summary: + live: 7 + dead: 2 + total lines: 9 + + $ goblint --enable warn.deterministic --set solver td3 01-assert.c + [Error][Assert] Assertion "fail" will fail. (01-assert.c:12:3-12:25) + [Warning][Assert] Assertion "unknown == 4" is unknown. (01-assert.c:11:3-11:33) + [Success][Assert] Assertion "success" will succeed (01-assert.c:10:3-10:28) + [Warning][Deadcode] Function 'main' does not return + [Warning][Deadcode] Function 'main' has dead code: + on lines 13..14 (01-assert.c:13-14) + [Warning][Deadcode] Logical lines of code (LLoC) summary: + live: 7 + dead: 2 + total lines: 9 + + +Test SLR solvers: + + $ goblint --enable warn.deterministic --set solver widen1 01-assert.c + [Error][Assert] Assertion "fail" will fail. (01-assert.c:12:3-12:25) + [Warning][Assert] Assertion "unknown == 4" is unknown. (01-assert.c:11:3-11:33) + [Success][Assert] Assertion "success" will succeed (01-assert.c:10:3-10:28) + [Warning][Deadcode] Function 'main' does not return + [Warning][Deadcode] Function 'main' has dead code: + on lines 13..14 (01-assert.c:13-14) + [Warning][Deadcode] Logical lines of code (LLoC) summary: + live: 7 + dead: 2 + total lines: 9 + + $ goblint --enable warn.deterministic --set solver widen2 01-assert.c + [Error][Assert] Assertion "fail" will fail. (01-assert.c:12:3-12:25) + [Warning][Assert] Assertion "unknown == 4" is unknown. (01-assert.c:11:3-11:33) + [Success][Assert] Assertion "success" will succeed (01-assert.c:10:3-10:28) + [Warning][Deadcode] Function 'main' does not return + [Warning][Deadcode] Function 'main' has dead code: + on lines 13..14 (01-assert.c:13-14) + [Warning][Deadcode] Logical lines of code (LLoC) summary: + live: 7 + dead: 2 + total lines: 9 + + $ goblint --enable warn.deterministic --set solver widen3 01-assert.c + [Error][Assert] Assertion "fail" will fail. (01-assert.c:12:3-12:25) + [Warning][Assert] Assertion "unknown == 4" is unknown. (01-assert.c:11:3-11:33) + [Success][Assert] Assertion "success" will succeed (01-assert.c:10:3-10:28) + [Warning][Deadcode] Function 'main' does not return + [Warning][Deadcode] Function 'main' has dead code: + on lines 13..14 (01-assert.c:13-14) + [Warning][Deadcode] Logical lines of code (LLoC) summary: + live: 7 + dead: 2 + total lines: 9 + + $ goblint --enable warn.deterministic --set solver new 01-assert.c + [Error][Assert] Assertion "fail" will fail. (01-assert.c:12:3-12:25) + [Warning][Assert] Assertion "unknown == 4" is unknown. (01-assert.c:11:3-11:33) + [Success][Assert] Assertion "success" will succeed (01-assert.c:10:3-10:28) + [Warning][Deadcode] Function 'main' does not return + [Warning][Deadcode] Function 'main' has dead code: + on lines 13..14 (01-assert.c:13-14) + [Warning][Deadcode] Logical lines of code (LLoC) summary: + live: 7 + dead: 2 + total lines: 9 + + $ goblint --enable warn.deterministic --set solver slr+ 01-assert.c + [Error][Assert] Assertion "fail" will fail. (01-assert.c:12:3-12:25) + [Warning][Assert] Assertion "unknown == 4" is unknown. (01-assert.c:11:3-11:33) + [Success][Assert] Assertion "success" will succeed (01-assert.c:10:3-10:28) + [Warning][Deadcode] Function 'main' does not return + [Warning][Deadcode] Function 'main' has dead code: + on lines 13..14 (01-assert.c:13-14) + [Warning][Deadcode] Logical lines of code (LLoC) summary: + live: 7 + dead: 2 + total lines: 9 + + $ goblint --enable warn.deterministic --set solver slr1 01-assert.c + [Error][Assert] Assertion "fail" will fail. (01-assert.c:12:3-12:25) + [Warning][Assert] Assertion "unknown == 4" is unknown. (01-assert.c:11:3-11:33) + [Success][Assert] Assertion "success" will succeed (01-assert.c:10:3-10:28) + [Warning][Deadcode] Function 'main' does not return + [Warning][Deadcode] Function 'main' has dead code: + on lines 13..14 (01-assert.c:13-14) + [Warning][Deadcode] Logical lines of code (LLoC) summary: + live: 7 + dead: 2 + total lines: 9 + + $ goblint --enable warn.deterministic --set solver slr2 01-assert.c + [Error][Assert] Assertion "fail" will fail. (01-assert.c:12:3-12:25) + [Warning][Assert] Assertion "unknown == 4" is unknown. (01-assert.c:11:3-11:33) + [Success][Assert] Assertion "success" will succeed (01-assert.c:10:3-10:28) + [Warning][Deadcode] Function 'main' does not return + [Warning][Deadcode] Function 'main' has dead code: + on lines 13..14 (01-assert.c:13-14) + [Warning][Deadcode] Logical lines of code (LLoC) summary: + live: 7 + dead: 2 + total lines: 9 + + $ goblint --enable warn.deterministic --set solver slr3 01-assert.c + [Error][Assert] Assertion "fail" will fail. (01-assert.c:12:3-12:25) + [Warning][Assert] Assertion "unknown == 4" is unknown. (01-assert.c:11:3-11:33) + [Success][Assert] Assertion "success" will succeed (01-assert.c:10:3-10:28) + [Warning][Deadcode] Function 'main' does not return + [Warning][Deadcode] Function 'main' has dead code: + on lines 13..14 (01-assert.c:13-14) + [Warning][Deadcode] Logical lines of code (LLoC) summary: + live: 7 + dead: 2 + total lines: 9 + + $ goblint --enable warn.deterministic --set solver slr4 01-assert.c + [Error][Assert] Assertion "fail" will fail. (01-assert.c:12:3-12:25) + [Warning][Assert] Assertion "unknown == 4" is unknown. (01-assert.c:11:3-11:33) + [Success][Assert] Assertion "success" will succeed (01-assert.c:10:3-10:28) + [Warning][Deadcode] Function 'main' does not return + [Warning][Deadcode] Function 'main' has dead code: + on lines 13..14 (01-assert.c:13-14) + [Warning][Deadcode] Logical lines of code (LLoC) summary: + live: 7 + dead: 2 + total lines: 9 + + $ goblint --enable warn.deterministic --set solver slr1p 01-assert.c + [Error][Assert] Assertion "fail" will fail. (01-assert.c:12:3-12:25) + [Warning][Assert] Assertion "unknown == 4" is unknown. (01-assert.c:11:3-11:33) + [Success][Assert] Assertion "success" will succeed (01-assert.c:10:3-10:28) + [Warning][Deadcode] Function 'main' does not return + [Warning][Deadcode] Function 'main' has dead code: + on lines 13..14 (01-assert.c:13-14) + [Warning][Deadcode] Logical lines of code (LLoC) summary: + live: 7 + dead: 2 + total lines: 9 + $ goblint --enable warn.deterministic --set solver slr2p 01-assert.c + [Error][Assert] Assertion "fail" will fail. (01-assert.c:12:3-12:25) + [Warning][Assert] Assertion "unknown == 4" is unknown. (01-assert.c:11:3-11:33) + [Success][Assert] Assertion "success" will succeed (01-assert.c:10:3-10:28) + [Warning][Deadcode] Function 'main' does not return + [Warning][Deadcode] Function 'main' has dead code: + on lines 13..14 (01-assert.c:13-14) + [Warning][Deadcode] Logical lines of code (LLoC) summary: + live: 7 + dead: 2 + total lines: 9 + + $ goblint --enable warn.deterministic --set solver slr3p 01-assert.c + [Error][Assert] Assertion "fail" will fail. (01-assert.c:12:3-12:25) + [Warning][Assert] Assertion "unknown == 4" is unknown. (01-assert.c:11:3-11:33) + [Success][Assert] Assertion "success" will succeed (01-assert.c:10:3-10:28) + [Warning][Deadcode] Function 'main' does not return + [Warning][Deadcode] Function 'main' has dead code: + on lines 13..14 (01-assert.c:13-14) + [Warning][Deadcode] Logical lines of code (LLoC) summary: + live: 7 + dead: 2 + total lines: 9 + + $ goblint --enable warn.deterministic --set solver slr4p 01-assert.c + [Error][Assert] Assertion "fail" will fail. (01-assert.c:12:3-12:25) + [Warning][Assert] Assertion "unknown == 4" is unknown. (01-assert.c:11:3-11:33) + [Success][Assert] Assertion "success" will succeed (01-assert.c:10:3-10:28) + [Warning][Deadcode] Function 'main' does not return + [Warning][Deadcode] Function 'main' has dead code: + on lines 13..14 (01-assert.c:13-14) + [Warning][Deadcode] Logical lines of code (LLoC) summary: + live: 7 + dead: 2 + total lines: 9 + + $ goblint --enable warn.deterministic --set solver slr3t 01-assert.c + [Error][Assert] Assertion "fail" will fail. (01-assert.c:12:3-12:25) + [Warning][Assert] Assertion "unknown == 4" is unknown. (01-assert.c:11:3-11:33) + [Success][Assert] Assertion "success" will succeed (01-assert.c:10:3-10:28) + [Warning][Deadcode] Function 'main' does not return + [Warning][Deadcode] Function 'main' has dead code: + on lines 13..14 (01-assert.c:13-14) + [Warning][Deadcode] Logical lines of code (LLoC) summary: + live: 7 + dead: 2 + total lines: 9 + + $ goblint --enable warn.deterministic --set solver slr3tp 01-assert.c + [Error][Assert] Assertion "fail" will fail. (01-assert.c:12:3-12:25) + [Warning][Assert] Assertion "unknown == 4" is unknown. (01-assert.c:11:3-11:33) + [Success][Assert] Assertion "success" will succeed (01-assert.c:10:3-10:28) + [Warning][Deadcode] Function 'main' does not return + [Warning][Deadcode] Function 'main' has dead code: + on lines 13..14 (01-assert.c:13-14) + [Warning][Deadcode] Logical lines of code (LLoC) summary: + live: 7 + dead: 2 + total lines: 9 diff --git a/tests/regression/00-sanity/18-parse-empty-array.c b/tests/regression/00-sanity/18-parse-empty-array.c index a38cbc3712..2205d412da 100644 --- a/tests/regression/00-sanity/18-parse-empty-array.c +++ b/tests/regression/00-sanity/18-parse-empty-array.c @@ -1,4 +1,5 @@ // Test for issue in https://github.com/goblint/cil/issues/19 +// NOCRASH static int a[]; static int a[] = {}; static int b[0] = {}; diff --git a/tests/regression/00-sanity/20-if-0-realnode.c b/tests/regression/00-sanity/20-if-0-realnode.c index 01b9ca74dd..52140324a6 100644 --- a/tests/regression/00-sanity/20-if-0-realnode.c +++ b/tests/regression/00-sanity/20-if-0-realnode.c @@ -1,3 +1,5 @@ +// CRAM + void stuff() { } diff --git a/tests/regression/00-sanity/20-if-0-realnode.t b/tests/regression/00-sanity/20-if-0-realnode.t index 32f06ecdd4..46a0e47a69 100644 --- a/tests/regression/00-sanity/20-if-0-realnode.t +++ b/tests/regression/00-sanity/20-if-0-realnode.t @@ -8,29 +8,29 @@ │ (body) ▼ ┌─────────────────────────────────────────┐ - │ 20-if-0-realnode.c:8:5-14:5 │ - │ (20-if-0-realnode.c:8:9-8:10) │ - │ [20-if-0-realnode.c:7:5-8:5 │ Neg(0) + │ 20-if-0-realnode.c:10:5-16:5 │ + │ (20-if-0-realnode.c:10:9-10:10) │ + │ [20-if-0-realnode.c:9:5-10:5 │ Neg(0) │ (unknown)] │ ─────────┐ - │ YAML loc: 20-if-0-realnode.c:8:5-14:5 │ │ + │ YAML loc: 20-if-0-realnode.c:10:5-16:5 │ │ │ GraphML: true; server: true │ ◀────────┘ └─────────────────────────────────────────┘ │ │ Pos(0) ▼ ┌─────────────────────────────────────────┐ - │ 20-if-0-realnode.c:10:9-10:16 │ - │ (20-if-0-realnode.c:10:9-10:16) │ - │ YAML loc: 20-if-0-realnode.c:10:9-10:16 │ + │ 20-if-0-realnode.c:12:9-12:16 │ + │ (20-if-0-realnode.c:12:9-12:16) │ + │ YAML loc: 20-if-0-realnode.c:12:9-12:16 │ │ GraphML: true; server: true │ └─────────────────────────────────────────┘ │ │ stuff() ▼ ┌─────────────────────────────────────────┐ - │ 20-if-0-realnode.c:15:5-15:13 │ - │ (20-if-0-realnode.c:15:12-15:13) │ - │ YAML loc: 20-if-0-realnode.c:15:5-15:13 │ + │ 20-if-0-realnode.c:17:5-17:13 │ + │ (20-if-0-realnode.c:17:12-17:13) │ + │ YAML loc: 20-if-0-realnode.c:17:5-17:13 │ │ GraphML: true; server: true │ └─────────────────────────────────────────┘ │ diff --git a/tests/regression/00-sanity/21-empty-loops.c b/tests/regression/00-sanity/21-empty-loops.c index f3f0feb2e5..220e5bf6e9 100644 --- a/tests/regression/00-sanity/21-empty-loops.c +++ b/tests/regression/00-sanity/21-empty-loops.c @@ -1,3 +1,4 @@ +// CRAM int main() { // non-deterministically make all variants live diff --git a/tests/regression/00-sanity/21-empty-loops.t b/tests/regression/00-sanity/21-empty-loops.t index 932a21f049..c9cc096d15 100644 --- a/tests/regression/00-sanity/21-empty-loops.t +++ b/tests/regression/00-sanity/21-empty-loops.t @@ -8,20 +8,20 @@ │ (body) ▼ ┌───────────────────────────────────────┐ - │ 21-empty-loops.c:57:3-57:31 │ + │ 21-empty-loops.c:58:3-58:31 │ │ (unknown) │ - │ [21-empty-loops.c:56:1-57:3 │ skip + │ [21-empty-loops.c:57:1-58:3 │ skip │ (unknown)] │ ───────┐ - │ YAML loc: 21-empty-loops.c:57:3-57:31 │ │ + │ YAML loc: 21-empty-loops.c:58:3-58:31 │ │ │ GraphML: true; server: true │ ◀──────┘ └───────────────────────────────────────┘ │ │ Neg(1) ▼ ┌───────────────────────────────────────┐ - │ 21-empty-loops.c:58:1-58:1 │ + │ 21-empty-loops.c:59:1-59:1 │ │ (unknown) │ - │ YAML loc: 21-empty-loops.c:58:1-58:1 │ + │ YAML loc: 21-empty-loops.c:59:1-59:1 │ │ GraphML: true; server: true │ └───────────────────────────────────────┘ │ @@ -39,19 +39,19 @@ │ (body) ▼ ┌────────────────────────────────────────────┐ - │ 21-empty-loops.c:62:3-62:14 (synthetic) │ - │ (21-empty-loops.c:62:10-62:11 (synthetic)) │ Pos(1) - │ YAML loop: 21-empty-loops.c:62:3-62:14 │ ─────────┐ + │ 21-empty-loops.c:63:3-63:14 (synthetic) │ + │ (21-empty-loops.c:63:10-63:11 (synthetic)) │ Pos(1) + │ YAML loop: 21-empty-loops.c:63:3-63:14 │ ─────────┐ │ GraphML: true; server: false │ │ - │ loop: 21-empty-loops.c:62:3-62:14 │ ◀────────┘ + │ loop: 21-empty-loops.c:63:3-63:14 │ ◀────────┘ └────────────────────────────────────────────┘ │ │ Neg(1) ▼ ┌────────────────────────────────────────────┐ - │ 21-empty-loops.c:63:1-63:1 │ + │ 21-empty-loops.c:64:1-64:1 │ │ (unknown) │ - │ YAML loc: 21-empty-loops.c:63:1-63:1 │ + │ YAML loc: 21-empty-loops.c:64:1-64:1 │ │ GraphML: true; server: true │ └────────────────────────────────────────────┘ │ @@ -63,18 +63,18 @@ $ graph-easy --as=boxart f_empty_goto_loop_suffix.dot ┌───────────────────────────────────────┐ - │ 21-empty-loops.c:75:3-75:11 │ - │ (21-empty-loops.c:75:3-75:11) │ - │ YAML loc: 21-empty-loops.c:75:3-75:11 │ + │ 21-empty-loops.c:76:3-76:11 │ + │ (21-empty-loops.c:76:3-76:11) │ + │ YAML loc: 21-empty-loops.c:76:3-76:11 │ │ GraphML: true; server: true │ └───────────────────────────────────────┘ │ │ suffix() ▼ ┌───────────────────────────────────────┐ - │ 21-empty-loops.c:76:1-76:1 │ + │ 21-empty-loops.c:77:1-77:1 │ │ (unknown) │ - │ YAML loc: 21-empty-loops.c:76:1-76:1 │ + │ YAML loc: 21-empty-loops.c:77:1-77:1 │ │ GraphML: true; server: true │ ◀┐ └───────────────────────────────────────┘ │ │ │ @@ -90,11 +90,11 @@ │ (body) │ ▼ │ ┌───────────────────────────────────────┐ │ - │ 21-empty-loops.c:73:3-73:38 │ │ + │ 21-empty-loops.c:74:3-74:38 │ │ │ (unknown) │ │ - skip │ [21-empty-loops.c:72:1-73:3 │ │ + skip │ [21-empty-loops.c:73:1-74:3 │ │ ┌─────── │ (unknown)] │ │ - │ │ YAML loc: 21-empty-loops.c:73:3-73:38 │ │ + │ │ YAML loc: 21-empty-loops.c:74:3-74:38 │ │ └──────▶ │ GraphML: true; server: true │ ─┘ └───────────────────────────────────────┘ @@ -106,28 +106,28 @@ │ (body) ▼ ┌────────────────────────────────────────────┐ - │ 21-empty-loops.c:80:3-80:14 (synthetic) │ - │ (21-empty-loops.c:80:10-80:11 (synthetic)) │ Pos(1) - │ YAML loop: 21-empty-loops.c:80:3-80:14 │ ─────────┐ + │ 21-empty-loops.c:81:3-81:14 (synthetic) │ + │ (21-empty-loops.c:81:10-81:11 (synthetic)) │ Pos(1) + │ YAML loop: 21-empty-loops.c:81:3-81:14 │ ─────────┐ │ GraphML: true; server: false │ │ - │ loop: 21-empty-loops.c:80:3-80:14 │ ◀────────┘ + │ loop: 21-empty-loops.c:81:3-81:14 │ ◀────────┘ └────────────────────────────────────────────┘ │ │ Neg(1) ▼ ┌────────────────────────────────────────────┐ - │ 21-empty-loops.c:82:3-82:11 │ - │ (21-empty-loops.c:82:3-82:11) │ - │ YAML loc: 21-empty-loops.c:82:3-82:11 │ + │ 21-empty-loops.c:83:3-83:11 │ + │ (21-empty-loops.c:83:3-83:11) │ + │ YAML loc: 21-empty-loops.c:83:3-83:11 │ │ GraphML: true; server: true │ └────────────────────────────────────────────┘ │ │ suffix() ▼ ┌────────────────────────────────────────────┐ - │ 21-empty-loops.c:83:1-83:1 │ + │ 21-empty-loops.c:84:1-84:1 │ │ (unknown) │ - │ YAML loc: 21-empty-loops.c:83:1-83:1 │ + │ YAML loc: 21-empty-loops.c:84:1-84:1 │ │ GraphML: true; server: true │ └────────────────────────────────────────────┘ │ @@ -145,18 +145,18 @@ │ (body) ▼ ┌──────────────────────────────────────┐ - │ 21-empty-loops.c:93:3-93:9 │ body() - │ (21-empty-loops.c:93:3-93:9) │ ─────────┐ - │ YAML loc: 21-empty-loops.c:93:3-93:9 │ │ + │ 21-empty-loops.c:94:3-94:9 │ body() + │ (21-empty-loops.c:94:3-94:9) │ ─────────┐ + │ YAML loc: 21-empty-loops.c:94:3-94:9 │ │ │ GraphML: true; server: true │ ◀────────┘ └──────────────────────────────────────┘ │ │ Neg(1) ▼ ┌──────────────────────────────────────┐ - │ 21-empty-loops.c:95:1-95:1 │ + │ 21-empty-loops.c:96:1-96:1 │ │ (unknown) │ - │ YAML loc: 21-empty-loops.c:95:1-95:1 │ + │ YAML loc: 21-empty-loops.c:96:1-96:1 │ │ GraphML: true; server: true │ └──────────────────────────────────────┘ │ @@ -168,36 +168,36 @@ $ graph-easy --as=boxart f_nonempty_while_loop.dot - ┌───────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ │ - │ ┌────────────────────────────────────────────┐ │ - │ │ f_nonempty_while_loop() │ │ - │ └────────────────────────────────────────────┘ │ - │ │ │ body() - │ │ (body) │ - │ ▼ │ - ┌─────────────────────────────────────────┐ ┌────────────────────────────────────────────┐ │ - │ 21-empty-loops.c:101:5-101:11 │ │ 21-empty-loops.c:99:3-102:3 (synthetic) │ │ - │ (21-empty-loops.c:101:5-101:11) │ │ (21-empty-loops.c:99:10-99:11 (synthetic)) │ │ - │ YAML loc: 21-empty-loops.c:101:5-101:11 │ │ YAML loop: 21-empty-loops.c:99:3-102:3 │ │ - │ GraphML: true; server: true │ Pos(1) │ GraphML: true; server: false │ │ - │ │ ◀──────── │ loop: 21-empty-loops.c:99:3-102:3 │ ◀┘ - └─────────────────────────────────────────┘ └────────────────────────────────────────────┘ + ┌─────────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ │ + │ ┌──────────────────────────────────────────────┐ │ + │ │ f_nonempty_while_loop() │ │ + │ └──────────────────────────────────────────────┘ │ + │ │ │ body() + │ │ (body) │ + │ ▼ │ + ┌─────────────────────────────────────────┐ ┌──────────────────────────────────────────────┐ │ + │ 21-empty-loops.c:102:5-102:11 │ │ 21-empty-loops.c:100:3-103:3 (synthetic) │ │ + │ (21-empty-loops.c:102:5-102:11) │ │ (21-empty-loops.c:100:10-100:11 (synthetic)) │ │ + │ YAML loc: 21-empty-loops.c:102:5-102:11 │ │ YAML loop: 21-empty-loops.c:100:3-103:3 │ │ + │ GraphML: true; server: true │ Pos(1) │ GraphML: true; server: false │ │ + │ │ ◀──────── │ loop: 21-empty-loops.c:100:3-103:3 │ ◀┘ + └─────────────────────────────────────────┘ └──────────────────────────────────────────────┘ │ │ Neg(1) ▼ - ┌────────────────────────────────────────────┐ - │ 21-empty-loops.c:103:1-103:1 │ - │ (unknown) │ - │ YAML loc: 21-empty-loops.c:103:1-103:1 │ - │ GraphML: true; server: true │ - └────────────────────────────────────────────┘ + ┌──────────────────────────────────────────────┐ + │ 21-empty-loops.c:104:1-104:1 │ + │ (unknown) │ + │ YAML loc: 21-empty-loops.c:104:1-104:1 │ + │ GraphML: true; server: true │ + └──────────────────────────────────────────────┘ │ │ return ▼ - ┌────────────────────────────────────────────┐ - │ return of f_nonempty_while_loop() │ - └────────────────────────────────────────────┘ + ┌──────────────────────────────────────────────┐ + │ return of f_nonempty_while_loop() │ + └──────────────────────────────────────────────┘ $ graph-easy --as=boxart f_empty_goto_loop_prefix.dot @@ -208,29 +208,29 @@ │ (body) ▼ ┌─────────────────────────────────────────┐ - │ 21-empty-loops.c:112:3-112:11 │ - │ (21-empty-loops.c:112:3-112:11) │ - │ YAML loc: 21-empty-loops.c:112:3-112:11 │ + │ 21-empty-loops.c:113:3-113:11 │ + │ (21-empty-loops.c:113:3-113:11) │ + │ YAML loc: 21-empty-loops.c:113:3-113:11 │ │ GraphML: true; server: true │ └─────────────────────────────────────────┘ │ │ prefix() ▼ ┌─────────────────────────────────────────┐ - │ 21-empty-loops.c:115:3-115:38 │ + │ 21-empty-loops.c:116:3-116:38 │ │ (unknown) │ - │ [21-empty-loops.c:114:1-115:3 │ skip + │ [21-empty-loops.c:115:1-116:3 │ skip │ (unknown)] │ ───────┐ - │ YAML loc: 21-empty-loops.c:115:3-115:38 │ │ + │ YAML loc: 21-empty-loops.c:116:3-116:38 │ │ │ GraphML: true; server: true │ ◀──────┘ └─────────────────────────────────────────┘ │ │ Neg(1) ▼ ┌─────────────────────────────────────────┐ - │ 21-empty-loops.c:116:1-116:1 │ + │ 21-empty-loops.c:117:1-117:1 │ │ (unknown) │ - │ YAML loc: 21-empty-loops.c:116:1-116:1 │ + │ YAML loc: 21-empty-loops.c:117:1-117:1 │ │ GraphML: true; server: true │ └─────────────────────────────────────────┘ │ @@ -248,28 +248,28 @@ │ (body) ▼ ┌──────────────────────────────────────────────┐ - │ 21-empty-loops.c:120:3-120:11 │ - │ (21-empty-loops.c:120:3-120:11) │ - │ YAML loc: 21-empty-loops.c:120:3-120:11 │ + │ 21-empty-loops.c:121:3-121:11 │ + │ (21-empty-loops.c:121:3-121:11) │ + │ YAML loc: 21-empty-loops.c:121:3-121:11 │ │ GraphML: true; server: true │ └──────────────────────────────────────────────┘ │ │ prefix() ▼ ┌──────────────────────────────────────────────┐ - │ 21-empty-loops.c:122:3-122:14 (synthetic) │ - │ (21-empty-loops.c:122:10-122:11 (synthetic)) │ Pos(1) - │ YAML loop: 21-empty-loops.c:122:3-122:14 │ ─────────┐ + │ 21-empty-loops.c:123:3-123:14 (synthetic) │ + │ (21-empty-loops.c:123:10-123:11 (synthetic)) │ Pos(1) + │ YAML loop: 21-empty-loops.c:123:3-123:14 │ ─────────┐ │ GraphML: true; server: false │ │ - │ loop: 21-empty-loops.c:122:3-122:14 │ ◀────────┘ + │ loop: 21-empty-loops.c:123:3-123:14 │ ◀────────┘ └──────────────────────────────────────────────┘ │ │ Neg(1) ▼ ┌──────────────────────────────────────────────┐ - │ 21-empty-loops.c:123:1-123:1 │ + │ 21-empty-loops.c:124:1-124:1 │ │ (unknown) │ - │ YAML loc: 21-empty-loops.c:123:1-123:1 │ + │ YAML loc: 21-empty-loops.c:124:1-124:1 │ │ GraphML: true; server: true │ └──────────────────────────────────────────────┘ │ @@ -289,7 +289,7 @@ ┌─────────────────────────────────────────┐ │ unknown │ │ (unknown) │ skip - │ [21-empty-loops.c:127:1-128:3 │ ───────┐ + │ [21-empty-loops.c:128:1-129:3 │ ───────┐ │ (unknown)] │ │ │ GraphML: true; server: true │ ◀──────┘ └─────────────────────────────────────────┘ @@ -297,9 +297,9 @@ │ Neg(1) ▼ ┌─────────────────────────────────────────┐ - │ 21-empty-loops.c:131:1-131:1 │ + │ 21-empty-loops.c:132:1-132:1 │ │ (unknown) │ - │ YAML loc: 21-empty-loops.c:131:1-131:1 │ + │ YAML loc: 21-empty-loops.c:132:1-132:1 │ │ GraphML: true; server: true │ └─────────────────────────────────────────┘ │ @@ -317,19 +317,19 @@ │ (body) ▼ ┌──────────────────────────────────────────────┐ - │ 21-empty-loops.c:135:3-137:3 (synthetic) │ - │ (21-empty-loops.c:135:10-135:11 (synthetic)) │ Pos(1) - │ YAML loop: 21-empty-loops.c:135:3-137:3 │ ─────────┐ + │ 21-empty-loops.c:136:3-138:3 (synthetic) │ + │ (21-empty-loops.c:136:10-136:11 (synthetic)) │ Pos(1) + │ YAML loop: 21-empty-loops.c:136:3-138:3 │ ─────────┐ │ GraphML: true; server: false │ │ - │ loop: 21-empty-loops.c:135:3-137:3 │ ◀────────┘ + │ loop: 21-empty-loops.c:136:3-138:3 │ ◀────────┘ └──────────────────────────────────────────────┘ │ │ Neg(1) ▼ ┌──────────────────────────────────────────────┐ - │ 21-empty-loops.c:138:1-138:1 │ + │ 21-empty-loops.c:139:1-139:1 │ │ (unknown) │ - │ YAML loc: 21-empty-loops.c:138:1-138:1 │ + │ YAML loc: 21-empty-loops.c:139:1-139:1 │ │ GraphML: true; server: true │ └──────────────────────────────────────────────┘ │ @@ -347,20 +347,20 @@ │ (body) ▼ ┌─────────────────────────────────────────┐ - │ 21-empty-loops.c:143:3-143:42 │ + │ 21-empty-loops.c:144:3-144:42 │ │ (unknown) │ - │ [21-empty-loops.c:142:1-143:3 │ skip + │ [21-empty-loops.c:143:1-144:3 │ skip │ (unknown)] │ ───────┐ - │ YAML loc: 21-empty-loops.c:143:3-143:42 │ │ + │ YAML loc: 21-empty-loops.c:144:3-144:42 │ │ │ GraphML: true; server: true │ ◀──────┘ └─────────────────────────────────────────┘ │ │ Neg(1) ▼ ┌─────────────────────────────────────────┐ - │ 21-empty-loops.c:146:1-146:1 │ + │ 21-empty-loops.c:147:1-147:1 │ │ (unknown) │ - │ YAML loc: 21-empty-loops.c:146:1-146:1 │ + │ YAML loc: 21-empty-loops.c:147:1-147:1 │ │ GraphML: true; server: true │ └─────────────────────────────────────────┘ │ @@ -380,7 +380,7 @@ ┌────────────────────────────────────────────────────────┐ │ unknown │ │ (unknown) │ skip - │ [21-empty-loops.c:150:1-151:3 │ ───────┐ + │ [21-empty-loops.c:151:1-152:3 │ ───────┐ │ (unknown)] │ │ │ GraphML: true; server: true │ ◀──────┘ └────────────────────────────────────────────────────────┘ @@ -388,9 +388,9 @@ │ Neg(1) ▼ ┌────────────────────────────────────────────────────────┐ - │ 21-empty-loops.c:155:1-155:1 │ + │ 21-empty-loops.c:156:1-156:1 │ │ (unknown) │ - │ YAML loc: 21-empty-loops.c:155:1-155:1 │ + │ YAML loc: 21-empty-loops.c:156:1-156:1 │ │ GraphML: true; server: true │ └────────────────────────────────────────────────────────┘ │ @@ -408,20 +408,20 @@ │ (body) ▼ ┌─────────────────────────────────────────────────────────┐ - │ 21-empty-loops.c:160:3-160:59 │ + │ 21-empty-loops.c:161:3-161:59 │ │ (unknown) │ - │ [21-empty-loops.c:159:1-160:3 │ skip + │ [21-empty-loops.c:160:1-161:3 │ skip │ (unknown)] │ ───────┐ - │ YAML loc: 21-empty-loops.c:160:3-160:59 │ │ + │ YAML loc: 21-empty-loops.c:161:3-161:59 │ │ │ GraphML: true; server: true │ ◀──────┘ └─────────────────────────────────────────────────────────┘ │ │ Neg(1) ▼ ┌─────────────────────────────────────────────────────────┐ - │ 21-empty-loops.c:164:1-164:1 │ + │ 21-empty-loops.c:165:1-165:1 │ │ (unknown) │ - │ YAML loc: 21-empty-loops.c:164:1-164:1 │ + │ YAML loc: 21-empty-loops.c:165:1-165:1 │ │ GraphML: true; server: true │ └─────────────────────────────────────────────────────────┘ │ @@ -441,7 +441,7 @@ ┌───────────────────────────────────────────────────────┐ │ unknown │ │ (unknown) │ skip - │ [21-empty-loops.c:168:1-169:3 │ ───────┐ + │ [21-empty-loops.c:169:1-170:3 │ ───────┐ │ (unknown)] │ │ │ GraphML: true; server: true │ ◀──────┘ └───────────────────────────────────────────────────────┘ @@ -449,9 +449,9 @@ │ Neg(1) ▼ ┌───────────────────────────────────────────────────────┐ - │ 21-empty-loops.c:174:1-174:1 │ + │ 21-empty-loops.c:175:1-175:1 │ │ (unknown) │ - │ YAML loc: 21-empty-loops.c:174:1-174:1 │ + │ YAML loc: 21-empty-loops.c:175:1-175:1 │ │ GraphML: true; server: true │ └───────────────────────────────────────────────────────┘ │ diff --git a/tests/regression/00-sanity/29-earlyglobs-insens.c b/tests/regression/00-sanity/29-earlyglobs-insens.c index 9d2f186c3e..e671522c51 100644 --- a/tests/regression/00-sanity/29-earlyglobs-insens.c +++ b/tests/regression/00-sanity/29-earlyglobs-insens.c @@ -1,2 +1,3 @@ // PARAM: --set ana.ctx_insens[+] base --enable exp.earlyglobs +// NOCRASH int main() {} diff --git a/tests/regression/00-sanity/33-hoare-over-paths.c b/tests/regression/00-sanity/33-hoare-over-paths.c index b30586fa59..e3af40cbb3 100644 --- a/tests/regression/00-sanity/33-hoare-over-paths.c +++ b/tests/regression/00-sanity/33-hoare-over-paths.c @@ -1,7 +1,7 @@ // PARAM: --set ana.path_sens[+] mutex #include #include - +// CRAM pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER; int main() { @@ -27,8 +27,8 @@ int main() { // NEW explanation: // using SensitiveDomain correctly keeps both paths as path-sensitivity demands - // TODO: manually check final join node in HTML - // cannot be automated because concrete execution cannot check + // cram test checks internal result + // cannot be automated with annotations because concrete execution cannot check // if _must_ lockset _may_ contain m on some path return 0; } diff --git a/tests/regression/00-sanity/33-hoare-over-paths.t b/tests/regression/00-sanity/33-hoare-over-paths.t new file mode 100644 index 0000000000..9f88f836b0 --- /dev/null +++ b/tests/regression/00-sanity/33-hoare-over-paths.t @@ -0,0 +1,228 @@ + $ goblint --set ana.path_sens[+] mutex --set result pretty-deterministic --set outfile pretty.txt 33-hoare-over-paths.c + [Success][Assert] Assertion "1" will succeed (33-hoare-over-paths.c:11:5-11:24) + [Success][Assert] Assertion "1" will succeed (33-hoare-over-paths.c:16:5-16:24) + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 7 + dead: 0 + total lines: 7 + + $ cat pretty.txt + Mapping { + 33-hoare-over-paths.c:9:7-9:8(main) -> + PathSensitive (ProjectiveSet (MCP.D * map)):{(MCP.D:[expRelation:(), + mallocWrapper:(wrapper call:Unknown node, unique calls:{}), + base:({ + Global { + m -> mutex + } + Local { + r -> ⊤ + } + }, {}, {}, {}), + threadid:(wrapper call:unknown node, Thread:[main], created:(current function:bot, callees:bot)), + threadflag:Singlethreaded, + threadreturn:true, + escape:{}, + mutexEvents:(), + access:(), + mutex:(lockset:{}, multiplicity:{}), + race:(), + mhp:(), + assert:(), + pthreadMutexType:()], map:{})} + 33-hoare-over-paths.c:10:5-10:10(main) -> + PathSensitive (ProjectiveSet (MCP.D * map)):{(MCP.D:[expRelation:(), + mallocWrapper:(wrapper call:Unknown node, unique calls:{}), + base:({ + Global { + m -> mutex + } + Local { + r -> + (Not {0}([-31,31])) + } + }, {}, {}, {}), + threadid:(wrapper call:unknown node, Thread:[main], created:(current function:bot, callees:bot)), + threadflag:Singlethreaded, + threadreturn:true, + escape:{}, + mutexEvents:(), + access:(), + mutex:(lockset:{}, multiplicity:{}), + race:(), + mhp:(), + assert:(), + pthreadMutexType:()], map:{})} + 33-hoare-over-paths.c:11:5-11:24(main) -> + PathSensitive (ProjectiveSet (MCP.D * map)):{(MCP.D:[expRelation:(), + mallocWrapper:(wrapper call:Unknown node, unique calls:{}), + base:({ + Global { + m -> mutex + } + Local { + r -> 0 + } + }, {}, {}, {}), + threadid:(wrapper call:unknown node, Thread:[main], created:(current function:bot, callees:bot)), + threadflag:Singlethreaded, + threadreturn:true, + escape:{}, + mutexEvents:(), + access:(), + mutex:(lockset:{}, multiplicity:{}), + race:(), + mhp:(), + assert:(), + pthreadMutexType:()], map:{})} + 33-hoare-over-paths.c:15:5-15:27(main) -> + PathSensitive (ProjectiveSet (MCP.D * map)):{(MCP.D:[expRelation:(), + mallocWrapper:(wrapper call:Unknown node, unique calls:{}), + base:({ + Global { + m -> mutex + } + Local { + r -> 0 + } + }, {}, {}, {}), + threadid:(wrapper call:unknown node, Thread:[main], created:(current function:bot, callees:bot)), + threadflag:Singlethreaded, + threadreturn:true, + escape:{}, + mutexEvents:(), + access:(), + mutex:(lockset:{}, multiplicity:{}), + race:(), + mhp:(), + assert:(), + pthreadMutexType:()], map:{})} + 33-hoare-over-paths.c:16:5-16:24(main) -> + PathSensitive (ProjectiveSet (MCP.D * map)):{(MCP.D:[expRelation:(), + mallocWrapper:(wrapper call:Unknown node, unique calls:{}), + base:({ + Global { + m -> mutex + } + Local { + r -> 0 + } + }, {}, {}, {}), + threadid:(wrapper call:unknown node, Thread:[main], created:(current function:bot, callees:bot)), + threadflag:Singlethreaded, + threadreturn:true, + escape:{}, + mutexEvents:(), + access:(), + mutex:(lockset:{m}, multiplicity:{}), + race:(), + mhp:(), + assert:(), + pthreadMutexType:()], map:{})} + 33-hoare-over-paths.c:33:10-33:11(main) -> + PathSensitive (ProjectiveSet (MCP.D * map)):{(MCP.D:[expRelation:(), + mallocWrapper:(wrapper call:Unknown node, unique calls:{}), + base:({ + Global { + m -> mutex + } + Local { + r -> 0 + } + }, {}, {}, {}), + threadid:(wrapper call:unknown node, Thread:[main], created:(current function:bot, callees:bot)), + threadflag:Singlethreaded, + threadreturn:true, + escape:{}, + mutexEvents:(), + access:(), + mutex:(lockset:{m}, multiplicity:{}), + race:(), + mhp:(), + assert:(), + pthreadMutexType:()], map:{}), + (MCP.D:[expRelation:(), + mallocWrapper:(wrapper call:Unknown node, unique calls:{}), + base:({ + Global { + m -> mutex + } + Local { + r -> 0 + } + }, {}, {}, {}), + threadid:(wrapper call:unknown node, Thread:[main], created:(current function:bot, callees:bot)), + threadflag:Singlethreaded, + threadreturn:true, + escape:{}, + mutexEvents:(), + access:(), + mutex:(lockset:{}, multiplicity:{}), + race:(), + mhp:(), + assert:(), + pthreadMutexType:()], map:{})} + 33-hoare-over-paths.c:7:1-34:1(main) -> + PathSensitive (ProjectiveSet (MCP.D * map)):{(MCP.D:[expRelation:(), + mallocWrapper:(wrapper call:Unknown node, unique calls:{}), + base:({ + Global { + m -> mutex + } + }, {}, {}, {}), + threadid:(wrapper call:unknown node, Thread:[main], created:(current function:bot, callees:bot)), + threadflag:Singlethreaded, + threadreturn:true, + escape:{}, + mutexEvents:(), + access:(), + mutex:(lockset:{}, multiplicity:{}), + race:(), + mhp:(), + assert:(), + pthreadMutexType:()], map:{})} + 33-hoare-over-paths.c:7:1-34:1(main) -> + PathSensitive (ProjectiveSet (MCP.D * map)):{(MCP.D:[expRelation:(), + mallocWrapper:(wrapper call:Unknown node, unique calls:{}), + base:({ + Global { + m -> mutex + } + Temp { + RETURN -> 0 + } + }, {}, {}, {}), + threadid:(wrapper call:unknown node, Thread:[main], created:(current function:bot, callees:bot)), + threadflag:Singlethreaded, + threadreturn:true, + escape:{}, + mutexEvents:(), + access:(), + mutex:(lockset:{m}, multiplicity:{}), + race:(), + mhp:(), + assert:(), + pthreadMutexType:()], map:{}), + (MCP.D:[expRelation:(), + mallocWrapper:(wrapper call:Unknown node, unique calls:{}), + base:({ + Global { + m -> mutex + } + Temp { + RETURN -> 0 + } + }, {}, {}, {}), + threadid:(wrapper call:unknown node, Thread:[main], created:(current function:bot, callees:bot)), + threadflag:Singlethreaded, + threadreturn:true, + escape:{}, + mutexEvents:(), + access:(), + mutex:(lockset:{}, multiplicity:{}), + race:(), + mhp:(), + assert:(), + pthreadMutexType:()], map:{})} + OTHERS -> Not available + } diff --git a/tests/regression/00-sanity/37-long-double.c b/tests/regression/00-sanity/37-long-double.c index 01c9b8bb9b..b4eb5e29c8 100644 --- a/tests/regression/00-sanity/37-long-double.c +++ b/tests/regression/00-sanity/37-long-double.c @@ -1,3 +1,4 @@ +// CRAM int main() { long double l = 0.0L; return (int)l; diff --git a/tests/regression/00-sanity/52-thread-unsafe-libfuns-single-thread.c b/tests/regression/00-sanity/52-thread-unsafe-libfuns-single-thread.c index 94c0f3efeb..aa1bc24aa4 100644 --- a/tests/regression/00-sanity/52-thread-unsafe-libfuns-single-thread.c +++ b/tests/regression/00-sanity/52-thread-unsafe-libfuns-single-thread.c @@ -1,4 +1,5 @@ // PARAM: --enable allglobs --set ana.activated[+] threadJoins +// CRAM #include #include diff --git a/tests/regression/01-cpa/38-enum.c b/tests/regression/01-cpa/38-enum.c index 818705843e..ff345b6ba7 100644 --- a/tests/regression/01-cpa/38-enum.c +++ b/tests/regression/01-cpa/38-enum.c @@ -1,7 +1,7 @@ // PARAM: --disable ana.int.interval --disable ana.int.def_exc --enable ana.int.enums void main(){ int n = 1; - for (; n; n++) { // fixed point not reached here + for (; n; n++) { // FIXPOINT: previously fixed point not reached here } return; } diff --git a/tests/regression/01-cpa/51-marshal.c b/tests/regression/01-cpa/51-marshal.c index bf4e8faebf..c718159e33 100644 --- a/tests/regression/01-cpa/51-marshal.c +++ b/tests/regression/01-cpa/51-marshal.c @@ -1,4 +1,5 @@ // PARAM: --enable ana.int.interval +// NOCHECK void callee(int j) { j++; } diff --git a/tests/regression/01-cpa/55-def_exc-widen.c b/tests/regression/01-cpa/55-def_exc-widen.c index 23b724ae7b..c19f3ab62e 100644 --- a/tests/regression/01-cpa/55-def_exc-widen.c +++ b/tests/regression/01-cpa/55-def_exc-widen.c @@ -1,4 +1,5 @@ //PARAM: --disable ana.int.def_exc_widen_by_join +// NOTIMEOUT int main(int argc , char **argv ) { char buf[512]; diff --git a/tests/regression/01-cpa/56-def_exc-fp1.c b/tests/regression/01-cpa/56-def_exc-fp1.c index 69390b1e35..8e38a04531 100644 --- a/tests/regression/01-cpa/56-def_exc-fp1.c +++ b/tests/regression/01-cpa/56-def_exc-fp1.c @@ -1,6 +1,6 @@ // PARAM: --enable ana.sv-comp.functions // manually minimized from sv-benchmarks/c/seq-mthreaded/pals_lcr.3.ufo.UNBOUNDED.pals.c -// used to not reach fixpoint due to def_exc range +// FIXPOINT: used to not reach fixpoint due to def_exc range _Bool __VERIFIER_nondet_bool(void) ; _Bool mode1 ; diff --git a/tests/regression/01-cpa/57-def_exc-interval-inconsistent.c b/tests/regression/01-cpa/57-def_exc-interval-inconsistent.c index a550ea172f..b0ac9ae5ba 100644 --- a/tests/regression/01-cpa/57-def_exc-interval-inconsistent.c +++ b/tests/regression/01-cpa/57-def_exc-interval-inconsistent.c @@ -1,5 +1,5 @@ // PARAM: --enable ana.int.def_exc --enable ana.int.interval --enable ana.sv-comp.functions --set sem.int.signed_overflow assume_none --set ana.int.refinement never -// used to crash in branch when is_bool returned true, but to_bool returned None on (0,[1,1]) +// NOCRASH: used to crash in branch when is_bool returned true, but to_bool returned None on (0,[1,1]) // manually minimized from sv-benchmarks/c/recursive/MultCommutative-2.c extern int __VERIFIER_nondet_int(void); diff --git a/tests/regression/01-cpa/71-widen-sides.c b/tests/regression/01-cpa/71-widen-sides.c index 522f6fae44..a93124c6e5 100644 --- a/tests/regression/01-cpa/71-widen-sides.c +++ b/tests/regression/01-cpa/71-widen-sides.c @@ -1,4 +1,4 @@ -// PARAM: --set ana.ctx_insens "['base', 'mallocWrapper']" --enable ana.int.interval --sets solvers.td3.side_widen sides-local +// PARAM: --set ana.ctx_insens "['base', 'mallocWrapper']" --enable ana.int.interval --set solvers.td3.side_widen sides-local #include int further(int n) { diff --git a/tests/regression/02-base/34-builtin_va_list.c b/tests/regression/02-base/34-builtin_va_list.c index 2ab9168c68..f16d6fc0fd 100644 --- a/tests/regression/02-base/34-builtin_va_list.c +++ b/tests/regression/02-base/34-builtin_va_list.c @@ -1,3 +1,4 @@ +// NOCRASH #include int sum(int n, ...) { diff --git a/tests/regression/02-base/57-meet-def-exc.c b/tests/regression/02-base/57-meet-def-exc.c index d66b1120d6..cf24431f8c 100644 --- a/tests/regression/02-base/57-meet-def-exc.c +++ b/tests/regression/02-base/57-meet-def-exc.c @@ -1,3 +1,4 @@ +// NOCHECK void main(void) { int x; int i = 41; diff --git a/tests/regression/02-base/64-enums-minmax.c b/tests/regression/02-base/64-enums-minmax.c index 9d366aa56f..6c0d827cf0 100644 --- a/tests/regression/02-base/64-enums-minmax.c +++ b/tests/regression/02-base/64-enums-minmax.c @@ -1,4 +1,5 @@ // PARAM: --enable ana.int.enums +// NOTIMEOUT int main(void) { unsigned char c; int top; diff --git a/tests/regression/02-base/69-ipmi-struct-blob-fixpoint.c b/tests/regression/02-base/69-ipmi-struct-blob-fixpoint.c index 3aa06d1820..8e7e42f0e6 100644 --- a/tests/regression/02-base/69-ipmi-struct-blob-fixpoint.c +++ b/tests/regression/02-base/69-ipmi-struct-blob-fixpoint.c @@ -30,7 +30,7 @@ static int ipmi_open(struct inode *inode, struct file *file) struct ipmi_file_private *priv; priv = kmalloc(sizeof(*priv), GFP_KERNEL); mutex_lock(&ipmi_mutex); - priv->file = file; // should reach fixpoint from priv side effect from here + priv->file = file; // FIXPOINT: should reach fixpoint from priv side effect from here ipmi_create_user(0, &ipmi_hndlrs, priv, &(priv->user)); file->private_data = priv; diff --git a/tests/regression/02-base/72-ad-widen-duplicate.c b/tests/regression/02-base/72-ad-widen-duplicate.c index 436ae70c93..c893d4ae79 100644 --- a/tests/regression/02-base/72-ad-widen-duplicate.c +++ b/tests/regression/02-base/72-ad-widen-duplicate.c @@ -1,4 +1,5 @@ // https://github.com/goblint/analyzer/issues/554 +// FIXPOINT int a; int main() { c(&a); } diff --git a/tests/regression/02-base/73-no-eval-on-write-offset.c b/tests/regression/02-base/73-no-eval-on-write-offset.c index da0e635dfb..780c244e9a 100644 --- a/tests/regression/02-base/73-no-eval-on-write-offset.c +++ b/tests/regression/02-base/73-no-eval-on-write-offset.c @@ -1,4 +1,5 @@ // PARAM: --enable exp.earlyglobs +// NOCRASH char a; int c; diff --git a/tests/regression/02-base/87-casts-dep-on-param.c b/tests/regression/02-base/87-casts-dep-on-param.c index 93a16e4bc3..a265b9db04 100644 --- a/tests/regression/02-base/87-casts-dep-on-param.c +++ b/tests/regression/02-base/87-casts-dep-on-param.c @@ -9,7 +9,7 @@ int g () { int a[10]; f(1, &i); - // check that i is an integer and can be used for array indexing without Goblint crashing + // NOCRASH: check that i is an integer and can be used for array indexing without Goblint crashing int r = a[i]; a[i] = r; diff --git a/tests/regression/03-practical/03-pthread_ptrs.c b/tests/regression/03-practical/03-pthread_ptrs.c index c619c994b2..0694698af8 100644 --- a/tests/regression/03-practical/03-pthread_ptrs.c +++ b/tests/regression/03-practical/03-pthread_ptrs.c @@ -1,5 +1,5 @@ #include - +// NOCHECK static pthread_t sid1 ; static pthread_t sid2 ; static pthread_t sid3 ; @@ -15,22 +15,22 @@ void *fn2(void) { extern void *fn3(void * a); -int main() { +int main() { /* normal call to fn1 */ pthread_create(&sid1, NULL, fn1, NULL); - - /* ignore parameter (cast parameter to void) call */ + + /* ignore parameter (cast parameter to void) call */ pthread_create(&sid2, NULL, (void *(*)(void * )) (& fn2), NULL); - + /* we create a unknown thread -- that can't be good */ pthread_create(&sid3, NULL, &fn3, NULL); pthread_join(sid3, NULL); - + pthread_join(sid2, NULL); - + pthread_join(sid1, NULL); - + return 0; } diff --git a/tests/regression/03-practical/21-pfscan_combine_minimal.c b/tests/regression/03-practical/21-pfscan_combine_minimal.c index 5054260216..b8fc7948b2 100644 --- a/tests/regression/03-practical/21-pfscan_combine_minimal.c +++ b/tests/regression/03-practical/21-pfscan_combine_minimal.c @@ -1,3 +1,4 @@ +// FIXPOINT #include struct __anonstruct_PQUEUE_63 { @@ -17,7 +18,7 @@ int pqueue_init(PQUEUE *qp) void pqueue_close(PQUEUE *qp ) { - pthread_mutex_lock(& qp->mtx); + pthread_mutex_lock(& qp->mtx); // TODO (OSX): WARN (must double locking) qp->closed = 1; pthread_mutex_unlock(& qp->mtx); return; @@ -25,7 +26,7 @@ void pqueue_close(PQUEUE *qp ) int pqueue_put(PQUEUE *qp) { - pthread_mutex_lock(& qp->mtx); + pthread_mutex_lock(& qp->mtx); // TODO (OSX): WARN (must double locking) if (qp->closed) { // pfscan actually has a bug and is missing the following unlock at early return // pthread_mutex_unlock(& qp->mtx); diff --git a/tests/regression/03-practical/22-nested-infinite-loops.c b/tests/regression/03-practical/22-nested-infinite-loops.c index f3e1fed23b..658bcbacb0 100644 --- a/tests/regression/03-practical/22-nested-infinite-loops.c +++ b/tests/regression/03-practical/22-nested-infinite-loops.c @@ -1,4 +1,5 @@ // https://github.com/goblint/analyzer/issues/231#issuecomment-868369123 +// NOCHECK: should check CFG? int main(void) { int x = 0; while(1) { diff --git a/tests/regression/03-practical/23-knot-timeout.c b/tests/regression/03-practical/23-knot-timeout.c index f972363cea..2fafe5d4da 100644 --- a/tests/regression/03-practical/23-knot-timeout.c +++ b/tests/regression/03-practical/23-knot-timeout.c @@ -1,5 +1,5 @@ // PARAM: --disable ana.int.def_exc_widen_by_join -// Used to timeout without ana.int.def_exc_widen_by_join +// NOTIMEOUT: Used to timeout without ana.int.def_exc_widen_by_join #include struct a { diff --git a/tests/regression/03-practical/33-threshold-narrowing-intervals.c b/tests/regression/03-practical/33-threshold-narrowing-intervals.c new file mode 100644 index 0000000000..ef38c151da --- /dev/null +++ b/tests/regression/03-practical/33-threshold-narrowing-intervals.c @@ -0,0 +1,13 @@ +// PARAM: --enable ana.int.interval --enable ana.int.interval_threshold_widening --set ana.int.interval_threshold_widening_constants comparisons +#include + +int main() { + int i; + for(i = 0; i < 10 && i < 20; i += 3); + __goblint_check(i <= 12); + + int j; + for(j = 0; j > -10 && j > -20; j-= 3); + __goblint_check(j >= -12); + +} diff --git a/tests/regression/03-practical/34-threshold-narrowing-interval-sets.c b/tests/regression/03-practical/34-threshold-narrowing-interval-sets.c new file mode 100644 index 0000000000..c1f11c091e --- /dev/null +++ b/tests/regression/03-practical/34-threshold-narrowing-interval-sets.c @@ -0,0 +1,13 @@ +// PARAM: --enable ana.int.interval_set --enable ana.int.interval_threshold_widening --set ana.int.interval_threshold_widening_constants comparisons +#include + +int main() { + int i; + for(i = 0; i < 10 && i < 20; i += 3); + __goblint_check(i <= 12); + + int j; + for(j = 0; j > -10 && j > -20; j-= 3); + __goblint_check(j >= -12); + +} diff --git a/tests/regression/03-practical/35-base-mutex-macos.c b/tests/regression/03-practical/35-base-mutex-macos.c new file mode 100644 index 0000000000..a83022e91b --- /dev/null +++ b/tests/regression/03-practical/35-base-mutex-macos.c @@ -0,0 +1,20 @@ +// Intentionally no #include , because we want to imitate/debug MacOS construction on anything. +// CRAM +#define __PTHREAD_MUTEX_SIZE__ 56 + +struct _opaque_pthread_mutex_t { + long __sig; + char __opaque[__PTHREAD_MUTEX_SIZE__]; +}; + +typedef struct _opaque_pthread_mutex_t __darwin_pthread_mutex_t; +typedef __darwin_pthread_mutex_t pthread_mutex_t; + +#define _PTHREAD_MUTEX_SIG_init 0x32AAABA7 +#define PTHREAD_MUTEX_INITIALIZER {_PTHREAD_MUTEX_SIG_init, {0}} + +pthread_mutex_t A = PTHREAD_MUTEX_INITIALIZER; + +int main() { + return 0; +} diff --git a/tests/regression/03-practical/35-base-mutex-macos.t b/tests/regression/03-practical/35-base-mutex-macos.t new file mode 100644 index 0000000000..1d8a184d4c --- /dev/null +++ b/tests/regression/03-practical/35-base-mutex-macos.t @@ -0,0 +1,17 @@ + $ goblint --enable witness.yaml.enabled --disable witness.invariant.accessed --set pre.cppflags[+] -DGOBLINT_NO_PTHREAD_ONCE 35-base-mutex-macos.c + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 2 + dead: 0 + total lines: 2 + [Info][Witness] witness generation summary: + location invariants: 0 + loop invariants: 0 + flow-insensitive invariants: 0 + total generation entries: 1 + +There should be no invariants about __sig. +Base analysis should hide mutex contents. + + $ yamlWitnessStrip < witness.yml + - entry_type: invariant_set + content: [] diff --git a/tests/regression/03-practical/dune b/tests/regression/03-practical/dune new file mode 100644 index 0000000000..23c0dd3290 --- /dev/null +++ b/tests/regression/03-practical/dune @@ -0,0 +1,2 @@ +(cram + (deps (glob_files *.c))) diff --git a/tests/regression/04-mutex/32-allfuns.c b/tests/regression/04-mutex/32-allfuns.c index b59c487c53..e6b88e47ce 100644 --- a/tests/regression/04-mutex/32-allfuns.c +++ b/tests/regression/04-mutex/32-allfuns.c @@ -8,11 +8,11 @@ pthread_mutex_t B_mutex = PTHREAD_MUTEX_INITIALIZER; void t1() { pthread_mutex_lock(&A_mutex); myglobal++; //RACE! - pthread_mutex_lock(&A_mutex); + pthread_mutex_unlock(&A_mutex); } void t2() { pthread_mutex_lock(&B_mutex); myglobal++; //RACE! - pthread_mutex_lock(&B_mutex); + pthread_mutex_unlock(&B_mutex); } diff --git a/tests/regression/05-lval_ls/24-idxunknown_recursive.c b/tests/regression/05-lval_ls/24-idxunknown_recursive.c new file mode 100644 index 0000000000..95e8bff798 --- /dev/null +++ b/tests/regression/05-lval_ls/24-idxunknown_recursive.c @@ -0,0 +1,38 @@ +// PARAM: --enable ana.sv-comp.functions --set ana.activated[+] pthreadMutexType +_Bool __VERIFIER_nondet_bool(); + +#define _GNU_SOURCE +#include + +int g; + +#ifdef __APPLE__ +pthread_mutex_t m[2] = {PTHREAD_RECURSIVE_MUTEX_INITIALIZER, PTHREAD_RECURSIVE_MUTEX_INITIALIZER}; +#else +pthread_mutex_t m[2] = {PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP, PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP}; +#endif + +void *t_fun(void *arg) { + int i, j, k; + i = __VERIFIER_nondet_bool(); + j = __VERIFIER_nondet_bool(); + k = __VERIFIER_nondet_bool(); + + pthread_mutex_lock(&m[i]); // may lock m[1] + pthread_mutex_lock(&m[j]); // may lock m[1] recursively + pthread_mutex_lock(&m[0]); // must lock m[0] + pthread_mutex_unlock(&m[k]); // may unlock m[0] + // m[0] should not be in must-lockset here! + g++; // RACE! + return NULL; +} + +int main() { + pthread_t id; + pthread_create(&id, NULL, t_fun, NULL); + + pthread_mutex_lock(&m[0]); + g++; // RACE! + pthread_mutex_unlock(&m[0]); + return 0; +} diff --git a/tests/regression/05-lval_ls/25-idxunknown_recursive_2.c b/tests/regression/05-lval_ls/25-idxunknown_recursive_2.c new file mode 100644 index 0000000000..47ea1ce0a7 --- /dev/null +++ b/tests/regression/05-lval_ls/25-idxunknown_recursive_2.c @@ -0,0 +1,36 @@ +// PARAM: --enable ana.sv-comp.functions --set ana.activated[+] pthreadMutexType +_Bool __VERIFIER_nondet_bool(); + +#define _GNU_SOURCE +#include + +int g; + +#ifdef __APPLE__ +pthread_mutex_t m[2] = {PTHREAD_RECURSIVE_MUTEX_INITIALIZER, PTHREAD_RECURSIVE_MUTEX_INITIALIZER}; +#else +pthread_mutex_t m[2] = {PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP, PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP}; +#endif + +void *t_fun(void *arg) { + int i; + i = __VERIFIER_nondet_bool(); + + pthread_mutex_lock(&m[0]); // must lock m[0] + pthread_mutex_unlock(&m[i]); // may unlock m[0] + pthread_mutex_lock(&m[0]); // must lock m[0] + pthread_mutex_unlock(&m[0]); // must unlock m[0] + // m[0] should not be in must-lockset here! + g++; // RACE! + return NULL; +} + +int main() { + pthread_t id; + pthread_create(&id, NULL, t_fun, NULL); + + pthread_mutex_lock(&m[0]); + g++; // RACE! + pthread_mutex_unlock(&m[0]); + return 0; +} diff --git a/tests/regression/06-symbeq/20-mult_accs_nr.c b/tests/regression/06-symbeq/20-mult_accs_nr.c index 7d66e3f5d2..50c8f19a62 100644 --- a/tests/regression/06-symbeq/20-mult_accs_nr.c +++ b/tests/regression/06-symbeq/20-mult_accs_nr.c @@ -15,7 +15,7 @@ void *t_fun(void *arg) { pthread_mutex_lock(&s->mutex); s->data = 5; // NORACE s->lore = 6; // NORACE - pthread_mutex_lock(&s->mutex); + pthread_mutex_unlock(&s->mutex); return NULL; } diff --git a/tests/regression/06-symbeq/21-mult_accs_rc.c b/tests/regression/06-symbeq/21-mult_accs_rc.c index 62550fab55..4acadb4a58 100644 --- a/tests/regression/06-symbeq/21-mult_accs_rc.c +++ b/tests/regression/06-symbeq/21-mult_accs_rc.c @@ -14,7 +14,7 @@ void *t_fun(void *arg) { pthread_mutex_lock(&s->mutex); s = get_s(); s->data = 5; // RACE! - pthread_mutex_lock(&s->mutex); + pthread_mutex_unlock(&s->mutex); return NULL; } diff --git a/tests/regression/06-symbeq/21-mult_accs_rc.t b/tests/regression/06-symbeq/21-mult_accs_rc.t index 90f0661f67..2eacd0382e 100644 --- a/tests/regression/06-symbeq/21-mult_accs_rc.t +++ b/tests/regression/06-symbeq/21-mult_accs_rc.t @@ -3,14 +3,18 @@ Disable info messages because race summary contains (safe) memory location count $ goblint --enable warn.deterministic --disable warn.info --enable ana.race.direct-arithmetic --set ana.activated[+] "'var_eq'" --set ana.activated[+] "'symb_locks'" 21-mult_accs_rc.c 2>&1 | tee default-output-1.txt [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (21-mult_accs_rc.c:14:3-14:32) [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (21-mult_accs_rc.c:16:3-16:14) - [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (21-mult_accs_rc.c:17:3-17:32) + [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (21-mult_accs_rc.c:17:3-17:34) [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (21-mult_accs_rc.c:28:3-28:16) [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (21-mult_accs_rc.c:29:3-29:15) [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (21-mult_accs_rc.c:34:3-34:9) [Warning][Race] Memory location (struct s).data (race with conf. 100): write with thread:[main, t_fun@21-mult_accs_rc.c:31:3-31:37] (conf. 100) (exp: & s->data) (21-mult_accs_rc.c:16:3-16:14) write with [symblock:{p-lock:*.mutex}, mhp:{created={[main, t_fun@21-mult_accs_rc.c:31:3-31:37]}}, thread:[main]] (conf. 100) (exp: & *d) (21-mult_accs_rc.c:34:3-34:9) - [Warning][Unknown] unlocking mutex (NULL) which may not be held (21-mult_accs_rc.c:35:3-35:26) + [Warning][Unknown] locking NULL mutex (21-mult_accs_rc.c:14:3-14:32) + [Warning][Unknown] unlocking NULL mutex (21-mult_accs_rc.c:17:3-17:34) + [Warning][Unknown] unlocking unknown mutex which may not be held (21-mult_accs_rc.c:17:3-17:34) + [Warning][Unknown] locking NULL mutex (21-mult_accs_rc.c:33:3-33:24) + [Warning][Unknown] unlocking NULL mutex (21-mult_accs_rc.c:35:3-35:26) [Warning][Unknown] unlocking unknown mutex which may not be held (21-mult_accs_rc.c:35:3-35:26) [Error][Imprecise][Unsound] Function definition missing for get_s (21-mult_accs_rc.c:13:3-13:14) [Error][Imprecise][Unsound] Function definition missing for get_s (21-mult_accs_rc.c:15:3-15:14) @@ -20,13 +24,17 @@ Disable info messages because race summary contains (safe) memory location count $ goblint --enable warn.deterministic --disable warn.info --disable ana.race.direct-arithmetic --set ana.activated[+] "'var_eq'" --set ana.activated[+] "'symb_locks'" --enable allglobs 21-mult_accs_rc.c 2>&1 | tee default-output-2.txt [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (21-mult_accs_rc.c:14:3-14:32) [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (21-mult_accs_rc.c:16:3-16:14) - [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (21-mult_accs_rc.c:17:3-17:32) + [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (21-mult_accs_rc.c:17:3-17:34) [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (21-mult_accs_rc.c:28:3-28:16) [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (21-mult_accs_rc.c:29:3-29:15) [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (21-mult_accs_rc.c:34:3-34:9) [Success][Race] Memory location (struct s).data (safe): write with thread:[main, t_fun@21-mult_accs_rc.c:31:3-31:37] (conf. 100) (exp: & s->data) (21-mult_accs_rc.c:16:3-16:14) - [Warning][Unknown] unlocking mutex (NULL) which may not be held (21-mult_accs_rc.c:35:3-35:26) + [Warning][Unknown] locking NULL mutex (21-mult_accs_rc.c:14:3-14:32) + [Warning][Unknown] unlocking NULL mutex (21-mult_accs_rc.c:17:3-17:34) + [Warning][Unknown] unlocking unknown mutex which may not be held (21-mult_accs_rc.c:17:3-17:34) + [Warning][Unknown] locking NULL mutex (21-mult_accs_rc.c:33:3-33:24) + [Warning][Unknown] unlocking NULL mutex (21-mult_accs_rc.c:35:3-35:26) [Warning][Unknown] unlocking unknown mutex which may not be held (21-mult_accs_rc.c:35:3-35:26) [Error][Imprecise][Unsound] Function definition missing for get_s (21-mult_accs_rc.c:13:3-13:14) [Error][Imprecise][Unsound] Function definition missing for get_s (21-mult_accs_rc.c:15:3-15:14) diff --git a/tests/regression/06-symbeq/34-var_eq-exponential-context.c b/tests/regression/06-symbeq/34-var_eq-exponential-context.c index bc478374f0..8b9b76a89e 100644 --- a/tests/regression/06-symbeq/34-var_eq-exponential-context.c +++ b/tests/regression/06-symbeq/34-var_eq-exponential-context.c @@ -1,5 +1,5 @@ // SKIP PARAM: --set ana.activated[+] var_eq - +// NOTIMEOUT? void level0(int *p) { } diff --git a/tests/regression/06-symbeq/40-var_eq-widen1.c b/tests/regression/06-symbeq/40-var_eq-widen1.c index 72e28c57a0..1cef7fe324 100644 --- a/tests/regression/06-symbeq/40-var_eq-widen1.c +++ b/tests/regression/06-symbeq/40-var_eq-widen1.c @@ -1,6 +1,6 @@ // PARAM: --set ana.activated[+] var_eq // manually minimized from sv-benchmarks/c/ldv-linux-3.16-rc1/205_9a_array_unsafes_linux-3.16-rc1.tar.xz-205_9a-drivers--net--usb--cx82310_eth.ko-entry_point.cil.out.i -// used to call widen incorrectly +// NOCRASH: used to call widen incorrectly typedef _Bool bool; void usb_bulk_msg(int *arg4, int x) { diff --git a/tests/regression/06-symbeq/41-var_eq_multithread.c b/tests/regression/06-symbeq/47-var_eq_multithread.c similarity index 100% rename from tests/regression/06-symbeq/41-var_eq_multithread.c rename to tests/regression/06-symbeq/47-var_eq_multithread.c diff --git a/tests/regression/09-regions/31-equ_rc.c b/tests/regression/09-regions/31-equ_rc.c index 7cea370c58..2f3aff7f63 100644 --- a/tests/regression/09-regions/31-equ_rc.c +++ b/tests/regression/09-regions/31-equ_rc.c @@ -15,7 +15,7 @@ struct s { void *t_fun(void *arg) { pthread_mutex_lock(&A.mutex); B.datum = 5; // RACE! - pthread_mutex_lock(&A.mutex); + pthread_mutex_unlock(&A.mutex); return NULL; } diff --git a/tests/regression/09-regions/32-equ_nr.c b/tests/regression/09-regions/32-equ_nr.c index d9b909546e..a242448ba8 100644 --- a/tests/regression/09-regions/32-equ_nr.c +++ b/tests/regression/09-regions/32-equ_nr.c @@ -15,7 +15,7 @@ struct s { void *t_fun(void *arg) { pthread_mutex_lock(&A.mutex); A.datum = 5; // NORACE - pthread_mutex_lock(&A.mutex); + pthread_mutex_unlock(&A.mutex); return NULL; } diff --git a/tests/regression/10-synch/07-thread_self_create.c b/tests/regression/10-synch/07-thread_self_create.c index 473a26a25b..18c5f3ee83 100644 --- a/tests/regression/10-synch/07-thread_self_create.c +++ b/tests/regression/10-synch/07-thread_self_create.c @@ -1,5 +1,5 @@ // PARAM: --set ana.activated[+] thread -// Checks termination of thread analysis with a thread who is its own single parent. +// NOTIMEOUT: Checks termination of thread analysis with a thread who is its own single parent. #include void *t_fun(void *arg) { diff --git a/tests/regression/10-synch/23-tid-partitioned-array.c b/tests/regression/10-synch/23-tid-partitioned-array.c index 0f4942ec0e..e0dc849fad 100644 --- a/tests/regression/10-synch/23-tid-partitioned-array.c +++ b/tests/regression/10-synch/23-tid-partitioned-array.c @@ -1,4 +1,5 @@ // PARAM: --set ana.activated[+] thread --set ana.base.arrays.domain partitioned +// NOCRASH #include void *t_fun(void *arg) { diff --git a/tests/regression/10-synch/24-tid-partitioned-array-global.c b/tests/regression/10-synch/24-tid-partitioned-array-global.c index b61daed77f..00685dc1c8 100644 --- a/tests/regression/10-synch/24-tid-partitioned-array-global.c +++ b/tests/regression/10-synch/24-tid-partitioned-array-global.c @@ -1,4 +1,5 @@ // PARAM: --set ana.activated[+] thread --set ana.base.arrays.domain partitioned +// NOCRASH #include pthread_t t_ids[10000]; diff --git a/tests/regression/10-synch/25-tid-array-malloc.c b/tests/regression/10-synch/25-tid-array-malloc.c index 4000577fd3..e59cd02e97 100644 --- a/tests/regression/10-synch/25-tid-array-malloc.c +++ b/tests/regression/10-synch/25-tid-array-malloc.c @@ -1,4 +1,5 @@ // PARAM: --set ana.activated[+] thread +// NOCRASH #include #include diff --git a/tests/regression/10-synch/26-tid-array-malloc-free.c b/tests/regression/10-synch/26-tid-array-malloc-free.c index e8f00d13ae..b070fd0d84 100644 --- a/tests/regression/10-synch/26-tid-array-malloc-free.c +++ b/tests/regression/10-synch/26-tid-array-malloc-free.c @@ -1,4 +1,5 @@ // PARAM: --set ana.activated[+] thread +// NOCRASH #include #include diff --git a/tests/regression/10-synch/27-tid-array-malloc-2.c b/tests/regression/10-synch/27-tid-array-malloc-2.c index 462901459a..d1b41843a7 100644 --- a/tests/regression/10-synch/27-tid-array-malloc-2.c +++ b/tests/regression/10-synch/27-tid-array-malloc-2.c @@ -1,4 +1,5 @@ -// PARAM: --set ana.activated[+] thread +// PARAM: --set ana.activated[+] thread +// NOCRASH #include #include @@ -19,7 +20,7 @@ int main() pthread_create(&t[tid], 0, thread, 0); tid++; pthread_create(&t[tid], 0, thread, 0); - + tid=0; pthread_join(t[tid], 0); tid++; diff --git a/tests/regression/10-synch/29-multiple-created-only.c b/tests/regression/10-synch/29-multiple-created-only.c new file mode 100644 index 0000000000..e65021caaf --- /dev/null +++ b/tests/regression/10-synch/29-multiple-created-only.c @@ -0,0 +1,26 @@ +// PARAM: --set ana.activated[+] thread --set ana.activated[+] threadid --set ana.thread.domain plain + +#include +#include + +int myglobal; +int myglobal2; + +void* bla(void *arg) { + // This is created multiple times, it should race with itself + myglobal = 10; //RACE + return NULL; +} + +void* other(void) { + // This is created only once, it should not be marked as non-unique + unknown(bla); + myglobal2 = 30; //NORACE +} + +int main(void) { + pthread_t id; + pthread_create(&id, NULL, other, NULL); + + return 0; +} diff --git a/tests/regression/10-synch/30-no-crash.c b/tests/regression/10-synch/30-no-crash.c new file mode 100644 index 0000000000..c9a6c7d88b --- /dev/null +++ b/tests/regression/10-synch/30-no-crash.c @@ -0,0 +1,31 @@ +// PARAM: --set ana.context.gas_value 0 --set ana.activated[+] thread --set ana.activated[+] threadid + +#include +#include + +int myglobal; +int myglobal2; + +void *t_flurb(void *arg) { + myglobal=40; //RACE + return NULL; +} + +void* bla(void *arg) { + return NULL; +} + +void *t_fun(void *arg) { + unknown(t_flurb); // NOCRASH + return NULL; +} + +int main(void) { + pthread_t id; + pthread_create(&id, NULL, t_fun, NULL); + pthread_create(&id, NULL, t_fun, NULL); + + unknown(bla); + + return 0; +} diff --git a/tests/regression/13-privatized/01-priv_nr.t b/tests/regression/13-privatized/01-priv_nr.t new file mode 100644 index 0000000000..0186709027 --- /dev/null +++ b/tests/regression/13-privatized/01-priv_nr.t @@ -0,0 +1,170 @@ +`protection` privatization: + + $ goblint --enable witness.yaml.enabled --set witness.yaml.entry-types '["location_invariant"]' --disable witness.invariant.other --set ana.base.privatization protection 01-priv_nr.c + [Success][Assert] Assertion "glob1 == 5" will succeed (01-priv_nr.c:22:3-22:30) + [Success][Assert] Assertion "t == 5" will succeed (01-priv_nr.c:12:3-12:26) + [Success][Assert] Assertion "glob1 == -10" will succeed (01-priv_nr.c:14:3-14:32) + [Success][Assert] Assertion "glob1 == 6" will succeed (01-priv_nr.c:26:3-26:30) + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 19 + dead: 0 + total lines: 19 + [Info][Witness] witness generation summary: + location invariants: 3 + loop invariants: 0 + flow-insensitive invariants: 0 + total generation entries: 3 + [Info][Race] Memory locations race summary: + safe: 1 + vulnerable: 0 + unsafe: 0 + total memory locations: 1 + + $ yamlWitnessStrip < witness.yml + - entry_type: location_invariant + location: + file_name: 01-priv_nr.c + file_hash: $FILE_HASH + line: 25 + column: 3 + function: main + location_invariant: + string: glob1 == 5 + type: assertion + format: C + - entry_type: location_invariant + location: + file_name: 01-priv_nr.c + file_hash: $FILE_HASH + line: 11 + column: 3 + function: t_fun + location_invariant: + string: glob1 == 5 + type: assertion + format: C + - entry_type: location_invariant + location: + file_name: 01-priv_nr.c + file_hash: $FILE_HASH + line: 11 + column: 3 + function: t_fun + location_invariant: + string: (unsigned long )arg == 0UL + type: assertion + format: C + +`mutex-meet` privatization: + + $ goblint --enable witness.yaml.enabled --set witness.yaml.entry-types '["location_invariant"]' --disable witness.invariant.other --set ana.base.privatization mutex-meet 01-priv_nr.c + [Success][Assert] Assertion "glob1 == 5" will succeed (01-priv_nr.c:22:3-22:30) + [Success][Assert] Assertion "t == 5" will succeed (01-priv_nr.c:12:3-12:26) + [Success][Assert] Assertion "glob1 == -10" will succeed (01-priv_nr.c:14:3-14:32) + [Success][Assert] Assertion "glob1 == 6" will succeed (01-priv_nr.c:26:3-26:30) + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 19 + dead: 0 + total lines: 19 + [Info][Witness] witness generation summary: + location invariants: 3 + loop invariants: 0 + flow-insensitive invariants: 0 + total generation entries: 3 + [Info][Race] Memory locations race summary: + safe: 1 + vulnerable: 0 + unsafe: 0 + total memory locations: 1 + + $ yamlWitnessStrip < witness.yml + - entry_type: location_invariant + location: + file_name: 01-priv_nr.c + file_hash: $FILE_HASH + line: 25 + column: 3 + function: main + location_invariant: + string: glob1 == 5 + type: assertion + format: C + - entry_type: location_invariant + location: + file_name: 01-priv_nr.c + file_hash: $FILE_HASH + line: 11 + column: 3 + function: t_fun + location_invariant: + string: glob1 == 5 + type: assertion + format: C + - entry_type: location_invariant + location: + file_name: 01-priv_nr.c + file_hash: $FILE_HASH + line: 11 + column: 3 + function: t_fun + location_invariant: + string: (unsigned long )arg == 0UL + type: assertion + format: C + +`write+lock` privatization: + + $ goblint --enable witness.yaml.enabled --set witness.yaml.entry-types '["location_invariant"]' --disable witness.invariant.other --set ana.base.privatization write+lock 01-priv_nr.c + [Success][Assert] Assertion "glob1 == 5" will succeed (01-priv_nr.c:22:3-22:30) + [Success][Assert] Assertion "t == 5" will succeed (01-priv_nr.c:12:3-12:26) + [Success][Assert] Assertion "glob1 == -10" will succeed (01-priv_nr.c:14:3-14:32) + [Success][Assert] Assertion "glob1 == 6" will succeed (01-priv_nr.c:26:3-26:30) + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 19 + dead: 0 + total lines: 19 + [Info][Witness] witness generation summary: + location invariants: 3 + loop invariants: 0 + flow-insensitive invariants: 0 + total generation entries: 3 + [Info][Race] Memory locations race summary: + safe: 1 + vulnerable: 0 + unsafe: 0 + total memory locations: 1 + + $ yamlWitnessStrip < witness.yml + - entry_type: location_invariant + location: + file_name: 01-priv_nr.c + file_hash: $FILE_HASH + line: 25 + column: 3 + function: main + location_invariant: + string: glob1 == 5 + type: assertion + format: C + - entry_type: location_invariant + location: + file_name: 01-priv_nr.c + file_hash: $FILE_HASH + line: 11 + column: 3 + function: t_fun + location_invariant: + string: glob1 == 5 + type: assertion + format: C + - entry_type: location_invariant + location: + file_name: 01-priv_nr.c + file_hash: $FILE_HASH + line: 11 + column: 3 + function: t_fun + location_invariant: + string: (unsigned long )arg == 0UL + type: assertion + format: C diff --git a/tests/regression/13-privatized/04-priv_multi.t b/tests/regression/13-privatized/04-priv_multi.t new file mode 100644 index 0000000000..1f6dff3fdc --- /dev/null +++ b/tests/regression/13-privatized/04-priv_multi.t @@ -0,0 +1,335 @@ + $ goblint --set ana.base.privatization protection --enable witness.yaml.enabled --set ana.activated[+] mutexGhosts --set witness.yaml.entry-types '["flow_insensitive_invariant", "ghost_instrumentation"]' 04-priv_multi.c + [Success][Assert] Assertion "p == 5" will succeed (04-priv_multi.c:50:7-50:30) + [Success][Assert] Assertion "A == B" will succeed (04-priv_multi.c:71:5-71:28) + [Warning][Deadcode] Function 'dispose' has dead code: + on line 53 (04-priv_multi.c:53-53) + on line 56 (04-priv_multi.c:56-56) + [Warning][Deadcode] Function 'process' has dead code: + on line 37 (04-priv_multi.c:37-37) + on line 40 (04-priv_multi.c:40-40) + [Warning][Deadcode] Logical lines of code (LLoC) summary: + live: 40 + dead: 4 + total lines: 44 + [Warning][Deadcode][CWE-571] condition '1' (possibly inserted by CIL) is always true (04-priv_multi.c:25:10-25:11) + [Warning][Deadcode][CWE-571] condition 'A > 0' is always true (04-priv_multi.c:27:9-27:14) + [Warning][Deadcode][CWE-571] condition '1' (possibly inserted by CIL) is always true (04-priv_multi.c:45:10-45:11) + [Warning][Deadcode][CWE-571] condition 'B > 0' is always true (04-priv_multi.c:47:9-47:14) + [Info][Witness] witness generation summary: + location invariants: 0 + loop invariants: 0 + flow-insensitive invariants: 3 + total generation entries: 4 + [Info][Race] Memory locations race summary: + safe: 2 + vulnerable: 0 + unsafe: 0 + total memory locations: 2 + + $ yamlWitnessStrip < witness.yml | tee witness.flow_insensitive.yml + - entry_type: ghost_instrumentation + content: + ghost_variables: + - name: multithreaded + scope: global + type: int + initial: + value: "0" + format: c_expression + - name: mutex_A_locked + scope: global + type: int + initial: + value: "0" + format: c_expression + - name: mutex_B_locked + scope: global + type: int + initial: + value: "0" + format: c_expression + ghost_updates: + - location: + file_name: 04-priv_multi.c + file_hash: $FILE_HASH + line: 15 + column: 5 + function: generate + updates: + - variable: mutex_A_locked + value: "1" + format: c_expression + - location: + file_name: 04-priv_multi.c + file_hash: $FILE_HASH + line: 18 + column: 5 + function: generate + updates: + - variable: mutex_A_locked + value: "0" + format: c_expression + - location: + file_name: 04-priv_multi.c + file_hash: $FILE_HASH + line: 26 + column: 5 + function: process + updates: + - variable: mutex_A_locked + value: "1" + format: c_expression + - location: + file_name: 04-priv_multi.c + file_hash: $FILE_HASH + line: 29 + column: 7 + function: process + updates: + - variable: mutex_B_locked + value: "1" + format: c_expression + - location: + file_name: 04-priv_multi.c + file_hash: $FILE_HASH + line: 32 + column: 7 + function: process + updates: + - variable: mutex_B_locked + value: "0" + format: c_expression + - location: + file_name: 04-priv_multi.c + file_hash: $FILE_HASH + line: 34 + column: 7 + function: process + updates: + - variable: mutex_A_locked + value: "0" + format: c_expression + - location: + file_name: 04-priv_multi.c + file_hash: $FILE_HASH + line: 46 + column: 5 + function: dispose + updates: + - variable: mutex_B_locked + value: "1" + format: c_expression + - location: + file_name: 04-priv_multi.c + file_hash: $FILE_HASH + line: 49 + column: 7 + function: dispose + updates: + - variable: mutex_B_locked + value: "0" + format: c_expression + - location: + file_name: 04-priv_multi.c + file_hash: $FILE_HASH + line: 63 + column: 3 + function: main + updates: + - variable: multithreaded + value: "1" + format: c_expression + - location: + file_name: 04-priv_multi.c + file_hash: $FILE_HASH + line: 68 + column: 5 + function: main + updates: + - variable: mutex_A_locked + value: "1" + format: c_expression + - location: + file_name: 04-priv_multi.c + file_hash: $FILE_HASH + line: 69 + column: 5 + function: main + updates: + - variable: mutex_B_locked + value: "1" + format: c_expression + - location: + file_name: 04-priv_multi.c + file_hash: $FILE_HASH + line: 73 + column: 5 + function: main + updates: + - variable: mutex_B_locked + value: "0" + format: c_expression + - location: + file_name: 04-priv_multi.c + file_hash: $FILE_HASH + line: 74 + column: 5 + function: main + updates: + - variable: mutex_A_locked + value: "0" + format: c_expression + - entry_type: flow_insensitive_invariant + flow_insensitive_invariant: + string: '! multithreaded || (mutex_B_locked || (mutex_A_locked || B == 5))' + type: assertion + format: C + - entry_type: flow_insensitive_invariant + flow_insensitive_invariant: + string: '! multithreaded || (mutex_A_locked || A == 5)' + type: assertion + format: C + - entry_type: flow_insensitive_invariant + flow_insensitive_invariant: + string: '! multithreaded || ((0 <= B && B <= 127) && B != 0)' + type: assertion + format: C + +Flow-insensitive invariants as location invariants. + + $ goblint --set ana.base.privatization protection --enable witness.yaml.enabled --set ana.activated[+] mutexGhosts --set witness.yaml.entry-types '["flow_insensitive_invariant", "ghost_instrumentation"]' --set witness.invariant.flow_insensitive-as location_invariant 04-priv_multi.c + [Success][Assert] Assertion "p == 5" will succeed (04-priv_multi.c:50:7-50:30) + [Success][Assert] Assertion "A == B" will succeed (04-priv_multi.c:71:5-71:28) + [Warning][Deadcode] Function 'dispose' has dead code: + on line 53 (04-priv_multi.c:53-53) + on line 56 (04-priv_multi.c:56-56) + [Warning][Deadcode] Function 'process' has dead code: + on line 37 (04-priv_multi.c:37-37) + on line 40 (04-priv_multi.c:40-40) + [Warning][Deadcode] Logical lines of code (LLoC) summary: + live: 40 + dead: 4 + total lines: 44 + [Warning][Deadcode][CWE-571] condition '1' (possibly inserted by CIL) is always true (04-priv_multi.c:25:10-25:11) + [Warning][Deadcode][CWE-571] condition 'A > 0' is always true (04-priv_multi.c:27:9-27:14) + [Warning][Deadcode][CWE-571] condition '1' (possibly inserted by CIL) is always true (04-priv_multi.c:45:10-45:11) + [Warning][Deadcode][CWE-571] condition 'B > 0' is always true (04-priv_multi.c:47:9-47:14) + [Info][Witness] witness generation summary: + location invariants: 9 + loop invariants: 0 + flow-insensitive invariants: 0 + total generation entries: 10 + [Info][Race] Memory locations race summary: + safe: 2 + vulnerable: 0 + unsafe: 0 + total memory locations: 2 + + $ yamlWitnessStrip < witness.yml > witness.location.yml + +Location invariant at `for` loop in `main` should be on column 3, not 7. + + $ diff witness.flow_insensitive.yml witness.location.yml + 153,154c153,160 + < - entry_type: flow_insensitive_invariant + < flow_insensitive_invariant: + --- + > - entry_type: location_invariant + > location: + > file_name: 04-priv_multi.c + > file_hash: $FILE_HASH + > line: 67 + > column: 3 + > function: main + > location_invariant: + 158,159c164,171 + < - entry_type: flow_insensitive_invariant + < flow_insensitive_invariant: + --- + > - entry_type: location_invariant + > location: + > file_name: 04-priv_multi.c + > file_hash: $FILE_HASH + > line: 67 + > column: 3 + > function: main + > location_invariant: + 163,164c175,248 + < - entry_type: flow_insensitive_invariant + < flow_insensitive_invariant: + --- + > - entry_type: location_invariant + > location: + > file_name: 04-priv_multi.c + > file_hash: $FILE_HASH + > line: 67 + > column: 3 + > function: main + > location_invariant: + > string: '! multithreaded || ((0 <= B && B <= 127) && B != 0)' + > type: assertion + > format: C + > - entry_type: location_invariant + > location: + > file_name: 04-priv_multi.c + > file_hash: $FILE_HASH + > line: 65 + > column: 3 + > function: main + > location_invariant: + > string: '! multithreaded || (mutex_B_locked || (mutex_A_locked || B == 5))' + > type: assertion + > format: C + > - entry_type: location_invariant + > location: + > file_name: 04-priv_multi.c + > file_hash: $FILE_HASH + > line: 65 + > column: 3 + > function: main + > location_invariant: + > string: '! multithreaded || (mutex_A_locked || A == 5)' + > type: assertion + > format: C + > - entry_type: location_invariant + > location: + > file_name: 04-priv_multi.c + > file_hash: $FILE_HASH + > line: 65 + > column: 3 + > function: main + > location_invariant: + > string: '! multithreaded || ((0 <= B && B <= 127) && B != 0)' + > type: assertion + > format: C + > - entry_type: location_invariant + > location: + > file_name: 04-priv_multi.c + > file_hash: $FILE_HASH + > line: 64 + > column: 3 + > function: main + > location_invariant: + > string: '! multithreaded || (mutex_B_locked || (mutex_A_locked || B == 5))' + > type: assertion + > format: C + > - entry_type: location_invariant + > location: + > file_name: 04-priv_multi.c + > file_hash: $FILE_HASH + > line: 64 + > column: 3 + > function: main + > location_invariant: + > string: '! multithreaded || (mutex_A_locked || A == 5)' + > type: assertion + > format: C + > - entry_type: location_invariant + > location: + > file_name: 04-priv_multi.c + > file_hash: $FILE_HASH + > line: 64 + > column: 3 + > function: main + > location_invariant: + [1] diff --git a/tests/regression/13-privatized/25-struct_nr.t b/tests/regression/13-privatized/25-struct_nr.t new file mode 100644 index 0000000000..fa00b2a5a5 --- /dev/null +++ b/tests/regression/13-privatized/25-struct_nr.t @@ -0,0 +1,97 @@ + $ goblint --set ana.base.privatization protection --enable witness.yaml.enabled --set ana.activated[+] mutexGhosts --set witness.yaml.entry-types '["flow_insensitive_invariant", "ghost_instrumentation"]' 25-struct_nr.c + [Success][Assert] Assertion "glob1 == 5" will succeed (25-struct_nr.c:26:3-26:30) + [Success][Assert] Assertion "t == 5" will succeed (25-struct_nr.c:16:3-16:26) + [Success][Assert] Assertion "glob1 == -10" will succeed (25-struct_nr.c:18:3-18:32) + [Success][Assert] Assertion "glob1 == 6" will succeed (25-struct_nr.c:30:3-30:30) + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 19 + dead: 0 + total lines: 19 + [Info][Witness] witness generation summary: + location invariants: 0 + loop invariants: 0 + flow-insensitive invariants: 2 + total generation entries: 3 + [Info][Race] Memory locations race summary: + safe: 1 + vulnerable: 0 + unsafe: 0 + total memory locations: 1 + + $ yamlWitnessStrip < witness.yml + - entry_type: ghost_instrumentation + content: + ghost_variables: + - name: lock1_mutex_locked + scope: global + type: int + initial: + value: "0" + format: c_expression + - name: multithreaded + scope: global + type: int + initial: + value: "0" + format: c_expression + ghost_updates: + - location: + file_name: 25-struct_nr.c + file_hash: $FILE_HASH + line: 14 + column: 3 + function: t_fun + updates: + - variable: lock1_mutex_locked + value: "1" + format: c_expression + - location: + file_name: 25-struct_nr.c + file_hash: $FILE_HASH + line: 20 + column: 3 + function: t_fun + updates: + - variable: lock1_mutex_locked + value: "0" + format: c_expression + - location: + file_name: 25-struct_nr.c + file_hash: $FILE_HASH + line: 27 + column: 3 + function: main + updates: + - variable: multithreaded + value: "1" + format: c_expression + - location: + file_name: 25-struct_nr.c + file_hash: $FILE_HASH + line: 28 + column: 3 + function: main + updates: + - variable: lock1_mutex_locked + value: "1" + format: c_expression + - location: + file_name: 25-struct_nr.c + file_hash: $FILE_HASH + line: 32 + column: 3 + function: main + updates: + - variable: lock1_mutex_locked + value: "0" + format: c_expression + - entry_type: flow_insensitive_invariant + flow_insensitive_invariant: + string: '! multithreaded || (lock1_mutex_locked || glob1 == 5)' + type: assertion + format: C + - entry_type: flow_insensitive_invariant + flow_insensitive_invariant: + string: '! multithreaded || ((-128 <= glob1 && glob1 <= 127) && glob1 != 0)' + type: assertion + format: C diff --git a/tests/regression/13-privatized/59-smtprc_threadenter_path_minimal.c b/tests/regression/13-privatized/59-smtprc_threadenter_path_minimal.c index 5a9342cfd0..dcbc617e0b 100644 --- a/tests/regression/13-privatized/59-smtprc_threadenter_path_minimal.c +++ b/tests/regression/13-privatized/59-smtprc_threadenter_path_minimal.c @@ -48,7 +48,7 @@ int main(int argc , char **argv ) } pthread_mutex_unlock(& main_thread_count_mutex); - // lock gets here with two paths and crashes + // NOCRASH: lock gets here with two paths and crashes pthread_create(& c_tid, NULL, & thread_start, NULL); return (0); } \ No newline at end of file diff --git a/tests/regression/13-privatized/62-global-threadid.c b/tests/regression/13-privatized/62-global-threadid.c index 38d21700ea..b5f3c831bb 100644 --- a/tests/regression/13-privatized/62-global-threadid.c +++ b/tests/regression/13-privatized/62-global-threadid.c @@ -1,5 +1,5 @@ #include - +// NOCHECK pthread_t id; extern void magic(); diff --git a/tests/regression/13-privatized/63-access-threadspawn-lval.c b/tests/regression/13-privatized/63-access-threadspawn-lval.c index 7f98b129ab..9ebd29a89d 100644 --- a/tests/regression/13-privatized/63-access-threadspawn-lval.c +++ b/tests/regression/13-privatized/63-access-threadspawn-lval.c @@ -1,3 +1,4 @@ +// CRAM #include pthread_t id1; diff --git a/tests/regression/13-privatized/63-access-threadspawn-lval.t b/tests/regression/13-privatized/63-access-threadspawn-lval.t new file mode 100644 index 0000000000..313459637c --- /dev/null +++ b/tests/regression/13-privatized/63-access-threadspawn-lval.t @@ -0,0 +1,24 @@ +Should have (safe) write accesses to id1 and id2: + + $ goblint --enable allglobs 63-access-threadspawn-lval.c + [Error][Imprecise][Unsound] Function definition missing for magic2 (63-access-threadspawn-lval.c:21:3-21:12) + [Info][Imprecise] INVALIDATING ALL GLOBALS! (63-access-threadspawn-lval.c:21:3-21:12) + [Info][Imprecise] Invalidating expressions: & A, & id2, & id1, & e (63-access-threadspawn-lval.c:21:3-21:12) + [Error][Imprecise][Unsound] Function definition missing for magic1 (63-access-threadspawn-lval.c:13:3-13:11) + [Info][Imprecise] INVALIDATING ALL GLOBALS! (63-access-threadspawn-lval.c:13:3-13:11) + [Info][Imprecise] Invalidating expressions: & A, & id2, & id1 (63-access-threadspawn-lval.c:13:3-13:11) + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 13 + dead: 0 + total lines: 13 + [Success][Race] Memory location id1 (safe): (63-access-threadspawn-lval.c:4:11-4:14) + write with [multi:false, thread:[main]] (conf. 110) (exp: & *((pthread_t * __restrict )(& id1))) (63-access-threadspawn-lval.c:27:3-27:37) + [Success][Race] Memory location id2 (safe): (63-access-threadspawn-lval.c:5:11-5:14) + write with [mhp:{created={[main, f@63-access-threadspawn-lval.c:27:3-27:37]}}, thread:[main]] (conf. 110) (exp: (pthread_t * __restrict )(& id2)) (63-access-threadspawn-lval.c:28:3-28:37) + write with [mhp:{created={[main, f@63-access-threadspawn-lval.c:27:3-27:37]}}, thread:[main]] (conf. 110) (exp: & *((pthread_t * __restrict )(& id2))) (63-access-threadspawn-lval.c:28:3-28:37) + [Info][Race] Memory locations race summary: + safe: 2 + vulnerable: 0 + unsafe: 0 + total memory locations: 2 + [Error][Imprecise][Unsound] Function definition missing diff --git a/tests/regression/13-privatized/64-access-invalidate.c b/tests/regression/13-privatized/64-access-invalidate.c index 7a191a9650..1461d413e2 100644 --- a/tests/regression/13-privatized/64-access-invalidate.c +++ b/tests/regression/13-privatized/64-access-invalidate.c @@ -1,3 +1,4 @@ +// CRAM #include pthread_t id; diff --git a/tests/regression/13-privatized/64-access-invalidate.t b/tests/regression/13-privatized/64-access-invalidate.t new file mode 100644 index 0000000000..28227ec475 --- /dev/null +++ b/tests/regression/13-privatized/64-access-invalidate.t @@ -0,0 +1,21 @@ +Should have (safe) write access to id1 and magic2 invalidate to A: + + $ goblint --enable allglobs 64-access-invalidate.c + [Error][Imprecise][Unsound] Function definition missing for magic2 (64-access-invalidate.c:16:3-16:12) + [Info][Imprecise] INVALIDATING ALL GLOBALS! (64-access-invalidate.c:16:3-16:12) + [Info][Imprecise] Invalidating expressions: & A, & id, & e (64-access-invalidate.c:16:3-16:12) + [Error][Imprecise][Unsound] Function definition missing for magic1 (64-access-invalidate.c:12:3-12:11) + [Info][Imprecise] INVALIDATING ALL GLOBALS! (64-access-invalidate.c:12:3-12:11) + [Info][Imprecise] Invalidating expressions: & A, & id (64-access-invalidate.c:12:3-12:11) + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 10 + dead: 0 + total lines: 10 + [Success][Race] Memory location id (safe): (64-access-invalidate.c:4:11-4:13) + write with [multi:false, thread:[main]] (conf. 110) (exp: & *((pthread_t * __restrict )(& id))) (64-access-invalidate.c:21:3-21:36) + [Info][Race] Memory locations race summary: + safe: 1 + vulnerable: 0 + unsafe: 0 + total memory locations: 1 + [Error][Imprecise][Unsound] Function definition missing diff --git a/tests/regression/13-privatized/65-threadreturn-cpa-remove.c b/tests/regression/13-privatized/65-threadreturn-cpa-remove.c index 3bbe85b3ea..58d8c9ea2f 100644 --- a/tests/regression/13-privatized/65-threadreturn-cpa-remove.c +++ b/tests/regression/13-privatized/65-threadreturn-cpa-remove.c @@ -1,5 +1,5 @@ #include - +// NOCHECK int d; pthread_t g; enum { b } c() {} diff --git a/tests/regression/13-privatized/74-mutex.t b/tests/regression/13-privatized/74-mutex.t new file mode 100644 index 0000000000..4b370db387 --- /dev/null +++ b/tests/regression/13-privatized/74-mutex.t @@ -0,0 +1,395 @@ + $ goblint --enable ana.sv-comp.functions --set ana.base.privatization protection --enable witness.yaml.enabled --set ana.activated[+] mutexGhosts --set witness.yaml.entry-types '["flow_insensitive_invariant", "ghost_instrumentation"]' 74-mutex.c + [Success][Assert] Assertion "used == 0" will succeed (74-mutex.c:37:3-37:29) + [Warning][Deadcode] Function 'producer' has dead code: + on line 26 (74-mutex.c:26-26) + [Warning][Deadcode] Logical lines of code (LLoC) summary: + live: 14 + dead: 1 + total lines: 15 + [Warning][Deadcode][CWE-571] condition '1' (possibly inserted by CIL) is always true (74-mutex.c:19:10-19:11) + [Info][Witness] witness generation summary: + location invariants: 0 + loop invariants: 0 + flow-insensitive invariants: 2 + total generation entries: 3 + [Info][Race] Memory locations race summary: + safe: 1 + vulnerable: 0 + unsafe: 0 + total memory locations: 1 + + $ yamlWitnessStrip < witness.yml | tee witness.flow_insensitive.yml + - entry_type: ghost_instrumentation + content: + ghost_variables: + - name: m_locked + scope: global + type: int + initial: + value: "0" + format: c_expression + - name: multithreaded + scope: global + type: int + initial: + value: "0" + format: c_expression + ghost_updates: + - location: + file_name: 74-mutex.c + file_hash: $FILE_HASH + line: 20 + column: 5 + function: producer + updates: + - variable: m_locked + value: "1" + format: c_expression + - location: + file_name: 74-mutex.c + file_hash: $FILE_HASH + line: 23 + column: 5 + function: producer + updates: + - variable: m_locked + value: "0" + format: c_expression + - location: + file_name: 74-mutex.c + file_hash: $FILE_HASH + line: 34 + column: 3 + function: main + updates: + - variable: multithreaded + value: "1" + format: c_expression + - location: + file_name: 74-mutex.c + file_hash: $FILE_HASH + line: 36 + column: 3 + function: main + updates: + - variable: m_locked + value: "1" + format: c_expression + - location: + file_name: 74-mutex.c + file_hash: $FILE_HASH + line: 38 + column: 3 + function: main + updates: + - variable: m_locked + value: "0" + format: c_expression + - entry_type: flow_insensitive_invariant + flow_insensitive_invariant: + string: '! multithreaded || (m_locked || used == 0)' + type: assertion + format: C + - entry_type: flow_insensitive_invariant + flow_insensitive_invariant: + string: '! multithreaded || (0 <= used && used <= 1)' + type: assertion + format: C + +Flow-insensitive invariants as location invariants. + + $ goblint --enable ana.sv-comp.functions --set ana.base.privatization protection --enable witness.yaml.enabled --set ana.activated[+] mutexGhosts --set witness.yaml.entry-types '["flow_insensitive_invariant", "ghost_instrumentation"]' --set witness.invariant.flow_insensitive-as location_invariant 74-mutex.c + [Success][Assert] Assertion "used == 0" will succeed (74-mutex.c:37:3-37:29) + [Warning][Deadcode] Function 'producer' has dead code: + on line 26 (74-mutex.c:26-26) + [Warning][Deadcode] Logical lines of code (LLoC) summary: + live: 14 + dead: 1 + total lines: 15 + [Warning][Deadcode][CWE-571] condition '1' (possibly inserted by CIL) is always true (74-mutex.c:19:10-19:11) + [Info][Witness] witness generation summary: + location invariants: 2 + loop invariants: 0 + flow-insensitive invariants: 0 + total generation entries: 3 + [Info][Race] Memory locations race summary: + safe: 1 + vulnerable: 0 + unsafe: 0 + total memory locations: 1 + + $ yamlWitnessStrip < witness.yml > witness.location.yml + + $ diff witness.flow_insensitive.yml witness.location.yml + 67,68c67,74 + < - entry_type: flow_insensitive_invariant + < flow_insensitive_invariant: + --- + > - entry_type: location_invariant + > location: + > file_name: 74-mutex.c + > file_hash: $FILE_HASH + > line: 36 + > column: 3 + > function: main + > location_invariant: + 72,73c78,85 + < - entry_type: flow_insensitive_invariant + < flow_insensitive_invariant: + --- + > - entry_type: location_invariant + > location: + > file_name: 74-mutex.c + > file_hash: $FILE_HASH + > line: 36 + > column: 3 + > function: main + > location_invariant: + [1] + +Should also work with earlyglobs. +Earlyglobs shouldn't cause protected writes in multithreaded mode from being immediately published to protected invariant. + + $ goblint --enable ana.sv-comp.functions --set ana.base.privatization protection --enable exp.earlyglobs 74-mutex.c + [Success][Assert] Assertion "used == 0" will succeed (74-mutex.c:37:3-37:29) + [Warning][Deadcode] Function 'producer' has dead code: + on line 26 (74-mutex.c:26-26) + [Warning][Deadcode] Logical lines of code (LLoC) summary: + live: 14 + dead: 1 + total lines: 15 + [Warning][Deadcode][CWE-571] condition '1' (possibly inserted by CIL) is always true (74-mutex.c:19:10-19:11) + [Info][Race] Memory locations race summary: + safe: 1 + vulnerable: 0 + unsafe: 0 + total memory locations: 1 + +Same with ghost_instrumentation and invariant_set entries. + + $ goblint --enable ana.sv-comp.functions --set ana.base.privatization protection --enable witness.yaml.enabled --set ana.activated[+] mutexGhosts --set witness.yaml.entry-types '["flow_insensitive_invariant", "ghost_instrumentation"]' --set witness.invariant.flow_insensitive-as invariant_set-location_invariant 74-mutex.c + [Success][Assert] Assertion "used == 0" will succeed (74-mutex.c:37:3-37:29) + [Warning][Deadcode] Function 'producer' has dead code: + on line 26 (74-mutex.c:26-26) + [Warning][Deadcode] Logical lines of code (LLoC) summary: + live: 14 + dead: 1 + total lines: 15 + [Warning][Deadcode][CWE-571] condition '1' (possibly inserted by CIL) is always true (74-mutex.c:19:10-19:11) + [Info][Witness] witness generation summary: + location invariants: 2 + loop invariants: 0 + flow-insensitive invariants: 0 + total generation entries: 2 + [Info][Race] Memory locations race summary: + safe: 1 + vulnerable: 0 + unsafe: 0 + total memory locations: 1 + + $ yamlWitnessStrip < witness.yml + - entry_type: ghost_instrumentation + content: + ghost_variables: + - name: m_locked + scope: global + type: int + initial: + value: "0" + format: c_expression + - name: multithreaded + scope: global + type: int + initial: + value: "0" + format: c_expression + ghost_updates: + - location: + file_name: 74-mutex.c + file_hash: $FILE_HASH + line: 20 + column: 5 + function: producer + updates: + - variable: m_locked + value: "1" + format: c_expression + - location: + file_name: 74-mutex.c + file_hash: $FILE_HASH + line: 23 + column: 5 + function: producer + updates: + - variable: m_locked + value: "0" + format: c_expression + - location: + file_name: 74-mutex.c + file_hash: $FILE_HASH + line: 34 + column: 3 + function: main + updates: + - variable: multithreaded + value: "1" + format: c_expression + - location: + file_name: 74-mutex.c + file_hash: $FILE_HASH + line: 36 + column: 3 + function: main + updates: + - variable: m_locked + value: "1" + format: c_expression + - location: + file_name: 74-mutex.c + file_hash: $FILE_HASH + line: 38 + column: 3 + function: main + updates: + - variable: m_locked + value: "0" + format: c_expression + - entry_type: invariant_set + content: + - invariant: + type: location_invariant + location: + file_name: 74-mutex.c + file_hash: $FILE_HASH + line: 36 + column: 3 + function: main + value: '! multithreaded || (0 <= used && used <= 1)' + format: c_expression + - invariant: + type: location_invariant + location: + file_name: 74-mutex.c + file_hash: $FILE_HASH + line: 36 + column: 3 + function: main + value: '! multithreaded || (m_locked || used == 0)' + format: c_expression + +Same with mutex-meet. + + $ goblint --enable ana.sv-comp.functions --set ana.base.privatization mutex-meet --enable witness.yaml.enabled --set ana.activated[+] mutexGhosts --set witness.yaml.entry-types '["flow_insensitive_invariant", "ghost_instrumentation"]' 74-mutex.c + [Success][Assert] Assertion "used == 0" will succeed (74-mutex.c:37:3-37:29) + [Warning][Deadcode] Function 'producer' has dead code: + on line 26 (74-mutex.c:26-26) + [Warning][Deadcode] Logical lines of code (LLoC) summary: + live: 14 + dead: 1 + total lines: 15 + [Warning][Deadcode][CWE-571] condition '1' (possibly inserted by CIL) is always true (74-mutex.c:19:10-19:11) + [Info][Witness] witness generation summary: + location invariants: 0 + loop invariants: 0 + flow-insensitive invariants: 2 + total generation entries: 3 + [Info][Race] Memory locations race summary: + safe: 1 + vulnerable: 0 + unsafe: 0 + total memory locations: 1 + + $ yamlWitnessStrip < witness.yml + - entry_type: ghost_instrumentation + content: + ghost_variables: + - name: m_locked + scope: global + type: int + initial: + value: "0" + format: c_expression + - name: multithreaded + scope: global + type: int + initial: + value: "0" + format: c_expression + ghost_updates: + - location: + file_name: 74-mutex.c + file_hash: $FILE_HASH + line: 20 + column: 5 + function: producer + updates: + - variable: m_locked + value: "1" + format: c_expression + - location: + file_name: 74-mutex.c + file_hash: $FILE_HASH + line: 23 + column: 5 + function: producer + updates: + - variable: m_locked + value: "0" + format: c_expression + - location: + file_name: 74-mutex.c + file_hash: $FILE_HASH + line: 34 + column: 3 + function: main + updates: + - variable: multithreaded + value: "1" + format: c_expression + - location: + file_name: 74-mutex.c + file_hash: $FILE_HASH + line: 36 + column: 3 + function: main + updates: + - variable: m_locked + value: "1" + format: c_expression + - location: + file_name: 74-mutex.c + file_hash: $FILE_HASH + line: 38 + column: 3 + function: main + updates: + - variable: m_locked + value: "0" + format: c_expression + - entry_type: flow_insensitive_invariant + flow_insensitive_invariant: + string: '! multithreaded || (m_locked || used == 0)' + type: assertion + format: C + - entry_type: flow_insensitive_invariant + flow_insensitive_invariant: + string: '! multithreaded || (0 <= used && used <= 1)' + type: assertion + format: C + +Should also work with earlyglobs. + + $ goblint --enable ana.sv-comp.functions --set ana.base.privatization mutex-meet --enable exp.earlyglobs 74-mutex.c + [Success][Assert] Assertion "used == 0" will succeed (74-mutex.c:37:3-37:29) + [Warning][Deadcode] Function 'producer' has dead code: + on line 26 (74-mutex.c:26-26) + [Warning][Deadcode] Logical lines of code (LLoC) summary: + live: 14 + dead: 1 + total lines: 15 + [Warning][Deadcode][CWE-571] condition '1' (possibly inserted by CIL) is always true (74-mutex.c:19:10-19:11) + [Info][Race] Memory locations race summary: + safe: 1 + vulnerable: 0 + unsafe: 0 + total memory locations: 1 diff --git a/tests/regression/13-privatized/90-union.c b/tests/regression/13-privatized/90-union.c new file mode 100644 index 0000000000..df5990d7a4 --- /dev/null +++ b/tests/regression/13-privatized/90-union.c @@ -0,0 +1,29 @@ +// PARAM: --set ana.base.privatization write +#include +#include +#include +union g { + int h; + int i; +}; +struct j { + union g data; +}; + + +void* af(void* arg) { + int top; + struct j* jptr = (struct j*) malloc(sizeof(struct j)); + memset(jptr, 0, sizeof(struct j)); + + while (top) { + if (jptr->data.i); + + __goblint_check(jptr->data.i == 0); // Should alo work for write! + } +} + +void main() { + pthread_t ah; + pthread_create(&ah, 0, af, 0); +} diff --git a/tests/regression/13-privatized/91-union-direct.c b/tests/regression/13-privatized/91-union-direct.c new file mode 100644 index 0000000000..bf1127d389 --- /dev/null +++ b/tests/regression/13-privatized/91-union-direct.c @@ -0,0 +1,26 @@ +// PARAM: --set ana.base.privatization write +#include +#include +#include +union g { + int h; + int i; +}; + +void* af(void* arg) { + // Go MT +} + +void main() { + pthread_t ah; + pthread_create(&ah, 0, af, 0); + + int top; + union g* jptr = (union g*) malloc(sizeof(union g)); + memset(jptr, 0, sizeof(union g)); + + while (top) { + if (jptr->i); + __goblint_check(jptr->i == 0); // Should alo work for write! + } +} diff --git a/tests/regression/13-privatized/92-idx_priv.c b/tests/regression/13-privatized/92-idx_priv.c new file mode 100644 index 0000000000..ed0e8d3228 --- /dev/null +++ b/tests/regression/13-privatized/92-idx_priv.c @@ -0,0 +1,26 @@ +#include +#include + +int data; +pthread_mutex_t m[10]; + +void *t_fun(void *arg) { + pthread_mutex_lock(&m[4]); + data++; // NORACE + data--; // NORACE + pthread_mutex_unlock(&m[4]); + return NULL; +} + +int main() { + for (int i = 0; i < 10; i++) + pthread_mutex_init(&m[i], NULL); + + pthread_t id; + pthread_create(&id, NULL, t_fun, NULL); + pthread_mutex_lock(&m[4]); + __goblint_check(data == 0); // NORACE + pthread_mutex_unlock(&m[4]); + return 0; +} + diff --git a/tests/regression/13-privatized/92-idx_priv.t b/tests/regression/13-privatized/92-idx_priv.t new file mode 100644 index 0000000000..7b3adc5e0b --- /dev/null +++ b/tests/regression/13-privatized/92-idx_priv.t @@ -0,0 +1,45 @@ + $ goblint --set ana.base.privatization protection --enable witness.yaml.enabled --set ana.activated[+] mutexGhosts --set witness.yaml.entry-types '["flow_insensitive_invariant", "ghost_instrumentation"]' 92-idx_priv.c + [Success][Assert] Assertion "data == 0" will succeed (92-idx_priv.c:22:3-22:29) + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 14 + dead: 0 + total lines: 14 + [Info][Witness] witness generation summary: + location invariants: 0 + loop invariants: 0 + flow-insensitive invariants: 1 + total generation entries: 2 + [Info][Race] Memory locations race summary: + safe: 1 + vulnerable: 0 + unsafe: 0 + total memory locations: 1 + + $ yamlWitnessStrip < witness.yml + - entry_type: ghost_instrumentation + content: + ghost_variables: + - name: multithreaded + scope: global + type: int + initial: + value: "0" + format: c_expression + ghost_updates: + - location: + file_name: 92-idx_priv.c + file_hash: $FILE_HASH + line: 20 + column: 3 + function: main + updates: + - variable: multithreaded + value: "1" + format: c_expression + - entry_type: flow_insensitive_invariant + flow_insensitive_invariant: + string: '! multithreaded || (0 <= data && data <= 1)' + type: assertion + format: C + +TODO: protected invariant with m_4_locked without making 56-witness/68-ghost-ambiguous-idx unsound diff --git a/tests/regression/13-privatized/93-unlock-idx-ambiguous.c b/tests/regression/13-privatized/93-unlock-idx-ambiguous.c new file mode 100644 index 0000000000..081302514b --- /dev/null +++ b/tests/regression/13-privatized/93-unlock-idx-ambiguous.c @@ -0,0 +1,27 @@ +// PARAM: --set ana.base.privatization protection --enable ana.sv-comp.functions +#include +#include +extern _Bool __VERIFIER_nondet_bool(); + +int g; +pthread_mutex_t m[2] = {PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER}; + +void *t_fun(void *arg) { + pthread_mutex_lock(&m[0]); + pthread_mutex_lock(&m[1]); // so we're unlocking a mutex we definitely hold + g++; + int r = __VERIFIER_nondet_bool(); + pthread_mutex_unlock(&m[r]); // TODO NOWARN (definitely held either way) + // could have unlocked m[0], so should have published g there + return NULL; +} + +int main() { + pthread_t id; + pthread_create(&id, NULL, t_fun, NULL); + + pthread_mutex_lock(&m[0]); + __goblint_check(g == 0); // UNKNOWN! + pthread_mutex_unlock(&m[0]); + return 0; +} diff --git a/tests/regression/13-privatized/93-unlock-idx-ambiguous.t b/tests/regression/13-privatized/93-unlock-idx-ambiguous.t new file mode 100644 index 0000000000..8f24d728bf --- /dev/null +++ b/tests/regression/13-privatized/93-unlock-idx-ambiguous.t @@ -0,0 +1,65 @@ + $ goblint --set ana.base.privatization protection --enable ana.sv-comp.functions 93-unlock-idx-ambiguous.c + [Warning][Unknown] unlocking mutex (m[def_exc:Unknown int([0,1])]) which may not be held (93-unlock-idx-ambiguous.c:14:3-14:30) + [Warning][Assert] Assertion "g == 0" is unknown. (93-unlock-idx-ambiguous.c:24:3-24:26) + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 14 + dead: 0 + total lines: 14 + [Info][Race] Memory locations race summary: + safe: 1 + vulnerable: 0 + unsafe: 0 + total memory locations: 1 + + $ goblint --set ana.base.privatization mutex-meet --enable ana.sv-comp.functions 93-unlock-idx-ambiguous.c + [Warning][Unknown] unlocking mutex (m[def_exc:Unknown int([0,1])]) which may not be held (93-unlock-idx-ambiguous.c:14:3-14:30) + [Warning][Assert] Assertion "g == 0" is unknown. (93-unlock-idx-ambiguous.c:24:3-24:26) + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 14 + dead: 0 + total lines: 14 + [Info][Race] Memory locations race summary: + safe: 1 + vulnerable: 0 + unsafe: 0 + total memory locations: 1 + + $ goblint --set ana.base.privatization lock --enable ana.sv-comp.functions 93-unlock-idx-ambiguous.c + [Warning][Unknown] unlocking mutex (m[def_exc:Unknown int([0,1])]) which may not be held (93-unlock-idx-ambiguous.c:14:3-14:30) + [Warning][Assert] Assertion "g == 0" is unknown. (93-unlock-idx-ambiguous.c:24:3-24:26) + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 14 + dead: 0 + total lines: 14 + [Info][Race] Memory locations race summary: + safe: 1 + vulnerable: 0 + unsafe: 0 + total memory locations: 1 + + $ goblint --set ana.base.privatization write --enable ana.sv-comp.functions 93-unlock-idx-ambiguous.c + [Warning][Unknown] unlocking mutex (m[def_exc:Unknown int([0,1])]) which may not be held (93-unlock-idx-ambiguous.c:14:3-14:30) + [Warning][Assert] Assertion "g == 0" is unknown. (93-unlock-idx-ambiguous.c:24:3-24:26) + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 14 + dead: 0 + total lines: 14 + [Info][Race] Memory locations race summary: + safe: 1 + vulnerable: 0 + unsafe: 0 + total memory locations: 1 + + $ goblint --set ana.base.privatization mine-nothread --enable ana.sv-comp.functions 93-unlock-idx-ambiguous.c + [Warning][Unknown] unlocking mutex (m[def_exc:Unknown int([0,1])]) which may not be held (93-unlock-idx-ambiguous.c:14:3-14:30) + [Warning][Assert] Assertion "g == 0" is unknown. (93-unlock-idx-ambiguous.c:24:3-24:26) + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 14 + dead: 0 + total lines: 14 + [Info][Race] Memory locations race summary: + safe: 1 + vulnerable: 0 + unsafe: 0 + total memory locations: 1 + diff --git a/tests/regression/13-privatized/94-unlock-unknown.c b/tests/regression/13-privatized/94-unlock-unknown.c new file mode 100644 index 0000000000..0897c38de8 --- /dev/null +++ b/tests/regression/13-privatized/94-unlock-unknown.c @@ -0,0 +1,25 @@ +// PARAM: --set ana.base.privatization protection --enable ana.sv-comp.functions +#include +#include + +int g; +pthread_mutex_t m[2] = {PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER}; + +void *t_fun(void *arg) { + pthread_mutex_lock(&m[0]); + g++; + pthread_mutex_t *r; // rand + pthread_mutex_unlock(r); + // could have unlocked m[0], so should have published g there + return NULL; +} + +int main() { + pthread_t id; + pthread_create(&id, NULL, t_fun, NULL); + + pthread_mutex_lock(&m[0]); + __goblint_check(g == 0); // UNKNOWN! + pthread_mutex_unlock(&m[0]); + return 0; +} diff --git a/tests/regression/13-privatized/94-unlock-unknown.t b/tests/regression/13-privatized/94-unlock-unknown.t new file mode 100644 index 0000000000..cc0aa921fe --- /dev/null +++ b/tests/regression/13-privatized/94-unlock-unknown.t @@ -0,0 +1,70 @@ + $ goblint --set ana.base.privatization protection --enable ana.sv-comp.functions 94-unlock-unknown.c + [Warning][Unknown] unlocking unknown mutex which may not be held (94-unlock-unknown.c:12:3-12:26) + [Warning][Unknown] unlocking NULL mutex (94-unlock-unknown.c:12:3-12:26) + [Warning][Assert] Assertion "g == 0" is unknown. (94-unlock-unknown.c:22:3-22:26) + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 11 + dead: 0 + total lines: 11 + [Info][Race] Memory locations race summary: + safe: 1 + vulnerable: 0 + unsafe: 0 + total memory locations: 1 + + $ goblint --set ana.base.privatization mutex-meet --enable ana.sv-comp.functions 94-unlock-unknown.c + [Warning][Unknown] unlocking unknown mutex which may not be held (94-unlock-unknown.c:12:3-12:26) + [Warning][Unknown] unlocking NULL mutex (94-unlock-unknown.c:12:3-12:26) + [Warning][Assert] Assertion "g == 0" is unknown. (94-unlock-unknown.c:22:3-22:26) + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 11 + dead: 0 + total lines: 11 + [Info][Race] Memory locations race summary: + safe: 1 + vulnerable: 0 + unsafe: 0 + total memory locations: 1 + + $ goblint --set ana.base.privatization lock --enable ana.sv-comp.functions 94-unlock-unknown.c + [Warning][Unknown] unlocking unknown mutex which may not be held (94-unlock-unknown.c:12:3-12:26) + [Warning][Unknown] unlocking NULL mutex (94-unlock-unknown.c:12:3-12:26) + [Warning][Assert] Assertion "g == 0" is unknown. (94-unlock-unknown.c:22:3-22:26) + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 11 + dead: 0 + total lines: 11 + [Info][Race] Memory locations race summary: + safe: 1 + vulnerable: 0 + unsafe: 0 + total memory locations: 1 + + $ goblint --set ana.base.privatization write --enable ana.sv-comp.functions 94-unlock-unknown.c + [Warning][Unknown] unlocking unknown mutex which may not be held (94-unlock-unknown.c:12:3-12:26) + [Warning][Unknown] unlocking NULL mutex (94-unlock-unknown.c:12:3-12:26) + [Warning][Assert] Assertion "g == 0" is unknown. (94-unlock-unknown.c:22:3-22:26) + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 11 + dead: 0 + total lines: 11 + [Info][Race] Memory locations race summary: + safe: 1 + vulnerable: 0 + unsafe: 0 + total memory locations: 1 + + $ goblint --set ana.base.privatization mine-nothread --enable ana.sv-comp.functions 94-unlock-unknown.c + [Warning][Unknown] unlocking unknown mutex which may not be held (94-unlock-unknown.c:12:3-12:26) + [Warning][Unknown] unlocking NULL mutex (94-unlock-unknown.c:12:3-12:26) + [Warning][Assert] Assertion "g == 0" is unknown. (94-unlock-unknown.c:22:3-22:26) + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 11 + dead: 0 + total lines: 11 + [Info][Race] Memory locations race summary: + safe: 1 + vulnerable: 0 + unsafe: 0 + total memory locations: 1 + diff --git a/tests/regression/13-privatized/dune b/tests/regression/13-privatized/dune new file mode 100644 index 0000000000..9227128b15 --- /dev/null +++ b/tests/regression/13-privatized/dune @@ -0,0 +1,6 @@ +(cram + (deps (glob_files *.c))) + +(cram + (applies_to 04-priv_multi) + (enabled_if (<> %{system} macosx))) diff --git a/tests/regression/20-slr_term/01-no-int-context.c b/tests/regression/20-slr_term/01-no-int-context.c index d057098559..ce8d3cb833 100644 --- a/tests/regression/20-slr_term/01-no-int-context.c +++ b/tests/regression/20-slr_term/01-no-int-context.c @@ -1,5 +1,5 @@ // PARAM: --enable ana.int.interval --set solver slr3t --disable ana.base.context.int - +// NOCHECK int f (int i) { // -2 return i+1; } // -3 void g(int j) { // -4 diff --git a/tests/regression/20-slr_term/02-global-inc.c b/tests/regression/20-slr_term/02-global-inc.c index cfb2432999..fc2f9100df 100644 --- a/tests/regression/20-slr_term/02-global-inc.c +++ b/tests/regression/20-slr_term/02-global-inc.c @@ -1,4 +1,4 @@ - +// NOCHECK int g = 0; int f = 0; diff --git a/tests/regression/20-slr_term/03-3ctxs.c b/tests/regression/20-slr_term/03-3ctxs.c index d0236960ea..b622549419 100644 --- a/tests/regression/20-slr_term/03-3ctxs.c +++ b/tests/regression/20-slr_term/03-3ctxs.c @@ -1,4 +1,4 @@ - +// NOCHECK int f(int j){ return j+1; } diff --git a/tests/regression/20-slr_term/05-selfloop.c b/tests/regression/20-slr_term/05-selfloop.c index 94286393a6..638d11cb1b 100644 --- a/tests/regression/20-slr_term/05-selfloop.c +++ b/tests/regression/20-slr_term/05-selfloop.c @@ -1,3 +1,4 @@ +// NOCHECK void f() { } void g() { } void h() { } diff --git a/tests/regression/20-slr_term/06-trylock_rc_slr.c b/tests/regression/20-slr_term/06-trylock_rc_slr.c index ab24b142f6..d6e3c3f31e 100644 --- a/tests/regression/20-slr_term/06-trylock_rc_slr.c +++ b/tests/regression/20-slr_term/06-trylock_rc_slr.c @@ -1,4 +1,5 @@ // PARAM: --enable ana.int.interval --set solver slr3t +// FIXPOINT #include pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; diff --git a/tests/regression/22-partitioned_arrays/08-unsupported.c b/tests/regression/22-partitioned_arrays/08-unsupported.c index 51e897d840..73f5871d9c 100644 --- a/tests/regression/22-partitioned_arrays/08-unsupported.c +++ b/tests/regression/22-partitioned_arrays/08-unsupported.c @@ -1,6 +1,7 @@ // PARAM: --enable ana.int.interval --disable exp.fast_global_inits --set ana.base.arrays.domain partitioned // This is just to test that the analysis does not cause problems for features that are not explicitly dealt with +// NOCHECK: what problems? int main(void) { callok(); } diff --git a/tests/regression/22-partitioned_arrays/12-was_problematic_2.c b/tests/regression/22-partitioned_arrays/12-was_problematic_2.c index fd57549008..2ff396feb4 100644 --- a/tests/regression/22-partitioned_arrays/12-was_problematic_2.c +++ b/tests/regression/22-partitioned_arrays/12-was_problematic_2.c @@ -1,4 +1,5 @@ // PARAM: --enable ana.int.interval --set ana.base.arrays.domain partitioned +// NOCHECK int main(void) { int arr[260]; diff --git a/tests/regression/22-partitioned_arrays/13-was_problematic_3.c b/tests/regression/22-partitioned_arrays/13-was_problematic_3.c index ff7e839753..1822af4f0a 100644 --- a/tests/regression/22-partitioned_arrays/13-was_problematic_3.c +++ b/tests/regression/22-partitioned_arrays/13-was_problematic_3.c @@ -1,4 +1,5 @@ // PARAM: --enable ana.int.interval --set ana.base.arrays.domain partitioned +// NOCHECK struct some_struct { int dir[7]; diff --git a/tests/regression/22-partitioned_arrays/16-refine-meet.c b/tests/regression/22-partitioned_arrays/16-refine-meet.c index 05a73a70ba..7f238ab858 100644 --- a/tests/regression/22-partitioned_arrays/16-refine-meet.c +++ b/tests/regression/22-partitioned_arrays/16-refine-meet.c @@ -1,4 +1,5 @@ // PARAM: --set ana.base.arrays.domain partitioned +// NOCHECK int garr[7]; int main(int argc, char **argv) diff --git a/tests/regression/22-partitioned_arrays/19-fixpoint.c b/tests/regression/22-partitioned_arrays/19-fixpoint.c index 1ac55216dd..30d7821db5 100644 --- a/tests/regression/22-partitioned_arrays/19-fixpoint.c +++ b/tests/regression/22-partitioned_arrays/19-fixpoint.c @@ -1,4 +1,5 @@ // PARAM: --set ana.base.arrays.domain partitioned +// FIXPOINT #include int stored_elements[20]; diff --git a/tests/regression/22-partitioned_arrays/20-more-fixpoint.c b/tests/regression/22-partitioned_arrays/20-more-fixpoint.c index 8181cf85dd..ab87e1944a 100644 --- a/tests/regression/22-partitioned_arrays/20-more-fixpoint.c +++ b/tests/regression/22-partitioned_arrays/20-more-fixpoint.c @@ -1,4 +1,5 @@ // PARAM: --set ana.base.arrays.domain partitioned +// FIXPOINT #include #include #include diff --git a/tests/regression/23-partitioned_arrays_last/08-unsupported.c b/tests/regression/23-partitioned_arrays_last/08-unsupported.c index 0b0a1baca9..cff075d786 100644 --- a/tests/regression/23-partitioned_arrays_last/08-unsupported.c +++ b/tests/regression/23-partitioned_arrays_last/08-unsupported.c @@ -1,6 +1,7 @@ // PARAM: --enable ana.int.interval --disable exp.fast_global_inits --set ana.base.arrays.domain partitioned --set ana.base.partition-arrays.keep-expr "last" // This is just to test that the analysis does not cause problems for features that are not explicitly dealt with +// NOCHECK: what problems? int main(void) { vla(); callok(); diff --git a/tests/regression/23-partitioned_arrays_last/12-was_problematic_2.c b/tests/regression/23-partitioned_arrays_last/12-was_problematic_2.c index 9523510472..d5593ffe8b 100644 --- a/tests/regression/23-partitioned_arrays_last/12-was_problematic_2.c +++ b/tests/regression/23-partitioned_arrays_last/12-was_problematic_2.c @@ -1,4 +1,5 @@ // PARAM: --enable ana.int.interval --set ana.base.arrays.domain partitioned --set ana.base.partition-arrays.keep-expr "last" +// NOCHECK int main(void) { int arr[260]; diff --git a/tests/regression/24-octagon/03-previously_problematic_a.c b/tests/regression/24-octagon/03-previously_problematic_a.c index 986e2821fd..a266358408 100644 --- a/tests/regression/24-octagon/03-previously_problematic_a.c +++ b/tests/regression/24-octagon/03-previously_problematic_a.c @@ -1,5 +1,5 @@ // SKIP PARAM: --enable ana.int.interval --set ana.activated[+] apron -// These examples were cases were we saw issues of not reaching a fixpoint during development of the octagon domain. Since those issues might +// FIXPOINT: These examples were cases were we saw issues of not reaching a fixpoint during development of the octagon domain. Since those issues might // resurface, these tests without asserts are included int main(int argc, char const *argv[]) { diff --git a/tests/regression/24-octagon/04-previously_problematic_b.c b/tests/regression/24-octagon/04-previously_problematic_b.c index 62649e925d..e9a239f66b 100644 --- a/tests/regression/24-octagon/04-previously_problematic_b.c +++ b/tests/regression/24-octagon/04-previously_problematic_b.c @@ -1,5 +1,5 @@ // SKIP PARAM: --enable ana.int.interval --set ana.activated[+] apron -// These examples were cases were we saw issues of not reaching a fixpoint during development of the octagon domain. Since those issues might +// FIXPOINT: These examples were cases were we saw issues of not reaching a fixpoint during development of the octagon domain. Since those issues might // resurface, these tests without asserts are included typedef int wchar_t; typedef unsigned long size_t; diff --git a/tests/regression/24-octagon/05-previously_problematic_c.c b/tests/regression/24-octagon/05-previously_problematic_c.c index c260646c23..8d08c343ad 100644 --- a/tests/regression/24-octagon/05-previously_problematic_c.c +++ b/tests/regression/24-octagon/05-previously_problematic_c.c @@ -1,5 +1,5 @@ // SKIP PARAM: --enable ana.int.interval --set ana.base.arrays.domain partitioned --set ana.activated[+] apron -// These examples were cases were we saw issues of not reaching a fixpoint during development of the octagon domain. Since those issues might +// FIXPOINT: These examples were cases were we saw issues of not reaching a fixpoint during development of the octagon domain. Since those issues might // resurface, these tests without asserts are included int main(int argc, char const *argv[]) { diff --git a/tests/regression/24-octagon/06-previously_problematic_d.c b/tests/regression/24-octagon/06-previously_problematic_d.c index 733b07d015..cca02f523b 100644 --- a/tests/regression/24-octagon/06-previously_problematic_d.c +++ b/tests/regression/24-octagon/06-previously_problematic_d.c @@ -1,5 +1,5 @@ // SKIP PARAM: --enable ana.int.interval --set ana.activated[+] apron -// These examples were cases were we saw issues of not reaching a fixpoint during development of the octagon domain. Since those issues might +// FIXPOINT: These examples were cases were we saw issues of not reaching a fixpoint during development of the octagon domain. Since those issues might // resurface, these tests without asserts are included int main(int argc, char const *argv[]) { diff --git a/tests/regression/24-octagon/07-previously_problematic_e.c b/tests/regression/24-octagon/07-previously_problematic_e.c index cb9b12cafe..0dee19eb94 100644 --- a/tests/regression/24-octagon/07-previously_problematic_e.c +++ b/tests/regression/24-octagon/07-previously_problematic_e.c @@ -1,5 +1,5 @@ // SKIP PARAM: --enable ana.int.interval --set ana.activated[+] apron -// These examples were cases were we saw issues of not reaching a fixpoint during development of the octagon domain. Since those issues might +// FIXPOINT: These examples were cases were we saw issues of not reaching a fixpoint during development of the octagon domain. Since those issues might // resurface, these tests without asserts are included int main(int argc, char const *argv[]) { diff --git a/tests/regression/24-octagon/08-previously_problematic_f.c b/tests/regression/24-octagon/08-previously_problematic_f.c index 431b8a6c57..93c4d3195f 100644 --- a/tests/regression/24-octagon/08-previously_problematic_f.c +++ b/tests/regression/24-octagon/08-previously_problematic_f.c @@ -1,5 +1,5 @@ // SKIP PARAM: --enable ana.int.interval --set ana.base.arrays.domain partitioned --set ana.activated[+] apron -// These examples were cases were we saw issues of not reaching a fixpoint during development of the octagon domain. Since those issues might +// FIXPOINT: These examples were cases were we saw issues of not reaching a fixpoint during development of the octagon domain. Since those issues might // resurface, these tests without asserts are included int main(int argc, char const *argv[]) { diff --git a/tests/regression/24-octagon/09-previously_problematic_g.c b/tests/regression/24-octagon/09-previously_problematic_g.c index c48184d4dd..910a28703d 100644 --- a/tests/regression/24-octagon/09-previously_problematic_g.c +++ b/tests/regression/24-octagon/09-previously_problematic_g.c @@ -1,5 +1,5 @@ // SKIP PARAM: --enable ana.int.interval --set ana.base.arrays.domain partitioned --set ana.activated[+] apron -// These examples were cases were we saw issues of not reaching a fixpoint during development of the octagon domain. Since those issues might +// FIXPOINT: These examples were cases were we saw issues of not reaching a fixpoint during development of the octagon domain. Since those issues might // resurface, these tests without asserts are included int main(int argc, char const *argv[]) { diff --git a/tests/regression/24-octagon/10-previously_problematic_h.c b/tests/regression/24-octagon/10-previously_problematic_h.c index 969ae965df..ff29145789 100644 --- a/tests/regression/24-octagon/10-previously_problematic_h.c +++ b/tests/regression/24-octagon/10-previously_problematic_h.c @@ -1,5 +1,5 @@ // SKIP PARAM: --enable ana.int.interval --set ana.base.arrays.domain partitioned --set ana.activated[+] apron -// These examples were cases were we saw issues of not reaching a fixpoint during development of the octagon domain. Since those issues might +// FIXPOINT: These examples were cases were we saw issues of not reaching a fixpoint during development of the octagon domain. Since those issues might // resurface, these tests without asserts are included int main(int argc, char const *argv[]) { diff --git a/tests/regression/24-octagon/11-previously_problematic_i.c b/tests/regression/24-octagon/11-previously_problematic_i.c index f1956919d8..517b8e58d2 100644 --- a/tests/regression/24-octagon/11-previously_problematic_i.c +++ b/tests/regression/24-octagon/11-previously_problematic_i.c @@ -1,5 +1,5 @@ // SKIP PARAM: --enable ana.int.interval --set ana.base.arrays.domain partitioned --set ana.activated[+] apron -// These examples were cases were we saw issues of not reaching a fixpoint during development of the octagon domain. Since those issues might +// FIXPOINT: These examples were cases were we saw issues of not reaching a fixpoint during development of the octagon domain. Since those issues might // resurface, these tests without asserts are included char buf2[67]; diff --git a/tests/regression/24-octagon/12-previously_problematic_j.c b/tests/regression/24-octagon/12-previously_problematic_j.c index 208a89d50f..a289e70de3 100644 --- a/tests/regression/24-octagon/12-previously_problematic_j.c +++ b/tests/regression/24-octagon/12-previously_problematic_j.c @@ -1,4 +1,5 @@ // SKIP PARAM: --set ana.activated[+] apron +// NOCHECK void main(void) { int i = 0; int j = i; diff --git a/tests/regression/25-vla/03-calls.c b/tests/regression/25-vla/03-calls.c index 133e89f704..fbe38af248 100644 --- a/tests/regression/25-vla/03-calls.c +++ b/tests/regression/25-vla/03-calls.c @@ -1,5 +1,6 @@ // PARAM: --enable ana.int.interval --disable ana.int.def_exc --set ana.base.arrays.domain partitioned // Variable-sized arrays +// NOCHECK void foo(int n, int a[n]); void foo2(int n, int a[30][n]); void foo3(int n, int a[n][30]); diff --git a/tests/regression/27-inv_invariants/02-bot-during-condition.c b/tests/regression/27-inv_invariants/02-bot-during-condition.c index a94c65bb81..88c5ff2ada 100644 --- a/tests/regression/27-inv_invariants/02-bot-during-condition.c +++ b/tests/regression/27-inv_invariants/02-bot-during-condition.c @@ -1,3 +1,4 @@ +// NOCHECK int main () { int tmp; diff --git a/tests/regression/27-inv_invariants/07-more-bot.c b/tests/regression/27-inv_invariants/07-more-bot.c index e4b677b599..8e5819158e 100644 --- a/tests/regression/27-inv_invariants/07-more-bot.c +++ b/tests/regression/27-inv_invariants/07-more-bot.c @@ -1,5 +1,6 @@ // PARAM: --enable ana.int.interval // Adapted from sv-comp array-programs/partial_mod_count_1.c +// NOCHECK int N = 1000; int main(){ int i; diff --git a/tests/regression/27-inv_invariants/22-mine-tutorial-ex4.4.c b/tests/regression/27-inv_invariants/22-mine-tutorial-ex4.4.c new file mode 100644 index 0000000000..9770d03de7 --- /dev/null +++ b/tests/regression/27-inv_invariants/22-mine-tutorial-ex4.4.c @@ -0,0 +1,38 @@ +// PARAM: --enable ana.int.interval +#include +int main() { + int x, y, z; + __goblint_assume(0 <= x); + __goblint_assume(x <= 10); + __goblint_assume(5 <= y); + __goblint_assume(y <= 15); + __goblint_assume(-10 <= z); + __goblint_assume(z <= 10); + + if (x >= y) { + __goblint_check(5 <= x); + __goblint_check(y <= 10); // why doesn't Miné refine this? + } + + if (z >= x) { + __goblint_check(0 <= z); + } + + if (x >= y && z >= x) { // CIL transform does branches sequentially (good order) + __goblint_check(5 <= x); + __goblint_check(y <= 10); // why doesn't Miné refine this? + __goblint_check(0 <= z); + + __goblint_check(5 <= z); + } + + if (z >= x && x >= y) { // CIL transform does branches sequentially (bad order) + __goblint_check(5 <= x); + __goblint_check(y <= 10); // why doesn't Miné refine this? + __goblint_check(0 <= z); + + __goblint_check(5 <= z); // TODO + } + + return 0; +} diff --git a/tests/regression/27-inv_invariants/22-meet-ptrs.c b/tests/regression/27-inv_invariants/23-meet-ptrs.c similarity index 100% rename from tests/regression/27-inv_invariants/22-meet-ptrs.c rename to tests/regression/27-inv_invariants/23-meet-ptrs.c diff --git a/tests/regression/28-race_reach/95-deref-fun.c b/tests/regression/28-race_reach/95-deref-fun.c index 04cf1a8723..f1431d9221 100644 --- a/tests/regression/28-race_reach/95-deref-fun.c +++ b/tests/regression/28-race_reach/95-deref-fun.c @@ -1,6 +1,6 @@ // PARAM: --enable ana.sv-comp.enabled --set ana.specification "CHECK( init(main()), LTL(G ! call(reach_error())) )" #include - +// NOCRASH void foo(int (*callback)()) { } diff --git a/tests/regression/28-race_reach/96-deref-fun2.c b/tests/regression/28-race_reach/96-deref-fun2.c index 9e76c8604d..cb3aee7535 100644 --- a/tests/regression/28-race_reach/96-deref-fun2.c +++ b/tests/regression/28-race_reach/96-deref-fun2.c @@ -1,6 +1,6 @@ // PARAM: --enable ana.sv-comp.enabled --set ana.specification "CHECK( init(main()), LTL(G ! call(reach_error())) )" #include - +// NOCRASH #define SVCOMP 1 #include diff --git a/tests/regression/29-svcomp/05-isp1362-malloc-fun.c b/tests/regression/29-svcomp/05-isp1362-malloc-fun.c index b8ded7e341..f1c8dece14 100644 --- a/tests/regression/29-svcomp/05-isp1362-malloc-fun.c +++ b/tests/regression/29-svcomp/05-isp1362-malloc-fun.c @@ -1,5 +1,5 @@ // PARAM: --set ana.malloc.wrappers "['ldv_malloc']" - +// NOCRASH #include typedef unsigned long __kernel_ulong_t; diff --git a/tests/regression/29-svcomp/11-arithmetic-bot.c b/tests/regression/29-svcomp/11-arithmetic-bot.c index ce8635b939..d365f14a31 100644 --- a/tests/regression/29-svcomp/11-arithmetic-bot.c +++ b/tests/regression/29-svcomp/11-arithmetic-bot.c @@ -1,5 +1,6 @@ // PARAM: --enable ana.int.interval --enable ana.int.def_exc // from: ldv-linux-3.0/usb_urb-drivers-vhost-vhost_net.ko.cil.out.i +// NOCRASH typedef unsigned long long u64; int main( ) diff --git a/tests/regression/29-svcomp/12-interval-bot.c b/tests/regression/29-svcomp/12-interval-bot.c index 77739efdbd..9d6157875a 100644 --- a/tests/regression/29-svcomp/12-interval-bot.c +++ b/tests/regression/29-svcomp/12-interval-bot.c @@ -1,5 +1,5 @@ // PARAM: --enable ana.int.interval --enable ana.int.def_exc - +// NOCRASH int main(){ unsigned long long a ; diff --git a/tests/regression/29-svcomp/13-comparision-bot.c b/tests/regression/29-svcomp/13-comparision-bot.c index f64fa8a349..843a5fabb4 100644 --- a/tests/regression/29-svcomp/13-comparision-bot.c +++ b/tests/regression/29-svcomp/13-comparision-bot.c @@ -1,4 +1,5 @@ // PARAM: --enable ana.int.interval --enable ana.int.def_exc +// NOCRASH #include int main(){ int a = 0; diff --git a/tests/regression/29-svcomp/14-addition-in-comparision-bot.c b/tests/regression/29-svcomp/14-addition-in-comparision-bot.c index f667ca88f6..c729cf6019 100644 --- a/tests/regression/29-svcomp/14-addition-in-comparision-bot.c +++ b/tests/regression/29-svcomp/14-addition-in-comparision-bot.c @@ -1,4 +1,5 @@ // PARAM: --enable ana.int.interval +// NOCRASH int main() { unsigned int top; diff --git a/tests/regression/29-svcomp/16-atomic_priv.t b/tests/regression/29-svcomp/16-atomic_priv.t new file mode 100644 index 0000000000..1662f881b7 --- /dev/null +++ b/tests/regression/29-svcomp/16-atomic_priv.t @@ -0,0 +1,111 @@ + $ goblint --enable ana.sv-comp.functions --set ana.base.privatization protection-atomic --enable witness.yaml.enabled --set ana.activated[+] mutexGhosts --set witness.yaml.entry-types '["flow_insensitive_invariant", "ghost_instrumentation"]' 16-atomic_priv.c + [Success][Assert] Assertion "myglobal == 5" will succeed (16-atomic_priv.c:12:3-12:33) + [Success][Assert] Assertion "myglobal == 6" will succeed (16-atomic_priv.c:14:3-14:33) + [Success][Assert] Assertion "myglobal == 5" will succeed (16-atomic_priv.c:16:3-16:33) + [Success][Assert] Assertion "myglobal == 5" will succeed (16-atomic_priv.c:24:3-24:33) + [Success][Assert] Assertion "myglobal == 5" will succeed (16-atomic_priv.c:26:3-26:33) + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 17 + dead: 0 + total lines: 17 + [Warning][Race] Memory location myglobal (race with conf. 110): (16-atomic_priv.c:8:5-8:17) + write with [lock:{[__VERIFIER_atomic]}, thread:[main, t_fun@16-atomic_priv.c:23:3-23:40]] (conf. 110) (exp: & myglobal) (16-atomic_priv.c:13:3-13:13) + write with [lock:{[__VERIFIER_atomic]}, thread:[main, t_fun@16-atomic_priv.c:23:3-23:40]] (conf. 110) (exp: & myglobal) (16-atomic_priv.c:15:3-15:13) + read with [mhp:{created={[main, t_fun@16-atomic_priv.c:23:3-23:40]}}, thread:[main]] (conf. 110) (exp: & myglobal) (16-atomic_priv.c:24:3-24:33) + [Info][Witness] witness generation summary: + location invariants: 0 + loop invariants: 0 + flow-insensitive invariants: 1 + total generation entries: 2 + [Info][Race] Memory locations race summary: + safe: 0 + vulnerable: 0 + unsafe: 1 + total memory locations: 1 + + $ yamlWitnessStrip < witness.yml + - entry_type: ghost_instrumentation + content: + ghost_variables: + - name: multithreaded + scope: global + type: int + initial: + value: "0" + format: c_expression + ghost_updates: + - location: + file_name: 16-atomic_priv.c + file_hash: $FILE_HASH + line: 23 + column: 3 + function: main + updates: + - variable: multithreaded + value: "1" + format: c_expression + - entry_type: flow_insensitive_invariant + flow_insensitive_invariant: + string: '! multithreaded || myglobal == 5' + type: assertion + format: C + +Non-atomic privatization: + + $ goblint --enable ana.sv-comp.functions --set ana.base.privatization protection --enable witness.yaml.enabled --set ana.activated[+] mutexGhosts --set witness.yaml.entry-types '["flow_insensitive_invariant", "ghost_instrumentation"]' 16-atomic_priv.c + [Success][Assert] Assertion "myglobal == 5" will succeed (16-atomic_priv.c:12:3-12:33) + [Success][Assert] Assertion "myglobal == 6" will succeed (16-atomic_priv.c:14:3-14:33) + [Success][Assert] Assertion "myglobal == 5" will succeed (16-atomic_priv.c:16:3-16:33) + [Warning][Assert] Assertion "myglobal == 5" is unknown. (16-atomic_priv.c:24:3-24:33) + [Success][Assert] Assertion "myglobal == 5" will succeed (16-atomic_priv.c:26:3-26:33) + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 17 + dead: 0 + total lines: 17 + [Warning][Race] Memory location myglobal (race with conf. 110): (16-atomic_priv.c:8:5-8:17) + write with [lock:{[__VERIFIER_atomic]}, thread:[main, t_fun@16-atomic_priv.c:23:3-23:40]] (conf. 110) (exp: & myglobal) (16-atomic_priv.c:13:3-13:13) + write with [lock:{[__VERIFIER_atomic]}, thread:[main, t_fun@16-atomic_priv.c:23:3-23:40]] (conf. 110) (exp: & myglobal) (16-atomic_priv.c:15:3-15:13) + read with [mhp:{created={[main, t_fun@16-atomic_priv.c:23:3-23:40]}}, thread:[main]] (conf. 110) (exp: & myglobal) (16-atomic_priv.c:24:3-24:33) + [Info][Witness] witness generation summary: + location invariants: 0 + loop invariants: 0 + flow-insensitive invariants: 2 + total generation entries: 3 + [Info][Race] Memory locations race summary: + safe: 0 + vulnerable: 0 + unsafe: 1 + total memory locations: 1 + + $ yamlWitnessStrip < witness.yml + - entry_type: ghost_instrumentation + content: + ghost_variables: + - name: multithreaded + scope: global + type: int + initial: + value: "0" + format: c_expression + ghost_updates: + - location: + file_name: 16-atomic_priv.c + file_hash: $FILE_HASH + line: 23 + column: 3 + function: main + updates: + - variable: multithreaded + value: "1" + format: c_expression + - entry_type: flow_insensitive_invariant + flow_insensitive_invariant: + string: '! multithreaded || myglobal == 5' + type: assertion + format: C + - entry_type: flow_insensitive_invariant + flow_insensitive_invariant: + string: '! multithreaded || ((0 <= myglobal && myglobal <= 127) && myglobal != + 0)' + type: assertion + format: C diff --git a/tests/regression/29-svcomp/19-problematic.c b/tests/regression/29-svcomp/19-problematic.c index 63b4fc7af0..99c7c01197 100644 --- a/tests/regression/29-svcomp/19-problematic.c +++ b/tests/regression/29-svcomp/19-problematic.c @@ -1,5 +1,6 @@ // PARAM: --enable ana.sv-comp.functions // Adapted from: https://github.com/sosy-lab/sv-benchmarks/blob/master/c/ldv-regression/test27-2.c +// NOTIMEOUT extern int __VERIFIER_nondet_int(void); struct dummy { diff --git a/tests/regression/29-svcomp/21-issue-casting.c b/tests/regression/29-svcomp/21-issue-casting.c index e02989559c..35d81d4898 100644 --- a/tests/regression/29-svcomp/21-issue-casting.c +++ b/tests/regression/29-svcomp/21-issue-casting.c @@ -1,6 +1,7 @@ // PARAM: --set ana.activated ["'base'","'mallocWrapper'"] --set ana.base.privatization none // minimal analyses to reveal bug // none privatization because mutex deactivated +// NOTIMEOUT static long main(void) { unsigned int cmd; diff --git a/tests/regression/29-svcomp/25-writing-into-char-array.c b/tests/regression/29-svcomp/25-writing-into-char-array.c index 8894007d23..ce8fea40f4 100644 --- a/tests/regression/29-svcomp/25-writing-into-char-array.c +++ b/tests/regression/29-svcomp/25-writing-into-char-array.c @@ -1,3 +1,4 @@ +// NOCHECK #include struct __anonstruct_mbox_t_232 { int one; diff --git a/tests/regression/29-svcomp/26-ikinds_if.c b/tests/regression/29-svcomp/26-ikinds_if.c index b902659f47..a8f726353f 100644 --- a/tests/regression/29-svcomp/26-ikinds_if.c +++ b/tests/regression/29-svcomp/26-ikinds_if.c @@ -1,4 +1,5 @@ //PARAM: --enable ana.int.interval +// NOCHECK #include static long sound_ioctl(unsigned int cmd , unsigned long arg ) diff --git a/tests/regression/29-svcomp/29-witness.c b/tests/regression/29-svcomp/29-witness.c index 72d1c4435a..b56b47a74c 100644 --- a/tests/regression/29-svcomp/29-witness.c +++ b/tests/regression/29-svcomp/29-witness.c @@ -1,4 +1,5 @@ // PARAM: --enable ana.sv-comp.enabled --set ana.specification "CHECK( init(main()), LTL(G ! call(reach_error())) )" +// NOCRASH #include #include diff --git a/tests/regression/29-svcomp/32-no-ov.c b/tests/regression/29-svcomp/32-no-ov.c index 0167098c29..3612b34459 100644 --- a/tests/regression/29-svcomp/32-no-ov.c +++ b/tests/regression/29-svcomp/32-no-ov.c @@ -1,5 +1,5 @@ // PARAM: --enable ana.int.interval --enable ana.sv-comp.enabled --enable ana.sv-comp.functions --set ana.specification "CHECK( init(main()), LTL(G ! overflow) )" - +// CRAM int main(){ // This is not an overflow, just implementation defined behavior on a cast int data = ((int)(rand() & 1 ? (((unsigned)rand()<<30) ^ ((unsigned)rand()<<15) ^ rand()) : -(((unsigned)rand()<<30) ^ ((unsigned)rand()<<15) ^ rand()) - 1)); diff --git a/tests/regression/29-svcomp/32-no-ov.t b/tests/regression/29-svcomp/32-no-ov.t index c58099b5e4..72978a1fac 100644 --- a/tests/regression/29-svcomp/32-no-ov.t +++ b/tests/regression/29-svcomp/32-no-ov.t @@ -1,4 +1,5 @@ $ goblint --enable ana.int.interval --enable ana.sv-comp.enabled --enable ana.sv-comp.functions --set ana.specification "CHECK( init(main()), LTL(G ! overflow) )" 32-no-ov.c + [Info] Setting "ana.int.interval" to true [Info] SV-COMP specification: CHECK( init(main()), LTL(G ! overflow) ) [Warning][Integer > Overflow][CWE-190] Unsigned integer overflow (32-no-ov.c:5:6-5:159) [Warning][Integer > Overflow][CWE-190] Unsigned integer overflow (32-no-ov.c:5:6-5:159) diff --git a/tests/regression/29-svcomp/35-nla-sqrt.c b/tests/regression/29-svcomp/35-nla-sqrt.c new file mode 100644 index 0000000000..dc382181cc --- /dev/null +++ b/tests/regression/29-svcomp/35-nla-sqrt.c @@ -0,0 +1,18 @@ +// SKIP PARAM: --enable ana.sv-comp.functions --enable ana.autotune.enabled --set ana.autotune.activated[+] octagon +// Extracted from: nla-digbench-scaling/sqrt1-ll_unwindbound5.c +#include +extern int __VERIFIER_nondet_int(void); + +int main() { + int n; + long long s; + n = __VERIFIER_nondet_int(); + + if (!(s <= n)) { + __goblint_check(s > n); + } else { + __goblint_check(s <= 2147483647); + } + + return 0; +} diff --git a/tests/regression/29-svcomp/36-svcomp-arch.c b/tests/regression/29-svcomp/36-svcomp-arch.c new file mode 100644 index 0000000000..ea68ba187c --- /dev/null +++ b/tests/regression/29-svcomp/36-svcomp-arch.c @@ -0,0 +1,8 @@ +// CRAM +#include + +int main() { + long k = INT_MAX; + long n = k * k; + return 0; +} diff --git a/tests/regression/29-svcomp/36-svcomp-arch.t b/tests/regression/29-svcomp/36-svcomp-arch.t new file mode 100644 index 0000000000..a0715e3872 --- /dev/null +++ b/tests/regression/29-svcomp/36-svcomp-arch.t @@ -0,0 +1,22 @@ +There should be overflow on ILP32: + + $ goblint --enable ana.sv-comp.enabled --set ana.specification "CHECK( init(main()), LTL(G ! overflow) )" --set exp.architecture 32bit 36-svcomp-arch.c + [Info] Setting "ana.int.interval" to true + [Info] SV-COMP specification: CHECK( init(main()), LTL(G ! overflow) ) + [Warning][Integer > Overflow][CWE-190] Signed integer overflow (36-svcomp-arch.c:6:8-6:17) + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 4 + dead: 0 + total lines: 4 + SV-COMP result: unknown + +There shouldn't be an overflow on LP64: + + $ goblint --enable ana.sv-comp.enabled --set ana.specification "CHECK( init(main()), LTL(G ! overflow) )" --set exp.architecture 64bit 36-svcomp-arch.c + [Info] Setting "ana.int.interval" to true + [Info] SV-COMP specification: CHECK( init(main()), LTL(G ! overflow) ) + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 4 + dead: 0 + total lines: 4 + SV-COMP result: true diff --git a/tests/regression/29-svcomp/dune b/tests/regression/29-svcomp/dune index 23c0dd3290..9b2396b313 100644 --- a/tests/regression/29-svcomp/dune +++ b/tests/regression/29-svcomp/dune @@ -1,2 +1,20 @@ +(rule + (aliases runtest runaprontest) + (enabled_if %{lib-available:apron}) + (deps + (package goblint) + ../../../goblint ; update_suite calls local goblint + (:update_suite ../../../scripts/update_suite.rb) + (glob_files ??-*.c)) + (locks /update_suite) + (action + (chdir ../../.. + (progn + (run %{update_suite} nla-sqrt -q))))) + (cram (deps (glob_files *.c))) + +(cram + (applies_to 36-svcomp-arch) + (enabled_if %{read:../../util/multilibAvailable})) ; https://dune.readthedocs.io/en/stable/reference/boolean-language.html diff --git a/tests/regression/31-ikind-aware-ints/03-lnot.c b/tests/regression/31-ikind-aware-ints/03-lnot.c index 2fb6d79c61..6d96d88dea 100644 --- a/tests/regression/31-ikind-aware-ints/03-lnot.c +++ b/tests/regression/31-ikind-aware-ints/03-lnot.c @@ -1,4 +1,5 @@ // PARAM: --enable ana.int.interval +// NOCRASH int main() { unsigned int l = 0; diff --git a/tests/regression/31-ikind-aware-ints/04-ptrdiff.c b/tests/regression/31-ikind-aware-ints/04-ptrdiff.c index 4be48c7793..be658b3cd0 100644 --- a/tests/regression/31-ikind-aware-ints/04-ptrdiff.c +++ b/tests/regression/31-ikind-aware-ints/04-ptrdiff.c @@ -1,4 +1,5 @@ // PARAM: --enable ana.int.interval --set ana.base.arrays.domain partitioned --set ana.activated[+] var_eq +// NOCRASH int *tmp; int main () diff --git a/tests/regression/31-ikind-aware-ints/05-shift.c b/tests/regression/31-ikind-aware-ints/05-shift.c index a0cf2182d8..87bfba5a0b 100644 --- a/tests/regression/31-ikind-aware-ints/05-shift.c +++ b/tests/regression/31-ikind-aware-ints/05-shift.c @@ -1,6 +1,6 @@ // PARAM: --enable ana.int.interval int main(void) { - // Shifting by a negative number is UB, but we should still not crash on it, but go to top instead + // NOCRASH: Shifting by a negative number is UB, but we should still not crash on it, but go to top instead int v = -1; int r = 17; int u = r >> v; diff --git a/tests/regression/31-ikind-aware-ints/06-structs.c b/tests/regression/31-ikind-aware-ints/06-structs.c index ddf3b51cf0..d3c944c4cd 100644 --- a/tests/regression/31-ikind-aware-ints/06-structs.c +++ b/tests/regression/31-ikind-aware-ints/06-structs.c @@ -1,4 +1,5 @@ // PARAM: --enable ana.int.interval +// NOCRASH struct rtl8169_private { unsigned int features ; }; diff --git a/tests/regression/31-ikind-aware-ints/15-strange.c b/tests/regression/31-ikind-aware-ints/15-strange.c index d063b3c6e2..550baf0e1d 100644 --- a/tests/regression/31-ikind-aware-ints/15-strange.c +++ b/tests/regression/31-ikind-aware-ints/15-strange.c @@ -1,7 +1,7 @@ //PARAM: --disable ana.int.interval --disable ana.int.def_exc --enable ana.int.enums int main (int argc, char* argv[]) { - // This used to cause an exception because of incompatible ikinds + // NOCRASH: This used to cause an exception because of incompatible ikinds // See https://github.com/goblint/cil/issues/29 signed char f2 = 7; signed char l_1857 = ((0xFFBD4A17L && f2) | f2); diff --git a/tests/regression/31-ikind-aware-ints/17-def-enum-refine.c b/tests/regression/31-ikind-aware-ints/17-def-enum-refine.c index e34d2d1c79..fd3a75154e 100644 --- a/tests/regression/31-ikind-aware-ints/17-def-enum-refine.c +++ b/tests/regression/31-ikind-aware-ints/17-def-enum-refine.c @@ -4,7 +4,7 @@ int main() { _Bool c; if(c) { x--;} else { x--;} - // The veryfier claimed that the fixed-point was not reached here due to a bug in Enums.leq + // FIXPOINT: The veryfier claimed that the fixed-point was not reached here due to a bug in Enums.leq // The leq wrongly returned false for the Enums {0} and not{}[0,1] return 0; } diff --git a/tests/regression/33-constants/01-const.c b/tests/regression/33-constants/01-const.c index 8db984a4b3..436a389545 100644 --- a/tests/regression/33-constants/01-const.c +++ b/tests/regression/33-constants/01-const.c @@ -1,5 +1,6 @@ //PARAM: --set ana.activated '["constants"]' // intentional explicit ana.activated to do tutorial in isolation +// NOCHECK int f(int a, int b){ int d = 3; int z = a + d; diff --git a/tests/regression/33-constants/02-simple.c b/tests/regression/33-constants/02-simple.c index aa370aa4da..75cd33ee8a 100644 --- a/tests/regression/33-constants/02-simple.c +++ b/tests/regression/33-constants/02-simple.c @@ -1,6 +1,6 @@ //PARAM: --set ana.activated '["constants"]' // intentional explicit ana.activated to do tutorial in isolation - +// NOCHECK int main(){ int x = 3; int y = 4; diff --git a/tests/regression/33-constants/03-empty-not-dead-branch.c b/tests/regression/33-constants/03-empty-not-dead-branch.c index 2b6d662589..2bd6a10190 100644 --- a/tests/regression/33-constants/03-empty-not-dead-branch.c +++ b/tests/regression/33-constants/03-empty-not-dead-branch.c @@ -1,6 +1,6 @@ //PARAM: --set ana.activated '["constants"]' // intentional explicit ana.activated to do tutorial in isolation - +// NOCHECK int g; int main() { diff --git a/tests/regression/33-constants/04-empty-not-dead.c b/tests/regression/33-constants/04-empty-not-dead.c index 8ad4199aa8..377a066370 100644 --- a/tests/regression/33-constants/04-empty-not-dead.c +++ b/tests/regression/33-constants/04-empty-not-dead.c @@ -1,6 +1,6 @@ //PARAM: --set ana.activated '["constants"]' // intentional explicit ana.activated to do tutorial in isolation - +// NOCHECK int g; int main() { diff --git a/tests/regression/33-constants/05-fun_ptranal.c b/tests/regression/33-constants/05-fun_ptranal.c index 5ebaf24e22..bb8f2146e5 100644 --- a/tests/regression/33-constants/05-fun_ptranal.c +++ b/tests/regression/33-constants/05-fun_ptranal.c @@ -1,5 +1,6 @@ //PARAM: --set ana.activated '["constants", "ptranal"]' // intentional explicit ana.activated to do tutorial in isolation +// NOCHECK int f(int a, int b){ int d = 3; int z = a + d; diff --git a/tests/regression/34-localwn_restart/05-nested.w.counter.c b/tests/regression/34-localwn_restart/05-nested.w.counter.c index 2d0cca1853..7cc7c6ce59 100644 --- a/tests/regression/34-localwn_restart/05-nested.w.counter.c +++ b/tests/regression/34-localwn_restart/05-nested.w.counter.c @@ -1,4 +1,5 @@ // Variant of nested.c with a counter. +// NOCHECK void main() { int z = 0; diff --git a/tests/regression/35-marshaling/01-disable_hashcons.c b/tests/regression/35-marshaling/01-disable_hashcons.c index f6a72bd685..5be9e47819 100644 --- a/tests/regression/35-marshaling/01-disable_hashcons.c +++ b/tests/regression/35-marshaling/01-disable_hashcons.c @@ -1,2 +1,3 @@ // PARAM: --disable ana.opt.hashcons +// NOCRASH int main(void) { return 0; } diff --git a/tests/regression/36-apron/12-traces-min-rpb1.t b/tests/regression/36-apron/12-traces-min-rpb1.t new file mode 100644 index 0000000000..2a760c0dcb --- /dev/null +++ b/tests/regression/36-apron/12-traces-min-rpb1.t @@ -0,0 +1,179 @@ + $ goblint --enable witness.yaml.enabled --set witness.yaml.entry-types '["location_invariant"]' --disable witness.invariant.other --disable ana.base.invariant.enabled --set ana.relation.privatization mutex-meet --set ana.activated[+] apron --enable ana.sv-comp.functions --set ana.apron.domain polyhedra 12-traces-min-rpb1.c + [Success][Assert] Assertion "g == h" will succeed (12-traces-min-rpb1.c:16:3-16:26) + [Warning][Assert] Assertion "g == h" is unknown. (12-traces-min-rpb1.c:27:3-27:26) + [Success][Assert] Assertion "g == h" will succeed (12-traces-min-rpb1.c:29:3-29:26) + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 18 + dead: 0 + total lines: 18 + [Warning][Race] Memory location h (race with conf. 110): (12-traces-min-rpb1.c:8:5-8:10) + write with [lock:{A}, thread:[main, t_fun@12-traces-min-rpb1.c:25:3-25:40]] (conf. 110) (exp: & h) (12-traces-min-rpb1.c:15:3-15:8) + read with [mhp:{created={[main, t_fun@12-traces-min-rpb1.c:25:3-25:40]}}, thread:[main]] (conf. 110) (exp: & h) (12-traces-min-rpb1.c:27:3-27:26) + [Warning][Race] Memory location g (race with conf. 110): (12-traces-min-rpb1.c:7:5-7:10) + write with [lock:{A}, thread:[main, t_fun@12-traces-min-rpb1.c:25:3-25:40]] (conf. 110) (exp: & g) (12-traces-min-rpb1.c:14:3-14:8) + read with [mhp:{created={[main, t_fun@12-traces-min-rpb1.c:25:3-25:40]}}, thread:[main]] (conf. 110) (exp: & g) (12-traces-min-rpb1.c:27:3-27:26) + [Info][Witness] witness generation summary: + location invariants: 3 + loop invariants: 0 + flow-insensitive invariants: 0 + total generation entries: 3 + [Info][Race] Memory locations race summary: + safe: 0 + vulnerable: 0 + unsafe: 2 + total memory locations: 2 + + $ yamlWitnessStrip < witness.yml + - entry_type: location_invariant + location: + file_name: 12-traces-min-rpb1.c + file_hash: $FILE_HASH + line: 29 + column: 3 + function: main + location_invariant: + string: (0LL - (long long )g) + (long long )h == 0LL + type: assertion + format: C + - entry_type: location_invariant + location: + file_name: 12-traces-min-rpb1.c + file_hash: $FILE_HASH + line: 19 + column: 3 + function: t_fun + location_invariant: + string: (0LL - (long long )g) + (long long )h == 0LL + type: assertion + format: C + - entry_type: location_invariant + location: + file_name: 12-traces-min-rpb1.c + file_hash: $FILE_HASH + line: 14 + column: 3 + function: t_fun + location_invariant: + string: (0LL - (long long )g) + (long long )h == 0LL + type: assertion + format: C + + + $ goblint --enable warn.deterministic --set ana.activated[+] apron --set ana.path_sens[+] threadflag --enable ana.sv-comp.functions --enable witness.yaml.enabled --set ana.activated[+] mutexGhosts --set witness.yaml.entry-types '["flow_insensitive_invariant", "ghost_instrumentation"]' 12-traces-min-rpb1.c --enable ana.apron.invariant.diff-box + [Warning][Assert] Assertion "g == h" is unknown. (12-traces-min-rpb1.c:27:3-27:26) + [Success][Assert] Assertion "g == h" will succeed (12-traces-min-rpb1.c:16:3-16:26) + [Success][Assert] Assertion "g == h" will succeed (12-traces-min-rpb1.c:29:3-29:26) + [Warning][Race] Memory location g (race with conf. 110): (12-traces-min-rpb1.c:7:5-7:10) + write with [lock:{A}, thread:[main, t_fun@12-traces-min-rpb1.c:25:3-25:40]] (conf. 110) (exp: & g) (12-traces-min-rpb1.c:14:3-14:8) + read with [mhp:{created={[main, t_fun@12-traces-min-rpb1.c:25:3-25:40]}}, thread:[main]] (conf. 110) (exp: & g) (12-traces-min-rpb1.c:27:3-27:26) + [Warning][Race] Memory location h (race with conf. 110): (12-traces-min-rpb1.c:8:5-8:10) + write with [lock:{A}, thread:[main, t_fun@12-traces-min-rpb1.c:25:3-25:40]] (conf. 110) (exp: & h) (12-traces-min-rpb1.c:15:3-15:8) + read with [mhp:{created={[main, t_fun@12-traces-min-rpb1.c:25:3-25:40]}}, thread:[main]] (conf. 110) (exp: & h) (12-traces-min-rpb1.c:27:3-27:26) + [Info][Race] Memory locations race summary: + safe: 0 + vulnerable: 0 + unsafe: 2 + total memory locations: 2 + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 18 + dead: 0 + total lines: 18 + [Info][Witness] witness generation summary: + location invariants: 0 + loop invariants: 0 + flow-insensitive invariants: 1 + total generation entries: 2 + + $ yamlWitnessStrip < witness.yml + - entry_type: ghost_instrumentation + content: + ghost_variables: + - name: A_locked + scope: global + type: int + initial: + value: "0" + format: c_expression + - name: multithreaded + scope: global + type: int + initial: + value: "0" + format: c_expression + ghost_updates: + - location: + file_name: 12-traces-min-rpb1.c + file_hash: $FILE_HASH + line: 13 + column: 3 + function: t_fun + updates: + - variable: A_locked + value: "1" + format: c_expression + - location: + file_name: 12-traces-min-rpb1.c + file_hash: $FILE_HASH + line: 17 + column: 3 + function: t_fun + updates: + - variable: A_locked + value: "0" + format: c_expression + - location: + file_name: 12-traces-min-rpb1.c + file_hash: $FILE_HASH + line: 18 + column: 3 + function: t_fun + updates: + - variable: A_locked + value: "1" + format: c_expression + - location: + file_name: 12-traces-min-rpb1.c + file_hash: $FILE_HASH + line: 19 + column: 3 + function: t_fun + updates: + - variable: A_locked + value: "0" + format: c_expression + - location: + file_name: 12-traces-min-rpb1.c + file_hash: $FILE_HASH + line: 25 + column: 3 + function: main + updates: + - variable: multithreaded + value: "1" + format: c_expression + - location: + file_name: 12-traces-min-rpb1.c + file_hash: $FILE_HASH + line: 28 + column: 3 + function: main + updates: + - variable: A_locked + value: "1" + format: c_expression + - location: + file_name: 12-traces-min-rpb1.c + file_hash: $FILE_HASH + line: 30 + column: 3 + function: main + updates: + - variable: A_locked + value: "0" + format: c_expression + - entry_type: flow_insensitive_invariant + flow_insensitive_invariant: + string: '! multithreaded || (A_locked || ((0LL - (long long )g) + (long long )h + >= 0LL && (long long )g - (long long )h >= 0LL))' + type: assertion + format: C diff --git a/tests/regression/36-apron/45-context.c b/tests/regression/36-apron/45-context.c index 94328af97f..eda945abd5 100644 --- a/tests/regression/36-apron/45-context.c +++ b/tests/regression/36-apron/45-context.c @@ -1,4 +1,4 @@ -// SKIP PARAM: --set ana.activated[+] apron --set ana.path_sens[+] threadflag --enable ana.int.interval --enable ana.relation.context +// SKIP PARAM: --enable ana.sv-comp.functions --set ana.activated[+] apron --set ana.path_sens[+] threadflag --enable ana.int.interval --enable ana.relation.context extern int __VERIFIER_nondet_int(); #include diff --git a/tests/regression/36-apron/46-no-context.c b/tests/regression/36-apron/46-no-context.c index bf115cee24..640784b913 100644 --- a/tests/regression/36-apron/46-no-context.c +++ b/tests/regression/36-apron/46-no-context.c @@ -1,4 +1,4 @@ -// SKIP PARAM: --set ana.activated[+] apron --set ana.path_sens[+] threadflag --enable ana.int.interval --disable ana.relation.context +// SKIP PARAM: --enable ana.sv-comp.functions --set ana.activated[+] apron --set ana.path_sens[+] threadflag --enable ana.int.interval --disable ana.relation.context extern int __VERIFIER_nondet_int(); #include diff --git a/tests/regression/36-apron/47-no-context-attribute.c b/tests/regression/36-apron/47-no-context-attribute.c index 90b58cdc28..cf111f5ffc 100644 --- a/tests/regression/36-apron/47-no-context-attribute.c +++ b/tests/regression/36-apron/47-no-context-attribute.c @@ -1,4 +1,4 @@ -// SKIP PARAM: --set ana.activated[+] apron --set ana.path_sens[+] threadflag --enable ana.int.interval --enable ana.relation.context +// SKIP PARAM: --enable ana.sv-comp.functions --set ana.activated[+] apron --set ana.path_sens[+] threadflag --enable ana.int.interval --enable ana.relation.context extern int __VERIFIER_nondet_int(); #include diff --git a/tests/regression/36-apron/48-context-attribute.c b/tests/regression/36-apron/48-context-attribute.c index 5e5ecf01fe..3304c20388 100644 --- a/tests/regression/36-apron/48-context-attribute.c +++ b/tests/regression/36-apron/48-context-attribute.c @@ -1,4 +1,4 @@ -// SKIP PARAM: --set ana.activated[+] apron --set ana.path_sens[+] threadflag --enable ana.int.interval --disable ana.relation.context +// SKIP PARAM: --enable ana.sv-comp.functions --set ana.activated[+] apron --set ana.path_sens[+] threadflag --enable ana.int.interval --disable ana.relation.context extern int __VERIFIER_nondet_int(); #include diff --git a/tests/regression/36-apron/52-queuesize.t b/tests/regression/36-apron/52-queuesize.t new file mode 100644 index 0000000000..f0a977891a --- /dev/null +++ b/tests/regression/36-apron/52-queuesize.t @@ -0,0 +1,287 @@ +`ana.apron.invariant.diff-box` test case from https://github.com/goblint/analyzer/pull/762. + +Without diff-box: + + $ goblint --enable witness.yaml.enabled --set witness.yaml.entry-types '["location_invariant"]' --disable witness.invariant.other --disable ana.base.invariant.enabled --set ana.relation.privatization mutex-meet --set ana.activated[+] apron --enable ana.sv-comp.functions --set ana.apron.domain polyhedra --enable ana.relation.invariant.one-var --disable ana.apron.invariant.diff-box 52-queuesize.c + [Success][Assert] Assertion "free >= 0" will succeed (52-queuesize.c:67:5-67:31) + [Success][Assert] Assertion "free <= capacity" will succeed (52-queuesize.c:68:5-68:38) + [Success][Assert] Assertion "used >= 0" will succeed (52-queuesize.c:69:5-69:31) + [Success][Assert] Assertion "used <= capacity" will succeed (52-queuesize.c:70:5-70:38) + [Success][Assert] Assertion "used + free == capacity" will succeed (52-queuesize.c:71:5-71:45) + [Success][Assert] Assertion "free >= 0" will succeed (52-queuesize.c:15:3-15:29) + [Success][Assert] Assertion "free <= capacity" will succeed (52-queuesize.c:16:3-16:36) + [Success][Assert] Assertion "used >= 0" will succeed (52-queuesize.c:17:3-17:29) + [Success][Assert] Assertion "used <= capacity" will succeed (52-queuesize.c:18:3-18:36) + [Success][Assert] Assertion "used + free == capacity" will succeed (52-queuesize.c:19:3-19:43) + [Success][Assert] Assertion "free >= 0" will succeed (52-queuesize.c:26:3-26:29) + [Success][Assert] Assertion "free <= capacity" will succeed (52-queuesize.c:27:3-27:36) + [Success][Assert] Assertion "used >= 0" will succeed (52-queuesize.c:28:3-28:29) + [Success][Assert] Assertion "used <= capacity" will succeed (52-queuesize.c:29:3-29:36) + [Success][Assert] Assertion "used + free == capacity" will succeed (52-queuesize.c:30:3-30:43) + [Success][Assert] Assertion "free >= 0" will succeed (52-queuesize.c:36:3-36:29) + [Success][Assert] Assertion "free <= capacity" will succeed (52-queuesize.c:37:3-37:36) + [Success][Assert] Assertion "used >= 0" will succeed (52-queuesize.c:38:3-38:29) + [Success][Assert] Assertion "used <= capacity" will succeed (52-queuesize.c:39:3-39:36) + [Success][Assert] Assertion "used + free == capacity" will succeed (52-queuesize.c:40:3-40:43) + [Success][Assert] Assertion "free >= 0" will succeed (52-queuesize.c:47:3-47:29) + [Success][Assert] Assertion "free <= capacity" will succeed (52-queuesize.c:48:3-48:36) + [Success][Assert] Assertion "used >= 0" will succeed (52-queuesize.c:49:3-49:29) + [Success][Assert] Assertion "used <= capacity" will succeed (52-queuesize.c:50:3-50:36) + [Success][Assert] Assertion "used + free == capacity" will succeed (52-queuesize.c:51:3-51:43) + [Warning][Deadcode] Function 'worker' has dead code: + on line 58 (52-queuesize.c:58-58) + [Warning][Deadcode] Logical lines of code (LLoC) summary: + live: 53 + dead: 1 + total lines: 54 + [Warning][Deadcode][CWE-571] condition '1' (possibly inserted by CIL) is always true (52-queuesize.c:56:10-56:11) + [Warning][Deadcode][CWE-571] condition '1' (possibly inserted by CIL) is always true (52-queuesize.c:78:12-78:13) + [Info][Witness] witness generation summary: + location invariants: 8 + loop invariants: 0 + flow-insensitive invariants: 0 + total generation entries: 8 + [Info][Race] Memory locations race summary: + safe: 3 + vulnerable: 0 + unsafe: 0 + total memory locations: 3 + + $ yamlWitnessStrip < witness.yml | tee witness-disable-diff-box.yml + - entry_type: location_invariant + location: + file_name: 52-queuesize.c + file_hash: $FILE_HASH + line: 36 + column: 3 + function: push + location_invariant: + string: 2147483647LL - (long long )capacity >= 0LL + type: assertion + format: C + - entry_type: location_invariant + location: + file_name: 52-queuesize.c + file_hash: $FILE_HASH + line: 36 + column: 3 + function: push + location_invariant: + string: (long long )free >= 0LL + type: assertion + format: C + - entry_type: location_invariant + location: + file_name: 52-queuesize.c + file_hash: $FILE_HASH + line: 36 + column: 3 + function: push + location_invariant: + string: (long long )capacity - (long long )free >= 0LL + type: assertion + format: C + - entry_type: location_invariant + location: + file_name: 52-queuesize.c + file_hash: $FILE_HASH + line: 36 + column: 3 + function: push + location_invariant: + string: ((0LL - (long long )capacity) + (long long )free) + (long long )used == + 0LL + type: assertion + format: C + - entry_type: location_invariant + location: + file_name: 52-queuesize.c + file_hash: $FILE_HASH + line: 15 + column: 3 + function: pop + location_invariant: + string: 2147483647LL - (long long )capacity >= 0LL + type: assertion + format: C + - entry_type: location_invariant + location: + file_name: 52-queuesize.c + file_hash: $FILE_HASH + line: 15 + column: 3 + function: pop + location_invariant: + string: (long long )free >= 0LL + type: assertion + format: C + - entry_type: location_invariant + location: + file_name: 52-queuesize.c + file_hash: $FILE_HASH + line: 15 + column: 3 + function: pop + location_invariant: + string: (long long )capacity - (long long )free >= 0LL + type: assertion + format: C + - entry_type: location_invariant + location: + file_name: 52-queuesize.c + file_hash: $FILE_HASH + line: 15 + column: 3 + function: pop + location_invariant: + string: ((0LL - (long long )capacity) + (long long )free) + (long long )used == + 0LL + type: assertion + format: C + +With diff-box: + + $ goblint --enable witness.yaml.enabled --set witness.yaml.entry-types '["location_invariant"]' --disable witness.invariant.other --disable ana.base.invariant.enabled --set ana.relation.privatization mutex-meet --set ana.activated[+] apron --enable ana.sv-comp.functions --set ana.apron.domain polyhedra --enable ana.relation.invariant.one-var --enable ana.apron.invariant.diff-box 52-queuesize.c + [Success][Assert] Assertion "free >= 0" will succeed (52-queuesize.c:67:5-67:31) + [Success][Assert] Assertion "free <= capacity" will succeed (52-queuesize.c:68:5-68:38) + [Success][Assert] Assertion "used >= 0" will succeed (52-queuesize.c:69:5-69:31) + [Success][Assert] Assertion "used <= capacity" will succeed (52-queuesize.c:70:5-70:38) + [Success][Assert] Assertion "used + free == capacity" will succeed (52-queuesize.c:71:5-71:45) + [Success][Assert] Assertion "free >= 0" will succeed (52-queuesize.c:15:3-15:29) + [Success][Assert] Assertion "free <= capacity" will succeed (52-queuesize.c:16:3-16:36) + [Success][Assert] Assertion "used >= 0" will succeed (52-queuesize.c:17:3-17:29) + [Success][Assert] Assertion "used <= capacity" will succeed (52-queuesize.c:18:3-18:36) + [Success][Assert] Assertion "used + free == capacity" will succeed (52-queuesize.c:19:3-19:43) + [Success][Assert] Assertion "free >= 0" will succeed (52-queuesize.c:26:3-26:29) + [Success][Assert] Assertion "free <= capacity" will succeed (52-queuesize.c:27:3-27:36) + [Success][Assert] Assertion "used >= 0" will succeed (52-queuesize.c:28:3-28:29) + [Success][Assert] Assertion "used <= capacity" will succeed (52-queuesize.c:29:3-29:36) + [Success][Assert] Assertion "used + free == capacity" will succeed (52-queuesize.c:30:3-30:43) + [Success][Assert] Assertion "free >= 0" will succeed (52-queuesize.c:36:3-36:29) + [Success][Assert] Assertion "free <= capacity" will succeed (52-queuesize.c:37:3-37:36) + [Success][Assert] Assertion "used >= 0" will succeed (52-queuesize.c:38:3-38:29) + [Success][Assert] Assertion "used <= capacity" will succeed (52-queuesize.c:39:3-39:36) + [Success][Assert] Assertion "used + free == capacity" will succeed (52-queuesize.c:40:3-40:43) + [Success][Assert] Assertion "free >= 0" will succeed (52-queuesize.c:47:3-47:29) + [Success][Assert] Assertion "free <= capacity" will succeed (52-queuesize.c:48:3-48:36) + [Success][Assert] Assertion "used >= 0" will succeed (52-queuesize.c:49:3-49:29) + [Success][Assert] Assertion "used <= capacity" will succeed (52-queuesize.c:50:3-50:36) + [Success][Assert] Assertion "used + free == capacity" will succeed (52-queuesize.c:51:3-51:43) + [Warning][Deadcode] Function 'worker' has dead code: + on line 58 (52-queuesize.c:58-58) + [Warning][Deadcode] Logical lines of code (LLoC) summary: + live: 53 + dead: 1 + total lines: 54 + [Warning][Deadcode][CWE-571] condition '1' (possibly inserted by CIL) is always true (52-queuesize.c:56:10-56:11) + [Warning][Deadcode][CWE-571] condition '1' (possibly inserted by CIL) is always true (52-queuesize.c:78:12-78:13) + [Info][Witness] witness generation summary: + location invariants: 6 + loop invariants: 0 + flow-insensitive invariants: 0 + total generation entries: 6 + [Info][Race] Memory locations race summary: + safe: 3 + vulnerable: 0 + unsafe: 0 + total memory locations: 3 + + $ yamlWitnessStrip < witness.yml | tee witness-enable-diff-box.yml + - entry_type: location_invariant + location: + file_name: 52-queuesize.c + file_hash: $FILE_HASH + line: 36 + column: 3 + function: push + location_invariant: + string: (long long )free >= 0LL + type: assertion + format: C + - entry_type: location_invariant + location: + file_name: 52-queuesize.c + file_hash: $FILE_HASH + line: 36 + column: 3 + function: push + location_invariant: + string: (long long )capacity - (long long )free >= 0LL + type: assertion + format: C + - entry_type: location_invariant + location: + file_name: 52-queuesize.c + file_hash: $FILE_HASH + line: 36 + column: 3 + function: push + location_invariant: + string: ((0LL - (long long )capacity) + (long long )free) + (long long )used == + 0LL + type: assertion + format: C + - entry_type: location_invariant + location: + file_name: 52-queuesize.c + file_hash: $FILE_HASH + line: 15 + column: 3 + function: pop + location_invariant: + string: (long long )free >= 0LL + type: assertion + format: C + - entry_type: location_invariant + location: + file_name: 52-queuesize.c + file_hash: $FILE_HASH + line: 15 + column: 3 + function: pop + location_invariant: + string: (long long )capacity - (long long )free >= 0LL + type: assertion + format: C + - entry_type: location_invariant + location: + file_name: 52-queuesize.c + file_hash: $FILE_HASH + line: 15 + column: 3 + function: pop + location_invariant: + string: ((0LL - (long long )capacity) + (long long )free) + (long long )used == + 0LL + type: assertion + format: C + +Compare witnesses: + + $ yamlWitnessStripDiff witness-disable-diff-box.yml witness-enable-diff-box.yml + # Left-only entries: + - entry_type: location_invariant + location: + file_name: 52-queuesize.c + file_hash: $FILE_HASH + line: 36 + column: 3 + function: push + location_invariant: + string: 2147483647LL - (long long )capacity >= 0LL + type: assertion + format: C + - entry_type: location_invariant + location: + file_name: 52-queuesize.c + file_hash: $FILE_HASH + line: 15 + column: 3 + function: pop + location_invariant: + string: 2147483647LL - (long long )capacity >= 0LL + type: assertion + format: C + --- + # Right-only entries: + [] diff --git a/tests/regression/36-apron/65-multi-lock-producer-consumer.c b/tests/regression/36-apron/65-multi-lock-producer-consumer.c index ae71c36dcf..e7041a09d3 100644 --- a/tests/regression/36-apron/65-multi-lock-producer-consumer.c +++ b/tests/regression/36-apron/65-multi-lock-producer-consumer.c @@ -1,5 +1,6 @@ // SKIP PARAM: --set ana.activated[+] apron --enable ana.sv-comp.functions --set ana.relation.privatization mutex-meet --set ana.apron.domain polyhedra // TODO: why doesn't mutex-meet-tid succeed? a widening loses some upper bound and we forget a possible overflow, succeeds with assume_none +// NOCHECK #include #include diff --git a/tests/regression/36-apron/86-branched-thread-creation.c b/tests/regression/36-apron/86-branched-thread-creation.c index cac0a881a6..91a5411e8f 100644 --- a/tests/regression/36-apron/86-branched-thread-creation.c +++ b/tests/regression/36-apron/86-branched-thread-creation.c @@ -40,7 +40,7 @@ int main(void) { if(!mt) { pthread_mutex_lock(&mutex); - __goblint_check(g==h); //MAYFAIL + __goblint_check(g==h); //FAIL pthread_mutex_unlock(&mutex); } diff --git a/tests/regression/36-apron/dune b/tests/regression/36-apron/dune index 6ea87f5ba8..42869053bc 100644 --- a/tests/regression/36-apron/dune +++ b/tests/regression/36-apron/dune @@ -8,3 +8,8 @@ (glob_files ??-*.c)) (locks /update_suite) (action (chdir ../../.. (run %{update_suite} group apron -q)))) + +(cram + (alias runaprontest) + (enabled_if %{lib-available:apron}) + (deps (glob_files *.c))) diff --git a/tests/regression/38-int-refinements/05-invalid-widen.c b/tests/regression/38-int-refinements/05-invalid-widen.c index 59d76019a8..ad39ff761c 100644 --- a/tests/regression/38-int-refinements/05-invalid-widen.c +++ b/tests/regression/38-int-refinements/05-invalid-widen.c @@ -1,4 +1,5 @@ //PARAM: --set ana.int.refinement once --enable ana.int.enums +// NOCRASH #include int main() { diff --git a/tests/regression/39-signed-overflows/11-imaxabs.c b/tests/regression/39-signed-overflows/11-imaxabs.c new file mode 100644 index 0000000000..47bd26569f --- /dev/null +++ b/tests/regression/39-signed-overflows/11-imaxabs.c @@ -0,0 +1,24 @@ +//PARAM: --enable ana.int.interval --set ana.activated[+] tmpSpecial +#include +#include +#include +int main() { + int64_t data; + if (data > (-0x7fffffffffffffff - 1)) + { + if (imaxabs(data) < 100) + { + __goblint_check(data < 100); + __goblint_check(-100 < data); + int64_t result = data * data; // NOWARN + } + + if(imaxabs(data) <= 100) + { + __goblint_check(data <= 100); + __goblint_check(-100 <= data); + int64_t result = data * data; // NOWARN + } + } + return 8; +} diff --git a/tests/regression/39-signed-overflows/12-imaxabs-sqrt.c b/tests/regression/39-signed-overflows/12-imaxabs-sqrt.c new file mode 100644 index 0000000000..b121645b27 --- /dev/null +++ b/tests/regression/39-signed-overflows/12-imaxabs-sqrt.c @@ -0,0 +1,12 @@ +//PARAM: --enable ana.int.interval --enable ana.float.interval --enable ana.float.evaluate_math_functions --set ana.activated[+] tmpSpecial +#include +#include +#include +int main() { + int64_t data; + if (data > (-0x7fffffffffffffff - 1) && imaxabs((intmax_t)data) <= sqrtl(0x7fffffffffffffffLL)) + { + int64_t result = data * data; // TODO NOWARN + } + return 8; +} diff --git a/tests/regression/39-signed-overflows/13-imaxabs-macos.c b/tests/regression/39-signed-overflows/13-imaxabs-macos.c new file mode 100644 index 0000000000..745d5b74c4 --- /dev/null +++ b/tests/regression/39-signed-overflows/13-imaxabs-macos.c @@ -0,0 +1,25 @@ +//PARAM: --enable ana.int.interval --set ana.activated[+] tmpSpecial +// 39-signed-overflows/11-imaxabs, but with long long as int64_t instead (https://github.com/goblint/analyzer/pull/1519#issuecomment-2417032186). +#include +#include +#include +int main() { + long long data; + if (data > (-0x7fffffffffffffff - 1)) + { + if (imaxabs(data) < 100) + { + __goblint_check(data < 100); + __goblint_check(-100 < data); + long long result = data * data; // NOWARN + } + + if(imaxabs(data) <= 100) + { + __goblint_check(data <= 100); + __goblint_check(-100 <= data); + long long result = data * data; // NOWARN + } + } + return 8; +} diff --git a/tests/regression/41-stdlib/03-noqsort.c b/tests/regression/41-stdlib/03-noqsort.c index e7a1de89a3..22672083d7 100644 --- a/tests/regression/41-stdlib/03-noqsort.c +++ b/tests/regression/41-stdlib/03-noqsort.c @@ -1,4 +1,5 @@ // PARAM: --set pre.cppflags[+] -DGOBLINT_NO_QSORT +// CRAM #include // There should be no CIL warning about multiple definitions here diff --git a/tests/regression/41-stdlib/03-noqsort.t b/tests/regression/41-stdlib/03-noqsort.t new file mode 100644 index 0000000000..cd56e0047d --- /dev/null +++ b/tests/regression/41-stdlib/03-noqsort.t @@ -0,0 +1,8 @@ +There should be no CIL warning about multiple definitions: + + $ goblint --set pre.cppflags[+] -DGOBLINT_NO_QSORT 03-noqsort.c + [Warning][Deadcode] Function 'qsort' is uncalled: 1 LLoC (03-noqsort.c:6:1-7:1) + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 2 + dead: 1 (1 in uncalled functions) + total lines: 3 diff --git a/tests/regression/41-stdlib/08-atexit-no-spawn.c b/tests/regression/41-stdlib/08-atexit-no-spawn.c index 7f25f42183..3bbba82634 100644 --- a/tests/regression/41-stdlib/08-atexit-no-spawn.c +++ b/tests/regression/41-stdlib/08-atexit-no-spawn.c @@ -1,4 +1,4 @@ -// PARAM: --disable sem.unknown_function.spawn +// PARAM: --enable sem.atexit.ignore #include #include diff --git a/tests/regression/41-stdlib/dune b/tests/regression/41-stdlib/dune new file mode 100644 index 0000000000..23c0dd3290 --- /dev/null +++ b/tests/regression/41-stdlib/dune @@ -0,0 +1,2 @@ +(cram + (deps (glob_files *.c))) diff --git a/tests/regression/43-struct-domain/12-aget.c b/tests/regression/43-struct-domain/12-aget.c index 193e463418..6e67cefe75 100644 --- a/tests/regression/43-struct-domain/12-aget.c +++ b/tests/regression/43-struct-domain/12-aget.c @@ -1,5 +1,5 @@ // PARAM: --set ana.base.structs.domain "sets" - +// NOCHECK // This is shortened from aget.c in the bench repository // There was an issue when testing arrays, as they init to bottom and this code triggers narrowing. /* Generated by CIL v. 1.3.6 */ diff --git a/tests/regression/43-struct-domain/25-aget-keyed.c b/tests/regression/43-struct-domain/25-aget-keyed.c index 94d5495ae1..09c80c44cc 100644 --- a/tests/regression/43-struct-domain/25-aget-keyed.c +++ b/tests/regression/43-struct-domain/25-aget-keyed.c @@ -1,5 +1,5 @@ // PARAM: --set ana.base.structs.domain "keyed" - +// NOCHECK // This is shortened from aget.c in the bench repository // There was an issue when testing arrays, as they init to bottom and this code triggers narrowing. /* Generated by CIL v. 1.3.6 */ diff --git a/tests/regression/44-trier_analyzer/00-A0.c b/tests/regression/44-trier_analyzer/00-A0.c index defd7022f0..bfeb7c5509 100644 --- a/tests/regression/44-trier_analyzer/00-A0.c +++ b/tests/regression/44-trier_analyzer/00-A0.c @@ -1,3 +1,4 @@ +// NOCHECK extern int printf(char *, ...); main () { diff --git a/tests/regression/44-trier_analyzer/01-A1.c b/tests/regression/44-trier_analyzer/01-A1.c index a04fafc238..967b8d79d0 100644 --- a/tests/regression/44-trier_analyzer/01-A1.c +++ b/tests/regression/44-trier_analyzer/01-A1.c @@ -1,3 +1,4 @@ +// NOCHECK extern int printf (char *, ...); extern int scanf (char *, ...); diff --git a/tests/regression/44-trier_analyzer/02-abc.c b/tests/regression/44-trier_analyzer/02-abc.c index 6a23697a7b..f29d3787e7 100644 --- a/tests/regression/44-trier_analyzer/02-abc.c +++ b/tests/regression/44-trier_analyzer/02-abc.c @@ -1,3 +1,4 @@ +// NOCHECK #include #include diff --git a/tests/regression/44-trier_analyzer/03-break.c b/tests/regression/44-trier_analyzer/03-break.c index 4c87a8277f..78a8cf26d6 100644 --- a/tests/regression/44-trier_analyzer/03-break.c +++ b/tests/regression/44-trier_analyzer/03-break.c @@ -1,3 +1,4 @@ +// NOCHECK extern int printf(); main () { diff --git a/tests/regression/44-trier_analyzer/13-ifif.c b/tests/regression/44-trier_analyzer/13-ifif.c index 3e456a9576..e9025f1fa8 100644 --- a/tests/regression/44-trier_analyzer/13-ifif.c +++ b/tests/regression/44-trier_analyzer/13-ifif.c @@ -1,3 +1,4 @@ +// NOCHECK extern int printf(); extern int scanf(); diff --git a/tests/regression/44-trier_analyzer/15-P1.c b/tests/regression/44-trier_analyzer/15-P1.c index 2690a6da44..b169ea4876 100644 --- a/tests/regression/44-trier_analyzer/15-P1.c +++ b/tests/regression/44-trier_analyzer/15-P1.c @@ -1,3 +1,4 @@ +// NOCHECK main () { int y, a; int *p, *q, *t; diff --git a/tests/regression/44-trier_analyzer/16-P2.c b/tests/regression/44-trier_analyzer/16-P2.c index 3942760be7..2f27d5dbe0 100644 --- a/tests/regression/44-trier_analyzer/16-P2.c +++ b/tests/regression/44-trier_analyzer/16-P2.c @@ -1,3 +1,4 @@ +// NOCHECK main () { int y, a, b; int *p, *q; diff --git a/tests/regression/44-trier_analyzer/17-P3.c b/tests/regression/44-trier_analyzer/17-P3.c index 7b8c8a13d8..0bbc9ee2d2 100644 --- a/tests/regression/44-trier_analyzer/17-P3.c +++ b/tests/regression/44-trier_analyzer/17-P3.c @@ -1,3 +1,4 @@ +// NOCHECK #include extern void *malloc(size_t); diff --git a/tests/regression/44-trier_analyzer/23-rec0.c b/tests/regression/44-trier_analyzer/23-rec0.c index aa13bbac8e..ee43820bb1 100644 --- a/tests/regression/44-trier_analyzer/23-rec0.c +++ b/tests/regression/44-trier_analyzer/23-rec0.c @@ -1,3 +1,4 @@ +// NOCHECK extern int printf(char *, ...); extern int scanf(char *, ...); extern void exit(int); diff --git a/tests/regression/44-trier_analyzer/33-recA.c b/tests/regression/44-trier_analyzer/33-recA.c index 74fa54b768..2938403326 100644 --- a/tests/regression/44-trier_analyzer/33-recA.c +++ b/tests/regression/44-trier_analyzer/33-recA.c @@ -1,4 +1,5 @@ //PARAM: --enable ana.context.widen +// NOCHECK //Needs context widening even with only def_exc extern int scanf(char *, ...); extern int printf(char *, ...); diff --git a/tests/regression/46-apron2/03-other-assume.c b/tests/regression/46-apron2/03-other-assume.c index 635911a197..c136d1bde3 100644 --- a/tests/regression/46-apron2/03-other-assume.c +++ b/tests/regression/46-apron2/03-other-assume.c @@ -1,4 +1,4 @@ -// SKIP PARAM: --set ana.activated[+] apron --set ana.path_sens[+] threadflag --set ana.activated[+] threadJoins --sets ana.relation.privatization mutex-meet-tid +// SKIP PARAM: --set ana.activated[+] apron --set ana.path_sens[+] threadflag --set ana.activated[+] threadJoins --set ana.relation.privatization mutex-meet-tid #include #include diff --git a/tests/regression/46-apron2/04-other-assume-inprec.c b/tests/regression/46-apron2/04-other-assume-inprec.c index 09b8d150a8..ccf8ecefa1 100644 --- a/tests/regression/46-apron2/04-other-assume-inprec.c +++ b/tests/regression/46-apron2/04-other-assume-inprec.c @@ -1,4 +1,4 @@ -// SKIP PARAM: --set ana.activated[+] apron --set ana.path_sens[+] threadflag --set ana.activated[+] threadJoins --sets ana.relation.privatization mutex-meet-tid +// SKIP PARAM: --set ana.activated[+] apron --set ana.path_sens[+] threadflag --set ana.activated[+] threadJoins --set ana.relation.privatization mutex-meet-tid #include #include diff --git a/tests/regression/46-apron2/26-autotune.c b/tests/regression/46-apron2/26-autotune.c index 96c5c85278..ec8f55fe19 100644 --- a/tests/regression/46-apron2/26-autotune.c +++ b/tests/regression/46-apron2/26-autotune.c @@ -1,4 +1,4 @@ -//SKIP PARAM: --enable ana.int.interval --sets sem.int.signed_overflow assume_none --set ana.activated[+] apron --enable ana.autotune.enabled +//SKIP PARAM: --enable ana.int.interval --set sem.int.signed_overflow assume_none --set ana.activated[+] apron --enable ana.autotune.enabled // Check that autotuner disables context for apron as well for recursive calls #include diff --git a/tests/regression/46-apron2/28-sv-comp-unroll-term.c b/tests/regression/46-apron2/28-sv-comp-unroll-term.c index a22b0e16bd..2a1f947dca 100644 --- a/tests/regression/46-apron2/28-sv-comp-unroll-term.c +++ b/tests/regression/46-apron2/28-sv-comp-unroll-term.c @@ -2,7 +2,7 @@ // Minimized from sv-benchmarks/c/ldv-linux-3.4-simple/32_1_cilled_ok_nondet_linux-3.4-32_1-drivers--staging--speakup--speakup_spkout.ko-ldv_main0_sequence_infinite_withcheck_stateful.cil.out.i // using loop unrolling of 1. -// Used to not terminate. +// NOTIMEOUT: Used to not terminate. struct speakup_info_t { int port_tts; diff --git a/tests/regression/46-apron2/30-autotune-stub.c b/tests/regression/46-apron2/30-autotune-stub.c index e1b7603c3b..02c80e7d21 100644 --- a/tests/regression/46-apron2/30-autotune-stub.c +++ b/tests/regression/46-apron2/30-autotune-stub.c @@ -1,4 +1,4 @@ -//SKIP PARAM: --enable ana.int.interval --sets sem.int.signed_overflow assume_none --set ana.activated[+] apron --enable ana.autotune.enabled +//SKIP PARAM: --enable ana.int.interval --set sem.int.signed_overflow assume_none --set ana.activated[+] apron --enable ana.autotune.enabled // Check that autotuner respect goblint_stub attributes as hints to not track variables. #include diff --git a/tests/regression/46-apron2/34-iset_previously_problematic_c.c b/tests/regression/46-apron2/34-iset_previously_problematic_c.c index 3c1847bbcf..2b0f526957 100644 --- a/tests/regression/46-apron2/34-iset_previously_problematic_c.c +++ b/tests/regression/46-apron2/34-iset_previously_problematic_c.c @@ -1,6 +1,7 @@ // SKIP PARAM: --enable ana.int.interval_set --set ana.base.arrays.domain partitioned --set ana.activated[+] apron // These examples were cases were we saw issues of not reaching a fixpoint during development of the octagon domain. Since those issues might // resurface, these tests without asserts are included +// NOCHECK int main(int argc, char const *argv[]) { int i = 0; diff --git a/tests/regression/46-apron2/36-iset_previosuly_problematic_g.c b/tests/regression/46-apron2/36-iset_previosuly_problematic_g.c index 4cbcaef064..89253052d5 100644 --- a/tests/regression/46-apron2/36-iset_previosuly_problematic_g.c +++ b/tests/regression/46-apron2/36-iset_previosuly_problematic_g.c @@ -1,6 +1,7 @@ // SKIP PARAM: --enable ana.int.interval_set --set ana.base.arrays.domain partitioned --set ana.activated[+] apron // These examples were cases were we saw issues of not reaching a fixpoint during development of the octagon domain. Since those issues might // resurface, these tests without asserts are included +// NOCHECK int main(int argc, char const *argv[]) { int i = 0; diff --git a/tests/regression/46-apron2/37-iset_previosuly_problematic_f.c b/tests/regression/46-apron2/37-iset_previosuly_problematic_f.c index 918e67b0e8..c88aabe377 100644 --- a/tests/regression/46-apron2/37-iset_previosuly_problematic_f.c +++ b/tests/regression/46-apron2/37-iset_previosuly_problematic_f.c @@ -1,6 +1,7 @@ // SKIP PARAM: --enable ana.int.interval_set --set ana.base.arrays.domain partitioned --set ana.activated[+] apron // These examples were cases were we saw issues of not reaching a fixpoint during development of the octagon domain. Since those issues might // resurface, these tests without asserts are included +// NOCHECK int main(int argc, char const *argv[]) { int a[256]; diff --git a/tests/regression/46-apron2/39-iset_previosuly_problematic_d.c b/tests/regression/46-apron2/39-iset_previosuly_problematic_d.c index 18a53d31aa..480691409d 100644 --- a/tests/regression/46-apron2/39-iset_previosuly_problematic_d.c +++ b/tests/regression/46-apron2/39-iset_previosuly_problematic_d.c @@ -1,6 +1,7 @@ // SKIP PARAM: --enable ana.int.interval_set --set ana.activated[+] apron // These examples were cases were we saw issues of not reaching a fixpoint during development of the octagon domain. Since those issues might // resurface, these tests without asserts are included +// NOCHECK int main(int argc, char const *argv[]) { int l; diff --git a/tests/regression/46-apron2/45-iset_previosuly_problematic_i.c b/tests/regression/46-apron2/45-iset_previosuly_problematic_i.c index dab6078b98..b9506cb453 100644 --- a/tests/regression/46-apron2/45-iset_previosuly_problematic_i.c +++ b/tests/regression/46-apron2/45-iset_previosuly_problematic_i.c @@ -1,6 +1,7 @@ // SKIP PARAM: --enable ana.int.interval_set --set ana.base.arrays.domain partitioned --set ana.activated[+] apron // These examples were cases were we saw issues of not reaching a fixpoint during development of the octagon domain. Since those issues might // resurface, these tests without asserts are included +// NOCHECK char buf2[67]; int main(int argc, char **argv) diff --git a/tests/regression/46-apron2/47-iset_previously_problematic_a.c b/tests/regression/46-apron2/47-iset_previously_problematic_a.c index 2bf6044560..15d61d436b 100644 --- a/tests/regression/46-apron2/47-iset_previously_problematic_a.c +++ b/tests/regression/46-apron2/47-iset_previously_problematic_a.c @@ -1,6 +1,7 @@ // SKIP PARAM: --enable ana.int.interval_set --set ana.activated[+] apron // These examples were cases were we saw issues of not reaching a fixpoint during development of the octagon domain. Since those issues might // resurface, these tests without asserts are included +// NOCHECK int main(int argc, char const *argv[]) { int top; diff --git a/tests/regression/46-apron2/49-iset_previously_problematic_b.c b/tests/regression/46-apron2/49-iset_previously_problematic_b.c index 19d925013c..336efa4eb0 100644 --- a/tests/regression/46-apron2/49-iset_previously_problematic_b.c +++ b/tests/regression/46-apron2/49-iset_previously_problematic_b.c @@ -1,6 +1,7 @@ // SKIP PARAM: --enable ana.int.interval_set --set ana.activated[+] apron // These examples were cases were we saw issues of not reaching a fixpoint during development of the octagon domain. Since those issues might // resurface, these tests without asserts are included +// NOCHECK typedef int wchar_t; typedef unsigned long size_t; diff --git a/tests/regression/46-apron2/51-iset_previosuly_problematic_e.c b/tests/regression/46-apron2/51-iset_previosuly_problematic_e.c index 551bb731e9..582cd4916c 100644 --- a/tests/regression/46-apron2/51-iset_previosuly_problematic_e.c +++ b/tests/regression/46-apron2/51-iset_previosuly_problematic_e.c @@ -1,6 +1,7 @@ // SKIP PARAM: --enable ana.int.interval_set --set ana.activated[+] apron // These examples were cases were we saw issues of not reaching a fixpoint during development of the octagon domain. Since those issues might // resurface, these tests without asserts are included +// NOCHECK int main(int argc, char const *argv[]) { int j = -1; diff --git a/tests/regression/46-apron2/53-iset_previously_problematic_h.c b/tests/regression/46-apron2/53-iset_previously_problematic_h.c index 05489e47dc..242f1fb377 100644 --- a/tests/regression/46-apron2/53-iset_previously_problematic_h.c +++ b/tests/regression/46-apron2/53-iset_previously_problematic_h.c @@ -1,6 +1,7 @@ // SKIP PARAM: --enable ana.int.interval_set --set ana.base.arrays.domain partitioned --set ana.activated[+] apron // These examples were cases were we saw issues of not reaching a fixpoint during development of the octagon domain. Since those issues might // resurface, these tests without asserts are included +// NOCHECK int main(int argc, char const *argv[]) { int iter = 0; diff --git a/tests/regression/46-apron2/58-issue-1249.c b/tests/regression/46-apron2/58-issue-1249.c index 862c539d15..903eafdac8 100644 --- a/tests/regression/46-apron2/58-issue-1249.c +++ b/tests/regression/46-apron2/58-issue-1249.c @@ -2,7 +2,7 @@ int *a; int b; void c(int d) { - // *a is a null pointer here, so we should warn but maybe not crash + // NOCRASH: *a is a null pointer here, so we should warn but maybe not crash *a = d; } int main() { diff --git a/tests/regression/46-apron2/59-issue-1319.c b/tests/regression/46-apron2/59-issue-1319.c index 1c11d6093e..62520c95c1 100644 --- a/tests/regression/46-apron2/59-issue-1319.c +++ b/tests/regression/46-apron2/59-issue-1319.c @@ -8,7 +8,7 @@ int main() t = &c; - // Type of *t and c do not match, this caused a crash before + // NOCRASH: Type of *t and c do not match, this caused a crash before if(*t == 'a') { t++; } @@ -18,7 +18,7 @@ int main() int other() { - // Same problem, but a bit more involved + // NOCRASH: Same problem, but a bit more involved unsigned char *t; char buf[100] = "bliblablubapk\r"; diff --git a/tests/regression/46-apron2/60-issue-1338.c b/tests/regression/46-apron2/60-issue-1338.c index 899fe613b3..92afa3b74f 100644 --- a/tests/regression/46-apron2/60-issue-1338.c +++ b/tests/regression/46-apron2/60-issue-1338.c @@ -1,4 +1,5 @@ // SKIP PARAM: --set ana.activated[+] apron +// NOCRASH #include int main() { diff --git a/tests/regression/46-apron2/82-fixpoint-not-reached.c b/tests/regression/46-apron2/82-fixpoint-not-reached.c index 1dff575a7c..66f00770da 100644 --- a/tests/regression/46-apron2/82-fixpoint-not-reached.c +++ b/tests/regression/46-apron2/82-fixpoint-not-reached.c @@ -1,5 +1,5 @@ // SKIP PARAM: --set sem.int.signed_overflow assume_none --set ana.activated[+] apron - +// FIXPOINT int main() { int minInt = -2147483647 + -1; int x = (minInt + -1) +1; diff --git a/tests/regression/46-apron2/85-fix.c b/tests/regression/46-apron2/85-fix.c new file mode 100644 index 0000000000..e0d7e8964b --- /dev/null +++ b/tests/regression/46-apron2/85-fix.c @@ -0,0 +1,13 @@ +// SKIP PARAM: --set ana.activated[+] apron --set sem.int.signed_overflow assume_none +#include + +int main() { + int d = 1; + while (d < 6) { + if (0) + return 0; //FIXPOINT: Earlier this fixpoint was not reached here + d++; + } + + return 0; +} diff --git a/tests/regression/46-apron2/86-escape-cluster12.c b/tests/regression/46-apron2/86-escape-cluster12.c new file mode 100644 index 0000000000..283e45b728 --- /dev/null +++ b/tests/regression/46-apron2/86-escape-cluster12.c @@ -0,0 +1,21 @@ +// SKIP PARAM: --set ana.activated[+] apron --set ana.path_sens[+] threadflag --set ana.relation.privatization mutex-meet-tid-cluster12 +#include +#include +void *a; + +void* nothing(void* arg) { + // Go multithreaded! +} + +void main() { + pthread_t t; + pthread_create(&t, 0, nothing, 0); + + int d = 5; + a = &d; + + if (0) + ; + + __goblint_check(1); // Should be reachable! +} diff --git a/tests/regression/46-apron2/87-unlock-idx-ambiguous.c b/tests/regression/46-apron2/87-unlock-idx-ambiguous.c new file mode 100644 index 0000000000..ff6461217e --- /dev/null +++ b/tests/regression/46-apron2/87-unlock-idx-ambiguous.c @@ -0,0 +1,28 @@ +// SKIP PARAM: --set ana.activated[+] apron --set ana.relation.privatization mutex-meet --enable ana.sv-comp.functions --set ana.path_sens[+] threadflag +// TODO: why nonterm without threadflag path-sens? +#include +#include +extern _Bool __VERIFIER_nondet_bool(); + +int g; +pthread_mutex_t m[2] = {PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER}; + +void *t_fun(void *arg) { + pthread_mutex_lock(&m[0]); + pthread_mutex_lock(&m[1]); // so we're unlocking a mutex we definitely hold + g++; + int r = __VERIFIER_nondet_bool(); + pthread_mutex_unlock(&m[r]); // TODO NOWARN (definitely held either way) + // could have unlocked m[0], so should have published g there + return NULL; +} + +int main() { + pthread_t id; + pthread_create(&id, NULL, t_fun, NULL); + + pthread_mutex_lock(&m[0]); + __goblint_check(g == 0); // UNKNOWN! + pthread_mutex_unlock(&m[0]); + return 0; +} diff --git a/tests/regression/46-apron2/87-unlock-idx-ambiguous.t b/tests/regression/46-apron2/87-unlock-idx-ambiguous.t new file mode 100644 index 0000000000..abbdd74f00 --- /dev/null +++ b/tests/regression/46-apron2/87-unlock-idx-ambiguous.t @@ -0,0 +1,39 @@ + $ goblint --set ana.activated[+] apron --set ana.relation.privatization mutex-meet --enable ana.sv-comp.functions --set ana.path_sens[+] threadflag 87-unlock-idx-ambiguous.c + [Warning][Unknown] unlocking mutex (m[def_exc:Unknown int([0,1])]) which may not be held (87-unlock-idx-ambiguous.c:15:3-15:30) + [Warning][Assert] Assertion "g == 0" is unknown. (87-unlock-idx-ambiguous.c:25:3-25:26) + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 14 + dead: 0 + total lines: 14 + [Info][Race] Memory locations race summary: + safe: 1 + vulnerable: 0 + unsafe: 0 + total memory locations: 1 + + $ goblint --set ana.activated[+] apron --set ana.relation.privatization mutex-meet-tid --enable ana.sv-comp.functions --set ana.path_sens[+] threadflag 87-unlock-idx-ambiguous.c + [Warning][Unknown] unlocking mutex (m[def_exc:Unknown int([0,1])]) which may not be held (87-unlock-idx-ambiguous.c:15:3-15:30) + [Warning][Assert] Assertion "g == 0" is unknown. (87-unlock-idx-ambiguous.c:25:3-25:26) + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 14 + dead: 0 + total lines: 14 + [Info][Race] Memory locations race summary: + safe: 1 + vulnerable: 0 + unsafe: 0 + total memory locations: 1 + + $ goblint --set ana.activated[+] apron --set ana.relation.privatization mutex-meet-tid-cluster12 --enable ana.sv-comp.functions --set ana.path_sens[+] threadflag 87-unlock-idx-ambiguous.c + [Warning][Unknown] unlocking mutex (m[def_exc:Unknown int([0,1])]) which may not be held (87-unlock-idx-ambiguous.c:15:3-15:30) + [Warning][Assert] Assertion "g == 0" is unknown. (87-unlock-idx-ambiguous.c:25:3-25:26) + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 14 + dead: 0 + total lines: 14 + [Info][Race] Memory locations race summary: + safe: 1 + vulnerable: 0 + unsafe: 0 + total memory locations: 1 + diff --git a/tests/regression/46-apron2/88-unlock-unknown.c b/tests/regression/46-apron2/88-unlock-unknown.c new file mode 100644 index 0000000000..dbdc4b9f77 --- /dev/null +++ b/tests/regression/46-apron2/88-unlock-unknown.c @@ -0,0 +1,25 @@ +// SKIP PARAM: --set ana.activated[+] apron --set ana.relation.privatization mutex-meet --enable ana.sv-comp.functions +#include +#include + +int g; +pthread_mutex_t m[2] = {PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER}; + +void *t_fun(void *arg) { + pthread_mutex_lock(&m[0]); + g++; + pthread_mutex_t *r; // rand + pthread_mutex_unlock(r); + // could have unlocked m[0], so should have published g there + return NULL; +} + +int main() { + pthread_t id; + pthread_create(&id, NULL, t_fun, NULL); + + pthread_mutex_lock(&m[0]); + __goblint_check(g == 0); // UNKNOWN! + pthread_mutex_unlock(&m[0]); + return 0; +} diff --git a/tests/regression/46-apron2/88-unlock-unknown.t b/tests/regression/46-apron2/88-unlock-unknown.t new file mode 100644 index 0000000000..35e4ec4503 --- /dev/null +++ b/tests/regression/46-apron2/88-unlock-unknown.t @@ -0,0 +1,42 @@ + $ goblint --set ana.activated[+] apron --set ana.relation.privatization mutex-meet --enable ana.sv-comp.functions 88-unlock-unknown.c + [Warning][Unknown] unlocking unknown mutex which may not be held (88-unlock-unknown.c:12:3-12:26) + [Warning][Unknown] unlocking NULL mutex (88-unlock-unknown.c:12:3-12:26) + [Warning][Assert] Assertion "g == 0" is unknown. (88-unlock-unknown.c:22:3-22:26) + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 11 + dead: 0 + total lines: 11 + [Info][Race] Memory locations race summary: + safe: 1 + vulnerable: 0 + unsafe: 0 + total memory locations: 1 + + $ goblint --set ana.activated[+] apron --set ana.relation.privatization mutex-meet-tid --enable ana.sv-comp.functions --set ana.path_sens[+] threadflag 88-unlock-unknown.c + [Warning][Unknown] unlocking unknown mutex which may not be held (88-unlock-unknown.c:12:3-12:26) + [Warning][Unknown] unlocking NULL mutex (88-unlock-unknown.c:12:3-12:26) + [Warning][Assert] Assertion "g == 0" is unknown. (88-unlock-unknown.c:22:3-22:26) + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 11 + dead: 0 + total lines: 11 + [Info][Race] Memory locations race summary: + safe: 1 + vulnerable: 0 + unsafe: 0 + total memory locations: 1 + + $ goblint --set ana.activated[+] apron --set ana.relation.privatization mutex-meet-tid-cluster12 --enable ana.sv-comp.functions --set ana.path_sens[+] threadflag 88-unlock-unknown.c + [Warning][Unknown] unlocking unknown mutex which may not be held (88-unlock-unknown.c:12:3-12:26) + [Warning][Unknown] unlocking NULL mutex (88-unlock-unknown.c:12:3-12:26) + [Warning][Assert] Assertion "g == 0" is unknown. (88-unlock-unknown.c:22:3-22:26) + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 11 + dead: 0 + total lines: 11 + [Info][Race] Memory locations race summary: + safe: 1 + vulnerable: 0 + unsafe: 0 + total memory locations: 1 + diff --git a/tests/regression/46-apron2/89-flag-ctx-sens.c b/tests/regression/46-apron2/89-flag-ctx-sens.c new file mode 100644 index 0000000000..41e4f02520 --- /dev/null +++ b/tests/regression/46-apron2/89-flag-ctx-sens.c @@ -0,0 +1,28 @@ +// SKIP PARAM: --set ana.activated[+] apron --set ana.relation.privatization mutex-meet +// NOTIMEOUT +// See https://github.com/goblint/analyzer/pull/1508 +#include +#include + +int g; +pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER; + +void fn() { + // Just do nothing + return; +} + +void *t_fun(void *arg) { + pthread_mutex_lock(&m); + g = g+1; + fn(); + pthread_mutex_unlock(&m); + return NULL; +} + +int main() { + pthread_t id; + pthread_create(&id, NULL, t_fun, NULL); + + return 0; +} diff --git a/tests/regression/46-apron2/90-malloc.c b/tests/regression/46-apron2/90-malloc.c new file mode 100644 index 0000000000..8780568748 --- /dev/null +++ b/tests/regression/46-apron2/90-malloc.c @@ -0,0 +1,21 @@ +// SKIP PARAM: --set ana.activated[+] apron --set ana.relation.privatization mutex-meet --set ana.apron.domain interval --set sem.int.signed_overflow assume_none +// Checks that assinging to malloc'ed memory does not cause both branches to be dead +#include +#include +void nop(void* arg) { +} + +void main() { + pthread_t thread; + pthread_create(&thread, 0, &nop, 0); + + long *k = malloc(sizeof(long)); + *k = 5; + if (1) + ; + + __goblint_check(*k >= 5); // Reachable and true + + *k = *k+1; + __goblint_check(*k >= 5); // Reachable and true +} diff --git a/tests/regression/46-apron2/91-malloc-tid.c b/tests/regression/46-apron2/91-malloc-tid.c new file mode 100644 index 0000000000..36696956e7 --- /dev/null +++ b/tests/regression/46-apron2/91-malloc-tid.c @@ -0,0 +1,21 @@ +// SKIP PARAM: --set ana.activated[+] apron --set ana.path_sens[+] threadflag --set ana.relation.privatization mutex-meet-tid --set ana.apron.domain interval --set sem.int.signed_overflow assume_none +// Checks that assinging to malloc'ed memory does not cause both branches to be dead +#include +#include +void nop(void* arg) { +} + +void main() { + pthread_t thread; + pthread_create(&thread, 0, &nop, 0); + + long *k = malloc(sizeof(long)); + *k = 5; + if (1) + ; + + __goblint_check(*k >= 5); // Reachable and true + + *k = *k+1; + __goblint_check(*k >= 5); // Reachable and true +} diff --git a/tests/regression/46-apron2/92-malloc-atomic.c b/tests/regression/46-apron2/92-malloc-atomic.c new file mode 100644 index 0000000000..b2f057f6a4 --- /dev/null +++ b/tests/regression/46-apron2/92-malloc-atomic.c @@ -0,0 +1,21 @@ +// SKIP PARAM: --set ana.activated[+] apron --set ana.relation.privatization mutex-meet-atomic --set ana.apron.domain interval --set sem.int.signed_overflow assume_none +// Checks that assinging to malloc'ed memory does not cause both branches to be dead +#include +#include +void nop(void* arg) { +} + +void main() { + pthread_t thread; + pthread_create(&thread, 0, &nop, 0); + + long *k = malloc(sizeof(long)); + *k = 5; + if (1) + ; + + __goblint_check(*k >= 5); // Reachable and true + + *k = *k+1; + __goblint_check(*k >= 5); // Reachable and true +} diff --git a/tests/regression/46-apron2/dune b/tests/regression/46-apron2/dune index cc0c04185c..89efde3083 100644 --- a/tests/regression/46-apron2/dune +++ b/tests/regression/46-apron2/dune @@ -8,3 +8,8 @@ (glob_files ??-*.c)) (locks /update_suite) (action (chdir ../../.. (run %{update_suite} group apron2 -q)))) + +(cram + (alias runaprontest) + (enabled_if %{lib-available:apron}) + (deps (glob_files *.c))) diff --git a/tests/regression/51-threadjoins/09-join-main.c b/tests/regression/51-threadjoins/09-join-main.c new file mode 100644 index 0000000000..196ef8bc00 --- /dev/null +++ b/tests/regression/51-threadjoins/09-join-main.c @@ -0,0 +1,29 @@ +//PARAM: --set ana.activated[+] threadJoins +#include +#include + +pthread_t mainid; + +int g = 10; + +void *t_fun(void *arg) { + int r = pthread_join(mainid, NULL); // TSan doesn't like this... + printf("j: %d\n", r); + g++; // NORACE + printf("t_fun: %d\n", g); + return NULL; +} + + +int main(void) { + mainid = pthread_self(); + + pthread_t id2; + pthread_create(&id2, NULL, t_fun, NULL); + + g++; // NORACE + printf("main: %d\n", g); + + pthread_exit(NULL); // exit main thread but keep id2 alive, otherwise main returning kills id2 + return 0; +} diff --git a/tests/regression/51-threadjoins/10-join-main-plain.c b/tests/regression/51-threadjoins/10-join-main-plain.c new file mode 100644 index 0000000000..5b2c188bf5 --- /dev/null +++ b/tests/regression/51-threadjoins/10-join-main-plain.c @@ -0,0 +1,29 @@ +//PARAM: --set ana.activated[+] threadJoins --set ana.thread.domain plain +#include +#include + +pthread_t mainid; + +int g = 10; + +void *t_fun(void *arg) { + int r = pthread_join(mainid, NULL); // TSan doesn't like this... + printf("j: %d\n", r); + g++; // RACE (imprecise by plain thread IDs) + printf("t_fun: %d\n", g); + return NULL; +} + + +int main(void) { + mainid = pthread_self(); + + pthread_t id2; + pthread_create(&id2, NULL, t_fun, NULL); + + g++; // NORACE + printf("main: %d\n", g); + + pthread_exit(NULL); // exit main thread but keep id2 alive, otherwise main returning kills id2 + return 0; +} diff --git a/tests/regression/51-threadjoins/11-join-main-plain-no-node.c b/tests/regression/51-threadjoins/11-join-main-plain-no-node.c new file mode 100644 index 0000000000..7f235fd1d8 --- /dev/null +++ b/tests/regression/51-threadjoins/11-join-main-plain-no-node.c @@ -0,0 +1,29 @@ +//PARAM: --set ana.activated[+] threadJoins --set ana.thread.domain plain --disable ana.thread.include-node +#include +#include + +pthread_t mainid; + +int g = 10; + +void *t_fun(void *arg) { + int r = pthread_join(mainid, NULL); // TSan doesn't like this... + printf("j: %d\n", r); + g++; // RACE (imprecise by plain thread IDs) + printf("t_fun: %d\n", g); + return NULL; +} + + +int main(void) { + mainid = pthread_self(); + + pthread_t id2; + pthread_create(&id2, NULL, t_fun, NULL); + + g++; // RACE (imprecise by plain thread IDs not knowing if main is actual main or spawned by program) + printf("main: %d\n", g); + + pthread_exit(NULL); // exit main thread but keep id2 alive, otherwise main returning kills id2 + return 0; +} diff --git a/tests/regression/52-apron-mukherjee/19-mukherjee_fig_3_11.c b/tests/regression/52-apron-mukherjee/19-mukherjee_fig_3_11.c index b00a2b2270..73499179c5 100644 --- a/tests/regression/52-apron-mukherjee/19-mukherjee_fig_3_11.c +++ b/tests/regression/52-apron-mukherjee/19-mukherjee_fig_3_11.c @@ -1,5 +1,5 @@ // SKIP PARAM: --set ana.activated[+] apron --set ana.path_sens[+] threadflag --set ana.activated[+] threadJoins -// TODO: checks nothing? +// NOCHECK: checks nothing? #include int x; diff --git a/tests/regression/54-unroll_arrays/03-large_index_type.c b/tests/regression/54-unroll_arrays/03-large_index_type.c index 91c8056b32..80851aa4b8 100644 --- a/tests/regression/54-unroll_arrays/03-large_index_type.c +++ b/tests/regression/54-unroll_arrays/03-large_index_type.c @@ -1,6 +1,6 @@ //PARAM: --set ana.base.arrays.domain unroll --set ana.base.arrays.unrolling-factor 5 //from sv-comp test c/aws-c-common/memset_using_uint64_harness.i - +// NOCRASH typedef long unsigned int size_t; int main() { diff --git a/tests/regression/54-unroll_arrays/04-access_no_bounds.c b/tests/regression/54-unroll_arrays/04-access_no_bounds.c index 70a2726045..bd4fca4b68 100644 --- a/tests/regression/54-unroll_arrays/04-access_no_bounds.c +++ b/tests/regression/54-unroll_arrays/04-access_no_bounds.c @@ -1,6 +1,6 @@ // PARAM: --set ana.base.arrays.domain unroll --set ana.base.arrays.unrolling-factor 5 //from sv-comp test c/aws-c-common/aws_array_eq_c_str_ignore_case_harness.i - +// NOCRASH typedef long unsigned int size_t; typedef unsigned char uint8_t; diff --git a/tests/regression/55-loop-unrolling/08-bad.c b/tests/regression/55-loop-unrolling/08-bad.c index e2ad0f0453..df9131d6bf 100644 --- a/tests/regression/55-loop-unrolling/08-bad.c +++ b/tests/regression/55-loop-unrolling/08-bad.c @@ -1,4 +1,5 @@ // PARAM: --set exp.unrolling-factor 1 --enable dbg.run_cil_check +// CRAM int main() { int m; diff --git a/tests/regression/55-loop-unrolling/08-bad.t b/tests/regression/55-loop-unrolling/08-bad.t index 11cded728f..a8d8d62522 100644 --- a/tests/regression/55-loop-unrolling/08-bad.t +++ b/tests/regression/55-loop-unrolling/08-bad.t @@ -1,6 +1,4 @@ $ goblint --set lib.activated '[]' --set exp.unrolling-factor 1 --enable justcil --set dbg.justcil-printer clean 08-bad.c - [Info] unrolling loop at 08-bad.c:8:7-8:23 with factor 1 - [Info] unrolling loop at 08-bad.c:14:8-14:24 with factor 1 int main(void) { int m ; @@ -8,11 +6,6 @@ { { goto switch_default; - { - if (! 0) { - goto loop_end; - } - loop_continue_0: /* CIL Label */ ; switch_default: /* CIL Label */ { while (1) { @@ -23,16 +16,9 @@ } while_break: /* CIL Label */ ; } - loop_end: /* CIL Label */ ; - } switch_break: /* CIL Label */ ; } goto lab; - { - if (! 0) { - goto loop_end___0; - } - loop_continue_0___0: /* CIL Label */ ; lab: { while (1) { @@ -43,8 +29,6 @@ } while_break___0: /* CIL Label */ ; } - loop_end___0: /* CIL Label */ ; - } return (0); } } diff --git a/tests/regression/55-loop-unrolling/11-unrolled-loop-invariant.c b/tests/regression/55-loop-unrolling/11-unrolled-loop-invariant.c index 096c031cf1..4a3f7c1cfa 100644 --- a/tests/regression/55-loop-unrolling/11-unrolled-loop-invariant.c +++ b/tests/regression/55-loop-unrolling/11-unrolled-loop-invariant.c @@ -1,3 +1,4 @@ +// CRAM int main() { int i = 0; while (i < 10) diff --git a/tests/regression/55-loop-unrolling/11-unrolled-loop-invariant.t b/tests/regression/55-loop-unrolling/11-unrolled-loop-invariant.t index 32ec663717..860ffae3bd 100644 --- a/tests/regression/55-loop-unrolling/11-unrolled-loop-invariant.t +++ b/tests/regression/55-loop-unrolling/11-unrolled-loop-invariant.t @@ -1,7 +1,7 @@ $ cfgDot --unroll 1 11-unrolled-loop-invariant.c - [Info] unrolling loop at 11-unrolled-loop-invariant.c:3:3-4:8 with factor 1 - [Info] unrolling loop at 11-unrolled-loop-invariant.c:8:5-9:10 with factor 1 - [Info] unrolling loop at 11-unrolled-loop-invariant.c:7:3-11:3 with factor 1 + [Info] unrolling loop at 11-unrolled-loop-invariant.c:4:3-5:8 with factor 1 + [Info] unrolling loop at 11-unrolled-loop-invariant.c:9:5-10:10 with factor 1 + [Info] unrolling loop at 11-unrolled-loop-invariant.c:8:3-12:3 with factor 1 $ graph-easy --as=boxart main.dot ┌──────────────────────────────────────────────────────┐ @@ -11,160 +11,160 @@ │ (body) ▼ ┌──────────────────────────────────────────────────────┐ - │ 11-unrolled-loop-invariant.c:2:3-2:12 │ - │ (11-unrolled-loop-invariant.c:2:7-2:12 (synthetic)) │ - │ YAML loc: 11-unrolled-loop-invariant.c:2:3-2:12 │ + │ 11-unrolled-loop-invariant.c:3:3-3:12 │ + │ (11-unrolled-loop-invariant.c:3:7-3:12 (synthetic)) │ + │ YAML loc: 11-unrolled-loop-invariant.c:3:3-3:12 │ │ GraphML: true; server: false │ └──────────────────────────────────────────────────────┘ │ │ i = 0 ▼ ┌──────────────────────────────────────────────────────┐ - │ 11-unrolled-loop-invariant.c:3:3-4:8 (synthetic) │ - │ (11-unrolled-loop-invariant.c:3:10-3:16 (synthetic)) │ - │ YAML loop: 11-unrolled-loop-invariant.c:3:3-4:8 │ + │ 11-unrolled-loop-invariant.c:4:3-5:8 (synthetic) │ + │ (11-unrolled-loop-invariant.c:4:10-4:16 (synthetic)) │ + │ YAML loop: 11-unrolled-loop-invariant.c:4:3-5:8 │ │ GraphML: true; server: false │ - ┌───────────── │ loop: 11-unrolled-loop-invariant.c:3:3-4:8 │ ·┐ + ┌───────────── │ loop: 11-unrolled-loop-invariant.c:4:3-5:8 │ ·┐ │ └──────────────────────────────────────────────────────┘ : │ │ : │ │ Pos(i < 10) : │ ▼ : │ ┌──────────────────────────────────────────────────────┐ : - │ │ 11-unrolled-loop-invariant.c:4:5-4:8 │ : - │ │ (11-unrolled-loop-invariant.c:4:5-4:8) │ : - │ │ YAML loc: 11-unrolled-loop-invariant.c:4:5-4:8 │ : + │ │ 11-unrolled-loop-invariant.c:5:5-5:8 │ : + │ │ (11-unrolled-loop-invariant.c:5:5-5:8) │ : + │ │ YAML loc: 11-unrolled-loop-invariant.c:5:5-5:8 │ : │ │ GraphML: true; server: true │ ·┼····································································┐ │ └──────────────────────────────────────────────────────┘ : : │ │ : : │ │ i = i + 1 : : │ Neg(i < 10) ▼ ▼ : │ ┌───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ : - │ │ 11-unrolled-loop-invariant.c:3:3-4:8 (synthetic) │ : - │ │ (11-unrolled-loop-invariant.c:3:10-3:16 (synthetic)) │ : - │ │ YAML loop: 11-unrolled-loop-invariant.c:3:3-4:8 │ : + │ │ 11-unrolled-loop-invariant.c:4:3-5:8 (synthetic) │ : + │ │ (11-unrolled-loop-invariant.c:4:10-4:16 (synthetic)) │ : + │ │ YAML loop: 11-unrolled-loop-invariant.c:4:3-5:8 │ : │ │ GraphML: true; server: false │ : - │ │ loop: 11-unrolled-loop-invariant.c:3:3-4:8 │ : + │ │ loop: 11-unrolled-loop-invariant.c:4:3-5:8 │ : │ └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ : │ │ │ ▲ : │ │ Neg(i < 10) │ Pos(i < 10) │ i = i + 1 : │ ▼ │ │ : │ ┌──────────────────────────────────────────────────────┐ │ ┌───────────────────────────────────────────────────┐ : - │ │ 11-unrolled-loop-invariant.c:6:3-6:19 │ │ │ 11-unrolled-loop-invariant.c:4:5-4:8 │ : - │ │ (11-unrolled-loop-invariant.c:6:7-6:12 (synthetic)) │ │ │ (11-unrolled-loop-invariant.c:4:5-4:8) │ : - │ │ YAML loc: 11-unrolled-loop-invariant.c:6:3-6:19 │ │ │ YAML loc: 11-unrolled-loop-invariant.c:4:5-4:8 │ : + │ │ 11-unrolled-loop-invariant.c:7:3-7:19 │ │ │ 11-unrolled-loop-invariant.c:5:5-5:8 │ : + │ │ (11-unrolled-loop-invariant.c:7:7-7:12 (synthetic)) │ │ │ (11-unrolled-loop-invariant.c:5:5-5:8) │ : + │ │ YAML loc: 11-unrolled-loop-invariant.c:7:3-7:19 │ │ │ YAML loc: 11-unrolled-loop-invariant.c:5:5-5:8 │ : └────────────▶ │ GraphML: true; server: false │ └───────────▶ │ GraphML: true; server: true │ ◀┘ └──────────────────────────────────────────────────────┘ └───────────────────────────────────────────────────┘ │ │ j = 0 ▼ ┌──────────────────────────────────────────────────────┐ - │ 11-unrolled-loop-invariant.c:6:3-6:19 (synthetic) │ - │ (11-unrolled-loop-invariant.c:6:14-6:19 (synthetic)) │ + │ 11-unrolled-loop-invariant.c:7:3-7:19 (synthetic) │ + │ (11-unrolled-loop-invariant.c:7:14-7:19 (synthetic)) │ │ GraphML: true; server: false │ └──────────────────────────────────────────────────────┘ │ │ k = 0 ▼ ┌──────────────────────────────────────────────────────┐ ┌───────────────────────────────────────────────────┐ ┌──────────────────┐ - │ 11-unrolled-loop-invariant.c:7:3-11:3 (synthetic) │ │ 11-unrolled-loop-invariant.c:12:3-12:11 │ │ │ - │ (11-unrolled-loop-invariant.c:7:10-7:16 (synthetic)) │ │ (11-unrolled-loop-invariant.c:12:10-12:11) │ │ │ - │ YAML loop: 11-unrolled-loop-invariant.c:7:3-11:3 │ │ YAML loc: 11-unrolled-loop-invariant.c:12:3-12:11 │ │ return of main() │ + │ 11-unrolled-loop-invariant.c:8:3-12:3 (synthetic) │ │ 11-unrolled-loop-invariant.c:13:3-13:11 │ │ │ + │ (11-unrolled-loop-invariant.c:8:10-8:16 (synthetic)) │ │ (11-unrolled-loop-invariant.c:13:10-13:11) │ │ │ + │ YAML loop: 11-unrolled-loop-invariant.c:8:3-12:3 │ │ YAML loc: 11-unrolled-loop-invariant.c:13:3-13:11 │ │ return of main() │ │ GraphML: true; server: false │ Neg(j < 10) │ GraphML: true; server: true │ return 0 │ │ - ┌············· │ loop: 11-unrolled-loop-invariant.c:7:3-11:3 │ ─────────────▶ │ │ ──────────▶ │ │ + ┌············· │ loop: 11-unrolled-loop-invariant.c:8:3-12:3 │ ─────────────▶ │ │ ──────────▶ │ │ : └──────────────────────────────────────────────────────┘ └───────────────────────────────────────────────────┘ └──────────────────┘ : │ ▲ Neg(j < 10) : │ Pos(j < 10) └──────────────────────────────────────────────────────────────────────────────────────────┐ : ▼ │ : ┌──────────────────────────────────────────────────────┐ │ - : │ 11-unrolled-loop-invariant.c:8:5-9:10 (synthetic) │ │ - : │ (11-unrolled-loop-invariant.c:8:12-8:19 (synthetic)) │ │ - : │ YAML loop: 11-unrolled-loop-invariant.c:8:5-9:10 │ │ + : │ 11-unrolled-loop-invariant.c:9:5-10:10 (synthetic) │ │ + : │ (11-unrolled-loop-invariant.c:9:12-9:19 (synthetic)) │ │ + : │ YAML loop: 11-unrolled-loop-invariant.c:9:5-10:10 │ │ : │ GraphML: true; server: false │ │ - ┌──────────────────────────┼───────────── │ loop: 11-unrolled-loop-invariant.c:8:5-9:10 │ ······································································┐ │ + ┌──────────────────────────┼───────────── │ loop: 11-unrolled-loop-invariant.c:9:5-10:10 │ ······································································┐ │ │ : └──────────────────────────────────────────────────────┘ : │ │ : │ : │ │ : │ Pos(k < 100) : │ │ : ▼ : │ │ : ┌──────────────────────────────────────────────────────┐ : │ - │ : │ 11-unrolled-loop-invariant.c:9:7-9:10 │ : │ - │ : │ (11-unrolled-loop-invariant.c:9:7-9:10) │ : │ - │ : │ YAML loc: 11-unrolled-loop-invariant.c:9:7-9:10 │ : │ + │ : │ 11-unrolled-loop-invariant.c:10:7-10:10 │ : │ + │ : │ (11-unrolled-loop-invariant.c:10:7-10:10) │ : │ + │ : │ YAML loc: 11-unrolled-loop-invariant.c:10:7-10:10 │ : │ │ : │ GraphML: true; server: true │ ······································································┼············┐ │ │ : └──────────────────────────────────────────────────────┘ : : │ │ : │ : : │ │ : │ k = k + 1 ┌────────────────────────────────────────────────────┼────────────┼────────────────────────┼─────────────┐ │ : ▼ │ : : │ │ │ : ┌───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ : : │ │ - │ : │ 11-unrolled-loop-invariant.c:8:5-9:10 (synthetic) │ : : │ │ - │ : │ (11-unrolled-loop-invariant.c:8:12-8:19 (synthetic)) │ : : │ │ - │ : │ YAML loop: 11-unrolled-loop-invariant.c:8:5-9:10 │ : : │ │ + │ : │ 11-unrolled-loop-invariant.c:9:5-10:10 (synthetic) │ : : │ │ + │ : │ (11-unrolled-loop-invariant.c:9:12-9:19 (synthetic)) │ : : │ │ + │ : │ YAML loop: 11-unrolled-loop-invariant.c:9:5-10:10 │ : : │ │ │ : │ GraphML: true; server: false │ : : │ │ - │ : │ loop: 11-unrolled-loop-invariant.c:8:5-9:10 │ : : │ │ + │ : │ loop: 11-unrolled-loop-invariant.c:9:5-10:10 │ : : │ │ │ : └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ : : │ │ │ : │ : ▲ : : │ │ │ : │ Pos(k < 100) : │ k = k + 1 : : │ │ │ : ▼ : │ : : │ │ │ : ┌──────────────────────────────────────────────────────┐ : │ : : │ │ - │ : │ 11-unrolled-loop-invariant.c:9:7-9:10 │ : │ : : │ │ - │ : │ (11-unrolled-loop-invariant.c:9:7-9:10) │ : │ : : │ │ - │ : │ YAML loc: 11-unrolled-loop-invariant.c:9:7-9:10 │ : │ : : │ │ + │ : │ 11-unrolled-loop-invariant.c:10:7-10:10 │ : │ : : │ │ + │ : │ (11-unrolled-loop-invariant.c:10:7-10:10) │ : │ : : │ │ + │ : │ YAML loc: 11-unrolled-loop-invariant.c:10:7-10:10 │ : │ : : │ │ │ : │ GraphML: true; server: true │ ─┼───────────────┘ : : │ │ │ : └──────────────────────────────────────────────────────┘ : : : │ │ │ : : : : : │ │ ┌────┘ : : : : : │ │ │ : ▼ : : : │ │ │ : ┌──────────────────────────────────────────────────────┐ : : : │ │ - │ : │ 11-unrolled-loop-invariant.c:9:7-9:10 │ : : : │ │ - │ : │ (11-unrolled-loop-invariant.c:9:7-9:10) │ : : : │ │ - │ : │ YAML loc: 11-unrolled-loop-invariant.c:9:7-9:10 │ : : : │ │ + │ : │ 11-unrolled-loop-invariant.c:10:7-10:10 │ : : : │ │ + │ : │ (11-unrolled-loop-invariant.c:10:7-10:10) │ : : : │ │ + │ : │ YAML loc: 11-unrolled-loop-invariant.c:10:7-10:10 │ : : : │ │ │ ┌··························┼············▶ │ GraphML: true; server: true │ ◀┼───────────────┐ : : │ │ │ : : └──────────────────────────────────────────────────────┘ : │ : : │ │ │ : : │ : │ : : │ │ │ : : │ k = k + 1 : │ Pos(k < 100) : : │ │ │ : : ▼ ▼ │ : : │ │ │ : : ┌───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ : : │ │ - │ : : │ 11-unrolled-loop-invariant.c:8:5-9:10 (synthetic) │ : : │ │ - │ : : │ (11-unrolled-loop-invariant.c:8:12-8:19 (synthetic)) │ : : │ │ - │ : : │ YAML loop: 11-unrolled-loop-invariant.c:8:5-9:10 │ : : │ │ + │ : : │ 11-unrolled-loop-invariant.c:9:5-10:10 (synthetic) │ : : │ │ + │ : : │ (11-unrolled-loop-invariant.c:9:12-9:19 (synthetic)) │ : : │ │ + │ : : │ YAML loop: 11-unrolled-loop-invariant.c:9:5-10:10 │ : : │ │ │ : k = k + 1 : │ GraphML: true; server: false │ : : │ │ - │ : ┌─────────────────────┼────────────▶ │ loop: 11-unrolled-loop-invariant.c:8:5-9:10 │ ◀┼············┼···················┐ │ │ + │ : ┌─────────────────────┼────────────▶ │ loop: 11-unrolled-loop-invariant.c:9:5-10:10 │ ◀┼············┼···················┐ │ │ │ : │ : └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ : : : │ │ │ : │ : │ : : : │ │ │ : │ : │ Neg(k < 100) : : : │ │ │ : │ : ▼ : : : │ │ │ : │ : ┌──────────────────────────────────────────────────────┐ : : : │ │ - │ : │ : │ 11-unrolled-loop-invariant.c:10:5-10:8 │ : : : │ │ - │ : │ : │ (11-unrolled-loop-invariant.c:10:5-10:8) │ : : : │ │ - │ : │ : │ YAML loc: 11-unrolled-loop-invariant.c:10:5-10:8 │ : : : │ │ + │ : │ : │ 11-unrolled-loop-invariant.c:11:5-11:8 │ : : : │ │ + │ : │ : │ (11-unrolled-loop-invariant.c:11:5-11:8) │ : : : │ │ + │ : │ : │ YAML loc: 11-unrolled-loop-invariant.c:11:5-11:8 │ : : : │ │ │ : │ ┌────────────────┼────────────▶ │ GraphML: true; server: true │ ◀·····································································┼············┼···················┼····┼·············┼····┐ │ : │ │ : └──────────────────────────────────────────────────────┘ : : : │ │ : │ : │ │ : │ : : : │ │ : │ : │ │ : │ j = j + 1 ┌────────────────────────────────────────────────────┼────────────┼───────────────────┼────┘ │ : │ : │ │ : ▼ │ : : : │ : │ : │ │ : ┌───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ : : : │ : - │ : │ │ : │ 11-unrolled-loop-invariant.c:7:3-11:3 (synthetic) │ : : : │ : - │ : │ │ : │ (11-unrolled-loop-invariant.c:7:10-7:16 (synthetic)) │ : : : │ : - │ : │ │ Neg(k < 100) : │ YAML loop: 11-unrolled-loop-invariant.c:7:3-11:3 │ : : : │ : + │ : │ │ : │ 11-unrolled-loop-invariant.c:8:3-12:3 (synthetic) │ : : : │ : + │ : │ │ : │ (11-unrolled-loop-invariant.c:8:10-8:16 (synthetic)) │ : : : │ : + │ : │ │ Neg(k < 100) : │ YAML loop: 11-unrolled-loop-invariant.c:8:3-12:3 │ : : : │ : │ : │ │ : │ GraphML: true; server: false │ : : : │ : - │ : │ │ └············▶ │ loop: 11-unrolled-loop-invariant.c:7:3-11:3 │ ◀┼────────────┼───────────────────┼────┐ │ : + │ : │ │ └············▶ │ loop: 11-unrolled-loop-invariant.c:8:3-12:3 │ ◀┼────────────┼───────────────────┼────┐ │ : │ : │ │ └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ : : : │ │ : │ : │ │ │ : : : │ │ : │ : │ │ │ Pos(j < 10) : : : │ │ : │ : │ │ ▼ : : : │ │ : │ : │ │ ┌───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ : : : │ │ : - │ : │ │ │ 11-unrolled-loop-invariant.c:8:5-9:10 (synthetic) │ : : : │ │ : - │ : │ │ │ (11-unrolled-loop-invariant.c:8:12-8:19 (synthetic)) │ : : : │ │ : - │ : │ │ │ YAML loop: 11-unrolled-loop-invariant.c:8:5-9:10 │ : : : │ j = j + 1 │ : + │ : │ │ │ 11-unrolled-loop-invariant.c:9:5-10:10 (synthetic) │ : : : │ │ : + │ : │ │ │ (11-unrolled-loop-invariant.c:9:12-9:19 (synthetic)) │ : : : │ │ : + │ : │ │ │ YAML loop: 11-unrolled-loop-invariant.c:9:5-10:10 │ : : : │ j = j + 1 │ : │ : │ │ │ GraphML: true; server: false │ : : : │ │ : - │ : │ └────────────────────────────── │ loop: 11-unrolled-loop-invariant.c:8:5-9:10 │ ◀┘ : : │ │ : + │ : │ └────────────────────────────── │ loop: 11-unrolled-loop-invariant.c:9:5-10:10 │ ◀┘ : : │ │ : │ : │ └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ : : │ │ : │ : │ │ : : : │ │ : │ : │ │ Pos(k < 100) └·································································┼···················┘ │ │ : │ : │ ▼ : │ │ : │ : │ ┌──────────────────────────────────────────────────────┐ : │ │ : - │ : │ │ 11-unrolled-loop-invariant.c:9:7-9:10 │ : │ │ : - │ : │ │ (11-unrolled-loop-invariant.c:9:7-9:10) │ : │ │ : - │ : │ │ YAML loc: 11-unrolled-loop-invariant.c:9:7-9:10 │ : │ │ : + │ : │ │ 11-unrolled-loop-invariant.c:10:7-10:10 │ : │ │ : + │ : │ │ (11-unrolled-loop-invariant.c:10:7-10:10) │ : │ │ : + │ : │ │ YAML loc: 11-unrolled-loop-invariant.c:10:7-10:10 │ : │ │ : │ : └─────────────────────────────────── │ GraphML: true; server: true │ ◀··················································································┘ ┌····┼·············┼····┘ │ : └──────────────────────────────────────────────────────┘ : │ │ │ : : : │ │ @@ -174,43 +174,46 @@ │ ┌·····························································································································································┘ │ │ │ : │ │ │ ┌──────────────────────────────────────────────────────┐ │ │ - │ │ 11-unrolled-loop-invariant.c:10:5-10:8 │ │ │ - │ │ (11-unrolled-loop-invariant.c:10:5-10:8) │ │ │ - │ Neg(k < 100) │ YAML loc: 11-unrolled-loop-invariant.c:10:5-10:8 │ │ │ + │ │ 11-unrolled-loop-invariant.c:11:5-11:8 │ │ │ + │ │ (11-unrolled-loop-invariant.c:11:5-11:8) │ │ │ + │ Neg(k < 100) │ YAML loc: 11-unrolled-loop-invariant.c:11:5-11:8 │ │ │ └────────────────────────────────────────────▶ │ GraphML: true; server: true │ ────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ │ └──────────────────────────────────────────────────────┘ │ ▲ Neg(k < 100) │ └────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ $ goblint --set lib.activated '[]' --set exp.unrolling-factor 5 --enable ana.int.interval --enable witness.yaml.enabled --set witness.yaml.entry-types '["location_invariant", "loop_invariant"]' 11-unrolled-loop-invariant.c - [Info] unrolling loop at 11-unrolled-loop-invariant.c:3:3-4:8 with factor 5 - [Info] unrolling loop at 11-unrolled-loop-invariant.c:8:5-9:10 with factor 5 - [Info] unrolling loop at 11-unrolled-loop-invariant.c:7:3-11:3 with factor 5 + [Info] unrolling loop at 11-unrolled-loop-invariant.c:4:3-5:8 with factor 5 + [Info] unrolling loop at 11-unrolled-loop-invariant.c:9:5-10:10 with factor 5 + [Info] unrolling loop at 11-unrolled-loop-invariant.c:8:3-12:3 with factor 5 [Info][Deadcode] Logical lines of code (LLoC) summary: live: 10 dead: 0 total lines: 10 - [Warning][Deadcode][CWE-570] condition 'k < 100' (possibly inserted by CIL) is always false (11-unrolled-loop-invariant.c:8:12-8:19) - [Warning][Deadcode][CWE-571] condition 'j < 10' (possibly inserted by CIL) is always true (11-unrolled-loop-invariant.c:7:10-7:16) - [Warning][Deadcode][CWE-570] condition 'k < 100' (possibly inserted by CIL) is always false (11-unrolled-loop-invariant.c:8:12-8:19) - [Warning][Deadcode][CWE-570] condition 'k < 100' (possibly inserted by CIL) is always false (11-unrolled-loop-invariant.c:8:12-8:19) - [Warning][Deadcode][CWE-571] condition 'j < 10' (possibly inserted by CIL) is always true (11-unrolled-loop-invariant.c:7:10-7:16) - [Warning][Deadcode][CWE-570] condition 'k < 100' (possibly inserted by CIL) is always false (11-unrolled-loop-invariant.c:8:12-8:19) - [Warning][Deadcode][CWE-571] condition 'i < 10' (possibly inserted by CIL) is always true (11-unrolled-loop-invariant.c:3:10-3:16) - [Warning][Deadcode][CWE-571] condition 'i < 10' (possibly inserted by CIL) is always true (11-unrolled-loop-invariant.c:3:10-3:16) - [Warning][Deadcode][CWE-571] condition 'i < 10' (possibly inserted by CIL) is always true (11-unrolled-loop-invariant.c:3:10-3:16) - [Warning][Deadcode][CWE-571] condition 'i < 10' (possibly inserted by CIL) is always true (11-unrolled-loop-invariant.c:3:10-3:16) - [Warning][Deadcode][CWE-571] condition 'i < 10' (possibly inserted by CIL) is always true (11-unrolled-loop-invariant.c:3:10-3:16) - [Warning][Deadcode][CWE-571] condition 'j < 10' (possibly inserted by CIL) is always true (11-unrolled-loop-invariant.c:7:10-7:16) - [Warning][Deadcode][CWE-570] condition 'k < 100' (possibly inserted by CIL) is always false (11-unrolled-loop-invariant.c:8:12-8:19) - [Warning][Deadcode][CWE-571] condition 'j < 10' (possibly inserted by CIL) is always true (11-unrolled-loop-invariant.c:7:10-7:16) - [Warning][Deadcode][CWE-571] condition 'k < 100' (possibly inserted by CIL) is always true (11-unrolled-loop-invariant.c:8:12-8:19) - [Warning][Deadcode][CWE-571] condition 'k < 100' (possibly inserted by CIL) is always true (11-unrolled-loop-invariant.c:8:12-8:19) - [Warning][Deadcode][CWE-571] condition 'k < 100' (possibly inserted by CIL) is always true (11-unrolled-loop-invariant.c:8:12-8:19) - [Warning][Deadcode][CWE-571] condition 'k < 100' (possibly inserted by CIL) is always true (11-unrolled-loop-invariant.c:8:12-8:19) - [Warning][Deadcode][CWE-571] condition 'k < 100' (possibly inserted by CIL) is always true (11-unrolled-loop-invariant.c:8:12-8:19) - [Warning][Deadcode][CWE-571] condition 'j < 10' (possibly inserted by CIL) is always true (11-unrolled-loop-invariant.c:7:10-7:16) + [Warning][Deadcode][CWE-570] condition 'k < 100' (possibly inserted by CIL) is always false (11-unrolled-loop-invariant.c:9:12-9:19) + [Warning][Deadcode][CWE-571] condition 'j < 10' (possibly inserted by CIL) is always true (11-unrolled-loop-invariant.c:8:10-8:16) + [Warning][Deadcode][CWE-570] condition 'k < 100' (possibly inserted by CIL) is always false (11-unrolled-loop-invariant.c:9:12-9:19) + [Warning][Deadcode][CWE-570] condition 'k < 100' (possibly inserted by CIL) is always false (11-unrolled-loop-invariant.c:9:12-9:19) + [Warning][Deadcode][CWE-571] condition 'j < 10' (possibly inserted by CIL) is always true (11-unrolled-loop-invariant.c:8:10-8:16) + [Warning][Deadcode][CWE-570] condition 'k < 100' (possibly inserted by CIL) is always false (11-unrolled-loop-invariant.c:9:12-9:19) + [Warning][Deadcode][CWE-571] condition 'i < 10' (possibly inserted by CIL) is always true (11-unrolled-loop-invariant.c:4:10-4:16) + [Warning][Deadcode][CWE-571] condition 'i < 10' (possibly inserted by CIL) is always true (11-unrolled-loop-invariant.c:4:10-4:16) + [Warning][Deadcode][CWE-571] condition 'i < 10' (possibly inserted by CIL) is always true (11-unrolled-loop-invariant.c:4:10-4:16) + [Warning][Deadcode][CWE-571] condition 'i < 10' (possibly inserted by CIL) is always true (11-unrolled-loop-invariant.c:4:10-4:16) + [Warning][Deadcode][CWE-571] condition 'i < 10' (possibly inserted by CIL) is always true (11-unrolled-loop-invariant.c:4:10-4:16) + [Warning][Deadcode][CWE-571] condition 'j < 10' (possibly inserted by CIL) is always true (11-unrolled-loop-invariant.c:8:10-8:16) + [Warning][Deadcode][CWE-570] condition 'k < 100' (possibly inserted by CIL) is always false (11-unrolled-loop-invariant.c:9:12-9:19) + [Warning][Deadcode][CWE-571] condition 'j < 10' (possibly inserted by CIL) is always true (11-unrolled-loop-invariant.c:8:10-8:16) + [Warning][Deadcode][CWE-571] condition 'k < 100' (possibly inserted by CIL) is always true (11-unrolled-loop-invariant.c:9:12-9:19) + [Warning][Deadcode][CWE-571] condition 'k < 100' (possibly inserted by CIL) is always true (11-unrolled-loop-invariant.c:9:12-9:19) + [Warning][Deadcode][CWE-571] condition 'k < 100' (possibly inserted by CIL) is always true (11-unrolled-loop-invariant.c:9:12-9:19) + [Warning][Deadcode][CWE-571] condition 'k < 100' (possibly inserted by CIL) is always true (11-unrolled-loop-invariant.c:9:12-9:19) + [Warning][Deadcode][CWE-571] condition 'k < 100' (possibly inserted by CIL) is always true (11-unrolled-loop-invariant.c:9:12-9:19) + [Warning][Deadcode][CWE-571] condition 'j < 10' (possibly inserted by CIL) is always true (11-unrolled-loop-invariant.c:8:10-8:16) [Info][Witness] witness generation summary: + location invariants: 11 + loop invariants: 5 + flow-insensitive invariants: 0 total generation entries: 16 $ yamlWitnessStrip < witness.yml @@ -218,7 +221,7 @@ location: file_name: 11-unrolled-loop-invariant.c file_hash: $FILE_HASH - line: 8 + line: 9 column: 5 function: main loop_invariant: @@ -229,7 +232,7 @@ location: file_name: 11-unrolled-loop-invariant.c file_hash: $FILE_HASH - line: 8 + line: 9 column: 5 function: main loop_invariant: @@ -243,7 +246,7 @@ location: file_name: 11-unrolled-loop-invariant.c file_hash: $FILE_HASH - line: 7 + line: 8 column: 3 function: main loop_invariant: @@ -254,7 +257,7 @@ location: file_name: 11-unrolled-loop-invariant.c file_hash: $FILE_HASH - line: 7 + line: 8 column: 3 function: main loop_invariant: @@ -266,7 +269,7 @@ location: file_name: 11-unrolled-loop-invariant.c file_hash: $FILE_HASH - line: 3 + line: 4 column: 3 function: main loop_invariant: @@ -278,7 +281,7 @@ location: file_name: 11-unrolled-loop-invariant.c file_hash: $FILE_HASH - line: 12 + line: 13 column: 3 function: main location_invariant: @@ -289,7 +292,7 @@ location: file_name: 11-unrolled-loop-invariant.c file_hash: $FILE_HASH - line: 12 + line: 13 column: 3 function: main location_invariant: @@ -300,7 +303,7 @@ location: file_name: 11-unrolled-loop-invariant.c file_hash: $FILE_HASH - line: 12 + line: 13 column: 3 function: main location_invariant: @@ -311,7 +314,7 @@ location: file_name: 11-unrolled-loop-invariant.c file_hash: $FILE_HASH - line: 10 + line: 11 column: 5 function: main location_invariant: @@ -322,7 +325,7 @@ location: file_name: 11-unrolled-loop-invariant.c file_hash: $FILE_HASH - line: 10 + line: 11 column: 5 function: main location_invariant: @@ -333,7 +336,7 @@ location: file_name: 11-unrolled-loop-invariant.c file_hash: $FILE_HASH - line: 10 + line: 11 column: 5 function: main location_invariant: @@ -345,7 +348,7 @@ location: file_name: 11-unrolled-loop-invariant.c file_hash: $FILE_HASH - line: 9 + line: 10 column: 7 function: main location_invariant: @@ -356,7 +359,7 @@ location: file_name: 11-unrolled-loop-invariant.c file_hash: $FILE_HASH - line: 9 + line: 10 column: 7 function: main location_invariant: @@ -367,7 +370,7 @@ location: file_name: 11-unrolled-loop-invariant.c file_hash: $FILE_HASH - line: 9 + line: 10 column: 7 function: main location_invariant: @@ -379,7 +382,7 @@ location: file_name: 11-unrolled-loop-invariant.c file_hash: $FILE_HASH - line: 6 + line: 7 column: 3 function: main location_invariant: @@ -390,7 +393,7 @@ location: file_name: 11-unrolled-loop-invariant.c file_hash: $FILE_HASH - line: 4 + line: 5 column: 5 function: main location_invariant: diff --git a/tests/regression/55-loop-unrolling/12-loop-no-overflows.c b/tests/regression/55-loop-unrolling/12-loop-no-overflows.c new file mode 100644 index 0000000000..03554e06c5 --- /dev/null +++ b/tests/regression/55-loop-unrolling/12-loop-no-overflows.c @@ -0,0 +1,37 @@ +// PARAM: --enable ana.int.interval_set +// extracted from SV-COMP task ldv-memsafety/memleaks_test12-2.i + +typedef unsigned int size_t; +struct ldv_list_head { + struct ldv_list_head *next, *prev; +}; +struct ldv_list_head ldv_global_msg_list = {&(ldv_global_msg_list), &(ldv_global_msg_list)}; +struct ldv_msg { + void *data; + struct ldv_list_head list; +}; + +static inline void __ldv_list_del(struct ldv_list_head *prev, struct ldv_list_head *next) { + next->prev = prev; + prev->next = next; +} + +static inline void ldv_list_del(struct ldv_list_head *entry) { + __ldv_list_del(entry->prev, entry->next); +} + +void ldv_msg_free(struct ldv_msg *msg) { + free(msg->data); + free(msg); +} + +// ldv_destroy_msgs +void main(void) { + struct ldv_msg *msg; + struct ldv_msg *n; + for (msg = ({ const typeof( ((typeof(*msg) *)0)->list ) *__mptr = ((&ldv_global_msg_list)->next); (typeof(*msg) *)( (char *)__mptr - ((size_t) &((typeof(*msg) *)0)->list) ); }), n = ({ const typeof( ((typeof(*(msg)) *)0)->list ) *__mptr = ((msg)->list.next); (typeof(*(msg)) *)( (char *)__mptr - ((size_t) &((typeof(*(msg)) *)0)->list) ); }); &msg->list != (&ldv_global_msg_list); msg = n, n = ({ const typeof( ((typeof(*(n)) *)0)->list ) *__mptr = ((n)->list.next); (typeof(*(n)) *)( (char *)__mptr - ((size_t) &((typeof(*(n)) *)0)->list) ); })) // NOWARN + { + ldv_list_del(&msg->list); + ldv_msg_free(msg); + } +} \ No newline at end of file diff --git a/tests/regression/55-loop-unrolling/13-unrolled-loop-no-overflows.c b/tests/regression/55-loop-unrolling/13-unrolled-loop-no-overflows.c new file mode 100644 index 0000000000..84700ac9da --- /dev/null +++ b/tests/regression/55-loop-unrolling/13-unrolled-loop-no-overflows.c @@ -0,0 +1,72 @@ +// PARAM: --enable ana.int.interval_set +// extracted from SV-COMP task ldv-memsafety/memleaks_test12-2.i with --set exp.unrolling-factor 1 + +typedef unsigned int size_t; +struct ldv_list_head { + struct ldv_list_head *next, *prev; +}; +struct ldv_list_head ldv_global_msg_list = {&(ldv_global_msg_list), &(ldv_global_msg_list)}; +struct ldv_msg { + void *data; + struct ldv_list_head list; +}; + +__inline static void __ldv_list_del(struct ldv_list_head *prev, struct ldv_list_head *next) { + next->prev = prev; + prev->next = next; + return; +} + +__inline static void ldv_list_del(struct ldv_list_head *entry) { + __ldv_list_del(entry->prev, entry->next); + return; +} + +void ldv_msg_free(struct ldv_msg *msg) { + free(msg->data); + free((void *)msg); + return; +} + +// ldv_destroy_msgs +void main(void) { + struct ldv_msg *msg; + struct ldv_msg *n; + struct ldv_list_head const *__mptr; + struct ldv_list_head const *__mptr___0; + struct ldv_list_head const *__mptr___1; + + __mptr = (struct ldv_list_head const *)ldv_global_msg_list.next; + msg = (struct ldv_msg *)((char *)__mptr - (size_t)(&((struct ldv_msg *)0)->list)); + __mptr___0 = (struct ldv_list_head const *)msg->list.next; + n = (struct ldv_msg *)((char *)__mptr___0 - (size_t)(&((struct ldv_msg *)0)->list)); + + if (!((unsigned long)(&msg->list) != (unsigned long)(&ldv_global_msg_list))) { // NOWARN + goto loop_end; + } + + ldv_list_del(&msg->list); + ldv_msg_free(msg); + msg = n; + __mptr___1 = (struct ldv_list_head const *)n->list.next; + n = (struct ldv_msg *)((char *)__mptr___1 - (size_t)(&((struct ldv_msg *)0)->list)); + + loop_continue_0: /* CIL Label */; + { + while (1) { + while_continue: /* CIL Label */; + if (!((unsigned long)(&msg->list) != (unsigned long)(&ldv_global_msg_list))) { + goto while_break; + } + + ldv_list_del(&msg->list); + ldv_msg_free(msg); + msg = n; + __mptr___1 = (struct ldv_list_head const *)n->list.next; + n = (struct ldv_msg *)((char *)__mptr___1 - (size_t)(&((struct ldv_msg *)0)->list)); + } + while_break: /* CIL Label */; + } + loop_end: /* CIL Label */; + return; +} \ No newline at end of file diff --git a/tests/regression/56-witness/05-prec-problem.t b/tests/regression/56-witness/05-prec-problem.t index 733f16269e..51f92ca203 100644 --- a/tests/regression/56-witness/05-prec-problem.t +++ b/tests/regression/56-witness/05-prec-problem.t @@ -6,6 +6,9 @@ total lines: 13 [Warning][Deadcode][CWE-570] condition '0' (possibly inserted by CIL) is always false (05-prec-problem.c:13:12-13:13) [Info][Witness] witness generation summary: + location invariants: 0 + loop invariants: 0 + flow-insensitive invariants: 0 total generation entries: 6 TODO: Don't generate duplicate entries from each context: should have generated just 3. diff --git a/tests/regression/56-witness/08-witness-all-locals.t b/tests/regression/56-witness/08-witness-all-locals.t index fc4462201d..fe6aefefbd 100644 --- a/tests/regression/56-witness/08-witness-all-locals.t +++ b/tests/regression/56-witness/08-witness-all-locals.t @@ -4,6 +4,9 @@ dead: 0 total lines: 4 [Info][Witness] witness generation summary: + location invariants: 3 + loop invariants: 0 + flow-insensitive invariants: 0 total generation entries: 3 $ yamlWitnessStrip < witness.yml @@ -50,6 +53,9 @@ Fewer entries are emitted if locals from nested block scopes are excluded: dead: 0 total lines: 4 [Info][Witness] witness generation summary: + location invariants: 2 + loop invariants: 0 + flow-insensitive invariants: 0 total generation entries: 2 $ yamlWitnessStrip < witness.yml diff --git a/tests/regression/56-witness/46-top-bool-invariant.c b/tests/regression/56-witness/46-top-bool-invariant.c index 2c90a55a2d..a11fea0991 100644 --- a/tests/regression/56-witness/46-top-bool-invariant.c +++ b/tests/regression/56-witness/46-top-bool-invariant.c @@ -1,5 +1,5 @@ // PARAM: --enable witness.yaml.enabled --set witness.yaml.entry-types '["location_invariant"]' --enable ana.int.def_exc --enable ana.int.interval --enable ana.int.enums --enable ana.int.congruence --enable ana.int.interval_set --disable witness.invariant.inexact-type-bounds - +// CRAM int main() { _Bool x; return 0; diff --git a/tests/regression/56-witness/46-top-bool-invariant.t b/tests/regression/56-witness/46-top-bool-invariant.t index b04d33dda8..be41ef58f2 100644 --- a/tests/regression/56-witness/46-top-bool-invariant.t +++ b/tests/regression/56-witness/46-top-bool-invariant.t @@ -6,6 +6,9 @@ def_exc only: dead: 0 total lines: 2 [Info][Witness] witness generation summary: + location invariants: 2 + loop invariants: 0 + flow-insensitive invariants: 0 total generation entries: 2 $ yamlWitnessStrip < witness.yml @@ -40,6 +43,9 @@ interval only: dead: 0 total lines: 2 [Info][Witness] witness generation summary: + location invariants: 2 + loop invariants: 0 + flow-insensitive invariants: 0 total generation entries: 2 $ yamlWitnessStrip < witness.yml @@ -74,6 +80,9 @@ enums only: dead: 0 total lines: 2 [Info][Witness] witness generation summary: + location invariants: 1 + loop invariants: 0 + flow-insensitive invariants: 0 total generation entries: 1 $ yamlWitnessStrip < witness.yml @@ -97,6 +106,9 @@ congruence only: dead: 0 total lines: 2 [Info][Witness] witness generation summary: + location invariants: 0 + loop invariants: 0 + flow-insensitive invariants: 0 total generation entries: 0 $ yamlWitnessStrip < witness.yml @@ -110,6 +122,9 @@ interval_set only: dead: 0 total lines: 2 [Info][Witness] witness generation summary: + location invariants: 2 + loop invariants: 0 + flow-insensitive invariants: 0 total generation entries: 2 $ yamlWitnessStrip < witness.yml @@ -144,7 +159,10 @@ all: dead: 0 total lines: 2 [Info][Witness] witness generation summary: - total generation entries: 3 + location invariants: 1 + loop invariants: 0 + flow-insensitive invariants: 0 + total generation entries: 1 $ yamlWitnessStrip < witness.yml - entry_type: location_invariant @@ -158,28 +176,6 @@ all: string: x == (_Bool)0 || x == (_Bool)1 type: assertion format: C - - entry_type: location_invariant - location: - file_name: 46-top-bool-invariant.c - file_hash: $FILE_HASH - line: 5 - column: 3 - function: main - location_invariant: - string: x <= (_Bool)1 - type: assertion - format: C - - entry_type: location_invariant - location: - file_name: 46-top-bool-invariant.c - file_hash: $FILE_HASH - line: 5 - column: 3 - function: main - location_invariant: - string: (_Bool)0 <= x - type: assertion - format: C all without inexact-type-bounds: @@ -189,6 +185,9 @@ all without inexact-type-bounds: dead: 0 total lines: 2 [Info][Witness] witness generation summary: + location invariants: 0 + loop invariants: 0 + flow-insensitive invariants: 0 total generation entries: 0 $ yamlWitnessStrip < witness.yml diff --git a/tests/regression/56-witness/47-top-int-invariant.c b/tests/regression/56-witness/47-top-int-invariant.c index 45f6106ce8..ef39ecd9b2 100644 --- a/tests/regression/56-witness/47-top-int-invariant.c +++ b/tests/regression/56-witness/47-top-int-invariant.c @@ -1,5 +1,5 @@ // PARAM: --enable witness.yaml.enabled --set witness.yaml.entry-types '["location_invariant"]' --enable ana.int.def_exc --enable ana.int.interval --enable ana.int.enums --enable ana.int.congruence --enable ana.int.interval_set --disable witness.invariant.inexact-type-bounds - +// CRAM int main() { int x; return 0; diff --git a/tests/regression/56-witness/47-top-int-invariant.t b/tests/regression/56-witness/47-top-int-invariant.t index cdfe65673f..35d5978c00 100644 --- a/tests/regression/56-witness/47-top-int-invariant.t +++ b/tests/regression/56-witness/47-top-int-invariant.t @@ -6,6 +6,9 @@ def_exc only: dead: 0 total lines: 2 [Info][Witness] witness generation summary: + location invariants: 2 + loop invariants: 0 + flow-insensitive invariants: 0 total generation entries: 2 $ yamlWitnessStrip < witness.yml @@ -40,6 +43,9 @@ interval only: dead: 0 total lines: 2 [Info][Witness] witness generation summary: + location invariants: 2 + loop invariants: 0 + flow-insensitive invariants: 0 total generation entries: 2 $ yamlWitnessStrip < witness.yml @@ -74,6 +80,9 @@ enums only: dead: 0 total lines: 2 [Info][Witness] witness generation summary: + location invariants: 2 + loop invariants: 0 + flow-insensitive invariants: 0 total generation entries: 2 $ yamlWitnessStrip < witness.yml @@ -108,6 +117,9 @@ congruence only: dead: 0 total lines: 2 [Info][Witness] witness generation summary: + location invariants: 0 + loop invariants: 0 + flow-insensitive invariants: 0 total generation entries: 0 $ yamlWitnessStrip < witness.yml @@ -121,6 +133,9 @@ interval_set only: dead: 0 total lines: 2 [Info][Witness] witness generation summary: + location invariants: 2 + loop invariants: 0 + flow-insensitive invariants: 0 total generation entries: 2 $ yamlWitnessStrip < witness.yml @@ -155,6 +170,9 @@ all: dead: 0 total lines: 2 [Info][Witness] witness generation summary: + location invariants: 2 + loop invariants: 0 + flow-insensitive invariants: 0 total generation entries: 2 $ yamlWitnessStrip < witness.yml @@ -189,6 +207,9 @@ all without inexact-type-bounds: dead: 0 total lines: 2 [Info][Witness] witness generation summary: + location invariants: 0 + loop invariants: 0 + flow-insensitive invariants: 0 total generation entries: 0 $ yamlWitnessStrip < witness.yml diff --git a/tests/regression/56-witness/50-witness-lifter-fp1.c b/tests/regression/56-witness/50-witness-lifter-fp1.c index 8c6e45e648..1db839cd7b 100644 --- a/tests/regression/56-witness/50-witness-lifter-fp1.c +++ b/tests/regression/56-witness/50-witness-lifter-fp1.c @@ -1,5 +1,5 @@ // PARAM: --enable ana.sv-comp.enabled --enable ana.sv-comp.functions --set ana.specification 'CHECK( init(main()), LTL(G ! call(reach_error())) )' --enable ana.int.interval -// previously fixpoint not reached +// FIXPOINT: previously fixpoint not reached // extracted from sv-benchmarks loops-crafted-1/loopv2 int SIZE = 50000001; int __VERIFIER_nondet_int(); diff --git a/tests/regression/56-witness/52-witness-lifter-ps2.c b/tests/regression/56-witness/52-witness-lifter-ps2.c index bcb7c1410c..1eb3495235 100644 --- a/tests/regression/56-witness/52-witness-lifter-ps2.c +++ b/tests/regression/56-witness/52-witness-lifter-ps2.c @@ -1,4 +1,5 @@ // PARAM: --enable ana.sv-comp.enabled --enable ana.sv-comp.functions --enable witness.graphml.enabled --set ana.specification 'CHECK( init(main()), LTL(G valid-memtrack) )' --set ana.activated[+] memLeak --set ana.path_sens[+] memLeak --set ana.malloc.unique_address_count 1 +// NOCRASH struct _twoIntsStruct { int intOne ; int intTwo ; diff --git a/tests/regression/56-witness/53-witness-lifter-ps3.c b/tests/regression/56-witness/53-witness-lifter-ps3.c index 06b73b3888..ff368a88fc 100644 --- a/tests/regression/56-witness/53-witness-lifter-ps3.c +++ b/tests/regression/56-witness/53-witness-lifter-ps3.c @@ -1,4 +1,5 @@ // PARAM: --enable ana.sv-comp.enabled --enable ana.sv-comp.functions --enable witness.graphml.enabled --set ana.specification 'CHECK( init(main()), LTL(G valid-memtrack) )' --set ana.activated[+] memLeak --set ana.path_sens[+] memLeak --set ana.malloc.unique_address_count 1 +// NOCRASH struct _twoIntsStruct { int intOne ; int intTwo ; diff --git a/tests/regression/56-witness/54-witness-lifter-abortUnless.c b/tests/regression/56-witness/54-witness-lifter-abortUnless.c new file mode 100644 index 0000000000..9f2a984777 --- /dev/null +++ b/tests/regression/56-witness/54-witness-lifter-abortUnless.c @@ -0,0 +1,23 @@ +// PARAM: --set ana.activated[+] abortUnless --enable exp.arg.enabled +// NOCRASH + +void printLine() +{ + +} + +void goodB2G1Sink(int data) +{ + // abortUnless=true (one int argument, nothing has ruined it yet) + // sync map: {[abortUnless=true, ...] -> {[abortUnless=true, ...]}} + // enter: (abortUnless=false, ...) (enter ruined it) + // left sync map must be updated to: {[abortUnless=false, ...] -> {[abortUnless=true, ...]}} + printLine(); + // left sync map is looked up by abortUnless=false in combine_env +} + +int main() +{ + goodB2G1Sink(0); + return 0; +} diff --git a/tests/regression/56-witness/54-witness-lifter-abortUnless.t b/tests/regression/56-witness/54-witness-lifter-abortUnless.t new file mode 100644 index 0000000000..7168a63afa --- /dev/null +++ b/tests/regression/56-witness/54-witness-lifter-abortUnless.t @@ -0,0 +1,70 @@ + $ goblint --set ana.activated[+] abortUnless --enable exp.arg.enabled --set exp.arg.dot.path arg.dot --set exp.arg.dot.node-label empty 54-witness-lifter-abortUnless.c + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 8 + dead: 0 + total lines: 8 + + $ graph-easy --as=boxart arg.dot + ┌───────────────────────────┐ + │ _ │ + └───────────────────────────┘ + │ + │ Entry main + ▼ + ┌───────────────────────────┐ + │ _ │ ·┐ + └───────────────────────────┘ : + │ : + │ InlineEntry '(0)' : + ▼ : + ┌───────────────────────────┐ : + │ _ │ : + └───────────────────────────┘ : + │ : + │ Entry goodB2G1Sink : + ▼ : + ┌───────────────────────────┐ : + ┌··························· │ _ │ : + : └───────────────────────────┘ : + : │ : + : │ InlineEntry '()' : + : ▼ : + : ┌───────────────────────────┐ : + : │ _ │ : + : └───────────────────────────┘ : + : │ : + : │ Entry printLine : + : ▼ : + : ┌───────────────────────────┐ : + : Inlined Proc 'printLine()' │ _ │ : Inlined Proc 'goodB2G1Sink(0)' + : └───────────────────────────┘ : + : │ : + : │ Ret (None, printLine) : + : ▼ : + : ┌───────────────────────────┐ : + : │ _ │ : + : └───────────────────────────┘ : + : │ : + : │ InlineReturn : + : ▼ : + : ┌───────────────────────────┐ : + └··························▶ │ _ │ : + └───────────────────────────┘ : + │ : + │ Ret (None, goodB2G1Sink) : + ▼ : + ┌───────────────────────────┐ : + │ _ │ : + └───────────────────────────┘ : + │ : + │ InlineReturn : + ▼ : + ┌───────────────────────────┐ : + │ _ │ ◀┘ + └───────────────────────────┘ + │ + │ Ret (Some 0, main) + ▼ + ┌───────────────────────────┐ + │ _ │ + └───────────────────────────┘ diff --git a/tests/regression/56-witness/63-hh-ex3-term.c b/tests/regression/56-witness/63-hh-ex3-term.c index 80913c3b9d..6f21652226 100644 --- a/tests/regression/56-witness/63-hh-ex3-term.c +++ b/tests/regression/56-witness/63-hh-ex3-term.c @@ -1,4 +1,4 @@ -// SKIP PARAM: --enable ana.int.interval --set ana.activated[+] apron --set ana.apron.domain polyhedra --enable ana.apron.strengthening --set ana.activated[+] unassume --set witness.yaml.unassume 63-hh-ex3-term.yml --enable ana.widen.tokens --disable witness.invariant.other --enable exp.arg +// SKIP PARAM: --enable ana.int.interval --set ana.activated[+] apron --set ana.apron.domain polyhedra --enable ana.apron.strengthening --set ana.activated[+] unassume --set witness.yaml.unassume 63-hh-ex3-term.yml --enable ana.widen.tokens --disable witness.invariant.other --enable exp.arg.enabled extern void __assert_fail (const char *__assertion, const char *__file, unsigned int __line, const char *__function) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__noreturn__)); diff --git a/tests/regression/56-witness/64-ghost-multiple-protecting.c b/tests/regression/56-witness/64-ghost-multiple-protecting.c new file mode 100644 index 0000000000..a9fa3310e4 --- /dev/null +++ b/tests/regression/56-witness/64-ghost-multiple-protecting.c @@ -0,0 +1,41 @@ +// PARAM: --set ana.activated[+] mutexGhosts +#include +#include +int g1, g2; +pthread_mutex_t m1 = PTHREAD_MUTEX_INITIALIZER; +pthread_mutex_t m2 = PTHREAD_MUTEX_INITIALIZER; +// CRAM +void *t_fun(void *arg) { + pthread_mutex_lock(&m1); + pthread_mutex_lock(&m2); + g1 = 1; + g1 = 0; + pthread_mutex_unlock(&m2); + pthread_mutex_unlock(&m1); + + pthread_mutex_lock(&m1); + pthread_mutex_lock(&m2); + g2 = 1; + pthread_mutex_unlock(&m2); + pthread_mutex_lock(&m2); + g2 = 0; + pthread_mutex_unlock(&m2); + pthread_mutex_unlock(&m1); + return NULL; +} + +int main() { + pthread_t id; + pthread_create(&id, NULL, t_fun, NULL); + + /* pthread_mutex_lock(&m1); + __goblint_check(g1 == 0); // NOWARN (commented out) + __goblint_check(g2 == 0); // NOWARN (commented out) + pthread_mutex_unlock(&m1); + + pthread_mutex_lock(&m2); + __goblint_check(g1 == 0); // NOWARN (commented out) + __goblint_check(g2 == 0); // NOWARN (commented out) + pthread_mutex_unlock(&m2); */ + return 0; +} diff --git a/tests/regression/56-witness/64-ghost-multiple-protecting.t b/tests/regression/56-witness/64-ghost-multiple-protecting.t new file mode 100644 index 0000000000..cfa3995005 --- /dev/null +++ b/tests/regression/56-witness/64-ghost-multiple-protecting.t @@ -0,0 +1,508 @@ + $ goblint --set ana.base.privatization protection --enable witness.yaml.enabled --set ana.activated[+] mutexGhosts --set witness.yaml.entry-types '["flow_insensitive_invariant", "ghost_instrumentation"]' 64-ghost-multiple-protecting.c + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 19 + dead: 0 + total lines: 19 + [Info][Witness] witness generation summary: + location invariants: 0 + loop invariants: 0 + flow-insensitive invariants: 3 + total generation entries: 4 + [Info][Race] Memory locations race summary: + safe: 2 + vulnerable: 0 + unsafe: 0 + total memory locations: 2 + +protection doesn't have precise protected invariant for g2. + + $ yamlWitnessStrip < witness.yml + - entry_type: ghost_instrumentation + content: + ghost_variables: + - name: m1_locked + scope: global + type: int + initial: + value: "0" + format: c_expression + - name: m2_locked + scope: global + type: int + initial: + value: "0" + format: c_expression + - name: multithreaded + scope: global + type: int + initial: + value: "0" + format: c_expression + ghost_updates: + - location: + file_name: 64-ghost-multiple-protecting.c + file_hash: $FILE_HASH + line: 9 + column: 3 + function: t_fun + updates: + - variable: m1_locked + value: "1" + format: c_expression + - location: + file_name: 64-ghost-multiple-protecting.c + file_hash: $FILE_HASH + line: 10 + column: 3 + function: t_fun + updates: + - variable: m2_locked + value: "1" + format: c_expression + - location: + file_name: 64-ghost-multiple-protecting.c + file_hash: $FILE_HASH + line: 13 + column: 3 + function: t_fun + updates: + - variable: m2_locked + value: "0" + format: c_expression + - location: + file_name: 64-ghost-multiple-protecting.c + file_hash: $FILE_HASH + line: 14 + column: 3 + function: t_fun + updates: + - variable: m1_locked + value: "0" + format: c_expression + - location: + file_name: 64-ghost-multiple-protecting.c + file_hash: $FILE_HASH + line: 16 + column: 3 + function: t_fun + updates: + - variable: m1_locked + value: "1" + format: c_expression + - location: + file_name: 64-ghost-multiple-protecting.c + file_hash: $FILE_HASH + line: 17 + column: 3 + function: t_fun + updates: + - variable: m2_locked + value: "1" + format: c_expression + - location: + file_name: 64-ghost-multiple-protecting.c + file_hash: $FILE_HASH + line: 19 + column: 3 + function: t_fun + updates: + - variable: m2_locked + value: "0" + format: c_expression + - location: + file_name: 64-ghost-multiple-protecting.c + file_hash: $FILE_HASH + line: 20 + column: 3 + function: t_fun + updates: + - variable: m2_locked + value: "1" + format: c_expression + - location: + file_name: 64-ghost-multiple-protecting.c + file_hash: $FILE_HASH + line: 22 + column: 3 + function: t_fun + updates: + - variable: m2_locked + value: "0" + format: c_expression + - location: + file_name: 64-ghost-multiple-protecting.c + file_hash: $FILE_HASH + line: 23 + column: 3 + function: t_fun + updates: + - variable: m1_locked + value: "0" + format: c_expression + - location: + file_name: 64-ghost-multiple-protecting.c + file_hash: $FILE_HASH + line: 29 + column: 3 + function: main + updates: + - variable: multithreaded + value: "1" + format: c_expression + - entry_type: flow_insensitive_invariant + flow_insensitive_invariant: + string: '! multithreaded || (m2_locked || (m1_locked || g1 == 0))' + type: assertion + format: C + - entry_type: flow_insensitive_invariant + flow_insensitive_invariant: + string: '! multithreaded || (0 <= g2 && g2 <= 1)' + type: assertion + format: C + - entry_type: flow_insensitive_invariant + flow_insensitive_invariant: + string: '! multithreaded || (0 <= g1 && g1 <= 1)' + type: assertion + format: C + + $ goblint --set ana.base.privatization protection-read --enable witness.yaml.enabled --set ana.activated[+] mutexGhosts --set witness.yaml.entry-types '["flow_insensitive_invariant", "ghost_instrumentation"]' 64-ghost-multiple-protecting.c + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 19 + dead: 0 + total lines: 19 + [Info][Witness] witness generation summary: + location invariants: 0 + loop invariants: 0 + flow-insensitive invariants: 4 + total generation entries: 5 + [Info][Race] Memory locations race summary: + safe: 2 + vulnerable: 0 + unsafe: 0 + total memory locations: 2 + +protection-read has precise protected invariant for g2. + + $ yamlWitnessStrip < witness.yml + - entry_type: ghost_instrumentation + content: + ghost_variables: + - name: m1_locked + scope: global + type: int + initial: + value: "0" + format: c_expression + - name: m2_locked + scope: global + type: int + initial: + value: "0" + format: c_expression + - name: multithreaded + scope: global + type: int + initial: + value: "0" + format: c_expression + ghost_updates: + - location: + file_name: 64-ghost-multiple-protecting.c + file_hash: $FILE_HASH + line: 9 + column: 3 + function: t_fun + updates: + - variable: m1_locked + value: "1" + format: c_expression + - location: + file_name: 64-ghost-multiple-protecting.c + file_hash: $FILE_HASH + line: 10 + column: 3 + function: t_fun + updates: + - variable: m2_locked + value: "1" + format: c_expression + - location: + file_name: 64-ghost-multiple-protecting.c + file_hash: $FILE_HASH + line: 13 + column: 3 + function: t_fun + updates: + - variable: m2_locked + value: "0" + format: c_expression + - location: + file_name: 64-ghost-multiple-protecting.c + file_hash: $FILE_HASH + line: 14 + column: 3 + function: t_fun + updates: + - variable: m1_locked + value: "0" + format: c_expression + - location: + file_name: 64-ghost-multiple-protecting.c + file_hash: $FILE_HASH + line: 16 + column: 3 + function: t_fun + updates: + - variable: m1_locked + value: "1" + format: c_expression + - location: + file_name: 64-ghost-multiple-protecting.c + file_hash: $FILE_HASH + line: 17 + column: 3 + function: t_fun + updates: + - variable: m2_locked + value: "1" + format: c_expression + - location: + file_name: 64-ghost-multiple-protecting.c + file_hash: $FILE_HASH + line: 19 + column: 3 + function: t_fun + updates: + - variable: m2_locked + value: "0" + format: c_expression + - location: + file_name: 64-ghost-multiple-protecting.c + file_hash: $FILE_HASH + line: 20 + column: 3 + function: t_fun + updates: + - variable: m2_locked + value: "1" + format: c_expression + - location: + file_name: 64-ghost-multiple-protecting.c + file_hash: $FILE_HASH + line: 22 + column: 3 + function: t_fun + updates: + - variable: m2_locked + value: "0" + format: c_expression + - location: + file_name: 64-ghost-multiple-protecting.c + file_hash: $FILE_HASH + line: 23 + column: 3 + function: t_fun + updates: + - variable: m1_locked + value: "0" + format: c_expression + - location: + file_name: 64-ghost-multiple-protecting.c + file_hash: $FILE_HASH + line: 29 + column: 3 + function: main + updates: + - variable: multithreaded + value: "1" + format: c_expression + - entry_type: flow_insensitive_invariant + flow_insensitive_invariant: + string: '! multithreaded || (m2_locked || (m1_locked || g2 == 0))' + type: assertion + format: C + - entry_type: flow_insensitive_invariant + flow_insensitive_invariant: + string: '! multithreaded || (m2_locked || (m1_locked || g1 == 0))' + type: assertion + format: C + - entry_type: flow_insensitive_invariant + flow_insensitive_invariant: + string: '! multithreaded || (0 <= g2 && g2 <= 1)' + type: assertion + format: C + - entry_type: flow_insensitive_invariant + flow_insensitive_invariant: + string: '! multithreaded || (0 <= g1 && g1 <= 1)' + type: assertion + format: C + + $ goblint --set ana.base.privatization mutex-meet --enable witness.yaml.enabled --set ana.activated[+] mutexGhosts --set witness.yaml.entry-types '["flow_insensitive_invariant", "ghost_instrumentation"]' 64-ghost-multiple-protecting.c + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 19 + dead: 0 + total lines: 19 + [Info][Witness] witness generation summary: + location invariants: 0 + loop invariants: 0 + flow-insensitive invariants: 4 + total generation entries: 5 + [Info][Race] Memory locations race summary: + safe: 2 + vulnerable: 0 + unsafe: 0 + total memory locations: 2 + + $ yamlWitnessStrip < witness.yml + - entry_type: ghost_instrumentation + content: + ghost_variables: + - name: m1_locked + scope: global + type: int + initial: + value: "0" + format: c_expression + - name: m2_locked + scope: global + type: int + initial: + value: "0" + format: c_expression + - name: multithreaded + scope: global + type: int + initial: + value: "0" + format: c_expression + ghost_updates: + - location: + file_name: 64-ghost-multiple-protecting.c + file_hash: $FILE_HASH + line: 9 + column: 3 + function: t_fun + updates: + - variable: m1_locked + value: "1" + format: c_expression + - location: + file_name: 64-ghost-multiple-protecting.c + file_hash: $FILE_HASH + line: 10 + column: 3 + function: t_fun + updates: + - variable: m2_locked + value: "1" + format: c_expression + - location: + file_name: 64-ghost-multiple-protecting.c + file_hash: $FILE_HASH + line: 13 + column: 3 + function: t_fun + updates: + - variable: m2_locked + value: "0" + format: c_expression + - location: + file_name: 64-ghost-multiple-protecting.c + file_hash: $FILE_HASH + line: 14 + column: 3 + function: t_fun + updates: + - variable: m1_locked + value: "0" + format: c_expression + - location: + file_name: 64-ghost-multiple-protecting.c + file_hash: $FILE_HASH + line: 16 + column: 3 + function: t_fun + updates: + - variable: m1_locked + value: "1" + format: c_expression + - location: + file_name: 64-ghost-multiple-protecting.c + file_hash: $FILE_HASH + line: 17 + column: 3 + function: t_fun + updates: + - variable: m2_locked + value: "1" + format: c_expression + - location: + file_name: 64-ghost-multiple-protecting.c + file_hash: $FILE_HASH + line: 19 + column: 3 + function: t_fun + updates: + - variable: m2_locked + value: "0" + format: c_expression + - location: + file_name: 64-ghost-multiple-protecting.c + file_hash: $FILE_HASH + line: 20 + column: 3 + function: t_fun + updates: + - variable: m2_locked + value: "1" + format: c_expression + - location: + file_name: 64-ghost-multiple-protecting.c + file_hash: $FILE_HASH + line: 22 + column: 3 + function: t_fun + updates: + - variable: m2_locked + value: "0" + format: c_expression + - location: + file_name: 64-ghost-multiple-protecting.c + file_hash: $FILE_HASH + line: 23 + column: 3 + function: t_fun + updates: + - variable: m1_locked + value: "0" + format: c_expression + - location: + file_name: 64-ghost-multiple-protecting.c + file_hash: $FILE_HASH + line: 29 + column: 3 + function: main + updates: + - variable: multithreaded + value: "1" + format: c_expression + - entry_type: flow_insensitive_invariant + flow_insensitive_invariant: + string: '! multithreaded || (m2_locked || ((0 <= g2 && g2 <= 1) && g1 == 0))' + type: assertion + format: C + - entry_type: flow_insensitive_invariant + flow_insensitive_invariant: + string: '! multithreaded || (m1_locked || (g1 == 0 && g2 == 0))' + type: assertion + format: C + - entry_type: flow_insensitive_invariant + flow_insensitive_invariant: + string: '! multithreaded || (0 <= g2 && g2 <= 1)' + type: assertion + format: C + - entry_type: flow_insensitive_invariant + flow_insensitive_invariant: + string: '! multithreaded || (0 <= g1 && g1 <= 1)' + type: assertion + format: C diff --git a/tests/regression/56-witness/65-ghost-ambiguous-lock.c b/tests/regression/56-witness/65-ghost-ambiguous-lock.c new file mode 100644 index 0000000000..7719c0cec4 --- /dev/null +++ b/tests/regression/56-witness/65-ghost-ambiguous-lock.c @@ -0,0 +1,44 @@ +// PARAM: --set ana.activated[+] mutexGhosts +#include +#include +// CRAM +int g1, g2; +pthread_mutex_t m1 = PTHREAD_MUTEX_INITIALIZER; +pthread_mutex_t m2 = PTHREAD_MUTEX_INITIALIZER; + +void *t_fun(void *arg) { + pthread_mutex_lock(&m1); + g1 = 1; + g1 = 0; + pthread_mutex_unlock(&m1); + pthread_mutex_lock(&m2); + g2 = 1; + g2 = 0; + pthread_mutex_unlock(&m2); + return NULL; +} + +void fun(pthread_mutex_t *m) { + pthread_mutex_lock(m); + // what g2 can read? + pthread_mutex_unlock(m); +} + +int main() { + pthread_t id; + pthread_create(&id, NULL, t_fun, NULL); + + pthread_mutex_t *m; + int r; // rand + m = r ? &m1 : &m2; + + pthread_mutex_lock(m); + // what g1 can read? + pthread_mutex_unlock(m); + + if (r) + fun(&m1); + else + fun(&m2); + return 0; +} diff --git a/tests/regression/56-witness/65-ghost-ambiguous-lock.t b/tests/regression/56-witness/65-ghost-ambiguous-lock.t new file mode 100644 index 0000000000..36df2e4576 --- /dev/null +++ b/tests/regression/56-witness/65-ghost-ambiguous-lock.t @@ -0,0 +1,47 @@ + $ goblint --set ana.base.privatization protection --enable witness.yaml.enabled --set ana.activated[+] mutexGhosts --set witness.yaml.entry-types '["flow_insensitive_invariant", "ghost_instrumentation"]' 65-ghost-ambiguous-lock.c + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 23 + dead: 0 + total lines: 23 + [Info][Witness] witness generation summary: + location invariants: 0 + loop invariants: 0 + flow-insensitive invariants: 2 + total generation entries: 3 + [Info][Race] Memory locations race summary: + safe: 2 + vulnerable: 0 + unsafe: 0 + total memory locations: 2 + + $ yamlWitnessStrip < witness.yml + - entry_type: ghost_instrumentation + content: + ghost_variables: + - name: multithreaded + scope: global + type: int + initial: + value: "0" + format: c_expression + ghost_updates: + - location: + file_name: 65-ghost-ambiguous-lock.c + file_hash: $FILE_HASH + line: 29 + column: 3 + function: main + updates: + - variable: multithreaded + value: "1" + format: c_expression + - entry_type: flow_insensitive_invariant + flow_insensitive_invariant: + string: '! multithreaded || (0 <= g2 && g2 <= 1)' + type: assertion + format: C + - entry_type: flow_insensitive_invariant + flow_insensitive_invariant: + string: '! multithreaded || (0 <= g1 && g1 <= 1)' + type: assertion + format: C diff --git a/tests/regression/56-witness/66-ghost-alloc-lock.c b/tests/regression/56-witness/66-ghost-alloc-lock.c new file mode 100644 index 0000000000..073540b9db --- /dev/null +++ b/tests/regression/56-witness/66-ghost-alloc-lock.c @@ -0,0 +1,37 @@ +// PARAM: --set ana.activated[+] mutexGhosts --set ana.malloc.unique_address_count 1 +#include +#include + +int g1, g2; +pthread_mutex_t *m1; +pthread_mutex_t *m2; + +void *t_fun(void *arg) { + pthread_mutex_lock(m1); + g1 = 1; + g1 = 0; + pthread_mutex_unlock(m1); + pthread_mutex_lock(m2); + g2 = 1; + g2 = 0; + pthread_mutex_unlock(m2); + return NULL; +} + +int main() { + m1 = malloc(sizeof(pthread_mutex_t)); + pthread_mutex_init(m1, NULL); + m2 = malloc(sizeof(pthread_mutex_t)); + pthread_mutex_init(m2, NULL); + + pthread_t id; + pthread_create(&id, NULL, t_fun, NULL); + + pthread_mutex_lock(m1); + __goblint_check(g1 == 0); + pthread_mutex_unlock(m1); + pthread_mutex_lock(m2); + __goblint_check(g2 == 0); + pthread_mutex_unlock(m2); + return 0; +} diff --git a/tests/regression/56-witness/66-ghost-alloc-lock.t b/tests/regression/56-witness/66-ghost-alloc-lock.t new file mode 100644 index 0000000000..09c7c4250b --- /dev/null +++ b/tests/regression/56-witness/66-ghost-alloc-lock.t @@ -0,0 +1,151 @@ + $ goblint --set ana.base.privatization protection --enable witness.yaml.enabled --set ana.activated[+] mutexGhosts --set ana.malloc.unique_address_count 1 --set witness.yaml.entry-types '["flow_insensitive_invariant", "ghost_instrumentation"]' 66-ghost-alloc-lock.c + [Success][Assert] Assertion "g1 == 0" will succeed (66-ghost-alloc-lock.c:31:3-31:27) + [Success][Assert] Assertion "g2 == 0" will succeed (66-ghost-alloc-lock.c:34:3-34:27) + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 23 + dead: 0 + total lines: 23 + [Info][Witness] witness generation summary: + location invariants: 0 + loop invariants: 0 + flow-insensitive invariants: 4 + total generation entries: 5 + [Info][Race] Memory locations race summary: + safe: 4 + vulnerable: 0 + unsafe: 0 + total memory locations: 4 + + $ yamlWitnessStrip < witness.yml + - entry_type: ghost_instrumentation + content: + ghost_variables: + - name: alloc_m559918035_locked + scope: global + type: int + initial: + value: "0" + format: c_expression + - name: alloc_m861095507_locked + scope: global + type: int + initial: + value: "0" + format: c_expression + - name: multithreaded + scope: global + type: int + initial: + value: "0" + format: c_expression + ghost_updates: + - location: + file_name: 66-ghost-alloc-lock.c + file_hash: $FILE_HASH + line: 10 + column: 3 + function: t_fun + updates: + - variable: alloc_m559918035_locked + value: "1" + format: c_expression + - location: + file_name: 66-ghost-alloc-lock.c + file_hash: $FILE_HASH + line: 13 + column: 3 + function: t_fun + updates: + - variable: alloc_m559918035_locked + value: "0" + format: c_expression + - location: + file_name: 66-ghost-alloc-lock.c + file_hash: $FILE_HASH + line: 14 + column: 3 + function: t_fun + updates: + - variable: alloc_m861095507_locked + value: "1" + format: c_expression + - location: + file_name: 66-ghost-alloc-lock.c + file_hash: $FILE_HASH + line: 17 + column: 3 + function: t_fun + updates: + - variable: alloc_m861095507_locked + value: "0" + format: c_expression + - location: + file_name: 66-ghost-alloc-lock.c + file_hash: $FILE_HASH + line: 28 + column: 3 + function: main + updates: + - variable: multithreaded + value: "1" + format: c_expression + - location: + file_name: 66-ghost-alloc-lock.c + file_hash: $FILE_HASH + line: 30 + column: 3 + function: main + updates: + - variable: alloc_m559918035_locked + value: "1" + format: c_expression + - location: + file_name: 66-ghost-alloc-lock.c + file_hash: $FILE_HASH + line: 32 + column: 3 + function: main + updates: + - variable: alloc_m559918035_locked + value: "0" + format: c_expression + - location: + file_name: 66-ghost-alloc-lock.c + file_hash: $FILE_HASH + line: 33 + column: 3 + function: main + updates: + - variable: alloc_m861095507_locked + value: "1" + format: c_expression + - location: + file_name: 66-ghost-alloc-lock.c + file_hash: $FILE_HASH + line: 35 + column: 3 + function: main + updates: + - variable: alloc_m861095507_locked + value: "0" + format: c_expression + - entry_type: flow_insensitive_invariant + flow_insensitive_invariant: + string: '! multithreaded || (alloc_m861095507_locked || g2 == 0)' + type: assertion + format: C + - entry_type: flow_insensitive_invariant + flow_insensitive_invariant: + string: '! multithreaded || (alloc_m559918035_locked || g1 == 0)' + type: assertion + format: C + - entry_type: flow_insensitive_invariant + flow_insensitive_invariant: + string: '! multithreaded || (0 <= g2 && g2 <= 1)' + type: assertion + format: C + - entry_type: flow_insensitive_invariant + flow_insensitive_invariant: + string: '! multithreaded || (0 <= g1 && g1 <= 1)' + type: assertion + format: C diff --git a/tests/regression/56-witness/67-ghost-no-unlock.c b/tests/regression/56-witness/67-ghost-no-unlock.c new file mode 100644 index 0000000000..69ad571118 --- /dev/null +++ b/tests/regression/56-witness/67-ghost-no-unlock.c @@ -0,0 +1,27 @@ +// PARAM: --set ana.activated[+] mutexGhosts +#include +#include + +int g1; +pthread_mutex_t m1 = PTHREAD_MUTEX_INITIALIZER; + +void *t_fun(void *arg) { + pthread_mutex_lock(&m1); + g1 = 1; + g1 = 0; + pthread_mutex_unlock(&m1); + return NULL; +} + +int main() { + + + + pthread_t id; + pthread_create(&id, NULL, t_fun, NULL); + + pthread_mutex_lock(&m1); + __goblint_check(g1 == 0); + // no unlock + return 0; // there should be no ghost updates for unlock here +} diff --git a/tests/regression/56-witness/67-ghost-no-unlock.t b/tests/regression/56-witness/67-ghost-no-unlock.t new file mode 100644 index 0000000000..c49daf3e24 --- /dev/null +++ b/tests/regression/56-witness/67-ghost-no-unlock.t @@ -0,0 +1,84 @@ + $ goblint --set ana.base.privatization protection --enable witness.yaml.enabled --set ana.activated[+] mutexGhosts --set witness.yaml.entry-types '["flow_insensitive_invariant", "ghost_instrumentation"]' 67-ghost-no-unlock.c + [Success][Assert] Assertion "g1 == 0" will succeed (67-ghost-no-unlock.c:24:3-24:27) + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 11 + dead: 0 + total lines: 11 + [Info][Witness] witness generation summary: + location invariants: 0 + loop invariants: 0 + flow-insensitive invariants: 2 + total generation entries: 3 + [Info][Race] Memory locations race summary: + safe: 1 + vulnerable: 0 + unsafe: 0 + total memory locations: 1 + + $ yamlWitnessStrip < witness.yml + - entry_type: ghost_instrumentation + content: + ghost_variables: + - name: m1_locked + scope: global + type: int + initial: + value: "0" + format: c_expression + - name: multithreaded + scope: global + type: int + initial: + value: "0" + format: c_expression + ghost_updates: + - location: + file_name: 67-ghost-no-unlock.c + file_hash: $FILE_HASH + line: 9 + column: 3 + function: t_fun + updates: + - variable: m1_locked + value: "1" + format: c_expression + - location: + file_name: 67-ghost-no-unlock.c + file_hash: $FILE_HASH + line: 12 + column: 3 + function: t_fun + updates: + - variable: m1_locked + value: "0" + format: c_expression + - location: + file_name: 67-ghost-no-unlock.c + file_hash: $FILE_HASH + line: 21 + column: 3 + function: main + updates: + - variable: multithreaded + value: "1" + format: c_expression + - location: + file_name: 67-ghost-no-unlock.c + file_hash: $FILE_HASH + line: 23 + column: 3 + function: main + updates: + - variable: m1_locked + value: "1" + format: c_expression + - entry_type: flow_insensitive_invariant + flow_insensitive_invariant: + string: '! multithreaded || (m1_locked || g1 == 0)' + type: assertion + format: C + - entry_type: flow_insensitive_invariant + flow_insensitive_invariant: + string: '! multithreaded || (0 <= g1 && g1 <= 1)' + type: assertion + format: C diff --git a/tests/regression/56-witness/68-ghost-ambiguous-idx.c b/tests/regression/56-witness/68-ghost-ambiguous-idx.c new file mode 100644 index 0000000000..7babbe003c --- /dev/null +++ b/tests/regression/56-witness/68-ghost-ambiguous-idx.c @@ -0,0 +1,28 @@ +#include +#include + +int data; +pthread_mutex_t m[10]; + +void *t_fun(void *arg) { + pthread_mutex_lock(&m[4]); + data++; + data--; + pthread_mutex_unlock(&m[4]); + return NULL; +} + +int main() { + for (int i = 0; i < 10; i++) + pthread_mutex_init(&m[i], NULL); + + pthread_t id; + pthread_create(&id, NULL, t_fun, NULL); + int r; + int j = r ? 4 : 5; + pthread_mutex_lock(&m[r]); + __goblint_check(data == 0); // UNKNOWN! + pthread_mutex_unlock(&m[4]); + return 0; +} + diff --git a/tests/regression/56-witness/68-ghost-ambiguous-idx.t b/tests/regression/56-witness/68-ghost-ambiguous-idx.t new file mode 100644 index 0000000000..40025acf17 --- /dev/null +++ b/tests/regression/56-witness/68-ghost-ambiguous-idx.t @@ -0,0 +1,48 @@ + $ goblint --set ana.base.privatization protection --enable witness.yaml.enabled --set ana.activated[+] mutexGhosts --set witness.yaml.entry-types '["flow_insensitive_invariant", "ghost_instrumentation"]' 68-ghost-ambiguous-idx.c + [Warning][Assert] Assertion "data == 0" is unknown. (68-ghost-ambiguous-idx.c:24:3-24:29) + [Warning][Unknown] unlocking mutex (m[4]) which may not be held (68-ghost-ambiguous-idx.c:25:3-25:30) + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 15 + dead: 0 + total lines: 15 + [Warning][Race] Memory location data (race with conf. 110): (68-ghost-ambiguous-idx.c:4:5-4:9) + write with [lock:{m[4]}, thread:[main, t_fun@68-ghost-ambiguous-idx.c:20:3-20:40]] (conf. 110) (exp: & data) (68-ghost-ambiguous-idx.c:9:3-9:9) + write with [lock:{m[4]}, thread:[main, t_fun@68-ghost-ambiguous-idx.c:20:3-20:40]] (conf. 110) (exp: & data) (68-ghost-ambiguous-idx.c:10:3-10:9) + read with [mhp:{created={[main, t_fun@68-ghost-ambiguous-idx.c:20:3-20:40]}}, thread:[main]] (conf. 110) (exp: & data) (68-ghost-ambiguous-idx.c:24:3-24:29) + [Info][Witness] witness generation summary: + location invariants: 0 + loop invariants: 0 + flow-insensitive invariants: 1 + total generation entries: 2 + [Info][Race] Memory locations race summary: + safe: 0 + vulnerable: 0 + unsafe: 1 + total memory locations: 1 + + $ yamlWitnessStrip < witness.yml + - entry_type: ghost_instrumentation + content: + ghost_variables: + - name: multithreaded + scope: global + type: int + initial: + value: "0" + format: c_expression + ghost_updates: + - location: + file_name: 68-ghost-ambiguous-idx.c + file_hash: $FILE_HASH + line: 20 + column: 3 + function: main + updates: + - variable: multithreaded + value: "1" + format: c_expression + - entry_type: flow_insensitive_invariant + flow_insensitive_invariant: + string: '! multithreaded || (0 <= data && data <= 1)' + type: assertion + format: C diff --git a/tests/regression/56-witness/69-ghost-ptr-protection.c b/tests/regression/56-witness/69-ghost-ptr-protection.c new file mode 100644 index 0000000000..f5557f3fc8 --- /dev/null +++ b/tests/regression/56-witness/69-ghost-ptr-protection.c @@ -0,0 +1,30 @@ +// PARAM: --set ana.activated[+] mutexGhosts +// CRAM +#include +#include + +int g = 0; +int *p = &g; +pthread_mutex_t m1 = PTHREAD_MUTEX_INITIALIZER; +pthread_mutex_t m2 = PTHREAD_MUTEX_INITIALIZER; + +void *t_fun(void *arg) { + int x = 10; + pthread_mutex_lock(&m2); + p = &x; + p = &g; + pthread_mutex_unlock(&m2); + return NULL; +} + +int main() { + pthread_t id; + pthread_create(&id, NULL, t_fun, NULL); + pthread_mutex_lock(&m1); + g = 1; + // m2_locked || (p == & g && *p == 0) would be violated here + __goblint_check(*p != 0); // 1 from g or 10 from x in t_fun + g = 0; + pthread_mutex_unlock(&m1); + return 0; +} diff --git a/tests/regression/56-witness/69-ghost-ptr-protection.t b/tests/regression/56-witness/69-ghost-ptr-protection.t new file mode 100644 index 0000000000..53aa7dd34a --- /dev/null +++ b/tests/regression/56-witness/69-ghost-ptr-protection.t @@ -0,0 +1,116 @@ + $ goblint --set ana.base.privatization protection --enable witness.yaml.enabled --set ana.activated[+] mutexGhosts --set witness.yaml.entry-types '["flow_insensitive_invariant", "ghost_instrumentation"]' 69-ghost-ptr-protection.c + [Success][Assert] Assertion "*p != 0" will succeed (69-ghost-ptr-protection.c:26:3-26:27) + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 15 + dead: 0 + total lines: 15 + [Warning][Race] Memory location p (race with conf. 110): (69-ghost-ptr-protection.c:7:5-7:12) + write with [lock:{m2}, thread:[main, t_fun@69-ghost-ptr-protection.c:22:3-22:40]] (conf. 110) (exp: & p) (69-ghost-ptr-protection.c:14:3-14:9) + write with [lock:{m2}, thread:[main, t_fun@69-ghost-ptr-protection.c:22:3-22:40]] (conf. 110) (exp: & p) (69-ghost-ptr-protection.c:15:3-15:9) + read with [mhp:{created={[main, t_fun@69-ghost-ptr-protection.c:22:3-22:40]}}, lock:{m1}, thread:[main]] (conf. 110) (exp: & p) (69-ghost-ptr-protection.c:26:3-26:27) + [Info][Witness] witness generation summary: + location invariants: 0 + loop invariants: 0 + flow-insensitive invariants: 4 + total generation entries: 5 + [Info][Race] Memory locations race summary: + safe: 2 + vulnerable: 0 + unsafe: 1 + total memory locations: 3 + +Should not contain unsound flow-insensitive invariant m2_locked || (p == & g && *p == 0): + + $ yamlWitnessStrip < witness.yml + - entry_type: ghost_instrumentation + content: + ghost_variables: + - name: m1_locked + scope: global + type: int + initial: + value: "0" + format: c_expression + - name: m2_locked + scope: global + type: int + initial: + value: "0" + format: c_expression + - name: multithreaded + scope: global + type: int + initial: + value: "0" + format: c_expression + ghost_updates: + - location: + file_name: 69-ghost-ptr-protection.c + file_hash: $FILE_HASH + line: 13 + column: 3 + function: t_fun + updates: + - variable: m2_locked + value: "1" + format: c_expression + - location: + file_name: 69-ghost-ptr-protection.c + file_hash: $FILE_HASH + line: 16 + column: 3 + function: t_fun + updates: + - variable: m2_locked + value: "0" + format: c_expression + - location: + file_name: 69-ghost-ptr-protection.c + file_hash: $FILE_HASH + line: 22 + column: 3 + function: main + updates: + - variable: multithreaded + value: "1" + format: c_expression + - location: + file_name: 69-ghost-ptr-protection.c + file_hash: $FILE_HASH + line: 23 + column: 3 + function: main + updates: + - variable: m1_locked + value: "1" + format: c_expression + - location: + file_name: 69-ghost-ptr-protection.c + file_hash: $FILE_HASH + line: 28 + column: 3 + function: main + updates: + - variable: m1_locked + value: "0" + format: c_expression + - entry_type: flow_insensitive_invariant + flow_insensitive_invariant: + string: '! multithreaded || (m2_locked || ((0 <= *p && *p <= 1) && p == & g))' + type: assertion + format: C + - entry_type: flow_insensitive_invariant + flow_insensitive_invariant: + string: '! multithreaded || (m1_locked || g == 0)' + type: assertion + format: C + - entry_type: flow_insensitive_invariant + flow_insensitive_invariant: + string: '! multithreaded || (0 <= g && g <= 1)' + type: assertion + format: C + - entry_type: flow_insensitive_invariant + flow_insensitive_invariant: + string: '! multithreaded || (*p == 10 || ((0 <= *p && *p <= 1) && p == & g))' + type: assertion + format: C diff --git a/tests/regression/56-witness/70-apron-unassume-set-tokens.c b/tests/regression/56-witness/70-apron-unassume-set-tokens.c new file mode 100644 index 0000000000..6ff07f6064 --- /dev/null +++ b/tests/regression/56-witness/70-apron-unassume-set-tokens.c @@ -0,0 +1,18 @@ +// SKIP PARAM: --set ana.activated[+] apron --set ana.activated[+] unassume --set witness.yaml.unassume 70-apron-unassume-set-tokens.yml --set ana.apron.domain polyhedra --enable ana.widen.tokens +#include +// Uses polyhedra instead of octagon such that widening tokens are actually needed by test instead of narrowing. +// Copied & extended from 56-witness/12-apron-unassume-branch. +int main() { + int i = 0; + while (i < 100) { + i++; + } + assert(i == 100); + + int j = 0; + while (j < 100) { + j++; + } + assert(j == 100); + return 0; +} diff --git a/tests/regression/56-witness/70-apron-unassume-set-tokens.yml b/tests/regression/56-witness/70-apron-unassume-set-tokens.yml new file mode 100644 index 0000000000..1fdc29067c --- /dev/null +++ b/tests/regression/56-witness/70-apron-unassume-set-tokens.yml @@ -0,0 +1,59 @@ +- entry_type: invariant_set + metadata: + format_version: "0.1" + uuid: 0a72f7b3-7826-4f68-bc7b-25425e95946e + creation_time: 2022-07-26T09:11:03Z + producer: + name: Goblint + version: heads/yaml-witness-unassume-0-g48503c690-dirty + command_line: '''./goblint'' ''--enable'' ''dbg.debug'' ''--enable'' ''dbg.regression'' + ''--html'' ''--set'' ''ana.activated[+]'' ''apron'' ''--enable'' ''witness.yaml.enabled'' + ''70-apron-unassume-set-tokens.c''' + task: + input_files: + - 70-apron-unassume-set-tokens.c + input_file_hashes: + 70-apron-unassume-set-tokens.c: 71e40ed99b5217343d0831e293e7207e5bd30ce53f6ab73f0c1ef6ced1afcc60 + data_model: LP64 + language: C + content: + - invariant: + type: location_invariant + location: + file_name: 70-apron-unassume-set-tokens.c + file_hash: 71e40ed99b5217343d0831e293e7207e5bd30ce53f6ab73f0c1ef6ced1afcc60 + line: 8 + column: 3 + function: main + value: 99LL - (long long )i >= 0LL + format: c_expression + - invariant: + type: location_invariant + location: + file_name: 70-apron-unassume-set-tokens.c + file_hash: 71e40ed99b5217343d0831e293e7207e5bd30ce53f6ab73f0c1ef6ced1afcc60 + line: 8 + column: 3 + function: main + value: (long long )i >= 0LL + format: c_expression + - invariant: + type: location_invariant + location: + file_name: 70-apron-unassume-set-tokens.c + file_hash: 71e40ed99b5217343d0831e293e7207e5bd30ce53f6ab73f0c1ef6ced1afcc60 + line: 14 + column: 3 + function: main + value: 99LL - (long long )j >= 0LL + format: c_expression + - invariant: + type: location_invariant + location: + file_name: 70-apron-unassume-set-tokens.c + file_hash: 71e40ed99b5217343d0831e293e7207e5bd30ce53f6ab73f0c1ef6ced1afcc60 + line: 14 + column: 3 + function: main + value: (long long )j >= 0LL + format: c_expression diff --git a/tests/regression/56-witness/dune b/tests/regression/56-witness/dune index 53882c885d..f6694c60ec 100644 --- a/tests/regression/56-witness/dune +++ b/tests/regression/56-witness/dune @@ -21,7 +21,12 @@ (run %{update_suite} hh-ex3 -q) (run %{update_suite} bh-ex1-poly -q) (run %{update_suite} apron-unassume-precheck -q) - (run %{update_suite} apron-tracked-global-annot -q))))) + (run %{update_suite} apron-tracked-global-annot -q) + (run %{update_suite} apron-unassume-set-tokens -q))))) (cram (deps (glob_files *.c) (glob_files ??-*.yml))) + +(cram + (applies_to 54-witness-lifter-abortUnless) + (enabled_if %{bin-available:graph-easy})) diff --git a/tests/regression/58-base-mm-tid/01-tid-toy1.c b/tests/regression/58-base-mm-tid/01-tid-toy1.c index bc3cd18efa..49e96881d1 100644 --- a/tests/regression/58-base-mm-tid/01-tid-toy1.c +++ b/tests/regression/58-base-mm-tid/01-tid-toy1.c @@ -1,4 +1,4 @@ -// PARAM: --set ana.path_sens[+] threadflag --sets ana.base.privatization mutex-meet-tid +// PARAM: --set ana.path_sens[+] threadflag --set ana.base.privatization mutex-meet-tid // Inspired by 36/71 #include #include diff --git a/tests/regression/58-base-mm-tid/15-branched-thread-creation.c b/tests/regression/58-base-mm-tid/15-branched-thread-creation.c index 3292182adc..c7bb43519f 100644 --- a/tests/regression/58-base-mm-tid/15-branched-thread-creation.c +++ b/tests/regression/58-base-mm-tid/15-branched-thread-creation.c @@ -41,7 +41,7 @@ int main(void) { if(!mt) { pthread_mutex_lock(&mutex); - __goblint_check(g==h); //MAYFAIL + __goblint_check(g==h); //FAIL pthread_mutex_unlock(&mutex); } diff --git a/tests/regression/58-base-mm-tid/22-other-assume.c b/tests/regression/58-base-mm-tid/22-other-assume.c index 4f5c73b038..301195ac1f 100644 --- a/tests/regression/58-base-mm-tid/22-other-assume.c +++ b/tests/regression/58-base-mm-tid/22-other-assume.c @@ -1,4 +1,4 @@ -// PARAM: --set ana.path_sens[+] threadflag --sets ana.base.privatization mutex-meet-tid +// PARAM: --set ana.path_sens[+] threadflag --set ana.base.privatization mutex-meet-tid // Copy of 46/03 for base #include #include diff --git a/tests/regression/58-base-mm-tid/23-other-assume-inprec.c b/tests/regression/58-base-mm-tid/23-other-assume-inprec.c index 37e59d3edf..a4bac04c59 100644 --- a/tests/regression/58-base-mm-tid/23-other-assume-inprec.c +++ b/tests/regression/58-base-mm-tid/23-other-assume-inprec.c @@ -1,4 +1,4 @@ -// PARAM: --set ana.path_sens[+] threadflag --sets ana.base.privatization mutex-meet-tid --set ana.activated[+] threadJoins +// PARAM: --set ana.path_sens[+] threadflag --set ana.base.privatization mutex-meet-tid --set ana.activated[+] threadJoins // Copy of 46/04 for base #include #include diff --git a/tests/regression/61-evalAssert/01-union_evalAssert.c b/tests/regression/61-evalAssert/01-union_evalAssert.c index 22e72e0e51..150d1fada2 100644 --- a/tests/regression/61-evalAssert/01-union_evalAssert.c +++ b/tests/regression/61-evalAssert/01-union_evalAssert.c @@ -1,5 +1,5 @@ // PARAM: --set trans.activated[+] "assert" - +// NOCHECK // Running the assert transformation on this test used to yield code that cannot be compiled with gcc, due to superfluous offsets on a pointer struct s { int a; diff --git a/tests/regression/62-abortUnless/05-apron-mutex-meet-abortUnless.c b/tests/regression/62-abortUnless/05-apron-mutex-meet-abortUnless.c new file mode 100644 index 0000000000..e00c665056 --- /dev/null +++ b/tests/regression/62-abortUnless/05-apron-mutex-meet-abortUnless.c @@ -0,0 +1,39 @@ +// SKIP PARAM: --set ana.activated[+] apron --set ana.activated[+] abortUnless --set ana.path_sens[+] threadflag +// Minimized SV-COMP version of our regression test +// NOTIMEOUT +#include +#include + +void reach_error() { + assert(0); // NOWARN +} +void __VERIFIER_assert(int cond) { // NOWARN + if(!(cond)) { ERROR: {reach_error();abort();} } +} + +int glob1 = 5; +pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER; +pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER; +void *t_fun(void *arg) { + int t; + pthread_mutex_lock(&mutex1); + t = glob1; + __VERIFIER_assert(t == 5); // NOWARN + glob1 = -10; + __VERIFIER_assert(glob1 == -10); // NOWARN + glob1 = t; + pthread_mutex_unlock(&mutex1); + return ((void *)0); +} +int main(void) { + pthread_t id; + __VERIFIER_assert(glob1 == 5); // NOWARN + pthread_create(&id, ((void *)0), t_fun, ((void *)0)); + pthread_mutex_lock(&mutex1); + glob1++; + __VERIFIER_assert(glob1 == 6); // NOWARN + glob1--; + pthread_mutex_unlock(&mutex1); + pthread_join (id, ((void *)0)); + return 0; +} diff --git a/tests/regression/62-abortUnless/dune b/tests/regression/62-abortUnless/dune new file mode 100644 index 0000000000..d0f8f1af41 --- /dev/null +++ b/tests/regression/62-abortUnless/dune @@ -0,0 +1,14 @@ +(rule + (aliases runtest runaprontest) + (enabled_if %{lib-available:apron}) + (deps + (package goblint) + ../../../goblint ; update_suite calls local goblint + (:update_suite ../../../scripts/update_suite.rb) + (glob_files ??-*.c)) + (locks /update_suite) + (action + (chdir ../../.. + (progn + (run %{update_suite} apron-mutex-meet-abortUnless -q))))) + diff --git a/tests/regression/63-affeq/14-norm_inv.c b/tests/regression/63-affeq/14-norm_inv.c index d93fa00cd4..df144fc402 100644 --- a/tests/regression/63-affeq/14-norm_inv.c +++ b/tests/regression/63-affeq/14-norm_inv.c @@ -3,6 +3,7 @@ // Normalization should be triggered when an invertible expression is assigned. // No asserts, likely fixpoint regression. // TODO: used to have list-based matrices, issue was only with those? +// NOCHECK int main() { int A, B; int r, d, p, q; diff --git a/tests/regression/63-affeq/17-verify.c b/tests/regression/63-affeq/17-verify.c index 7ac1b202e4..2ae7d338ba 100644 --- a/tests/regression/63-affeq/17-verify.c +++ b/tests/regression/63-affeq/17-verify.c @@ -1,5 +1,5 @@ //SKIP PARAM: --set ana.activated[+] affeq --sem.int.signed_overflow "assume_none" --enable ana.int.interval -// Error in leq check led to verify error +// FIXPOINT: Error in leq check led to verify error int main() { int n, a, b; diff --git a/tests/regression/66-interval-set-one/00-was_problematic_2.c b/tests/regression/66-interval-set-one/00-was_problematic_2.c index e5b3938a76..187e6f39df 100644 --- a/tests/regression/66-interval-set-one/00-was_problematic_2.c +++ b/tests/regression/66-interval-set-one/00-was_problematic_2.c @@ -1,4 +1,5 @@ // PARAM: --enable ana.int.interval_set --set ana.base.arrays.domain partitioned +// NOCHECK int main(void) { int arr[260]; diff --git a/tests/regression/66-interval-set-one/03-was_problematic_3.c b/tests/regression/66-interval-set-one/03-was_problematic_3.c index 59ce6e5cb6..2a8bd4bc46 100644 --- a/tests/regression/66-interval-set-one/03-was_problematic_3.c +++ b/tests/regression/66-interval-set-one/03-was_problematic_3.c @@ -1,4 +1,5 @@ // PARAM: --enable ana.int.interval_set --set ana.base.arrays.domain partitioned +// NOCHECK struct some_struct { int dir[7]; diff --git a/tests/regression/66-interval-set-one/14-no-int-context.c b/tests/regression/66-interval-set-one/14-no-int-context.c index 69d2aa2aa7..d701caf59e 100644 --- a/tests/regression/66-interval-set-one/14-no-int-context.c +++ b/tests/regression/66-interval-set-one/14-no-int-context.c @@ -1,5 +1,5 @@ // PARAM: --enable ana.int.interval_set --set solver slr3t --disable ana.base.context.int - +// NOCHECK int f (int i) { // -2 return i+1; } // -3 void g(int j) { // -4 diff --git a/tests/regression/66-interval-set-one/39-calls.c b/tests/regression/66-interval-set-one/39-calls.c index 67ff46ad77..acfade3084 100644 --- a/tests/regression/66-interval-set-one/39-calls.c +++ b/tests/regression/66-interval-set-one/39-calls.c @@ -1,5 +1,6 @@ // PARAM: --enable ana.int.interval_set --disable ana.int.def_exc --set ana.base.arrays.domain partitioned // Variable-sized arrays +// NOCHECK void foo(int n, int a[n]); void foo2(int n, int a[30][n]); void foo3(int n, int a[n][30]); diff --git a/tests/regression/66-interval-set-one/51-widen-sides.c b/tests/regression/66-interval-set-one/51-widen-sides.c index b086baf026..53fe749704 100644 --- a/tests/regression/66-interval-set-one/51-widen-sides.c +++ b/tests/regression/66-interval-set-one/51-widen-sides.c @@ -1,4 +1,4 @@ -// PARAM: --set ana.ctx_insens "['base', 'mallocWrapper']" --enable ana.int.interval_set --sets solvers.td3.side_widen sides-local +// PARAM: --set ana.ctx_insens "['base', 'mallocWrapper']" --enable ana.int.interval_set --set solvers.td3.side_widen sides-local #include int further(int n) { diff --git a/tests/regression/66-interval-set-one/93-enum.c b/tests/regression/66-interval-set-one/93-enum.c index 5d2b45043d..ca769fb126 100644 --- a/tests/regression/66-interval-set-one/93-enum.c +++ b/tests/regression/66-interval-set-one/93-enum.c @@ -1,4 +1,5 @@ // PARAM: --disable ana.int.interval_set --disable ana.int.def_exc --enable ana.int.enums +// NOCHECK void main(){ int n = 1; for (; n; n++) { // fixed point not reached here diff --git a/tests/regression/67-interval-sets-two/03-def_exc-interval-inconsistent.c b/tests/regression/67-interval-sets-two/03-def_exc-interval-inconsistent.c index 8d56fa0e3c..f5291fdcdc 100644 --- a/tests/regression/67-interval-sets-two/03-def_exc-interval-inconsistent.c +++ b/tests/regression/67-interval-sets-two/03-def_exc-interval-inconsistent.c @@ -1,6 +1,7 @@ // PARAM: --enable ana.int.def_exc --enable ana.int.interval_set --enable ana.sv-comp.functions --set sem.int.signed_overflow assume_none --set ana.int.refinement never // used to crash in branch when is_bool returned true, but to_bool returned None on (0,[1,1]) // manually minimized from sv-benchmarks/c/recursive/MultCommutative-2.c +// NOCHECK extern int __VERIFIER_nondet_int(void); void f(int m) { diff --git a/tests/regression/67-interval-sets-two/04-unsupported.c b/tests/regression/67-interval-sets-two/04-unsupported.c index 97ce11258a..4127da4d8b 100644 --- a/tests/regression/67-interval-sets-two/04-unsupported.c +++ b/tests/regression/67-interval-sets-two/04-unsupported.c @@ -1,6 +1,7 @@ // PARAM: --enable ana.int.interval_set --disable exp.fast_global_inits --set ana.base.arrays.domain partitioned // This is just to test that the analysis does not cause problems for features that are not explicitly dealt with +// NOCHECK: what problems? int main(void) { callok(); } diff --git a/tests/regression/67-interval-sets-two/14-trylock_rc_slr.c b/tests/regression/67-interval-sets-two/14-trylock_rc_slr.c index af46023135..a5e5e937cb 100644 --- a/tests/regression/67-interval-sets-two/14-trylock_rc_slr.c +++ b/tests/regression/67-interval-sets-two/14-trylock_rc_slr.c @@ -1,4 +1,5 @@ // PARAM: --enable ana.int.interval_set --set solver slr3t +// NOCHECK #include pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; diff --git a/tests/regression/67-interval-sets-two/15-interval-bot.c b/tests/regression/67-interval-sets-two/15-interval-bot.c index ab7d043b92..6ab90062ae 100644 --- a/tests/regression/67-interval-sets-two/15-interval-bot.c +++ b/tests/regression/67-interval-sets-two/15-interval-bot.c @@ -1,5 +1,5 @@ // PARAM: --enable ana.int.interval_set --enable ana.int.def_exc - +// NOCHECK int main(){ unsigned long long a ; diff --git a/tests/regression/67-interval-sets-two/16-branched-thread-creation.c b/tests/regression/67-interval-sets-two/16-branched-thread-creation.c index c309ec8ffd..d97f800621 100644 --- a/tests/regression/67-interval-sets-two/16-branched-thread-creation.c +++ b/tests/regression/67-interval-sets-two/16-branched-thread-creation.c @@ -41,7 +41,7 @@ int main(void) { if(!mt) { pthread_mutex_lock(&mutex); - __goblint_check(g==h); //MAYFAIL + __goblint_check(g==h); //FAIL pthread_mutex_unlock(&mutex); } diff --git a/tests/regression/67-interval-sets-two/24-arithmetic-bot.c b/tests/regression/67-interval-sets-two/24-arithmetic-bot.c index f238788bb3..f9a2d0086a 100644 --- a/tests/regression/67-interval-sets-two/24-arithmetic-bot.c +++ b/tests/regression/67-interval-sets-two/24-arithmetic-bot.c @@ -1,5 +1,6 @@ // PARAM: --enable ana.int.interval_set --enable ana.int.def_exc // from: ldv-linux-3.0/usb_urb-drivers-vhost-vhost_net.ko.cil.out.i +// NOCHECK typedef unsigned long long u64; int main( ) diff --git a/tests/regression/67-interval-sets-two/31-ptrdiff.c b/tests/regression/67-interval-sets-two/31-ptrdiff.c index d7bd4667df..d6135f8734 100644 --- a/tests/regression/67-interval-sets-two/31-ptrdiff.c +++ b/tests/regression/67-interval-sets-two/31-ptrdiff.c @@ -1,4 +1,5 @@ // PARAM: --enable ana.int.interval_set --set ana.base.arrays.domain partitioned --set ana.activated[+] var_eq +// NOCHECK int *tmp; int main () diff --git a/tests/regression/67-interval-sets-two/44-comparision-bot.c b/tests/regression/67-interval-sets-two/44-comparision-bot.c index 5b3622828a..eb6e90b5ea 100644 --- a/tests/regression/67-interval-sets-two/44-comparision-bot.c +++ b/tests/regression/67-interval-sets-two/44-comparision-bot.c @@ -1,4 +1,5 @@ // PARAM: --enable ana.int.interval_set --enable ana.int.def_exc +// NOCHECK #include int main(){ int a = 0; diff --git a/tests/regression/67-interval-sets-two/56-interval-set-dead-code.c b/tests/regression/67-interval-sets-two/56-interval-set-dead-code.c index e1345155d9..df1d7346bd 100644 --- a/tests/regression/67-interval-sets-two/56-interval-set-dead-code.c +++ b/tests/regression/67-interval-sets-two/56-interval-set-dead-code.c @@ -1,4 +1,5 @@ // PARAM: --enable ana.int.interval_set +// NOCHECK #include #include diff --git a/tests/regression/67-interval-sets-two/58-interval-set-dead-code-with-fun-call.c b/tests/regression/67-interval-sets-two/58-interval-set-dead-code-with-fun-call.c index b63ebd6fab..1a709243cb 100644 --- a/tests/regression/67-interval-sets-two/58-interval-set-dead-code-with-fun-call.c +++ b/tests/regression/67-interval-sets-two/58-interval-set-dead-code-with-fun-call.c @@ -1,4 +1,5 @@ // PARAM: --enable ana.int.interval_set +// NOCHECK #include #include diff --git a/tests/regression/71-doublelocking/15-rec-dyn-nested.c b/tests/regression/71-doublelocking/15-rec-dyn-nested.c index d5dac9cd81..472783a9f3 100644 --- a/tests/regression/71-doublelocking/15-rec-dyn-nested.c +++ b/tests/regression/71-doublelocking/15-rec-dyn-nested.c @@ -1,5 +1,5 @@ // PARAM: --set ana.activated[+] 'pthreadMutexType' -// Check we don't have a stack overflow because of tracking multiplicities +// NOCRASH: Check we don't have a stack overflow because of tracking multiplicities #define _GNU_SOURCE #include #include diff --git a/tests/regression/74-invalid_deref/15-juliet-uaf-global-var.c b/tests/regression/74-invalid_deref/15-juliet-uaf-global-var.c index cc9819950f..93d4d6f307 100644 --- a/tests/regression/74-invalid_deref/15-juliet-uaf-global-var.c +++ b/tests/regression/74-invalid_deref/15-juliet-uaf-global-var.c @@ -1,4 +1,5 @@ //PARAM: --set ana.activated[+] useAfterFree +// NOCHECK #include int *global; diff --git a/tests/regression/77-lin2vareq/34-coefficient-features.c b/tests/regression/77-lin2vareq/34-coefficient-features.c new file mode 100644 index 0000000000..561eccf0af --- /dev/null +++ b/tests/regression/77-lin2vareq/34-coefficient-features.c @@ -0,0 +1,62 @@ +//SKIP PARAM: --set ana.activated[+] lin2vareq --set sem.int.signed_overflow assume_none +// this test checks basic coefficient handing in main and join capabilities in loop + +#include + +void loop() { + int random; + int i = 0; + int x = 0; + int y = 0; + + x=x+4; + y=y+8; + i=i+1; + + if (random) { + x=x+4; + y=y+8; + i=i+1; + } + + __goblint_check(x == 4*i); //SUCCESS + + x=0; + y=0; + + for(i = 1; i < 100; i++) { + x=x+4; + y=y+8; + + __goblint_check(y == 2*x); //SUCCESS + __goblint_check(x == 4*i); //SUCCESS + } +} + +void main() { + int a; + int b; + int c; + int unknown; + a = 4; + + b = 4*c; + + __goblint_check(b == 4*c); //SUCCESS + + b = a*c; + + __goblint_check(b == 4*c); //SUCCESS + + if (7*b == 20*unknown + a){ + + __goblint_check(7*b == 20*unknown + a); //SUCCESS + } + + b = unknown ? a*c : 4*c; + + __goblint_check(b == 4*c); //SUCCESS + + loop(); + +} \ No newline at end of file diff --git a/tests/regression/78-termination/51-modulo.c b/tests/regression/78-termination/51-modulo.c index 5f5b8f1924..81811c1acf 100644 --- a/tests/regression/78-termination/51-modulo.c +++ b/tests/regression/78-termination/51-modulo.c @@ -1,4 +1,4 @@ -// SKIP TERM PARAM: --enable ana.autotune.enabled --enable ana.sv-comp.functions --enable ana.sv-comp.enabled --set ana.autotune.activated "['congruence']" --set ana.specification "CHECK( init(main()), LTL(F end) )" +// SKIP TODO TERM PARAM: --enable ana.autotune.enabled --enable ana.sv-comp.functions --enable ana.sv-comp.enabled --set ana.autotune.activated "['congruence']" --set ana.specification "CHECK( init(main()), LTL(F end) )" // This task previously crashed due to the autotuner int main() { diff --git a/tests/regression/80-context_gas/21-sync.c b/tests/regression/80-context_gas/21-sync.c new file mode 100644 index 0000000000..33edf254e5 --- /dev/null +++ b/tests/regression/80-context_gas/21-sync.c @@ -0,0 +1,35 @@ +// PARAM: --set ana.context.gas_value 1 +// Like 00/35 but with gas this time! +// Misbehaves for gas <= 3 +#include +#include + +int g = 1; + +void foo() { + // Single-threaded: g = 1 in local state + // Multi-threaded: g = 2 in global unprotected invariant + // Joined contexts: g is unprotected, so read g = 2 from global unprotected invariant (only) + // Was soundly claiming that check will succeed! + int x = g; + __goblint_check(x == 2); // UNKNOWN! +} + +void *t_fun(void *arg) { + foo(); +} + +int do_stuff() { + foo(); + g = 2; + + pthread_t id; + pthread_create(&id, NULL, t_fun, NULL); + + return 0; +} + +int main() { + do_stuff(); + return 0; +} diff --git a/tests/regression/80-context_gas/22-sync-precision.c b/tests/regression/80-context_gas/22-sync-precision.c new file mode 100644 index 0000000000..8fc1dd7779 --- /dev/null +++ b/tests/regression/80-context_gas/22-sync-precision.c @@ -0,0 +1,36 @@ +// PARAM: --set ana.context.gas_value 4 +// Like 00/35 but with gas this time! +// Misbehaves for gas <= 3 +#include +#include + +int g = 1; + +void foo() { + // Check that we don't lose precision due to JoinCall + int x = g; + int x2 = g; + // A hack: In both contexts g has a single possible value, so we check that x = x2 + // to verify there is no precision loss + + __goblint_check(x == x2); +} + +void *t_fun(void *arg) { + foo(); +} + +int do_stuff() { + foo(); + g = 2; + + pthread_t id; + pthread_create(&id, NULL, t_fun, NULL); + + return 0; +} + +int main() { + do_stuff(); + return 0; +} diff --git a/tests/regression/80-context_gas/23-per-fun.c b/tests/regression/80-context_gas/23-per-fun.c new file mode 100644 index 0000000000..1f64317b0f --- /dev/null +++ b/tests/regression/80-context_gas/23-per-fun.c @@ -0,0 +1,29 @@ +// PARAM: --enable ana.int.interval_set --set ana.context.gas_value 3 --set ana.context.gas_scope function +int nr(int x, int y) { + // Non-recursive, but would fail with global scope as gas for f is exhausted + __goblint_check(x==y); +} + +int f(int x, int y) +{ + int top; + if (x == 0) + { + return y; + } + + if(top) { + nr(5,5); + } else { + nr(6,6); + } + + return f(x - 1, y - 1); +} + +int main() +{ + // main -> f(3,3) -> f(2,2) -> f(1,1) -> f(0,0) -> return 0 + // 4 recursive calls -> boundary (excluded) + __goblint_check(f(3, 3) == 0); // UNKNOWN +} diff --git a/tests/regression/80-context_gas/24-per-fun-ex.c b/tests/regression/80-context_gas/24-per-fun-ex.c new file mode 100644 index 0000000000..ab5d203c45 --- /dev/null +++ b/tests/regression/80-context_gas/24-per-fun-ex.c @@ -0,0 +1,44 @@ +// PARAM: --enable ana.int.interval_set --set ana.context.gas_value 3 --set ana.context.gas_scope function +int nr(int x, int y) { + // Non-recursive, but would fail with global scope as gas for f is exhausted + __goblint_check(x==y); +} + +// Checks that gas is also applied to further functions +int g(int x, int y) +{ + int top; + if (x < -100000) + { + return y; + } + + if(top) { + nr(5,5); + } else { + nr(6,6); + } + + return g(x - 1, y - 1); +} + +int f(int x, int y) +{ + int top; + + if (x == 0) + { + return y; + } + + g(x,y); + + return f(x - 1, y - 1); +} + +int main() +{ + // main -> f(3,3) -> f(2,2) -> f(1,1) -> f(0,0) -> return 0 + // 4 recursive calls -> boundary (excluded) + __goblint_check(f(3, 3) == 0); // UNKNOWN +} diff --git a/tests/regression/80-context_gas/25-per-fun-nonterm.c b/tests/regression/80-context_gas/25-per-fun-nonterm.c new file mode 100644 index 0000000000..4c3871688f --- /dev/null +++ b/tests/regression/80-context_gas/25-per-fun-nonterm.c @@ -0,0 +1,29 @@ +//PARAM: --enable ana.int.interval_set --set ana.context.gas_value 3 --set ana.context.gas_scope function +// NOTIMEOUT +void h(int n) { + int x; + + if(x) { + return; + } + + g(n+1); + h(n+1); +} + +void g(int n) { + int x; + + if(x) { + return; + } + + g(n+1); + h(n+1); +} + +int main() +{ + g(0); + h(0); +} diff --git a/tests/regression/cfg/foo.t/run.t b/tests/regression/cfg/foo.t/run.t index 02f6c1e5e0..19873d7540 100644 --- a/tests/regression/cfg/foo.t/run.t +++ b/tests/regression/cfg/foo.t/run.t @@ -67,7 +67,10 @@ total lines: 6 [Warning][Deadcode][CWE-571] condition 'a > 0' (possibly inserted by CIL) is always true (foo.c:3:10-3:20) [Info][Witness] witness generation summary: - total generation entries: 15 + location invariants: 8 + loop invariants: 2 + flow-insensitive invariants: 0 + total generation entries: 10 $ yamlWitnessStrip < witness.yml - entry_type: loop_invariant @@ -103,17 +106,6 @@ string: b == 0 type: assertion format: C - - entry_type: location_invariant - location: - file_name: foo.c - file_hash: $FILE_HASH - line: 7 - column: 3 - function: main - location_invariant: - string: a != 0 - type: assertion - format: C - entry_type: location_invariant location: file_name: foo.c @@ -125,17 +117,6 @@ string: 1 <= a type: assertion format: C - - entry_type: location_invariant - location: - file_name: foo.c - file_hash: $FILE_HASH - line: 7 - column: 3 - function: main - location_invariant: - string: 0 <= a - type: assertion - format: C - entry_type: location_invariant location: file_name: foo.c @@ -158,17 +139,6 @@ string: b != 0 type: assertion format: C - - entry_type: location_invariant - location: - file_name: foo.c - file_hash: $FILE_HASH - line: 5 - column: 5 - function: main - location_invariant: - string: a != 1 - type: assertion - format: C - entry_type: location_invariant location: file_name: foo.c @@ -202,17 +172,6 @@ string: b != 0 type: assertion format: C - - entry_type: location_invariant - location: - file_name: foo.c - file_hash: $FILE_HASH - line: 4 - column: 5 - function: main - location_invariant: - string: a != 0 - type: assertion - format: C - entry_type: location_invariant location: file_name: foo.c @@ -224,14 +183,3 @@ string: 1 <= a type: assertion format: C - - entry_type: location_invariant - location: - file_name: foo.c - file_hash: $FILE_HASH - line: 4 - column: 5 - function: main - location_invariant: - string: 0 <= a - type: assertion - format: C diff --git a/tests/regression/cfg/issue-1356.t/run.t b/tests/regression/cfg/issue-1356.t/run.t index aee9456b61..d1fcb3c7ef 100644 --- a/tests/regression/cfg/issue-1356.t/run.t +++ b/tests/regression/cfg/issue-1356.t/run.t @@ -99,6 +99,9 @@ dead: 0 total lines: 13 [Info][Witness] witness generation summary: + location invariants: 0 + loop invariants: 0 + flow-insensitive invariants: 0 total generation entries: 0 $ yamlWitnessStrip < witness.yml diff --git a/tests/regression/cfg/loops.t/run.t b/tests/regression/cfg/loops.t/run.t index 6596e7b4a4..1fd19b41fe 100644 --- a/tests/regression/cfg/loops.t/run.t +++ b/tests/regression/cfg/loops.t/run.t @@ -219,6 +219,9 @@ dead: 0 total lines: 20 [Info][Witness] witness generation summary: + location invariants: 32 + loop invariants: 21 + flow-insensitive invariants: 0 total generation entries: 53 $ yamlWitnessStrip < witness.yml diff --git a/tests/regression/cfg/pr-758.t/run.t b/tests/regression/cfg/pr-758.t/run.t index 58bbb88ce4..082c63e860 100644 --- a/tests/regression/cfg/pr-758.t/run.t +++ b/tests/regression/cfg/pr-758.t/run.t @@ -93,6 +93,9 @@ dead: 0 total lines: 6 [Info][Witness] witness generation summary: + location invariants: 10 + loop invariants: 2 + flow-insensitive invariants: 0 total generation entries: 12 $ yamlWitnessStrip < witness.yml diff --git a/tests/regression/cfg/util/dune b/tests/regression/cfg/util/dune index fb3c5e6899..8ab300b531 100644 --- a/tests/regression/cfg/util/dune +++ b/tests/regression/cfg/util/dune @@ -5,7 +5,5 @@ goblint_logs goblint_common goblint_lib ; TODO: avoid: extract LoopUnrolling and WitnessUtil node predicates from goblint_lib - fpath - goblint.sites.dune - goblint.build-info.dune) + fpath) (preprocess (pps ppx_deriving.std))) diff --git a/tests/regression/dune b/tests/regression/dune index 8f550d190e..0fd8052478 100644 --- a/tests/regression/dune +++ b/tests/regression/dune @@ -2,7 +2,8 @@ (_ (binaries ./cfg/util/cfgDot.exe - ../util/yamlWitnessStrip.exe))) + ../util/yamlWitnessStrip.exe + ../util/yamlWitnessStripDiff.exe))) (rule (alias runtest) @@ -24,4 +25,5 @@ (alias runcramtest) (deps (package goblint) - %{bin:yamlWitnessStrip})) + %{bin:yamlWitnessStrip} + %{bin:yamlWitnessStripDiff})) diff --git a/tests/regression/issue-94.t/issue-94.c b/tests/regression/issue-94.t/issue-94.c new file mode 100644 index 0000000000..7bdc65b0a8 --- /dev/null +++ b/tests/regression/issue-94.t/issue-94.c @@ -0,0 +1,14 @@ +#include + +int main() { + int x; + if (1) + x = 1; + else + x = 2; + if (x) + x = 1; + else + x = 2; + assert(x > 1 && x < 0); +} diff --git a/tests/regression/issue-94.t/run.t b/tests/regression/issue-94.t/run.t new file mode 100644 index 0000000000..40b73f247b --- /dev/null +++ b/tests/regression/issue-94.t/run.t @@ -0,0 +1,14 @@ + $ goblint --enable ana.dead-code.lines --enable ana.dead-code.branches issue-94.c + [Error][Assert] Assertion "tmp" will fail. (issue-94.c:13:3-13:35) + [Warning][Deadcode] Function 'main' does not return + [Warning][Deadcode] Function 'main' has dead code: + on line 8 (issue-94.c:8-8) + on line 12 (issue-94.c:12-12) + on line 14 (issue-94.c:14-14) + [Warning][Deadcode] Logical lines of code (LLoC) summary: + live: 6 + dead: 3 + total lines: 9 + [Warning][Deadcode][CWE-571] condition '1' is always true (issue-94.c:5:7-5:8) + [Warning][Deadcode][CWE-571] condition 'x' is always true (issue-94.c:9:7-9:8) + [Warning][Deadcode][CWE-570] condition 'x > 1' is always false (issue-94.c:13:3-13:35) diff --git a/tests/regression/witness/int.t/int.c b/tests/regression/witness/int.t/int.c new file mode 100644 index 0000000000..4ad15b09ad --- /dev/null +++ b/tests/regression/witness/int.t/int.c @@ -0,0 +1,17 @@ +#include +extern int __VERIFIER_nondet_int(); + +int main() { + int i; + i = __VERIFIER_nondet_int(); + + if (i < 100) + __goblint_check(1); + + if (50 < i && i < 100) + __goblint_check(1); + + if (i == 42 || i == 5 || i == 101) + __goblint_check(1); + return 0; +} diff --git a/tests/regression/witness/int.t/run.t b/tests/regression/witness/int.t/run.t new file mode 100644 index 0000000000..9448ac7855 --- /dev/null +++ b/tests/regression/witness/int.t/run.t @@ -0,0 +1,48 @@ + $ goblint --enable ana.sv-comp.functions --enable witness.yaml.enabled --set witness.yaml.entry-types '["location_invariant"]' --enable ana.int.def_exc --enable ana.int.enums --enable ana.int.interval --enable ana.int.congruence --disable ana.int.interval_set --disable witness.invariant.split-conjunction int.c + [Success][Assert] Assertion "1" will succeed (int.c:9:5-9:23) + [Success][Assert] Assertion "1" will succeed (int.c:12:5-12:23) + [Success][Assert] Assertion "1" will succeed (int.c:15:5-15:23) + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 10 + dead: 0 + total lines: 10 + [Info][Witness] witness generation summary: + location invariants: 3 + loop invariants: 0 + flow-insensitive invariants: 0 + total generation entries: 3 + + $ yamlWitnessStrip < witness.yml + - entry_type: location_invariant + location: + file_name: int.c + file_hash: $FILE_HASH + line: 15 + column: 5 + function: main + location_invariant: + string: (i == 5 || i == 42) || i == 101 + type: assertion + format: C + - entry_type: location_invariant + location: + file_name: int.c + file_hash: $FILE_HASH + line: 12 + column: 5 + function: main + location_invariant: + string: 51 <= i && i <= 99 + type: assertion + format: C + - entry_type: location_invariant + location: + file_name: int.c + file_hash: $FILE_HASH + line: 9 + column: 5 + function: main + location_invariant: + string: i <= 99 + type: assertion + format: C diff --git a/tests/regression/witness/typedef.t/run.t b/tests/regression/witness/typedef.t/run.t index 55dcc1f911..f9fac0c743 100644 --- a/tests/regression/witness/typedef.t/run.t +++ b/tests/regression/witness/typedef.t/run.t @@ -4,6 +4,9 @@ dead: 0 total lines: 6 [Info][Witness] witness generation summary: + location invariants: 13 + loop invariants: 0 + flow-insensitive invariants: 0 total generation entries: 13 $ yamlWitnessStrip < witness.yml @@ -157,6 +160,9 @@ dead: 0 total lines: 6 [Info][Witness] witness generation summary: + location invariants: 14 + loop invariants: 0 + flow-insensitive invariants: 0 total generation entries: 14 $ yamlWitnessStrip < witness.yml diff --git a/tests/regression/witness/violation.t/correct-hard.c b/tests/regression/witness/violation.t/correct-hard.c new file mode 100644 index 0000000000..ce2b50a0c0 --- /dev/null +++ b/tests/regression/witness/violation.t/correct-hard.c @@ -0,0 +1,9 @@ +void reach_error(){} +extern int __VERIFIER_nondet_int(); + +int main() { + int x = __VERIFIER_nondet_int(); + if (x != x) + reach_error(); + return 0; +} diff --git a/tests/regression/witness/violation.t/correct-hard.yml b/tests/regression/witness/violation.t/correct-hard.yml new file mode 100644 index 0000000000..b8af2c2193 --- /dev/null +++ b/tests/regression/witness/violation.t/correct-hard.yml @@ -0,0 +1,26 @@ +- entry_type: violation_sequence + metadata: + format_version: "2.0" + uuid: 4412af70-389a-475e-849c-e57e5b92019e + creation_time: 2024-06-14T15:35:00+03:00 + producer: + name: Simmo Saan + version: n/a + task: + input_files: + - correct-hard.c + input_file_hashes: + correct-hard.c: 5cc49c1ce5a9aef64286c2a6e57f6955c5f4f9b19b43056507ae87a802802447 + specification: G ! call(reach_error()) + data_model: ILP32 + language: C + content: + - segment: + - waypoint: + type: target + action: follow + location: + file_name: correct-hard.c + line: 7 + column: 5 + function: main diff --git a/tests/regression/witness/violation.t/correct.c b/tests/regression/witness/violation.t/correct.c new file mode 100644 index 0000000000..30f58a2f7e --- /dev/null +++ b/tests/regression/witness/violation.t/correct.c @@ -0,0 +1,5 @@ +void reach_error(){} + +int main() { + return 0; +} diff --git a/tests/regression/witness/violation.t/correct.yml b/tests/regression/witness/violation.t/correct.yml new file mode 100644 index 0000000000..1f1d4f41da --- /dev/null +++ b/tests/regression/witness/violation.t/correct.yml @@ -0,0 +1,26 @@ +- entry_type: violation_sequence + metadata: + format_version: "2.0" + uuid: 4412af70-389a-475e-849c-e57e5b92019d + creation_time: 2024-06-14T15:35:00+03:00 + producer: + name: Simmo Saan + version: n/a + task: + input_files: + - correct.c + input_file_hashes: + correct.c: 6f760cf7f33fc152738bf3514fe623cc94e52cad9ddc2f0e744595ce0de07530 + specification: G ! call(reach_error()) + data_model: ILP32 + language: C + content: + - segment: + - waypoint: + type: target + action: follow + location: + file_name: correct.c + line: 4 + column: 3 + function: main diff --git a/tests/regression/witness/violation.t/incorrect.c b/tests/regression/witness/violation.t/incorrect.c new file mode 100644 index 0000000000..ff56fa2ef4 --- /dev/null +++ b/tests/regression/witness/violation.t/incorrect.c @@ -0,0 +1,6 @@ +void reach_error(){} + +int main() { + reach_error(); + return 0; +} diff --git a/tests/regression/witness/violation.t/incorrect.yml b/tests/regression/witness/violation.t/incorrect.yml new file mode 100644 index 0000000000..dd57ce3ca1 --- /dev/null +++ b/tests/regression/witness/violation.t/incorrect.yml @@ -0,0 +1,26 @@ +- entry_type: violation_sequence + metadata: + format_version: "2.0" + uuid: 4412af70-389a-475e-849c-e57e5b92019c + creation_time: 2024-06-14T15:35:00+03:00 + producer: + name: Simmo Saan + version: n/a + task: + input_files: + - incorrect.c + input_file_hashes: + incorrect.c: 1af4fd9e76418e4b95af9950b58248127e7c2d9eb791e1c9b92da53952e0fca2 + specification: G ! call(reach_error()) + data_model: ILP32 + language: C + content: + - segment: + - waypoint: + type: target + action: follow + location: + file_name: incorrect.c + line: 4 + column: 3 + function: main diff --git a/tests/regression/witness/violation.t/run.t b/tests/regression/witness/violation.t/run.t new file mode 100644 index 0000000000..1fc408635e --- /dev/null +++ b/tests/regression/witness/violation.t/run.t @@ -0,0 +1,57 @@ +Violation witness for a correct program can be refuted by proving the program correct and returning `true`: + + $ goblint --enable ana.sv-comp.enabled --set witness.yaml.entry-types[+] violation_sequence --set ana.specification "CHECK( init(main()), LTL(G ! call(reach_error())) )" correct.c --set witness.yaml.validate correct.yml + [Info] SV-COMP specification: CHECK( init(main()), LTL(G ! call(reach_error())) ) + [Warning][Deadcode] Function 'reach_error' is uncalled: 1 LLoC (correct.c:1:1-1:20) + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 2 + dead: 1 (1 in uncalled functions) + total lines: 3 + [Info][Witness] witness validation summary: + confirmed: 0 + unconfirmed: 0 + refuted: 0 + error: 0 + unchecked: 0 + unsupported: 0 + disabled: 0 + total validation entries: 0 + SV-COMP result: true + +If a correct progtam cannot be proven correct, return `unknown` for the violation witness: + + $ goblint --set ana.activated[-] expRelation --enable ana.sv-comp.functions --enable ana.sv-comp.enabled --set witness.yaml.entry-types[+] violation_sequence --set ana.specification "CHECK( init(main()), LTL(G ! call(reach_error())) )" correct-hard.c --set witness.yaml.validate correct-hard.yml + [Info] SV-COMP specification: CHECK( init(main()), LTL(G ! call(reach_error())) ) + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 7 + dead: 0 + total lines: 7 + [Info][Witness] witness validation summary: + confirmed: 0 + unconfirmed: 0 + refuted: 0 + error: 0 + unchecked: 0 + unsupported: 0 + disabled: 0 + total validation entries: 0 + SV-COMP result: unknown + +Violation witness for an incorrect program cannot be proven correct, so return `unknown`: + + $ goblint --enable ana.sv-comp.enabled --set witness.yaml.entry-types[+] violation_sequence --set ana.specification "CHECK( init(main()), LTL(G ! call(reach_error())) )" incorrect.c --set witness.yaml.validate incorrect.yml + [Info] SV-COMP specification: CHECK( init(main()), LTL(G ! call(reach_error())) ) + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 4 + dead: 0 + total lines: 4 + [Info][Witness] witness validation summary: + confirmed: 0 + unconfirmed: 0 + refuted: 0 + error: 0 + unchecked: 0 + unsupported: 0 + disabled: 0 + total validation entries: 0 + SV-COMP result: unknown diff --git a/tests/sv-comp/basic/for_fun_true-unreach-call.expected b/tests/sv-comp/basic/for_fun_true-unreach-call.expected new file mode 100644 index 0000000000..5276bf724b --- /dev/null +++ b/tests/sv-comp/basic/for_fun_true-unreach-call.expected @@ -0,0 +1,63 @@ + ┌────────────────────────────────┐ + │ _ │ + └────────────────────────────────┘ + │ + │ Entry main + ▼ + ┌────────────────────────────────┐ + │ _ │ ·┐ + └────────────────────────────────┘ : + │ : + │ InlineEntry '()' : + ▼ : + ┌────────────────────────────────┐ : + │ _ │ : + └────────────────────────────────┘ : + │ : + │ Entry fun : + ▼ : + ┌────────────────────────────────┐ : + │ _ │ : + └────────────────────────────────┘ : + │ : + │ Assign 'i = 0' : + ▼ : + ┌────────────────────────────────┐ : ┌───────────────────────┐ Ret (None, fun) ┌─────────────────────┐ + ┌──────────────────▶ │ _ │ ─┼──────────────────────────────────────────▶ │ _ │ ─────────────────▶ │ _ │ + │ └────────────────────────────────┘ : └───────────────────────┘ └─────────────────────┘ + │ │ : Inlined Proc 'fun()' │ + │ │ Test (i < 1000,true) └·······································································┐ │ InlineReturn + │ ▼ : ▼ + │ ┌────────────────────────────────┐ : ┌─────────────────────┐ + │ │ _ │ ·┐ └···············▶ │ _ │ + │ └────────────────────────────────┘ : └─────────────────────┘ + │ │ : │ + │ │ InlineEntry '(i < 2000)' : │ Ret (Some 0, main) + │ ▼ : ▼ + │ ┌────────────────────────────────┐ : ┌─────────────────────┐ + │ │ _ │ : │ _ │ + │ └────────────────────────────────┘ : └─────────────────────┘ + │ │ : + │ │ Entry __VERIFIER_assert : + │ ▼ : + │ ┌────────────────────────────────┐ : + │ Assign 'i = i + 1' │ _ │ : + │ └────────────────────────────────┘ : + │ │ : + │ │ Test (! cond,false) : Inlined Proc '__VERIFIER_assert(i < 2000)' + │ ▼ : + │ ┌────────────────────────────────┐ : + │ │ _ │ : + │ └────────────────────────────────┘ : + │ │ : + │ │ Ret (None, __VERIFIER_assert) : + │ ▼ : + │ ┌────────────────────────────────┐ : + │ │ _ │ : + │ └────────────────────────────────┘ : + │ │ : + │ │ InlineReturn : + │ ▼ : + │ ┌────────────────────────────────┐ : + └─────────────────── │ _ │ ◀┘ + └────────────────────────────────┘ diff --git a/tests/sv-comp/basic/for_odd_vesal_true-unreach-call.expected b/tests/sv-comp/basic/for_odd_vesal_true-unreach-call.expected new file mode 100644 index 0000000000..ac0cf29bc6 --- /dev/null +++ b/tests/sv-comp/basic/for_odd_vesal_true-unreach-call.expected @@ -0,0 +1,84 @@ + + ┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ │ + │ ┌────────────────────────────────┐ │ + │ │ _ │ │ + │ └────────────────────────────────┘ │ + │ │ │ + │ │ Entry main │ + │ ▼ │ + │ ┌────────────────────────────────┐ │ + │ │ _ │ ·┐ │ + │ └────────────────────────────────┘ : │ + │ │ : │ + │ │ InlineEntry '()' : │ + │ ▼ : │ + │ ┌────────────────────────────────┐ : │ + │ │ _ │ : │ + │ └────────────────────────────────┘ : │ + │ │ : │ + │ │ Entry fun : │ + │ ▼ : │ + │ ┌────────────────────────────────┐ : │ + │ │ _ │ : │ + │ └────────────────────────────────┘ : │ + │ │ : │ + │ │ Assign 'i = 1' : │ + │ ▼ : │ + │ ┌────────────────────────────────┐ InlineEntry '(i < 2000)' ┌───┐ Test (i < 1000,true) ┌────────────────────────────────┐ : Assign 'i = i + 2' │ + │ │ _ │ ◀──────────────────────────────────────────── │ _ │ ◀───────────────────────────────────────────── │ _ │ ◀┼───────────────────────────────────────────────┘ + │ └────────────────────────────────┘ └───┘ └────────────────────────────────┘ : + │ │ : │ : + │ │ Entry __VERIFIER_assert : │ Test (i < 1000,false) : + │ ▼ : ▼ : + │ ┌────────────────────────────────┐ : ┌────────────────────────────────┐ : + │ │ _ │ : ┌············································ │ _ │ : + │ └────────────────────────────────┘ : : └────────────────────────────────┘ : + │ │ : : │ : + │ │ Test (! cond,false) : : │ InlineEntry '(i == 1001)' : + │ ▼ : : ▼ : + │ ┌────────────────────────────────┐ : : ┌────────────────────────────────┐ : + │ │ _ │ : : │ _ │ : + │ └────────────────────────────────┘ : : └────────────────────────────────┘ : + │ │ : : │ : + │ │ Ret (None, __VERIFIER_assert) : : │ Entry __VERIFIER_assert : + │ ▼ : : ▼ : + │ ┌────────────────────────────────┐ : : ┌────────────────────────────────┐ : Test (! cond,true) ┌─────────────────────────┐ + │ │ _ │ : : │ _ │ ─┼──────────────────────────────────────────────────────────────────▶ │ _ │ + │ └────────────────────────────────┘ : : └────────────────────────────────┘ : └─────────────────────────┘ + │ │ : : │ : │ + │ │ InlineReturn : : Inlined Proc '__VERIFIER_assert(i == 1001)' │ Test (! cond,false) : │ InlineEntry '()' + │ ▼ : : ▼ : ▼ + │ ┌────────────────────────────────┐ Inlined Proc '__VERIFIER_assert(i < 2000)' : : ┌────────────────────────────────┐ : ┌─────────────────────────┐ + └─ │ _ │ ◀···············································┘ : │ _ │ : │ _ │ + └────────────────────────────────┘ : └────────────────────────────────┘ : └─────────────────────────┘ + : │ : │ + : │ Ret (None, __VERIFIER_assert) : │ Entry __VERIFIER_error + : ▼ : ▼ + : ┌────────────────────────────────┐ : ┌─────────────────────────┐ + : │ _ │ : │ _ │ + : └────────────────────────────────┘ : └─────────────────────────┘ + : │ : + : │ InlineReturn : Inlined Proc 'fun()' + : ▼ : + : ┌────────────────────────────────┐ : + └···········································▶ │ _ │ : + └────────────────────────────────┘ : + │ : + │ Ret (None, fun) : + ▼ : + ┌────────────────────────────────┐ : + │ _ │ : + └────────────────────────────────┘ : + │ : + │ InlineReturn : + ▼ : + ┌────────────────────────────────┐ : + │ _ │ ◀┘ + └────────────────────────────────┘ + │ + │ Ret (Some 0, main) + ▼ + ┌────────────────────────────────┐ + │ _ │ + └────────────────────────────────┘ diff --git a/tests/sv-comp/basic/for_true-unreach-call.expected b/tests/sv-comp/basic/for_true-unreach-call.expected new file mode 100644 index 0000000000..6e71e66a54 --- /dev/null +++ b/tests/sv-comp/basic/for_true-unreach-call.expected @@ -0,0 +1,51 @@ + ┌────────────────────────────────┐ + │ _ │ + └────────────────────────────────┘ + │ + │ Entry main + ▼ + ┌────────────────────────────────┐ + │ _ │ + └────────────────────────────────┘ + │ + │ Assign 'i = 0' + ▼ + ┌────────────────────────────────┐ Test (i < 1000,false) ┌───┐ Ret (Some 0, main) ┌───┐ + ┌──────────────────▶ │ _ │ ────────────────────────────────────────────▶ │ _ │ ────────────────────▶ │ _ │ + │ └────────────────────────────────┘ └───┘ └───┘ + │ │ + │ │ Test (i < 1000,true) + │ ▼ + │ ┌────────────────────────────────┐ + │ │ _ │ ·┐ + │ └────────────────────────────────┘ : + │ │ : + │ │ InlineEntry '(i < 2000)' : + │ ▼ : + │ ┌────────────────────────────────┐ : + │ │ _ │ : + │ └────────────────────────────────┘ : + │ │ : + │ │ Entry __VERIFIER_assert : + │ ▼ : + │ ┌────────────────────────────────┐ : + │ Assign 'i = i + 1' │ _ │ : + │ └────────────────────────────────┘ : + │ │ : + │ │ Test (! cond,false) : Inlined Proc '__VERIFIER_assert(i < 2000)' + │ ▼ : + │ ┌────────────────────────────────┐ : + │ │ _ │ : + │ └────────────────────────────────┘ : + │ │ : + │ │ Ret (None, __VERIFIER_assert) : + │ ▼ : + │ ┌────────────────────────────────┐ : + │ │ _ │ : + │ └────────────────────────────────┘ : + │ │ : + │ │ InlineReturn : + │ ▼ : + │ ┌────────────────────────────────┐ : + └─────────────────── │ _ │ ◀┘ + └────────────────────────────────┘ diff --git a/tests/sv-comp/basic/global_init_true-unreach-call.expected b/tests/sv-comp/basic/global_init_true-unreach-call.expected new file mode 100644 index 0000000000..dc67ece119 --- /dev/null +++ b/tests/sv-comp/basic/global_init_true-unreach-call.expected @@ -0,0 +1,45 @@ +┌────────────────────────────────┐ +│ _ │ +└────────────────────────────────┘ + │ + │ Entry main + ▼ +┌────────────────────────────────┐ +│ _ │ ·┐ +└────────────────────────────────┘ : + │ : + │ InlineEntry '(g == 1)' : + ▼ : +┌────────────────────────────────┐ : +│ _ │ : +└────────────────────────────────┘ : + │ : + │ Entry __VERIFIER_assert : + ▼ : +┌────────────────────────────────┐ : +│ _ │ : +└────────────────────────────────┘ : + │ : + │ Test (! cond,false) : Inlined Proc '__VERIFIER_assert(g == 1)' + ▼ : +┌────────────────────────────────┐ : +│ _ │ : +└────────────────────────────────┘ : + │ : + │ Ret (None, __VERIFIER_assert) : + ▼ : +┌────────────────────────────────┐ : +│ _ │ : +└────────────────────────────────┘ : + │ : + │ InlineReturn : + ▼ : +┌────────────────────────────────┐ : +│ _ │ ◀┘ +└────────────────────────────────┘ + │ + │ Ret (Some 0, main) + ▼ +┌────────────────────────────────┐ +│ _ │ +└────────────────────────────────┘ diff --git a/tests/sv-comp/basic/if_det_false-unreach-call.expected b/tests/sv-comp/basic/if_det_false-unreach-call.expected new file mode 100644 index 0000000000..49de731f92 --- /dev/null +++ b/tests/sv-comp/basic/if_det_false-unreach-call.expected @@ -0,0 +1,39 @@ +┌─────────────────────────┐ +│ _ │ +└─────────────────────────┘ + │ + │ Entry main + ▼ +┌─────────────────────────┐ +│ _ │ +└─────────────────────────┘ + │ + │ Test (1,true) + ▼ +┌─────────────────────────┐ +│ _ │ +└─────────────────────────┘ + │ + │ Assign 'x = 1' + ▼ +┌─────────────────────────┐ +│ _ │ +└─────────────────────────┘ + │ + │ Test (x,true) + ▼ +┌─────────────────────────┐ +│ _ │ +└─────────────────────────┘ + │ + │ InlineEntry '()' + ▼ +┌─────────────────────────┐ +│ _ │ +└─────────────────────────┘ + │ + │ Entry __VERIFIER_error + ▼ +┌─────────────────────────┐ +│ _ │ +└─────────────────────────┘ diff --git a/tests/sv-comp/basic/if_det_incr_true-unreach-call.expected b/tests/sv-comp/basic/if_det_incr_true-unreach-call.expected new file mode 100644 index 0000000000..336a8ccb63 --- /dev/null +++ b/tests/sv-comp/basic/if_det_incr_true-unreach-call.expected @@ -0,0 +1,27 @@ +┌─────────────────────┐ +│ _ │ +└─────────────────────┘ + │ + │ Entry main + ▼ +┌─────────────────────┐ +│ _ │ ◀┐ +└─────────────────────┘ │ + │ │ + │ Test (1,true) │ + ▼ │ +┌─────────────────────┐ │ +│ _ │ │ +└─────────────────────┘ │ + │ │ + │ Assign 'x = -1' │ Test (x,false) + ▼ │ +┌─────────────────────┐ │ +│ _ │ │ +└─────────────────────┘ │ + │ │ + │ Assign 'x = x + 1' │ + ▼ │ +┌─────────────────────┐ │ +│ _ │ ─┘ +└─────────────────────┘ diff --git a/tests/sv-comp/basic/if_det_true-unreach-call.expected b/tests/sv-comp/basic/if_det_true-unreach-call.expected new file mode 100644 index 0000000000..883b8a1a76 --- /dev/null +++ b/tests/sv-comp/basic/if_det_true-unreach-call.expected @@ -0,0 +1,21 @@ +┌─────────────────┐ +│ _ │ +└─────────────────┘ + │ + │ Entry main + ▼ +┌─────────────────┐ +│ _ │ ◀┐ +└─────────────────┘ │ + │ │ + │ Test (1,true) │ + ▼ │ +┌─────────────────┐ │ +│ _ │ │ Test (x,false) +└─────────────────┘ │ + │ │ + │ Assign 'x = 0' │ + ▼ │ +┌─────────────────┐ │ +│ _ │ ─┘ +└─────────────────┘ diff --git a/tests/sv-comp/basic/if_mod_false-unreach-call.expected b/tests/sv-comp/basic/if_mod_false-unreach-call.expected new file mode 100644 index 0000000000..5802a2ea7f --- /dev/null +++ b/tests/sv-comp/basic/if_mod_false-unreach-call.expected @@ -0,0 +1,63 @@ + ┌────────────────────────────────────────┐ + │ _ │ + └────────────────────────────────────────┘ + │ + │ Entry main + ▼ + ┌────────────────────────────────────────┐ + ┌────────────────────▶ │ _ │ + │ └────────────────────────────────────────┘ + │ │ + │ │ Test (1,true) + │ ▼ + │ ┌────────────────────────────────────────┐ + │ │ _ │ ·┐ + │ └────────────────────────────────────────┘ : + │ │ : + │ │ InlineEntry '()' : + │ ▼ : + │ ┌────────────────────────────────────────┐ : + │ │ _ │ : + │ └────────────────────────────────────────┘ : + │ │ : + │ │ Entry __VERIFIER_nondet_int : + │ ▼ : + │ ┌────────────────────────────────────────┐ : + │ Test (x >= 50,false) │ _ │ : Inlined Proc 'tmp = __VERIFIER_nondet_int()' + │ └────────────────────────────────────────┘ : + │ │ : + │ │ Ret (Some val, __VERIFIER_nondet_int) : + │ ▼ : + │ ┌────────────────────────────────────────┐ : + │ │ _ │ : + │ └────────────────────────────────────────┘ : + │ │ : + │ │ InlineReturn 'tmp' : + │ ▼ : + │ ┌────────────────────────────────────────┐ : + │ │ _ │ ◀┘ + │ └────────────────────────────────────────┘ + │ │ + │ │ Assign 'x = tmp 00' + │ ▼ + │ ┌────────────────────────────────────────┐ + └───────────────────── │ _ │ + └────────────────────────────────────────┘ + │ + │ Test (x >= 50,true) + ▼ + ┌────────────────────────────────────────┐ + │ _ │ + └────────────────────────────────────────┘ + │ + │ InlineEntry '()' + ▼ + ┌────────────────────────────────────────┐ + │ _ │ + └────────────────────────────────────────┘ + │ + │ Entry __VERIFIER_error + ▼ + ┌────────────────────────────────────────┐ + │ _ │ + └────────────────────────────────────────┘ diff --git a/tests/sv-comp/basic/if_mod_true-unreach-call.expected b/tests/sv-comp/basic/if_mod_true-unreach-call.expected new file mode 100644 index 0000000000..e4fbf41a08 --- /dev/null +++ b/tests/sv-comp/basic/if_mod_true-unreach-call.expected @@ -0,0 +1,45 @@ + ┌────────────────────────────────────────┐ + │ _ │ + └────────────────────────────────────────┘ + │ + │ Entry main + ▼ + ┌────────────────────────────────────────┐ + ┌─────────────────────▶ │ _ │ + │ └────────────────────────────────────────┘ + │ │ + │ │ Test (1,true) + │ ▼ + │ ┌────────────────────────────────────────┐ + │ │ _ │ ·┐ + │ └────────────────────────────────────────┘ : + │ │ : + │ │ InlineEntry '()' : + │ ▼ : + │ ┌────────────────────────────────────────┐ : + │ │ _ │ : + │ └────────────────────────────────────────┘ : + │ │ : + │ │ Entry __VERIFIER_nondet_int : + │ ▼ : + │ ┌────────────────────────────────────────┐ : + │ Test (x >= 100,false) │ _ │ : Inlined Proc 'tmp = __VERIFIER_nondet_int()' + │ └────────────────────────────────────────┘ : + │ │ : + │ │ Ret (Some val, __VERIFIER_nondet_int) : + │ ▼ : + │ ┌────────────────────────────────────────┐ : + │ │ _ │ : + │ └────────────────────────────────────────┘ : + │ │ : + │ │ InlineReturn 'tmp' : + │ ▼ : + │ ┌────────────────────────────────────────┐ : + │ │ _ │ ◀┘ + │ └────────────────────────────────────────┘ + │ │ + │ │ Assign 'x = tmp 00' + │ ▼ + │ ┌────────────────────────────────────────┐ + └────────────────────── │ _ │ + └────────────────────────────────────────┘ diff --git a/tests/sv-comp/basic/if_nondet_fun_false-unreach-call.expected b/tests/sv-comp/basic/if_nondet_fun_false-unreach-call.expected new file mode 100644 index 0000000000..0e61d769de --- /dev/null +++ b/tests/sv-comp/basic/if_nondet_fun_false-unreach-call.expected @@ -0,0 +1,63 @@ + ┌────────────────────────────────────────┐ + │ _ │ + └────────────────────────────────────────┘ + │ + │ Entry main + ▼ + ┌────────────────────────────────────────┐ + ┌──────────────▶ │ _ │ + │ └────────────────────────────────────────┘ + │ │ + │ │ Test (1,true) + │ ▼ + │ ┌────────────────────────────────────────┐ + │ │ _ │ ·┐ + │ └────────────────────────────────────────┘ : + │ │ : + │ │ InlineEntry '()' : + │ ▼ : + │ ┌────────────────────────────────────────┐ : + │ │ _ │ : + │ └────────────────────────────────────────┘ : + │ │ : + │ │ Entry __VERIFIER_nondet_int : + │ ▼ : + │ ┌────────────────────────────────────────┐ : + │ Test (x,false) │ _ │ : Inlined Proc 'tmp = __VERIFIER_nondet_int()' + │ └────────────────────────────────────────┘ : + │ │ : + │ │ Ret (Some val, __VERIFIER_nondet_int) : + │ ▼ : + │ ┌────────────────────────────────────────┐ : + │ │ _ │ : + │ └────────────────────────────────────────┘ : + │ │ : + │ │ InlineReturn 'tmp' : + │ ▼ : + │ ┌────────────────────────────────────────┐ : + │ │ _ │ ◀┘ + │ └────────────────────────────────────────┘ + │ │ + │ │ Assign 'x = tmp' + │ ▼ + │ ┌────────────────────────────────────────┐ + └─────────────── │ _ │ + └────────────────────────────────────────┘ + │ + │ Test (x,true) + ▼ + ┌────────────────────────────────────────┐ + │ _ │ + └────────────────────────────────────────┘ + │ + │ InlineEntry '()' + ▼ + ┌────────────────────────────────────────┐ + │ _ │ + └────────────────────────────────────────┘ + │ + │ Entry __VERIFIER_error + ▼ + ┌────────────────────────────────────────┐ + │ _ │ + └────────────────────────────────────────┘ diff --git a/tests/sv-comp/basic/if_nondet_var_false-unreach-call.expected b/tests/sv-comp/basic/if_nondet_var_false-unreach-call.expected new file mode 100644 index 0000000000..c09019ec2c --- /dev/null +++ b/tests/sv-comp/basic/if_nondet_var_false-unreach-call.expected @@ -0,0 +1,33 @@ +┌─────────────────────────┐ +│ _ │ +└─────────────────────────┘ + │ + │ Entry main + ▼ +┌─────────────────────────┐ +│ _ │ ◀┐ +└─────────────────────────┘ │ + │ │ + │ Test (1,true) │ Test (x,false) + ▼ │ +┌─────────────────────────┐ │ +│ _ │ ─┘ +└─────────────────────────┘ + │ + │ Test (x,true) + ▼ +┌─────────────────────────┐ +│ _ │ +└─────────────────────────┘ + │ + │ InlineEntry '()' + ▼ +┌─────────────────────────┐ +│ _ │ +└─────────────────────────┘ + │ + │ Entry __VERIFIER_error + ▼ +┌─────────────────────────┐ +│ _ │ +└─────────────────────────┘ diff --git a/tests/sv-comp/basic/if_trier_exclude_multiple_false-unreach-call.c b/tests/sv-comp/basic/if_trier_exclude_multiple_false-unreach-call.c index e021ff930d..7af8b4e967 100644 --- a/tests/sv-comp/basic/if_trier_exclude_multiple_false-unreach-call.c +++ b/tests/sv-comp/basic/if_trier_exclude_multiple_false-unreach-call.c @@ -25,5 +25,6 @@ int main() __VERIFIER_assert(x != 1); } } + // TODO: double InlineReturn edge in two first cases? return 0; } \ No newline at end of file diff --git a/tests/sv-comp/basic/if_trier_exclude_multiple_false-unreach-call.expected b/tests/sv-comp/basic/if_trier_exclude_multiple_false-unreach-call.expected new file mode 100644 index 0000000000..53bd068d84 --- /dev/null +++ b/tests/sv-comp/basic/if_trier_exclude_multiple_false-unreach-call.expected @@ -0,0 +1,92 @@ + + ┌··············································································································┐ + : : + : ┌────────────────────────────────────────┐ : + : │ _ │ : + : └────────────────────────────────────────┘ : + : │ : + ┌···················································································································┼·······················┐ │ Entry main : Inlined Proc '__VERIFIER_assert(x != 1)' + : : : ▼ ▼ + : ┌────────────────────────────────┐ InlineEntry '(x != 1)' ┌──────────────────────┐ : ┌─────────────────────────────────────────────────────────────────────────────────────────┐ + : │ _ │ ◀──────────────────────── │ _ │ └········································▶ │ │ + : └────────────────────────────────┘ └──────────────────────┘ │ │ + : │ ▲ InlineReturn │ │ InlineReturn + : │ Entry __VERIFIER_assert ┌──────────────────────────┼────────────────────────────────────────────────────────────────▶ │ _ │ ◀─────────────────┐ + : ▼ │ │ │ │ │ + : ┌─────────────────────────┐ Test (! cond,true) ┌────────────────────────────────┐ │ │ │ │ │ + : │ _ │ ◀──────────────────── │ _ │ │ │ ┌········································▶ │ │ ◀┐ │ + : └─────────────────────────┘ └────────────────────────────────┘ │ │ : └─────────────────────────────────────────────────────────────────────────────────────────┘ │ │ + : │ │ │ │ : │ │ │ + : │ InlineEntry '()' │ Test (! cond,false) │ │ : │ Test (1,true) │ │ + : ▼ ▼ │ │ : ▼ │ │ + : ┌─────────────────────────┐ ┌────────────────────────────────┐ │ │ : ┌────────────────────────────────────────┐ │ │ + : │ _ │ │ _ │ │ │ : │ _ │ ·┐ │ │ + : └─────────────────────────┘ └────────────────────────────────┘ │ │ : └────────────────────────────────────────┘ : │ │ + : │ │ │ │ : │ : │ │ + : │ Entry __VERIFIER_error │ Ret (None, __VERIFIER_assert) │ │ : │ InlineEntry '()' : │ │ + : ▼ ▼ │ │ : ▼ : │ │ + : ┌─────────────────────────┐ ┌────────────────────────────────┐ │ │ : ┌────────────────────────────────────────┐ : │ │ + : │ _ │ │ _ │ ─┘ │ : │ _ │ : │ │ + : └─────────────────────────┘ └────────────────────────────────┘ │ : └────────────────────────────────────────┘ : │ │ + : │ : │ : │ │ + └······················································┐ │ : │ Entry __VERIFIER_nondet_int : │ │ + : │ : ▼ : │ │ + : │ Test (x == 2,false) : ┌────────────────────────────────────────┐ : │ │ + : │ : Inlined Proc '__VERIFIER_assert(x == 2)' │ _ │ : Inlined Proc 'tmp = __VERIFIER_nondet_int()' │ │ + : │ : └────────────────────────────────────────┘ : │ │ + : │ : │ : │ │ + : │ : │ Ret (Some val, __VERIFIER_nondet_int) : │ │ + : │ : ▼ : │ │ + : │ : ┌────────────────────────────────────────┐ : │ │ + : │ : │ _ │ : │ │ + : │ : └────────────────────────────────────────┘ : │ │ + : │ : │ : │ │ + : │ : │ InlineReturn 'tmp' : │ │ + : │ : ▼ : │ │ + : │ : ┌────────────────────────────────────────┐ : │ │ + : │ : │ _ │ ◀┘ │ │ + : │ : └────────────────────────────────────────┘ │ │ + : │ : │ │ │ + : │ : │ Assign 'x = tmp' │ │ + : │ : ▼ │ │ + ┌────────────────────────────────┐ Test (x == 0,true) │ : ┌────────────────────────────────────────┐ │ │ + │ _ │ ◀───────────────────────────┼───────────────────────┼───────────────────────────────────────── │ _ │ │ InlineReturn │ + └────────────────────────────────┘ │ : └────────────────────────────────────────┘ │ │ + │ │ : │ │ │ + │ │ : │ Test (x == 0,false) │ │ + │ │ : ▼ │ │ + │ │ : ┌────────────────────────────────────────┐ │ │ + │ └───────────────────────┼───────────────────────────────────────── │ _ │ │ │ + │ : └────────────────────────────────────────┘ │ │ + │ : │ │ │ + │ : │ Test (x == 2,true) │ │ + │ : ▼ │ │ + │ : ┌────────────────────────────────────────┐ │ │ + │ └········································· │ _ │ │ │ + │ └────────────────────────────────────────┘ │ │ + │ │ │ │ + │ │ InlineEntry '(x == 2)' │ │ + │ ▼ │ │ + │ InlineEntry '(x == 0)' ┌────────────────────────────────────────┐ │ │ + └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────▶ │ _ │ │ │ + └────────────────────────────────────────┘ │ │ + │ │ │ + │ Entry __VERIFIER_assert │ │ + ▼ │ │ + ┌────────────────────────────────────────┐ │ │ + │ _ │ │ │ + └────────────────────────────────────────┘ │ │ + │ │ │ + │ Test (! cond,false) │ │ + ▼ │ │ + ┌────────────────────────────────────────┐ │ │ + │ _ │ │ │ + └────────────────────────────────────────┘ │ │ + │ │ │ + │ Ret (None, __VERIFIER_assert) │ │ + ▼ │ │ + ┌────────────────────────────────────────┐ │ │ + │ _ │ ──────────────────────────────────────────────────┘ │ + └────────────────────────────────────────┘ │ + │ │ + └───────────────────────────────────────────────────────────────────────────────────────────────────────────┘ diff --git a/tests/sv-comp/basic/if_trier_exclude_multiple_true-unreach-call.c b/tests/sv-comp/basic/if_trier_exclude_multiple_true-unreach-call.c index 83994c2be7..434b6b29fb 100644 --- a/tests/sv-comp/basic/if_trier_exclude_multiple_true-unreach-call.c +++ b/tests/sv-comp/basic/if_trier_exclude_multiple_true-unreach-call.c @@ -25,5 +25,6 @@ int main() __VERIFIER_assert(x != 0 && x != 2); } } + // TODO: double InlineReturn edge in two first cases? return 0; } \ No newline at end of file diff --git a/tests/sv-comp/basic/if_trier_exclude_multiple_true-unreach-call.expected b/tests/sv-comp/basic/if_trier_exclude_multiple_true-unreach-call.expected new file mode 100644 index 0000000000..b94f7fec7e --- /dev/null +++ b/tests/sv-comp/basic/if_trier_exclude_multiple_true-unreach-call.expected @@ -0,0 +1,99 @@ + + ┌·······················································································┐ + : : + : : + ┌─────────────────────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ : : │ + │ : ┌────────────────────────────────────────┐ : │ + │ : │ _ │ : │ + │ : └────────────────────────────────────────┘ : │ + │ : │ : │ + │ ┌·····························································┘ │ Entry main : Inlined Proc '__VERIFIER_assert(x == 0)' │ + │ : ▼ ▼ │ + │ : InlineReturn ┌──────────────────────────────────────────────────────────────────────────────────────────────┐ Inlined Proc '__VERIFIER_assert(x == 2)' ┌─────────────────────┐ + │ ┌────┼───────────────────────────────────────────────────────────────────────────────────────────────────────▶ │ │ ◀·········································· │ _ │ + │ │ : │ │ └─────────────────────┘ + │ │ : InlineReturn │ │ ▲ + │ │ : ┌─────────────────────────────────────────────────────────────────────▶ │ _ │ │ Test (x == 2,true) + │ │ : │ │ │ │ + │ │ : │ │ │ │ + │ │ : │ ┌·········································▶ │ │ ◀┐ │ + │ │ : │ : └──────────────────────────────────────────────────────────────────────────────────────────────┘ │ │ + │ │ : │ : │ │ │ + │ │ : │ : │ Test (1,true) │ InlineReturn │ + │ │ : │ : ▼ │ │ + │ │ : │ : ┌────────────────────────────────────────┐ │ │ + │ │ : │ : │ _ │ ·┐ ┌────┼────────────────────────────────────────────┘ + │ │ : │ : └────────────────────────────────────────┘ : │ │ + │ │ : │ : │ : │ │ + │ │ : │ : │ InlineEntry '()' : │ │ + │ │ : │ : ▼ : │ │ + │ │ : │ : ┌────────────────────────────────────────┐ : │ │ + │ │ : │ : │ _ │ : │ │ + │ │ : │ : └────────────────────────────────────────┘ : │ │ + │ InlineEntry '(x == 2)' │ : │ : │ : │ │ + │ │ : │ : │ Entry __VERIFIER_nondet_int : │ │ + │ │ : │ : ▼ : │ │ + │ │ : │ : ┌────────────────────────────────────────┐ : │ │ + │ │ : │ : Inlined Proc '__VERIFIER_assert(tmp___0)' │ _ │ : Inlined Proc 'tmp = __VERIFIER_nondet_int()' │ │ + │ │ : │ : └────────────────────────────────────────┘ : │ │ + │ │ : │ : │ : │ │ + │ │ : │ : │ Ret (Some val, __VERIFIER_nondet_int) : │ │ + │ │ : │ : ▼ : │ │ + │ │ : │ : ┌────────────────────────────────────────┐ : │ │ + │ │ : │ : │ _ │ : │ │ + │ │ : │ : └────────────────────────────────────────┘ : │ │ + │ │ : │ : │ : │ │ + │ │ : │ : │ InlineReturn 'tmp' : │ │ + │ │ : │ : ▼ : │ │ + │ │ : │ : ┌────────────────────────────────────────┐ : │ │ + │ │ : │ : │ _ │ ◀┘ │ │ + │ │ : │ : └────────────────────────────────────────┘ │ │ + │ │ : │ : │ │ │ + │ │ : │ : │ Assign 'x = tmp' │ │ + │ │ : │ : ▼ │ │ + │ │ ┌────────────────────────────────┐ │ Test (x == 0,true) : ┌────────────────────────────────────────┐ │ │ + │ │ │ _ │ ◀┼───────────────────────────┼────────────────────────────────────────── │ _ │ │ │ + │ │ └────────────────────────────────┘ │ : └────────────────────────────────────────┘ │ │ + │ │ │ │ : │ │ │ + │ │ │ InlineEntry '(x == 0)' │ : │ Test (x == 0,false) │ │ + │ │ ▼ │ : ▼ │ │ + │ │ ┌────────────────────────────────┐ │ : ┌────────────────────────────────────────┐ │ │ + └──────────────────────────┼▶ │ _ │ │ : │ _ │ ──────────────────────────────────────────────────┘ │ + │ └────────────────────────────────┘ │ : └────────────────────────────────────────┘ │ + │ │ │ : │ │ + │ │ Entry __VERIFIER_assert │ : │ Test (x == 2,false) │ + │ ▼ │ : ▼ │ + │ ┌────────────────────────────────┐ │ : ┌────────────────────────────────────────┐ │ + │ │ _ │ │ : │ _ │ │ + │ └────────────────────────────────┘ │ : └────────────────────────────────────────┘ │ + │ │ │ : │ │ + │ │ Test (! cond,false) │ : │ Assign 'tmp___0 = x != 0 && x != 2' │ + │ ▼ │ : ▼ │ + │ ┌────────────────────────────────┐ │ : ┌────────────────────────────────────────┐ │ + │ │ _ │ │ └·········································· │ _ │ │ + │ └────────────────────────────────┘ │ └────────────────────────────────────────┘ │ + │ │ │ │ │ + │ │ Ret (None, __VERIFIER_assert) │ │ InlineEntry '(tmp___0)' │ + │ ▼ │ ▼ │ + │ ┌────────────────────────────────┐ │ ┌────────────────────────────────────────┐ │ + └─ │ _ │ ─┘ │ _ │ │ + └────────────────────────────────┘ └────────────────────────────────────────┘ │ + │ │ + │ Entry __VERIFIER_assert │ + ▼ │ + ┌────────────────────────────────────────┐ │ + │ _ │ │ + └────────────────────────────────────────┘ │ + │ │ + │ Test (! cond,false) │ + ▼ │ + ┌────────────────────────────────────────┐ │ + │ _ │ │ + └────────────────────────────────────────┘ │ + │ │ + │ Ret (None, __VERIFIER_assert) │ + ▼ │ + ┌────────────────────────────────────────┐ │ + │ _ │ ───────────────────────────────────────────────────────┘ + └────────────────────────────────────────┘ diff --git a/tests/sv-comp/basic/if_trier_exclude_true-unreach-call.expected b/tests/sv-comp/basic/if_trier_exclude_true-unreach-call.expected new file mode 100644 index 0000000000..cad63798c9 --- /dev/null +++ b/tests/sv-comp/basic/if_trier_exclude_true-unreach-call.expected @@ -0,0 +1,81 @@ + + ┌······································································································································································································┐ + : : + : : + : ┌───────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ : + : │ │ : + : │ ┌────────────────────────────────────────┐ │ : + : │ │ _ │ │ : + : │ └────────────────────────────────────────┘ │ : + : │ │ │ : + : │ │ Entry main │ InlineReturn : Inlined Proc '__VERIFIER_assert(x != 0)' + : │ ▼ ▼ ▼ + : │ ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ + : │ ┌········································▶ │ _ │ ◀┐ + : │ : └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ │ + : │ : │ │ + : │ : │ Test (1,true) │ + : │ : ▼ │ + : │ : ┌────────────────────────────────────────┐ │ + : │ : │ _ │ ·┐ │ + : │ : └────────────────────────────────────────┘ : │ + : │ : │ : │ + : │ : │ InlineEntry '()' : │ + : │ : ▼ : │ + : │ : ┌────────────────────────────────────────┐ : │ + : │ : │ _ │ : │ + : │ : └────────────────────────────────────────┘ : │ + : │ : │ : │ + : │ : │ Entry __VERIFIER_nondet_int : │ + : │ : ▼ : │ + : │ : ┌────────────────────────────────────────┐ : │ + : │ : Inlined Proc '__VERIFIER_assert(x == 0)' │ _ │ : Inlined Proc 'tmp = __VERIFIER_nondet_int()' │ + : │ : └────────────────────────────────────────┘ : │ + : │ : │ : │ + : │ : │ Ret (Some val, __VERIFIER_nondet_int) : │ + : │ : ▼ : │ + : │ : ┌────────────────────────────────────────┐ : │ + : │ : │ _ │ : │ + : │ : └────────────────────────────────────────┘ : │ + : │ : │ : │ + : │ : │ InlineReturn 'tmp' : │ + : │ : ▼ : │ + : │ : ┌────────────────────────────────────────┐ : │ + : │ : │ _ │ ◀┘ │ + : │ : └────────────────────────────────────────┘ │ + : │ : │ │ + : │ : │ Assign 'x = tmp' │ InlineReturn + : │ : ▼ │ +┌────────────────────────────────┐ │ Test (x == 0,false) : ┌────────────────────────────────────────┐ │ +│ _ │ ◀┼────────────────────────────┼───────────────────────────────────────── │ _ │ │ +└────────────────────────────────┘ │ : └────────────────────────────────────────┘ │ + │ │ : │ │ + │ InlineEntry '(x != 0)' │ : │ Test (x == 0,true) │ + ▼ │ : ▼ │ +┌────────────────────────────────┐ │ : ┌────────────────────────────────────────┐ │ +│ _ │ │ └········································· │ _ │ │ +└────────────────────────────────┘ │ └────────────────────────────────────────┘ │ + │ │ │ │ + │ Entry __VERIFIER_assert │ │ InlineEntry '(x == 0)' │ + ▼ │ ▼ │ +┌────────────────────────────────┐ │ ┌────────────────────────────────────────┐ │ +│ _ │ │ │ _ │ │ +└────────────────────────────────┘ │ └────────────────────────────────────────┘ │ + │ │ │ │ + │ Test (! cond,false) │ │ Entry __VERIFIER_assert │ + ▼ │ ▼ │ +┌────────────────────────────────┐ │ ┌────────────────────────────────────────┐ │ +│ _ │ │ │ _ │ │ +└────────────────────────────────┘ │ └────────────────────────────────────────┘ │ + │ │ │ │ + │ Ret (None, __VERIFIER_assert) │ │ Test (! cond,false) │ + ▼ │ ▼ │ +┌────────────────────────────────┐ │ ┌────────────────────────────────────────┐ │ +│ _ │ ─┘ │ _ │ │ +└────────────────────────────────┘ └────────────────────────────────────────┘ │ + │ │ + │ Ret (None, __VERIFIER_assert) │ + ▼ │ + ┌────────────────────────────────────────┐ │ + │ _ │ ───────────────────────────────────────────────────────────────────────────────────────────────┘ + └────────────────────────────────────────┘ diff --git a/tests/sv-comp/cfg/builtin_expect_true-unreach-call.expected b/tests/sv-comp/cfg/builtin_expect_true-unreach-call.expected new file mode 100644 index 0000000000..7f2218fdcb --- /dev/null +++ b/tests/sv-comp/cfg/builtin_expect_true-unreach-call.expected @@ -0,0 +1,27 @@ +┌───────────────────────────────────────────────┐ +│ _ │ +└───────────────────────────────────────────────┘ + │ + │ Entry main + ▼ +┌───────────────────────────────────────────────┐ +│ _ │ +└───────────────────────────────────────────────┘ + │ + │ Assign 'x = 0' + ▼ +┌───────────────────────────────────────────────┐ +│ _ │ +└───────────────────────────────────────────────┘ + │ + │ Proc '__builtin_expect((long )(x == 0), 1L)' + ▼ +┌───────────────────────────────────────────────┐ +│ _ │ +└───────────────────────────────────────────────┘ + │ + │ Ret (Some 0, main) + ▼ +┌───────────────────────────────────────────────┐ +│ _ │ +└───────────────────────────────────────────────┘ diff --git a/tests/sv-comp/cfg/free_spawn_true-unreach-call.expected b/tests/sv-comp/cfg/free_spawn_true-unreach-call.expected new file mode 100644 index 0000000000..5b6b9bfe95 --- /dev/null +++ b/tests/sv-comp/cfg/free_spawn_true-unreach-call.expected @@ -0,0 +1,75 @@ + ┌────────────────────────────────────────────┐ + │ _ │ + └────────────────────────────────────────────┘ + │ + │ Entry main + ▼ + ┌────────────────────────────────────────────┐ + │ _ │ + └────────────────────────────────────────────┘ + │ + │ Assign 'p = (void *)0' + ▼ + ┌────────────────────────────────────────────┐ + │ _ │ ·┐ + └────────────────────────────────────────────┘ : + │ : + │ InlineEntry '()' : + ▼ : + ┌────────────────────────────────────────────┐ : + │ _ │ : + └────────────────────────────────────────────┘ : + │ : + │ Entry __VERIFIER_nondet_int : + ▼ : + ┌────────────────────────────────────────────┐ : + │ _ │ : Inlined Proc 'tmp = __VERIFIER_nondet_int()' + └────────────────────────────────────────────┘ : + │ : + │ Ret (Some val, __VERIFIER_nondet_int) : + ▼ : + ┌────────────────────────────────────────────┐ : + │ _ │ : + └────────────────────────────────────────────┘ : + │ : + │ InlineReturn 'tmp' : + ▼ : + ┌────────────────────────────────────────────┐ : + │ _ │ ◀┘ + └────────────────────────────────────────────┘ + │ + │ Assign 'x = tmp ' + ▼ +┌───┐ Test (x == 0 || (x == 1 || x == -1),false) ┌────────────────────────────────────────────┐ +│ _ │ ◀──────────────────────────────────────────── │ _ │ +└───┘ └────────────────────────────────────────────┘ + │ │ + │ │ Test (x == 0 || (x == 1 || x == -1),true) + │ ▼ + │ ┌────────────────────────────────────────────┐ + │ │ _ │ + │ └────────────────────────────────────────────┘ + │ │ + │ │ Proc 'tmp___0 = malloc(1)' + │ ▼ + │ ┌────────────────────────────────────────────┐ + │ │ _ │ + │ └────────────────────────────────────────────┘ + │ │ + │ │ Assign 'p = (void *)tmp___0' + │ ▼ + │ Assign 'p = (void *)(& foo)' ┌────────────────────────────────────────────┐ + └───────────────────────────────────────────────▶ │ _ │ + └────────────────────────────────────────────┘ + │ + │ Proc 'free(p)' + ▼ + ┌────────────────────────────────────────────┐ + │ _ │ + └────────────────────────────────────────────┘ + │ + │ Ret (Some 0, main) + ▼ + ┌────────────────────────────────────────────┐ + │ _ │ + └────────────────────────────────────────────┘ diff --git a/tests/sv-comp/cfg/free_spawn_ub_true-unreach-call.expected b/tests/sv-comp/cfg/free_spawn_ub_true-unreach-call.expected new file mode 100644 index 0000000000..0a0df994d4 --- /dev/null +++ b/tests/sv-comp/cfg/free_spawn_ub_true-unreach-call.expected @@ -0,0 +1,27 @@ +┌───────────────────────────────────┐ +│ _ │ +└───────────────────────────────────┘ + │ + │ Entry main + ▼ +┌───────────────────────────────────┐ +│ _ │ +└───────────────────────────────────┘ + │ + │ Assign 'p = (void (*)())(& foo)' + ▼ +┌───────────────────────────────────┐ +│ _ │ +└───────────────────────────────────┘ + │ + │ Proc 'free(p)' + ▼ +┌───────────────────────────────────┐ +│ _ │ +└───────────────────────────────────┘ + │ + │ Ret (Some 0, main) + ▼ +┌───────────────────────────────────┐ +│ _ │ +└───────────────────────────────────┘ diff --git a/tests/sv-comp/cfg/join_true-unreach-call.expected b/tests/sv-comp/cfg/join_true-unreach-call.expected new file mode 100644 index 0000000000..c882471576 --- /dev/null +++ b/tests/sv-comp/cfg/join_true-unreach-call.expected @@ -0,0 +1,87 @@ + ┌────────────────────────────────────────┐ + │ _ │ + └────────────────────────────────────────┘ + │ + │ Entry main + ▼ + ┌────────────────────────────────────────┐ + │ _ │ ·┐ + └────────────────────────────────────────┘ : + │ : + │ InlineEntry '()' : + ▼ : + ┌────────────────────────────────────────┐ : + │ _ │ : + └────────────────────────────────────────┘ : + │ : + │ Entry __VERIFIER_nondet_int : + ▼ : + ┌────────────────────────────────────────┐ : + │ _ │ : Inlined Proc 'tmp = __VERIFIER_nondet_int()' + └────────────────────────────────────────┘ : + │ : + │ Ret (Some val, __VERIFIER_nondet_int) : + ▼ : + ┌────────────────────────────────────────┐ : + │ _ │ : + └────────────────────────────────────────┘ : + │ : + │ InlineReturn 'tmp' : + ▼ : +┌───┐ Test (tmp,true) ┌────────────────────────────────────────┐ : +│ _ │ ◀───────────────── │ _ │ ◀┘ +└───┘ └────────────────────────────────────────┘ + │ │ + │ │ Test (tmp,false) + │ ▼ + │ ┌────────────────────────────────────────┐ + │ │ _ │ + │ └────────────────────────────────────────┘ + │ │ + │ │ Assign 'x = 2' + │ ▼ + │ Assign 'x = 1' ┌────────────────────────────────────────┐ + └────────────────────▶ │ _ │ + └────────────────────────────────────────┘ + │ + │ Assign 'tmp___0 = 1 <= x && x <= 2' + ▼ + ┌────────────────────────────────────────┐ + │ _ │ ·┐ + └────────────────────────────────────────┘ : + │ : + │ InlineEntry '(tmp___0)' : + ▼ : + ┌────────────────────────────────────────┐ : + │ _ │ : + └────────────────────────────────────────┘ : + │ : + │ Entry __VERIFIER_assert : + ▼ : + ┌────────────────────────────────────────┐ : + │ _ │ : + └────────────────────────────────────────┘ : + │ : + │ Test (! cond,false) : Inlined Proc '__VERIFIER_assert(tmp___0)' + ▼ : + ┌────────────────────────────────────────┐ : + │ _ │ : + └────────────────────────────────────────┘ : + │ : + │ Ret (None, __VERIFIER_assert) : + ▼ : + ┌────────────────────────────────────────┐ : + │ _ │ : + └────────────────────────────────────────┘ : + │ : + │ InlineReturn : + ▼ : + ┌────────────────────────────────────────┐ : + │ _ │ ◀┘ + └────────────────────────────────────────┘ + │ + │ Ret (Some 0, main) + ▼ + ┌────────────────────────────────────────┐ + │ _ │ + └────────────────────────────────────────┘ diff --git a/tests/sv-comp/cfg/local_shadow_fun_true-unreach-call.expected b/tests/sv-comp/cfg/local_shadow_fun_true-unreach-call.expected new file mode 100644 index 0000000000..bcdf9d176b --- /dev/null +++ b/tests/sv-comp/cfg/local_shadow_fun_true-unreach-call.expected @@ -0,0 +1,125 @@ + ┌────────────────────────────────────────┐ + │ _ │ + └────────────────────────────────────────┘ + │ + │ Entry main + ▼ + ┌────────────────────────────────────────┐ + │ _ │ ·┐ + └────────────────────────────────────────┘ : + │ : + │ InlineEntry '()' : + ▼ : + ┌────────────────────────────────────────┐ : + │ _ │ : + └────────────────────────────────────────┘ : + │ : + │ Entry __VERIFIER_nondet_int : + ▼ : + ┌────────────────────────────────────────┐ : + │ _ │ : Inlined Proc 'tmp = __VERIFIER_nondet_int()' + └────────────────────────────────────────┘ : + │ : + │ Ret (Some val, __VERIFIER_nondet_int) : + ▼ : + ┌────────────────────────────────────────┐ : + │ _ │ : + └────────────────────────────────────────┘ : + │ : + │ InlineReturn 'tmp' : + ▼ : + ┌────────────────────────────────────────┐ : + │ _ │ ◀┘ + └────────────────────────────────────────┘ + │ + │ Assign 'n = tmp' + ▼ + ┌────────────────────────────────────────┐ + │ _ │ ·┐ + └────────────────────────────────────────┘ : + │ : + │ InlineEntry '(n)' : + ▼ : + ┌────────────────────────────────────────┐ : + │ _ │ : + └────────────────────────────────────────┘ : + │ : + │ Entry sum : + ▼ : + ┌────────────────────────────────────────┐ : + │ _ │ : + └────────────────────────────────────────┘ : + │ : + │ Assign 'sum___0 = 0' : + ▼ : + ┌────────────────────────────────────────┐ : + │ _ │ : + └────────────────────────────────────────┘ : + │ : + │ Assign 'i = 0' : + ▼ : +┌─────────────────────────────────┐ Test (i < n,true) ┌────────────────────────────────────────┐ : +│ _ │ ◀─────────────────── │ _ │ ◀┼────────────────────────────────────────────────┐ +└─────────────────────────────────┘ └────────────────────────────────────────┘ : │ + │ │ : │ + │ Assign 'sum___0 = sum___0 + i' │ Test (i < n,false) : │ + ▼ ▼ : │ +┌─────────────────────────────────┐ ┌────────────────────────────────────────┐ : │ +│ _ │ ─┐ │ _ │ : │ +└─────────────────────────────────┘ │ └────────────────────────────────────────┘ : │ + │ │ : │ + │ │ Ret (Some sum___0, sum) : Inlined Proc 'tmp___0 = sum(n)' │ + │ ▼ : │ + │ ┌────────────────────────────────────────┐ : │ + │ │ _ │ : │ + │ └────────────────────────────────────────┘ : │ + │ │ : │ + │ │ InlineReturn 'tmp___0' : │ + │ ▼ : │ + │ ┌────────────────────────────────────────┐ : │ + │ │ _ │ ◀┘ │ + │ └────────────────────────────────────────┘ │ + │ │ │ + │ │ Assign 's = tmp___0' │ + │ ▼ │ + │ ┌────────────────────────────────────────┐ │ + │ │ _ │ ·┐ │ + │ └────────────────────────────────────────┘ : │ + │ │ : │ + │ │ InlineEntry '(s >= 0)' : │ + │ ▼ : │ + │ ┌────────────────────────────────────────┐ : │ + │ │ _ │ : │ + │ └────────────────────────────────────────┘ : │ + │ │ : │ Assign 'i = i + 1' + │ │ Entry __VERIFIER_assert : │ + │ ▼ : │ + │ ┌────────────────────────────────────────┐ : │ + │ │ _ │ : │ + │ └────────────────────────────────────────┘ : │ + │ │ : │ + │ │ Test (! cond,false) : Inlined Proc '__VERIFIER_assert(s >= 0)' │ + │ ▼ : │ + │ ┌────────────────────────────────────────┐ : │ + │ │ _ │ : │ + │ └────────────────────────────────────────┘ : │ + │ │ : │ + │ │ Ret (None, __VERIFIER_assert) : │ + │ ▼ : │ + │ ┌────────────────────────────────────────┐ : │ + │ │ _ │ : │ + │ └────────────────────────────────────────┘ : │ + │ │ : │ + │ │ InlineReturn : │ + │ ▼ : │ + │ ┌────────────────────────────────────────┐ : │ + │ │ _ │ ◀┘ │ + │ └────────────────────────────────────────┘ │ + │ │ │ + │ │ Ret (Some 0, main) │ + │ ▼ │ + │ ┌────────────────────────────────────────┐ │ + │ │ _ │ │ + │ └────────────────────────────────────────┘ │ + │ │ + └────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ diff --git a/tests/sv-comp/cfg/multicall_context_join_true-unreach-call.expected b/tests/sv-comp/cfg/multicall_context_join_true-unreach-call.expected new file mode 100644 index 0000000000..f0df0ef706 --- /dev/null +++ b/tests/sv-comp/cfg/multicall_context_join_true-unreach-call.expected @@ -0,0 +1,101 @@ + ┌────────────────────────────────────────┐ + │ _ │ + └────────────────────────────────────────┘ + │ + │ Entry main + ▼ + ┌────────────────────────────────────────┐ + │ _ │ ·┐ + └────────────────────────────────────────┘ : + │ : + │ InlineEntry '()' : + ▼ : + ┌────────────────────────────────────────┐ : + │ _ │ : + └────────────────────────────────────────┘ : + │ : + │ Entry __VERIFIER_nondet_int : + ▼ : + ┌────────────────────────────────────────┐ : + │ _ │ : Inlined Proc 'tmp = __VERIFIER_nondet_int()' + └────────────────────────────────────────┘ : + │ : + │ Ret (Some val, __VERIFIER_nondet_int) : + ▼ : + ┌────────────────────────────────────────┐ : + │ _ │ : + └────────────────────────────────────────┘ : + │ : + │ InlineReturn 'tmp' : + ▼ : + ┌────────────────────┐ Test (tmp,false) ┌────────────────────────────────────────┐ : + ┌··················································································· │ _ │ ◀───────────────────────────────────────────── │ _ │ ◀┘ + : └────────────────────┘ └────────────────────────────────────────┘ + : │ │ + : │ InlineEntry '(2)' │ Test (tmp,true) + : ▼ ▼ + : ┌────────────────────┐ ┌────────────────────────────────────────┐ + : │ _ │ │ _ │ ·┐ + : └────────────────────┘ └────────────────────────────────────────┘ : + : │ │ : + : │ Entry foo │ InlineEntry '(1)' : + : ▼ ▼ : + : ┌────────────────────────────────┐ InlineEntry '(x - 1 < x)' ┌────────────────────┐ ┌────────────────────────────────────────┐ : + : │ _ │ ◀───────────────────────────────────────────── │ _ │ │ _ │ : + : └────────────────────────────────┘ └────────────────────┘ └────────────────────────────────────────┘ : + : │ : │ : + : │ Entry __VERIFIER_assert : │ Entry foo : + : ▼ : ▼ : + : ┌────────────────────────────────┐ : ┌────────────────────────────────────────┐ : + : │ _ │ : ┌············································ │ _ │ : + : └────────────────────────────────┘ : : └────────────────────────────────────────┘ : + : │ : : │ : + : │ Test (! cond,false) : : │ InlineEntry '(x - 1 < x)' : + : ▼ : : ▼ : + : ┌────────────────────────────────┐ : : ┌────────────────────────────────────────┐ : + : │ _ │ : : │ _ │ : + : └────────────────────────────────┘ : : └────────────────────────────────────────┘ : + : │ : : │ : + : │ Ret (None, __VERIFIER_assert) : : │ Entry __VERIFIER_assert : + : ▼ : : ▼ : + : ┌────────────────────────────────┐ : : ┌────────────────────────────────────────┐ : + : │ _ │ : : │ _ │ : + : └────────────────────────────────┘ : : └────────────────────────────────────────┘ : + : │ : : │ : + : │ InlineReturn : : Inlined Proc '__VERIFIER_assert(x - 1 < x)' │ Test (! cond,false) : + : ▼ : : ▼ : + : ┌────────────────────────────────┐ Inlined Proc '__VERIFIER_assert(x - 1 < x)' : : ┌────────────────────────────────────────┐ : + : │ _ │ ◀················································┘ : │ _ │ : + : └────────────────────────────────┘ : └────────────────────────────────────────┘ : + : │ : │ : + : │ Ret (None, foo) : │ Ret (None, __VERIFIER_assert) : + : ▼ : ▼ : + : ┌────────────────────────────────┐ : ┌────────────────────────────────────────┐ : + : │ _ │ : │ _ │ : + : └────────────────────────────────┘ : └────────────────────────────────────────┘ : + : │ : │ : + : │ : │ InlineReturn : + : │ : ▼ : + : │ : ┌────────────────────────────────────────┐ : + : │ └···········································▶ │ _ │ : + : │ └────────────────────────────────────────┘ : + : │ │ : + : │ │ Ret (None, foo) : + : │ ▼ : + : │ ┌────────────────────────────────────────┐ : + : │ │ _ │ : + : │ └────────────────────────────────────────┘ : + : │ │ : + : │ │ InlineReturn : Inlined Proc 'foo(1)' + : │ ▼ ▼ + : │ InlineReturn ┌─────────────────────────────────────────────────────────────────────────────────────────┐ + : └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────▶ │ _ │ + : └─────────────────────────────────────────────────────────────────────────────────────────┘ + : │ ▲ + : │ Ret (Some 0, main) : Inlined Proc 'foo(2)' + : ▼ : + : ┌────────────────────────────────────────┐ : + : │ _ │ : + : └────────────────────────────────────────┘ : + : : + └······································································································································································································┘ diff --git a/tests/sv-comp/cfg/multicall_context_true-unreach-call.expected b/tests/sv-comp/cfg/multicall_context_true-unreach-call.expected new file mode 100644 index 0000000000..e520689b19 --- /dev/null +++ b/tests/sv-comp/cfg/multicall_context_true-unreach-call.expected @@ -0,0 +1,123 @@ + ┌────────────────────────────────┐ + │ _ │ + └────────────────────────────────┘ + │ + │ Entry main + ▼ + ┌────────────────────────────────┐ + │ _ │ ·┐ + └────────────────────────────────┘ : + │ : + │ InlineEntry '(1)' : + ▼ : + ┌────────────────────────────────┐ : + │ _ │ : + └────────────────────────────────┘ : + │ : + │ Entry foo : + ▼ : + ┌────────────────────────────────┐ : + ┌············································ │ _ │ : + : └────────────────────────────────┘ : + : │ : + : │ InlineEntry '(x - 1 < x)' : + : ▼ : + : ┌────────────────────────────────┐ : + : │ _ │ : + : └────────────────────────────────┘ : + : │ : + : │ Entry __VERIFIER_assert : + : ▼ : + : ┌────────────────────────────────┐ : + : │ _ │ : + : └────────────────────────────────┘ : + : │ : + : Inlined Proc '__VERIFIER_assert(x - 1 < x)' │ Test (! cond,false) : Inlined Proc 'foo(1)' + : ▼ : + : ┌────────────────────────────────┐ : + : │ _ │ : + : └────────────────────────────────┘ : + : │ : + : │ Ret (None, __VERIFIER_assert) : + : ▼ : + : ┌────────────────────────────────┐ : + : │ _ │ : + : └────────────────────────────────┘ : + : │ : + : │ InlineReturn : + : ▼ : + : ┌────────────────────────────────┐ : + └···········································▶ │ _ │ : + └────────────────────────────────┘ : + │ : + │ Ret (None, foo) : + ▼ : + ┌────────────────────────────────┐ : + │ _ │ : + └────────────────────────────────┘ : + │ : + │ InlineReturn : + ▼ : + ┌────────────────────────────────┐ : + ┌············································ │ _ │ ◀┘ + : └────────────────────────────────┘ + : │ + : │ InlineEntry '(2)' + : ▼ + : ┌────────────────────────────────┐ + : │ _ │ + : └────────────────────────────────┘ + : │ + : │ Entry foo + : ▼ + : ┌────────────────────────────────┐ + : │ _ │ ·┐ + : └────────────────────────────────┘ : + : │ : + : │ InlineEntry '(x - 1 < x)' : + : ▼ : + : ┌────────────────────────────────┐ : + : │ _ │ : + : └────────────────────────────────┘ : + : │ : + : │ Entry __VERIFIER_assert : + : ▼ : + : ┌────────────────────────────────┐ : + : │ _ │ : + : └────────────────────────────────┘ : + : │ : + : Inlined Proc 'foo(2)' │ Test (! cond,false) : Inlined Proc '__VERIFIER_assert(x - 1 < x)' + : ▼ : + : ┌────────────────────────────────┐ : + : │ _ │ : + : └────────────────────────────────┘ : + : │ : + : │ Ret (None, __VERIFIER_assert) : + : ▼ : + : ┌────────────────────────────────┐ : + : │ _ │ : + : └────────────────────────────────┘ : + : │ : + : │ InlineReturn : + : ▼ : + : ┌────────────────────────────────┐ : + : │ _ │ ◀┘ + : └────────────────────────────────┘ + : │ + : │ Ret (None, foo) + : ▼ + : ┌────────────────────────────────┐ + : │ _ │ + : └────────────────────────────────┘ + : │ + : │ InlineReturn + : ▼ + : ┌────────────────────────────────┐ + └···········································▶ │ _ │ + └────────────────────────────────┘ + │ + │ Ret (Some 0, main) + ▼ + ┌────────────────────────────────┐ + │ _ │ + └────────────────────────────────┘ diff --git a/tests/sv-comp/cfg/multicall_join_true-unreach-call.expected b/tests/sv-comp/cfg/multicall_join_true-unreach-call.expected new file mode 100644 index 0000000000..de0d379bc7 --- /dev/null +++ b/tests/sv-comp/cfg/multicall_join_true-unreach-call.expected @@ -0,0 +1,99 @@ + ┌────────────────────────────────────────┐ + │ _ │ + └────────────────────────────────────────┘ + │ + │ Entry main + ▼ + ┌────────────────────────────────────────┐ + │ _ │ ·┐ + └────────────────────────────────────────┘ : + │ : + │ InlineEntry '()' : + ▼ : + ┌────────────────────────────────────────┐ : + │ _ │ : + └────────────────────────────────────────┘ : + │ : + │ Entry __VERIFIER_nondet_int : + ▼ : + ┌────────────────────────────────────────┐ : + │ _ │ : Inlined Proc 'tmp = __VERIFIER_nondet_int()' + └────────────────────────────────────────┘ : + │ : + │ Ret (Some val, __VERIFIER_nondet_int) : + ▼ : + ┌────────────────────────────────────────┐ : + │ _ │ : + └────────────────────────────────────────┘ : + │ : + │ InlineReturn 'tmp' : + ▼ : + ┌───┐ Test (tmp,false) ┌────────────────────────────────────────┐ : + ┌· │ _ │ ◀───────────────────────────────────────────── │ _ │ ◀┘ + : └───┘ └────────────────────────────────────────┘ + : │ │ + : │ │ Test (tmp,true) + : │ ▼ + : │ ┌────────────────────────────────────────┐ + : │ │ _ │ ·┐ + : │ └────────────────────────────────────────┘ : + : │ │ : + : │ │ InlineEntry '(1)' : + : │ ▼ : + : │ InlineEntry '(1)' ┌────────────────────────────────────────┐ : + : └────────────────────────────────────────────────▶ │ _ │ : + : └────────────────────────────────────────┘ : + : │ : + : │ Entry foo : + : ▼ : + : ┌────────────────────────────────────────┐ : + : ┌············································ │ _ │ : + : : └────────────────────────────────────────┘ : + : : │ : + : : │ InlineEntry '(x - 1 < x)' : + : : ▼ : + : : ┌────────────────────────────────────────┐ : + : : │ _ │ : + : : └────────────────────────────────────────┘ : + : : │ : + : : │ Entry __VERIFIER_assert : + : : ▼ : + : : ┌────────────────────────────────────────┐ : + : : │ _ │ : + : : └────────────────────────────────────────┘ : + : : │ : + : : Inlined Proc '__VERIFIER_assert(x - 1 < x)' │ Test (! cond,false) : Inlined Proc 'foo(1)' + : : ▼ : + : : ┌────────────────────────────────────────┐ : + : : │ _ │ : + : : └────────────────────────────────────────┘ : + : : │ : + : : │ Ret (None, __VERIFIER_assert) : + : : ▼ : + : : ┌────────────────────────────────────────┐ : + : : │ _ │ : + : : └────────────────────────────────────────┘ : + : : │ : + : : │ InlineReturn : + : : ▼ : + : : ┌────────────────────────────────────────┐ : + : └···········································▶ │ _ │ : + : └────────────────────────────────────────┘ : + : │ : + : │ Ret (None, foo) : + : ▼ : + : ┌────────────────────────────────────────┐ : + : │ _ │ : + : └────────────────────────────────────────┘ : + : │ : + : │ InlineReturn : + : ▼ : + : Inlined Proc 'foo(1)' ┌────────────────────────────────────────┐ : + └·····················································▶ │ _ │ ◀┘ + └────────────────────────────────────────┘ + │ + │ Ret (Some 0, main) + ▼ + ┌────────────────────────────────────────┐ + │ _ │ + └────────────────────────────────────────┘ diff --git a/tests/sv-comp/cfg/multicall_nested_join_true-unreach-call.expected b/tests/sv-comp/cfg/multicall_nested_join_true-unreach-call.expected new file mode 100644 index 0000000000..7bfaa476b1 --- /dev/null +++ b/tests/sv-comp/cfg/multicall_nested_join_true-unreach-call.expected @@ -0,0 +1,194 @@ + + ┌················································································································································································································································································································································································································┐ + : : + : : + ┌······································┼··········································································································┐ : + : : : : + : : InlineReturn : : + : ┌────┼────────────────────────────────────────────────────────────────┐ : Inlined Proc 'foo(x, 4)' ┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ : + : │ : ▼ ▼ │ │ : + : │ : ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │ ┌────────────────────────────────────────┐ │ : + : │ : ┌───────────────────────────────────────▶ │ _ │ │ │ _ │ │ : + : │ : │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ │ └────────────────────────────────────────┘ │ : + : │ : │ │ ▲ │ │ │ : + : │ : │ │ Ret (None, bar) : Inlined Proc 'foo(x, 3)' │ │ Entry main │ : + : │ : │ ▼ : │ ▼ │ : + : │ : │ ┌────────────────────────────────────────┐ : │ ┌────────────────────────────────────────┐ │ : + : │ : │ InlineReturn │ _ │ ──────────────────────────────────────────────────┼────────────────────────────┘ │ _ │ ·┐ │ : + : │ : │ └────────────────────────────────────────┘ : └────────────────────────────────────────┘ : │ : + : │ : │ : │ : │ : + : │ : │ └····························┐ │ InlineEntry '()' : │ : + : │ : │ : ▼ : │ : + : │ : │ : ┌────────────────────────────────────────┐ : │ : + : │ └····················┼······································································································································┐ : │ _ │ : │ : + : │ │ : : └────────────────────────────────────────┘ : │ : + : │ │ : : │ : │ : + : │ │ : : │ Entry __VERIFIER_nondet_int : │ : + : │ │ : : ▼ : │ : + : │ │ : : ┌────────────────────────────────────────┐ : │ : + : │ │ : : │ _ │ : Inlined Proc 'tmp = __VERIFIER_nondet_int()' │ : + : │ │ : : └────────────────────────────────────────┘ : │ : + : │ │ : : │ : │ : + : │ │ : : │ Ret (Some val, __VERIFIER_nondet_int) : │ : + : │ │ : : ▼ : │ : + : │ │ : : ┌────────────────────────────────────────┐ : │ : + : │ │ : : │ _ │ : │ : + : │ │ : : └────────────────────────────────────────┘ : │ : + : │ │ : : │ : │ : + : │ │ : : │ InlineReturn 'tmp' : │ : + : │ │ : : ▼ : │ : + : │ │ ┌────────────────────────────────────────┐ InlineEntry '(2)' ┌───────────────────────────┐ : ┌────────────────────────────────────────┐ : │ : + : │ │ │ _ │ ◀────────────────────────────────────────────── │ _ │ ◀┼────────────────────── │ _ │ ◀┘ │ : + : │ │ └────────────────────────────────────────┘ └───────────────────────────┘ : └────────────────────────────────────────┘ │ : + : │ │ │ : │ │ : + : │ │ │ Entry bar ┌····························┘ │ Test (tmp,true) │ : + : │ │ ▼ : ▼ │ : + : │ │ ┌────────────────────────────────────────┐ : ┌────────────────────────────────────────┐ │ : + : │ │ │ _ │ ·┐ : ┌······················ │ _ │ │ : + : │ │ └────────────────────────────────────────┘ : : : └────────────────────────────────────────┘ │ : + : │ │ │ : : : │ │ : + : │ │ │ InlineEntry '()' : : : │ InlineEntry '(1)' │ : + : │ │ ▼ : : : ▼ │ : + : │ │ ┌────────────────────────────────────────┐ : : : ┌────────────────────────────────────────┐ │ : + : │ │ │ _ │ : : : │ _ │ │ : + : │ │ └────────────────────────────────────────┘ : : : └────────────────────────────────────────┘ │ : + : │ │ │ : : : │ │ : + : │ │ │ Entry __VERIFIER_nondet_int : : : │ Entry bar │ : + : │ │ ▼ : : : ▼ │ : + : │ │ ┌────────────────────────────────────────┐ : : : ┌────────────────────────────────────────┐ │ : + : │ │ │ _ │ : Inlined Proc 'tmp = __VERIFIER_nondet_int()' : : │ _ │ ·┐ │ : + : │ │ └────────────────────────────────────────┘ : : : └────────────────────────────────────────┘ : │ : + : │ │ │ : : : │ : │ : + : │ │ │ Ret (Some val, __VERIFIER_nondet_int) : : : │ InlineEntry '()' : │ : + : │ │ ▼ : : : ▼ : │ : + : │ │ ┌────────────────────────────────────────┐ : : : ┌────────────────────────────────────────┐ : │ : + : │ │ │ _ │ : : : │ _ │ : │ : + : │ │ └────────────────────────────────────────┘ : : : └────────────────────────────────────────┘ : │ : + : │ │ │ : : : │ : │ : + : │ │ │ InlineReturn 'tmp' : : : │ Entry __VERIFIER_nondet_int : │ : + : │ │ ▼ : : : ▼ : │ : + ┌────────────────────────────────┐ │ Test (tmp,false) │ ┌────────────────────────────────────────┐ : : : ┌────────────────────────────────────────┐ : │ : + │ _ │ ◀┼─────────────────────────┼──────────────────────────────────────── │ _ │ ◀┘ : : │ _ │ : Inlined Proc 'tmp = __VERIFIER_nondet_int()' │ : + └────────────────────────────────┘ │ │ └────────────────────────────────────────┘ : : └────────────────────────────────────────┘ : │ : + │ │ │ │ : : │ : │ : + │ InlineEntry '(x, 4)' │ │ │ Test (tmp,true) : : │ Ret (Some val, __VERIFIER_nondet_int) : │ : + ▼ │ │ ▼ : : ▼ : │ : + ┌────────────────────────────────┐ │ │ ┌────────────────────────────────────────┐ : : ┌────────────────────────────────────────┐ : │ : + │ _ │ │ │ │ _ │ ··················································┘ : │ _ │ : │ : + └────────────────────────────────┘ │ │ └────────────────────────────────────────┘ : └────────────────────────────────────────┘ : │ : + │ │ │ │ : │ : │ : + │ Entry foo │ │ │ InlineEntry '(x, 3)' : │ InlineReturn 'tmp' : │ : + ▼ │ │ ▼ : ▼ : │ : + ┌────────────────────────────────┐ │ │ ┌────────────────────────────────────────┐ : ┌────────────────────────────────────────┐ : │ : + ┌········································ │ _ │ │ │ │ _ │ ┌────────────────────────────┼────────────────────── │ _ │ ◀┘ │ : + : └────────────────────────────────┘ │ │ └────────────────────────────────────────┘ │ : └────────────────────────────────────────┘ │ : + : │ │ │ │ │ : │ │ : + : │ InlineEntry '(x < y)' │ │ │ Entry foo │ : │ Test (tmp,true) │ : + : ▼ │ │ ▼ │ : ▼ │ : + : ┌────────────────────────────────┐ │ │ ┌────────────────────────────────────────┐ │ : ┌────────────────────────────────────────┐ │ : + : │ _ │ │ │ │ _ │ ·┐ │ : │ _ │ ·┐ │ : + : └────────────────────────────────┘ │ │ └────────────────────────────────────────┘ : │ : └────────────────────────────────────────┘ : │ : + : │ │ │ │ : │ : │ : │ : + : │ Entry __VERIFIER_assert │ │ │ InlineEntry '(x < y)' : │ : │ InlineEntry '(x, 3)' : │ : + : ▼ │ │ ▼ : │ : ▼ : │ : + : ┌────────────────────────────────┐ │ │ ┌────────────────────────────────────────┐ : │ : ┌────────────────────────────────────────┐ : │ : + : │ _ │ │ │ │ _ │ : │ : │ _ │ : │ : + : └────────────────────────────────┘ │ │ └────────────────────────────────────────┘ : │ : └────────────────────────────────────────┘ : │ : + : │ │ │ │ : │ : │ : │ : + : Inlined Proc '__VERIFIER_assert(x < y)' │ Test (! cond,false) │ │ │ Entry __VERIFIER_assert : │ : │ Entry foo : │ : + : ▼ │ │ ▼ : │ : ▼ : │ : + : ┌────────────────────────────────┐ │ │ ┌────────────────────────────────────────┐ : │ : ┌────────────────────────────────────────┐ : │ : + : │ _ │ │ │ │ _ │ : │ : │ _ │ ·┼················································┐ │ : + : └────────────────────────────────┘ │ │ └────────────────────────────────────────┘ : │ : └────────────────────────────────────────┘ : : │ : + : │ │ │ │ : │ : │ : : │ : + : │ Ret (None, __VERIFIER_assert) │ │ │ Test (! cond,false) : Inlined Proc '__VERIFIER_assert(x < y)' │ : │ InlineEntry '(x < y)' : : │ : + : ▼ │ │ ▼ : │ : ▼ : : │ : + : ┌────────────────────────────────┐ │ │ ┌────────────────────────────────────────┐ : │ : ┌────────────────────────────────────────┐ : : │ : + : │ _ │ │ │ │ _ │ : │ : │ _ │ : : │ : + : └────────────────────────────────┘ │ │ └────────────────────────────────────────┘ : │ : └────────────────────────────────────────┘ : : │ : + : │ │ │ │ : │ : │ : : │ : + : │ InlineReturn │ │ │ Ret (None, __VERIFIER_assert) : │ : │ Entry __VERIFIER_assert : : │ : + : ▼ │ │ ▼ : │ : ▼ : : │ : + : ┌────────────────────────────────┐ │ │ ┌────────────────────────────────────────┐ : │ : ┌────────────────────────────────────────┐ : : │ : + └·······································▶ │ _ │ │ │ │ _ │ : │ : │ _ │ : : │ : + └────────────────────────────────┘ │ │ └────────────────────────────────────────┘ : │ : └────────────────────────────────────────┘ : : │ : + │ │ │ │ : │ : │ : : │ : + │ Ret (None, foo) │ │ │ InlineReturn : │ : │ Test (! cond,false) : : Inlined Proc '__VERIFIER_assert(x < y)' │ : + ▼ │ │ ▼ : │ : ▼ : : │ : + ┌────────────────────────────────┐ │ │ ┌────────────────────────────────────────┐ : │ : ┌────────────────────────────────────────┐ : : │ : + │ _ │ ─┘ │ │ _ │ ◀┘ │ : │ _ │ : : │ : + └────────────────────────────────┘ │ └────────────────────────────────────────┘ │ : └────────────────────────────────────────┘ : : │ : + │ │ │ : │ : : │ : + │ │ Ret (None, foo) │ : │ Ret (None, __VERIFIER_assert) : : │ : + │ ▼ │ : ▼ : : │ : + │ ┌────────────────────────────────────────┐ │ : ┌────────────────────────────────────────┐ : : │ : + └──────────────────────────────────────── │ _ │ │ : │ _ │ : : │ : + └────────────────────────────────────────┘ │ : └────────────────────────────────────────┘ : : │ : + │ : │ : : │ : + │ : │ InlineReturn : : │ : + │ : ▼ : : │ : + │ : ┌────────────────────────────────────────┐ : : │ : + │ : │ _ │ ◀┼················································┘ │ : + │ : └────────────────────────────────────────┘ : │ : + │ : │ : │ : + │ : │ Ret (None, foo) : │ : + │ : ▼ : │ : + │ : ┌────────────────────────────────────────┐ : │ : + │ : │ _ │ : │ : + │ : └────────────────────────────────────────┘ : │ : + Test (tmp,false) │ : │ : │ : + ┌──────────────────────────────────────────────────────────────────────────────────────────┘ : │ InlineReturn : Inlined Proc 'foo(x, 3)' │ : + ▼ : ▼ ▼ │ : + ┌────────────────────────────────────────┐ InlineReturn : ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │ : + ┌····························································· │ _ │ ┌─────────────────────────────────────────────────────────────────────────────┼─────────────────────▶ │ _ │ ◀┼·························┐ : + : └────────────────────────────────────────┘ │ : └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ │ : : + : │ │ : │ │ : : + : │ InlineEntry '(x, 4)' │ : │ Ret (None, bar) │ : : + : ▼ │ : ▼ │ : : + : ┌────────────────────────────────────────┐ │ : ┌────────────────────────────────────────┐ │ : : + : │ _ │ │ : Inlined Proc 'bar(1)' │ _ │ │ InlineReturn : : + : └────────────────────────────────────────┘ │ : └────────────────────────────────────────┘ │ : : + : │ │ : │ │ : Inlined Proc 'foo(x, 4)' : + : │ Entry foo │ : │ InlineReturn │ : : + : ▼ │ : ▼ │ : : + : ┌────────────────────────────────────────┐ │ : ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │ : : + : ┌········································ │ _ │ │ └·····················▶ │ _ │ ◀┘ : : + : : └────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ : : + : : │ │ │ ▲ Inlined Proc 'bar(2)' : : + : : │ InlineEntry '(x < y)' │ │ Ret (Some 0, main) └·····································································┼····························┘ + : : ▼ │ ▼ : + : : ┌────────────────────────────────────────┐ │ ┌────────────────────────────────────────┐ : + : : │ _ │ │ │ _ │ : + : : └────────────────────────────────────────┘ │ └────────────────────────────────────────┘ : + : : │ │ : + : : │ Entry __VERIFIER_assert │ : + : : ▼ │ : + : : ┌────────────────────────────────────────┐ │ : + : : │ _ │ │ : + : : └────────────────────────────────────────┘ │ : + : : │ │ : + : : Inlined Proc '__VERIFIER_assert(x < y)' │ Test (! cond,false) │ : + : : ▼ │ : + : : ┌────────────────────────────────────────┐ │ : + : : │ _ │ │ : + : : └────────────────────────────────────────┘ │ : + : : │ │ : + : : │ Ret (None, __VERIFIER_assert) │ : + : : ▼ │ : + : : ┌────────────────────────────────────────┐ │ : + : : │ _ │ │ : + : : └────────────────────────────────────────┘ │ : + : : │ │ : + : : │ InlineReturn │ : + : : ▼ │ : + : : ┌────────────────────────────────────────┐ │ : + : └·······································▶ │ _ │ │ : + : └────────────────────────────────────────┘ │ : + : │ │ : + : │ Ret (None, foo) │ : + : ▼ │ : + : ┌────────────────────────────────────────┐ │ : + : │ _ │ ─┘ : + : └────────────────────────────────────────┘ : + : : + └···················································································································································································································································································································································································┘ diff --git a/tests/sv-comp/cfg/multicall_nested_true-unreach-call.expected b/tests/sv-comp/cfg/multicall_nested_true-unreach-call.expected new file mode 100644 index 0000000000..0aa2c0679e --- /dev/null +++ b/tests/sv-comp/cfg/multicall_nested_true-unreach-call.expected @@ -0,0 +1,279 @@ + ┌────────────────────────────────┐ + │ _ │ + └────────────────────────────────┘ + │ + │ Entry main + ▼ + ┌────────────────────────────────┐ + │ _ │ ·┐ + └────────────────────────────────┘ : + │ : + │ InlineEntry '(1)' : + ▼ : + ┌────────────────────────────────┐ : + │ _ │ : + └────────────────────────────────┘ : + │ : + │ Entry bar : + ▼ : + ┌────────────────────────────────┐ : + ┌········································ │ _ │ : + : └────────────────────────────────┘ : + : │ : + : │ InlineEntry '(x, 3)' : + : ▼ : + : ┌────────────────────────────────┐ : + : │ _ │ : + : └────────────────────────────────┘ : + : │ : + : │ Entry foo : + : ▼ : + : ┌────────────────────────────────┐ : + : │ _ │ ·┼···········································┐ + : └────────────────────────────────┘ : : + : │ : : + : │ InlineEntry '(x < y)' : : + : ▼ : : + : ┌────────────────────────────────┐ : : + : │ _ │ : : + : └────────────────────────────────┘ : : + : │ : : + : │ Entry __VERIFIER_assert : : + : ▼ : : + : ┌────────────────────────────────┐ : : + : │ _ │ : : + : └────────────────────────────────┘ : : + : │ : : + : Inlined Proc 'foo(x, 3)' │ Test (! cond,false) : : Inlined Proc '__VERIFIER_assert(x < y)' + : ▼ : : + : ┌────────────────────────────────┐ : : + : │ _ │ : : + : └────────────────────────────────┘ : : + : │ : : + : │ Ret (None, __VERIFIER_assert) : : + : ▼ : : + : ┌────────────────────────────────┐ : : + : │ _ │ : : + : └────────────────────────────────┘ : : + : │ : : + : │ InlineReturn : : + : ▼ : : + : ┌────────────────────────────────┐ : : + : │ _ │ ◀┼···········································┘ + : └────────────────────────────────┘ : + : │ : + : │ Ret (None, foo) : + : ▼ : + : ┌────────────────────────────────┐ : + : │ _ │ : + : └────────────────────────────────┘ : + : │ : + : │ InlineReturn : + : ▼ : + : ┌────────────────────────────────┐ : + └·······································▶ │ _ │ ·┼···········································┐ + └────────────────────────────────┘ : : + │ : : + │ InlineEntry '(x, 4)' : : + ▼ : : + ┌────────────────────────────────┐ : : + │ _ │ : : + └────────────────────────────────┘ : : + │ : : + │ Entry foo : : + ▼ : : + ┌────────────────────────────────┐ : : + ┌········································ │ _ │ : : + : └────────────────────────────────┘ : : + : │ : : + : │ InlineEntry '(x < y)' : : + : ▼ : : + : ┌────────────────────────────────┐ : : + : │ _ │ : : + : └────────────────────────────────┘ : : + : │ : : + : │ Entry __VERIFIER_assert : : + : ▼ : : + : ┌────────────────────────────────┐ : : + : │ _ │ : : + : └────────────────────────────────┘ : : + : │ : : + : Inlined Proc '__VERIFIER_assert(x < y)' │ Test (! cond,false) : : Inlined Proc 'foo(x, 4)' + : ▼ : : + : ┌────────────────────────────────┐ : : + : │ _ │ : : + : └────────────────────────────────┘ : : + : │ : : + : │ Ret (None, __VERIFIER_assert) : : + : ▼ : : + : ┌────────────────────────────────┐ : : + : │ _ │ : : + : └────────────────────────────────┘ : : + : │ : : + : │ InlineReturn : : + : ▼ : : + : ┌────────────────────────────────┐ : : + └·······································▶ │ _ │ : : + └────────────────────────────────┘ : : + │ : : + │ Ret (None, foo) : : + ▼ : : + ┌────────────────────────────────┐ : : + │ _ │ : : + └────────────────────────────────┘ : : + │ : : + │ InlineReturn : : + ▼ : : + ┌────────────────────────────────┐ : : + │ _ │ ◀┼···········································┘ + └────────────────────────────────┘ : + │ : + │ Ret (None, bar) : + ▼ : + ┌────────────────────────────────┐ : + │ _ │ : Inlined Proc 'bar(1)' + └────────────────────────────────┘ : + │ : + │ InlineReturn : + ▼ : + ┌────────────────────────────────┐ : + ┌········································ │ _ │ ◀┘ + : └────────────────────────────────┘ + : │ + : │ InlineEntry '(2)' + : ▼ + : ┌────────────────────────────────┐ + : │ _ │ + : └────────────────────────────────┘ + : │ + : │ Entry bar + : ▼ + : ┌────────────────────────────────┐ + : │ _ │ ·┐ + : └────────────────────────────────┘ : + : │ : + : │ InlineEntry '(x, 3)' : + : ▼ : + : ┌────────────────────────────────┐ : + : │ _ │ : + : └────────────────────────────────┘ : + : │ : + : │ Entry foo : + : ▼ : + : ┌────────────────────────────────┐ : + : │ _ │ ·┼···········································┐ + : └────────────────────────────────┘ : : + : │ : : + : │ InlineEntry '(x < y)' : : + : ▼ : : + : ┌────────────────────────────────┐ : : + : │ _ │ : : + : └────────────────────────────────┘ : : + : │ : : + : │ Entry __VERIFIER_assert : : + : ▼ : : + : ┌────────────────────────────────┐ : : + : │ _ │ : : + : └────────────────────────────────┘ : : + : │ : : + : │ Test (! cond,false) : : Inlined Proc '__VERIFIER_assert(x < y)' + : ▼ : : + : ┌────────────────────────────────┐ : : + : │ _ │ : : + : └────────────────────────────────┘ : : + : │ : : + : │ Ret (None, __VERIFIER_assert) : : + : ▼ : : + : ┌────────────────────────────────┐ : : + : │ _ │ : : + : └────────────────────────────────┘ : : + : │ : : + : │ InlineReturn : : + : ▼ : : + : ┌────────────────────────────────┐ : : + : │ _ │ ◀┼···········································┘ + : └────────────────────────────────┘ : + : │ : + : │ Ret (None, foo) : + : ▼ : + : ┌────────────────────────────────┐ : + : │ _ │ : Inlined Proc 'foo(x, 3)' + : └────────────────────────────────┘ : + : │ : + : │ InlineReturn : + : ▼ : + : ┌────────────────────────────────┐ : + ┌····························┼········································ │ _ │ ◀┘ + : : └────────────────────────────────┘ + : : │ + : : │ InlineEntry '(x, 4)' + : : ▼ + : : ┌────────────────────────────────┐ + : : │ _ │ + : : └────────────────────────────────┘ + : : │ + : : │ Entry foo + : : ▼ + : : ┌────────────────────────────────┐ + : : │ _ │ ·┐ + : : └────────────────────────────────┘ : + : : │ : + : : │ InlineEntry '(x < y)' : + : : ▼ : + : : ┌────────────────────────────────┐ : + : : │ _ │ : + : : └────────────────────────────────┘ : + : : │ : + : : │ Entry __VERIFIER_assert : + : : ▼ : + : : ┌────────────────────────────────┐ : + : : │ _ │ : + : : └────────────────────────────────┘ : + : : │ : + : Inlined Proc 'foo(x, 4)' : │ Test (! cond,false) : Inlined Proc '__VERIFIER_assert(x < y)' + : : ▼ : + : : ┌────────────────────────────────┐ : + : : │ _ │ : + : : └────────────────────────────────┘ : + : : │ : + : : │ Ret (None, __VERIFIER_assert) : + : : ▼ : + : : ┌────────────────────────────────┐ : + : : │ _ │ : + : : └────────────────────────────────┘ : + : : │ : + : : │ InlineReturn : + : : ▼ : + : : ┌────────────────────────────────┐ : + : : │ _ │ ◀┘ + : : └────────────────────────────────┘ + : : │ + : : │ Ret (None, foo) + : : ▼ + : : ┌────────────────────────────────┐ + : : │ _ │ + : : └────────────────────────────────┘ + : : │ + : : │ InlineReturn + : : ▼ + : : ┌────────────────────────────────┐ + └····························┼·······································▶ │ _ │ + : └────────────────────────────────┘ + : │ + : │ Ret (None, bar) + : ▼ + : ┌────────────────────────────────┐ + : Inlined Proc 'bar(2)' │ _ │ + : └────────────────────────────────┘ + : │ + : │ InlineReturn + : ▼ + : ┌────────────────────────────────┐ + └·······································▶ │ _ │ + └────────────────────────────────┘ + │ + │ Ret (Some 0, main) + ▼ + ┌────────────────────────────────┐ + │ _ │ + └────────────────────────────────┘ diff --git a/tests/sv-comp/cfg/multicall_return_context_true-unreach-call.expected b/tests/sv-comp/cfg/multicall_return_context_true-unreach-call.expected new file mode 100644 index 0000000000..cd3c9cf767 --- /dev/null +++ b/tests/sv-comp/cfg/multicall_return_context_true-unreach-call.expected @@ -0,0 +1,171 @@ + ┌────────────────────────────────┐ + │ _ │ + └────────────────────────────────┘ + │ + │ Entry main + ▼ + ┌────────────────────────────────┐ + │ _ │ ·┐ + └────────────────────────────────┘ : + │ : + │ InlineEntry '(1)' : + ▼ : + ┌────────────────────────────────┐ : + │ _ │ : + └────────────────────────────────┘ : + │ : + │ Entry bar : + ▼ : + ┌────────────────────────────────┐ : + ┌························· │ _ │ : + : └────────────────────────────────┘ : + : │ : + : │ InlineEntry '(1, 3)' : + : ▼ : + : ┌────────────────────────────────┐ : + : │ _ │ : + : └────────────────────────────────┘ : + : │ : + : │ Entry foo : + : ▼ : + : ┌────────────────────────────────┐ : + : │ _ │ ·┼····························┐ + : └────────────────────────────────┘ : : + : │ : : + : │ InlineEntry '(x < y)' : : + : ▼ : : + : ┌────────────────────────────────┐ : : + : │ _ │ : : + : └────────────────────────────────┘ : : + : │ : : + : │ Entry __VERIFIER_assert : : + : ▼ : : + : ┌────────────────────────────────┐ : : + : │ _ │ : : + : └────────────────────────────────┘ : : + : │ : : + : Inlined Proc 'foo(1, 3)' │ Test (! cond,false) : : Inlined Proc '__VERIFIER_assert(x < y)' + : ▼ : : + : ┌────────────────────────────────┐ : : + : │ _ │ : : + : └────────────────────────────────┘ : : + : │ : : + : │ Ret (None, __VERIFIER_assert) : : + : ▼ : : + : ┌────────────────────────────────┐ : : + : │ _ │ : : + : └────────────────────────────────┘ : : + : │ : : + : │ InlineReturn : : + : ▼ : : + : ┌────────────────────────────────┐ : : + : │ _ │ ◀┼····························┘ + : └────────────────────────────────┘ : + : │ : + : │ Ret (None, foo) : + : ▼ : + : ┌────────────────────────────────┐ : + : │ _ │ : + : └────────────────────────────────┘ : + : │ : + : │ InlineReturn : + : ▼ : + : ┌────────────────────────────────┐ : + └························▶ │ _ │ : Inlined Proc 'bar(1)' + └────────────────────────────────┘ : + │ : + │ Ret (None, bar) : + ▼ : + ┌────────────────────────────────┐ : + │ _ │ : + └────────────────────────────────┘ : + │ : + │ InlineReturn : + ▼ : + ┌────────────────────────────────┐ : + ┌························· │ _ │ ◀┘ + : └────────────────────────────────┘ + : │ + : │ InlineEntry '(2)' + : ▼ + : ┌────────────────────────────────┐ + : │ _ │ + : └────────────────────────────────┘ + : │ + : │ Entry bar + : ▼ + : ┌────────────────────────────────┐ + : │ _ │ ·┐ + : └────────────────────────────────┘ : + : │ : + : │ InlineEntry '(1, 3)' : + : ▼ : + : ┌────────────────────────────────┐ : + : │ _ │ : + : └────────────────────────────────┘ : + : │ : + : │ Entry foo : + : ▼ : + : ┌────────────────────────────────┐ : + : │ _ │ ·┼····························┐ + : └────────────────────────────────┘ : : + : │ : : + : │ InlineEntry '(x < y)' : : + : ▼ : : + : ┌────────────────────────────────┐ : : + : │ _ │ : : + : └────────────────────────────────┘ : : + : │ : : + : │ Entry __VERIFIER_assert : : + : ▼ : : + : ┌────────────────────────────────┐ : : + : │ _ │ : : + : └────────────────────────────────┘ : : + : │ : : + : Inlined Proc 'bar(2)' │ Test (! cond,false) : : Inlined Proc '__VERIFIER_assert(x < y)' + : ▼ : : + : ┌────────────────────────────────┐ : : + : │ _ │ : : + : └────────────────────────────────┘ : : + : │ : : + : │ Ret (None, __VERIFIER_assert) : : + : ▼ : : + : ┌────────────────────────────────┐ : : + : │ _ │ : : + : └────────────────────────────────┘ : : + : │ : : + : │ InlineReturn : : + : ▼ : : + : ┌────────────────────────────────┐ : : + : │ _ │ ◀┼····························┘ + : └────────────────────────────────┘ : + : │ : + : │ Ret (None, foo) : + : ▼ : + : ┌────────────────────────────────┐ : + : │ _ │ : Inlined Proc 'foo(1, 3)' + : └────────────────────────────────┘ : + : │ : + : │ InlineReturn : + : ▼ : + : ┌────────────────────────────────┐ : + : │ _ │ ◀┘ + : └────────────────────────────────┘ + : │ + : │ Ret (None, bar) + : ▼ + : ┌────────────────────────────────┐ + : │ _ │ + : └────────────────────────────────┘ + : │ + : │ InlineReturn + : ▼ + : ┌────────────────────────────────┐ + └························▶ │ _ │ + └────────────────────────────────┘ + │ + │ Ret (Some 0, main) + ▼ + ┌────────────────────────────────┐ + │ _ │ + └────────────────────────────────┘ diff --git a/tests/sv-comp/cfg/multicall_true-unreach-call.expected b/tests/sv-comp/cfg/multicall_true-unreach-call.expected new file mode 100644 index 0000000000..5b2375dad7 --- /dev/null +++ b/tests/sv-comp/cfg/multicall_true-unreach-call.expected @@ -0,0 +1,45 @@ +Warning: Layouter could only place 13 nodes/15 edges out of 13/16 - giving up at /usr/bin/graph-easy line 144. + + ┌───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ │ + │ ┌────────────────────────────┐ │ + │ │ _ │ │ + │ └────────────────────────────┘ │ + │ │ │ + │ InlineReturn │ Entry main │ + ▼ ▼ │ + ┌─────────────────────┐ Inlined Proc 'foo(1)' ┌────────────────────────────┐ │ + │ _ │ ◀······························· │ _ │ │ + └─────────────────────┘ └────────────────────────────┘ │ + │ │ │ + └──────────────────────┐ │ InlineEntry '(1)' │ + │ ▼ │ + ┌─────────────────────┐ │ ┌────────────────────────────┐ │ + ┌────────────▶ │ _ │ └─────────────────────────────▶ │ _ │ │ + │ └─────────────────────┘ └────────────────────────────┘ │ + │ │ │ │ + │ InlineReturn │ Ret (Some 0, main) │ Entry foo │ + │ ▼ ▼ │ + │ ┌─────────────────────┐ ┌────────────────────────────┐ Inlined Proc '__VERIFIER_assert(x - 1 < x)' ┌───┐ Ret (None, foo) ┌───┐ + │ │ _ │ │ _ │ ·············································▶ │ _ │ ─────────────────▶ │ _ │ + │ └─────────────────────┘ └────────────────────────────┘ └───┘ └───┘ + │ │ ▲ │ + │ │ InlineEntry '(x - 1 < x)' │ │ + │ ▼ │ │ + │ ┌────────────────────────────┐ │ │ + │ │ _ │ │ │ + │ └────────────────────────────┘ │ │ + │ │ │ │ + │ │ Entry __VERIFIER_assert │ │ + │ ▼ │ │ + │ ┌─────────────────────┐ Test (! cond,false) ┌────────────────────────────┐ │ │ + │ │ _ │ ◀─────────────────────────────── │ _ │ │ │ + │ └─────────────────────┘ └────────────────────────────┘ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ Ret (None, __VERIFIER_assert) ┌────────────────────────────┐ InlineReturn │ │ + │ └────────────────────────────────────────────────────▶ │ _ │ ─────────────────────────────────────────────────┘ │ + │ └────────────────────────────┘ │ + │ │ + └────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ diff --git a/tests/sv-comp/cfg/region_global_init_true-unreach-call.expected b/tests/sv-comp/cfg/region_global_init_true-unreach-call.expected new file mode 100644 index 0000000000..c882471576 --- /dev/null +++ b/tests/sv-comp/cfg/region_global_init_true-unreach-call.expected @@ -0,0 +1,87 @@ + ┌────────────────────────────────────────┐ + │ _ │ + └────────────────────────────────────────┘ + │ + │ Entry main + ▼ + ┌────────────────────────────────────────┐ + │ _ │ ·┐ + └────────────────────────────────────────┘ : + │ : + │ InlineEntry '()' : + ▼ : + ┌────────────────────────────────────────┐ : + │ _ │ : + └────────────────────────────────────────┘ : + │ : + │ Entry __VERIFIER_nondet_int : + ▼ : + ┌────────────────────────────────────────┐ : + │ _ │ : Inlined Proc 'tmp = __VERIFIER_nondet_int()' + └────────────────────────────────────────┘ : + │ : + │ Ret (Some val, __VERIFIER_nondet_int) : + ▼ : + ┌────────────────────────────────────────┐ : + │ _ │ : + └────────────────────────────────────────┘ : + │ : + │ InlineReturn 'tmp' : + ▼ : +┌───┐ Test (tmp,true) ┌────────────────────────────────────────┐ : +│ _ │ ◀───────────────── │ _ │ ◀┘ +└───┘ └────────────────────────────────────────┘ + │ │ + │ │ Test (tmp,false) + │ ▼ + │ ┌────────────────────────────────────────┐ + │ │ _ │ + │ └────────────────────────────────────────┘ + │ │ + │ │ Assign 'x = 2' + │ ▼ + │ Assign 'x = 1' ┌────────────────────────────────────────┐ + └────────────────────▶ │ _ │ + └────────────────────────────────────────┘ + │ + │ Assign 'tmp___0 = 1 <= x && x <= 2' + ▼ + ┌────────────────────────────────────────┐ + │ _ │ ·┐ + └────────────────────────────────────────┘ : + │ : + │ InlineEntry '(tmp___0)' : + ▼ : + ┌────────────────────────────────────────┐ : + │ _ │ : + └────────────────────────────────────────┘ : + │ : + │ Entry __VERIFIER_assert : + ▼ : + ┌────────────────────────────────────────┐ : + │ _ │ : + └────────────────────────────────────────┘ : + │ : + │ Test (! cond,false) : Inlined Proc '__VERIFIER_assert(tmp___0)' + ▼ : + ┌────────────────────────────────────────┐ : + │ _ │ : + └────────────────────────────────────────┘ : + │ : + │ Ret (None, __VERIFIER_assert) : + ▼ : + ┌────────────────────────────────────────┐ : + │ _ │ : + └────────────────────────────────────────┘ : + │ : + │ InlineReturn : + ▼ : + ┌────────────────────────────────────────┐ : + │ _ │ ◀┘ + └────────────────────────────────────────┘ + │ + │ Ret (Some 0, main) + ▼ + ┌────────────────────────────────────────┐ + │ _ │ + └────────────────────────────────────────┘ diff --git a/tests/sv-comp/cfg/uncil/and3_true-unreach-call.expected b/tests/sv-comp/cfg/uncil/and3_true-unreach-call.expected new file mode 100644 index 0000000000..611e3e8f83 --- /dev/null +++ b/tests/sv-comp/cfg/uncil/and3_true-unreach-call.expected @@ -0,0 +1,81 @@ + ┌────────────────────────────────────────┐ + │ _ │ + └────────────────────────────────────────┘ + │ + │ Entry main + ▼ + ┌────────────────────────────────────────┐ + │ _ │ ·┐ + └────────────────────────────────────────┘ : + │ : + │ InlineEntry '()' : + ▼ : + ┌────────────────────────────────────────┐ : + │ _ │ : + └────────────────────────────────────────┘ : + │ : + │ Entry __VERIFIER_nondet_int : + ▼ : + ┌────────────────────────────────────────┐ : + │ _ │ : Inlined Proc 'tmp = __VERIFIER_nondet_int()' + └────────────────────────────────────────┘ : + │ : + │ Ret (Some val, __VERIFIER_nondet_int) : + ▼ : + ┌────────────────────────────────────────┐ : + │ _ │ : + └────────────────────────────────────────┘ : + │ : + │ InlineReturn 'tmp' : + ▼ : + ┌────────────────────────────────────────┐ : + │ _ │ ◀┘ + └────────────────────────────────────────┘ + │ + │ Assign 'x = tmp' + ▼ + ┌────────────────────────────────────────┐ + │ _ │ ─┐ + └────────────────────────────────────────┘ │ + │ │ + │ Test (x > 0 && (x < 3 && x < 2),true) │ + ▼ │ + ┌────────────────────────────────────────┐ │ + ┌········································· │ _ │ │ + : └────────────────────────────────────────┘ │ + : │ │ + : │ InlineEntry '(x == 1)' │ + : ▼ │ + : ┌────────────────────────────────────────┐ │ + : │ _ │ │ + : └────────────────────────────────────────┘ │ + : │ │ + : │ Entry __VERIFIER_assert │ + : ▼ │ + : ┌────────────────────────────────────────┐ │ + : │ _ │ │ Test (x > 0 && (x < 3 && x < 2),false) + : └────────────────────────────────────────┘ │ + : │ │ + : Inlined Proc '__VERIFIER_assert(x == 1)' │ Test (! cond,false) │ + : ▼ │ + : ┌────────────────────────────────────────┐ │ + : │ _ │ │ + : └────────────────────────────────────────┘ │ + : │ │ + : │ Ret (None, __VERIFIER_assert) │ + : ▼ │ + : ┌────────────────────────────────────────┐ │ + : │ _ │ │ + : └────────────────────────────────────────┘ │ + : │ │ + : │ InlineReturn │ + : ▼ │ + : ┌────────────────────────────────────────┐ │ + └········································▶ │ _ │ ◀┘ + └────────────────────────────────────────┘ + │ + │ Ret (Some 0, main) + ▼ + ┌────────────────────────────────────────┐ + │ _ │ + └────────────────────────────────────────┘ diff --git a/tests/sv-comp/cfg/uncil/and3dead_true-unreach-call.expected b/tests/sv-comp/cfg/uncil/and3dead_true-unreach-call.expected new file mode 100644 index 0000000000..040699c7f5 --- /dev/null +++ b/tests/sv-comp/cfg/uncil/and3dead_true-unreach-call.expected @@ -0,0 +1,81 @@ + ┌─────────────────────────────────────────┐ + │ _ │ + └─────────────────────────────────────────┘ + │ + │ Entry main + ▼ + ┌─────────────────────────────────────────┐ + │ _ │ ·┐ + └─────────────────────────────────────────┘ : + │ : + │ InlineEntry '()' : + ▼ : + ┌─────────────────────────────────────────┐ : + │ _ │ : + └─────────────────────────────────────────┘ : + │ : + │ Entry __VERIFIER_nondet_int : + ▼ : + ┌─────────────────────────────────────────┐ : + │ _ │ : Inlined Proc 'tmp = __VERIFIER_nondet_int()' + └─────────────────────────────────────────┘ : + │ : + │ Ret (Some val, __VERIFIER_nondet_int) : + ▼ : + ┌─────────────────────────────────────────┐ : + │ _ │ : + └─────────────────────────────────────────┘ : + │ : + │ InlineReturn 'tmp' : + ▼ : + ┌─────────────────────────────────────────┐ : + │ _ │ ◀┘ + └─────────────────────────────────────────┘ + │ + │ Assign 'x = tmp' + ▼ + ┌─────────────────────────────────────────┐ + │ _ │ ─┐ + └─────────────────────────────────────────┘ │ + │ │ + │ Test (x > 0 && (x < 3 && x == 1),true) │ + ▼ │ + ┌─────────────────────────────────────────┐ │ + ┌········································· │ _ │ │ + : └─────────────────────────────────────────┘ │ + : │ │ + : │ InlineEntry '(x == 1)' │ + : ▼ │ + : ┌─────────────────────────────────────────┐ │ + : │ _ │ │ + : └─────────────────────────────────────────┘ │ + : │ │ + : │ Entry __VERIFIER_assert │ + : ▼ │ + : ┌─────────────────────────────────────────┐ │ + : │ _ │ │ Test (x > 0 && (x < 3 && x == 1),false) + : └─────────────────────────────────────────┘ │ + : │ │ + : Inlined Proc '__VERIFIER_assert(x == 1)' │ Test (! cond,false) │ + : ▼ │ + : ┌─────────────────────────────────────────┐ │ + : │ _ │ │ + : └─────────────────────────────────────────┘ │ + : │ │ + : │ Ret (None, __VERIFIER_assert) │ + : ▼ │ + : ┌─────────────────────────────────────────┐ │ + : │ _ │ │ + : └─────────────────────────────────────────┘ │ + : │ │ + : │ InlineReturn │ + : ▼ │ + : ┌─────────────────────────────────────────┐ │ + └········································▶ │ _ │ ◀┘ + └─────────────────────────────────────────┘ + │ + │ Ret (Some 0, main) + ▼ + ┌─────────────────────────────────────────┐ + │ _ │ + └─────────────────────────────────────────┘ diff --git a/tests/sv-comp/cfg/uncil/and_copy_true-unreach-call.expected b/tests/sv-comp/cfg/uncil/and_copy_true-unreach-call.expected new file mode 100644 index 0000000000..277fb8785c --- /dev/null +++ b/tests/sv-comp/cfg/uncil/and_copy_true-unreach-call.expected @@ -0,0 +1,84 @@ + + ┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ │ + │ ┌────────────────────────────────────────┐ │ + │ │ _ │ │ + │ └────────────────────────────────────────┘ │ + │ │ │ + │ │ Entry main │ + │ ▼ │ + │ ┌────────────────────────────────────────┐ │ + │ │ _ │ ·┐ │ + │ └────────────────────────────────────────┘ : │ + │ │ : │ + │ │ InlineEntry '()' : │ + │ ▼ : │ + │ ┌────────────────────────────────────────┐ : │ + │ │ _ │ : │ + │ └────────────────────────────────────────┘ : │ + │ │ : │ + │ │ Entry __VERIFIER_nondet_int : │ + │ ▼ : │ + │ ┌────────────────────────────────────────┐ : │ + │ │ _ │ : Inlined Proc 'tmp = __VERIFIER_nondet_int()' │ + │ └────────────────────────────────────────┘ : │ + │ │ : │ + │ │ Ret (Some val, __VERIFIER_nondet_int) : │ + │ ▼ : │ + │ ┌────────────────────────────────────────┐ : │ + │ │ _ │ : │ + │ └────────────────────────────────────────┘ : │ + │ │ : │ + │ │ InlineReturn 'tmp' : │ + │ ▼ : │ + │ ┌────────────────────────────────────────┐ : │ + │ │ _ │ ◀┘ │ + │ └────────────────────────────────────────┘ │ + │ │ │ + │ Test (x > 0 && x < 2,false) │ Assign 'x = tmp' │ + ▼ ▼ │ +┌────────────────────────────────┐ ┌────────────────────────────────────────┐ │ +│ _ │ ·┐ │ _ │ ──────────────────────────────────────────────────┘ +└────────────────────────────────┘ : └────────────────────────────────────────┘ + │ : │ + │ InlineEntry '(1)' : │ Test (x > 0 && x < 2,true) + ▼ : ▼ +┌────────────────────────────────┐ : ┌────────────────────────────────────────┐ +│ _ │ : │ _ │ ·┐ +└────────────────────────────────┘ : └────────────────────────────────────────┘ : + │ : │ : + │ Entry __VERIFIER_assert : │ InlineEntry '(x == 1)' : + ▼ : ▼ : +┌────────────────────────────────┐ : ┌────────────────────────────────────────┐ : +│ _ │ : │ _ │ : +└────────────────────────────────┘ : └────────────────────────────────────────┘ : + │ : │ : + │ Test (! cond,false) : Inlined Proc '__VERIFIER_assert(1)' │ Entry __VERIFIER_assert : + ▼ : ▼ : +┌────────────────────────────────┐ : ┌────────────────────────────────────────┐ : +│ _ │ : │ _ │ : +└────────────────────────────────┘ : └────────────────────────────────────────┘ : + │ : │ : + │ Ret (None, __VERIFIER_assert) : │ Test (! cond,false) : Inlined Proc '__VERIFIER_assert(x == 1)' + ▼ : ▼ : +┌────────────────────────────────┐ : ┌────────────────────────────────────────┐ : +│ _ │ : │ _ │ : +└────────────────────────────────┘ : └────────────────────────────────────────┘ : + │ : │ : + │ InlineReturn : │ Ret (None, __VERIFIER_assert) : + ▼ : ▼ : +┌────────────────────────────────┐ : ┌────────────────────────────────────────┐ : +│ _ │ ◀┘ │ _ │ : +└────────────────────────────────┘ └────────────────────────────────────────┘ : + │ │ : + │ │ InlineReturn : + │ ▼ : + │ ┌────────────────────────────────────────┐ : + │ │ _ │ ◀┘ + │ └────────────────────────────────────────┘ + │ │ + │ │ Ret (Some 0, main) + │ ▼ + │ Ret (Some 1, main) ┌────────────────────────────────────────┐ + └─────────────────────────────────────────────────────────────────────▶ │ _ │ + └────────────────────────────────────────┘ diff --git a/tests/sv-comp/cfg/uncil/and_join_invariant_true-unreach-call.expected b/tests/sv-comp/cfg/uncil/and_join_invariant_true-unreach-call.expected new file mode 100644 index 0000000000..b9b2de3852 --- /dev/null +++ b/tests/sv-comp/cfg/uncil/and_join_invariant_true-unreach-call.expected @@ -0,0 +1,83 @@ + ┌────────────────────────────────────────┐ + │ _ │ + └────────────────────────────────────────┘ + │ + │ Entry main + ▼ + ┌────────────────────────────────────────┐ + │ _ │ ·┐ + └────────────────────────────────────────┘ : + │ : + │ InlineEntry '()' : + ▼ : + ┌────────────────────────────────────────┐ : + │ _ │ : + └────────────────────────────────────────┘ : + │ : + │ Entry __VERIFIER_nondet_int : + ▼ : + ┌────────────────────────────────────────┐ : + │ _ │ : Inlined Proc 'tmp = __VERIFIER_nondet_int()' + └────────────────────────────────────────┘ : + │ : + │ Ret (Some val, __VERIFIER_nondet_int) : + ▼ : + ┌────────────────────────────────────────┐ : + │ _ │ : + └────────────────────────────────────────┘ : + │ : + │ InlineReturn 'tmp' : + ▼ : + ┌────────────────────────────────────────┐ : + │ _ │ ◀┘ + └────────────────────────────────────────┘ + │ + │ Assign 'x = tmp' + ▼ + ┌────────────────────────────────┐ Test (x > 0 && x < 2,false) ┌────────────────────────────────────────┐ + ┌· │ _ │ ◀───────────────────────────── │ _ │ + : └────────────────────────────────┘ └────────────────────────────────────────┘ + : │ │ + : │ InlineEntry '(1)' │ Test (x > 0 && x < 2,true) + : ▼ ▼ + : ┌────────────────────────────────┐ ┌────────────────────────────────────────┐ + : │ _ │ │ _ │ ·┐ + : └────────────────────────────────┘ └────────────────────────────────────────┘ : + : │ │ : + : │ Entry __VERIFIER_assert │ InlineEntry '(x == 1)' : + : ▼ ▼ : + : ┌────────────────────────────────┐ ┌────────────────────────────────────────┐ : + : │ _ │ │ _ │ : + : └────────────────────────────────┘ └────────────────────────────────────────┘ : + : │ │ : + : │ Test (! cond,false) │ Entry __VERIFIER_assert : + : ▼ ▼ : + : ┌────────────────────────────────┐ ┌────────────────────────────────────────┐ : + : │ _ │ │ _ │ : + : └────────────────────────────────┘ └────────────────────────────────────────┘ : + : │ │ : + : │ Ret (None, __VERIFIER_assert) │ Test (! cond,false) : + : ▼ ▼ : + : ┌────────────────────────────────┐ ┌────────────────────────────────────────┐ : + : │ _ │ │ _ │ : + : └────────────────────────────────┘ └────────────────────────────────────────┘ : + : │ │ : + : │ │ Ret (None, __VERIFIER_assert) : + : │ ▼ : + : │ ┌────────────────────────────────────────┐ : + : │ │ _ │ : + : │ └────────────────────────────────────────┘ : + : │ │ : + : │ │ InlineReturn : Inlined Proc '__VERIFIER_assert(x == 1)' + : │ ▼ ▼ + : │ InlineReturn ┌─────────────────────────────────────────────────────────────────────────────────────────┐ + : └─────────────────────────────────────────────────────────────▶ │ _ │ + : └─────────────────────────────────────────────────────────────────────────────────────────┘ + : │ ▲ + : │ Ret (Some 0, main) : Inlined Proc '__VERIFIER_assert(1)' + : ▼ : + : ┌────────────────────────────────────────┐ : + : │ _ │ : + : └────────────────────────────────────────┘ : + : : + └················································································································┘ diff --git a/tests/sv-comp/cfg/uncil/and_true-unreach-call.expected b/tests/sv-comp/cfg/uncil/and_true-unreach-call.expected new file mode 100644 index 0000000000..59fc453f85 --- /dev/null +++ b/tests/sv-comp/cfg/uncil/and_true-unreach-call.expected @@ -0,0 +1,81 @@ + ┌────────────────────────────────────────┐ + │ _ │ + └────────────────────────────────────────┘ + │ + │ Entry main + ▼ + ┌────────────────────────────────────────┐ + │ _ │ ·┐ + └────────────────────────────────────────┘ : + │ : + │ InlineEntry '()' : + ▼ : + ┌────────────────────────────────────────┐ : + │ _ │ : + └────────────────────────────────────────┘ : + │ : + │ Entry __VERIFIER_nondet_int : + ▼ : + ┌────────────────────────────────────────┐ : + │ _ │ : Inlined Proc 'tmp = __VERIFIER_nondet_int()' + └────────────────────────────────────────┘ : + │ : + │ Ret (Some val, __VERIFIER_nondet_int) : + ▼ : + ┌────────────────────────────────────────┐ : + │ _ │ : + └────────────────────────────────────────┘ : + │ : + │ InlineReturn 'tmp' : + ▼ : + ┌────────────────────────────────────────┐ : + │ _ │ ◀┘ + └────────────────────────────────────────┘ + │ + │ Assign 'x = tmp' + ▼ + ┌────────────────────────────────────────┐ + │ _ │ ─┐ + └────────────────────────────────────────┘ │ + │ │ + │ Test (x > 0 && x < 2,true) │ + ▼ │ + ┌────────────────────────────────────────┐ │ + ┌········································· │ _ │ │ + : └────────────────────────────────────────┘ │ + : │ │ + : │ InlineEntry '(x == 1)' │ + : ▼ │ + : ┌────────────────────────────────────────┐ │ + : │ _ │ │ + : └────────────────────────────────────────┘ │ + : │ │ + : │ Entry __VERIFIER_assert │ + : ▼ │ + : ┌────────────────────────────────────────┐ │ + : │ _ │ │ Test (x > 0 && x < 2,false) + : └────────────────────────────────────────┘ │ + : │ │ + : Inlined Proc '__VERIFIER_assert(x == 1)' │ Test (! cond,false) │ + : ▼ │ + : ┌────────────────────────────────────────┐ │ + : │ _ │ │ + : └────────────────────────────────────────┘ │ + : │ │ + : │ Ret (None, __VERIFIER_assert) │ + : ▼ │ + : ┌────────────────────────────────────────┐ │ + : │ _ │ │ + : └────────────────────────────────────────┘ │ + : │ │ + : │ InlineReturn │ + : ▼ │ + : ┌────────────────────────────────────────┐ │ + └········································▶ │ _ │ ◀┘ + └────────────────────────────────────────┘ + │ + │ Ret (Some 0, main) + ▼ + ┌────────────────────────────────────────┐ + │ _ │ + └────────────────────────────────────────┘ diff --git a/tests/sv-comp/cfg/uncil/and_var_false-unreach-call.expected b/tests/sv-comp/cfg/uncil/and_var_false-unreach-call.expected new file mode 100644 index 0000000000..ae3e5e575b --- /dev/null +++ b/tests/sv-comp/cfg/uncil/and_var_false-unreach-call.expected @@ -0,0 +1,69 @@ + ┌────────────────────────────────────────┐ + │ _ │ + └────────────────────────────────────────┘ + │ + │ Entry main + ▼ + ┌────────────────────────────────────────┐ + │ _ │ ·┐ + └────────────────────────────────────────┘ : + │ : + │ InlineEntry '()' : + ▼ : + ┌────────────────────────────────────────┐ : + │ _ │ : + └────────────────────────────────────────┘ : + │ : + │ Entry __VERIFIER_nondet_int : + ▼ : + ┌────────────────────────────────────────┐ : + │ _ │ : Inlined Proc 'tmp = __VERIFIER_nondet_int()' + └────────────────────────────────────────┘ : + │ : + │ Ret (Some val, __VERIFIER_nondet_int) : + ▼ : + ┌────────────────────────────────────────┐ : + │ _ │ : + └────────────────────────────────────────┘ : + │ : + │ InlineReturn 'tmp' : + ▼ : + ┌────────────────────────────────────────┐ : + │ _ │ ◀┘ + └────────────────────────────────────────┘ + │ + │ Assign 'x = tmp' + ▼ + ┌────────────────────────────────────────┐ + │ _ │ + └────────────────────────────────────────┘ + │ + │ Assign 'tmp___0 = x > 0 && x < 2' + ▼ + ┌────────────────────────────────────────┐ + │ _ │ + └────────────────────────────────────────┘ + │ + │ Assign 'y = tmp___0' + ▼ +┌─────────────────────┐ Test (y,false) ┌────────────────────────────────────────┐ +│ _ │ ◀──────────────── │ _ │ +└─────────────────────┘ └────────────────────────────────────────┘ + │ │ + │ Ret (Some 0, main) │ Test (y,true) + ▼ ▼ +┌─────────────────────┐ ┌────────────────────────────────────────┐ +│ _ │ │ _ │ +└─────────────────────┘ └────────────────────────────────────────┘ + │ + │ InlineEntry '()' + ▼ + ┌────────────────────────────────────────┐ + │ _ │ + └────────────────────────────────────────┘ + │ + │ Entry __VERIFIER_error + ▼ + ┌────────────────────────────────────────┐ + │ _ │ + └────────────────────────────────────────┘ diff --git a/tests/sv-comp/cfg/uncil/and_var_true-unreach-call.expected b/tests/sv-comp/cfg/uncil/and_var_true-unreach-call.expected new file mode 100644 index 0000000000..561561fa15 --- /dev/null +++ b/tests/sv-comp/cfg/uncil/and_var_true-unreach-call.expected @@ -0,0 +1,63 @@ +┌────────────────────────────────────────┐ +│ _ │ +└────────────────────────────────────────┘ + │ + │ Entry main + ▼ +┌────────────────────────────────────────┐ +│ _ │ ·┐ +└────────────────────────────────────────┘ : + │ : + │ InlineEntry '()' : + ▼ : +┌────────────────────────────────────────┐ : +│ _ │ : +└────────────────────────────────────────┘ : + │ : + │ Entry __VERIFIER_nondet_int : + ▼ : +┌────────────────────────────────────────┐ : +│ _ │ : Inlined Proc 'tmp = __VERIFIER_nondet_int()' +└────────────────────────────────────────┘ : + │ : + │ Ret (Some val, __VERIFIER_nondet_int) : + ▼ : +┌────────────────────────────────────────┐ : +│ _ │ : +└────────────────────────────────────────┘ : + │ : + │ InlineReturn 'tmp' : + ▼ : +┌────────────────────────────────────────┐ : +│ _ │ ◀┘ +└────────────────────────────────────────┘ + │ + │ Assign 'x = tmp' + ▼ +┌────────────────────────────────────────┐ +│ _ │ +└────────────────────────────────────────┘ + │ + │ Assign 'tmp___0 = x > 0 && x < 0' + ▼ +┌────────────────────────────────────────┐ +│ _ │ +└────────────────────────────────────────┘ + │ + │ Assign 'y = tmp___0' + ▼ +┌────────────────────────────────────────┐ +│ _ │ +└────────────────────────────────────────┘ + │ + │ Test (y,false) + ▼ +┌────────────────────────────────────────┐ +│ _ │ +└────────────────────────────────────────┘ + │ + │ Ret (Some 0, main) + ▼ +┌────────────────────────────────────────┐ +│ _ │ +└────────────────────────────────────────┘ diff --git a/tests/sv-comp/cfg/uncil/or3_true-unreach-call.expected b/tests/sv-comp/cfg/uncil/or3_true-unreach-call.expected new file mode 100644 index 0000000000..08a49830e1 --- /dev/null +++ b/tests/sv-comp/cfg/uncil/or3_true-unreach-call.expected @@ -0,0 +1,81 @@ + ┌─────────────────────────────────────────┐ + │ _ │ + └─────────────────────────────────────────┘ + │ + │ Entry main + ▼ + ┌─────────────────────────────────────────┐ + │ _ │ ·┐ + └─────────────────────────────────────────┘ : + │ : + │ InlineEntry '()' : + ▼ : + ┌─────────────────────────────────────────┐ : + │ _ │ : + └─────────────────────────────────────────┘ : + │ : + │ Entry __VERIFIER_nondet_int : + ▼ : + ┌─────────────────────────────────────────┐ : + │ _ │ : Inlined Proc 'tmp = __VERIFIER_nondet_int()' + └─────────────────────────────────────────┘ : + │ : + │ Ret (Some val, __VERIFIER_nondet_int) : + ▼ : + ┌─────────────────────────────────────────┐ : + │ _ │ : + └─────────────────────────────────────────┘ : + │ : + │ InlineReturn 'tmp' : + ▼ : + ┌─────────────────────────────────────────┐ : + │ _ │ ◀┘ + └─────────────────────────────────────────┘ + │ + │ Assign 'x = tmp' + ▼ + ┌─────────────────────────────────────────┐ + │ _ │ ─┐ + └─────────────────────────────────────────┘ │ + │ │ + │ Test (x < 1 || (x > 2 || x > 1),false) │ + ▼ │ + ┌─────────────────────────────────────────┐ │ + ┌········································· │ _ │ │ + : └─────────────────────────────────────────┘ │ + : │ │ + : │ InlineEntry '(x == 1)' │ + : ▼ │ + : ┌─────────────────────────────────────────┐ │ + : │ _ │ │ + : └─────────────────────────────────────────┘ │ + : │ │ + : │ Entry __VERIFIER_assert │ + : ▼ │ + : ┌─────────────────────────────────────────┐ │ + : │ _ │ │ Test (x < 1 || (x > 2 || x > 1),true) + : └─────────────────────────────────────────┘ │ + : │ │ + : Inlined Proc '__VERIFIER_assert(x == 1)' │ Test (! cond,false) │ + : ▼ │ + : ┌─────────────────────────────────────────┐ │ + : │ _ │ │ + : └─────────────────────────────────────────┘ │ + : │ │ + : │ Ret (None, __VERIFIER_assert) │ + : ▼ │ + : ┌─────────────────────────────────────────┐ │ + : │ _ │ │ + : └─────────────────────────────────────────┘ │ + : │ │ + : │ InlineReturn │ + : ▼ │ + : ┌─────────────────────────────────────────┐ │ + └········································▶ │ _ │ ◀┘ + └─────────────────────────────────────────┘ + │ + │ Ret (Some 0, main) + ▼ + ┌─────────────────────────────────────────┐ + │ _ │ + └─────────────────────────────────────────┘ diff --git a/tests/sv-comp/cfg/uncil/or3dead_true-unreach-call.expected b/tests/sv-comp/cfg/uncil/or3dead_true-unreach-call.expected new file mode 100644 index 0000000000..4bda4ebe45 --- /dev/null +++ b/tests/sv-comp/cfg/uncil/or3dead_true-unreach-call.expected @@ -0,0 +1,81 @@ + ┌──────────────────────────────────────────┐ + │ _ │ + └──────────────────────────────────────────┘ + │ + │ Entry main + ▼ + ┌──────────────────────────────────────────┐ + │ _ │ ·┐ + └──────────────────────────────────────────┘ : + │ : + │ InlineEntry '()' : + ▼ : + ┌──────────────────────────────────────────┐ : + │ _ │ : + └──────────────────────────────────────────┘ : + │ : + │ Entry __VERIFIER_nondet_int : + ▼ : + ┌──────────────────────────────────────────┐ : + │ _ │ : Inlined Proc 'tmp = __VERIFIER_nondet_int()' + └──────────────────────────────────────────┘ : + │ : + │ Ret (Some val, __VERIFIER_nondet_int) : + ▼ : + ┌──────────────────────────────────────────┐ : + │ _ │ : + └──────────────────────────────────────────┘ : + │ : + │ InlineReturn 'tmp' : + ▼ : + ┌──────────────────────────────────────────┐ : + │ _ │ ◀┘ + └──────────────────────────────────────────┘ + │ + │ Assign 'x = tmp' + ▼ + ┌──────────────────────────────────────────┐ + │ _ │ ─┐ + └──────────────────────────────────────────┘ │ + │ │ + │ Test (x < 1 || (x > 2 || x != 1),false) │ + ▼ │ + ┌──────────────────────────────────────────┐ │ + ┌········································· │ _ │ │ + : └──────────────────────────────────────────┘ │ + : │ │ + : │ InlineEntry '(x == 1)' │ + : ▼ │ + : ┌──────────────────────────────────────────┐ │ + : │ _ │ │ + : └──────────────────────────────────────────┘ │ + : │ │ + : │ Entry __VERIFIER_assert │ + : ▼ │ + : ┌──────────────────────────────────────────┐ │ + : │ _ │ │ Test (x < 1 || (x > 2 || x != 1),true) + : └──────────────────────────────────────────┘ │ + : │ │ + : Inlined Proc '__VERIFIER_assert(x == 1)' │ Test (! cond,false) │ + : ▼ │ + : ┌──────────────────────────────────────────┐ │ + : │ _ │ │ + : └──────────────────────────────────────────┘ │ + : │ │ + : │ Ret (None, __VERIFIER_assert) │ + : ▼ │ + : ┌──────────────────────────────────────────┐ │ + : │ _ │ │ + : └──────────────────────────────────────────┘ │ + : │ │ + : │ InlineReturn │ + : ▼ │ + : ┌──────────────────────────────────────────┐ │ + └········································▶ │ _ │ ◀┘ + └──────────────────────────────────────────┘ + │ + │ Ret (Some 0, main) + ▼ + ┌──────────────────────────────────────────┐ + │ _ │ + └──────────────────────────────────────────┘ diff --git a/tests/sv-comp/cfg/uncil/or_true-unreach-call.expected b/tests/sv-comp/cfg/uncil/or_true-unreach-call.expected new file mode 100644 index 0000000000..b21a275d14 --- /dev/null +++ b/tests/sv-comp/cfg/uncil/or_true-unreach-call.expected @@ -0,0 +1,81 @@ + ┌────────────────────────────────────────┐ + │ _ │ + └────────────────────────────────────────┘ + │ + │ Entry main + ▼ + ┌────────────────────────────────────────┐ + │ _ │ ·┐ + └────────────────────────────────────────┘ : + │ : + │ InlineEntry '()' : + ▼ : + ┌────────────────────────────────────────┐ : + │ _ │ : + └────────────────────────────────────────┘ : + │ : + │ Entry __VERIFIER_nondet_int : + ▼ : + ┌────────────────────────────────────────┐ : + │ _ │ : Inlined Proc 'tmp = __VERIFIER_nondet_int()' + └────────────────────────────────────────┘ : + │ : + │ Ret (Some val, __VERIFIER_nondet_int) : + ▼ : + ┌────────────────────────────────────────┐ : + │ _ │ : + └────────────────────────────────────────┘ : + │ : + │ InlineReturn 'tmp' : + ▼ : + ┌────────────────────────────────────────┐ : + │ _ │ ◀┘ + └────────────────────────────────────────┘ + │ + │ Assign 'x = tmp' + ▼ + ┌────────────────────────────────────────┐ + │ _ │ ─┐ + └────────────────────────────────────────┘ │ + │ │ + │ Test (x < 1 || x > 1,false) │ + ▼ │ + ┌────────────────────────────────────────┐ │ + ┌········································· │ _ │ │ + : └────────────────────────────────────────┘ │ + : │ │ + : │ InlineEntry '(x == 1)' │ + : ▼ │ + : ┌────────────────────────────────────────┐ │ + : │ _ │ │ + : └────────────────────────────────────────┘ │ + : │ │ + : │ Entry __VERIFIER_assert │ + : ▼ │ + : ┌────────────────────────────────────────┐ │ + : │ _ │ │ Test (x < 1 || x > 1,true) + : └────────────────────────────────────────┘ │ + : │ │ + : Inlined Proc '__VERIFIER_assert(x == 1)' │ Test (! cond,false) │ + : ▼ │ + : ┌────────────────────────────────────────┐ │ + : │ _ │ │ + : └────────────────────────────────────────┘ │ + : │ │ + : │ Ret (None, __VERIFIER_assert) │ + : ▼ │ + : ┌────────────────────────────────────────┐ │ + : │ _ │ │ + : └────────────────────────────────────────┘ │ + : │ │ + : │ InlineReturn │ + : ▼ │ + : ┌────────────────────────────────────────┐ │ + └········································▶ │ _ │ ◀┘ + └────────────────────────────────────────┘ + │ + │ Ret (Some 0, main) + ▼ + ┌────────────────────────────────────────┐ + │ _ │ + └────────────────────────────────────────┘ diff --git a/tests/sv-comp/dune b/tests/sv-comp/dune new file mode 100644 index 0000000000..bf44bb8cb1 --- /dev/null +++ b/tests/sv-comp/dune @@ -0,0 +1,19 @@ +(include dune.inc) + +(rule + (alias runtest) + (mode promote) + (deps + (:files ; patterns from ReachSafety-Basic.set + (glob_files basic/*.c) + (glob_files eq/*.c) + (glob_files cfg/multicall_*.c) + (glob_files cfg/join_true-unreach-call.c) + (glob_files cfg/builtin_expect_true-unreach-call.c) + (glob_files cfg/region_global_init_true-unreach-call.c) + (glob_files cfg/local_shadow_fun_true-unreach-call.c) + (glob_files cfg/free_spawn_*.c) + (glob_files cfg/uncil/*.c))) + (action + (with-stdout-to dune.inc + (run gen/gen.exe %{files})))) diff --git a/tests/sv-comp/dune.inc b/tests/sv-comp/dune.inc new file mode 100644 index 0000000000..009a09dcd3 --- /dev/null +++ b/tests/sv-comp/dune.inc @@ -0,0 +1,903 @@ +; Automatically generated, do not edit! + +(subdir basic + (rule + (deps + (sandbox always) ; must sandbox to prevent arg.dot-s from parallel runs from interfering + (package goblint) ; depend on entire goblint package for svcomp21 conf + (:c for_fun_true-unreach-call.c) + (:prop %{project_root}/tests/sv-comp/unreach-call-__VERIFIER_error.prp)) + (target for_fun_true-unreach-call.output) + (enabled_if %{bin-available:graph-easy}) + (action + (progn + (ignore-outputs + (run goblint --conf svcomp.json --disable ana.autotune.enabled --set ana.specification %{prop} %{c} --enable witness.graphml.uncil --enable exp.arg.enabled --set exp.arg.dot.path arg.dot --set exp.arg.dot.node-label empty)) + (with-outputs-to %{target} + (run graph-easy --as=boxart arg.dot))))) + + (rule + (alias runtest) + (enabled_if %{bin-available:graph-easy}) + (action + (diff for_fun_true-unreach-call.expected for_fun_true-unreach-call.output)))) + +(subdir basic + (rule + (deps + (sandbox always) ; must sandbox to prevent arg.dot-s from parallel runs from interfering + (package goblint) ; depend on entire goblint package for svcomp21 conf + (:c for_odd_vesal_true-unreach-call.c) + (:prop %{project_root}/tests/sv-comp/unreach-call-__VERIFIER_error.prp)) + (target for_odd_vesal_true-unreach-call.output) + (enabled_if %{bin-available:graph-easy}) + (action + (progn + (ignore-outputs + (run goblint --conf svcomp.json --disable ana.autotune.enabled --set ana.specification %{prop} %{c} --enable witness.graphml.uncil --enable exp.arg.enabled --set exp.arg.dot.path arg.dot --set exp.arg.dot.node-label empty)) + (with-outputs-to %{target} + (run graph-easy --as=boxart arg.dot))))) + + (rule + (alias runtest) + (enabled_if %{bin-available:graph-easy}) + (action + (diff for_odd_vesal_true-unreach-call.expected for_odd_vesal_true-unreach-call.output)))) + +(subdir basic + (rule + (deps + (sandbox always) ; must sandbox to prevent arg.dot-s from parallel runs from interfering + (package goblint) ; depend on entire goblint package for svcomp21 conf + (:c for_true-unreach-call.c) + (:prop %{project_root}/tests/sv-comp/unreach-call-__VERIFIER_error.prp)) + (target for_true-unreach-call.output) + (enabled_if %{bin-available:graph-easy}) + (action + (progn + (ignore-outputs + (run goblint --conf svcomp.json --disable ana.autotune.enabled --set ana.specification %{prop} %{c} --enable witness.graphml.uncil --enable exp.arg.enabled --set exp.arg.dot.path arg.dot --set exp.arg.dot.node-label empty)) + (with-outputs-to %{target} + (run graph-easy --as=boxart arg.dot))))) + + (rule + (alias runtest) + (enabled_if %{bin-available:graph-easy}) + (action + (diff for_true-unreach-call.expected for_true-unreach-call.output)))) + +(subdir basic + (rule + (deps + (sandbox always) ; must sandbox to prevent arg.dot-s from parallel runs from interfering + (package goblint) ; depend on entire goblint package for svcomp21 conf + (:c global_init_true-unreach-call.c) + (:prop %{project_root}/tests/sv-comp/unreach-call-__VERIFIER_error.prp)) + (target global_init_true-unreach-call.output) + (enabled_if %{bin-available:graph-easy}) + (action + (progn + (ignore-outputs + (run goblint --conf svcomp.json --disable ana.autotune.enabled --set ana.specification %{prop} %{c} --enable witness.graphml.uncil --enable exp.arg.enabled --set exp.arg.dot.path arg.dot --set exp.arg.dot.node-label empty)) + (with-outputs-to %{target} + (run graph-easy --as=boxart arg.dot))))) + + (rule + (alias runtest) + (enabled_if %{bin-available:graph-easy}) + (action + (diff global_init_true-unreach-call.expected global_init_true-unreach-call.output)))) + +(subdir basic + (rule + (deps + (sandbox always) ; must sandbox to prevent arg.dot-s from parallel runs from interfering + (package goblint) ; depend on entire goblint package for svcomp21 conf + (:c if_det_false-unreach-call.c) + (:prop %{project_root}/tests/sv-comp/unreach-call-__VERIFIER_error.prp)) + (target if_det_false-unreach-call.output) + (enabled_if %{bin-available:graph-easy}) + (action + (progn + (ignore-outputs + (run goblint --conf svcomp.json --disable ana.autotune.enabled --set ana.specification %{prop} %{c} --enable witness.graphml.uncil --enable exp.arg.enabled --set exp.arg.dot.path arg.dot --set exp.arg.dot.node-label empty)) + (with-outputs-to %{target} + (run graph-easy --as=boxart arg.dot))))) + + (rule + (alias runtest) + (enabled_if %{bin-available:graph-easy}) + (action + (diff if_det_false-unreach-call.expected if_det_false-unreach-call.output)))) + +(subdir basic + (rule + (deps + (sandbox always) ; must sandbox to prevent arg.dot-s from parallel runs from interfering + (package goblint) ; depend on entire goblint package for svcomp21 conf + (:c if_det_incr_true-unreach-call.c) + (:prop %{project_root}/tests/sv-comp/unreach-call-__VERIFIER_error.prp)) + (target if_det_incr_true-unreach-call.output) + (enabled_if %{bin-available:graph-easy}) + (action + (progn + (ignore-outputs + (run goblint --conf svcomp.json --disable ana.autotune.enabled --set ana.specification %{prop} %{c} --enable witness.graphml.uncil --enable exp.arg.enabled --set exp.arg.dot.path arg.dot --set exp.arg.dot.node-label empty)) + (with-outputs-to %{target} + (run graph-easy --as=boxart arg.dot))))) + + (rule + (alias runtest) + (enabled_if %{bin-available:graph-easy}) + (action + (diff if_det_incr_true-unreach-call.expected if_det_incr_true-unreach-call.output)))) + +(subdir basic + (rule + (deps + (sandbox always) ; must sandbox to prevent arg.dot-s from parallel runs from interfering + (package goblint) ; depend on entire goblint package for svcomp21 conf + (:c if_det_true-unreach-call.c) + (:prop %{project_root}/tests/sv-comp/unreach-call-__VERIFIER_error.prp)) + (target if_det_true-unreach-call.output) + (enabled_if %{bin-available:graph-easy}) + (action + (progn + (ignore-outputs + (run goblint --conf svcomp.json --disable ana.autotune.enabled --set ana.specification %{prop} %{c} --enable witness.graphml.uncil --enable exp.arg.enabled --set exp.arg.dot.path arg.dot --set exp.arg.dot.node-label empty)) + (with-outputs-to %{target} + (run graph-easy --as=boxart arg.dot))))) + + (rule + (alias runtest) + (enabled_if %{bin-available:graph-easy}) + (action + (diff if_det_true-unreach-call.expected if_det_true-unreach-call.output)))) + +(subdir basic + (rule + (deps + (sandbox always) ; must sandbox to prevent arg.dot-s from parallel runs from interfering + (package goblint) ; depend on entire goblint package for svcomp21 conf + (:c if_mod_false-unreach-call.c) + (:prop %{project_root}/tests/sv-comp/unreach-call-__VERIFIER_error.prp)) + (target if_mod_false-unreach-call.output) + (enabled_if %{bin-available:graph-easy}) + (action + (progn + (ignore-outputs + (run goblint --conf svcomp.json --disable ana.autotune.enabled --set ana.specification %{prop} %{c} --enable witness.graphml.uncil --enable exp.arg.enabled --set exp.arg.dot.path arg.dot --set exp.arg.dot.node-label empty)) + (with-outputs-to %{target} + (run graph-easy --as=boxart arg.dot))))) + + (rule + (alias runtest) + (enabled_if %{bin-available:graph-easy}) + (action + (diff if_mod_false-unreach-call.expected if_mod_false-unreach-call.output)))) + +(subdir basic + (rule + (deps + (sandbox always) ; must sandbox to prevent arg.dot-s from parallel runs from interfering + (package goblint) ; depend on entire goblint package for svcomp21 conf + (:c if_mod_true-unreach-call.c) + (:prop %{project_root}/tests/sv-comp/unreach-call-__VERIFIER_error.prp)) + (target if_mod_true-unreach-call.output) + (enabled_if %{bin-available:graph-easy}) + (action + (progn + (ignore-outputs + (run goblint --conf svcomp.json --disable ana.autotune.enabled --set ana.specification %{prop} %{c} --enable witness.graphml.uncil --enable exp.arg.enabled --set exp.arg.dot.path arg.dot --set exp.arg.dot.node-label empty)) + (with-outputs-to %{target} + (run graph-easy --as=boxart arg.dot))))) + + (rule + (alias runtest) + (enabled_if %{bin-available:graph-easy}) + (action + (diff if_mod_true-unreach-call.expected if_mod_true-unreach-call.output)))) + +(subdir basic + (rule + (deps + (sandbox always) ; must sandbox to prevent arg.dot-s from parallel runs from interfering + (package goblint) ; depend on entire goblint package for svcomp21 conf + (:c if_nondet_fun_false-unreach-call.c) + (:prop %{project_root}/tests/sv-comp/unreach-call-__VERIFIER_error.prp)) + (target if_nondet_fun_false-unreach-call.output) + (enabled_if %{bin-available:graph-easy}) + (action + (progn + (ignore-outputs + (run goblint --conf svcomp.json --disable ana.autotune.enabled --set ana.specification %{prop} %{c} --enable witness.graphml.uncil --enable exp.arg.enabled --set exp.arg.dot.path arg.dot --set exp.arg.dot.node-label empty)) + (with-outputs-to %{target} + (run graph-easy --as=boxart arg.dot))))) + + (rule + (alias runtest) + (enabled_if %{bin-available:graph-easy}) + (action + (diff if_nondet_fun_false-unreach-call.expected if_nondet_fun_false-unreach-call.output)))) + +(subdir basic + (rule + (deps + (sandbox always) ; must sandbox to prevent arg.dot-s from parallel runs from interfering + (package goblint) ; depend on entire goblint package for svcomp21 conf + (:c if_nondet_var_false-unreach-call.c) + (:prop %{project_root}/tests/sv-comp/unreach-call-__VERIFIER_error.prp)) + (target if_nondet_var_false-unreach-call.output) + (enabled_if %{bin-available:graph-easy}) + (action + (progn + (ignore-outputs + (run goblint --conf svcomp.json --disable ana.autotune.enabled --set ana.specification %{prop} %{c} --enable witness.graphml.uncil --enable exp.arg.enabled --set exp.arg.dot.path arg.dot --set exp.arg.dot.node-label empty)) + (with-outputs-to %{target} + (run graph-easy --as=boxart arg.dot))))) + + (rule + (alias runtest) + (enabled_if %{bin-available:graph-easy}) + (action + (diff if_nondet_var_false-unreach-call.expected if_nondet_var_false-unreach-call.output)))) + +(subdir basic + (rule + (deps + (sandbox always) ; must sandbox to prevent arg.dot-s from parallel runs from interfering + (package goblint) ; depend on entire goblint package for svcomp21 conf + (:c if_trier_exclude_multiple_false-unreach-call.c) + (:prop %{project_root}/tests/sv-comp/unreach-call-__VERIFIER_error.prp)) + (target if_trier_exclude_multiple_false-unreach-call.output) + (enabled_if %{bin-available:graph-easy}) + (action + (progn + (ignore-outputs + (run goblint --conf svcomp.json --disable ana.autotune.enabled --set ana.specification %{prop} %{c} --enable witness.graphml.uncil --enable exp.arg.enabled --set exp.arg.dot.path arg.dot --set exp.arg.dot.node-label empty)) + (with-outputs-to %{target} + (run graph-easy --as=boxart arg.dot))))) + + (rule + (alias runtest) + (enabled_if %{bin-available:graph-easy}) + (action + (diff if_trier_exclude_multiple_false-unreach-call.expected if_trier_exclude_multiple_false-unreach-call.output)))) + +(subdir basic + (rule + (deps + (sandbox always) ; must sandbox to prevent arg.dot-s from parallel runs from interfering + (package goblint) ; depend on entire goblint package for svcomp21 conf + (:c if_trier_exclude_multiple_true-unreach-call.c) + (:prop %{project_root}/tests/sv-comp/unreach-call-__VERIFIER_error.prp)) + (target if_trier_exclude_multiple_true-unreach-call.output) + (enabled_if %{bin-available:graph-easy}) + (action + (progn + (ignore-outputs + (run goblint --conf svcomp.json --disable ana.autotune.enabled --set ana.specification %{prop} %{c} --enable witness.graphml.uncil --enable exp.arg.enabled --set exp.arg.dot.path arg.dot --set exp.arg.dot.node-label empty)) + (with-outputs-to %{target} + (run graph-easy --as=boxart arg.dot))))) + + (rule + (alias runtest) + (enabled_if %{bin-available:graph-easy}) + (action + (diff if_trier_exclude_multiple_true-unreach-call.expected if_trier_exclude_multiple_true-unreach-call.output)))) + +(subdir basic + (rule + (deps + (sandbox always) ; must sandbox to prevent arg.dot-s from parallel runs from interfering + (package goblint) ; depend on entire goblint package for svcomp21 conf + (:c if_trier_exclude_true-unreach-call.c) + (:prop %{project_root}/tests/sv-comp/unreach-call-__VERIFIER_error.prp)) + (target if_trier_exclude_true-unreach-call.output) + (enabled_if %{bin-available:graph-easy}) + (action + (progn + (ignore-outputs + (run goblint --conf svcomp.json --disable ana.autotune.enabled --set ana.specification %{prop} %{c} --enable witness.graphml.uncil --enable exp.arg.enabled --set exp.arg.dot.path arg.dot --set exp.arg.dot.node-label empty)) + (with-outputs-to %{target} + (run graph-easy --as=boxart arg.dot))))) + + (rule + (alias runtest) + (enabled_if %{bin-available:graph-easy}) + (action + (diff if_trier_exclude_true-unreach-call.expected if_trier_exclude_true-unreach-call.output)))) + +(subdir eq + (rule + (deps + (sandbox always) ; must sandbox to prevent arg.dot-s from parallel runs from interfering + (package goblint) ; depend on entire goblint package for svcomp21 conf + (:c eq_double_true-unreach-call.c) + (:prop %{project_root}/tests/sv-comp/unreach-call-__VERIFIER_error.prp)) + (target eq_double_true-unreach-call.output) + (enabled_if %{bin-available:graph-easy}) + (action + (progn + (ignore-outputs + (run goblint --conf svcomp.json --disable ana.autotune.enabled --set ana.specification %{prop} %{c} --enable witness.graphml.uncil --enable exp.arg.enabled --set exp.arg.dot.path arg.dot --set exp.arg.dot.node-label empty)) + (with-outputs-to %{target} + (run graph-easy --as=boxart arg.dot))))) + + (rule + (alias runtest) + (enabled_if %{bin-available:graph-easy}) + (action + (diff eq_double_true-unreach-call.expected eq_double_true-unreach-call.output)))) + +(subdir eq + (rule + (deps + (sandbox always) ; must sandbox to prevent arg.dot-s from parallel runs from interfering + (package goblint) ; depend on entire goblint package for svcomp21 conf + (:c eq_single_true-unreach-call.c) + (:prop %{project_root}/tests/sv-comp/unreach-call-__VERIFIER_error.prp)) + (target eq_single_true-unreach-call.output) + (enabled_if %{bin-available:graph-easy}) + (action + (progn + (ignore-outputs + (run goblint --conf svcomp.json --disable ana.autotune.enabled --set ana.specification %{prop} %{c} --enable witness.graphml.uncil --enable exp.arg.enabled --set exp.arg.dot.path arg.dot --set exp.arg.dot.node-label empty)) + (with-outputs-to %{target} + (run graph-easy --as=boxart arg.dot))))) + + (rule + (alias runtest) + (enabled_if %{bin-available:graph-easy}) + (action + (diff eq_single_true-unreach-call.expected eq_single_true-unreach-call.output)))) + +(subdir eq + (rule + (deps + (sandbox always) ; must sandbox to prevent arg.dot-s from parallel runs from interfering + (package goblint) ; depend on entire goblint package for svcomp21 conf + (:c multivar_false-unreach-call1.c) + (:prop %{project_root}/tests/sv-comp/unreach-call-__VERIFIER_error.prp)) + (target multivar_false-unreach-call1.output) + (enabled_if %{bin-available:graph-easy}) + (action + (progn + (ignore-outputs + (run goblint --conf svcomp.json --disable ana.autotune.enabled --set ana.specification %{prop} %{c} --enable witness.graphml.uncil --enable exp.arg.enabled --set exp.arg.dot.path arg.dot --set exp.arg.dot.node-label empty)) + (with-outputs-to %{target} + (run graph-easy --as=boxart arg.dot))))) + + (rule + (alias runtest) + (enabled_if %{bin-available:graph-easy}) + (action + (diff multivar_false-unreach-call1.expected multivar_false-unreach-call1.output)))) + +(subdir eq + (rule + (deps + (sandbox always) ; must sandbox to prevent arg.dot-s from parallel runs from interfering + (package goblint) ; depend on entire goblint package for svcomp21 conf + (:c multivar_true-unreach-call1.c) + (:prop %{project_root}/tests/sv-comp/unreach-call-__VERIFIER_error.prp)) + (target multivar_true-unreach-call1.output) + (enabled_if %{bin-available:graph-easy}) + (action + (progn + (ignore-outputs + (run goblint --conf svcomp.json --disable ana.autotune.enabled --set ana.specification %{prop} %{c} --enable witness.graphml.uncil --enable exp.arg.enabled --set exp.arg.dot.path arg.dot --set exp.arg.dot.node-label empty)) + (with-outputs-to %{target} + (run graph-easy --as=boxart arg.dot))))) + + (rule + (alias runtest) + (enabled_if %{bin-available:graph-easy}) + (action + (diff multivar_true-unreach-call1.expected multivar_true-unreach-call1.output)))) + +(subdir cfg + (rule + (deps + (sandbox always) ; must sandbox to prevent arg.dot-s from parallel runs from interfering + (package goblint) ; depend on entire goblint package for svcomp21 conf + (:c multicall_context_join_true-unreach-call.c) + (:prop %{project_root}/tests/sv-comp/unreach-call-__VERIFIER_error.prp)) + (target multicall_context_join_true-unreach-call.output) + (enabled_if %{bin-available:graph-easy}) + (action + (progn + (ignore-outputs + (run goblint --conf svcomp.json --disable ana.autotune.enabled --set ana.specification %{prop} %{c} --enable witness.graphml.uncil --enable exp.arg.enabled --set exp.arg.dot.path arg.dot --set exp.arg.dot.node-label empty)) + (with-outputs-to %{target} + (run graph-easy --as=boxart arg.dot))))) + + (rule + (alias runtest) + (enabled_if %{bin-available:graph-easy}) + (action + (diff multicall_context_join_true-unreach-call.expected multicall_context_join_true-unreach-call.output)))) + +(subdir cfg + (rule + (deps + (sandbox always) ; must sandbox to prevent arg.dot-s from parallel runs from interfering + (package goblint) ; depend on entire goblint package for svcomp21 conf + (:c multicall_context_true-unreach-call.c) + (:prop %{project_root}/tests/sv-comp/unreach-call-__VERIFIER_error.prp)) + (target multicall_context_true-unreach-call.output) + (enabled_if %{bin-available:graph-easy}) + (action + (progn + (ignore-outputs + (run goblint --conf svcomp.json --disable ana.autotune.enabled --set ana.specification %{prop} %{c} --enable witness.graphml.uncil --enable exp.arg.enabled --set exp.arg.dot.path arg.dot --set exp.arg.dot.node-label empty)) + (with-outputs-to %{target} + (run graph-easy --as=boxart arg.dot))))) + + (rule + (alias runtest) + (enabled_if %{bin-available:graph-easy}) + (action + (diff multicall_context_true-unreach-call.expected multicall_context_true-unreach-call.output)))) + +(subdir cfg + (rule + (deps + (sandbox always) ; must sandbox to prevent arg.dot-s from parallel runs from interfering + (package goblint) ; depend on entire goblint package for svcomp21 conf + (:c multicall_join_true-unreach-call.c) + (:prop %{project_root}/tests/sv-comp/unreach-call-__VERIFIER_error.prp)) + (target multicall_join_true-unreach-call.output) + (enabled_if %{bin-available:graph-easy}) + (action + (progn + (ignore-outputs + (run goblint --conf svcomp.json --disable ana.autotune.enabled --set ana.specification %{prop} %{c} --enable witness.graphml.uncil --enable exp.arg.enabled --set exp.arg.dot.path arg.dot --set exp.arg.dot.node-label empty)) + (with-outputs-to %{target} + (run graph-easy --as=boxart arg.dot))))) + + (rule + (alias runtest) + (enabled_if %{bin-available:graph-easy}) + (action + (diff multicall_join_true-unreach-call.expected multicall_join_true-unreach-call.output)))) + +(subdir cfg + (rule + (deps + (sandbox always) ; must sandbox to prevent arg.dot-s from parallel runs from interfering + (package goblint) ; depend on entire goblint package for svcomp21 conf + (:c multicall_nested_join_true-unreach-call.c) + (:prop %{project_root}/tests/sv-comp/unreach-call-__VERIFIER_error.prp)) + (target multicall_nested_join_true-unreach-call.output) + (enabled_if %{bin-available:graph-easy}) + (action + (progn + (ignore-outputs + (run goblint --conf svcomp.json --disable ana.autotune.enabled --set ana.specification %{prop} %{c} --enable witness.graphml.uncil --enable exp.arg.enabled --set exp.arg.dot.path arg.dot --set exp.arg.dot.node-label empty)) + (with-outputs-to %{target} + (run graph-easy --as=boxart arg.dot))))) + + (rule + (alias runtest) + (enabled_if %{bin-available:graph-easy}) + (action + (diff multicall_nested_join_true-unreach-call.expected multicall_nested_join_true-unreach-call.output)))) + +(subdir cfg + (rule + (deps + (sandbox always) ; must sandbox to prevent arg.dot-s from parallel runs from interfering + (package goblint) ; depend on entire goblint package for svcomp21 conf + (:c multicall_nested_true-unreach-call.c) + (:prop %{project_root}/tests/sv-comp/unreach-call-__VERIFIER_error.prp)) + (target multicall_nested_true-unreach-call.output) + (enabled_if %{bin-available:graph-easy}) + (action + (progn + (ignore-outputs + (run goblint --conf svcomp.json --disable ana.autotune.enabled --set ana.specification %{prop} %{c} --enable witness.graphml.uncil --enable exp.arg.enabled --set exp.arg.dot.path arg.dot --set exp.arg.dot.node-label empty)) + (with-outputs-to %{target} + (run graph-easy --as=boxart arg.dot))))) + + (rule + (alias runtest) + (enabled_if %{bin-available:graph-easy}) + (action + (diff multicall_nested_true-unreach-call.expected multicall_nested_true-unreach-call.output)))) + +(subdir cfg + (rule + (deps + (sandbox always) ; must sandbox to prevent arg.dot-s from parallel runs from interfering + (package goblint) ; depend on entire goblint package for svcomp21 conf + (:c multicall_return_context_true-unreach-call.c) + (:prop %{project_root}/tests/sv-comp/unreach-call-__VERIFIER_error.prp)) + (target multicall_return_context_true-unreach-call.output) + (enabled_if %{bin-available:graph-easy}) + (action + (progn + (ignore-outputs + (run goblint --conf svcomp.json --disable ana.autotune.enabled --set ana.specification %{prop} %{c} --enable witness.graphml.uncil --enable exp.arg.enabled --set exp.arg.dot.path arg.dot --set exp.arg.dot.node-label empty)) + (with-outputs-to %{target} + (run graph-easy --as=boxart arg.dot))))) + + (rule + (alias runtest) + (enabled_if %{bin-available:graph-easy}) + (action + (diff multicall_return_context_true-unreach-call.expected multicall_return_context_true-unreach-call.output)))) + +(subdir cfg + (rule + (deps + (sandbox always) ; must sandbox to prevent arg.dot-s from parallel runs from interfering + (package goblint) ; depend on entire goblint package for svcomp21 conf + (:c multicall_true-unreach-call.c) + (:prop %{project_root}/tests/sv-comp/unreach-call-__VERIFIER_error.prp)) + (target multicall_true-unreach-call.output) + (enabled_if %{bin-available:graph-easy}) + (action + (progn + (ignore-outputs + (run goblint --conf svcomp.json --disable ana.autotune.enabled --set ana.specification %{prop} %{c} --enable witness.graphml.uncil --enable exp.arg.enabled --set exp.arg.dot.path arg.dot --set exp.arg.dot.node-label empty)) + (with-outputs-to %{target} + (run graph-easy --as=boxart arg.dot))))) + + (rule + (alias runtest) + (enabled_if %{bin-available:graph-easy}) + (action + (diff multicall_true-unreach-call.expected multicall_true-unreach-call.output)))) + +(subdir cfg + (rule + (deps + (sandbox always) ; must sandbox to prevent arg.dot-s from parallel runs from interfering + (package goblint) ; depend on entire goblint package for svcomp21 conf + (:c join_true-unreach-call.c) + (:prop %{project_root}/tests/sv-comp/unreach-call-__VERIFIER_error.prp)) + (target join_true-unreach-call.output) + (enabled_if %{bin-available:graph-easy}) + (action + (progn + (ignore-outputs + (run goblint --conf svcomp.json --disable ana.autotune.enabled --set ana.specification %{prop} %{c} --enable witness.graphml.uncil --enable exp.arg.enabled --set exp.arg.dot.path arg.dot --set exp.arg.dot.node-label empty)) + (with-outputs-to %{target} + (run graph-easy --as=boxart arg.dot))))) + + (rule + (alias runtest) + (enabled_if %{bin-available:graph-easy}) + (action + (diff join_true-unreach-call.expected join_true-unreach-call.output)))) + +(subdir cfg + (rule + (deps + (sandbox always) ; must sandbox to prevent arg.dot-s from parallel runs from interfering + (package goblint) ; depend on entire goblint package for svcomp21 conf + (:c builtin_expect_true-unreach-call.c) + (:prop %{project_root}/tests/sv-comp/unreach-call-__VERIFIER_error.prp)) + (target builtin_expect_true-unreach-call.output) + (enabled_if %{bin-available:graph-easy}) + (action + (progn + (ignore-outputs + (run goblint --conf svcomp.json --disable ana.autotune.enabled --set ana.specification %{prop} %{c} --enable witness.graphml.uncil --enable exp.arg.enabled --set exp.arg.dot.path arg.dot --set exp.arg.dot.node-label empty)) + (with-outputs-to %{target} + (run graph-easy --as=boxart arg.dot))))) + + (rule + (alias runtest) + (enabled_if %{bin-available:graph-easy}) + (action + (diff builtin_expect_true-unreach-call.expected builtin_expect_true-unreach-call.output)))) + +(subdir cfg + (rule + (deps + (sandbox always) ; must sandbox to prevent arg.dot-s from parallel runs from interfering + (package goblint) ; depend on entire goblint package for svcomp21 conf + (:c region_global_init_true-unreach-call.c) + (:prop %{project_root}/tests/sv-comp/unreach-call-__VERIFIER_error.prp)) + (target region_global_init_true-unreach-call.output) + (enabled_if %{bin-available:graph-easy}) + (action + (progn + (ignore-outputs + (run goblint --conf svcomp.json --disable ana.autotune.enabled --set ana.specification %{prop} %{c} --enable witness.graphml.uncil --enable exp.arg.enabled --set exp.arg.dot.path arg.dot --set exp.arg.dot.node-label empty)) + (with-outputs-to %{target} + (run graph-easy --as=boxart arg.dot))))) + + (rule + (alias runtest) + (enabled_if %{bin-available:graph-easy}) + (action + (diff region_global_init_true-unreach-call.expected region_global_init_true-unreach-call.output)))) + +(subdir cfg + (rule + (deps + (sandbox always) ; must sandbox to prevent arg.dot-s from parallel runs from interfering + (package goblint) ; depend on entire goblint package for svcomp21 conf + (:c local_shadow_fun_true-unreach-call.c) + (:prop %{project_root}/tests/sv-comp/unreach-call-__VERIFIER_error.prp)) + (target local_shadow_fun_true-unreach-call.output) + (enabled_if %{bin-available:graph-easy}) + (action + (progn + (ignore-outputs + (run goblint --conf svcomp.json --disable ana.autotune.enabled --set ana.specification %{prop} %{c} --enable witness.graphml.uncil --enable exp.arg.enabled --set exp.arg.dot.path arg.dot --set exp.arg.dot.node-label empty)) + (with-outputs-to %{target} + (run graph-easy --as=boxart arg.dot))))) + + (rule + (alias runtest) + (enabled_if %{bin-available:graph-easy}) + (action + (diff local_shadow_fun_true-unreach-call.expected local_shadow_fun_true-unreach-call.output)))) + +(subdir cfg + (rule + (deps + (sandbox always) ; must sandbox to prevent arg.dot-s from parallel runs from interfering + (package goblint) ; depend on entire goblint package for svcomp21 conf + (:c free_spawn_true-unreach-call.c) + (:prop %{project_root}/tests/sv-comp/unreach-call-__VERIFIER_error.prp)) + (target free_spawn_true-unreach-call.output) + (enabled_if %{bin-available:graph-easy}) + (action + (progn + (ignore-outputs + (run goblint --conf svcomp.json --disable ana.autotune.enabled --set ana.specification %{prop} %{c} --enable witness.graphml.uncil --enable exp.arg.enabled --set exp.arg.dot.path arg.dot --set exp.arg.dot.node-label empty)) + (with-outputs-to %{target} + (run graph-easy --as=boxart arg.dot))))) + + (rule + (alias runtest) + (enabled_if %{bin-available:graph-easy}) + (action + (diff free_spawn_true-unreach-call.expected free_spawn_true-unreach-call.output)))) + +(subdir cfg + (rule + (deps + (sandbox always) ; must sandbox to prevent arg.dot-s from parallel runs from interfering + (package goblint) ; depend on entire goblint package for svcomp21 conf + (:c free_spawn_ub_true-unreach-call.c) + (:prop %{project_root}/tests/sv-comp/unreach-call-__VERIFIER_error.prp)) + (target free_spawn_ub_true-unreach-call.output) + (enabled_if %{bin-available:graph-easy}) + (action + (progn + (ignore-outputs + (run goblint --conf svcomp.json --disable ana.autotune.enabled --set ana.specification %{prop} %{c} --enable witness.graphml.uncil --enable exp.arg.enabled --set exp.arg.dot.path arg.dot --set exp.arg.dot.node-label empty)) + (with-outputs-to %{target} + (run graph-easy --as=boxart arg.dot))))) + + (rule + (alias runtest) + (enabled_if %{bin-available:graph-easy}) + (action + (diff free_spawn_ub_true-unreach-call.expected free_spawn_ub_true-unreach-call.output)))) + +(subdir cfg/uncil + (rule + (deps + (sandbox always) ; must sandbox to prevent arg.dot-s from parallel runs from interfering + (package goblint) ; depend on entire goblint package for svcomp21 conf + (:c and3_true-unreach-call.c) + (:prop %{project_root}/tests/sv-comp/unreach-call-__VERIFIER_error.prp)) + (target and3_true-unreach-call.output) + (enabled_if %{bin-available:graph-easy}) + (action + (progn + (ignore-outputs + (run goblint --conf svcomp.json --disable ana.autotune.enabled --set ana.specification %{prop} %{c} --enable witness.graphml.uncil --enable exp.arg.enabled --set exp.arg.dot.path arg.dot --set exp.arg.dot.node-label empty)) + (with-outputs-to %{target} + (run graph-easy --as=boxart arg.dot))))) + + (rule + (alias runtest) + (enabled_if %{bin-available:graph-easy}) + (action + (diff and3_true-unreach-call.expected and3_true-unreach-call.output)))) + +(subdir cfg/uncil + (rule + (deps + (sandbox always) ; must sandbox to prevent arg.dot-s from parallel runs from interfering + (package goblint) ; depend on entire goblint package for svcomp21 conf + (:c and3dead_true-unreach-call.c) + (:prop %{project_root}/tests/sv-comp/unreach-call-__VERIFIER_error.prp)) + (target and3dead_true-unreach-call.output) + (enabled_if %{bin-available:graph-easy}) + (action + (progn + (ignore-outputs + (run goblint --conf svcomp.json --disable ana.autotune.enabled --set ana.specification %{prop} %{c} --enable witness.graphml.uncil --enable exp.arg.enabled --set exp.arg.dot.path arg.dot --set exp.arg.dot.node-label empty)) + (with-outputs-to %{target} + (run graph-easy --as=boxart arg.dot))))) + + (rule + (alias runtest) + (enabled_if %{bin-available:graph-easy}) + (action + (diff and3dead_true-unreach-call.expected and3dead_true-unreach-call.output)))) + +(subdir cfg/uncil + (rule + (deps + (sandbox always) ; must sandbox to prevent arg.dot-s from parallel runs from interfering + (package goblint) ; depend on entire goblint package for svcomp21 conf + (:c and_copy_true-unreach-call.c) + (:prop %{project_root}/tests/sv-comp/unreach-call-__VERIFIER_error.prp)) + (target and_copy_true-unreach-call.output) + (enabled_if %{bin-available:graph-easy}) + (action + (progn + (ignore-outputs + (run goblint --conf svcomp.json --disable ana.autotune.enabled --set ana.specification %{prop} %{c} --enable witness.graphml.uncil --enable exp.arg.enabled --set exp.arg.dot.path arg.dot --set exp.arg.dot.node-label empty)) + (with-outputs-to %{target} + (run graph-easy --as=boxart arg.dot))))) + + (rule + (alias runtest) + (enabled_if %{bin-available:graph-easy}) + (action + (diff and_copy_true-unreach-call.expected and_copy_true-unreach-call.output)))) + +(subdir cfg/uncil + (rule + (deps + (sandbox always) ; must sandbox to prevent arg.dot-s from parallel runs from interfering + (package goblint) ; depend on entire goblint package for svcomp21 conf + (:c and_join_invariant_true-unreach-call.c) + (:prop %{project_root}/tests/sv-comp/unreach-call-__VERIFIER_error.prp)) + (target and_join_invariant_true-unreach-call.output) + (enabled_if %{bin-available:graph-easy}) + (action + (progn + (ignore-outputs + (run goblint --conf svcomp.json --disable ana.autotune.enabled --set ana.specification %{prop} %{c} --enable witness.graphml.uncil --enable exp.arg.enabled --set exp.arg.dot.path arg.dot --set exp.arg.dot.node-label empty)) + (with-outputs-to %{target} + (run graph-easy --as=boxart arg.dot))))) + + (rule + (alias runtest) + (enabled_if %{bin-available:graph-easy}) + (action + (diff and_join_invariant_true-unreach-call.expected and_join_invariant_true-unreach-call.output)))) + +(subdir cfg/uncil + (rule + (deps + (sandbox always) ; must sandbox to prevent arg.dot-s from parallel runs from interfering + (package goblint) ; depend on entire goblint package for svcomp21 conf + (:c and_true-unreach-call.c) + (:prop %{project_root}/tests/sv-comp/unreach-call-__VERIFIER_error.prp)) + (target and_true-unreach-call.output) + (enabled_if %{bin-available:graph-easy}) + (action + (progn + (ignore-outputs + (run goblint --conf svcomp.json --disable ana.autotune.enabled --set ana.specification %{prop} %{c} --enable witness.graphml.uncil --enable exp.arg.enabled --set exp.arg.dot.path arg.dot --set exp.arg.dot.node-label empty)) + (with-outputs-to %{target} + (run graph-easy --as=boxart arg.dot))))) + + (rule + (alias runtest) + (enabled_if %{bin-available:graph-easy}) + (action + (diff and_true-unreach-call.expected and_true-unreach-call.output)))) + +(subdir cfg/uncil + (rule + (deps + (sandbox always) ; must sandbox to prevent arg.dot-s from parallel runs from interfering + (package goblint) ; depend on entire goblint package for svcomp21 conf + (:c and_var_false-unreach-call.c) + (:prop %{project_root}/tests/sv-comp/unreach-call-__VERIFIER_error.prp)) + (target and_var_false-unreach-call.output) + (enabled_if %{bin-available:graph-easy}) + (action + (progn + (ignore-outputs + (run goblint --conf svcomp.json --disable ana.autotune.enabled --set ana.specification %{prop} %{c} --enable witness.graphml.uncil --enable exp.arg.enabled --set exp.arg.dot.path arg.dot --set exp.arg.dot.node-label empty)) + (with-outputs-to %{target} + (run graph-easy --as=boxart arg.dot))))) + + (rule + (alias runtest) + (enabled_if %{bin-available:graph-easy}) + (action + (diff and_var_false-unreach-call.expected and_var_false-unreach-call.output)))) + +(subdir cfg/uncil + (rule + (deps + (sandbox always) ; must sandbox to prevent arg.dot-s from parallel runs from interfering + (package goblint) ; depend on entire goblint package for svcomp21 conf + (:c and_var_true-unreach-call.c) + (:prop %{project_root}/tests/sv-comp/unreach-call-__VERIFIER_error.prp)) + (target and_var_true-unreach-call.output) + (enabled_if %{bin-available:graph-easy}) + (action + (progn + (ignore-outputs + (run goblint --conf svcomp.json --disable ana.autotune.enabled --set ana.specification %{prop} %{c} --enable witness.graphml.uncil --enable exp.arg.enabled --set exp.arg.dot.path arg.dot --set exp.arg.dot.node-label empty)) + (with-outputs-to %{target} + (run graph-easy --as=boxart arg.dot))))) + + (rule + (alias runtest) + (enabled_if %{bin-available:graph-easy}) + (action + (diff and_var_true-unreach-call.expected and_var_true-unreach-call.output)))) + +(subdir cfg/uncil + (rule + (deps + (sandbox always) ; must sandbox to prevent arg.dot-s from parallel runs from interfering + (package goblint) ; depend on entire goblint package for svcomp21 conf + (:c or3_true-unreach-call.c) + (:prop %{project_root}/tests/sv-comp/unreach-call-__VERIFIER_error.prp)) + (target or3_true-unreach-call.output) + (enabled_if %{bin-available:graph-easy}) + (action + (progn + (ignore-outputs + (run goblint --conf svcomp.json --disable ana.autotune.enabled --set ana.specification %{prop} %{c} --enable witness.graphml.uncil --enable exp.arg.enabled --set exp.arg.dot.path arg.dot --set exp.arg.dot.node-label empty)) + (with-outputs-to %{target} + (run graph-easy --as=boxart arg.dot))))) + + (rule + (alias runtest) + (enabled_if %{bin-available:graph-easy}) + (action + (diff or3_true-unreach-call.expected or3_true-unreach-call.output)))) + +(subdir cfg/uncil + (rule + (deps + (sandbox always) ; must sandbox to prevent arg.dot-s from parallel runs from interfering + (package goblint) ; depend on entire goblint package for svcomp21 conf + (:c or3dead_true-unreach-call.c) + (:prop %{project_root}/tests/sv-comp/unreach-call-__VERIFIER_error.prp)) + (target or3dead_true-unreach-call.output) + (enabled_if %{bin-available:graph-easy}) + (action + (progn + (ignore-outputs + (run goblint --conf svcomp.json --disable ana.autotune.enabled --set ana.specification %{prop} %{c} --enable witness.graphml.uncil --enable exp.arg.enabled --set exp.arg.dot.path arg.dot --set exp.arg.dot.node-label empty)) + (with-outputs-to %{target} + (run graph-easy --as=boxart arg.dot))))) + + (rule + (alias runtest) + (enabled_if %{bin-available:graph-easy}) + (action + (diff or3dead_true-unreach-call.expected or3dead_true-unreach-call.output)))) + +(subdir cfg/uncil + (rule + (deps + (sandbox always) ; must sandbox to prevent arg.dot-s from parallel runs from interfering + (package goblint) ; depend on entire goblint package for svcomp21 conf + (:c or_true-unreach-call.c) + (:prop %{project_root}/tests/sv-comp/unreach-call-__VERIFIER_error.prp)) + (target or_true-unreach-call.output) + (enabled_if %{bin-available:graph-easy}) + (action + (progn + (ignore-outputs + (run goblint --conf svcomp.json --disable ana.autotune.enabled --set ana.specification %{prop} %{c} --enable witness.graphml.uncil --enable exp.arg.enabled --set exp.arg.dot.path arg.dot --set exp.arg.dot.node-label empty)) + (with-outputs-to %{target} + (run graph-easy --as=boxart arg.dot))))) + + (rule + (alias runtest) + (enabled_if %{bin-available:graph-easy}) + (action + (diff or_true-unreach-call.expected or_true-unreach-call.output)))) diff --git a/tests/sv-comp/eq/eq_double_true-unreach-call.expected b/tests/sv-comp/eq/eq_double_true-unreach-call.expected new file mode 100644 index 0000000000..9779fbd085 --- /dev/null +++ b/tests/sv-comp/eq/eq_double_true-unreach-call.expected @@ -0,0 +1,141 @@ + ┌────────────────────────────────────────┐ + │ _ │ + └────────────────────────────────────────┘ + │ + │ Entry main + ▼ + InlineReturn ┌────────────────────────────────────────┐ Inlined Proc '__VERIFIER_assert(a == b)' + ┌─────────────────────────────────────────────────────▶ │ _ │ ◀·················································┐ + │ └────────────────────────────────────────┘ : + │ │ : + │ │ Test (1,true) : + │ ▼ : + │ ┌────────────────────────────────────────┐ : + │ │ _ │ ·┐ : + │ └────────────────────────────────────────┘ : : + │ │ : : + │ │ InlineEntry '()' : : + │ ▼ : : + │ ┌────────────────────────────────────────┐ : : + │ │ _ │ : : + │ └────────────────────────────────────────┘ : : + │ │ : : + │ │ Entry __VERIFIER_nondet_int : : + │ ▼ : : + │ ┌────────────────────────────────────────┐ : : + │ │ _ │ : Inlined Proc 'tmp = __VERIFIER_nondet_int()' : + │ └────────────────────────────────────────┘ : : + │ │ : : + │ │ Ret (Some val, __VERIFIER_nondet_int) : : + │ ▼ : : + │ ┌────────────────────────────────────────┐ : : + │ │ _ │ : : + │ └────────────────────────────────────────┘ : : + │ │ : : + │ │ InlineReturn 'tmp' : : + │ ▼ : : + │ ┌────────────────────────────────────────┐ : : + │ │ _ │ ◀┘ : + │ └────────────────────────────────────────┘ : + │ │ : + │ │ Assign 'x = tmp' : + │ ▼ : + │ ┌────────────────────────────────────────┐ : + │ │ _ │ : + │ └────────────────────────────────────────┘ : + │ │ : + │ │ Assign 'y = x' : + │ ▼ : + │ ┌────────────────────────────────────────┐ : + │ │ _ │ ·┐ : + │ └────────────────────────────────────────┘ : : + │ │ : : + │ │ InlineEntry '(x == y)' : : + │ ▼ : : + │ ┌────────────────────────────────────────┐ : : + │ │ _ │ : : + │ └────────────────────────────────────────┘ : : + │ │ : : + │ │ Entry __VERIFIER_assert : : + │ ▼ : : + │ ┌────────────────────────────────────────┐ : : + │ │ _ │ : : + │ └────────────────────────────────────────┘ : : + │ │ : : + │ │ Test (! cond,false) : Inlined Proc '__VERIFIER_assert(x == y)' : + │ ▼ : : + │ ┌────────────────────────────────────────┐ : : + │ │ _ │ : : + │ └────────────────────────────────────────┘ : : + │ │ : : + │ │ Ret (None, __VERIFIER_assert) : : + │ ▼ : : + │ ┌────────────────────────────────────────┐ : : + │ │ _ │ : : + │ └────────────────────────────────────────┘ : : + │ │ : : + │ │ InlineReturn : : + │ ▼ : : + │ ┌────────────────────────────────────────┐ : : + │ ┌················································· │ _ │ ◀┘ : + │ : └────────────────────────────────────────┘ : + │ : │ : + │ : │ InlineEntry '()' ┌················································┘ + │ : ▼ : + │ : ┌────────────────────────────────────────┐ : + │ : │ _ │ : + │ : └────────────────────────────────────────┘ : + │ : │ : + │ : │ Entry __VERIFIER_nondet_int : + │ : ▼ : + │ : ┌────────────────────────────────────────┐ : + │ : Inlined Proc 'tmp___0 = __VERIFIER_nondet_int()' │ _ │ : + │ : └────────────────────────────────────────┘ : + │ : │ : + │ : │ Ret (Some val, __VERIFIER_nondet_int) : + │ : ▼ : + │ : ┌────────────────────────────────────────┐ : + │ : │ _ │ : + │ : └────────────────────────────────────────┘ : + │ : │ : + │ : │ InlineReturn 'tmp___0' : + │ : ▼ : + │ : ┌────────────────────────────────────────┐ : + │ └················································▶ │ _ │ : + │ └────────────────────────────────────────┘ : + │ │ : + └────┐ │ Assign 'a = tmp___0' : + │ ▼ : + │ ┌────────────────────────────────────────┐ : + │ │ _ │ : + │ └────────────────────────────────────────┘ : + │ │ : + │ │ Assign 'b = a' : + │ ▼ : + │ ┌────────────────────────────────────────┐ : + │ │ _ │ ·┘ + │ └────────────────────────────────────────┘ + │ │ + │ │ InlineEntry '(a == b)' + │ ▼ + │ ┌────────────────────────────────────────┐ + │ │ _ │ + │ └────────────────────────────────────────┘ + │ │ + │ │ Entry __VERIFIER_assert + │ ▼ + │ ┌────────────────────────────────────────┐ + │ │ _ │ + │ └────────────────────────────────────────┘ + │ │ + │ │ Test (! cond,false) + │ ▼ + │ ┌────────────────────────────────────────┐ + │ │ _ │ + │ └────────────────────────────────────────┘ + │ │ + │ │ Ret (None, __VERIFIER_assert) + │ ▼ + │ ┌────────────────────────────────────────┐ + └───────────────────────────────────────────────── │ _ │ + └────────────────────────────────────────┘ diff --git a/tests/sv-comp/eq/eq_single_true-unreach-call.expected b/tests/sv-comp/eq/eq_single_true-unreach-call.expected new file mode 100644 index 0000000000..395c01ce71 --- /dev/null +++ b/tests/sv-comp/eq/eq_single_true-unreach-call.expected @@ -0,0 +1,75 @@ + ┌────────────────────────────────────────┐ + │ _ │ + └────────────────────────────────────────┘ + │ + │ Entry main + ▼ + ┌────────────────────────────────────────┐ InlineReturn + ┌········································▶ │ _ │ ◀─────────────────────────────────────────────────┐ + : └────────────────────────────────────────┘ │ + : │ │ + : │ Test (1,true) │ + : ▼ │ + : ┌────────────────────────────────────────┐ │ + : │ _ │ ·┐ │ + : └────────────────────────────────────────┘ : │ + : │ : │ + : │ InlineEntry '()' : │ + : ▼ : │ + : ┌────────────────────────────────────────┐ : │ + : │ _ │ : │ + : └────────────────────────────────────────┘ : │ + : │ : │ + : │ Entry __VERIFIER_nondet_int : │ + : ▼ : │ + : ┌────────────────────────────────────────┐ : │ + : │ _ │ : Inlined Proc 'tmp = __VERIFIER_nondet_int()' │ + : └────────────────────────────────────────┘ : │ + : │ : │ + : Inlined Proc '__VERIFIER_assert(x == y)' │ Ret (Some val, __VERIFIER_nondet_int) : │ + : ▼ : │ + : ┌────────────────────────────────────────┐ : │ + : │ _ │ : │ + : └────────────────────────────────────────┘ : │ + : │ : │ + : │ InlineReturn 'tmp' : │ + : ▼ : │ + : ┌────────────────────────────────────────┐ : │ + : │ _ │ ◀┘ │ + : └────────────────────────────────────────┘ │ + : │ │ + : │ Assign 'x = tmp' ┌────────────────────────────────────────────────┘ + : ▼ │ + : ┌────────────────────────────────────────┐ │ + : │ _ │ │ + : └────────────────────────────────────────┘ │ + : │ │ + : │ Assign 'y = x' │ + : ▼ │ + : ┌────────────────────────────────────────┐ │ + └········································· │ _ │ │ + └────────────────────────────────────────┘ │ + │ │ + │ InlineEntry '(x == y)' │ + ▼ │ + ┌────────────────────────────────────────┐ │ + │ _ │ │ + └────────────────────────────────────────┘ │ + │ │ + │ Entry __VERIFIER_assert │ + ▼ │ + ┌────────────────────────────────────────┐ │ + │ _ │ │ + └────────────────────────────────────────┘ │ + │ │ + │ Test (! cond,false) │ + ▼ │ + ┌────────────────────────────────────────┐ │ + │ _ │ │ + └────────────────────────────────────────┘ │ + │ │ + │ Ret (None, __VERIFIER_assert) │ + ▼ │ + ┌────────────────────────────────────────┐ │ + │ _ │ ─┘ + └────────────────────────────────────────┘ diff --git a/tests/sv-comp/eq/multivar_false-unreach-call1.expected b/tests/sv-comp/eq/multivar_false-unreach-call1.expected new file mode 100644 index 0000000000..018b0e06fe --- /dev/null +++ b/tests/sv-comp/eq/multivar_false-unreach-call1.expected @@ -0,0 +1,90 @@ + + ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ │ + │ ┌─────────────────────────────────────────┐ │ + │ │ _ │ │ + │ └─────────────────────────────────────────┘ │ + │ │ │ + │ │ Entry main │ + │ ▼ │ + │ ┌─────────────────────────────────────────┐ │ + │ │ _ │ ·┐ │ + │ └─────────────────────────────────────────┘ : │ + │ │ : │ + │ │ InlineEntry '()' : │ + │ ▼ : │ + │ ┌─────────────────────────────────────────┐ : │ + │ │ _ │ : │ + │ └─────────────────────────────────────────┘ : │ + │ │ : │ + │ │ Entry __VERIFIER_nondet_uint : │ + │ ▼ : │ + │ ┌─────────────────────────────────────────┐ : │ + │ │ _ │ : Inlined Proc 'tmp = __VERIFIER_nondet_uint()' │ + │ └─────────────────────────────────────────┘ : │ + │ │ : │ + │ │ Ret (Some val, __VERIFIER_nondet_uint) : │ + │ ▼ : │ + │ ┌─────────────────────────────────────────┐ : │ + │ │ _ │ : │ + │ └─────────────────────────────────────────┘ : │ + │ │ : │ + │ │ InlineReturn 'tmp' : │ + │ ▼ : │ + │ ┌─────────────────────────────────────────┐ : │ + │ │ _ │ ◀┘ │ + │ └─────────────────────────────────────────┘ │ + │ │ │ + │ │ Assign 'x = tmp' │ + │ ▼ │ + │ ┌─────────────────────────────────────────┐ │ + │ │ _ │ │ + │ └─────────────────────────────────────────┘ │ + │ │ │ + │ │ Assign 'y = x' │ + │ ▼ │ + │ ┌─────────────────────────┐ Test (x < 1024U,true) ┌─────────────────────────────────────────┐ Assign 'y = y + 1U' │ + │ │ _ │ ◀─────────────────────── │ _ │ ◀──────────────────────────────────────────────────┘ + │ └─────────────────────────┘ └─────────────────────────────────────────┘ + │ │ │ + │ │ Assign 'x = x + 1U' │ Test (x < 1024U,false) + │ ▼ ▼ + │ ┌─────────────────────────┐ ┌─────────────────────────────────────────┐ + └─ │ _ │ │ _ │ ·┐ + └─────────────────────────┘ └─────────────────────────────────────────┘ : + │ : + │ InlineEntry '(x < y)' : + ▼ : + ┌─────────────────────────────────────────┐ : + │ _ │ : + └─────────────────────────────────────────┘ : + │ : + │ Entry __VERIFIER_assert : + ▼ : + ┌─────────────────────────┐ Test (! cond,true) ┌─────────────────────────────────────────┐ : + │ _ │ ◀─────────────────────── │ _ │ : + └─────────────────────────┘ └─────────────────────────────────────────┘ : + │ │ : + │ InlineEntry '()' │ Test (! cond,false) : Inlined Proc '__VERIFIER_assert(x < y)' + ▼ ▼ : + ┌─────────────────────────┐ ┌─────────────────────────────────────────┐ : + │ _ │ │ _ │ : + └─────────────────────────┘ └─────────────────────────────────────────┘ : + │ │ : + │ Entry __VERIFIER_error │ Ret (None, __VERIFIER_assert) : + ▼ ▼ : + ┌─────────────────────────┐ ┌─────────────────────────────────────────┐ : + │ _ │ │ _ │ : + └─────────────────────────┘ └─────────────────────────────────────────┘ : + │ : + │ InlineReturn : + ▼ : + ┌─────────────────────────────────────────┐ : + │ _ │ ◀┘ + └─────────────────────────────────────────┘ + │ + │ Ret (Some 0, main) + ▼ + ┌─────────────────────────────────────────┐ + │ _ │ + └─────────────────────────────────────────┘ diff --git a/tests/sv-comp/eq/multivar_true-unreach-call1.expected b/tests/sv-comp/eq/multivar_true-unreach-call1.expected new file mode 100644 index 0000000000..f1217ef669 --- /dev/null +++ b/tests/sv-comp/eq/multivar_true-unreach-call1.expected @@ -0,0 +1,90 @@ + + ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ │ + │ ┌─────────────────────────────────────────┐ │ + │ │ _ │ │ + │ └─────────────────────────────────────────┘ │ + │ │ │ + │ │ Entry main │ + │ ▼ │ + │ ┌─────────────────────────────────────────┐ │ + │ │ _ │ ·┐ │ + │ └─────────────────────────────────────────┘ : │ + │ │ : │ + │ │ InlineEntry '()' : │ + │ ▼ : │ + │ ┌─────────────────────────────────────────┐ : │ + │ │ _ │ : │ + │ └─────────────────────────────────────────┘ : │ + │ │ : │ + │ │ Entry __VERIFIER_nondet_uint : │ + │ ▼ : │ + │ ┌─────────────────────────────────────────┐ : │ + │ │ _ │ : Inlined Proc 'tmp = __VERIFIER_nondet_uint()' │ + │ └─────────────────────────────────────────┘ : │ + │ │ : │ + │ │ Ret (Some val, __VERIFIER_nondet_uint) : │ + │ ▼ : │ + │ ┌─────────────────────────────────────────┐ : │ + │ │ _ │ : │ + │ └─────────────────────────────────────────┘ : │ + │ │ : │ + │ │ InlineReturn 'tmp' : │ + │ ▼ : │ + │ ┌─────────────────────────────────────────┐ : │ + │ │ _ │ ◀┘ │ + │ └─────────────────────────────────────────┘ │ + │ │ │ + │ │ Assign 'x = tmp' │ + │ ▼ │ + │ ┌─────────────────────────────────────────┐ │ + │ │ _ │ │ + │ └─────────────────────────────────────────┘ │ + │ │ │ + │ │ Assign 'y = x' │ + │ ▼ │ + │ ┌─────────────────────────┐ Test (x < 1024U,true) ┌─────────────────────────────────────────┐ Assign 'y = y + 1U' │ + │ │ _ │ ◀─────────────────────── │ _ │ ◀──────────────────────────────────────────────────┘ + │ └─────────────────────────┘ └─────────────────────────────────────────┘ + │ │ │ + │ │ Assign 'x = x + 1U' │ Test (x < 1024U,false) + │ ▼ ▼ + │ ┌─────────────────────────┐ ┌─────────────────────────────────────────┐ + └─ │ _ │ │ _ │ ·┐ + └─────────────────────────┘ └─────────────────────────────────────────┘ : + │ : + │ InlineEntry '(x == y)' : + ▼ : + ┌─────────────────────────────────────────┐ : + │ _ │ : + └─────────────────────────────────────────┘ : + │ : + │ Entry __VERIFIER_assert : + ▼ : + ┌─────────────────────────┐ Test (! cond,true) ┌─────────────────────────────────────────┐ : + │ _ │ ◀─────────────────────── │ _ │ : + └─────────────────────────┘ └─────────────────────────────────────────┘ : + │ │ : + │ InlineEntry '()' │ Test (! cond,false) : Inlined Proc '__VERIFIER_assert(x == y)' + ▼ ▼ : + ┌─────────────────────────┐ ┌─────────────────────────────────────────┐ : + │ _ │ │ _ │ : + └─────────────────────────┘ └─────────────────────────────────────────┘ : + │ │ : + │ Entry __VERIFIER_error │ Ret (None, __VERIFIER_assert) : + ▼ ▼ : + ┌─────────────────────────┐ ┌─────────────────────────────────────────┐ : + │ _ │ │ _ │ : + └─────────────────────────┘ └─────────────────────────────────────────┘ : + │ : + │ InlineReturn : + ▼ : + ┌─────────────────────────────────────────┐ : + │ _ │ ◀┘ + └─────────────────────────────────────────┘ + │ + │ Ret (Some 0, main) + ▼ + ┌─────────────────────────────────────────┐ + │ _ │ + └─────────────────────────────────────────┘ diff --git a/tests/sv-comp/gen/dune b/tests/sv-comp/gen/dune new file mode 100644 index 0000000000..9df6b5100e --- /dev/null +++ b/tests/sv-comp/gen/dune @@ -0,0 +1,2 @@ +(executable + (name gen)) diff --git a/tests/sv-comp/gen/gen.ml b/tests/sv-comp/gen/gen.ml new file mode 100644 index 0000000000..a694c581a0 --- /dev/null +++ b/tests/sv-comp/gen/gen.ml @@ -0,0 +1,38 @@ +let generate_header () = + Printf.printf {|; Automatically generated, do not edit! +|} + +let generate_rule c_dir_file = + let dir = Filename.dirname c_dir_file in + let c_file = Filename.basename c_dir_file in + let file = Filename.chop_extension c_file in + Printf.printf {| +(subdir %s + (rule + (deps + (sandbox always) ; must sandbox to prevent arg.dot-s from parallel runs from interfering + (package goblint) ; depend on entire goblint package for svcomp21 conf + (:c %s) + (:prop %%{project_root}/tests/sv-comp/unreach-call-__VERIFIER_error.prp)) + (target %s.output) + (enabled_if %%{bin-available:graph-easy}) + (action + (progn + (ignore-outputs + (run goblint --conf svcomp.json --disable ana.autotune.enabled --set ana.specification %%{prop} %%{c} --enable witness.graphml.uncil --enable exp.arg.enabled --set exp.arg.dot.path arg.dot --set exp.arg.dot.node-label empty)) + (with-outputs-to %%{target} + (run graph-easy --as=boxart arg.dot))))) + + (rule + (alias runtest) + (enabled_if %%{bin-available:graph-easy}) + (action + (diff %s.expected %s.output)))) +|} dir c_file file file file + +let () = + generate_header (); + Sys.argv + |> Array.to_seq + |> Seq.drop 1 + |> Seq.iter generate_rule diff --git a/tests/unit/cdomains/floatDomainTest.ml b/tests/unit/cdomains/floatDomainTest.ml index 5f3cd4db47..c6cb18500a 100644 --- a/tests/unit/cdomains/floatDomainTest.ml +++ b/tests/unit/cdomains/floatDomainTest.ml @@ -14,12 +14,12 @@ struct let mul = Float_t.mul let div = Float_t.div - let pred x = Option.get (to_float (Float_t.pred (of_float Nearest x))) - let succ x = Option.get (to_float (Float_t.succ (of_float Nearest x))) + let pred x = to_float (Float_t.pred (of_float Nearest x)) + let succ x = to_float (Float_t.succ (of_float Nearest x)) - let fmax = Option.get (to_float Float_t.upper_bound) - let fmin = Option.get (to_float Float_t.lower_bound) - let fsmall = Option.get (to_float Float_t.smallest) + let fmax = to_float Float_t.upper_bound + let fmin = to_float Float_t.lower_bound + let fsmall = to_float Float_t.smallest let fi_zero = FI.of_const 0. let fi_one = FI.of_const 1. @@ -53,7 +53,7 @@ struct FI.top () + FI.top () = FI.top (); (FI.of_const fmin) + (FI.of_const fmax) = fi_zero; (FI.of_const fsmall) + (FI.of_const fsmall) = FI.of_const (fsmall +. fsmall); - let one_plus_fsmall = Option.get (to_float (Float_t.add Up (Float_t.of_float Up 1.) Float_t.smallest)) in + let one_plus_fsmall = to_float (Float_t.add Up (Float_t.of_float Up 1.) Float_t.smallest) in (FI.of_const fsmall) + (FI.of_const 1.) = FI.of_interval (1., one_plus_fsmall); (FI.of_interval (1., 2.)) + (FI.of_interval (2., 3.)) = FI.of_interval (3., 5.); (FI.of_interval (-. 2., 3.)) + (FI.of_interval (-. 100., 20.)) = FI.of_interval (-. 102., 23.); @@ -286,27 +286,27 @@ struct let test_FI_add = QCheck.Test.make ~name:"test_FI_add" (QCheck.pair QCheck.float QCheck.float) (fun (arg1, arg2) -> let result = FI.add (FI.of_const arg1) (FI.of_const arg2) in - (FI.leq (FI.of_const (Option.get (to_float (add Up (of_float Nearest arg1) (of_float Nearest arg2))))) result) && - (FI.leq (FI.of_const (Option.get (to_float (add Down (of_float Nearest arg1) (of_float Nearest arg2))))) result)) + (FI.leq (FI.of_const (to_float (add Up (of_float Nearest arg1) (of_float Nearest arg2)))) result) && + (FI.leq (FI.of_const (to_float (add Down (of_float Nearest arg1) (of_float Nearest arg2)))) result)) let test_FI_sub = QCheck.Test.make ~name:"test_FI_sub" (QCheck.pair QCheck.float QCheck.float) (fun (arg1, arg2) -> let result = FI.sub (FI.of_const arg1) (FI.of_const arg2) in - (FI.leq (FI.of_const (Option.get (to_float (sub Up (of_float Nearest arg1) (of_float Nearest arg2))))) result) && - (FI.leq (FI.of_const (Option.get (to_float (sub Down (of_float Nearest arg1) (of_float Nearest arg2))))) result)) + (FI.leq (FI.of_const (to_float (sub Up (of_float Nearest arg1) (of_float Nearest arg2)))) result) && + (FI.leq (FI.of_const (to_float (sub Down (of_float Nearest arg1) (of_float Nearest arg2))))) result) let test_FI_mul = QCheck.Test.make ~name:"test_FI_mul" (QCheck.pair QCheck.float QCheck.float) (fun (arg1, arg2) -> let result = FI.mul (FI.of_const arg1) (FI.of_const arg2) in - (FI.leq (FI.of_const (Option.get (to_float (mul Up (of_float Nearest arg1) (of_float Nearest arg2))))) result) && - (FI.leq (FI.of_const (Option.get (to_float (mul Down (of_float Nearest arg1) (of_float Nearest arg2))))) result)) + (FI.leq (FI.of_const (to_float (mul Up (of_float Nearest arg1) (of_float Nearest arg2)))) result) && + (FI.leq (FI.of_const (to_float (mul Down (of_float Nearest arg1) (of_float Nearest arg2)))) result)) let test_FI_div = QCheck.Test.make ~name:"test_FI_div" (QCheck.pair QCheck.float QCheck.float) (fun (arg1, arg2) -> let result = FI.div (FI.of_const arg1) (FI.of_const arg2) in - (FI.leq (FI.of_const (Option.get (to_float (div Up (of_float Nearest arg1) (of_float Nearest arg2))))) result) && - (FI.leq (FI.of_const (Option.get (to_float (div Down (of_float Nearest arg1) (of_float Nearest arg2))))) result)) + (FI.leq (FI.of_const (to_float (div Up (of_float Nearest arg1) (of_float Nearest arg2)))) result) && + (FI.leq (FI.of_const (to_float (div Down (of_float Nearest arg1) (of_float Nearest arg2)))) result)) let test () = [ diff --git a/tests/unit/cdomains/intDomainTest.ml b/tests/unit/cdomains/intDomainTest.ml index 288d8f9337..a60b7a6cb1 100644 --- a/tests/unit/cdomains/intDomainTest.ml +++ b/tests/unit/cdomains/intDomainTest.ml @@ -198,22 +198,61 @@ let test_ex_set _ = assert_equal (Some true) (T.to_bool tex10); assert_equal None (T.to_bool tex1) -module Interval = +module IntervalTest (I : IntDomain.SOverflow with type int_t = Z.t) = struct - module I = IntDomain.SOverflowUnlifter(IntDomain.Interval) + module I = IntDomain.SOverflowUnlifter (I) + let ik = Cil.IInt + let i65536 = I.of_interval ik (Z.zero, of_int 65536) + let i65537 = I.of_interval ik (Z.zero, of_int 65537) + let imax = I.of_interval ik (Z.zero, of_int 2147483647) + let imin = I.of_interval ik (of_int (-2147483648), Z.zero) let assert_equal x y = assert_equal ~cmp:I.equal ~printer:I.show x y let test_interval_rem _ = - let ik = Cil.IInt in assert_equal (I.of_int ik Z.zero) (I.rem ik (I.of_int ik Z.minus_one) (I.of_int ik Z.one)) + let test_interval_widen _ = + GobConfig.set_bool "ana.int.interval_threshold_widening" true; + GobConfig.set_string "ana.int.interval_threshold_widening_constants" "comparisons"; + assert_equal imax (I.widen ik i65536 i65537); + assert_equal imax (I.widen ik i65536 imax) + + let test_interval_narrow _ = + GobConfig.set_bool "ana.int.interval_threshold_widening" true; + GobConfig.set_string "ana.int.interval_threshold_widening_constants" "comparisons"; + let i_zero_one = I.of_interval ik (Z.zero, Z.one) in + let i_zero_five = I.of_interval ik (Z.zero, of_int 5) in + let to_widen = I.of_interval ik (Z.zero, Z.zero) in + (* this should widen to [0, x], where x is the next largest threshold above 5 or the maximal int*) + let widened = I.widen ik to_widen i_zero_five in + (* either way, narrowing from [0, x] to [0, 1] should be possible *) + let narrowed = I.narrow ik widened i_zero_one in + (* however, narrowing should not allow [0, x] to grow *) + let narrowed2 = I.narrow ik widened imax in + assert_equal i_zero_one narrowed; + assert_equal widened narrowed2; + + (* the same tests, but for lower bounds *) + let i_minus_one_zero = I.of_interval ik (Z.minus_one, Z.zero) in + let i_minus_five_zero = I.of_interval ik (of_int (-5), Z.zero) in + let widened = I.widen ik to_widen i_minus_five_zero in + let narrowed = I.narrow ik widened i_minus_one_zero in + let narrowed2 = I.narrow ik widened imin in + assert_equal i_minus_one_zero narrowed; + assert_equal widened narrowed2 + let test () = [ "test_interval_rem" >:: test_interval_rem; + "test_interval_widen" >:: test_interval_widen; + "test_interval_narrow" >:: test_interval_narrow; ] end +module Interval = IntervalTest (IntDomain.Interval) +module IntervalSet = IntervalTest (IntDomain.IntervalSet) + module Congruence = struct module C = IntDomain.Congruence @@ -291,6 +330,7 @@ let test () = "test_meet" >:: test_meet; "test_excl_list">:: test_ex_set; "interval" >::: Interval.test (); + "intervalSet" >::: IntervalSet.test (); "congruence" >::: Congruence.test (); "intDomTuple" >::: IntDomTuple.test (); ] diff --git a/tests/unit/dune b/tests/unit/dune index 5f0b909a77..07c87e7822 100644 --- a/tests/unit/dune +++ b/tests/unit/dune @@ -2,7 +2,7 @@ (test (name mainTest) - (libraries ounit2 qcheck-ounit goblint.std goblint.common goblint.lib goblint.constraint goblint.solver goblint.cdomain.value goblint.sites.dune goblint.build-info.dune) + (libraries ounit2 qcheck-ounit goblint.std goblint.common goblint.lib goblint.constraint goblint.solver goblint.cdomain.value) (preprocess (pps ppx_deriving.std ppx_deriving_hash ppx_deriving_yojson)) (flags :standard -linkall)) diff --git a/tests/unit/maindomaintest.ml b/tests/unit/maindomaintest.ml index 8e6a7f5a3a..4b379a252f 100644 --- a/tests/unit/maindomaintest.ml +++ b/tests/unit/maindomaintest.ml @@ -15,14 +15,11 @@ struct let show = show end include Printable.SimpleShow (P) + + let elems = ['a'; 'b'; 'c'; 'd'] end -module ArbitraryLattice = SetDomain.FiniteSet (PrintableChar) ( - struct - type t = char - let elems = ['a'; 'b'; 'c'; 'd'] - end - ) +module ArbitraryLattice = SetDomain.FiniteSet (PrintableChar) module HoareArbitrary = HoareDomain.Set_LiftTop (ArbitraryLattice) (struct let topname = "Top" end) module HoareArbitrary_NoTop = HoareDomain.Set (ArbitraryLattice) @@ -31,9 +28,6 @@ let domains: (module Lattice.S) list = [ (* (module IntDomainProperties.IntegerSet); (* TODO: top properties error *) *) (module IntDomain.Lifted); (* not abstraction of IntegerSet *) - (* TODO: move to intDomains if passing *) - (module IntDomain.Booleans); - (* TODO: fix *) (* (module IntDomain.Enums); *) (* (module IntDomain.IntDomTuple); *) diff --git a/tests/util/dune b/tests/util/dune index 6637217651..00ae40a0b9 100644 --- a/tests/util/dune +++ b/tests/util/dune @@ -1,11 +1,14 @@ -(executable - (name yamlWitnessStrip) +(executables + (names yamlWitnessStrip yamlWitnessStripDiff multilibConfigure) (libraries batteries.unthreaded + goblint-cil goblint_std goblint_lib - yaml - goblint.sites.dune - goblint.build-info.dune) + yaml) (flags :standard -open Goblint_std) (preprocess (pps ppx_deriving.std))) + +(rule + (target multilibAvailable) + (action (with-stdout-to %{target} (run ./multilibConfigure.exe)))) diff --git a/tests/util/multilibConfigure.ml b/tests/util/multilibConfigure.ml new file mode 100644 index 0000000000..96cf9a706a --- /dev/null +++ b/tests/util/multilibConfigure.ml @@ -0,0 +1,4 @@ +open GoblintCil + +let () = + Printf.printf "%B" (Option.is_some Machdep.gcc32 && Option.is_some Machdep.gcc64) diff --git a/tests/util/yamlWitnessStrip.ml b/tests/util/yamlWitnessStrip.ml index 8a5046d6ff..ece604f9a8 100644 --- a/tests/util/yamlWitnessStrip.ml +++ b/tests/util/yamlWitnessStrip.ml @@ -1,55 +1,5 @@ open Goblint_lib -open YamlWitnessType - -module StrippedEntry = -struct - type t = { - entry_type: EntryType.t; - } - [@@deriving ord] - - let strip_file_hashes {entry_type} = - let stripped_file_hash = "$FILE_HASH" in - let location_strip_file_hash location: Location.t = - {location with file_hash = stripped_file_hash} - in - let target_strip_file_hash target: Target.t = - {target with file_hash = stripped_file_hash} - in - let invariant_strip_file_hash ({invariant_type}: InvariantSet.Invariant.t): InvariantSet.Invariant.t = - let invariant_type: InvariantSet.InvariantType.t = - match invariant_type with - | LocationInvariant x -> - LocationInvariant {x with location = location_strip_file_hash x.location} - | LoopInvariant x -> - LoopInvariant {x with location = location_strip_file_hash x.location} - in - {invariant_type} - in - let entry_type: EntryType.t = - match entry_type with - | LocationInvariant x -> - LocationInvariant {x with location = location_strip_file_hash x.location} - | LoopInvariant x -> - LoopInvariant {x with location = location_strip_file_hash x.location} - | FlowInsensitiveInvariant x -> - FlowInsensitiveInvariant x (* no location to strip *) - | PreconditionLoopInvariant x -> - PreconditionLoopInvariant {x with location = location_strip_file_hash x.location} - | LoopInvariantCertificate x -> - LoopInvariantCertificate {x with target = target_strip_file_hash x.target} - | PreconditionLoopInvariantCertificate x -> - PreconditionLoopInvariantCertificate {x with target = target_strip_file_hash x.target} - | InvariantSet x -> - InvariantSet {content = List.map invariant_strip_file_hash x.content} - in - {entry_type} - - let to_yaml {entry_type} = - `O ([ - ("entry_type", `String (EntryType.entry_type entry_type)); - ] @ EntryType.to_yaml' entry_type) -end +open YamlWitnessStripCommon (* Use set for output, so order is deterministic regardless of Goblint. *) module StrippedEntrySet = Set.Make (StrippedEntry) @@ -69,18 +19,7 @@ let main () = stripped_entries ) StrippedEntrySet.empty yaml_entries in - let stripped_yaml_entries = - StrippedEntrySet.elements stripped_entries - |> List.map StrippedEntry.strip_file_hashes - |> List.rev_map StrippedEntry.to_yaml - in - let stripped_yaml = `A stripped_yaml_entries in - (* to_file/to_string uses a fixed-size buffer... *) - let stripped_yaml_str = match GobYaml.to_string' stripped_yaml with - | Ok text -> text - | Error (`Msg m) -> failwith ("Yaml.to_string: " ^ m) - in - Batteries.output_string Batteries.stdout stripped_yaml_str + print_stripped_entries stripped_entries let () = main () diff --git a/tests/util/yamlWitnessStripCommon.ml b/tests/util/yamlWitnessStripCommon.ml new file mode 100644 index 0000000000..39bc231d72 --- /dev/null +++ b/tests/util/yamlWitnessStripCommon.ml @@ -0,0 +1,112 @@ +open Goblint_lib +open YamlWitnessType + +module StrippedEntry = +struct + type t = { + entry_type: EntryType.t; + } + [@@deriving ord] + + let strip_file_hashes {entry_type} = + let stripped_file_hash = "$FILE_HASH" in + let location_strip_file_hash (location: Location.t): Location.t = + let file_hash = + match location.file_hash with + | Some _ -> Some stripped_file_hash (* TODO: or just map to None always? *) + | None -> None + in + {location with file_hash} + in + let target_strip_file_hash target: Target.t = + {target with file_hash = stripped_file_hash} + in + let invariant_strip_file_hash ({invariant_type}: InvariantSet.Invariant.t): InvariantSet.Invariant.t = + let invariant_type: InvariantSet.InvariantType.t = + match invariant_type with + | LocationInvariant x -> + LocationInvariant {x with location = location_strip_file_hash x.location} + | LoopInvariant x -> + LoopInvariant {x with location = location_strip_file_hash x.location} + in + {invariant_type} + in + let waypoint_strip_file_hash ({waypoint_type}: ViolationSequence.Waypoint.t): ViolationSequence.Waypoint.t = + let waypoint_type: ViolationSequence.WaypointType.t = + match waypoint_type with + | Assumption x -> + Assumption {x with location = location_strip_file_hash x.location} + | Target x -> + Target {x with location = location_strip_file_hash x.location} + | FunctionEnter x -> + FunctionEnter {x with location = location_strip_file_hash x.location} + | FunctionReturn x -> + FunctionReturn {x with location = location_strip_file_hash x.location} + | Branching x -> + Branching {x with location = location_strip_file_hash x.location} + in + {waypoint_type} + in + let segment_strip_file_hash ({segment}: ViolationSequence.Segment.t): ViolationSequence.Segment.t = + {segment = List.map waypoint_strip_file_hash segment} + in + let ghost_location_update_strip_file_hash (x: GhostInstrumentation.LocationUpdate.t): GhostInstrumentation.LocationUpdate.t = + { + location = location_strip_file_hash x.location; + updates = List.sort GhostInstrumentation.Update.compare x.updates + } + in + let entry_type: EntryType.t = + match entry_type with + | LocationInvariant x -> + LocationInvariant {x with location = location_strip_file_hash x.location} + | LoopInvariant x -> + LoopInvariant {x with location = location_strip_file_hash x.location} + | FlowInsensitiveInvariant x -> + FlowInsensitiveInvariant x (* no location to strip *) + | PreconditionLoopInvariant x -> + PreconditionLoopInvariant {x with location = location_strip_file_hash x.location} + | LoopInvariantCertificate x -> + LoopInvariantCertificate {x with target = target_strip_file_hash x.target} + | PreconditionLoopInvariantCertificate x -> + PreconditionLoopInvariantCertificate {x with target = target_strip_file_hash x.target} + | InvariantSet x -> + InvariantSet {content = List.sort InvariantSet.Invariant.compare (List.map invariant_strip_file_hash x.content)} (* Sort, so order is deterministic regardless of Goblint. *) + | ViolationSequence x -> + ViolationSequence {content = List.map segment_strip_file_hash x.content} + | GhostInstrumentation x -> + GhostInstrumentation { (* Sort, so order is deterministic regardless of Goblint. *) + ghost_variables = List.sort GhostInstrumentation.Variable.compare x.ghost_variables; + ghost_updates = List.sort GhostInstrumentation.LocationUpdate.compare (List.map ghost_location_update_strip_file_hash x.ghost_updates); + } + in + {entry_type} + + let to_yaml {entry_type} = + `O ([ + ("entry_type", `String (EntryType.entry_type entry_type)); + ] @ EntryType.to_yaml' entry_type) + + let of_yaml y = + let open GobYaml in + let+ entry_type = y |> EntryType.of_yaml in + {entry_type} +end + +(* Use set for output, so order is deterministic regardless of Goblint. *) +module StrippedEntrySet = Set.Make (StrippedEntry) + +let print_stripped_entries stripped_entries = + let stripped_yaml_entries = + StrippedEntrySet.elements stripped_entries + |> List.map StrippedEntry.strip_file_hashes + |> List.rev_map StrippedEntry.to_yaml + in + + let stripped_yaml = `A stripped_yaml_entries in + (* to_file/to_string uses a fixed-size buffer... *) + let stripped_yaml_str = match GobYaml.to_string' stripped_yaml with + | Ok text -> text + | Error (`Msg m) -> failwith ("Yaml.to_string: " ^ m) + in + Batteries.output_string Batteries.stdout stripped_yaml_str diff --git a/tests/util/yamlWitnessStripDiff.ml b/tests/util/yamlWitnessStripDiff.ml new file mode 100644 index 0000000000..8a993382b3 --- /dev/null +++ b/tests/util/yamlWitnessStripDiff.ml @@ -0,0 +1,29 @@ +open YamlWitnessStripCommon + +let read_stripped_entries path = + let yaml_str = BatFile.with_file_in path BatIO.read_all in + let yaml = Yaml.of_string_exn yaml_str in + let yaml_entries = yaml |> GobYaml.list |> BatResult.get_ok in + + List.fold_left (fun stripped_entries yaml_entry -> + match StrippedEntry.of_yaml yaml_entry with + | Ok stripped_entry -> + StrippedEntrySet.add stripped_entry stripped_entries + | Error (`Msg e) -> + Format.eprintf "couldn't parse entry: %s" e; + stripped_entries + ) StrippedEntrySet.empty yaml_entries + +let main () = + let left_stripped_entries = read_stripped_entries Sys.argv.(1) in + let right_stripped_entries = read_stripped_entries Sys.argv.(2) in + let left_only_stripped_entries = StrippedEntrySet.diff left_stripped_entries right_stripped_entries in + let right_only_stripped_entries = StrippedEntrySet.diff right_stripped_entries left_stripped_entries in + + print_endline "# Left-only entries:"; + print_stripped_entries left_only_stripped_entries; + print_endline "---"; + print_endline "# Right-only entries:"; + print_stripped_entries right_only_stripped_entries + +let () = main ()