diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 7472cbc820..0208af7c7a 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -28,7 +28,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up OCaml ${{ matrix.ocaml-compiler }} env: @@ -65,6 +65,9 @@ jobs: - name: Test apron regression (Mukherjee et. al SAS '17 paper') # skipped by default but CI has apron, so explicitly test group (which ignores skipping -- it's now a feature!) run: ruby scripts/update_suite.rb group apron-mukherjee -s + - name: Test apron termination regression # skipped by default but CI has apron, so explicitly test group (which ignores skipping -- it's now a feature!) + run: ruby scripts/update_suite.rb group termination -s + - name: Test regression cram run: opam exec -- dune runtest tests/regression diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 02c5f07d90..36568e6cb2 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -35,13 +35,13 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 # needed for GitHub Actions Cache in build-push-action + uses: docker/setup-buildx-action@v3 # needed for GitHub Actions Cache in build-push-action - name: Log in to the Container registry - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} @@ -49,7 +49,7 @@ jobs: - name: Extract metadata (tags, labels) for Docker id: meta - uses: docker/metadata-action@v4 + uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} tags: | @@ -59,7 +59,7 @@ jobs: - name: Build Docker image id: build - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 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@v4 + uses: docker/build-push-action@v5 with: context: . push: true diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index cd0414d6fe..1d73e037f4 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -30,7 +30,10 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 + + - name: Check for undocumented modules + run: python scripts/goblint-lib-modules.py - name: Set up OCaml ${{ matrix.ocaml-compiler }} env: @@ -43,7 +46,7 @@ jobs: - name: Setup Pages id: pages - uses: actions/configure-pages@v3 + uses: actions/configure-pages@v4 - name: Install dependencies run: opam install . --deps-only --locked --with-doc @@ -65,4 +68,4 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v2 + uses: actions/deploy-pages@v3 diff --git a/.github/workflows/indentation.yml b/.github/workflows/indentation.yml index 14db288d60..e22e674301 100644 --- a/.github/workflows/indentation.yml +++ b/.github/workflows/indentation.yml @@ -20,7 +20,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 diff --git a/.github/workflows/locked.yml b/.github/workflows/locked.yml index 358682a2f3..8604e7f52c 100644 --- a/.github/workflows/locked.yml +++ b/.github/workflows/locked.yml @@ -30,7 +30,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up OCaml ${{ matrix.ocaml-compiler }} env: @@ -64,6 +64,9 @@ jobs: - name: Test apron regression (Mukherjee et. al SAS '17 paper') # skipped by default but CI has apron, so explicitly test group (which ignores skipping -- it's now a feature!) run: ruby scripts/update_suite.rb group apron-mukherjee -s + - name: Test apron termination regression # skipped by default but CI has apron, so explicitly test group (which ignores skipping -- it's now a feature!) + run: ruby scripts/update_suite.rb group termination -s + - name: Test regression cram run: opam exec -- dune runtest tests/regression @@ -101,7 +104,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up OCaml ${{ matrix.ocaml-compiler }} env: @@ -141,7 +144,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up OCaml ${{ matrix.ocaml-compiler }} env: @@ -153,7 +156,7 @@ jobs: ocaml-compiler: ${{ matrix.ocaml-compiler }} - name: Set up Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} diff --git a/.github/workflows/metadata.yml b/.github/workflows/metadata.yml index da20c6b675..3a48d52fa0 100644 --- a/.github/workflows/metadata.yml +++ b/.github/workflows/metadata.yml @@ -19,7 +19,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Validate CITATION.cff uses: docker://citationcff/cffconvert:latest @@ -27,6 +27,9 @@ jobs: args: --validate zenodo-validate: + # Zenodo schema URL is dead + if: ${{ false }} + strategy: matrix: node-version: @@ -36,10 +39,10 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} diff --git a/.github/workflows/options.yml b/.github/workflows/options.yml index b8522c03bb..7ef8b6929e 100644 --- a/.github/workflows/options.yml +++ b/.github/workflows/options.yml @@ -15,10 +15,10 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} @@ -26,10 +26,10 @@ jobs: run: npm install -g ajv-cli - name: Migrate schema # https://github.com/ajv-validator/ajv-cli/issues/199 - run: ajv migrate -s src/util/options.schema.json + run: ajv migrate -s src/config/options.schema.json - name: Validate conf - run: ajv validate -s src/util/options.schema.json -d "conf/**/*.json" + run: ajv validate -s src/config/options.schema.json -d "conf/**/*.json" - name: Validate incremental tests - run: ajv validate -s src/util/options.schema.json -d "tests/incremental/*/*.json" + run: ajv validate -s src/config/options.schema.json -d "tests/incremental/*/*.json" diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml index acd696e597..bd2dfd285c 100644 --- a/.github/workflows/semgrep.yml +++ b/.github/workflows/semgrep.yml @@ -16,10 +16,10 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Run semgrep - run: semgrep scan --sarif --output=semgrep.sarif + run: semgrep scan --config .semgrep/ --sarif > semgrep.sarif - name: Upload SARIF file to GitHub Advanced Security Dashboard uses: github/codeql-action/upload-sarif@v2 diff --git a/.github/workflows/unlocked.yml b/.github/workflows/unlocked.yml index 2bec6b72fb..57fa0cb6b5 100644 --- a/.github/workflows/unlocked.yml +++ b/.github/workflows/unlocked.yml @@ -18,6 +18,7 @@ jobs: - ubuntu-latest - macos-latest ocaml-compiler: + - 5.0.x - ocaml-variants.4.14.0+options,ocaml-option-flambda - 4.14.x - 4.13.x @@ -45,7 +46,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up OCaml ${{ matrix.ocaml-compiler }} uses: ocaml/setup-ocaml@v2 @@ -91,6 +92,10 @@ jobs: if: ${{ matrix.apron }} run: ruby scripts/update_suite.rb group apron-mukherjee -s + - name: Test apron termination regression # skipped by default but CI has apron, so explicitly test group (which ignores skipping -- it's now a feature!) + if: ${{ matrix.apron }} + run: ruby scripts/update_suite.rb group termination -s + - name: Test regression cram run: opam exec -- dune runtest tests/regression @@ -131,7 +136,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up OCaml ${{ matrix.ocaml-compiler }} uses: ocaml/setup-ocaml@v2 @@ -155,7 +160,8 @@ jobs: - name: Downgrade dependencies # must specify ocaml-base-compiler again to prevent it from being downgraded - run: opam install $(opam exec -- opam-0install --prefer-oldest goblint ocaml-variants.4.14.0+options ocaml-option-flambda) + # 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.4) - name: Build run: ./make.sh nat @@ -208,14 +214,14 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 # needed for GitHub Actions Cache in build-push-action + uses: docker/setup-buildx-action@v3 # needed for GitHub Actions Cache in build-push-action - name: Build dev Docker image id: build - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: context: . target: dev @@ -246,7 +252,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up OCaml ${{ matrix.ocaml-compiler }} uses: ocaml/setup-ocaml@v2 diff --git a/.gitignore b/.gitignore index 75bd23d36b..faf1513653 100644 --- a/.gitignore +++ b/.gitignore @@ -29,7 +29,6 @@ linux-headers .goblint*/ goblint_temp_*/ -src/spec/graph .vagrant g2html.jar diff --git a/.mailmap b/.mailmap index 9153d55765..9aa2d0cc02 100644 --- a/.mailmap +++ b/.mailmap @@ -23,6 +23,7 @@ Kerem Çakırer Sarah Tilscher <66023521+stilscher@users.noreply.github.com> Karoliine Holter <44437975+karoliineh@users.noreply.github.com> + Elias Brandstetter <15275491+superbr4in@users.noreply.github.com> wherekonshade <80516286+Wherekonshade@users.noreply.github.com> @@ -37,3 +38,6 @@ Mireia Cano Pujol Felix Krayer Felix Krayer <91671586+FelixKrayer@users.noreply.github.com> Manuel Pietsch +Tim Ortel <100865202+TimOrtel@users.noreply.github.com> +Tomáš Dacík + <43824605+TDacik@users.noreply.github.com> diff --git a/.readthedocs.yaml b/.readthedocs.yaml index c9b41df49d..22f9c86121 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -20,4 +20,4 @@ build: - pip install json-schema-for-humans post_build: - mkdir _readthedocs/html/jsfh/ - - generate-schema-doc --config-file jsfh.yml src/util/options.schema.json _readthedocs/html/jsfh/ + - generate-schema-doc --config-file jsfh.yml src/config/options.schema.json _readthedocs/html/jsfh/ diff --git a/.semgrep/tracing.yml b/.semgrep/tracing.yml index 4892066c76..061b3efa0d 100644 --- a/.semgrep/tracing.yml +++ b/.semgrep/tracing.yml @@ -9,6 +9,7 @@ rules: - pattern: Messages.traceu - pattern: Messages.traceli - pattern-not-inside: if Messages.tracing then ... + - pattern-not-inside: if Messages.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 5557622f9e..22705c2d9c 100644 --- a/.zenodo.json +++ b/.zenodo.json @@ -10,15 +10,18 @@ }, { "name": "Schwarz, Michael", - "affiliation": "Technische Universität München" + "affiliation": "Technische Universität München", + "orcid": "0000-0002-9828-0308" }, { "name": "Erhard, Julian", - "affiliation": "Technische Universität München" + "affiliation": "Technische Universität München", + "orcid": "0000-0002-1729-3925" }, { "name": "Tilscher, Sarah", - "affiliation": "Technische Universität München" + "affiliation": "Technische Universität München", + "orcid": "0009-0009-9644-7475" }, { "name": "Vogler, Ralf", @@ -30,14 +33,16 @@ }, { "name": "Vojdani, Vesal", - "affiliation": "University of Tartu" + "affiliation": "University of Tartu", + "orcid": "0000-0003-4336-7980" } ], "contributors": [ { "name": "Seidl, Helmut", "type": "ProjectLeader", - "affiliation": "Technische Universität München" + "affiliation": "Technische Universität München", + "orcid": "0000-0002-2135-1593" }, { "name": "Schwarz, Martin D.", diff --git a/CHANGELOG.md b/CHANGELOG.md index a9531a5766..d285480259 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,39 @@ +## v2.3.0 +Functionally equivalent to Goblint in SV-COMP 2024. + +* Add termination analysis for loops (#1093). +* Add memory out-of-bounds analysis (#1094, #1197). +* Add memory leak analysis (#1127, #1241, #1246). +* Add SV-COMP `termination`, `valid-memsafety` and `valid-memcleanup` properties support (#1220, #1228, #1201, #1199, #1259, #1262). +* Add YAML witness version 2.0 support (#1238, #1240, #1217, #1226, #1225, #1248). +* Add final warnings about unsound results (#1190, #1191). +* Add many library function specifications (#1167, #1174, #1203, #1205, #1212, #1220, #1239, #1242, #1244, #1254, #1269). +* Adapt automatic configuration tuning (#912, #921, #987, #1168, #1214, #1234). + +## v2.2.1 +* Bump batteries lower bound to 3.5.0. +* Fix flaky dead code elimination transformation test. + +## v2.2.0 +* Add `setjmp`/`longjmp` analysis (#887, #970, #1015, #1019). +* Refactor race analysis to lazy distribution (#1084, #1089, #1136, #1016). +* Add thread-unsafe library function call analysis (#723, #1082). +* Add mutex type analysis and mutex API analysis (#800, #839, #1073). +* Add interval set domain and string literals domain (#901, #966, #994, #1048). +* Add affine equalities analysis (#592). +* Add use-after-free analysis (#1050, #1114). +* Add dead code elimination transformation (#850, #979). +* Add taint analysis for partial contexts (#553, #952). +* Add YAML witness validation via unassume (#796, #977, #1044, #1045, #1124). +* Add incremental analysis rename detection (#774, #777). +* Fix address sets unsoundness (#822, #967, #564, #1032, #998, #1031). +* Fix thread escape analysis unsoundness (#939, #984, #1074, #1078). +* Fix many incremental analysis issues (#627, #836, #835, #841, #932, #678, #942, #949, #950, #957, #955, #954, #960, #959, #1004, #558, #1010, #1091). +* Fix server mode for abstract debugging (#983, #990, #997, #1000, #1001, #1013, #1018, #1017, #1026, #1027). +* Add documentation for configuration JSON schema and OCaml API (#999, #1054, #1055, #1053). +* Add many library function specifications (#962, #996, #1028, #1079, #1121, #1135, #1138). +* Add OCaml 5.0 support (#1003, #945, #1162). + ## v2.1.0 Functionally equivalent to Goblint in SV-COMP 2023. diff --git a/CITATION.cff b/CITATION.cff index 7a2dcf188d..25d46cf762 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -12,12 +12,15 @@ authors: # same authors as in .zenodo.json and dune-project - given-names: Michael family-names: Schwarz affiliation: "Technische Universität München" + orcid: "https://orcid.org/0000-0002-9828-0308" - given-names: Julian family-names: Erhard affiliation: "Technische Universität München" + orcid: "https://orcid.org/0000-0002-1729-3925" - given-names: Sarah family-names: Tilscher affiliation: "Technische Universität München" + orcid: "https://orcid.org/0009-0009-9644-7475" - given-names: Ralf family-names: Vogler affiliation: "Technische Universität München" @@ -27,6 +30,7 @@ authors: # same authors as in .zenodo.json and dune-project - given-names: Vesal family-names: Vojdani affiliation: "University of Tartu" + orcid: "https://orcid.org/0000-0003-4336-7980" license: MIT repository-code: "https://github.com/goblint/analyzer" diff --git a/README.md b/README.md index b03b7bbe36..4d97baa842 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ Documentation can be browsed on [Read the Docs](https://goblint.readthedocs.io/e ## Installing Both for using an up-to-date version of Goblint or developing it, the best way is to install from source by cloning this repository. +For benchmarking Goblint, please follow the [Benchmarking guide on Read the Docs](https://goblint.readthedocs.io/en/latest/user-guide/benchmarking/). ### Linux 1. Install [opam](https://opam.ocaml.org/doc/Install.html). diff --git a/conf/bench-yaml-validate.json b/conf/bench-yaml-validate.json index ca830be08a..7b18371bd1 100644 --- a/conf/bench-yaml-validate.json +++ b/conf/bench-yaml-validate.json @@ -52,14 +52,6 @@ "tokens": true } }, - "witness": { - "enabled": false, - "invariant": { - "loop-head": true, - "after-lock": true, - "other": false - } - }, "sem": { "unknown_function": { "invalidate": { diff --git a/conf/bench-yaml.json b/conf/bench-yaml.json index a24035fc9b..fd97b2c08c 100644 --- a/conf/bench-yaml.json +++ b/conf/bench-yaml.json @@ -48,20 +48,6 @@ ] } }, - "witness": { - "enabled": false, - "yaml": { - "enabled": true - }, - "invariant": { - "exact": false, - "exclude-vars": [ - "tmp\\(___[0-9]+\\)?", - "cond", - "RETURN" - ] - } - }, "sem": { "unknown_function": { "invalidate": { diff --git a/conf/examples/very-precise.json b/conf/examples/very-precise.json index 84cbf53585..2197335eaf 100644 --- a/conf/examples/very-precise.json +++ b/conf/examples/very-precise.json @@ -61,7 +61,9 @@ "structs" : { "domain" : "combined-sk" }, - "limit-string-addresses": false + "strings": { + "domain": "disjoint" + } } }, "exp": { diff --git a/conf/ldv-races.json b/conf/ldv-races.json index 01c60efc8d..8db800d74c 100644 --- a/conf/ldv-races.json +++ b/conf/ldv-races.json @@ -53,8 +53,11 @@ } }, "witness": { - "id": "enumerate", - "unknown": false + "graphml": { + "enabled": true, + "id": "enumerate", + "unknown": false + } }, "solver": "td3", "sem": { diff --git a/conf/svcomp-yaml-validate.json b/conf/svcomp-yaml-validate.json index 05bb1ebcc2..1934a56932 100644 --- a/conf/svcomp-yaml-validate.json +++ b/conf/svcomp-yaml-validate.json @@ -12,6 +12,10 @@ "float": { "interval": true }, + "apron": { + "domain": "polyhedra", + "strengthening": true + }, "activated": [ "base", "threadid", @@ -31,6 +35,7 @@ "region", "thread", "threadJoins", + "apron", "unassume" ], "context": { @@ -74,14 +79,6 @@ "exp": { "region-offsets": true }, - "witness": { - "enabled": false, - "invariant": { - "loop-head": true, - "after-lock": false, - "other": false - } - }, "solver": "td3", "sem": { "unknown_function": { diff --git a/conf/svcomp-yaml.json b/conf/svcomp-yaml.json index 6e3d0e4767..10a977ff47 100644 --- a/conf/svcomp-yaml.json +++ b/conf/svcomp-yaml.json @@ -12,6 +12,10 @@ "float": { "interval": true }, + "apron": { + "domain": "polyhedra", + "strengthening": true + }, "activated": [ "base", "threadid", @@ -30,7 +34,8 @@ "symb_locks", "region", "thread", - "threadJoins" + "threadJoins", + "apron" ], "context": { "widen": false @@ -71,11 +76,16 @@ "region-offsets": true }, "witness": { - "enabled": false, + "graphml": { + "enabled": false + }, "yaml": { "enabled": true }, "invariant": { + "loop-head": true, + "other": false, + "accessed": false, "exact": false, "exclude-vars": [ "tmp\\(___[0-9]+\\)?", diff --git a/conf/svcomp.json b/conf/svcomp.json index 913d43784b..7e30554ceb 100644 --- a/conf/svcomp.json +++ b/conf/svcomp.json @@ -32,6 +32,15 @@ "thread", "threadJoins" ], + "path_sens": [ + "mutex", + "malloc_null", + "uninit", + "expsplit", + "activeSetjmp", + "memLeak", + "threadflag" + ], "context": { "widen": false }, @@ -52,7 +61,8 @@ "ldv_xmalloc", "ldv_xzalloc", - "ldv_calloc" + "ldv_calloc", + "ldv_kzalloc" ] }, "base": { @@ -60,6 +70,10 @@ "domain": "partitioned" } }, + "race": { + "free": false, + "call": false + }, "autotune": { "enabled": true, "activated": [ @@ -70,7 +84,10 @@ "congruence", "octagon", "wideningThresholds", - "loopUnrollHeuristic" + "loopUnrollHeuristic", + "memsafetySpecification", + "termination", + "tmpSpecialAnalysis" ] } }, @@ -90,8 +107,38 @@ } }, "witness": { - "id": "enumerate", - "unknown": false + "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, + "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]+" + ] + } }, "pre": { "enabled": false diff --git a/conf/svcomp21.json b/conf/svcomp21.json index a19bfdb9d0..2e36e61d0c 100644 --- a/conf/svcomp21.json +++ b/conf/svcomp21.json @@ -64,6 +64,9 @@ } }, "witness": { - "id": "enumerate" + "graphml": { + "enabled": true, + "id": "enumerate" + } } } diff --git a/conf/svcomp22-intervals-novareq-affeq-apron.json b/conf/svcomp22-intervals-novareq-affeq-apron.json index 7f72f5d0d8..f7f7662b6a 100644 --- a/conf/svcomp22-intervals-novareq-affeq-apron.json +++ b/conf/svcomp22-intervals-novareq-affeq-apron.json @@ -68,7 +68,10 @@ } }, "witness": { - "id": "enumerate", - "unknown": false + "graphml": { + "enabled": true, + "id": "enumerate", + "unknown": false + } } } \ No newline at end of file diff --git a/conf/svcomp22-intervals-novareq-affeq-native.json b/conf/svcomp22-intervals-novareq-affeq-native.json index 3ae1b19788..00db00f30f 100644 --- a/conf/svcomp22-intervals-novareq-affeq-native.json +++ b/conf/svcomp22-intervals-novareq-affeq-native.json @@ -65,7 +65,10 @@ } }, "witness": { - "id": "enumerate", - "unknown": false + "graphml": { + "enabled": true, + "id": "enumerate", + "unknown": false + } } } diff --git a/conf/svcomp22-intervals-novareq-octagon-apron.json b/conf/svcomp22-intervals-novareq-octagon-apron.json index 3bf149800e..a0c09e8937 100644 --- a/conf/svcomp22-intervals-novareq-octagon-apron.json +++ b/conf/svcomp22-intervals-novareq-octagon-apron.json @@ -68,7 +68,10 @@ } }, "witness": { - "id": "enumerate", - "unknown": false + "graphml": { + "enabled": true, + "id": "enumerate", + "unknown": false + } } } diff --git a/conf/svcomp22-intervals-novareq-polyhedra-apron.json b/conf/svcomp22-intervals-novareq-polyhedra-apron.json index e4e513415a..3a478bf687 100644 --- a/conf/svcomp22-intervals-novareq-polyhedra-apron.json +++ b/conf/svcomp22-intervals-novareq-polyhedra-apron.json @@ -68,7 +68,10 @@ } }, "witness": { - "id": "enumerate", - "unknown": false + "graphml": { + "enabled": true, + "id": "enumerate", + "unknown": false + } } } diff --git a/conf/svcomp22.json b/conf/svcomp22.json index 85ea693375..316c3c5534 100644 --- a/conf/svcomp22.json +++ b/conf/svcomp22.json @@ -67,7 +67,10 @@ } }, "witness": { - "id": "enumerate", - "unknown": false + "graphml": { + "enabled": true, + "id": "enumerate", + "unknown": false + } } } diff --git a/conf/svcomp23.json b/conf/svcomp23.json index 56474fbe2b..af584f1593 100644 --- a/conf/svcomp23.json +++ b/conf/svcomp23.json @@ -90,7 +90,10 @@ } }, "witness": { - "id": "enumerate", - "unknown": false + "graphml": { + "enabled": true, + "id": "enumerate", + "unknown": false + } } } diff --git a/conf/svcomp24-validate.json b/conf/svcomp24-validate.json new file mode 100644 index 0000000000..7832ffa6af --- /dev/null +++ b/conf/svcomp24-validate.json @@ -0,0 +1,140 @@ +{ + "ana": { + "sv-comp": { + "enabled": true, + "functions": true + }, + "int": { + "def_exc": true, + "enums": false, + "interval": true + }, + "float": { + "interval": true + }, + "activated": [ + "base", + "threadid", + "threadflag", + "threadreturn", + "mallocWrapper", + "mutexEvents", + "mutex", + "access", + "race", + "escape", + "expRelation", + "mhp", + "assert", + "var_eq", + "symb_locks", + "region", + "thread", + "threadJoins", + "unassume" + ], + "path_sens": [ + "mutex", + "malloc_null", + "uninit", + "expsplit", + "activeSetjmp", + "memLeak", + "threadflag" + ], + "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" + } + }, + "race": { + "free": false, + "call": false + }, + "autotune": { + "enabled": true, + "activated": [ + "singleThreaded", + "mallocWrappers", + "noRecursiveIntervals", + "enums", + "congruence", + "octagon", + "wideningThresholds", + "loopUnrollHeuristic", + "memsafetySpecification", + "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" + ], + "invariant-types": [ + "location_invariant", + "loop_invariant" + ] + }, + "invariant": { + "loop-head": true, + "after-lock": true, + "other": true + } + }, + "pre": { + "enabled": false + } +} diff --git a/conf/svcomp24.json b/conf/svcomp24.json new file mode 100644 index 0000000000..7e30554ceb --- /dev/null +++ b/conf/svcomp24.json @@ -0,0 +1,146 @@ +{ + "ana": { + "sv-comp": { + "enabled": true, + "functions": true + }, + "int": { + "def_exc": true, + "enums": false, + "interval": true + }, + "float": { + "interval": true + }, + "activated": [ + "base", + "threadid", + "threadflag", + "threadreturn", + "mallocWrapper", + "mutexEvents", + "mutex", + "access", + "race", + "escape", + "expRelation", + "mhp", + "assert", + "var_eq", + "symb_locks", + "region", + "thread", + "threadJoins" + ], + "path_sens": [ + "mutex", + "malloc_null", + "uninit", + "expsplit", + "activeSetjmp", + "memLeak", + "threadflag" + ], + "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" + } + }, + "race": { + "free": false, + "call": false + }, + "autotune": { + "enabled": true, + "activated": [ + "singleThreaded", + "mallocWrappers", + "noRecursiveIntervals", + "enums", + "congruence", + "octagon", + "wideningThresholds", + "loopUnrollHeuristic", + "memsafetySpecification", + "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, + "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]+" + ] + } + }, + "pre": { + "enabled": false + } +} diff --git a/docs/developer-guide/debugging.md b/docs/developer-guide/debugging.md index 7875a9b01e..5c1db12227 100644 --- a/docs/developer-guide/debugging.md +++ b/docs/developer-guide/debugging.md @@ -85,7 +85,7 @@ The configuration file has to be named `launch.json` and must reside in the `./. "configurations": [ { "name": "Goblint", - "type": "ocamlearlybird", + "type": "ocaml.earlybird", "request": "launch", "program": "${workspaceFolder}/goblint.byte", "arguments": [ @@ -97,7 +97,8 @@ The configuration file has to be named `launch.json` and must reside in the `./. ] } ``` -Note that the individual arguments to Goblint should be passed here as separate strings that do not contain spaces. +Note that the individual arguments to Goblint should be passed here as separate strings that do not contain spaces. Finally, to enable breakpoints uncomment `(map_workspace_root false)` in the dune-project file. + ### Running Goblint in the VS Code Debugger diff --git a/docs/developer-guide/firstanalysis.md b/docs/developer-guide/firstanalysis.md index 0923e792cd..4eb35e7f5d 100644 --- a/docs/developer-guide/firstanalysis.md +++ b/docs/developer-guide/firstanalysis.md @@ -67,7 +67,7 @@ The key part now is to define transfer functions for assignment. We only handle There is no need to implement the transfer functions for branching for this example; it only relies on lattice join operations to correctly take both paths into account. The assignment relies on the function `eval`, which is almost there. It just needs you to fix the evaluation of constants! Unless you jumped straight to this line, it should not be too complicated to fix this. -With this in place, we should have sufficient information to tell Goblint that the assertion does hold. +With this in place, we should have sufficient information to tell Goblint that the assertion does hold (run `make` to compile the updated analysis in Goblint). For more information on the signature of the individual transfer functions, please check out `module type Spec` documentation in [`src/framework/analyses.ml`](https://github.com/goblint/analyzer/blob/master/src/framework/analyses.ml). diff --git a/docs/developer-guide/messaging.md b/docs/developer-guide/messaging.md index 28f24bc49c..0028d51f87 100644 --- a/docs/developer-guide/messaging.md +++ b/docs/developer-guide/messaging.md @@ -47,16 +47,3 @@ The `~loc` argument is optional and defaults to the current location, but allows The `_noloc` suffixed functions allow general messages without any location (not even current). By convention, may-warnings (the usual case) should use warning severity and must-warnings should use error severity. - -### Spec analysis - -Warnings inside `.spec` files are converted to warnings. -They parsed from string warnings: the first space-delimited substring determines the category and the rest determines the text. - -For example: -``` -w1 "behavior.undefined.use_after_free" -w2 "integer.overflow" -w3 "unknown my message" -w4 "integer.overflow some text describing the warning" -``` diff --git a/docs/developer-guide/releasing.md b/docs/developer-guide/releasing.md index f6bfbb459e..fcf69ea533 100644 --- a/docs/developer-guide/releasing.md +++ b/docs/developer-guide/releasing.md @@ -37,21 +37,27 @@ 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. Pin package from distribution archive: `opam pin add --no-action .`. - 6. Install depexts: `opam depext --with-test goblint`. - 7. Install and test package: `opam install --with-test goblint`. - 8. Activate opam environment: `eval $(opam env)`. - 9. Check version: `goblint --version`. - 10. Check that analysis works: `goblint -v tests/regression/04-mutex/01-simple_rc.c`. - 11. Exit Docker container. + 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. -12. Create a GitHub release with the git tag: `DUNE_RELEASE_DELEGATE=github-dune-release-delegate dune-release publish distrib`. +12. Temporarily enable Zenodo GitHub webhook. + + This is because we only want numbered version releases to automatically add a new version to our Zenodo artifact. + Other tags (like SV-COMP or paper artifacts) have manually created Zenodo artifacts anyway and thus shouldn't add new versions to the main Zenodo artifact. + +13. Create a GitHub release with the git tag: `DUNE_RELEASE_DELEGATE=github-dune-release-delegate dune-release publish distrib`. Explicitly specify `distrib` because we don't want to publish OCaml API docs. Environment variable workaround for the package having a Read the Docs `doc` URL (see ). -13. Create an opam package: `dune-release opam pkg`. -14. Submit the opam package to opam-repository: `dune-release opam submit`. +14. Re-disable Zenodo GitHub webhook. + +15. Create an opam package: `dune-release opam pkg`. +16. Submit the opam package to opam-repository: `dune-release opam submit`. +17. Revert temporary removal of opam pins. ## SV-COMP @@ -90,29 +96,24 @@ This ensures that the environment and the archive have all the correct system libraries. -6. Commit and push the archive to an SV-COMP archives repository branch (but don't open a MR yet): (SV-COMP 2023). -7. Check pushed archive via CoveriTeam-Remote: . +6. Create (or add new version) Zenodo artifact and upload the archive. - 1. Clone coveriteam repository. - 2. Locally modify `actors/goblint.yml` archive location to the raw URL of the pushed archive. - 3. Run Goblint on some sv-benchmarks and properties via CoveriTeam. +7. Open MR with Zenodo version DOI to the [fm-tools](https://gitlab.com/sosy-lab/benchmarking/fm-tools) repository. - This ensures that Goblint runs on SoSy-Lab servers. + ### After all preruns 1. Push git tag from last prerun: `git push origin svcompXY`. -2. Temporarily disable Zenodo webhook. - - This is because we don't want a new out-of-place version of Goblint in our Zenodo artifact. - A separate Zenodo artifact for the SV-COMP version can be created later if tool paper is submitted. - -3. Create GitHub release from the git tag and attach latest submitted archive as a download. -4. Manually run `docker` workflow on `svcompXY` git tag and targeting `svcompXY` Docker tag. +2. Create GitHub release from the git tag and attach latest submitted archive as a download. +3. Manually run `docker` workflow on `svcompXY` git tag and targeting `svcompXY` Docker tag. This is because the usual `docker` workflow only handles semver releases. -5. Re-enable Zenodo webhook. -6. Release new semver version on opam. See above. +4. Release new semver version on opam. See above. diff --git a/docs/user-guide/benchmarking.md b/docs/user-guide/benchmarking.md index 44811b61a5..5417375bdb 100644 --- a/docs/user-guide/benchmarking.md +++ b/docs/user-guide/benchmarking.md @@ -1,6 +1,31 @@ # Benchmarking +The following best practices should be followed when benchmarking Goblint. +This is to ensure valid, reproducible and representative benchmarking results. -To achieve reproducible builds and the best performance for benchmarking, it is recommended to compile Goblint using the `release` option: +# External benchmarking +External users should choose the version of Goblint to evaluate or benchmark as follows: + +1. Use the newest version release. + + The version from git `master` branch or any other intermediate git commit come without any guarantees. + They are bleeding-edge and haven't gone through validation like the version releases. + + SV-COMP releases are highly preferable since they've gone through rigorous validation in SV-COMP. + +2. Download the corresponding version from a Zenodo artifact or by checking out the respective git tag. **Do not install directly from opam repository!** + + Goblint pins optimized versions of some dependencies which cannot be done on the opam repository releases. + Thus, using the latter would yield unrepresentative results. + + Zenodo artifacts come with DOIs, which make them ideal for citation. + +3. Use OCaml 4.14. **Do not use OCaml 5!** + + OCaml 5 has significant performance regressions, which yield unrepresentative benchmarking results. + Goblint's `make setup` installs the correct OCaml version into a new opam switch. + +# Release build +To achieve the best performance for benchmarking, Goblint should be compiled using the `release` option: ```sh make release diff --git a/docs/user-guide/configuring.md b/docs/user-guide/configuring.md index 82e92f6fe7..cae57fc8cd 100644 --- a/docs/user-guide/configuring.md +++ b/docs/user-guide/configuring.md @@ -24,7 +24,7 @@ In `.vscode/settings.json` add the following: "/conf/*.json", "/tests/incremental/*/*.json" ], - "url": "/src/util/options.schema.json" + "url": "/src/config/options.schema.json" } ] } diff --git a/dune-project b/dune-project index 2fbfb271fc..37e81f4199 100644 --- a/dune-project +++ b/dune-project @@ -1,4 +1,4 @@ -(lang dune 3.6) +(lang dune 3.7) (using dune_site 0.1) (cram enable) (name goblint) @@ -24,8 +24,8 @@ (synopsis "Static analysis framework for C") (depends (ocaml (>= 4.10)) - (goblint-cil (>= 2.0.1)) ; 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.4.0)) + (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. + (batteries (>= 3.5.1)) (zarith (>= 1.8)) (yojson (>= 2.0.0)) (qcheck-core (>= 0.19)) @@ -44,7 +44,7 @@ (fileutils (>= 0.6.4)) cpu arg-complete - yaml + (yaml (>= 3.0.0)) uuidm catapult catapult-file @@ -64,3 +64,5 @@ (share lib) (share conf)) ) + +; (map_workspace_root false) ;uncomment to enable breakpoints diff --git a/goblint.opam b/goblint.opam index 678ad53d13..b5f1f360dc 100644 --- a/goblint.opam +++ b/goblint.opam @@ -19,10 +19,10 @@ homepage: "https://goblint.in.tum.de" doc: "https://goblint.readthedocs.io/en/latest/" bug-reports: "https://github.com/goblint/analyzer/issues" depends: [ - "dune" {>= "3.6"} + "dune" {>= "3.7"} "ocaml" {>= "4.10"} - "goblint-cil" {>= "2.0.1"} - "batteries" {>= "3.4.0"} + "goblint-cil" {>= "2.0.3"} + "batteries" {>= "3.5.1"} "zarith" {>= "1.8"} "yojson" {>= "2.0.0"} "qcheck-core" {>= "0.19"} @@ -41,7 +41,7 @@ depends: [ "fileutils" {>= "0.6.4"} "cpu" "arg-complete" - "yaml" + "yaml" {>= "3.0.0"} "uuidm" "catapult" "catapult-file" @@ -75,9 +75,11 @@ dev-repo: "git+https://github.com/goblint/analyzer.git" # also remember to generate/adjust goblint.opam.locked! available: os-distribution != "alpine" & arch != "arm64" pin-depends: [ - [ "goblint-cil.2.0.1" "git+https://github.com/goblint/cil.git#4df989fe625d91ce07d94afe1d85b3b5c6cdd63e" ] + # 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#d2760bacfbfdb25a374254de44f2ff1cb5f42abd" ] # 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" ] - # TODO: add back after release, only pinned for CI stability - [ "apron.v0.9.13" "git+https://github.com/antoinemine/apron.git#1a8e91062c0d7d1e80333d19d5a432332bbbaec8"] +] +post-messages: [ + "Do not benchmark Goblint on OCaml 5 (https://goblint.readthedocs.io/en/latest/user-guide/benchmarking/)." {ocaml:version >= "5.0.0"} ] diff --git a/goblint.opam.locked b/goblint.opam.locked index acb49a7b14..02eac0bb75 100644 --- a/goblint.opam.locked +++ b/goblint.opam.locked @@ -21,7 +21,7 @@ doc: "https://goblint.readthedocs.io/en/latest/" bug-reports: "https://github.com/goblint/analyzer/issues" depends: [ "angstrom" {= "0.15.0"} - "apron" {= "v0.9.13"} + "apron" {= "v0.9.14~beta.2"} "arg-complete" {= "0.1.0"} "astring" {= "0.8.5"} "base-bigarray" {= "base"} @@ -50,33 +50,33 @@ depends: [ "cpu" {= "2.0.0"} "csexp" {= "1.5.1"} "ctypes" {= "0.20.1"} - "dune" {= "3.6.1"} - "dune-build-info" {= "3.6.1"} - "dune-configurator" {= "3.6.1"} - "dune-private-libs" {= "3.6.1"} - "dune-site" {= "3.6.1"} - "dyn" {= "3.6.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"} + "fileutils" {= "0.6.4"} "fmt" {= "0.9.0"} "fpath" {= "0.7.3"} - "goblint-cil" {= "2.0.1"} + "goblint-cil" {= "2.0.3"} "integers" {= "0.7.0"} "json-data-encoding" {= "0.12.1"} "jsonrpc" {= "1.15.0~5.0preview1"} - "fileutils" {= "0.6.4"} "logs" {= "0.7.0"} - "mlgmpidl" {= "1.2.14"} + "mlgmpidl" {= "1.2.15"} "num" {= "1.4"} "ocaml" {= "4.14.0"} - "ocaml-variants" {= "4.14.0+options"} "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.6.1"} + "ordering" {= "3.7.1"} "ounit2" {= "2.2.6" & with-test} "pp" {= "1.1.2"} "ppx_derivers" {= "1.2.1"} @@ -93,7 +93,7 @@ depends: [ "sexplib0" {= "v0.15.1"} "sha" {= "1.15.2"} "stdlib-shims" {= "0.3.0"} - "stdune" {= "3.6.1"} + "stdune" {= "3.7.1"} "stringext" {= "1.6.0"} "topkg" {= "1.0.6"} "tyxml" {= "4.5.0" & with-doc} @@ -125,16 +125,11 @@ available: os-distribution != "alpine" & arch != "arm64" conflicts: [ "result" {< "1.5"} ] +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.1" - "git+https://github.com/goblint/cil.git#4df989fe625d91ce07d94afe1d85b3b5c6cdd63e" - ] - [ - "apron.v0.9.13" - "git+https://github.com/antoinemine/apron.git#1a8e91062c0d7d1e80333d19d5a432332bbbaec8" - ] [ "ppx_deriving.5.2.1" "git+https://github.com/ocaml-ppx/ppx_deriving.git#0a89b619f94cbbfc3b0fb3255ab4fe5bc77d32d6" diff --git a/goblint.opam.template b/goblint.opam.template index b7f5a7abff..ca2796b3c7 100644 --- a/goblint.opam.template +++ b/goblint.opam.template @@ -2,9 +2,11 @@ # also remember to generate/adjust goblint.opam.locked! available: os-distribution != "alpine" & arch != "arm64" pin-depends: [ - [ "goblint-cil.2.0.1" "git+https://github.com/goblint/cil.git#4df989fe625d91ce07d94afe1d85b3b5c6cdd63e" ] + # 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#d2760bacfbfdb25a374254de44f2ff1cb5f42abd" ] # 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" ] - # TODO: add back after release, only pinned for CI stability - [ "apron.v0.9.13" "git+https://github.com/antoinemine/apron.git#1a8e91062c0d7d1e80333d19d5a432332bbbaec8"] +] +post-messages: [ + "Do not benchmark Goblint on OCaml 5 (https://goblint.readthedocs.io/en/latest/user-guide/benchmarking/)." {ocaml:version >= "5.0.0"} ] diff --git a/gobview b/gobview index c3dcfaba97..3de13d7412 160000 --- a/gobview +++ b/gobview @@ -1 +1 @@ -Subproject commit c3dcfaba97a1df72f027e5dad317e2c201ce5e4b +Subproject commit 3de13d74124ab7bc30d8be299f02570d8f498b84 diff --git a/lib/goblint/runtime/include/goblint.h b/lib/goblint/runtime/include/goblint.h index b0af41616e..af87035d33 100644 --- a/lib/goblint/runtime/include/goblint.h +++ b/lib/goblint/runtime/include/goblint.h @@ -6,3 +6,5 @@ void __goblint_assume_join(/* pthread_t thread */); // undeclared argument to av void __goblint_split_begin(int exp); void __goblint_split_end(int exp); + +void __goblint_bounded(unsigned long long exp); \ No newline at end of file diff --git a/lib/goblint/runtime/src/goblint.c b/lib/goblint/runtime/src/goblint.c index bc176f93a6..cbcb7cf505 100644 --- a/lib/goblint/runtime/src/goblint.c +++ b/lib/goblint/runtime/src/goblint.c @@ -27,4 +27,8 @@ void __goblint_split_begin(int exp) { void __goblint_split_end(int exp) { +} + +void __goblint_bounded(unsigned long long exp) { + } \ No newline at end of file diff --git a/make.sh b/make.sh index 788289c5ed..af1411a8d3 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 ocaml-variants.4.14.0+options ocaml-option-flambda --locked + opam switch -y create . --deps-only --packages=ocaml-variants.4.14.0+options,ocaml-option-flambda --locked } rule() { diff --git a/mkdocs.yml b/mkdocs.yml index 558c381e66..428e28078d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -30,7 +30,7 @@ nav: - 👶 Your first analysis: developer-guide/firstanalysis.md - 🏫 Extending library: developer-guide/extending-library.md - 📢 Messaging: developer-guide/messaging.md - - 🗃️ API reference: https://goblint.github.io/analyzer/ + - 🗃️ API reference: https://goblint.github.io/analyzer/goblint/ - 🚨 Testing: developer-guide/testing.md - 🪲 Debugging: developer-guide/debugging.md - 📉 Profiling: developer-guide/profiling.md diff --git a/scripts/goblint-lib-modules.py b/scripts/goblint-lib-modules.py new file mode 100755 index 0000000000..ec0e78e440 --- /dev/null +++ b/scripts/goblint-lib-modules.py @@ -0,0 +1,67 @@ +#!/usr/bin/python3 + +from pathlib import Path +import re +import sys + +src_root_path = Path("./src") + +goblint_lib_paths = [ + src_root_path / "goblint_lib.ml", + src_root_path / "util" / "std" / "goblint_std.ml", +] +goblint_lib_modules = set() + +for goblint_lib_path in goblint_lib_paths: + with goblint_lib_path.open() as goblint_lib_file: + for line in goblint_lib_file: + line = line.strip() + m = re.match(r"module (.*) = .*", line) + if m is not None: + module_name = m.group(1) + goblint_lib_modules.add(module_name) + +src_vendor_path = src_root_path / "vendor" +exclude_module_names = set([ + "Goblint_lib", # itself + + # executables + "Goblint", + "MessagesCompare", + "PrivPrecCompare", + "ApronPrecCompare", + + # libraries + "Goblint_std", + "Goblint_timing", + "Goblint_backtrace", + "Goblint_tracing", + "Goblint_sites", + "Goblint_build_info", + "Dune_build_info", + + "MessageCategory", # included in Messages + "PreValueDomain", # included in ValueDomain + + "ConfigVersion", + "ConfigProfile", + "ConfigOcaml", +]) + +src_modules = set() + +for ml_path in src_root_path.glob("**/*.ml"): + if str(ml_path).startswith(str(src_vendor_path)): + continue + + module_name = ml_path.with_suffix("").with_suffix("").name + module_name = module_name[0].upper() + module_name[1:] + if module_name.endswith("0") or module_name.endswith("_intf") or module_name in exclude_module_names: + continue + + src_modules.add(module_name) + +missing_modules = src_modules - goblint_lib_modules +if len(missing_modules) > 0: + print(f"Modules missing from {goblint_lib_path}: {missing_modules}") + sys.exit(1) diff --git a/scripts/regression2sv-benchmarks.py b/scripts/regression2sv-benchmarks.py index 8f74a70f52..7bcc1c7ea3 100755 --- a/scripts/regression2sv-benchmarks.py +++ b/scripts/regression2sv-benchmarks.py @@ -31,7 +31,6 @@ "09-regions_34-escape_rc", # duplicate of 04/45 "09-regions_35-list2_rc-offsets-thread", # duplicate of 09/03 "10-synch_17-glob_fld_nr", # duplicate of 05/08 - "19-spec_02-mutex_rc", # duplicate of 04/01 "29-svcomp_01-race-2_3b-container_of", # duplicate sv-benchmarks "29-svcomp_01-race-2_4b-container_of", # duplicate sv-benchmarks diff --git a/scripts/spec/check.sh b/scripts/spec/check.sh deleted file mode 100755 index 57b63edfd2..0000000000 --- a/scripts/spec/check.sh +++ /dev/null @@ -1,27 +0,0 @@ -export OCAMLRUNPARAM=b -# file to analyze -file=${1-"tests/file.c"} -# analysis to run or spec file -ana=${2-"tests/regression/18-file/file.optimistic.spec"} -debug=${debug-"true"} -if [ $ana == "file" ]; then - ana="file" - opt="--set ana.file.optimistic true" -else - spec=$ana - ana="spec" - opt="--set ana.spec.file $spec" -fi -cmd="./goblint --set ana.activated[0][+] $ana $opt --html --set warn.debug $debug $file" -echo -e "$(tput setaf 6)$cmd$(tput sgr 0)" -$cmd - - -# # focuses Firefox and reloads current tab -# if false && command -v xdotool >/dev/null 2>&1; then -# WID=`xdotool search --name "Mozilla Firefox" | head -1` -# xdotool windowactivate $WID -# #xdotool key F5 -# # reload is done by add-on Auto Reload (reload result/* on change of report.html) -# # https://addons.mozilla.org/en-US/firefox/addon/auto-reload/?src=api -# fi diff --git a/scripts/spec/regression.py b/scripts/spec/regression.py deleted file mode 100755 index dc9f9fa276..0000000000 --- a/scripts/spec/regression.py +++ /dev/null @@ -1,61 +0,0 @@ -# import fileinput -# for line in fileinput.input(): -# pass - -import sys, os -import re - -if len(sys.argv) != 2: - print("Stdin: output from goblint, 1. argument: C source-file") - sys.exit(1) -path = sys.argv[1] - -goblint = {} -for line in sys.stdin.readlines(): - line = re.sub(r"\033.*?m", "", line) - m = re.match(r"(.+) \("+re.escape(path)+":(.+)\)", line) - if m: goblint[int(m.group(2))] = m.group(1) - -source = {} -lines = open(path).readlines() -for i,line in zip(range(1, len(lines)+1), lines): - m = re.match(r".+ // WARN: (.+)", line) - if m: source[i] = m.group(1) - -diff = {}; -for k,v in sorted(set.union(set(goblint.items()), set(source.items()))): - if k in diff: continue - if k in goblint and k in source and goblint[k]!=source[k]: - diff[k] = ('D', [goblint[k], source[k]]) - elif (k,v) in goblint.items() and (k,v) not in source.items(): - diff[k] = ('G', [goblint[k]]) - elif (k,v) not in goblint.items() and (k,v) in source.items(): - diff[k] = ('S', [source[k]]) - -if not len(diff): - sys.exit(0) - -print("#"*50) -print(path) -print("file://"+os.getcwd()+"/result/"+os.path.basename(path)+".html") - -if len(goblint): - print("## Goblint warnings:") - for k,v in sorted(goblint.items()): - print("{} \t {}".format(k, v)) - print - -if len(source): - print("## Source warnings:") - for k,v in source.items(): - print("{} \t {}".format(k, v)) - print - -if len(diff): - print("## Diff (G..only goblint, S..only source, D..different):") - for k,(s,v) in sorted(diff.items()): - print("{} {} \t {}".format(s, k, v[0])) - for v in v[1:]: print("\t {}".format(v)) - -print -sys.exit(1) \ No newline at end of file diff --git a/scripts/spec/regression.sh b/scripts/spec/regression.sh deleted file mode 100755 index 6dc740ca75..0000000000 --- a/scripts/spec/regression.sh +++ /dev/null @@ -1,18 +0,0 @@ -debug_tmp=$debug -export debug=false # temporarily disable debug output -n=0 -c=0 -dir=${2-"tests/regression/18-file"} -for f in $dir/*.c; do - ./scripts/spec/check.sh $f ${1-"file"} 2>/dev/null | python scripts/spec/regression.py $f && ((c++)) - ((n++)) -done -debug=$debug_tmp -msg="passed $c/$n tests" -echo $msg -if [ $c -eq $n ]; then - exit 0 -else - notify-send -i stop "$msg" - exit 1 -fi diff --git a/scripts/spec/spec.sh b/scripts/spec/spec.sh deleted file mode 100755 index 03abe9a0c7..0000000000 --- a/scripts/spec/spec.sh +++ /dev/null @@ -1,10 +0,0 @@ -# print all states the parser goes through -#export OCAMLRUNPARAM='p' -bin=src/mainspec.native -spec=${1-"tests/regression/18-file/file.spec"} -ocamlbuild -yaccflag -v -X webapp -no-links -use-ocamlfind $bin \ - && (./_build/$bin $spec \ - || (echo "$spec failed, running interactive now..."; - rlwrap ./_build/$bin - ) - ) diff --git a/scripts/update_suite.rb b/scripts/update_suite.rb index 07f06601f7..3668bdaaee 100755 --- a/scripts/update_suite.rb +++ b/scripts/update_suite.rb @@ -41,7 +41,7 @@ def clearline $goblint = File.join(Dir.getwd,"goblint") goblintbyte = File.join(Dir.getwd,"goblint.byte") -if File.exists?(goblintbyte) then +if File.exist?(goblintbyte) then puts "Running the byte-code version! Continue? (y/n)" exit unless $stdin.gets()[0] == 'y' $goblint = goblintbyte @@ -50,11 +50,11 @@ def clearline end $vrsn = `#{$goblint} --version` -if not File.exists? "linux-headers" then +if not File.exist? "linux-headers" then puts "Missing linux-headers, will download now!" `make headers` end -has_linux_headers = File.exists? "linux-headers" # skip kernel tests if make headers failed (e.g. on opam-repository opam-ci where network is forbidden) +has_linux_headers = File.exist? "linux-headers" # skip kernel tests if make headers failed (e.g. on opam-repository opam-ci where network is forbidden) #Command line parameters #Either only run a single test, or @@ -145,10 +145,11 @@ def collect_warnings @vars = $1 @evals = $2 end + if l =~ /\[Termination\]/ then warnings[-1] = "nonterm" end # Get Termination warning next unless l =~ /(.*)\(.*?\:(\d+)(?:\:\d+)?(?:-(?:\d+)(?:\:\d+)?)?\)/ obj,i = $1,$2.to_i - ranking = ["other", "warn", "race", "norace", "deadlock", "nodeadlock", "success", "fail", "unknown"] + ranking = ["other", "warn", "goto", "fundec", "loop", "term", "nonterm", "race", "norace", "deadlock", "nodeadlock", "success", "fail", "unknown"] thiswarn = case obj when /\(conf\. \d+\)/ then "race" when /Deadlock/ then "deadlock" @@ -159,6 +160,9 @@ def collect_warnings when /invariant confirmed/ then "success" when /invariant unconfirmed/ then "unknown" when /invariant refuted/ then "fail" + when /(Upjumping Goto)/ then "goto" + when /(Fundec \w+ is contained in a call graph cycle)/ then "fundec" + when /(Loop analysis)/ then "loop" when /^\[Warning\]/ then "warn" when /^\[Error\]/ then "warn" when /^\[Info\]/ then "warn" @@ -183,19 +187,33 @@ def compare_warnings if cond then @correct += 1 # full p.path is too long and p.name does not allow click to open in terminal - if todo.include? idx then puts "Excellent: ignored check on #{relpath(p.path).to_s.cyan}:#{idx.to_s.blue} is now passing!" end + if todo.include? idx + if idx < 0 + puts "Excellent: ignored check on #{relpath(p.path).to_s.cyan} for #{type.yellow} is now passing!" + else + puts "Excellent: ignored check on #{relpath(p.path).to_s.cyan}:#{idx.to_s.blue} is now passing!" + end + end else - if todo.include? idx then @ignored += 1 else - puts "Expected #{type.yellow}, but registered #{(warnings[idx] or "nothing").yellow} on #{p.name.cyan}:#{idx.to_s.blue}" - puts tests_line[idx].rstrip.gray - ferr = idx if ferr.nil? or idx < ferr + if todo.include? idx + @ignored += 1 + else + if idx < 0 # When non line specific keywords were used don't print a line + puts "Expected #{type.yellow}, but registered #{(warnings[idx] or "nothing").yellow} on #{p.name.cyan}" + else + puts "Expected #{type.yellow}, but registered #{(warnings[idx] or "nothing").yellow} on #{p.name.cyan}:#{idx.to_s.blue}" + puts tests_line[idx].rstrip.gray + ferr = idx if ferr.nil? or idx < ferr + end end end } case type - when "deadlock", "race", "fail", "unknown", "warn" + when "goto", "fundec", "loop", "deadlock", "race", "fail", "unknown", "warn" + check.call warnings[idx] == type + when "nonterm" check.call warnings[idx] == type - when "nowarn" + when "nowarn", "term" check.call warnings[idx].nil? when "assert", "success" check.call warnings[idx] == "success" @@ -304,6 +322,12 @@ def parse_tests (lines) tests[i] = "success" elsif obj =~ /(\b|\/)FAIL/ then tests[i] = "fail" + elsif obj =~ /(\b|\/)NONTERMLOOP/ then + tests[i] = "loop" + elsif obj =~ /(\b|\/)NONTERMGOTO/ then + tests[i] = "goto" + elsif obj =~ /(\b|\/)NONTERMFUNDEC/ then + tests[i] = "fundec" elsif obj =~ /(\b|\/)UNKNOWN/ then tests[i] = "unknown" elsif obj =~ /(\b|\/)(assert|__goblint_check).*\(/ then @@ -318,6 +342,15 @@ def parse_tests (lines) end end end + case lines[0] + when /NONTERM/ + tests[-1] = "nonterm" + when /TERM/ + tests[-1] = "term" + end + if lines[0] =~ /TODO/ then + todo << -1 + end Tests.new(self, tests, tests_line, todo) end diff --git a/src/analyses/abortUnless.ml b/src/analyses/abortUnless.ml index 813d999ac3..ee4db69820 100644 --- a/src/analyses/abortUnless.ml +++ b/src/analyses/abortUnless.ml @@ -65,8 +65,8 @@ struct false let startstate v = false - let threadenter ctx lval f args = [false] - let threadspawn ctx lval f args fctx = false + let threadenter ctx ~multiple lval f args = [false] + let threadspawn ctx ~multiple lval f args fctx = false let exitstate v = false end diff --git a/src/analyses/accessAnalysis.ml b/src/analyses/accessAnalysis.ml index 5245e4adfe..efad8b4c2e 100644 --- a/src/analyses/accessAnalysis.ml +++ b/src/analyses/accessAnalysis.ml @@ -29,13 +29,13 @@ struct let init _ = collect_local := get_bool "witness.yaml.enabled" && get_bool "witness.invariant.accessed"; let activated = get_string_list "ana.activated" in - emit_single_threaded := List.mem (ModifiedSinceLongjmp.Spec.name ()) activated || List.mem (PoisonVariables.Spec.name ()) activated + emit_single_threaded := List.mem (ModifiedSinceSetjmp.Spec.name ()) activated || List.mem (PoisonVariables.Spec.name ()) activated let do_access (ctx: (D.t, G.t, C.t, V.t) ctx) (kind:AccessKind.t) (reach:bool) (e:exp) = if M.tracing then M.trace "access" "do_access %a %a %B\n" d_exp e AccessKind.pretty kind reach; let reach_or_mpt: _ Queries.t = if reach then ReachableFrom e else MayPointTo e in - let ls = ctx.ask reach_or_mpt in - ctx.emit (Access {exp=e; lvals=ls; kind; reach}) + let ad = ctx.ask reach_or_mpt in + ctx.emit (Access {exp=e; ad; kind; reach}) (** Three access levels: + [deref=false], [reach=false] - Access [exp] without dereferencing, used for all normal reads and all function call arguments. @@ -44,7 +44,7 @@ struct let access_one_top ?(force=false) ?(deref=false) ctx (kind: AccessKind.t) reach exp = if M.tracing then M.traceli "access" "access_one_top %a (kind = %a, reach = %B, deref = %B)\n" CilType.Exp.pretty exp AccessKind.pretty kind reach deref; if force || !collect_local || !emit_single_threaded || ThreadFlag.has_ever_been_multi (Analyses.ask_of_ctx ctx) then ( - if deref then + if deref && Cil.isPointerType (Cilfacade.typeOf exp) then (* avoid dereferencing integers to unknown pointers, which cause many spurious type-based accesses *) do_access ctx kind reach exp; if M.tracing then M.tracei "access" "distribute_access_exp\n"; Access.distribute_access_exp (do_access ctx Read false) exp; @@ -54,7 +54,7 @@ struct (** We just lift start state, global and dependency functions: *) let startstate v = () - let threadenter ctx lval f args = [()] + let threadenter ctx ~multiple lval f args = [()] let exitstate v = () let context fd d = () @@ -121,7 +121,7 @@ struct ctx.local - let threadspawn ctx lval f args fctx = + let threadspawn ctx ~multiple lval f args fctx = (* must explicitly access thread ID lval because special to pthread_create doesn't if singlethreaded before *) begin match lval with | None -> () @@ -137,25 +137,20 @@ struct let event ctx e octx = match e with - | Events.Access {lvals; kind; _} when !collect_local && !AnalysisState.postsolving -> - begin match lvals with - | ls when Queries.LS.is_top ls -> - let access: AccessDomain.Event.t = {var_opt = None; offs_opt = None; kind} in - ctx.sideg ctx.node (G.singleton access) - | ls -> - let events = Queries.LS.fold (fun (var, offs) acc -> - let coffs = Offset.Exp.to_cil offs in - let access: AccessDomain.Event.t = - if CilType.Varinfo.equal var dummyFunDec.svar then - {var_opt = None; offs_opt = (Some coffs); kind} - else - {var_opt = (Some var); offs_opt = (Some coffs); kind} - in - G.add access acc - ) ls (G.empty ()) - in - ctx.sideg ctx.node events - end + | Events.Access {ad; kind; _} when !collect_local && !AnalysisState.postsolving -> + let events = Queries.AD.fold (fun addr es -> + match addr with + | Queries.AD.Addr.Addr (var, offs) -> + let coffs = ValueDomain.Offs.to_cil offs in + let access: AccessDomain.Event.t = {var_opt = (Some var); offs_opt = (Some coffs); kind} in + G.add access es + | UnknownPtr -> + let access: AccessDomain.Event.t = {var_opt = None; offs_opt = None; kind} in + G.add access es + | _ -> es + ) ad (G.empty ()) + in + ctx.sideg ctx.node events | _ -> ctx.local end diff --git a/src/analyses/activeLongjmp.ml b/src/analyses/activeLongjmp.ml index 9c9868e32f..9baa601ddc 100644 --- a/src/analyses/activeLongjmp.ml +++ b/src/analyses/activeLongjmp.ml @@ -26,7 +26,7 @@ struct (* Initial values don't really matter: overwritten at longjmp call. *) let startstate v = D.bot () - let threadenter ctx lval f args = [D.bot ()] + let threadenter ctx ~multiple lval f args = [D.bot ()] let exitstate v = D.top () let query ctx (type a) (q: a Queries.t): a Queries.result = diff --git a/src/analyses/activeSetjmp.ml b/src/analyses/activeSetjmp.ml index 069111d3ba..be13489993 100644 --- a/src/analyses/activeSetjmp.ml +++ b/src/analyses/activeSetjmp.ml @@ -25,7 +25,7 @@ struct | _ -> ctx.local let startstate v = D.bot () - let threadenter ctx lval f args = [D.bot ()] + let threadenter ctx ~multiple lval f args = [D.bot ()] let exitstate v = D.top () let query ctx (type a) (q: a Queries.t): a Queries.result = diff --git a/src/analyses/apron/affineEqualityAnalysis.apron.ml b/src/analyses/apron/affineEqualityAnalysis.apron.ml index 03a9ecdb57..ce859d87b7 100644 --- a/src/analyses/apron/affineEqualityAnalysis.apron.ml +++ b/src/analyses/apron/affineEqualityAnalysis.apron.ml @@ -11,7 +11,6 @@ let spec_module: (module MCPSpec) Lazy.t = let module AD = AffineEqualityDomain.D2 (VectorMatrix.ArrayVector) (VectorMatrix.ArrayMatrix) in let module RD: RelationDomain.RD = struct - module Var = AffineEqualityDomain.Var module V = AffineEqualityDomain.V include AD end diff --git a/src/analyses/apron/apronAnalysis.apron.ml b/src/analyses/apron/apronAnalysis.apron.ml index 29e295a662..0ba17cdb35 100644 --- a/src/analyses/apron/apronAnalysis.apron.ml +++ b/src/analyses/apron/apronAnalysis.apron.ml @@ -12,10 +12,9 @@ let spec_module: (module MCPSpec) Lazy.t = let module AD = (val if diff_box then (module ApronDomain.BoxProd (AD): ApronDomain.S3) else (module AD)) in let module RD: RelationDomain.RD = struct - module Var = ApronDomain.Var module V = ApronDomain.V include AD - type var = ApronDomain.Var.t + type var = Apron.Var.t end in let module Priv = (val RelationPriv.get_priv ()) in diff --git a/src/analyses/apron/relationAnalysis.apron.ml b/src/analyses/apron/relationAnalysis.apron.ml index c5ad08ec76..5e128ffc30 100644 --- a/src/analyses/apron/relationAnalysis.apron.ml +++ b/src/analyses/apron/relationAnalysis.apron.ml @@ -11,6 +11,7 @@ open Analyses open RelationDomain module M = Messages +module VS = SetDomain.Make (CilType.Varinfo) module SpecFunctor (Priv: RelationPriv.S) (RD: RelationDomain.RD) (PCU: RelationPrecCompareUtil.Util) = struct @@ -69,7 +70,7 @@ struct let visitor = object inherit nopCilVisitor method! vlval = function - | (Var v, NoOffset) when v.vglob || ThreadEscape.has_escaped ask v -> + | (Var v, NoOffset) when (v.vglob || ThreadEscape.has_escaped ask v) && RD.Tracked.varinfo_tracked v -> let v_in = if VH.mem v_ins v then VH.find v_ins v @@ -157,15 +158,13 @@ struct {st' with rel = rel''} ) | (Mem v, NoOffset) -> - (let r = ask.f (Queries.MayPointTo v) in - match r with - | `Top -> - st - | `Lifted s -> - let lvals = Queries.LS.elements r in - let ass' = List.map (fun lv -> assign_to_global_wrapper ask getg sideg st (Mval.Exp.to_cil lv) f) lvals in - List.fold_right D.join ass' (D.bot ()) - ) + begin match ask.f (Queries.MayPointTo v) with + | ad when Queries.AD.is_top ad -> st + | ad -> + let mvals = Queries.AD.to_mval ad in + let ass' = List.map (fun mval -> assign_to_global_wrapper ask getg sideg st (ValueDomain.Addr.Mval.to_cil mval) f) mvals in + List.fold_right D.join ass' (D.bot ()) + end (* Ignoring all other assigns *) | _ -> st @@ -197,7 +196,7 @@ struct let assert_type_bounds ask rel x = assert (RD.Tracked.varinfo_tracked x); match Cilfacade.get_ikind x.vtype with - | ik when not (IntDomain.should_ignore_overflow ik) -> (* don't add type bounds for signed when assume_none *) + | ik -> let (type_min, type_max) = IntDomain.Size.range ik in (* TODO: don't go through CIL exp? *) let e1 = BinOp (Le, Lval (Cil.var x), (Cil.kintegerCilint ik type_max), intType) in @@ -205,7 +204,6 @@ struct let rel = RD.assert_inv rel e1 false (no_overflow ask e1) in (* TODO: how can be overflow when asserting type bounds? *) let rel = RD.assert_inv rel e2 false (no_overflow ask e2) in rel - | _ | exception Invalid_argument _ -> rel @@ -217,12 +215,16 @@ struct | CastE (t,e) -> CastE (t, inner e) | Lval (Var v, off) -> Lval (Var v, off) | Lval (Mem e, NoOffset) -> - (match ask (Queries.MayPointTo e) with - | a when not (Queries.LS.is_top a || Queries.LS.mem (dummyFunDec.svar, `NoOffset) a) && (Queries.LS.cardinal a) = 1 -> - Mval.Exp.to_cil_exp (Queries.LS.choose a) - (* It would be possible to do better here, exploiting e.g. that the things pointed to are known to be equal *) - (* see: https://github.com/goblint/analyzer/pull/742#discussion_r879099745 *) - | _ -> Lval (Mem e, NoOffset)) + begin match ask (Queries.MayPointTo e) with + | ad when not (Queries.AD.is_top ad) && (Queries.AD.cardinal ad) = 1 -> + begin match Queries.AD.Addr.to_mval (Queries.AD.choose ad) with + | Some mval -> ValueDomain.Addr.Mval.to_cil_exp mval + | None -> Lval (Mem e, NoOffset) + end + (* It would be possible to do better here, exploiting e.g. that the things pointed to are known to be equal *) + (* see: https://github.com/goblint/analyzer/pull/742#discussion_r879099745 *) + | _ -> Lval (Mem e, NoOffset) + end | e -> e (* TODO: Potentially recurse further? *) in inner e @@ -268,13 +270,22 @@ struct let any_local_reachable fundec reachable_from_args = let locals = fundec.sformals @ fundec.slocals in let locals_id = List.map (fun v -> v.vid) locals in - Queries.LS.exists (fun (v',_) -> List.mem v'.vid locals_id && RD.Tracked.varinfo_tracked v') reachable_from_args + VS.exists (fun v -> List.mem v.vid locals_id && RD.Tracked.varinfo_tracked v) reachable_from_args + + let reachable_from_args ctx args = + let to_vs e = + ctx.ask (ReachableFrom e) + |> Queries.AD.to_var_may + |> VS.of_list + in + List.fold (fun vs e -> VS.join vs (to_vs e)) (VS.empty ()) args let pass_to_callee fundec any_local_reachable var = (* TODO: currently, we pass all locals of the caller to the callee, provided one of them is reachbale to preserve relationality *) (* 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 = RD.Var.to_string var in + let vname = Apron.Var.to_string 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 @@ -285,10 +296,8 @@ struct let st = ctx.local in let arg_assigns = GobList.combine_short f.sformals args (* TODO: is it right to ignore missing formals/args? *) - |> List.filter (fun (x, _) -> RD.Tracked.varinfo_tracked x) - |> List.map (Tuple2.map1 RV.arg) + |> List.filter_map (fun (x, e) -> if RD.Tracked.varinfo_tracked x then Some (RV.arg x, e) else None) in - let reachable_from_args = List.fold (fun ls e -> Queries.LS.join ls (ctx.ask (ReachableFrom e))) (Queries.LS.empty ()) args in let arg_vars = List.map fst arg_assigns in let new_rel = RD.add_vars st.rel arg_vars in (* RD.assign_exp_parallel_with new_rel arg_assigns; (* doesn't need to be parallel since exps aren't arg vars directly *) *) @@ -304,11 +313,12 @@ struct ) ) new_rel arg_assigns in + let reachable_from_args = reachable_from_args ctx args in let any_local_reachable = any_local_reachable fundec reachable_from_args in RD.remove_filter_with new_rel (fun var -> match RV.find_metadata var with | Some (Local _) when not (pass_to_callee fundec any_local_reachable var) -> true (* remove caller locals provided they are unreachable *) - | Some (Arg _) when not (List.mem_cmp RD.Var.compare var arg_vars) -> true (* remove caller args, but keep just added args *) + | Some (Arg _) when not (List.mem_cmp Apron.Var.compare var arg_vars) -> true (* remove caller args, but keep just added args *) | _ -> false (* keep everything else (just added args, globals, global privs) *) ); if M.tracing then M.tracel "combine" "relation enter newd: %a\n" RD.pretty new_rel; @@ -366,16 +376,20 @@ struct let combine_env ctx r fe f args fc fun_st (f_ask : Queries.ask) = let st = ctx.local in - let reachable_from_args = List.fold (fun ls e -> Queries.LS.join ls (ctx.ask (ReachableFrom e))) (Queries.LS.empty ()) args 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\n" CilType.Varinfo.pretty f.svar; if M.tracing then M.tracel "combine" "relation formals: %a\n" (d_list "," CilType.Varinfo.pretty) f.sformals; if M.tracing then M.tracel "combine" "relation args: %a\n" (d_list "," d_exp) args; let new_fun_rel = RD.add_vars fun_st.rel (RD.vars st.rel) in let arg_substitutes = + let filter_actuals (x,e) = + RD.Tracked.varinfo_tracked x + && List.for_all (fun v -> not (VS.mem v reachable_from_args)) (Basetype.CilExp.get_vars e) + in GobList.combine_short f.sformals args (* TODO: is it right to ignore missing formals/args? *) (* Do not do replacement for actuals whose value may be modified after the call *) - |> List.filter (fun (x, e) -> RD.Tracked.varinfo_tracked x && List.for_all (fun v -> not (Queries.LS.exists (fun (v',_) -> v'.vid = v.vid) reachable_from_args)) (Basetype.CilExp.get_vars e)) + |> List.filter filter_actuals |> List.map (Tuple2.map1 RV.arg) in (* RD.substitute_exp_parallel_with new_fun_rel arg_substitutes; (* doesn't need to be parallel since exps aren't arg vars directly *) *) @@ -390,7 +404,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\n" (docList (fun v -> Pretty.text (RD.Var.to_string v))) arg_vars; + if M.tracing then M.tracel "combine" "relation remove vars: %a\n" (docList (fun v -> Pretty.text (Apron.Var.to_string v))) 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 @@ -438,13 +452,13 @@ struct match st with | None -> None | Some st -> - let vs = ask.f (Queries.ReachableFrom e) in - if Queries.LS.is_top vs then + let ad = ask.f (Queries.ReachableFrom e) in + if Queries.AD.is_top ad then None else - Some (Queries.LS.join vs st) + Some (Queries.AD.join ad st) in - List.fold_right reachable es (Some (Queries.LS.empty ())) + List.fold_right reachable es (Some (Queries.AD.empty ())) let forget_reachable ctx st es = @@ -456,9 +470,13 @@ struct RD.vars st.rel |> List.filter_map RV.to_cil_varinfo |> List.map Cil.var - | Some rs -> - Queries.LS.elements rs - |> List.map Mval.Exp.to_cil + | Some ad -> + let to_cil addr rs = + match addr with + | Queries.AD.Addr.Addr mval -> (ValueDomain.Addr.Mval.to_cil mval) :: rs + | _ -> rs + in + Queries.AD.fold to_cil ad [] in List.fold_left (fun st lval -> invalidate_one ask ctx st lval @@ -515,10 +533,11 @@ struct | None -> st) | _, _ -> let lvallist e = - let s = ask.f (Queries.MayPointTo e) in - match s with - | `Top -> [] - | `Lifted _ -> List.map Mval.Exp.to_cil (Queries.LS.elements s) + match ask.f (Queries.MayPointTo e) with + | ad when Queries.AD.is_top ad -> [] + | ad -> + Queries.AD.to_mval ad + |> List.map ValueDomain.Addr.Mval.to_cil in let shallow_addrs = LibraryDesc.Accesses.find desc.accs { kind = Write; deep = false } args in let deep_addrs = LibraryDesc.Accesses.find desc.accs { kind = Write; deep = true } args in @@ -627,7 +646,7 @@ struct (* Thread transfer functions. *) - let threadenter ctx lval f args = + let threadenter ctx ~multiple lval f args = let st = ctx.local in match Cilfacade.find_varinfo_fundec f with | fd -> @@ -645,7 +664,7 @@ struct (* TODO: do something like base? *) failwith "relation.threadenter: unknown function" - let threadspawn ctx lval f args fctx = + let threadspawn ctx ~multiple lval f args fctx = ctx.local let event ctx e octx = diff --git a/src/analyses/apron/relationPriv.apron.ml b/src/analyses/apron/relationPriv.apron.ml index b386af162b..a51fc3545f 100644 --- a/src/analyses/apron/relationPriv.apron.ml +++ b/src/analyses/apron/relationPriv.apron.ml @@ -195,8 +195,7 @@ struct end module AV = struct - include RelationDomain.VarMetadataTbl (VM) (RD.Var) - + include RelationDomain.VarMetadataTbl (VM) let local g = make_var (Local g) let unprot g = make_var (Unprot g) diff --git a/src/analyses/base.ml b/src/analyses/base.ml index 7bc589df20..7c741e227e 100644 --- a/src/analyses/base.ml +++ b/src/analyses/base.ml @@ -108,7 +108,7 @@ struct | (info,(value:VD.t))::xs -> match value with | Address t when hasAttribute "goblint_array_domain" info.vattr -> - let possibleVars = List.to_seq (PreValueDomain.AD.to_var_may t) in + let possibleVars = List.to_seq (AD.to_var_may t) in Seq.fold_left (fun map arr -> VarMap.add arr (info.vattr) map) (pointedArrayMap xs) @@ Seq.filter (fun info -> isArrayType info.vtype) possibleVars | _ -> pointedArrayMap xs in @@ -150,8 +150,8 @@ struct let longjmp_return = ref dummyFunDec.svar - let heap_var ctx = - let info = match (ctx.ask Q.HeapVar) with + let heap_var on_stack ctx = + let info = match (ctx.ask (Q.AllocVar {on_stack})) with | `Lifted vinfo -> vinfo | _ -> failwith("Ran without a malloc analysis.") in info @@ -345,7 +345,7 @@ struct if AD.is_definite x && AD.is_definite y then let ax = AD.choose x in let ay = AD.choose y in - let handle_address_is_multiple addr = begin match AD.Addr.to_var addr with + let handle_address_is_multiple addr = begin match Addr.to_var addr with | Some v when a.f (Q.IsMultiple v) -> if M.tracing then M.tracel "addr" "IsMultiple %a\n" CilType.Varinfo.pretty v; None @@ -353,7 +353,7 @@ struct Some true end in - match AD.Addr.semantic_equal ax ay with + match Addr.semantic_equal ax ay with | Some true -> if M.tracing then M.tracel "addr" "semantic_equal %a %a\n" AD.pretty x AD.pretty y; handle_address_is_multiple ax @@ -397,6 +397,10 @@ struct Int (if AD.is_bot (AD.meet p1 p2) then ID.of_int ik BI.zero else match eq p1 p2 with Some x when x -> ID.of_int ik BI.one | _ -> bool_top ik) | Ne -> Int (if AD.is_bot (AD.meet p1 p2) then ID.of_int ik BI.one else match eq p1 p2 with Some x when x -> ID.of_int ik BI.zero | _ -> bool_top ik) + | IndexPI when AD.to_string p2 = ["all_index"] -> + addToAddrOp p1 (ID.top_of (Cilfacade.ptrdiff_ikind ())) + | IndexPI | PlusPI -> + addToAddrOp p1 (AD.to_int p2) (* sometimes index is AD for some reason... *) | _ -> VD.top () end (* For other values, we just give up! *) @@ -462,7 +466,7 @@ struct let var = get_var a gs st x in let v = VD.eval_offset (Queries.to_value_domain_ask a) (fun x -> get a gs st x exp) var offs exp (Some (Var x, Offs.to_cil_offset offs)) x.vtype in if M.tracing then M.tracec "get" "var = %a, %a = %a\n" VD.pretty var AD.pretty (AD.of_mval (x, offs)) VD.pretty v; - if full then v else match v with + if full then var else match v with | Blob (c,s,_) -> c | x -> x in @@ -568,6 +572,8 @@ struct if M.tracing then M.traceu "reachability" "All reachable vars: %a\n" AD.pretty !visited; List.map AD.singleton (AD.elements !visited) + let reachable_vars ask args gs st = Timing.wrap "reachability" (reachable_vars ask args gs) st + let drop_non_ptrs (st:CPA.t) : CPA.t = if CPA.is_top st then st else let rec replace_val = function @@ -591,7 +597,7 @@ struct | Struct n -> Struct (ValueDomain.Structs.map replace_val n) | Union (f,v) -> Union (f,replace_val v) | Blob (n,s,o) -> Blob (replace_val n,s,o) - | Address x -> Address (ValueDomain.AD.map ValueDomain.Addr.top_indices x) + | Address x -> Address (AD.map ValueDomain.Addr.top_indices x) | x -> x in CPA.map replace_val st @@ -611,16 +617,6 @@ struct %> f (ContextUtil.should_keep ~isAttr:GobContext ~keepOption:"ana.base.context.interval" ~removeAttr:"base.no-interval" ~keepAttr:"base.interval" fd) drop_interval %> f (ContextUtil.should_keep ~isAttr:GobContext ~keepOption:"ana.base.context.interval_set" ~removeAttr:"base.no-interval_set" ~keepAttr:"base.interval_set" fd) drop_intervalSet - (* TODO: Use AddressDomain for queries *) - let convertToQueryLval = function - | ValueDomain.AD.Addr.Addr (v,o) -> [v, Addr.Offs.to_exp o] - | _ -> [] - - let addrToLvalSet a = - let add x y = Q.LS.add y x in - try - AD.fold (fun e c -> List.fold_left add c (convertToQueryLval e)) a (Q.LS.empty ()) - with SetDomain.Unsupported _ -> Q.LS.top () let reachable_top_pointers_types ctx (ps: AD.t) : Queries.TS.t = let module TS = Queries.TS in @@ -767,7 +763,8 @@ struct (match str with Some x -> M.tracel "casto" "CInt (%s, %a, %s)\n" (Z.to_string num) d_ikind ikind x | None -> ()); Int (ID.cast_to ikind (IntDomain.of_const (num,ikind,str))) | Const (CReal (_,fkind, Some str)) when not (Cilfacade.isComplexFKind fkind) -> Float (FD.of_string fkind str) (* prefer parsing from string due to higher precision *) - | Const (CReal (num, fkind, None)) when not (Cilfacade.isComplexFKind fkind) -> Float (FD.of_const fkind num) + | Const (CReal (num, fkind, None)) when not (Cilfacade.isComplexFKind fkind) && num = 0.0 -> Float (FD.of_const fkind num) (* constant 0 is ok, CIL creates these for zero-initializers; it is safe across float types *) + | Const (CReal (_, fkind, None)) when not (Cilfacade.isComplexFKind fkind) -> assert false (* Cil does not create other CReal without string representation *) (* String literals *) | Const (CStr (x,_)) -> Address (AD.of_string x) (* normal 8-bit strings, type: char* *) | Const (CWStr (xs,_) as c) -> (* wide character strings, type: wchar_t* *) @@ -1048,12 +1045,25 @@ struct | Mem n, ofs -> begin match (eval_rv a gs st n) with | Address adr -> - (if AD.is_null adr - then M.error ~category:M.Category.Behavior.Undefined.nullpointer_dereference ~tags:[CWE 476] "Must dereference NULL pointer" - else if AD.may_be_null adr - then M.warn ~category:M.Category.Behavior.Undefined.nullpointer_dereference ~tags:[CWE 476] "May dereference NULL pointer"); + ( + if AD.is_null adr then ( + AnalysisStateUtil.set_mem_safety_flag InvalidDeref; + M.error ~category:M.Category.Behavior.Undefined.nullpointer_dereference ~tags:[CWE 476] "Must dereference NULL pointer" + ) + else if AD.may_be_null adr then ( + AnalysisStateUtil.set_mem_safety_flag InvalidDeref; + M.warn ~category:M.Category.Behavior.Undefined.nullpointer_dereference ~tags:[CWE 476] "May dereference NULL pointer" + ); + (* Warn if any of the addresses contains a non-local and non-global variable *) + if AD.exists (function + | AD.Addr.Addr (v, _) -> not (CPA.mem v st.cpa) && not (is_global a v) + | _ -> false + ) adr then ( + AnalysisStateUtil.set_mem_safety_flag InvalidDeref; + M.warn "lval %a points to a non-local variable. Invalid pointer dereference may occur" d_lval lval + ) + ); AD.map (add_offset_varinfo (convert_offset a gs st ofs)) adr - | Bot -> AD.bot () | _ -> M.debug ~category:Analyzer "Failed evaluating %a to lvalue" d_lval lval; AD.unknown_ptr @@ -1106,20 +1116,15 @@ struct | Int x -> ValueDomain.ID.to_int x | _ -> None - let eval_funvar ctx fval: varinfo list = - let exception OnlyUnknown in - try - let fp = eval_fv (Analyses.ask_of_ctx ctx) ctx.global ctx.local fval in - if AD.mem Addr.UnknownPtr fp then begin - let others = AD.to_var_may fp in - if others = [] then raise OnlyUnknown; - M.warn ~category:Imprecise "Function pointer %a may contain unknown functions." d_exp fval; - dummyFunDec.svar :: others - end else - AD.to_var_may fp - with SetDomain.Unsupported _ | OnlyUnknown -> - M.warn ~category:Unsound "Unknown call to function %a." d_exp fval; - [dummyFunDec.svar] + let eval_funvar ctx fval: Queries.AD.t = + let fp = eval_fv (Analyses.ask_of_ctx ctx) ctx.global ctx.local fval in + if AD.is_top fp then ( + if AD.cardinal fp = 1 then + M.warn ~category:Imprecise ~tags:[Category Call] "Unknown call to function %a." d_exp fval + else + M.warn ~category:Imprecise ~tags:[Category Call] "Function pointer %a may contain unknown functions." d_exp fval + ); + fp (** Evaluate expression as address. Avoids expensive Apron EvalInt if the Int result would be useless to us anyway. *) @@ -1134,6 +1139,13 @@ struct (* interpreter end *) + let is_not_alloc_var ctx v = + not (ctx.ask (Queries.IsAllocVar v)) + + let is_not_heap_alloc_var ctx v = + let is_alloc = ctx.ask (Queries.IsAllocVar v) in + not is_alloc || (is_alloc && not (ctx.ask (Queries.IsHeapVar v))) + let query_invariant ctx context = let cpa = ctx.local.BaseDomain.cpa in let ask = Analyses.ask_of_ctx ctx in @@ -1213,10 +1225,7 @@ struct let query ctx (type a) (q: a Q.t): a Q.result = match q with | Q.EvalFunvar e -> - begin - let fs = eval_funvar ctx e in - List.fold_left (fun xs v -> Q.LS.add (v,`NoOffset) xs) (Q.LS.empty ()) fs - end + eval_funvar ctx e | Q.EvalJumpBuf e -> begin match eval_rv_address (Analyses.ask_of_ctx ctx) ctx.global ctx.local e with | Address jmp_buf -> @@ -1227,9 +1236,16 @@ struct if copied then M.warn ~category:(Behavior (Undefined Other)) "The jump buffer %a contains values that were copied here instead of being set by setjmp. This is Undefined Behavior." d_exp e; x - | y -> failwith (GobPretty.sprintf "problem?! is %a %a:\n state is %a" CilType.Exp.pretty e VD.pretty y D.pretty ctx.local) + | Top + | Bot -> + JmpBufDomain.JmpBufSet.top () + | y -> + M.debug ~category:Imprecise "EvalJmpBuf %a is %a, not JmpBuf." CilType.Exp.pretty e VD.pretty y; + JmpBufDomain.JmpBufSet.top () end - | _ -> failwith "problem?!" + | _ -> + M.debug ~category:Imprecise "EvalJmpBuf is not Address"; + JmpBufDomain.JmpBufSet.top () end | Q.EvalInt e -> query_evalint (Analyses.ask_of_ctx ctx) ctx.global ctx.local e @@ -1256,26 +1272,45 @@ struct end | Q.EvalValue e -> eval_rv (Analyses.ask_of_ctx ctx) ctx.global ctx.local e - | Q.BlobSize e -> begin + | Q.BlobSize {exp = e; base_address = from_base_addr} -> begin let p = eval_rv_address (Analyses.ask_of_ctx ctx) ctx.global ctx.local e in (* ignore @@ printf "BlobSize %a MayPointTo %a\n" d_plainexp e VD.pretty p; *) match p with | Address a -> - let r = get ~full:true (Analyses.ask_of_ctx ctx) ctx.global ctx.local a None in - (* ignore @@ printf "BlobSize %a = %a\n" d_plainexp e VD.pretty r; *) - (match r with - | Blob (_,s,_) -> `Lifted s - | _ -> Queries.Result.top q) + (* If there's a non-heap var or an offset in the lval set, we answer with bottom *) + (* If we're asking for the BlobSize from the base address, then don't check for offsets => we want to avoid getting bot *) + if AD.exists (function + | Addr (v,o) -> is_not_alloc_var ctx v || (if not from_base_addr then o <> `NoOffset else false) + | _ -> false) a then + Queries.Result.bot q + else ( + (* If we need the BlobSize from the base address, then remove any offsets *) + let a = + if from_base_addr then AD.map (function + | Addr (v, o) -> Addr (v, `NoOffset) + | addr -> addr) a + else + a + in + let r = get ~full:true (Analyses.ask_of_ctx ctx) ctx.global ctx.local a None in + (* ignore @@ printf "BlobSize %a = %a\n" d_plainexp e VD.pretty r; *) + (match r with + | Array a -> + (* unroll into array for Calloc calls *) + (match ValueDomain.CArrays.get (Queries.to_value_domain_ask (Analyses.ask_of_ctx ctx)) a (None, (IdxDom.of_int (Cilfacade.ptrdiff_ikind ()) BI.zero)) with + | Blob (_,s,_) -> `Lifted s + | _ -> Queries.Result.top q + ) + | Blob (_,s,_) -> `Lifted s + | _ -> Queries.Result.top q) + ) | _ -> Queries.Result.top q end | Q.MayPointTo e -> begin match eval_rv_address (Analyses.ask_of_ctx ctx) ctx.global ctx.local e with - | Address a -> - let s = addrToLvalSet a in - if AD.mem Addr.UnknownPtr a - then Q.LS.add (dummyFunDec.svar, `NoOffset) s - else s + | Address a -> a | Bot -> Queries.Result.bot q (* TODO: remove *) + | Int i -> AD.of_int i | _ -> Queries.Result.top q end | Q.EvalThread e -> begin @@ -1291,14 +1326,20 @@ struct | Top -> Queries.Result.top q | Bot -> Queries.Result.bot q (* TODO: remove *) | Address a -> - let a' = AD.remove Addr.UnknownPtr a in (* run reachable_vars without unknown just to be safe *) - let xs = List.map addrToLvalSet (reachable_vars (Analyses.ask_of_ctx ctx) [a'] ctx.global ctx.local) in - let addrs = List.fold_left (Q.LS.join) (Q.LS.empty ()) xs in - if AD.mem Addr.UnknownPtr a then - Q.LS.add (dummyFunDec.svar, `NoOffset) addrs (* add unknown back *) + let a' = AD.remove Addr.UnknownPtr a in (* run reachable_vars without unknown just to be safe: TODO why? *) + let addrs = reachable_vars (Analyses.ask_of_ctx ctx) [a'] ctx.global ctx.local in + let addrs' = List.fold_left (AD.join) (AD.empty ()) addrs in + if AD.may_be_unknown a then + AD.add UnknownPtr addrs' (* add unknown back *) else - addrs - | _ -> Q.LS.empty () + addrs' + | Int i -> + begin match Cilfacade.typeOf e with + | t when Cil.isPointerType t -> AD.of_int i (* integer used as pointer *) + | _ + | exception Cilfacade.TypeOfError _ -> AD.empty () (* avoid unknown pointer result for non-pointer expression *) + end + | _ -> AD.empty () end | Q.ReachableUkTypes e -> begin match eval_rv_address (Analyses.ask_of_ctx ctx) ctx.global ctx.local e with @@ -1325,7 +1366,8 @@ struct (* ignore @@ printf "EvalStr Address: %a -> %s (must %i, may %i)\n" d_plainexp e (VD.short 80 (Address a)) (List.length @@ AD.to_var_must a) (List.length @@ AD.to_var_may a); *) begin match unrollType (Cilfacade.typeOf e) with | TPtr(TInt(IChar, _), _) -> - let lval = Mval.Exp.to_cil @@ Q.LS.choose @@ addrToLvalSet a in + let mval = List.hd (AD.to_mval a) in + let lval = Addr.Mval.to_cil mval in (try `Lifted (Bytes.to_string (Hashtbl.find char_array lval)) with Not_found -> Queries.Result.top q) | _ -> (* what about ISChar and IUChar? *) @@ -1392,7 +1434,7 @@ struct let t = match t_override with | Some t -> t | None -> - if a.f (Q.IsHeapVar x) then + if a.f (Q.IsAllocVar x) then (* the vtype of heap vars will be TVoid, so we need to trust the pointer we got to this to be of the right type *) (* i.e. use the static type of the pointer here *) lval_type @@ -1438,13 +1480,13 @@ struct (* Optimization to avoid evaluating integer values when setting them. The case when invariant = true requires the old_value to be sound for the meet. Allocated blocks are representend by Blobs with additional information, so they need to be looked-up. *) - let old_value = if not invariant && Cil.isIntegralType x.vtype && not (a.f (IsHeapVar x)) && offs = `NoOffset then begin + let old_value = if not invariant && Cil.isIntegralType x.vtype && not (a.f (IsAllocVar x)) && offs = `NoOffset then begin VD.bot_value ~varAttr:x.vattr lval_type end else Priv.read_global a priv_getg st x in let new_value = update_offset old_value in - M.tracel "hgh" "update_offset %a -> %a\n" VD.pretty old_value VD.pretty new_value; + if M.tracing then M.tracel "set" "update_offset %a -> %a\n" VD.pretty old_value VD.pretty new_value; let r = Priv.write_global ~invariant a priv_getg (priv_sideg ctx.sideg) st x new_value in if M.tracing then M.tracel "set" ~var:x.vname "update_one_addr: updated a global var '%s' \nstate:%a\n\n" x.vname D.pretty r; r @@ -1911,7 +1953,7 @@ struct - let forkfun (ctx:(D.t, G.t, C.t, V.t) Analyses.ctx) (lv: lval option) (f: varinfo) (args: exp list) : (lval option * varinfo * exp list) list = + let forkfun (ctx:(D.t, G.t, C.t, V.t) Analyses.ctx) (lv: lval option) (f: varinfo) (args: exp list) : (lval option * varinfo * exp list) list * bool = let create_thread lval arg v = try (* try to get function declaration *) @@ -1952,7 +1994,7 @@ struct else start_funvars in - List.filter_map (create_thread (Some (Mem id, NoOffset)) (Some ptc_arg)) start_funvars_with_unknown + List.filter_map (create_thread (Some (Mem id, NoOffset)) (Some ptc_arg)) start_funvars_with_unknown, false 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. @@ -1965,9 +2007,9 @@ struct let deep_flist = collect_invalidate ~deep:true (Analyses.ask_of_ctx ctx) ctx.global ctx.local deep_args in let flist = shallow_flist @ deep_flist in let addrs = List.concat_map AD.to_var_may flist in - if addrs <> [] then M.debug ~category:Analyzer "Spawning functions from unknown function: %a" (d_list ", " CilType.Varinfo.pretty) addrs; - List.filter_map (create_thread None None) addrs - | _, _ -> [] + 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 None None) addrs, true + | _, _ -> [], false let assert_fn ctx e refine = (* make the state meet the assertion in the rest of the code *) @@ -1979,7 +2021,7 @@ struct end let special_unknown_invalidate ctx ask gs st f args = - (if CilType.Varinfo.equal f dummyFunDec.svar then M.warn ~category:Imprecise "Unknown function ptr called"); + (if CilType.Varinfo.equal f dummyFunDec.svar then M.warn ~category:Imprecise ~tags:[Category Call] "Unknown function ptr called"); let desc = LF.find f in let shallow_addrs = LibraryDesc.Accesses.find desc.accs { kind = Write; deep = false } args in let deep_addrs = LibraryDesc.Accesses.find desc.accs { kind = Write; deep = true } args in @@ -2002,21 +2044,95 @@ struct let st' = invalidate ~deep:false ~ctx (Analyses.ask_of_ctx ctx) gs st shallow_addrs in invalidate ~deep:true ~ctx (Analyses.ask_of_ctx ctx) gs st' deep_addrs - let check_free_of_non_heap_mem ctx special_fn ptr = + let check_invalid_mem_dealloc ctx special_fn ptr = + let has_non_heap_var = AD.exists (function + | Addr (v,_) -> is_not_heap_alloc_var ctx v + | _ -> false) + in + let has_non_zero_offset = AD.exists (function + | Addr (_,o) -> Offs.cmp_zero_offset o <> `MustZero + | _ -> false) + in match eval_rv_address (Analyses.ask_of_ctx ctx) ctx.global ctx.local ptr with | Address a -> - let points_to_set = addrToLvalSet a in - if Q.LS.is_top points_to_set then - M.warn ~category:(Behavior (Undefined InvalidMemoryDeallocation)) ~tags:[CWE 590] "Points-to set for pointer %a in function %s is top. Potential free of non-dynamically allocated memory may occur" d_exp ptr special_fn.vname - else if (Q.LS.exists (fun (v, _) -> not (ctx.ask (Q.IsHeapVar v))) points_to_set) || (AD.mem Addr.UnknownPtr a) then + if AD.is_top a then ( + AnalysisStateUtil.set_mem_safety_flag InvalidFree; + M.warn ~category:(Behavior (Undefined InvalidMemoryDeallocation)) ~tags:[CWE 590] "Points-to set for pointer %a in function %s is top. Potentially invalid memory deallocation may occur" d_exp ptr special_fn.vname + ) else if has_non_heap_var a then ( + AnalysisStateUtil.set_mem_safety_flag InvalidFree; M.warn ~category:(Behavior (Undefined InvalidMemoryDeallocation)) ~tags:[CWE 590] "Free of non-dynamically allocated memory in function %s for pointer %a" special_fn.vname d_exp ptr - | _ -> M.warn ~category:MessageCategory.Analyzer "Pointer %a in function %s doesn't evaluate to a valid address." d_exp ptr special_fn.vname + ) else if has_non_zero_offset a then ( + AnalysisStateUtil.set_mem_safety_flag InvalidFree; + M.warn ~category:(Behavior (Undefined InvalidMemoryDeallocation)) ~tags:[CWE 761] "Free of memory not at start of buffer in function %s for pointer %a" special_fn.vname d_exp ptr + ) + | _ -> + AnalysisStateUtil.set_mem_safety_flag InvalidFree; + M.warn ~category:(Behavior (Undefined InvalidMemoryDeallocation)) ~tags:[CWE 590] "Pointer %a in function %s doesn't evaluate to a valid address. Invalid memory deallocation may occur" d_exp ptr special_fn.vname + + let points_to_heap_only ctx ptr = + match ctx.ask (Queries.MayPointTo ptr) with + | a when not (Queries.AD.is_top a)-> + Queries.AD.for_all (function + | Addr (v, _) -> ctx.ask (Queries.IsHeapVar v) + | _ -> false + ) a + | _ -> false + + let get_size_of_ptr_target ctx ptr = + let intdom_of_int x = + ID.of_int (Cilfacade.ptrdiff_ikind ()) (Z.of_int x) + in + let size_of_type_in_bytes typ = + let typ_size_in_bytes = (bitsSizeOf typ) / 8 in + intdom_of_int typ_size_in_bytes + in + if points_to_heap_only ctx ptr then + (* Ask for BlobSize from the base address (the second component being set to true) in order to avoid BlobSize giving us bot *) + ctx.ask (Queries.BlobSize {exp = ptr; base_address = true}) + else + match ctx.ask (Queries.MayPointTo ptr) with + | a when not (Queries.AD.is_top a) -> + let pts_list = Queries.AD.elements a in + let pts_elems_to_sizes (addr: Queries.AD.elt) = + begin match addr with + | Addr (v, _) -> + begin match v.vtype with + | TArray (item_typ, _, _) -> + let item_typ_size_in_bytes = size_of_type_in_bytes item_typ in + begin match ctx.ask (Queries.EvalLength ptr) with + | `Lifted arr_len -> + let arr_len_casted = ID.cast_to (Cilfacade.ptrdiff_ikind ()) arr_len in + begin + try `Lifted (ID.mul item_typ_size_in_bytes arr_len_casted) + with IntDomain.ArithmeticOnIntegerBot _ -> `Bot + end + | `Bot -> `Bot + | `Top -> `Top + end + | _ -> + let type_size_in_bytes = size_of_type_in_bytes v.vtype in + `Lifted type_size_in_bytes + end + | _ -> `Top + end + in + (* Map each points-to-set element to its size *) + let pts_sizes = List.map pts_elems_to_sizes pts_list in + (* Take the smallest of all sizes that ptr's contents may have *) + begin match pts_sizes with + | [] -> `Bot + | [x] -> x + | x::xs -> List.fold_left ValueDomainQueries.ID.join x xs + end + | _ -> + (M.warn "Pointer %a has a points-to-set of top. An invalid memory access might occur" d_exp ptr; + `Top) let special ctx (lv:lval option) (f: varinfo) (args: exp list) = let invalidate_ret_lv st = match lv with | Some lv -> if M.tracing then M.tracel "invalidate" "Invalidating lhs %a for function call %s\n" d_plainlval lv f.vname; - invalidate ~ctx (Analyses.ask_of_ctx ctx) ctx.global st [Cil.mkAddrOrStartOf lv] + invalidate ~deep:false ~ctx (Analyses.ask_of_ctx ctx) ctx.global st [Cil.mkAddrOrStartOf lv] | None -> st in let addr_type_of_exp exp = @@ -2024,19 +2140,34 @@ struct let addr = eval_lv (Analyses.ask_of_ctx ctx) ctx.global ctx.local lval in (addr, AD.type_of addr) in - let forks = forkfun ctx lv f args in + let forks, multiple = forkfun ctx lv f args in if M.tracing then if not (List.is_empty forks) then M.tracel "spawn" "Base.special %s: spawning functions %a\n" f.vname (d_list "," CilType.Varinfo.pretty) (List.map BatTuple.Tuple3.second forks); - List.iter (BatTuple.Tuple3.uncurry ctx.spawn) forks; + List.iter (BatTuple.Tuple3.uncurry (ctx.spawn ~multiple)) forks; let st: store = ctx.local in let gs = ctx.global in let desc = LF.find f in - let memory_copying dst src = + let memory_copying dst src n = + let dest_size = get_size_of_ptr_target ctx dst in + let n_intdom = Option.map_default (fun exp -> ctx.ask (Queries.EvalInt exp)) `Bot n in + let dest_size_equal_n = + match dest_size, n_intdom with + | `Lifted ds, `Lifted n -> + let casted_ds = ID.cast_to (Cilfacade.ptrdiff_ikind ()) ds in + let casted_n = ID.cast_to (Cilfacade.ptrdiff_ikind ()) n in + let ds_eq_n = + begin try ID.eq casted_ds casted_n + with IntDomain.ArithmeticOnIntegerBot _ -> ID.top_of @@ Cilfacade.ptrdiff_ikind () + end + in + Option.default false (ID.to_bool ds_eq_n) + | _ -> false + in let dest_a, dest_typ = addr_type_of_exp dst in let src_lval = mkMem ~addr:(Cil.stripCasts src) ~off:NoOffset in let src_typ = eval_lv (Analyses.ask_of_ctx ctx) gs st src_lval |> AD.type_of in (* when src and destination type coincide, take value from the source, otherwise use top *) - let value = if typeSig dest_typ = typeSig src_typ then + let value = if (typeSig dest_typ = typeSig src_typ) && dest_size_equal_n then let src_cast_lval = mkMem ~addr:(Cilfacade.mkCast ~e:src ~newt:(TPtr (dest_typ, []))) ~off:NoOffset in eval_rv (Analyses.ask_of_ctx ctx) gs st (Lval src_cast_lval) else @@ -2096,14 +2227,14 @@ struct let dest_a, dest_typ = addr_type_of_exp dest in let value = VD.zero_init_value dest_typ in set ~ctx (Analyses.ask_of_ctx ctx) gs st dest_a dest_typ value - | Memcpy { dest = dst; src }, _ -> - memory_copying dst src + | Memcpy { dest = dst; src; n; }, _ -> (* TODO: use n *) + memory_copying dst src (Some n) (* strcpy(dest, src); *) | Strcpy { dest = dst; src; n = None }, _ -> let dest_a, dest_typ = addr_type_of_exp dst in (* when dest surely isn't a string literal, try copying src to dest *) if AD.string_writing_defined dest_a then - memory_copying dst src + memory_copying dst src None else (* else return top (after a warning was issued) *) set ~ctx (Analyses.ask_of_ctx ctx) gs st dest_a dest_typ (VD.top_value (unrollType dest_typ)) @@ -2206,6 +2337,24 @@ struct | _ -> failwith ("non-floating-point argument in call to function "^f.vname) end in + let apply_abs ik x = + let eval_x = eval_rv (Analyses.ask_of_ctx ctx) gs st x in + begin match eval_x with + | Int int_x -> + let xcast = ID.cast_to ik int_x in + (* the absolute value of the most-negative value is out of range for 2'complement types *) + (match (ID.minimal xcast), (ID.minimal (ID.top_of ik)) with + | _, None + | None, _ -> ID.top_of ik + | Some mx, Some mm when Z.equal mx mm -> ID.top_of ik + | _, _ -> + let x1 = ID.neg (ID.meet (ID.ending ik Z.zero) xcast) in + let x2 = ID.meet (ID.starting ik Z.zero) xcast in + ID.join x1 x2 + ) + | _ -> failwith ("non-integer argument in call to function "^f.vname) + end + in let result:value = begin match fun_args with | Nan (fk, str) when Cil.isPointerType (Cilfacade.typeOf str) -> Float (FD.nan_of fk) @@ -2234,6 +2383,8 @@ struct | Isunordered (x,y) -> Int(ID.cast_to IInt (apply_binary FDouble FD.unordered x y)) | Fmax (fd, x ,y) -> Float (apply_binary fd FD.fmax x y) | Fmin (fd, x ,y) -> Float (apply_binary fd FD.fmin x y) + | Sqrt (fk, x) -> Float (apply_unary fk FD.sqrt x) + | Abs (ik, x) -> Int (ID.cast_to ik (apply_abs ik x)) end in begin match lv with @@ -2263,13 +2414,22 @@ struct | 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 + | 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.ask gs st size); *) + set_many ~ctx (Analyses.ask_of_ctx ctx) gs st [(heap_var, TVoid [], Blob (VD.bot (), eval_int (Analyses.ask_of_ctx ctx) gs st size, true)); + (eval_lv (Analyses.ask_of_ctx ctx) gs st lv, (Cilfacade.typeOfLval lv), Address heap_var)] + | _ -> st + end | Malloc size, _ -> begin match lv with | Some lv -> let heap_var = if (get_bool "sem.malloc.fail") - then AD.join (AD.of_var (heap_var ctx)) AD.null_ptr - else AD.of_var (heap_var ctx) + then AD.join (AD.of_var (heap_var false ctx)) AD.null_ptr + else AD.of_var (heap_var false ctx) in (* ignore @@ printf "malloc will allocate %a bytes\n" ID.pretty (eval_int ctx.ask gs st size); *) set_many ~ctx (Analyses.ask_of_ctx ctx) gs st [(heap_var, TVoid [], Blob (VD.bot (), eval_int (Analyses.ask_of_ctx ctx) gs st size, true)); @@ -2279,21 +2439,33 @@ struct | Calloc { count = n; size }, _ -> begin match lv with | Some lv -> (* array length is set to one, as num*size is done when turning into `Calloc *) - let heap_var = heap_var ctx in + let heap_var = heap_var false ctx in let add_null addr = if get_bool "sem.malloc.fail" then AD.join addr AD.null_ptr (* calloc can fail and return NULL *) else addr in let ik = Cilfacade.ptrdiff_ikind () in - let blobsize = ID.mul (ID.cast_to ik @@ eval_int (Analyses.ask_of_ctx ctx) gs st size) (ID.cast_to ik @@ eval_int (Analyses.ask_of_ctx ctx) gs st n) 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 (Analyses.ask_of_ctx ctx) gs st [(add_null (AD.of_var heap_var), TVoid [], Array (CArrays.make (IdxDom.of_int (Cilfacade.ptrdiff_ikind ()) BI.one) (Blob (VD.bot (), blobsize, false)))); - (eval_lv (Analyses.ask_of_ctx ctx) gs st lv, (Cilfacade.typeOfLval lv), Address (add_null (AD.of_mval (heap_var, `Index (IdxDom.of_int (Cilfacade.ptrdiff_ikind ()) BI.zero, `NoOffset)))))] + let sizeval = eval_int (Analyses.ask_of_ctx ctx) gs st size in + let countval = eval_int (Analyses.ask_of_ctx ctx) gs st n in + if ID.to_int countval = Some Z.one then ( + set_many ~ctx (Analyses.ask_of_ctx ctx) gs st [ + (add_null (AD.of_var heap_var), TVoid [], Blob (VD.bot (), sizeval, false)); + (eval_lv (Analyses.ask_of_ctx ctx) gs st lv, (Cilfacade.typeOfLval lv), Address (add_null (AD.of_var heap_var))) + ] + ) + else ( + 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 (Analyses.ask_of_ctx ctx) gs st [ + (add_null (AD.of_var heap_var), TVoid [], Array (CArrays.make (IdxDom.of_int (Cilfacade.ptrdiff_ikind ()) BI.one) (Blob (VD.bot (), blobsize, false)))); + (eval_lv (Analyses.ask_of_ctx ctx) gs st lv, (Cilfacade.typeOfLval lv), Address (add_null (AD.of_mval (heap_var, `Index (IdxDom.of_int (Cilfacade.ptrdiff_ikind ()) BI.zero, `NoOffset))))) + ] + ) | _ -> st end | Realloc { ptr = p; size }, _ -> (* Realloc shouldn't be passed non-dynamically allocated memory *) - check_free_of_non_heap_mem ctx f p; + check_invalid_mem_dealloc ctx f p; begin match lv with | Some lv -> let ask = Analyses.ask_of_ctx ctx in @@ -2310,7 +2482,7 @@ struct let p_addr_get = get ask gs st p_addr' None in (* implicitly includes join of malloc value (VD.bot) *) let size_int = eval_int ask gs st size in let heap_val:value = Blob (p_addr_get, size_int, true) in (* copy old contents with new size *) - let heap_addr = AD.of_var (heap_var ctx) in + let heap_addr = AD.of_var (heap_var false ctx) in let heap_addr' = if get_bool "sem.malloc.fail" then AD.join heap_addr AD.null_ptr @@ -2327,7 +2499,7 @@ struct end | Free ptr, _ -> (* Free shouldn't be passed non-dynamically allocated memory *) - check_free_of_non_heap_mem ctx f ptr; + check_invalid_mem_dealloc ctx f ptr; st | Assert { exp; refine; _ }, _ -> assert_fn ctx exp refine | Setjmp { env }, _ -> @@ -2388,34 +2560,38 @@ struct in if get_bool "sem.noreturn.dead_code" && Cil.hasAttribute "noreturn" f.vattr then raise Deadcode else st - let combine_st ctx (local_st : store) (fun_st : store) (tainted_lvs : Q.LS.t) : store = + let combine_st ctx (local_st : store) (fun_st : store) (tainted_lvs : AD.t) : store = let ask = (Analyses.ask_of_ctx ctx) in - Q.LS.fold (fun (v, o) st -> - if CPA.mem v fun_st.cpa then - let lval = Mval.Exp.to_cil (v,o) in - let address = eval_lv ask ctx.global st lval in - let lval_type = (AD.type_of address) in - if M.tracing then M.trace "taintPC" "updating %a; type: %a\n" Mval.Exp.pretty (v, o) d_type lval_type; - match (CPA.find_opt v (fun_st.cpa)), lval_type with - | None, _ -> st - (* partitioned arrays cannot be copied by individual lvalues, so if tainted just copy the whole callee value for the array variable *) - | Some (Array a), _ when (CArrays.domain_of_t a) = PartitionedDomain -> {st with cpa = CPA.add v (Array a) st.cpa} - (* "get" returned "unknown" when applied to a void type, so special case void types. This caused problems with some sv-comps (e.g. regtest 64 11) *) - | Some voidVal, TVoid _ -> {st with cpa = CPA.add v voidVal st.cpa} - | _, _ -> begin - let new_val = get ask ctx.global fun_st address None in - if M.tracing then M.trace "taintPC" "update val: %a\n\n" VD.pretty new_val; - let st' = set_savetop ~ctx ask ctx.global st address lval_type new_val in - let partDep = Dep.find_opt v fun_st.deps in - match partDep with - | None -> st' - (* if a var partitions an array, all cpa-info for arrays it may partition are added from callee to caller *) - | Some deps -> {st' with cpa = (Dep.VarSet.fold (fun v accCPA -> let val_opt = CPA.find_opt v fun_st.cpa in - match val_opt with - | None -> accCPA - | Some new_val -> CPA.add v new_val accCPA ) deps st'.cpa)} - end - else st) tainted_lvs local_st + AD.fold (fun addr st -> + match addr with + | Addr.Addr (v,o) -> + if CPA.mem v fun_st.cpa then + let lval = Addr.Mval.to_cil (v,o) in + let address = eval_lv ask ctx.global st lval in + let lval_type = Addr.type_of addr in + if M.tracing then M.trace "taintPC" "updating %a; type: %a\n" Addr.Mval.pretty (v,o) d_type lval_type; + match (CPA.find_opt v (fun_st.cpa)), lval_type with + | None, _ -> st + (* partitioned arrays cannot be copied by individual lvalues, so if tainted just copy the whole callee value for the array variable *) + | Some (Array a), _ when (CArrays.domain_of_t a) = PartitionedDomain -> {st with cpa = CPA.add v (Array a) st.cpa} + (* "get" returned "unknown" when applied to a void type, so special case void types. This caused problems with some sv-comps (e.g. regtest 64 11) *) + | Some voidVal, TVoid _ -> {st with cpa = CPA.add v voidVal st.cpa} + | _, _ -> begin + let new_val = get ask ctx.global fun_st address None in + if M.tracing then M.trace "taintPC" "update val: %a\n\n" VD.pretty new_val; + let st' = set_savetop ~ctx ask ctx.global st address lval_type new_val in + let partDep = Dep.find_opt v fun_st.deps in + match partDep with + | None -> st' + (* if a var partitions an array, all cpa-info for arrays it may partition are added from callee to caller *) + | Some deps -> {st' with cpa = (Dep.VarSet.fold (fun v accCPA -> let val_opt = CPA.find_opt v fun_st.cpa in + match val_opt with + | None -> accCPA + | Some new_val -> CPA.add v new_val accCPA ) deps st'.cpa)} + end + else st + | _ -> st + ) tainted_lvs local_st let combine_env ctx lval fexp f args fc au (f_ask: Queries.ask) = let combine_one (st: D.t) (fun_st: D.t) = @@ -2430,9 +2606,9 @@ struct let cpa_noreturn = CPA.remove (return_varinfo ()) fun_st.cpa in let ask = (Analyses.ask_of_ctx ctx) in let tainted = f_ask.f Q.MayBeTainted in - if M.tracing then M.trace "taintPC" "combine for %s in base: tainted: %a\n" f.svar.vname Q.LS.pretty tainted; + if M.tracing then M.trace "taintPC" "combine for %s in base: tainted: %a\n" f.svar.vname AD.pretty tainted; if M.tracing then M.trace "taintPC" "combine base:\ncaller: %a\ncallee: %a\n" CPA.pretty st.cpa CPA.pretty fun_st.cpa; - if Q.LS.is_top tainted then + if AD.is_top tainted then let cpa_local = CPA.filter (fun x _ -> not (is_global ask x)) st.cpa in let cpa' = CPA.fold CPA.add cpa_noreturn cpa_local in (* add cpa_noreturn to cpa_local *) if M.tracing then M.trace "taintPC" "combined: %a\n" CPA.pretty cpa'; @@ -2447,7 +2623,10 @@ struct let cpa_caller' = CPA.fold CPA.add cpa_new cpa_caller in if M.tracing then M.trace "taintPC" "cpa_caller': %a\n" CPA.pretty cpa_caller'; (* remove lvals from the tainted set that correspond to variables for which we just added a new mapping from the callee*) - let tainted = Q.LS.filter (fun (v, _) -> not (CPA.mem v cpa_new)) tainted in + let tainted = AD.filter (function + | Addr.Addr (v,_) -> not (CPA.mem v cpa_new) + | _ -> false + ) tainted in let st_combined = combine_st ctx {st with cpa = cpa_caller'} fun_st tainted in if M.tracing then M.trace "taintPC" "combined: %a\n" CPA.pretty st_combined.cpa; { fun_st with cpa = st_combined.cpa } @@ -2491,7 +2670,7 @@ struct in combine_one ctx.local after - let threadenter ctx (lval: lval option) (f: varinfo) (args: exp list): D.t list = + let threadenter ctx ~multiple (lval: lval option) (f: varinfo) (args: exp list): D.t list = match Cilfacade.find_varinfo_fundec f with | fd -> [make_entry ~thread:true ctx fd args] @@ -2501,7 +2680,7 @@ struct let st = special_unknown_invalidate ctx (Analyses.ask_of_ctx ctx) ctx.global st f args in [st] - let threadspawn ctx (lval: lval option) (f: varinfo) (args: exp list) fctx: D.t = + let threadspawn ctx ~multiple (lval: lval option) (f: varinfo) (args: exp list) fctx: D.t = begin match lval with | Some lval -> begin match ThreadId.get_current (Analyses.ask_of_ctx fctx) with @@ -2551,6 +2730,7 @@ struct | MayBeThreadReturn | PartAccess _ | IsHeapVar _ + | IsAllocVar _ | IsMultiple _ | CreatedThreads | MustJoinedThreads -> diff --git a/src/analyses/baseInvariant.ml b/src/analyses/baseInvariant.ml index aaef8076df..304d3e55ad 100644 --- a/src/analyses/baseInvariant.ml +++ b/src/analyses/baseInvariant.ml @@ -410,6 +410,18 @@ struct meet_bin c c else a, b + | BAnd as op -> + (* we only attempt to refine a here *) + let a = + match ID.to_int b with + | Some x when BI.equal x BI.one -> + (match ID.to_bool c with + | Some true -> ID.meet a (ID.of_congruence ikind (Z.one, Z.of_int 2)) + | Some false -> ID.meet a (ID.of_congruence ikind (Z.zero, Z.of_int 2)) + | None -> if M.tracing then M.tracel "inv" "Unhandled case for operator x %a 1 = %a\n" d_binop op ID.pretty c; a) + | _ -> if M.tracing then M.tracel "inv" "Unhandled case for operator x %a %a = %a\n" d_binop op ID.pretty b ID.pretty c; a + in + a, b | op -> if M.tracing then M.tracel "inv" "Unhandled operator %a\n" d_binop op; a, b @@ -545,6 +557,11 @@ struct in let eval e st = eval_rv a gs st e in let eval_bool e st = match eval e st with Int i -> ID.to_bool i | _ -> None in + let unroll_fk_of_exp e = + match unrollType (Cilfacade.typeOf e) with + | TFloat (fk, _) -> fk + | _ -> failwith "value which was expected to be a float is of different type?!" + in let rec inv_exp c_typed exp (st:D.t): D.t = (* trying to improve variables in an expression so it is bottom means dead code *) if VD.is_bot_value c_typed then contra st @@ -681,6 +698,7 @@ struct | Lval x, (Int _ | Float _ | Address _) -> (* meet x with c *) let update_lval c x c' pretty = refine_lv ctx a gs st c x c' pretty exp in let t = Cil.unrollType (Cilfacade.typeOfLval x) in (* unroll type to deal with TNamed *) + if M.tracing then M.trace "invSpecial" "invariant with Lval %a, c_typed %a, type %a\n" d_lval x VD.pretty c_typed d_type t; begin match c_typed with | Int c -> let c' = match t with @@ -690,7 +708,35 @@ struct | TFloat (fk, _) -> Float (FD.of_int fk c) | _ -> Int c in - update_lval c x c' ID.pretty + (* handle special calls *) + begin match x, t with + | (Var v, offs), TInt (ik, _) -> + let tmpSpecial = ctx.ask (Queries.TmpSpecial (v, Offset.Exp.of_cil offs)) in + if M.tracing then M.trace "invSpecial" "qry Result: %a\n" Queries.ML.pretty tmpSpecial; + begin match tmpSpecial with + | `Lifted (Abs (ik, xInt)) -> + let c' = ID.cast_to ik c in (* different ik! *) + inv_exp (Int (ID.join c' (ID.neg c'))) xInt st + | tmpSpecial -> + begin match ID.to_bool c with + | Some tv -> + begin match tmpSpecial with + | `Lifted (Isfinite xFloat) when tv -> inv_exp (Float (FD.finite (unroll_fk_of_exp xFloat))) xFloat st + | `Lifted (Isnan xFloat) when tv -> inv_exp (Float (FD.nan_of (unroll_fk_of_exp xFloat))) xFloat st + (* should be correct according to C99 standard*) + (* The following do to_bool and of_bool to convert Not{0} into 1 for downstream float inversions *) + | `Lifted (Isgreater (xFloat, yFloat)) -> inv_exp (Int (ID.of_bool ik tv)) (BinOp (Gt, xFloat, yFloat, (typeOf xFloat))) st + | `Lifted (Isgreaterequal (xFloat, yFloat)) -> inv_exp (Int (ID.of_bool ik tv)) (BinOp (Ge, xFloat, yFloat, (typeOf xFloat))) st + | `Lifted (Isless (xFloat, yFloat)) -> inv_exp (Int (ID.of_bool ik tv)) (BinOp (Lt, xFloat, yFloat, (typeOf xFloat))) st + | `Lifted (Islessequal (xFloat, yFloat)) -> inv_exp (Int (ID.of_bool ik tv)) (BinOp (Le, xFloat, yFloat, (typeOf xFloat))) st + | `Lifted (Islessgreater (xFloat, yFloat)) -> inv_exp (Int (ID.of_bool ik tv)) (BinOp (LOr, (BinOp (Lt, xFloat, yFloat, (typeOf xFloat))), (BinOp (Gt, xFloat, yFloat, (typeOf xFloat))), (TInt (IBool, [])))) st + | _ -> update_lval c x c' ID.pretty + end + | None -> update_lval c x c' ID.pretty + end + end + | _, _ -> update_lval c x c' ID.pretty + end | Float c -> let c' = match t with (* | TPtr _ -> ..., pointer conversion from/to float is not supported *) @@ -700,7 +746,24 @@ struct | TFloat (fk, _) -> Float (FD.cast_to fk c) | _ -> Float c in - update_lval c x c' FD.pretty + (* handle special calls *) + begin match x, t with + | (Var v, offs), TFloat (fk, _) -> + let tmpSpecial = ctx.ask (Queries.TmpSpecial (v, Offset.Exp.of_cil offs)) in + if M.tracing then M.trace "invSpecial" "qry Result: %a\n" Queries.ML.pretty tmpSpecial; + begin match tmpSpecial with + | `Lifted (Ceil (ret_fk, xFloat)) -> inv_exp (Float (FD.inv_ceil (FD.cast_to ret_fk c))) xFloat st + | `Lifted (Floor (ret_fk, xFloat)) -> inv_exp (Float (FD.inv_floor (FD.cast_to ret_fk c))) xFloat st + | `Lifted (Fabs (ret_fk, xFloat)) -> + let inv = FD.inv_fabs (FD.cast_to ret_fk c) in + if FD.is_bot inv then + raise Analyses.Deadcode + else + inv_exp (Float inv) xFloat st + | _ -> update_lval c x c' FD.pretty + end + | _ -> update_lval c x c' FD.pretty + end | Address c -> let c' = c_typed in (* TODO: need any of the type-matching nonsense? *) update_lval c x c' AD.pretty @@ -742,15 +805,15 @@ struct | BinOp ((Lt | Gt | Le | Ge | Eq | Ne | LAnd | LOr), _, _, _) -> true | _ -> false in - try - let ik = Cilfacade.get_ikind_exp exp in + match Cilfacade.get_ikind_exp exp with + | ik -> let itv = if not tv || is_cmp exp then (* false is 0, but true can be anything that is not 0, except for comparisons which yield 1 *) ID.of_bool ik tv (* this will give 1 for true which is only ok for comparisons *) else ID.of_excl_list ik [BI.zero] (* Lvals, Casts, arithmetic operations etc. should work with true = non_zero *) in inv_exp (Int itv) exp st - with Invalid_argument _ -> + | exception Invalid_argument _ -> let fk = Cilfacade.get_fkind_exp exp in let ftv = if not tv then (* false is 0, but true can be anything that is not 0, except for comparisons which yield 1 *) FD.of_const fk 0. diff --git a/src/analyses/basePriv.ml b/src/analyses/basePriv.ml index 0154924a1c..f9a4a22f44 100644 --- a/src/analyses/basePriv.ml +++ b/src/analyses/basePriv.ml @@ -211,12 +211,12 @@ struct let thread_join ?(force=false) ask get e st = st let thread_return ask get set tid st = st - let invariant_global getg g = - match g with - | `Left _ -> (* mutex *) - Invariant.none + let invariant_global getg = function | `Right g' -> (* global *) ValueDomain.invariant_global (read_unprotected_global getg) g' + | _ -> (* mutex *) + Invariant.none + end module PerMutexOplusPriv: S = @@ -230,7 +230,7 @@ struct CPA.find x st.cpa (* let read_global ask getg cpa x = let (cpa', v) as r = read_global ask getg cpa x in - ignore (Pretty.printf "READ GLOBAL %a (%a, %B) = %a\n" CilType.Varinfo.pretty x CilType.Location.pretty !Tracing.current_loc (is_unprotected ask x) VD.pretty v); + ignore (Pretty.printf "READ GLOBAL %a (%a, %B) = %a\n" CilType.Varinfo.pretty x CilType.Location.pretty !Goblint_tracing.current_loc (is_unprotected ask x) VD.pretty v); r *) let write_global ?(invariant=false) ask getg sideg (st: BaseComponents (D).t) x v = let cpa' = CPA.add x v st.cpa in @@ -625,13 +625,11 @@ struct let get_mutex_inits' = CPA.find x get_mutex_inits in VD.join get_mutex_global_x' get_mutex_inits' - let invariant_global getg g = - match g with - | `Left (`Left _) -> (* mutex *) - Invariant.none - | `Left (`Right g') -> (* global *) - ValueDomain.invariant_global (read_unprotected_global getg) g' - | `Right _ -> (* thread *) + let invariant_global getg = function + | `Middle g -> (* global *) + ValueDomain.invariant_global (read_unprotected_global getg) g + | `Left _ + | `Right _ -> (* mutex or thread *) Invariant.none end @@ -847,16 +845,15 @@ struct open Locksets - let invariant_global getg g = - match g with - | `Left _ -> (* mutex *) - Invariant.none + let invariant_global getg = function | `Right g' -> (* global *) ValueDomain.invariant_global (fun x -> GWeak.fold (fun s' tm acc -> WeakRange.fold_weak VD.join tm acc ) (G.weak (getg (V.global x))) (VD.bot ()) ) g' + | _ -> (* mutex *) + Invariant.none let invariant_vars ask getg st = let module VS = Set.Make (CilType.Varinfo) in @@ -893,7 +890,7 @@ end module MinePrivBase = struct include NoFinalize - include ConfCheck.RequireMutexPathSensInit + include ConfCheck.RequireMutexPathSensOneMainInit include MutexGlobals (* explicit not needed here because G is Prod anyway? *) let thread_join ?(force=false) ask get e st = st @@ -1668,7 +1665,7 @@ struct let read_global ask getg st x = let v = Priv.read_global ask getg st x in if !AnalysisState.postsolving && !is_dumping then - LVH.modify_def (VD.bot ()) (!Tracing.current_loc, x) (VD.join v) lvh; + LVH.modify_def (VD.bot ()) (!Goblint_tracing.current_loc, x) (VD.join v) lvh; v let dump () = diff --git a/src/analyses/commonPriv.ml b/src/analyses/commonPriv.ml index 1b92cb320d..73a2e75de1 100644 --- a/src/analyses/commonPriv.ml +++ b/src/analyses/commonPriv.ml @@ -19,12 +19,14 @@ struct if not mutex_active then failwith "Privatization (to be useful) requires the 'mutex' analysis to be enabled (it is currently disabled)" end - module RequireMutexPathSensInit = + module RequireMutexPathSensOneMainInit = struct let init () = RequireMutexActivatedInit.init (); let mutex_path_sens = List.mem "mutex" (GobConfig.get_string_list "ana.path_sens") in if not mutex_path_sens then failwith "The activated privatization requires the 'mutex' analysis to be enabled & path sensitive (it is currently enabled, but not path sensitive)"; + let mainfuns = List.length @@ GobConfig.get_list "mainfun" in + if not (mainfuns = 1) then failwith "The activated privatization requires exactly one main function to be specified"; () end @@ -60,14 +62,10 @@ struct ask.f (Q.MustBeProtectedBy {mutex=m; global=x; write=true; protection}) let protected_vars (ask: Q.ask): varinfo list = - let module VS = Set.Make (CilType.Varinfo) in - Q.LS.fold (fun (v, _) acc -> - let m = ValueDomain.Addr.of_var v in (* TODO: don't ignore offsets *) - Q.LS.fold (fun l acc -> - VS.add (fst l) acc (* always `NoOffset from mutex analysis *) - ) (ask.f (Q.MustProtectedVars {mutex = m; write = true})) acc - ) (ask.f Q.MustLockset) VS.empty - |> VS.elements + Q.AD.fold (fun m acc -> + Q.VS.join (ask.f (Q.MustProtectedVars {mutex = m; write = true})) acc + ) (ask.f Q.MustLockset) (Q.VS.empty ()) + |> Q.VS.elements end module MutexGlobals = @@ -87,11 +85,10 @@ struct end module V = struct - (* TODO: Either3? *) - include Printable.Either (Printable.Either (VMutex) (VMutexInits)) (VGlobal) + include Printable.Either3 (VMutex) (VMutexInits) (VGlobal) let name () = "MutexGlobals" - let mutex x: t = `Left (`Left x) - let mutex_inits: t = `Left (`Right ()) + let mutex x: t = `Left x + let mutex_inits: t = `Middle () let global x: t = `Right x end @@ -126,10 +123,8 @@ struct if !AnalysisState.global_initialization then Lockset.empty () else - let ls = ask.f Queries.MustLockset in - Q.LS.fold (fun (var, offs) acc -> - Lockset.add (Lock.of_mval (var, Lock.Offs.of_exp offs)) acc - ) ls (Lockset.empty ()) + 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 *) (* 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 *) diff --git a/src/analyses/condVars.ml b/src/analyses/condVars.ml index 5a2e97139c..3b23dc03fc 100644 --- a/src/analyses/condVars.ml +++ b/src/analyses/condVars.ml @@ -64,16 +64,16 @@ struct let (>?) = Option.bind let mayPointTo ctx exp = - match ctx.ask (Queries.MayPointTo exp) with - | a when not (Queries.LS.is_top a) && Queries.LS.cardinal a > 0 -> - let top_elt = (dummyFunDec.svar, `NoOffset) in - let a' = if Queries.LS.mem top_elt a then ( - M.info ~category:Unsound "mayPointTo: query result for %a contains TOP!" d_exp exp; (* UNSOUND *) - Queries.LS.remove top_elt a - ) else a - in - Queries.LS.elements a' - | _ -> [] + let ad = ctx.ask (Queries.MayPointTo exp) in + let a' = if Queries.AD.mem UnknownPtr ad then ( + M.info ~category:Unsound "mayPointTo: query result for %a contains TOP!" d_exp exp; (* UNSOUND *) + Queries.AD.remove UnknownPtr ad + ) else ad + in + List.filter_map (function + | ValueDomain.Addr.Addr (v,o) -> Some (v, ValueDomain.Addr.Offs.to_exp o) (* TODO: use unconverted addrs in domain? *) + | _ -> None + ) (Queries.AD.elements a') let mustPointTo ctx exp = (* this is just to get Mval.Exp *) match mayPointTo ctx exp with @@ -155,8 +155,8 @@ struct ctx.local let startstate v = D.bot () - let threadenter ctx lval f args = [D.bot ()] - let threadspawn ctx lval f args fctx = ctx.local + let threadenter ctx ~multiple lval f args = [D.bot ()] + let threadspawn ctx ~multiple lval f args fctx = ctx.local let exitstate v = D.bot () end diff --git a/src/analyses/expsplit.ml b/src/analyses/expsplit.ml index f121d0380e..fef3d9ff9f 100644 --- a/src/analyses/expsplit.ml +++ b/src/analyses/expsplit.ml @@ -84,9 +84,9 @@ struct in emit_splits ctx d - let threadenter ctx lval f args = [ctx.local] + let threadenter ctx ~multiple lval f args = [ctx.local] - let threadspawn ctx lval f args fctx = + let threadspawn ctx ~multiple lval f args fctx = emit_splits_ctx ctx let event ctx (event: Events.t) octx = diff --git a/src/analyses/extractPthread.ml b/src/analyses/extractPthread.ml index 2041c23e1b..8412a65683 100644 --- a/src/analyses/extractPthread.ml +++ b/src/analyses/extractPthread.ml @@ -220,7 +220,7 @@ module Tbls = struct let make_new_val table k = (* TODO: all same key occurrences instead *) let line = -5 - all_keys_count table in - let loc = { !Tracing.current_loc with line } in + let loc = { !Goblint_tracing.current_loc with line } in MyCFG.Statement { (mkStmtOneInstr @@ Set (var dummyFunDec.svar, zero, loc, loc)) with sid = new_sid () @@ -244,7 +244,7 @@ let fun_ctx ctx f = f.vname ^ "_" ^ ctx_hash -module Tasks = SetDomain.Make (Lattice.Prod (Queries.LS) (PthreadDomain.D)) +module Tasks = SetDomain.Make (Lattice.Prod (Queries.AD) (PthreadDomain.D)) module rec Env : sig type t @@ -869,8 +869,6 @@ module Spec : Analyses.MCPSpec = struct module C = D (** Set of created tasks to spawn when going multithreaded *) - module Tasks = SetDomain.Make (Lattice.Prod (Queries.LS) (D)) - module G = Tasks let tasks_var = @@ -879,22 +877,9 @@ module Spec : Analyses.MCPSpec = struct module ExprEval = struct let eval_ptr ctx exp = - let mayPointTo ctx exp = - let a = ctx.ask (Queries.MayPointTo exp) in - if (not (Queries.LS.is_top a)) && Queries.LS.cardinal a > 0 then - let top_elt = (dummyFunDec.svar, `NoOffset) in - let a' = - if Queries.LS.mem top_elt a - then (* UNSOUND *) - Queries.LS.remove top_elt a - else a - in - Queries.LS.elements a' - else - [] - in - List.map fst @@ mayPointTo ctx exp - + ctx.ask (Queries.MayPointTo exp) + |> Queries.AD.remove UnknownPtr (* UNSOUND *) + |> Queries.AD.to_var_may let eval_var ctx exp = match exp with @@ -1124,18 +1109,17 @@ module Spec : Analyses.MCPSpec = struct let arglist' = List.map (stripCasts % constFold false) arglist in match (LibraryFunctions.find f).special arglist', f.vname, arglist with | ThreadCreate { thread; start_routine = func; _ }, _, _ -> - let funs_ls = - let ls = ctx.ask (Queries.ReachableFrom func) in - Queries.LS.filter - (fun lv -> - let lval = Mval.Exp.to_cil lv in - isFunctionType (typeOfLval lval)) - ls + let funs_ad = + let ad = ctx.ask (Queries.ReachableFrom func) in + Queries.AD.filter + (function + | Queries.AD.Addr.Addr mval -> + isFunctionType (ValueDomain.Mval.type_of mval) + | _ -> false) + ad in let thread_fun = - funs_ls - |> Queries.LS.elements - |> List.map fst + Queries.AD.to_var_may funs_ad |> List.unique ~eq:(fun a b -> a.vid = b.vid) |> List.hd in @@ -1148,7 +1132,7 @@ module Spec : Analyses.MCPSpec = struct ; ctx = Ctx.top () } in - Tasks.singleton (funs_ls, f_d) + Tasks.singleton (funs_ad, f_d) in ctx.sideg tasks_var tasks ; in @@ -1254,20 +1238,23 @@ module Spec : Analyses.MCPSpec = struct (Ctx.top ()) - let threadenter ctx lval f args = + let threadenter ctx ~multiple lval f args = let d : D.t = ctx.local in let tasks = ctx.global tasks_var in (* TODO: optimize finding *) let tasks_f = - Tasks.filter - (fun (fs, f_d) -> Queries.LS.exists (fun (ls_f, _) -> ls_f = f) fs) - tasks + let var_in_ad ad f = Queries.AD.exists (function + | Queries.AD.Addr.Addr (ls_f,_) -> CilType.Varinfo.equal ls_f f + | _ -> false + ) ad + in + Tasks.filter (fun (ad,_) -> var_in_ad ad f) tasks in let f_d = snd (Tasks.choose tasks_f) in [ { f_d with pred = d.pred } ] - let threadspawn ctx lval f args fctx = ctx.local + let threadspawn ctx ~multiple lval f args fctx = ctx.local let exitstate v = D.top () diff --git a/src/analyses/fileUse.ml b/src/analyses/fileUse.ml deleted file mode 100644 index 174cd6a914..0000000000 --- a/src/analyses/fileUse.ml +++ /dev/null @@ -1,295 +0,0 @@ -(** Analysis of correct file handle usage ([file]). - - @see Vogler, R. Verifying Regular Safety Properties of C Programs Using the Static Analyzer Goblint. Section 3.*) - -open Batteries -open GoblintCil -open Analyses - -module Spec = -struct - include Analyses.DefaultSpec - - let name () = "file" - module D = FileDomain.Dom - module C = FileDomain.Dom - - (* special variables *) - let return_var = Cilfacade.create_var @@ Cil.makeVarinfo false "@return" Cil.voidType, `NoOffset - let unclosed_var = Cilfacade.create_var @@ Cil.makeVarinfo false "@unclosed" Cil.voidType, `NoOffset - - (* keys that were already warned about; needed for multiple returns (i.e. can't be kept in D) *) - let warned_unclosed = ref Set.empty - - (* queries *) - let query ctx (type a) (q: a Queries.t) = - match q with - | Queries.MayPointTo exp -> if M.tracing then M.tracel "file" "query MayPointTo: %a" d_plainexp exp; Queries.Result.top q - | _ -> Queries.Result.top q - - let query_lv (ask: Queries.ask) exp = - match ask.f (Queries.MayPointTo exp) with - | l when not (Queries.LS.is_top l) -> - Queries.LS.elements l - | _ -> [] - let print_query_lv ?msg:(msg="") ask exp = - let xs = query_lv ask exp in (* MayPointTo -> LValSet *) - let pretty_key k = Pretty.text (D.string_of_key k) in - if M.tracing then M.tracel "file" "%s MayPointTo %a = [%a]" msg d_exp exp (Pretty.docList ~sep:(Pretty.text ", ") pretty_key) xs - - let eval_fv ask exp: varinfo option = - match query_lv ask exp with - | [(v,_)] -> Some v - | _ -> None - - - (* transfer functions *) - let assign ctx (lval:lval) (rval:exp) : D.t = - let m = ctx.local in - (* ignore(printf "%a = %a\n" d_plainlval lval d_plainexp rval); *) - let saveOpened ?unknown:(unknown=false) k m = (* save maybe opened files in the domain to warn about maybe unclosed files at the end *) - if D.may k D.opened m && not (D.is_unknown k m) then (* if unknown we don't have any location for the warning and have handled it already anyway *) - let mustOpen, mayOpen = D.filter_records k D.opened m in - let mustOpen, mayOpen = if unknown then Set.empty, mayOpen else mustOpen, Set.diff mayOpen mustOpen in - D.extend_value unclosed_var (mustOpen, mayOpen) m - else m - in - let key_from_exp = function - | Lval x -> Some (D.key_from_lval x) - | _ -> None - in - match key_from_exp (Lval lval), key_from_exp (stripCasts rval) with (* we just care about Lval assignments *) - | Some k1, Some k2 when k1=k2 -> m (* do nothing on self-assignment *) - | Some k1, Some k2 when D.mem k1 m && D.mem k2 m -> (* both in D *) - if M.tracing then M.tracel "file" "assign (both in D): %s = %s" (D.string_of_key k1) (D.string_of_key k2); - saveOpened k1 m |> D.remove' k1 |> D.alias k1 k2 - | Some k1, Some k2 when D.mem k1 m -> (* only k1 in D *) - if M.tracing then M.tracel "file" "assign (only k1 in D): %s = %s" (D.string_of_key k1) (D.string_of_key k2); - saveOpened k1 m |> D.remove' k1 - | Some k1, Some k2 when D.mem k2 m -> (* only k2 in D *) - if M.tracing then M.tracel "file" "assign (only k2 in D): %s = %s" (D.string_of_key k1) (D.string_of_key k2); - D.alias k1 k2 m - | Some k1, _ when D.mem k1 m -> (* k1 in D and assign something unknown *) - if M.tracing then M.tracel "file" "assign (only k1 in D): %s = %a" (D.string_of_key k1) d_exp rval; - D.warn @@ "[Unsound]changed pointer "^D.string_of_key k1^" (no longer safe)"; - saveOpened ~unknown:true k1 m |> D.unknown k1 - | _ -> (* no change in D for other things *) - if M.tracing then M.tracel "file" "assign (none in D): %a = %a [%a]" d_lval lval d_exp rval d_plainexp rval; - m - - let branch ctx (exp:exp) (tv:bool) : D.t = - let m = ctx.local in - (* ignore(printf "if %a = %B (line %i)\n" d_plainexp exp tv (!Tracing.current_loc).line); *) - let check a b tv = - (* ignore(printf "check: %a = %a, %B\n" d_plainexp a d_plainexp b tv); *) - match a, b with - | Const (CInt(i, kind, str)), Lval lval - | Lval lval, Const (CInt(i, kind, str)) -> - (* ignore(printf "branch(%s==%i, %B)\n" v.vname (Int64.to_int i) tv); *) - let k = D.key_from_lval lval in - if Z.compare i Z.zero = 0 && tv then ( - (* ignore(printf "error-branch\n"); *) - D.error k m - )else - D.success k m - | _ -> M.debug ~category:Analyzer "nothing matched the given BinOp: %a = %a" d_plainexp a d_plainexp b; m - in - match stripCasts (constFold true exp) with - (* somehow there are a lot of casts inside the BinOp which stripCasts only removes when called on the subparts - -> matching as in flagMode didn't work *) - (* | BinOp (Eq, Const (CInt64(i, kind, str)), Lval (Var v, NoOffset), _) - | BinOp (Eq, Lval (Var v, NoOffset), Const (CInt64(i, kind, str)), _) -> - ignore(printf "%s %i\n" v.vname (Int64.to_int i)); m *) - | BinOp (Eq, a, b, _) -> check (stripCasts a) (stripCasts b) tv - | BinOp (Ne, a, b, _) -> check (stripCasts a) (stripCasts b) (not tv) - | e -> M.debug ~category:Analyzer "branch: nothing matched the given exp: %a" d_plainexp e; m - - let body ctx (f:fundec) : D.t = - ctx.local - - let return ctx (exp:exp option) (f:fundec) : D.t = - (* TODO check One Return transformation: oneret.ml *) - let m = ctx.local in - (* if f.svar.vname <> "main" && BatList.is_empty (callstack m) then M.write ("\n\t!!! call stack is empty for function "^f.svar.vname^" !!!"); *) - if f.svar.vname = "main" then ( - let mustOpen, mayOpen = D.union (D.filter_values D.opened m) (D.get_value unclosed_var m) in - if Set.cardinal mustOpen > 0 then ( - D.warn @@ "unclosed files: "^D.string_of_keys mustOpen; - Set.iter (fun v -> D.warn ~loc:(D.V.loc v) "file is never closed") mustOpen; - (* add warnings about currently open files (don't include overwritten or changed file handles!) *) - warned_unclosed := Set.union !warned_unclosed (fst (D.filter_values D.opened m)) (* can't save in domain b/c it wouldn't reach the other return *) - ); - (* go through files "never closed" and recheck for current return *) - Set.iter (fun v -> if D.must (D.V.key v) D.closed m then D.warn ~may:true ~loc:(D.V.loc v) "file is never closed") !warned_unclosed; - (* let mustOpenVars = List.map (fun x -> x.key) mustOpen in *) - (* let mayOpen = List.filter (fun x -> not (List.mem x.key mustOpenVars)) mayOpen in (* ignore values that are already in mustOpen *) *) - let mayOpen = Set.diff mayOpen mustOpen in - if Set.cardinal mayOpen > 0 then - D.warn ~may:true @@ "unclosed files: "^D.string_of_keys mayOpen; - Set.iter (fun v -> D.warn ~may:true ~loc:(D.V.loc v) "file is never closed") mayOpen - ); - (* take care of return value *) - let au = match exp with - | Some(Lval lval) when D.mem (D.key_from_lval lval) m -> (* we return a var in D *) - let k = D.key_from_lval lval in - let varinfo,offset = k in - if varinfo.vglob then - D.alias return_var k m (* if var is global, we alias it *) - else - D.add return_var (D.find' k m) m (* if var is local, we make a copy *) - | _ -> m - in - (* remove formals and locals *) - (* this is not a good approach, what if we added a key foo.fp? -> just keep the globals *) - List.fold_left (fun m var -> D.remove' (var, `NoOffset) m) au (f.sformals @ f.slocals) - (* D.only_globals au *) - - let enter ctx (lval: lval option) (f:fundec) (args:exp list) : (D.t * D.t) list = - let m = if f.svar.vname <> "main" then - (* push current location onto stack *) - D.edit_callstack (BatList.cons (Option.get !Node.current_node)) ctx.local - else ctx.local in - (* we need to remove all variables that are neither globals nor special variables from the domain for f *) - (* problem: we need to be able to check aliases of globals in check_overwrite_open -> keep those in too :/ *) - (* TODO see Base.make_entry, reachable vars > globals? *) - (* [m, D.only_globals m] *) - [m, m] (* this is [caller, callee] *) - - let check_overwrite_open k m = (* used in combine and special *) - if List.is_empty (D.get_aliases k m) then ( - (* there are no other variables pointing to the file handle - and it is opened again without being closed before *) - D.report k D.opened ("overwriting still opened file handle "^D.string_of_key k) m; - let mustOpen, mayOpen = D.filter_records k D.opened m in - let mayOpen = Set.diff mayOpen mustOpen in - (* save opened files in the domain to warn about unclosed files at the end *) - D.extend_value unclosed_var (mustOpen, mayOpen) m - ) else m - - let combine_env ctx lval fexp f args fc au f_ask = - let m = ctx.local in - (* pop the last location off the stack *) - let m = D.edit_callstack List.tl m in (* TODO could it be problematic to keep this in the caller instead of callee domain? if we only add the stack for the callee in enter, then there would be no need to pop a location anymore... *) - (* TODO add all globals from au to m (since we remove formals and locals on return, we can just add everything except special vars?) *) - D.without_special_vars au |> D.add_all m - - let combine_assign ctx (lval:lval option) fexp (f:fundec) (args:exp list) fc (au:D.t) (f_ask: Queries.ask) : D.t = - let m = ctx.local in - let return_val = D.find_option return_var au in - match lval, return_val with - | Some lval, Some v -> - let k = D.key_from_lval lval in - (* handle potential overwrites *) - let m = check_overwrite_open k m in - (* if v.key is still in D, then it must be a global and we need to alias instead of rebind *) - (* TODO what if there is a local with the same name as the global? *) - if D.V.is_top v then (* returned a local that was top -> just add k as top *) - D.add' k v m - else (* v is now a local which is not top or a global which is aliased *) - let vvar = D.V.get_alias v in (* this is also ok if v is not an alias since it chooses an element from the May-Set which is never empty (global top gets aliased) *) - if D.mem vvar au then (* returned variable was a global TODO what if local had the same name? -> seems to work *) - D.alias k vvar m - else (* returned variable was a local *) - let v = D.V.set_key k v in (* adjust var-field to lval *) - D.add' k v m - | _ -> m - - let special ctx (lval: lval option) (f:varinfo) (arglist:exp list) : D.t = - (* is f a pointer to a function we look out for? *) - let f = eval_fv (Analyses.ask_of_ctx ctx) (Lval (Var f, NoOffset)) |? f in - let m = ctx.local in - let loc = (Option.get !Node.current_node)::(D.callstack m) in - let arglist = List.map (Cil.stripCasts) arglist in (* remove casts, TODO safe? *) - let split_err_branch lval dom = - (* type? NULL = 0 = 0-ptr? Cil.intType, Cil.intPtrType, Cil.voidPtrType -> no difference *) - if not (GobConfig.get_bool "ana.file.optimistic") then - ctx.split dom [Events.SplitBranch ((Cil.BinOp (Cil.Eq, Cil.Lval lval, Cil.integer 0, Cil.intType)), true)]; - dom - in - (* fold possible keys on domain *) - let ret_all f lval = - let xs = D.keys_from_lval lval (Analyses.ask_of_ctx ctx) in (* get all possible keys for a given lval *) - if xs = [] then (D.warn @@ GobPretty.sprintf "could not resolve %a" CilType.Lval.pretty lval; m) - else if List.compare_length_with xs 1 = 0 then f (List.hd xs) m true - (* else List.fold_left (fun m k -> D.join m (f k m)) m xs *) - else - (* if there is more than one key, join all values and do warnings on the result *) - let v = List.fold_left (fun v k -> match v, D.find_option k m with - | None, None -> None - | Some a, None - | None, Some a -> Some a - | Some a, Some b -> Some (D.V.join a b)) None xs in - (* set all of the keys to the computed joined value *) - (* let m' = Option.map_default (fun v -> List.fold_left (fun m k -> D.add' k v m) m xs) m v in *) - (* then check each key *) - (* List.iter (fun k -> ignore(f k m')) xs; *) - (* get Mval.Exp from lval *) - let k' = D.key_from_lval lval in - (* add joined value for that key *) - let m' = Option.map_default (fun v -> D.add' k' v m) m v in - (* check for warnings *) - ignore(f k' m' true); - (* and join the old domain without issuing warnings *) - List.fold_left (fun m k -> D.join m (f k m false)) m xs - in - match lval, f.vname, arglist with - | None, "fopen", _ -> - D.warn "file handle is not saved!"; m - | Some lval, "fopen", _ -> - let f k m w = - let m = check_overwrite_open k m in - (match arglist with - | Const(CStr(filename,_))::Const(CStr(mode,_))::[] -> - (* M.debug ~category:Analyzer @@ "fopen(\""^filename^"\", \""^mode^"\")"; *) - D.fopen k loc filename mode m |> split_err_branch lval (* TODO k instead of lval? *) - | e::Const(CStr(mode,_))::[] -> - (* ignore(printf "CIL: %a\n" d_plainexp e); *) - (match ctx.ask (Queries.EvalStr e) with - | `Lifted filename -> D.fopen k loc filename mode m - | _ -> D.warn "[Unsound]unknown filename"; D.fopen k loc "???" mode m - ) - | xs -> - let args = (String.concat ", " (List.map CilType.Exp.show xs)) in - M.debug ~category:Analyzer "fopen args: %s" args; - (* List.iter (fun exp -> ignore(printf "%a\n" d_plainexp exp)) xs; *) - D.warn @@ "[Program]fopen needs two strings as arguments, given: "^args; m - ) - in ret_all f lval - - | _, "fclose", [Lval fp] -> - let f k m w = - if w then D.reports k [ - false, D.closed, "closeing already closed file handle "^D.string_of_key k; - true, D.opened, "closeing unopened file handle "^D.string_of_key k - ] m; - D.fclose k loc m - in ret_all f fp - | _, "fclose", _ -> - D.warn "fclose needs exactly one argument"; m - - | _, "fprintf", (Lval fp)::_::_ -> - let f k m w = - if w then D.reports k [ - false, D.closed, "writing to closed file handle "^D.string_of_key k; - true, D.opened, "writing to unopened file handle "^D.string_of_key k; - true, D.writable, "writing to read-only file handle "^D.string_of_key k; - ] m; - m - in ret_all f fp - | _, "fprintf", fp::_::_ -> - (* List.iter (fun exp -> ignore(printf "%a\n" d_plainexp exp)) arglist; *) - print_query_lv ~msg:"fprintf(?, ...): " (Analyses.ask_of_ctx ctx) fp; - D.warn "[Program]first argument to printf must be a Lval"; m - | _, "fprintf", _ -> - D.warn "[Program]fprintf needs at least two arguments"; m - - | _ -> m - - let startstate v = D.bot () - let threadenter ctx lval f args = [D.bot ()] - let threadspawn ctx lval f args fctx = ctx.local - let exitstate v = D.bot () -end - -let _ = - MCP.register_analysis (module Spec : MCPSpec) diff --git a/src/analyses/libraryDesc.ml b/src/analyses/libraryDesc.ml deleted file mode 100644 index 2df292772a..0000000000 --- a/src/analyses/libraryDesc.ml +++ /dev/null @@ -1,154 +0,0 @@ -(** Library function descriptor (specification). *) - -module Cil = GoblintCil - -(** Pointer argument access specification. *) -module Access = -struct - type t = { - kind: AccessKind.t; (** Kind of access. *) - deep: bool; (** Depth of access - - Shallow only accesses directly pointed values (may point to). - - Deep additionally follows all pointers in values (reachable). Rarely needed. *) - } -end - -type math = - | Nan of (Cil.fkind * Cil.exp) - | Inf of Cil.fkind - | Isfinite of Cil.exp - | Isinf of Cil.exp - | Isnan of Cil.exp - | Isnormal of Cil.exp - | Signbit of Cil.exp - | Isgreater of (Cil.exp * Cil.exp) - | Isgreaterequal of (Cil.exp * Cil.exp) - | Isless of (Cil.exp * Cil.exp) - | Islessequal of (Cil.exp * Cil.exp) - | Islessgreater of (Cil.exp * Cil.exp) - | Isunordered of (Cil.exp * Cil.exp) - | Ceil of (Cil.fkind * Cil.exp) - | Floor of (Cil.fkind * Cil.exp) - | Fabs of (Cil.fkind * Cil.exp) - | Fmax of (Cil.fkind * Cil.exp * Cil.exp) - | Fmin of (Cil.fkind * Cil.exp * Cil.exp) - | Acos of (Cil.fkind * Cil.exp) - | Asin of (Cil.fkind * Cil.exp) - | Atan of (Cil.fkind * Cil.exp) - | Atan2 of (Cil.fkind * Cil.exp * Cil.exp) - | Cos of (Cil.fkind * Cil.exp) - | Sin of (Cil.fkind * Cil.exp) - | Tan of (Cil.fkind * Cil.exp) - -(** Type of special function, or {!Unknown}. *) -(* Use inline record if not single {!Cil.exp} argument. *) -type special = - | Malloc of Cil.exp - | Calloc of { count: Cil.exp; size: Cil.exp; } - | Realloc of { ptr: Cil.exp; size: Cil.exp; } - | Free of Cil.exp - | Assert of { exp: Cil.exp; check: bool; refine: bool; } - | Lock of { lock: Cil.exp; try_: bool; write: bool; return_on_success: bool; } - | Unlock of Cil.exp - | ThreadCreate of { thread: Cil.exp; start_routine: Cil.exp; arg: Cil.exp; } - | ThreadJoin of { thread: Cil.exp; ret_var: Cil.exp; } - | ThreadExit of { ret_val: Cil.exp; } - | Signal of Cil.exp - | Broadcast of Cil.exp - | MutexAttrSetType of { attr:Cil.exp; typ: Cil.exp; } - | MutexInit of { mutex:Cil.exp; attr: Cil.exp; } - | Wait of { cond: Cil.exp; mutex: Cil.exp; } - | TimedWait of { cond: Cil.exp; mutex: Cil.exp; abstime: Cil.exp; (** Unused *) } - | Math of { fun_args: math; } - | Memset of { dest: Cil.exp; ch: Cil.exp; count: Cil.exp; } - | Bzero of { dest: Cil.exp; count: Cil.exp; } - | Memcpy of { dest: Cil.exp; src: Cil.exp } - | Strcpy of { dest: Cil.exp; src: Cil.exp; n: Cil.exp option; } - | Strcat of { dest: Cil.exp; src: Cil.exp; n: Cil.exp option; } - | Strlen of Cil.exp - | Strstr of { haystack: Cil.exp; needle: Cil.exp; } - | Strcmp of { s1: Cil.exp; s2: Cil.exp; n: Cil.exp option; } - | Abort - | Identity of Cil.exp (** Identity function. Some compiler optimization annotation functions map to this. *) - | Setjmp of { env: Cil.exp; } - | Longjmp of { env: Cil.exp; value: Cil.exp; } - | Rand - | Unknown (** Anything not belonging to other types. *) (* TODO: rename to Other? *) - - -(** Pointer arguments access specification. *) -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) |? []) - - let find_kind (accs: t): AccessKind.t -> Cil.exp list -> Cil.exp list = fun kind args -> - let f a = find accs a args in - f { kind; deep = true; } @ f { kind; deep = false; } - - let iter (accs: t) (f: Access.t -> Cil.exp -> unit) args: unit = - accs args - |> List.iter (fun (acc, exps) -> - List.iter (fun exp -> f acc exp) exps - ) - - let fold (accs: t) (f: Access.t -> Cil.exp -> 'a -> 'a) args (a: 'a): 'a = - accs args - |> List.fold_left (fun a (acc, exps) -> - List.fold_left (fun a exp -> f acc exp a) a exps - ) a -end - -(** Function attribute. *) -type attr = - | ThreadUnsafe (** Function is not thread-safe to call, e.g. due to its own internal (global) state. - @see for list of thread-unsafe functions under POSIX. - @see for Goblint issue about the (future) use of this attribute. *) - | InvalidateGlobals (** Function invalidates all globals when called. *) (* TODO: AccessGlobals of Access.t list? *) - -(** Library function descriptor. *) -type t = { - special: Cil.exp list -> special; (** Conversion to {!type-special} using arguments. *) - accs: Accesses.t; (** Pointer arguments access specification. *) - attrs: attr list; (** Attributes of function. *) -} - -let special_of_old classify_name = fun args -> - match classify_name args with - | `Malloc e -> Malloc e - | `Calloc (count, size) -> Calloc { count; size; } - | `Realloc (ptr, size) -> Realloc { ptr; size; } - | `Lock (try_, write, return_on_success) -> - begin match args with - | [lock] -> Lock { lock ; try_; write; return_on_success; } - | [] -> failwith "lock has no arguments" - | _ -> failwith "lock has multiple arguments" - end - | `Unlock -> - begin match args with - | [arg] -> Unlock arg - | [] -> failwith "unlock has no arguments" - | _ -> failwith "unlock has multiple arguments" - end - | `ThreadCreate (thread, start_routine, arg) -> ThreadCreate { thread; start_routine; arg; } - | `ThreadJoin (thread, ret_var) -> ThreadJoin { thread; ret_var; } - | `Unknown _ -> Unknown - -let of_old ?(attrs: attr list=[]) (old_accesses: Accesses.old) (classify_name): t = { - attrs; - accs = Accesses.of_old old_accesses; - special = special_of_old classify_name; -} diff --git a/src/analyses/libraryFunctions.ml b/src/analyses/libraryFunctions.ml deleted file mode 100644 index 1c4e84eab4..0000000000 --- a/src/analyses/libraryFunctions.ml +++ /dev/null @@ -1,1184 +0,0 @@ -(** Tools for dealing with library functions. *) - -open Batteries -open GoblintCil -open GobConfig - -module M = Messages - -(** C standard library functions. - These are specified by the C standard. *) -let c_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ - ("memset", special [__ "dest" [w]; __ "ch" []; __ "count" []] @@ fun dest ch count -> Memset { dest; ch; count; }); - ("__builtin_memset", special [__ "dest" [w]; __ "ch" []; __ "count" []] @@ fun dest ch count -> Memset { dest; ch; count; }); - ("__builtin___memset_chk", special [__ "dest" [w]; __ "ch" []; __ "count" []; drop "os" []] @@ fun dest ch count -> Memset { dest; ch; count; }); - ("memcpy", special [__ "dest" [w]; __ "src" [r]; drop "n" []] @@ fun dest src -> Memcpy { dest; src }); - ("__builtin_memcpy", special [__ "dest" [w]; __ "src" [r]; drop "n" []] @@ fun dest src -> Memcpy { dest; src }); - ("__builtin___memcpy_chk", special [__ "dest" [w]; __ "src" [r]; drop "n" []; drop "os" []] @@ fun dest src -> Memcpy { dest; src }); - ("strcpy", special [__ "dest" [w]; __ "src" [r]] @@ fun dest src -> Strcpy { dest; src; n = None; }); - ("__builtin_strcpy", special [__ "dest" [w]; __ "src" [r]] @@ fun dest src -> Strcpy { dest; src; n = None; }); - ("__builtin___strcpy_chk", special [__ "dest" [w]; __ "src" [r]; drop "os" []] @@ fun dest src -> Strcpy { dest; src; n = None; }); - ("strncpy", special [__ "dest" [w]; __ "src" [r]; __ "n" []] @@ fun dest src n -> Strcpy { dest; src; n = Some n; }); - ("__builtin_strncpy", special [__ "dest" [w]; __ "src" [r]; __ "n" []] @@ fun dest src n -> Strcpy { dest; src; n = Some n; }); - ("__builtin___strncpy_chk", special [__ "dest" [w]; __ "src" [r]; __ "n" []; drop "os" []] @@ fun dest src n -> Strcpy { dest; src; n = Some n; }); - ("strcat", special [__ "dest" [r; w]; __ "src" [r]] @@ fun dest src -> Strcat { dest; src; n = None; }); - ("__builtin_strcat", special [__ "dest" [r; w]; __ "src" [r]] @@ fun dest src -> Strcat { dest; src; n = None; }); - ("__builtin___strcat_chk", special [__ "dest" [r; w]; __ "src" [r]; drop "os" []] @@ fun dest src -> Strcat { dest; src; n = None; }); - ("strncat", special [__ "dest" [r; w]; __ "src" [r]; __ "n" []] @@ fun dest src n -> Strcat { dest; src; n = Some n; }); - ("__builtin_strncat", special [__ "dest" [r; w]; __ "src" [r]; __ "n" []] @@ fun dest src n -> Strcat { dest; src; n = Some n; }); - ("__builtin___strncat_chk", special [__ "dest" [r; w]; __ "src" [r]; __ "n" []; drop "os" []] @@ fun dest src n -> Strcat { dest; src; n = Some n; }); - ("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; }); - ("__builtin_strcmp", special [__ "s1" [r]; __ "s2" [r]] @@ fun s1 s2 -> Strcmp { s1; s2; n = None; }); - ("strncmp", special [__ "s1" [r]; __ "s2" [r]; __ "n" []] @@ fun s1 s2 n -> Strcmp { s1; s2; n = Some n; }); - ("malloc", special [__ "size" []] @@ fun size -> Malloc size); - ("realloc", special [__ "ptr" [r; f]; __ "size" []] @@ fun ptr size -> Realloc { ptr; size }); - ("free", special [__ "ptr" [f]] @@ fun ptr -> Free ptr); - ("abort", special [] Abort); - ("exit", special [drop "exit_code" []] Abort); - ("ungetc", unknown [drop "c" []; drop "stream" [r; w]]); - ("fscanf", unknown ((drop "stream" [r; w]) :: (drop "format" [r]) :: (VarArgs (drop' [w])))); - ("__freading", unknown [drop "stream" [r]]); - ("mbsinit", unknown [drop "ps" [r]]); - ("mbrtowc", unknown [drop "pwc" [w]; drop "s" [r]; drop "n" []; drop "ps" [r; w]]); - ("iswspace", unknown [drop "wc" []]); - ("iswalnum", unknown [drop "wc" []]); - ("iswprint", unknown [drop "wc" []]); - ("rename" , unknown [drop "oldpath" [r]; drop "newpath" [r];]); - ("puts", unknown [drop "s" [r]]); - ("strspn", unknown [drop "s" [r]; drop "accept" [r]]); - ("strcspn", unknown [drop "s" [r]; drop "accept" [r]]); - ("strtod", unknown [drop "nptr" [r]; drop "endptr" [w]]); - ("strtol", unknown [drop "nptr" [r]; drop "endptr" [w]; drop "base" []]); - ("__strtol_internal", unknown [drop "nptr" [r]; drop "endptr" [w]; drop "base" []; drop "group" []]); - ("strtoll", unknown [drop "nptr" [r]; drop "endptr" [w]; drop "base" []]); - ("strtoul", unknown [drop "nptr" [r]; drop "endptr" [w]; drop "base" []]); - ("strtoull", unknown [drop "nptr" [r]; drop "endptr" [w]; drop "base" []]); - ("mktime", unknown [drop "tm" [r;w]]); - ("ctime", unknown [drop "rm" [r]]); - ("clearerr", unknown [drop "stream" [w]]); - ("setbuf", unknown [drop "stream" [w]; drop "buf" [w]]); - ("swprintf", unknown (drop "wcs" [w] :: drop "maxlen" [] :: drop "fmt" [r] :: VarArgs (drop' []))); - ("assert", special [__ "exp" []] @@ fun exp -> Assert { exp; check = true; refine = get_bool "sem.assert.refine" }); (* only used if assert is used without include, e.g. in transformed files *) - ("difftime", unknown [drop "time1" []; drop "time2" []]); - ("system", unknown [drop "command" [r]]); - ("wcscat", unknown [drop "dest" [r; w]; drop "src" [r]]); - ("abs", unknown [drop "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 }); - ("rand", special [] Rand); - ] - -(** C POSIX library functions. - These are {e not} specified by the C standard, but available on POSIX systems. *) -let posix_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ - ("bzero", special [__ "dest" [w]; __ "count" []] @@ fun dest count -> Bzero { dest; count; }); - ("__builtin_bzero", special [__ "dest" [w]; __ "count" []] @@ fun dest count -> Bzero { dest; count; }); - ("explicit_bzero", special [__ "dest" [w]; __ "count" []] @@ fun dest count -> Bzero { dest; count; }); - ("__explicit_bzero_chk", special [__ "dest" [w]; __ "count" []; drop "os" []] @@ fun dest count -> Bzero { dest; count; }); - ("nl_langinfo", unknown [drop "item" []]); - ("nl_langinfo_l", unknown [drop "item" []; drop "locale" [r_deep]]); - ("getc_unlocked", unknown [drop "stream" [w]]); - ("getchar_unlocked", unknown []); - ("putc_unlocked", unknown [drop "c" []; drop "stream" [w]]); - ("putchar_unlocked", unknown [drop "c" []]); - ("lseek", unknown [drop "fd" []; drop "offset" []; drop "whence" []]); - ("fseeko", unknown [drop "stream" [w]; drop "offset" []; drop "whence" []]); - ("iconv_open", unknown [drop "tocode" [r]; drop "fromcode" [r]]); - ("iconv", unknown [drop "cd" [r]; drop "inbuf" [r]; drop "inbytesleft" [r;w]; drop "outbuf" [w]; drop "outbytesleft" [r;w]]); - ("iconv_close", unknown [drop "cd" [f]]); - ("strnlen", unknown [drop "s" [r]; drop "maxlen" []]); - ("chmod", unknown [drop "pathname" [r]; drop "mode" []]); - ("fchmod", unknown [drop "fd" []; drop "mode" []]); - ("fchown", unknown [drop "fd" []; drop "owner" []; drop "group" []]); - ("lchown", unknown [drop "pathname" [r]; drop "owner" []; drop "group" []]); - ("clock_gettime", unknown [drop "clockid" []; drop "tp" [w]]); - ("gettimeofday", unknown [drop "tv" [w]; drop "tz" [w]]); - ("futimens", unknown [drop "fd" []; drop "times" [r]]); - ("utimes", unknown [drop "filename" [r]; drop "times" [r]]); - ("linkat", unknown [drop "olddirfd" []; drop "oldpath" [r]; drop "newdirfd" []; drop "newpath" [r]; drop "flags" []]); - ("dirfd", unknown [drop "dirp" [r]]); - ("fdopendir", unknown [drop "fd" []]); - ("pathconf", unknown [drop "path" [r]; drop "name" []]); - ("symlink" , unknown [drop "oldpath" [r]; drop "newpath" [r];]); - ("ftruncate", unknown [drop "fd" []; drop "length" []]); - ("mkfifo", unknown [drop "pathname" [r]; drop "mode" []]); - ("ntohs", unknown [drop "netshort" []]); - ("alarm", unknown [drop "seconds" []]); - ("pwrite", unknown [drop "fd" []; drop "buf" [r]; drop "count" []; drop "offset" []]); - ("hstrerror", unknown [drop "err" []]); - ("inet_ntoa", unknown [drop "in" []]); - ("getsockopt", unknown [drop "sockfd" []; drop "level" []; drop "optname" []; drop "optval" [w]; drop "optlen" [w]]); - ("gethostbyaddr", unknown [drop "addr" [r_deep]; drop "len" []; drop "type" []]); - ("gethostbyaddr_r", unknown [drop "addr" [r_deep]; drop "len" []; drop "type" []; drop "ret" [w_deep]; drop "buf" [w]; drop "buflen" []; drop "result" [w]; drop "h_errnop" [w]]); - ("sigaction", unknown [drop "signum" []; drop "act" [r_deep; s_deep]; drop "oldact" [w_deep]]); - ("tcgetattr", unknown [drop "fd" []; drop "termios_p" [w_deep]]); - ("tcsetattr", unknown [drop "fd" []; drop "optional_actions" []; drop "termios_p" [r_deep]]); - ("access", unknown [drop "pathname" [r]; drop "mode" []]); - ("ttyname", unknown [drop "fd" []]); - ("shm_open", unknown [drop "name" [r]; drop "oflag" []; drop "mode" []]); - ("sched_get_priority_max", unknown [drop "policy" []]); - ("mprotect", unknown [drop "addr" []; drop "len" []; drop "prot" []]); - ("ftime", unknown [drop "tp" [w]]); - ("timer_create", unknown [drop "clockid" []; drop "sevp" [r; w; s]; drop "timerid" [w]]); - ("timer_settime", unknown [drop "timerid" []; drop "flags" []; drop "new_value" [r_deep]; drop "old_value" [w_deep]]); - ("timer_gettime", unknown [drop "timerid" []; drop "curr_value" [w_deep]]); - ("timer_getoverrun", unknown [drop "timerid" []]); - ("lstat", unknown [drop "pathname" [r]; drop "statbuf" [w]]); - ("getpwnam", unknown [drop "name" [r]]); - ("strndup", unknown [drop "s" [r]; drop "n" []]); - ("freeaddrinfo", unknown [drop "res" [f_deep]]); - ("getgid", unknown []); - ("pselect", unknown [drop "nfds" []; drop "readdfs" [r]; drop "writedfs" [r]; drop "exceptfds" [r]; drop "timeout" [r]; drop "sigmask" [r]]); - ("strncasecmp", unknown [drop "s1" [r]; drop "s2" [r]; drop "n" []]); - ("getnameinfo", unknown [drop "addr" [r_deep]; drop "addrlen" []; drop "host" [w]; drop "hostlen" []; drop "serv" [w]; drop "servlen" []; drop "flags" []]); - ("strtok_r", unknown [drop "str" [r; w]; drop "delim" [r]; drop "saveptr" [r_deep; w_deep]]); (* deep accesses through saveptr if str is NULL: https://github.com/lattera/glibc/blob/895ef79e04a953cac1493863bcae29ad85657ee1/string/strtok_r.c#L31-L40 *) - ("kill", unknown [drop "pid" []; drop "sig" []]); - ("closelog", unknown []); - ("dirname", unknown [drop "path" [r]]); - ("setpgid", unknown [drop "pid" []; drop "pgid" []]); - ("dup2", unknown [drop "oldfd" []; drop "newfd" []]); - ("pclose", unknown [drop "stream" [w; f]]); - ("getcwd", unknown [drop "buf" [w]; drop "size" []]); - ("inet_pton", unknown [drop "af" []; drop "src" [r]; drop "dst" [w]]); - ("inet_ntop", unknown [drop "af" []; drop "src" [r]; drop "dst" [w]; drop "size" []]); - ("gethostent", unknown []); - ("poll", unknown [drop "fds" [r]; drop "nfds" []; drop "timeout" []]); - ("semget", unknown [drop "key" []; drop "nsems" []; drop "semflg" []]); - ("semctl", unknown (drop "semid" [] :: drop "semnum" [] :: drop "cmd" [] :: VarArgs (drop "semun" [r_deep]))); - ("semop", unknown [drop "semid" []; drop "sops" [r]; drop "nsops" []]); - ("__sigsetjmp", special [__ "env" [w]; drop "savesigs" []] @@ fun env -> Setjmp { env }); (* has two underscores *) - ("sigsetjmp", special [__ "env" [w]; drop "savesigs" []] @@ fun env -> Setjmp { env }); - ("siglongjmp", special [__ "env" [r]; __ "value" []] @@ fun env value -> Longjmp { env; value }); - ] - -(** Pthread functions. *) -let pthread_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ - ("pthread_create", special [__ "thread" [w]; drop "attr" [r]; __ "start_routine" [s]; __ "arg" []] @@ fun thread start_routine arg -> ThreadCreate { thread; start_routine; arg }); (* For precision purposes arg is not considered accessed here. Instead all accesses (if any) come from actually analyzing start_routine. *) - ("pthread_exit", special [__ "retval" []] @@ fun retval -> ThreadExit { ret_val = retval }); (* Doesn't dereference the void* itself, but just passes to pthread_join. *) - ("pthread_cond_signal", special [__ "cond" []] @@ fun cond -> Signal cond); - ("pthread_cond_broadcast", special [__ "cond" []] @@ fun cond -> Broadcast cond); - ("pthread_cond_wait", special [__ "cond" []; __ "mutex" []] @@ fun cond mutex -> Wait {cond; mutex}); - ("pthread_cond_timedwait", special [__ "cond" []; __ "mutex" []; __ "abstime" [r]] @@ fun cond mutex abstime -> TimedWait {cond; mutex; abstime}); - ("pthread_mutexattr_settype", special [__ "attr" []; __ "type" []] @@ fun attr typ -> MutexAttrSetType {attr; typ}); - ("pthread_mutex_init", special [__ "mutex" []; __ "attr" []] @@ fun mutex attr -> MutexInit {mutex; attr}); - ("pthread_attr_destroy", unknown [drop "attr" [f]]); - ("pthread_setspecific", unknown ~attrs:[InvalidateGlobals] [drop "key" []; drop "value" [w_deep]]); - ("pthread_getspecific", unknown ~attrs:[InvalidateGlobals] [drop "key" []]); - ("pthread_key_delete", unknown [drop "key" [f]]); - ("pthread_cancel", unknown [drop "thread" []]); - ("pthread_setcanceltype", unknown [drop "type" []; drop "oldtype" [w]]); - ("pthread_detach", unknown [drop "thread" []]); - ("pthread_attr_setschedpolicy", unknown [drop "attr" [r; w]; drop "policy" []]); - ("pthread_condattr_init", unknown [drop "attr" [w]]); - ("pthread_condattr_setclock", unknown [drop "attr" [w]; drop "clock_id" []]); - ("pthread_mutexattr_destroy", unknown [drop "attr" [f]]); - ("pthread_attr_setschedparam", unknown [drop "attr" [r; w]; drop "param" [r]]); - ("sem_timedwait", unknown [drop "sem" [r]; drop "abs_timeout" [r]]); (* no write accesses to sem because sync primitive itself has no race *) - ] - -(** GCC builtin functions. - These are not builtin versions of functions from other lists. *) -let gcc_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ - ("__builtin_object_size", unknown [drop "ptr" [r]; drop' []]); - ("__builtin_prefetch", unknown (drop "addr" [] :: VarArgs (drop' []))); - ("__builtin_expect", special [__ "exp" []; drop' []] @@ fun exp -> Identity exp); (* Identity, because just compiler optimization annotation. *) - ("__builtin_unreachable", special' [] @@ fun () -> if get_bool "sem.builtin_unreachable.dead_code" then Abort else Unknown); (* https://github.com/sosy-lab/sv-benchmarks/issues/1296 *) - ("__assert_rtn", special [drop "func" [r]; drop "file" [r]; drop "line" []; drop "exp" [r]] @@ Abort); (* MacOS's built-in assert *) - ("__assert_fail", special [drop "assertion" [r]; drop "file" [r]; drop "line" []; drop "function" [r]] @@ Abort); (* gcc's built-in assert *) - ("__builtin_return_address", unknown [drop "level" []]); - ("__builtin___sprintf_chk", unknown (drop "s" [w] :: drop "flag" [] :: drop "os" [] :: drop "fmt" [r] :: VarArgs (drop' []))); - ("__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]]); - ("__builtin_saddll_overflow", unknown [drop "a" []; drop "b" []; drop "c" [w]]); - ("__builtin_uadd_overflow", unknown [drop "a" []; drop "b" []; drop "c" [w]]); - ("__builtin_uaddl_overflow", unknown [drop "a" []; drop "b" []; drop "c" [w]]); - ("__builtin_uaddll_overflow", unknown [drop "a" []; drop "b" []; drop "c" [w]]); - ("__builtin_sub_overflow", unknown [drop "a" []; drop "b" []; drop "c" [w]]); - ("__builtin_ssub_overflow", unknown [drop "a" []; drop "b" []; drop "c" [w]]); - ("__builtin_ssubl_overflow", unknown [drop "a" []; drop "b" []; drop "c" [w]]); - ("__builtin_ssubll_overflow", unknown [drop "a" []; drop "b" []; drop "c" [w]]); - ("__builtin_usub_overflow", unknown [drop "a" []; drop "b" []; drop "c" [w]]); - ("__builtin_usubl_overflow", unknown [drop "a" []; drop "b" []; drop "c" [w]]); - ("__builtin_usubll_overflow", unknown [drop "a" []; drop "b" []; drop "c" [w]]); - ("__builtin_mul_overflow", unknown [drop "a" []; drop "b" []; drop "c" [w]]); - ("__builtin_smul_overflow", unknown [drop "a" []; drop "b" []; drop "c" [w]]); - ("__builtin_smull_overflow", unknown [drop "a" []; drop "b" []; drop "c" [w]]); - ("__builtin_smulll_overflow", unknown [drop "a" []; drop "b" []; drop "c" [w]]); - ("__builtin_umul_overflow", unknown [drop "a" []; drop "b" []; drop "c" [w]]); - ("__builtin_umull_overflow", unknown [drop "a" []; drop "b" []; drop "c" [w]]); - ("__builtin_umulll_overflow", unknown [drop "a" []; drop "b" []; drop "c" [w]]); - ("__builtin_add_overflow_p", unknown [drop "a" []; drop "b" []; drop "c" []]); - ("__builtin_sub_overflow_p", unknown [drop "a" []; drop "b" []; drop "c" []]); - ("__builtin_mul_overflow_p", unknown [drop "a" []; drop "b" []; drop "c" []]); - ("__builtin_popcount", unknown [drop "x" []]); - ("__builtin_popcountl", unknown [drop "x" []]); - ("__builtin_popcountll", unknown [drop "x" []]); - ("__atomic_store_n", unknown [drop "ptr" [w]; drop "val" []; drop "memorder" []]); - ("__atomic_load_n", unknown [drop "ptr" [r]; drop "memorder" []]); - ("__sync_fetch_and_add", unknown (drop "ptr" [r; w] :: drop "value" [] :: VarArgs (drop' []))); - ("__sync_fetch_and_sub", unknown (drop "ptr" [r; w] :: drop "value" [] :: VarArgs (drop' []))); - ("__builtin_va_copy", unknown [drop "dest" [w]; drop "src" [r]]); - ] - -let glibc_desc_list: (string * LibraryDesc.t) list = LibraryDsl.[ - ("fputs_unlocked", unknown [drop "s" [r]; drop "stream" [w]]); - ("futimesat", unknown [drop "dirfd" [w]; drop "pathname" [r]; drop "times" [r]]); - ("error", unknown ((drop "status" []):: (drop "errnum" []) :: (drop "format" [r]) :: (VarArgs (drop' [r])))); - ("gettext", unknown [drop "msgid" [r]]); - ("euidaccess", unknown [drop "pathname" [r]; drop "mode" []]); - ("rpmatch", unknown [drop "response" [r]]); - ("getpagesize", unknown []); - ("__read_chk", unknown [drop "__fd" []; drop "__buf" [w]; drop "__nbytes" []; drop "__buflen" []]); - ("__read_alias", unknown [drop "__fd" []; drop "__buf" [w]; drop "__nbytes" []]); - ("__readlink_chk", unknown [drop "path" [r]; drop "buf" [w]; drop "len" []; drop "buflen" []]); - ("__readlink_alias", unknown [drop "path" [r]; drop "buf" [w]; drop "len" []]); - ("__overflow", unknown [drop "f" [r]; drop "ch" []]); - ("__ctype_get_mb_cur_max", unknown []); - ("__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]]); - ("xdr_string", unknown [drop "xdrs" [r_deep; w_deep]; drop "sp" [r; w]; drop "maxsize" []]); - ("xdr_enum", unknown [drop "xdrs" [r_deep; w_deep]; drop "ep" [r; w]]); - ("xdr_u_int", unknown [drop "xdrs" [r_deep; w_deep]; drop "up" [r; w]]); - ("xdr_opaque", unknown [drop "xdrs" [r_deep; w_deep]; drop "cp" [r; w]; drop "cnt" []]); - ("xdr_free", unknown [drop "proc" [s]; drop "objp" [f_deep]]); - ("svcerr_noproc", unknown [drop "xprt" [r_deep; w_deep]]); - ("svcerr_decode", unknown [drop "xprt" [r_deep; w_deep]]); - ("svcerr_systemerr", unknown [drop "xprt" [r_deep; w_deep]]); - ("svc_sendreply", unknown [drop "xprt" [r_deep; w_deep]; drop "outproc" [s]; drop "out" [r]]); - ("shutdown", unknown [drop "socket" []; drop "how" []]); - ("getaddrinfo_a", unknown [drop "mode" []; drop "list" [w_deep]; drop "nitems" []; drop "sevp" [r; w; s]]); - ("__uflow", unknown [drop "file" [r; w]]); - ("getservbyname_r", unknown [drop "name" [r]; drop "proto" [r]; drop "result_buf" [w_deep]; drop "buf" [w]; drop "buflen" []; drop "result" [w]]); - ("strsep", unknown [drop "stringp" [r_deep; w]; drop "delim" [r]]); - ("strcasestr", unknown [drop "haystack" [r]; drop "needle" [r]]); - ("inet_aton", unknown [drop "cp" [r]; drop "inp" [w]]); - ] - -let linux_userspace_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ - (* ("prctl", unknown [drop "option" []; drop "arg2" []; drop "arg3" []; drop "arg4" []; drop "arg5" []]); *) - ("prctl", unknown (drop "option" [] :: VarArgs (drop' []))); (* man page has 5 arguments, but header has varargs and real-world programs may call with <5 *) - ("__ctype_tolower_loc", unknown []); - ("__ctype_toupper_loc", unknown []); - ("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" []]); - ("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 *) - ] - -let big_kernel_lock = AddrOf (Cil.var (Cilfacade.create_var (makeGlobalVar "[big kernel lock]" intType))) -let console_sem = AddrOf (Cil.var (Cilfacade.create_var (makeGlobalVar "[console semaphore]" intType))) - -(** Linux kernel functions. *) -let linux_kernel_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ - ("spin_lock_irqsave", special [__ "lock" []; drop "flags" []] @@ fun lock -> Lock { lock; try_ = get_bool "sem.lock.fail"; write = true; return_on_success = true }); - ("spin_unlock_irqrestore", special [__ "lock" []; drop "flags" []] @@ fun lock -> Unlock lock); - ("_raw_spin_unlock_irqrestore", special [__ "lock" []; drop "flags" []] @@ fun lock -> Unlock lock); - ("spinlock_check", special [__ "lock" []] @@ fun lock -> Identity lock); (* Identity, because we don't want lock internals. *) - ("_lock_kernel", special [drop "func" [r]; drop "file" [r]; drop "line" []] @@ Lock { lock = big_kernel_lock; try_ = false; write = true; return_on_success = true }); - ("_unlock_kernel", special [drop "func" [r]; drop "file" [r]; drop "line" []] @@ Unlock big_kernel_lock); - ("acquire_console_sem", special [] @@ Lock { lock = console_sem; try_ = false; write = true; return_on_success = true }); - ("release_console_sem", special [] @@ Unlock console_sem); - ("misc_deregister", unknown [drop "misc" [r_deep]]); - ("__bad_percpu_size", special [] Abort); (* these do not have definitions so the linker will fail if they are actually called *) - ("__bad_size_call_parameter", special [] Abort); - ("__xchg_wrong_size", special [] Abort); - ("__cmpxchg_wrong_size", special [] Abort); - ("__xadd_wrong_size", special [] Abort); - ("__put_user_bad", special [] Abort); - ] - -(** Goblint functions. *) -let goblint_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ - ("__goblint_unknown", unknown [drop' [w]]); - ("__goblint_check", special [__ "exp" []] @@ fun exp -> Assert { exp; check = true; refine = false }); - ("__goblint_assume", special [__ "exp" []] @@ fun exp -> Assert { exp; check = false; refine = true }); - ("__goblint_assert", special [__ "exp" []] @@ fun exp -> Assert { exp; check = true; refine = get_bool "sem.assert.refine" }); - ("__goblint_split_begin", unknown [drop "exp" []]); - ("__goblint_split_end", unknown [drop "exp" []]); - ] - -(** zstd functions. - Only used with extraspecials. *) -let zstd_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ - ("ZSTD_customMalloc", special [__ "size" []; drop "customMem" [r]] @@ fun size -> Malloc size); - ("ZSTD_customCalloc", special [__ "size" []; drop "customMem" [r]] @@ fun size -> Calloc { size; count = Cil.one }); - ("ZSTD_customFree", special [__ "ptr" [f]; drop "customMem" [r]] @@ fun ptr -> Free ptr); - ] - -(** math functions. - Functions and builtin versions of function and macros defined in math.h. *) -let math_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ - ("__builtin_nan", special [__ "str" []] @@ fun str -> Math { fun_args = (Nan (FDouble, str)) }); - ("nan", special [__ "str" []] @@ fun str -> Math { fun_args = (Nan (FDouble, str)) }); - ("__builtin_nanf", special [__ "str" []] @@ fun str -> Math { fun_args = (Nan (FFloat, str)) }); - ("nanf", special [__ "str" []] @@ fun str -> Math { fun_args = (Nan (FFloat, str)) }); - ("__builtin_nanl", special [__ "str" []] @@ fun str -> Math { fun_args = (Nan (FLongDouble, str)) }); - ("nanl", special [__ "str" []] @@ fun str -> Math { fun_args = (Nan (FLongDouble, str)) }); - ("__builtin_inf", special [] @@ Math { fun_args = Inf FDouble}); - ("__builtin_huge_val", special [] @@ Math { fun_args = Inf FDouble}); (* we assume the target format can represent infinities *) - ("__builtin_inff", special [] @@ Math { fun_args = Inf FFloat}); - ("__builtin_huge_valf", special [] @@ Math { fun_args = Inf FFloat}); (* we assume the target format can represent infinities *) - ("__builtin_infl", special [] @@ Math { fun_args = Inf FLongDouble}); - ("__builtin_huge_vall", special [] @@ Math { fun_args = Inf FLongDouble}); (* we assume the target format can represent infinities *) - ("__builtin_isfinite", special [__ "x" []] @@ fun x -> Math { fun_args = (Isfinite x) }); - ("__finite", special [__ "x" []] @@ fun x -> Math { fun_args = (Isfinite x) }); - ("__finitef", special [__ "x" []] @@ fun x -> Math { fun_args = (Isfinite x) }); - ("__finitel", special [__ "x" []] @@ fun x -> Math { fun_args = (Isfinite x) }); - ("__builtin_isinf", special [__ "x" []] @@ fun x -> Math { fun_args = (Isinf x) }); - ("__isinf", special [__ "x" []] @@ fun x -> Math { fun_args = (Isinf x) }); - ("__isinff", special [__ "x" []] @@ fun x -> Math { fun_args = (Isinf x) }); - ("__isinfl", special [__ "x" []] @@ fun x -> Math { fun_args = (Isinf x) }); - ("__builtin_isinf_sign", special [__ "x" []] @@ fun x -> Math { fun_args = (Isinf x) }); - ("__builtin_isnan", special [__ "x" []] @@ fun x -> Math { fun_args = (Isnan x) }); - ("__isnan", special [__ "x" []] @@ fun x -> Math { fun_args = (Isnan x) }); - ("__isnanf", special [__ "x" []] @@ fun x -> Math { fun_args = (Isnan x) }); - ("__isnanl", special [__ "x" []] @@ fun x -> Math { fun_args = (Isnan x) }); - ("__builtin_isnormal", special [__ "x" []] @@ fun x -> Math { fun_args = (Isnormal x) }); - ("__builtin_signbit", special [__ "x" []] @@ fun x -> Math { fun_args = (Signbit x) }); - ("__signbit", special [__ "x" []] @@ fun x -> Math { fun_args = (Signbit x) }); - ("__signbitf", special [__ "x" []] @@ fun x -> Math { fun_args = (Signbit x) }); - ("__signbitl", special [__ "x" []] @@ fun x -> Math { fun_args = (Signbit x) }); - ("__builtin_fabs", special [__ "x" []] @@ fun x -> Math { fun_args = (Fabs (FDouble, x)) }); - ("__builtin_fabsf", special [__ "x" []] @@ fun x -> Math { fun_args = (Fabs (FFloat, x)) }); - ("__builtin_fabsl", special [__ "x" []] @@ fun x -> Math { fun_args = (Fabs (FLongDouble, x)) }); - ("__builtin_isgreater", special [__ "x" []; __ "y" []] @@ fun x y -> Math { fun_args = (Isgreater (x,y)) }); - ("__builtin_isgreaterequal", special [__ "x" []; __ "y" []] @@ fun x y -> Math { fun_args = (Isgreaterequal (x,y)) }); - ("__builtin_isless", special [__ "x" []; __ "y" []] @@ fun x y -> Math { fun_args = (Isless (x,y)) }); - ("__builtin_islessequal", special [__ "x" []; __ "y" []] @@ fun x y -> Math { fun_args = (Islessequal (x,y)) }); - ("__builtin_islessgreater", special [__ "x" []; __ "y" []] @@ fun x y -> Math { fun_args = (Islessgreater (x,y)) }); - ("__builtin_isunordered", special [__ "x" []; __ "y" []] @@ fun x y -> Math { fun_args = (Isunordered (x,y)) }); - ("ceil", special [__ "x" []] @@ fun x -> Math { fun_args = (Ceil (FDouble, x)) }); - ("ceilf", special [__ "x" []] @@ fun x -> Math { fun_args = (Ceil (FFloat, x)) }); - ("ceill", special [__ "x" []] @@ fun x -> Math { fun_args = (Ceil (FLongDouble, x)) }); - ("floor", special [__ "x" []] @@ fun x -> Math { fun_args = (Floor (FDouble, x)) }); - ("floorf", special [__ "x" []] @@ fun x -> Math { fun_args = (Floor (FFloat, x)) }); - ("floorl", special [__ "x" []] @@ fun x -> Math { fun_args = (Floor (FLongDouble, x)) }); - ("fabs", special [__ "x" []] @@ fun x -> Math { fun_args = (Fabs (FDouble, x)) }); - ("fabsf", special [__ "x" []] @@ fun x -> Math { fun_args = (Fabs (FFloat, x)) }); - ("fabsl", special [__ "x" []] @@ fun x -> Math { fun_args = (Fabs (FLongDouble, x)) }); - ("fmax", special [__ "x" []; __ "y" []] @@ fun x y -> Math { fun_args = (Fmax (FDouble, x, y)) }); - ("fmaxf", special [__ "x" []; __ "y" []] @@ fun x y -> Math { fun_args = (Fmax (FFloat, x, y)) }); - ("fmaxl", special [__ "x" []; __ "y" []] @@ fun x y -> Math { fun_args = (Fmax (FLongDouble, x, y)) }); - ("fmin", special [__ "x" []; __ "y" []] @@ fun x y -> Math { fun_args = (Fmin (FDouble, x, y)) }); - ("fminf", special [__ "x" []; __ "y" []] @@ fun x y -> Math { fun_args = (Fmin (FFloat, x, y)) }); - ("fminl", special [__ "x" []; __ "y" []] @@ fun x y -> Math { fun_args = (Fmin (FLongDouble, x, y)) }); - ("__builtin_acos", special [__ "x" []] @@ fun x -> Math { fun_args = (Acos (FDouble, x)) }); - ("acos", special [__ "x" []] @@ fun x -> Math { fun_args = (Acos (FDouble, x)) }); - ("acosf", special [__ "x" []] @@ fun x -> Math { fun_args = (Acos (FFloat, x)) }); - ("acosl", special [__ "x" []] @@ fun x -> Math { fun_args = (Acos (FLongDouble, x)) }); - ("__builtin_asin", special [__ "x" []] @@ fun x -> Math { fun_args = (Asin (FDouble, x)) }); - ("asin", special [__ "x" []] @@ fun x -> Math { fun_args = (Asin (FDouble, x)) }); - ("asinf", special [__ "x" []] @@ fun x -> Math { fun_args = (Asin (FFloat, x)) }); - ("asinl", special [__ "x" []] @@ fun x -> Math { fun_args = (Asin (FLongDouble, x)) }); - ("__builtin_atan", special [__ "x" []] @@ fun x -> Math { fun_args = (Atan (FDouble, x)) }); - ("atan", special [__ "x" []] @@ fun x -> Math { fun_args = (Atan (FDouble, x)) }); - ("atanf", special [__ "x" []] @@ fun x -> Math { fun_args = (Atan (FFloat, x)) }); - ("atanl", special [__ "x" []] @@ fun x -> Math { fun_args = (Atan (FLongDouble, x)) }); - ("__builtin_atan2", special [__ "y" []; __ "x" []] @@ fun y x -> Math { fun_args = (Atan2 (FDouble, y, x)) }); - ("atan2", special [__ "y" []; __ "x" []] @@ fun y x -> Math { fun_args = (Atan2 (FDouble, y, x)) }); - ("atan2f", special [__ "y" []; __ "x" []] @@ fun y x -> Math { fun_args = (Atan2 (FFloat, y, x)) }); - ("atan2l", special [__ "y" []; __ "x" []] @@ fun y x -> Math { fun_args = (Atan2 (FLongDouble, y, x)) }); - ("__builtin_cos", special [__ "x" []] @@ fun x -> Math { fun_args = (Cos (FDouble, x)) }); - ("cos", special [__ "x" []] @@ fun x -> Math { fun_args = (Cos (FDouble, x)) }); - ("cosf", special [__ "x" []] @@ fun x -> Math { fun_args = (Cos (FFloat, x)) }); - ("cosl", special [__ "x" []] @@ fun x -> Math { fun_args = (Cos (FLongDouble, x)) }); - ("__builtin_sin", special [__ "x" []] @@ fun x -> Math { fun_args = (Sin (FDouble, x)) }); - ("sin", special [__ "x" []] @@ fun x -> Math { fun_args = (Sin (FDouble, x)) }); - ("sinf", special [__ "x" []] @@ fun x -> Math { fun_args = (Sin (FFloat, x)) }); - ("sinl", special [__ "x" []] @@ fun x -> Math { fun_args = (Sin (FLongDouble, x)) }); - ("__builtin_tan", special [__ "x" []] @@ fun x -> Math { fun_args = (Tan (FDouble, x)) }); - ("tan", special [__ "x" []] @@ fun x -> Math { fun_args = (Tan (FDouble, x)) }); - ("tanf", special [__ "x" []] @@ fun x -> Math { fun_args = (Tan (FFloat, x)) }); - ("tanl", special [__ "x" []] @@ fun x -> Math { fun_args = (Tan (FLongDouble, x)) }); - ("fegetround", unknown []); - ("fesetround", unknown [drop "round" []]); (* Our float domain is rounding agnostic *) - ("__builtin_fpclassify", unknown [drop "nan" []; drop "infinite" []; drop "normal" []; drop "subnormal" []; drop "zero" []; drop "x" []]); (* TODO: We could do better here *) - ("__builtin_fpclassifyf", unknown [drop "nan" []; drop "infinite" []; drop "normal" []; drop "subnormal" []; drop "zero" []; drop "x" []]); - ("__builtin_fpclassifyl", unknown [drop "nan" []; drop "infinite" []; drop "normal" []; drop "subnormal" []; drop "zero" []; drop "x" []]); - ("__fpclassify", unknown [drop "x" []]); - ("__fpclassifyd", unknown [drop "x" []]); - ("__fpclassifyf", unknown [drop "x" []]); - ("__fpclassifyl", unknown [drop "x" []]); - ] - -let verifier_atomic_var = Cilfacade.create_var (makeGlobalVar "[__VERIFIER_atomic]" intType) -let verifier_atomic = AddrOf (Cil.var (Cilfacade.create_var verifier_atomic_var)) - -(** SV-COMP functions. - Just the ones that require special handling and cannot be stubbed. *) -let svcomp_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ - ("__VERIFIER_atomic_begin", special [] @@ Lock { lock = verifier_atomic; try_ = false; write = true; return_on_success = true }); - ("__VERIFIER_atomic_end", special [] @@ Unlock verifier_atomic); - ("__VERIFIER_nondet_loff_t", unknown []); (* cannot give it in sv-comp.c without including stdlib or similar *) - ] - -let ncurses_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ - ("echo", unknown []); - ("noecho", unknown []); - ("wattrset", unknown [drop "win" [r_deep; w_deep]; drop "attrs" []]); - ("endwin", unknown []); - ("wgetch", unknown [drop "win" [r_deep; w_deep]]); - ("wmove", unknown [drop "win" [r_deep; w_deep]; drop "y" []; drop "x" []]); - ("waddch", unknown [drop "win" [r_deep; w_deep]; drop "ch" []]); - ("waddnwstr", unknown [drop "win" [r_deep; w_deep]; drop "wstr" [r]; drop "n" []]); - ("wattr_on", unknown [drop "win" [r_deep; w_deep]; drop "attrs" []; drop "opts" []]); (* opts argument currently not used *) - ("wrefresh", unknown [drop "win" [r_deep; w_deep]]); - ("mvprintw", unknown (drop "win" [r_deep; w_deep] :: drop "y" [] :: drop "x" [] :: drop "fmt" [r] :: VarArgs (drop' [r]))); - ("initscr", unknown []); - ("curs_set", unknown [drop "visibility" []]); - ("wtimeout", unknown [drop "win" [r_deep; w_deep]; drop "delay" []]); - ("start_color", unknown []); - ("use_default_colors", unknown []); - ("wclear", unknown [drop "win" [r_deep; w_deep]]); - ("can_change_color", unknown []); - ("init_color", unknown [drop "color" []; drop "red" []; drop "green" []; drop "blue" []]); - ("init_pair", unknown [drop "pair" []; drop "f" [r]; drop "b" [r]]); - ("wbkgd", unknown [drop "win" [r_deep; w_deep]; drop "ch" []]); - ] - -let libraries = Hashtbl.of_list [ - ("c", c_descs_list @ math_descs_list); - ("posix", posix_descs_list); - ("pthread", pthread_descs_list); - ("gcc", gcc_descs_list); - ("glibc", glibc_desc_list); - ("linux-userspace", linux_userspace_descs_list); - ("linux-kernel", linux_kernel_descs_list); - ("goblint", goblint_descs_list); - ("sv-comp", svcomp_descs_list); - ("ncurses", ncurses_descs_list); - ("zstd", zstd_descs_list); - ] - -let activated_library_descs: (string, LibraryDesc.t) Hashtbl.t ResettableLazy.t = - ResettableLazy.from_fun (fun () -> - let activated = List.unique (GobConfig.get_string_list "lib.activated") in - let desc_list = List.concat_map (Hashtbl.find libraries) activated in - Hashtbl.of_list desc_list - ) - -let reset_lazy () = - ResettableLazy.reset activated_library_descs - -type categories = [ - | `Malloc of exp - | `Calloc of exp * exp - | `Realloc of exp * exp - | `Lock of bool * bool * bool (* try? * write? * return on success *) - | `Unlock - | `ThreadCreate of exp * exp * exp (* id * f * x *) - | `ThreadJoin of exp * exp (* id * ret_var *) - | `Unknown of string ] - - -let classify fn exps: categories = - let strange_arguments () = - M.warn ~category:Program "%s arguments are strange!" fn; - `Unknown fn - in - match fn with - | "pthread_join" -> - begin match exps with - | [id; ret_var] -> `ThreadJoin (id, ret_var) - | _ -> strange_arguments () - end - | "kmalloc" | "__kmalloc" | "usb_alloc_urb" | "__builtin_alloca" -> - begin match exps with - | size::_ -> `Malloc size - | _ -> strange_arguments () - end - | "kzalloc" -> - begin match exps with - | size::_ -> `Calloc (Cil.one, size) - | _ -> strange_arguments () - end - | "calloc" -> - begin match exps with - | n::size::_ -> `Calloc (n, size) - | _ -> strange_arguments () - end - | "_spin_trylock" | "spin_trylock" | "mutex_trylock" | "_spin_trylock_irqsave" - | "down_trylock" - -> `Lock(true, true, true) - | "pthread_mutex_trylock" | "pthread_rwlock_trywrlock" | "pthread_spin_trylock" - -> `Lock (true, true, false) - | "_spin_lock" | "_spin_lock_irqsave" | "_spin_lock_bh" | "down_write" - | "mutex_lock" | "mutex_lock_interruptible" | "_write_lock" | "_raw_write_lock" - | "pthread_rwlock_wrlock" | "GetResource" | "_raw_spin_lock" - | "_raw_spin_lock_flags" | "_raw_spin_lock_irqsave" | "_raw_spin_lock_irq" | "_raw_spin_lock_bh" - | "spin_lock" | "pthread_spin_lock" - -> `Lock (get_bool "sem.lock.fail", true, true) - | "pthread_mutex_lock" | "__pthread_mutex_lock" - -> `Lock (get_bool "sem.lock.fail", true, false) - | "pthread_rwlock_tryrdlock" | "pthread_rwlock_rdlock" | "_read_lock" | "_raw_read_lock" - | "down_read" - -> `Lock (get_bool "sem.lock.fail", false, true) - | "__raw_read_unlock" | "__raw_write_unlock" | "raw_spin_unlock" - | "_spin_unlock" | "spin_unlock" | "_spin_unlock_irqrestore" | "_spin_unlock_bh" | "_raw_spin_unlock_bh" - | "mutex_unlock" | "_write_unlock" | "_read_unlock" - | "pthread_mutex_unlock" | "__pthread_mutex_unlock" | "up_read" | "up_write" - | "up" | "pthread_spin_unlock" - -> `Unlock - | x -> `Unknown x - - -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 | Spawn -> f a x @ drop n x - | Read -> f a x - | Free -> [] - - let readsAllButFirst n f a x = - match a with - | Write | 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 | Spawn -> o - | Read -> i - | Free -> [] - - let writes ns a x = - let i, o = partition ns x in - match a with - | Write | Spawn -> i - | Read -> o - | Free -> [] - - let frees ns a x = - let i, o = partition ns x in - match a with - | Write | Spawn -> [] - | Read -> o - | Free -> i - - let readsFrees rs fs a x = - match a with - | Write | Spawn -> [] - | Read -> keep rs x - | Free -> keep fs x - - let onlyReads ns a x = - match a with - | Write | Spawn -> [] - | Read -> keep ns x - | Free -> [] - - let onlyWrites ns a x = - match a with - | Write | Spawn -> keep ns x - | Read -> [] - | Free -> [] - - let readsWrites rs ws a x = - match a with - | Write | Spawn -> keep ws x - | Read -> keep rs x - | Free -> [] - - let readsAll a x = - match a with - | Write | Spawn -> [] - | Read -> x - | Free -> [] - - let writesAll a x = - match a with - | Write | 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 = [ - "atoi", readsAll; (*safe*) - "__builtin_ctz", readsAll; - "__builtin_ctzl", readsAll; - "__builtin_ctzll", readsAll; - "__builtin_clz", readsAll; - "connect", readsAll; (*safe*) - "fclose", readsAll; (*safe*) - "fflush", writesAll; (*unsafe*) - "fopen", readsAll; (*safe*) - "fdopen", readsAll; (*safe*) - "setvbuf", writes[1;2]; (* TODO: if this is used to set an input buffer, the buffer (second argument) would need to remain TOP, *) - (* as any future write (or flush) of the stream could result in a write to the buffer *) - "fprintf", writes [1]; (*keep [1]*) - "__fprintf_chk", writes [1]; (*keep [1]*) - "fread", writes [1;4]; - "__fread_alias", writes [1;4]; - "__fread_chk", writes [1;4]; - "utimensat", readsAll; - "free", frees [1]; (*unsafe*) - "fwrite", readsAll;(*safe*) - "getopt", writes [2];(*keep [2]*) - "localtime", readsAll;(*safe*) - "mempcpy", writes [1];(*keep [1]*) - "__builtin___mempcpy_chk", writes [1]; - "printf", readsAll;(*safe*) - "__printf_chk", readsAll;(*safe*) - "printk", readsAll;(*safe*) - "perror", readsAll;(*safe*) - "pthread_mutex_lock", readsAll;(*safe*) - "pthread_mutex_trylock", readsAll; - "pthread_mutex_unlock", readsAll;(*safe*) - "pthread_spin_lock", readsAll;(*safe*) - "pthread_spin_trylock", readsAll; - "pthread_spin_unlock", readsAll;(*safe*) - "__pthread_mutex_lock", readsAll;(*safe*) - "__pthread_mutex_trylock", readsAll; - "__pthread_mutex_unlock", readsAll;(*safe*) - "__mutex_init", readsAll;(*safe*) - "mutex_init", readsAll;(*safe*) - "mutex_lock", readsAll;(*safe*) - "mutex_lock_interruptible", readsAll;(*safe*) - "mutex_unlock", readsAll;(*safe*) - "_spin_lock", readsAll;(*safe*) - "_spin_unlock", readsAll;(*safe*) - "_spin_lock_irqsave", readsAll;(*safe*) - "_spin_unlock_irqrestore", readsAll;(*safe*) - "pthread_mutex_init", readsAll;(*safe*) - "pthread_mutex_destroy", readsAll;(*safe*) - "pthread_mutexattr_settype", readsAll;(*safe*) - "pthread_mutexattr_init", readsAll;(*safe*) - "pthread_spin_init", readsAll;(*safe*) - "pthread_spin_destroy", readsAll;(*safe*) - "pthread_self", readsAll;(*safe*) - "read", writes [2];(*keep [2]*) - "recv", writes [2];(*keep [2]*) - "scanf", writesAllButFirst 1 readsAll;(*drop 1*) - "send", readsAll;(*safe*) - "snprintf", writes [1];(*keep [1]*) - "__builtin___snprintf_chk", writes [1];(*keep [1]*) - "sprintf", writes [1];(*keep [1]*) - "sscanf", writesAllButFirst 2 readsAll;(*drop 2*) - "strftime", writes [1];(*keep [1]*) - "strdup", readsAll;(*safe*) - "toupper", readsAll;(*safe*) - "tolower", readsAll;(*safe*) - "time", writesAll;(*unsafe*) - "vfprintf", writes [1];(*keep [1]*) - "__vfprintf_chk", writes [1];(*keep [1]*) - "vprintf", readsAll;(*safe*) - "vsprintf", writes [1];(*keep [1]*) - "write", readsAll;(*safe*) - "__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*) - "sigfillset", writesAll; (*unsafe*) - "sigprocmask", writesAll; (*unsafe*) - "uname", writesAll;(*unsafe*) - "getopt_long", writesAllButFirst 2 readsAll;(*drop 2*) - "__strdup", readsAll;(*safe*) - "strtoul__extinline", readsAll;(*safe*) - "geteuid", readsAll;(*safe*) - "opendir", readsAll; (*safe*) - "readdir_r", writesAll;(*unsafe*) - "atoi__extinline", readsAll;(*safe*) - "getpid", readsAll;(*safe*) - "fgetc", writesAll;(*unsafe*) - "getc", writesAll;(*unsafe*) - "_IO_getc", writesAll;(*unsafe*) - "closedir", writesAll;(*unsafe*) - "setrlimit", readsAll;(*safe*) - "chdir", readsAll;(*safe*) - "pipe", writesAll;(*unsafe*) - "close", writesAll;(*unsafe*) - "setsid", readsAll;(*safe*) - "strerror_r", writesAll;(*unsafe*) - "pthread_attr_init", writesAll; (*unsafe*) - "pthread_attr_setdetachstate", writesAll;(*unsafe*) - "pthread_attr_setstacksize", writesAll;(*unsafe*) - "pthread_attr_setscope", writesAll;(*unsafe*) - "pthread_attr_getdetachstate", readsAll;(*safe*) - "pthread_attr_getstacksize", readsAll;(*safe*) - "pthread_attr_getscope", readsAll;(*safe*) - "pthread_cond_init", readsAll; (*safe*) - "pthread_cond_wait", readsAll; (*safe*) - "pthread_cond_signal", readsAll;(*safe*) - "pthread_cond_broadcast", readsAll;(*safe*) - "pthread_cond_destroy", readsAll;(*safe*) - "__pthread_cond_init", readsAll; (*safe*) - "__pthread_cond_wait", readsAll; (*safe*) - "__pthread_cond_signal", readsAll;(*safe*) - "__pthread_cond_broadcast", readsAll;(*safe*) - "__pthread_cond_destroy", readsAll;(*safe*) - "pthread_key_create", writesAll;(*unsafe*) - "sigemptyset", writesAll;(*unsafe*) - "sigaddset", writesAll;(*unsafe*) - "pthread_sigmask", writesAllButFirst 2 readsAll;(*unsafe*) - "raise", writesAll;(*unsafe*) - "_strlen", readsAll;(*safe*) - "__builtin_object_size", readsAll;(*safe*) - "__builtin_alloca", readsAll;(*safe*) - "dlopen", readsAll;(*safe*) - "dlsym", readsAll;(*safe*) - "dlclose", readsAll;(*safe*) - "dlerror", readsAll;(*safe*) - "stat__extinline", writesAllButFirst 1 readsAll;(*drop 1*) - "lstat__extinline", writesAllButFirst 1 readsAll;(*drop 1*) - "__builtin_strchr", readsAll;(*safe*) - "strtok", readsAll;(*safe*) - "getpgrp", readsAll;(*safe*) - "umount2", readsAll;(*safe*) - "memchr", readsAll;(*safe*) - "memmove", writes [2;3];(*keep [2;3]*) - "__builtin_memmove", writes [2;3];(*keep [2;3]*) - "__builtin___memmove_chk", writes [2;3];(*keep [2;3]*) - "waitpid", readsAll;(*safe*) - "statfs", writes [1;3;4];(*keep [1;3;4]*) - "mkdir", readsAll;(*safe*) - "mount", readsAll;(*safe*) - "open", readsAll;(*safe*) - "__open_alias", readsAll;(*safe*) - "__open_2", readsAll;(*safe*) - "fcntl", readsAll;(*safe*) - "ioctl", writesAll;(*unsafe*) - "fstat__extinline", writesAll;(*unsafe*) - "umount", readsAll;(*safe*) - "rmdir", readsAll;(*safe*) - "strrchr", readsAll;(*safe*) - "scandir", writes [1;3;4];(*keep [1;3;4]*) - "unlink", readsAll;(*safe*) - "sched_yield", readsAll;(*safe*) - "nanosleep", writesAllButFirst 1 readsAll;(*drop 1*) - "sigdelset", readsAll;(*safe*) - "sigwait", writesAllButFirst 1 readsAll;(*drop 1*) - "setlocale", readsAll;(*safe*) - "bindtextdomain", readsAll;(*safe*) - "textdomain", readsAll;(*safe*) - "dcgettext", readsAll;(*safe*) - "syscall", writesAllButFirst 1 readsAll;(*drop 1*) - "sysconf", readsAll; - "fputs", readsAll;(*safe*) - "fputc", readsAll;(*safe*) - "fseek", writes[1]; - "rewind", writesAll; - "fileno", readsAll; - "ferror", readsAll; - "ftell", readsAll; - "putc", readsAll;(*safe*) - "putw", readsAll;(*safe*) - "putchar", readsAll;(*safe*) - "getchar", readsAll;(*safe*) - "feof", readsAll;(*safe*) - "__getdelim", writes [3];(*keep [3]*) - "vsyslog", readsAll;(*safe*) - "gethostbyname_r", readsAll;(*safe*) - "__h_errno_location", readsAll;(*safe*) - "__fxstat", readsAll;(*safe*) - "getuid", readsAll;(*safe*) - "strerror", readsAll;(*safe*) - "readdir", readsAll;(*safe*) - "openlog", readsAll;(*safe*) - "getdtablesize", readsAll;(*safe*) - "umask", readsAll;(*safe*) - "socket", readsAll;(*safe*) - "clntudp_create", writesAllButFirst 3 readsAll;(*drop 3*) - "svctcp_create", readsAll;(*safe*) - "clntudp_bufcreate", writesAll;(*unsafe*) - "authunix_create_default", readsAll;(*safe*) - "writev", readsAll;(*safe*) - "clnt_broadcast", writesAll;(*unsafe*) - "clnt_sperrno", readsAll;(*safe*) - "pmap_unset", writesAll;(*unsafe*) - "bind", readsAll;(*safe*) - "svcudp_create", readsAll;(*safe*) - "svc_register", writesAll;(*unsafe*) - "sleep", readsAll;(*safe*) - "usleep", readsAll; - "svc_run", writesAll;(*unsafe*) - "dup", readsAll; (*safe*) - "__builtin_expect", readsAll; (*safe*) - "vsnprintf", writesAllButFirst 3 readsAll; (*drop 3*) - "__builtin___vsnprintf", writesAllButFirst 3 readsAll; (*drop 3*) - "__builtin___vsnprintf_chk", writesAllButFirst 3 readsAll; (*drop 3*) - "syslog", readsAll; (*safe*) - "strcasecmp", readsAll; (*safe*) - "strchr", readsAll; (*safe*) - "getservbyname", readsAll; (*safe*) - "__error", readsAll; (*safe*) - "__maskrune", writesAll; (*unsafe*) - "inet_addr", readsAll; (*safe*) - "gethostbyname", readsAll; (*safe*) - "setsockopt", readsAll; (*safe*) - "listen", readsAll; (*safe*) - "getsockname", writes [1;3]; (*keep [1;3]*) - "getenv", readsAll; (*safe*) - "execl", readsAll; (*safe*) - "select", writes [1;5]; (*keep [1;5]*) - "accept", writesAll; (*keep [1]*) - "getpeername", writes [1]; (*keep [1]*) - "times", writesAll; (*unsafe*) - "timespec_get", writes [1]; - "fgets", writes [1;3]; (*keep [3]*) - "__fgets_alias", writes [1;3]; (*keep [3]*) - "__fgets_chk", writes [1;3]; (*keep [3]*) - "strtoul", readsAll; (*safe*) - "__tolower", readsAll; (*safe*) - "signal", writesAll; (*unsafe*) - "strsignal", readsAll; - "popen", 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]*) - "stat", writes [2]; (*keep [1]*) - "__xstat", writes [3]; (*keep [1]*) - "__lxstat", writes [3]; (*keep [1]*) - "remove", readsAll; - "BZ2_bzBuffToBuffCompress", writes [3;4]; (*keep [3;4]*) - "compress2", writes [3]; (*keep [3]*) - "__toupper", readsAll; (*safe*) - "BF_set_key", writes [3]; (*keep [3]*) - "memcmp", readsAll; (*safe*) - "sendto", writes [2;4]; (*keep [2;4]*) - "recvfrom", writes [4;5]; (*keep [4;5]*) - "srand", readsAll; (*safe*) - "gethostname", writesAll; (*unsafe*) - "fork", readsAll; (*safe*) - "setrlimit", readsAll; (*safe*) - "getrlimit", writes [2]; (*keep [2]*) - "sem_init", readsAll; (*safe*) - "sem_destroy", readsAll; (*safe*) - "sem_wait", readsAll; (*safe*) - "sem_post", readsAll; (*safe*) - "PL_NewHashTable", readsAll; (*safe*) - "__assert_fail", readsAll; (*safe*) - "assert_failed", readsAll; (*safe*) - "htonl", readsAll; (*safe*) - "htons", readsAll; (*safe*) - "ntohl", readsAll; (*safe*) - "htons", readsAll; (*safe*) - "munmap", readsAll;(*safe*) - "mmap", readsAll;(*safe*) - "clock", readsAll; - "pthread_rwlock_wrlock", readsAll; - "pthread_rwlock_trywrlock", readsAll; - "pthread_rwlock_rdlock", readsAll; - "pthread_rwlock_tryrdlock", readsAll; - "pthread_rwlockattr_destroy", writesAll; - "pthread_rwlockattr_init", writesAll; - "pthread_rwlock_destroy", readsAll; - "pthread_rwlock_init", readsAll; - "pthread_rwlock_unlock", readsAll; - "__builtin_bswap16", readsAll; - "__builtin_bswap32", readsAll; - "__builtin_bswap64", readsAll; - "__builtin_bswap128", readsAll; - "__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; - "dev_driver_string", readsAll; - "__spin_lock_init", writes [1]; - "kmem_cache_create", readsAll; - "idr_pre_get", readsAll; - "zil_replay", writes [1;2;3;5]; - "__VERIFIER_nondet_int", readsAll; (* no args, declare invalidate actions to prevent invalidating globals when extern in regression tests *) - (* no args, declare invalidate actions to prevent invalidating globals *) - "__VERIFIER_atomic_begin", readsAll; - "__VERIFIER_atomic_end", readsAll; - "isatty", readsAll; - "setpriority", readsAll; - "getpriority", readsAll; - (* ddverify *) - "spin_lock_init", readsAll; - "spin_lock", readsAll; - "spin_unlock", readsAll; - "sema_init", readsAll; - "down_trylock", readsAll; - "up", readsAll; - "acos", readsAll; - "acosf", readsAll; - "acosh", readsAll; - "acoshf", readsAll; - "acoshl", readsAll; - "acosl", readsAll; - "asin", readsAll; - "asinf", readsAll; - "asinh", readsAll; - "asinhf", readsAll; - "asinhl", readsAll; - "asinl", readsAll; - "atan", readsAll; - "atan2", readsAll; - "atan2f", readsAll; - "atan2l", readsAll; - "atanf", readsAll; - "atanh", readsAll; - "atanhf", readsAll; - "atanhl", readsAll; - "atanl", readsAll; - "cbrt", readsAll; - "cbrtf", readsAll; - "cbrtl", readsAll; - "ceil", readsAll; - "ceilf", readsAll; - "ceill", readsAll; - "copysign", readsAll; - "copysignf", readsAll; - "copysignl", readsAll; - "cos", readsAll; - "cosf", readsAll; - "cosh", readsAll; - "coshf", readsAll; - "coshl", readsAll; - "cosl", readsAll; - "erf", readsAll; - "erfc", readsAll; - "erfcf", readsAll; - "erfcl", readsAll; - "erff", readsAll; - "erfl", readsAll; - "exp", readsAll; - "exp2", readsAll; - "exp2f", readsAll; - "exp2l", readsAll; - "expf", readsAll; - "expl", readsAll; - "expm1", readsAll; - "expm1f", readsAll; - "expm1l", readsAll; - "fdim", readsAll; - "fdimf", readsAll; - "fdiml", readsAll; - "fma", readsAll; - "fmaf", readsAll; - "fmal", readsAll; - "fmax", readsAll; - "fmaxf", readsAll; - "fmaxl", readsAll; - "fmin", readsAll; - "fminf", readsAll; - "fminl", readsAll; - "fmod", readsAll; - "fmodf", readsAll; - "fmodl", readsAll; - "frexp", readsAll; - "frexpf", readsAll; - "frexpl", readsAll; - "hypot", readsAll; - "hypotf", readsAll; - "hypotl", readsAll; - "ilogb", readsAll; - "ilogbf", readsAll; - "ilogbl", readsAll; - "j0", readsAll; - "j1", readsAll; - "jn", readsAll; - "ldexp", readsAll; - "ldexpf", readsAll; - "ldexpl", readsAll; - "lgamma", readsAll; - "lgammaf", readsAll; - "lgammal", readsAll; - "llrint", readsAll; - "llrintf", readsAll; - "llrintl", readsAll; - "llround", readsAll; - "llroundf", readsAll; - "llroundl", readsAll; - "log", readsAll; - "log10", readsAll; - "log10f", readsAll; - "log10l", readsAll; - "log1p", readsAll; - "log1pf", readsAll; - "log1pl", readsAll; - "log2", readsAll; - "log2f", readsAll; - "log2l", readsAll; - "logb", readsAll; - "logbf", readsAll; - "logbl", readsAll; - "logf", readsAll; - "logl", readsAll; - "lrint", readsAll; - "lrintf", readsAll; - "lrintl", readsAll; - "lround", readsAll; - "lroundf", readsAll; - "lroundl", readsAll; - "modf", readsAll; - "modff", readsAll; - "modfl", readsAll; - "nan", readsAll; - "nanf", readsAll; - "nanl", readsAll; - "nearbyint", readsAll; - "nearbyintf", readsAll; - "nearbyintl", readsAll; - "nextafter", readsAll; - "nextafterf", readsAll; - "nextafterl", readsAll; - "nexttoward", readsAll; - "nexttowardf", readsAll; - "nexttowardl", readsAll; - "pow", readsAll; - "powf", readsAll; - "powl", readsAll; - "remainder", readsAll; - "remainderf", readsAll; - "remainderl", readsAll; - "remquo", readsAll; - "remquof", readsAll; - "remquol", readsAll; - "rint", readsAll; - "rintf", readsAll; - "rintl", readsAll; - "round", readsAll; - "roundf", readsAll; - "roundl", readsAll; - "scalbln", readsAll; - "scalblnf", readsAll; - "scalblnl", readsAll; - "scalbn", readsAll; - "scalbnf", readsAll; - "scalbnl", readsAll; - "sin", readsAll; - "sinf", readsAll; - "sinh", readsAll; - "sinhf", readsAll; - "sinhl", readsAll; - "sinl", readsAll; - "sqrt", readsAll; - "sqrtf", readsAll; - "sqrtl", readsAll; - "tan", readsAll; - "tanf", readsAll; - "tanh", readsAll; - "tanhf", readsAll; - "tanhl", readsAll; - "tanl", readsAll; - "tgamma", readsAll; - "tgammaf", readsAll; - "tgammal", readsAll; - "trunc", readsAll; - "truncf", readsAll; - "truncl", readsAll; - "y0", readsAll; - "y1", readsAll; - "yn", readsAll; - "__goblint_assume_join", readsAll; - ] - - -(* used by get_invalidate_action to make sure - * that hash of invalidates is built only once - * - * Hashtable from strings to functions of type (exp list -> exp list) -*) -let processed_table = ref None - -let get_invalidate_action name = - let tbl = match !processed_table with - | None -> begin - let hash = Hashtbl.create 113 in - let f (k, v) = Hashtbl.add hash k v in - List.iter f invalidate_actions; - processed_table := (Some hash); - hash - end - | Some x -> x - in - if Hashtbl.mem tbl name - then Some (Hashtbl.find tbl name) - else None - - -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 - -let kernel_safe_uncalled = Set.String.of_list ["__inittest"; "init_module"; "__exittest"; "cleanup_module"] -let kernel_safe_uncalled_regex = List.map Str.regexp ["__check_.*"] -let is_safe_uncalled fn_name = - Set.String.mem fn_name kernel_safe_uncalled || - List.exists (fun r -> Str.string_match r fn_name 0) kernel_safe_uncalled_regex - - -let unknown_desc ~f name = (* TODO: remove name argument, unknown function shouldn't have classify *) - 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 -> [] - | Spawn when get_bool "sem.unknown_function.spawn" -> args - | Spawn -> [] - in - let attrs: LibraryDesc.attr list = - if GobConfig.get_bool "sem.unknown_function.invalidate.globals" then - [InvalidateGlobals] - else - [] - in - let classify_name args = - match classify name args with - | `Unknown _ as category -> - (* TODO: remove hack when all classify are migrated *) - if not (CilType.Varinfo.equal f dummyFunDec.svar) && not (use_special f.vname) then - M.error ~category:Imprecise ~tags:[Category Unsound] "Function definition missing for %s" f.vname; - category - | category -> category - in - LibraryDesc.of_old ~attrs old_accesses classify_name - -let find f = - let name = f.vname in - match Hashtbl.find_option (ResettableLazy.force activated_library_descs) name with - | Some desc -> desc - | None -> - match get_invalidate_action name with - | Some old_accesses -> - LibraryDesc.of_old old_accesses (classify name) - | None -> - unknown_desc ~f name - - -let is_special fv = - if use_special fv.vname then - true - else - match Cilfacade.find_varinfo_fundec fv with - | _ -> false - | exception Not_found -> true diff --git a/src/analyses/locksetAnalysis.ml b/src/analyses/locksetAnalysis.ml index 2e9e08f03d..6a816b9e6c 100644 --- a/src/analyses/locksetAnalysis.ml +++ b/src/analyses/locksetAnalysis.ml @@ -18,7 +18,7 @@ struct module C = D let startstate v = D.empty () - let threadenter ctx lval f args = [D.empty ()] + let threadenter ctx ~multiple lval f args = [D.empty ()] let exitstate v = D.empty () end diff --git a/src/analyses/loopTermination.ml b/src/analyses/loopTermination.ml new file mode 100644 index 0000000000..10e0f5c5f4 --- /dev/null +++ b/src/analyses/loopTermination.ml @@ -0,0 +1,87 @@ +(** Termination analysis for loops and [goto] statements ([termination]). *) + +open Analyses +open GoblintCil +open TerminationPreprocessing + +(** Contains all loop counter variables (varinfo) and maps them to their corresponding loop statement. *) +let loop_counters : stmt VarToStmt.t ref = ref VarToStmt.empty + +(** Checks whether a variable can be bounded. *) +let check_bounded ctx varinfo = + let open IntDomain.IntDomTuple in + let exp = Lval (Var varinfo, NoOffset) in + match ctx.ask (EvalInt exp) with + | `Top -> false + | `Lifted v -> not (is_top_of (ikind v) v) + | `Bot -> failwith "Loop counter variable is Bot." + +(** We want to record termination information of loops and use the loop + * statements for that. We use this lifting because we need to have a + * lattice. *) +module Statements = Lattice.Flat (CilType.Stmt) (Printable.DefaultNames) + +(** The termination analysis considering loops and gotos *) +module Spec : Analyses.MCPSpec = +struct + + include Analyses.IdentitySpec + + let name () = "termination" + + module D = Lattice.Unit + module C = D + module V = struct + include UnitV + let is_write_only _ = true + end + module G = MapDomain.MapBot (Statements) (BoolDomain.MustBool) + + let startstate _ = () + let exitstate = startstate + + let find_loop ~loop_counter = + VarToStmt.find loop_counter !loop_counters + + (** Recognizes a call of [__goblint_bounded] to check the EvalInt of the + * respective loop counter variable at that position. *) + let special ctx (lval : lval option) (f : varinfo) (arglist : exp list) = + if !AnalysisState.postsolving then + match f.vname, arglist with + "__goblint_bounded", [Lval (Var loop_counter, NoOffset)] -> + (try + let loop_statement = find_loop ~loop_counter in + let is_bounded = check_bounded ctx loop_counter in + ctx.sideg () (G.add (`Lifted loop_statement) is_bounded (ctx.global ())); + (* In case the loop is not bounded, a warning is created. *) + if not (is_bounded) then ( + M.warn ~loc:(M.Location.CilLocation (Cilfacade.get_stmtLoc loop_statement)) ~category:Termination "The program might not terminate! (Loop analysis)" + ); + () + with Not_found -> + failwith "Encountered a call to __goblint_bounded with an unknown loop counter variable.") + | _ -> () + else () + + let query ctx (type a) (q: a Queries.t): a Queries.result = + match q with + | Queries.MustTermLoop loop_statement -> + let multithreaded = ctx.ask Queries.IsEverMultiThreaded in + (not multithreaded) + && (match G.find_opt (`Lifted loop_statement) (ctx.global ()) with + Some b -> b + | None -> false) + | Queries.MustTermAllLoops -> + let multithreaded = ctx.ask Queries.IsEverMultiThreaded in + if multithreaded then ( + M.warn ~category:Termination "The program might not terminate! (Multithreaded)\n"; + false) + else + G.for_all (fun _ term_info -> term_info) (ctx.global ()) + | _ -> Queries.Result.top q + +end + +let () = + Cilfacade.register_preprocess (Spec.name ()) (new loopCounterVisitor loop_counters); + MCP.register_analysis (module Spec : MCPSpec) diff --git a/src/analyses/mCP.ml b/src/analyses/mCP.ml index 1b6a7e5a1d..50f6d5409b 100644 --- a/src/analyses/mCP.ml +++ b/src/analyses/mCP.ml @@ -140,11 +140,13 @@ struct f ((k,v::a')::a) b in f [] xs - let do_spawns ctx (xs:(varinfo * (lval option * exp list)) list) = + let do_spawns ctx (xs:(varinfo * (lval option * exp list * bool)) list) = let spawn_one v d = - List.iter (fun (lval, args) -> ctx.spawn lval v args) d + List.iter (fun (lval, args, multiple) -> ctx.spawn ~multiple lval v args) d in - if not (get_bool "exp.single-threaded") then + if get_bool "exp.single-threaded" then + M.msg_final Error ~category:Unsound "Thread not spawned" + 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) = @@ -322,8 +324,8 @@ struct and outer_ctx tfname ?spawns ?sides ?emits ctx = let spawn = match spawns with - | Some spawns -> (fun l v a -> spawns := (v,(l,a)) :: !spawns) - | None -> (fun v d -> failwith ("Cannot \"spawn\" in " ^ tfname ^ " context.")) + | Some spawns -> (fun ?(multiple=false) l v a -> spawns := (v,(l,a,multiple)) :: !spawns) + | 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) @@ -565,20 +567,20 @@ struct let d = do_emits ctx !emits d q in if q then raise Deadcode else d - let threadenter (ctx:(D.t, G.t, C.t, V.t) ctx) lval f a = + let threadenter (ctx:(D.t, G.t, C.t, V.t) ctx) ~multiple lval f a = let sides = ref [] in let emits = ref [] in let ctx'' = outer_ctx "threadenter" ~sides ~emits ctx in let f (n,(module S:MCPSpec),d) = let ctx' : (S.D.t, S.G.t, S.C.t, S.V.t) ctx = inner_ctx "threadenter" ctx'' n d in - map (fun d -> (n, repr d)) @@ S.threadenter ctx' lval f a + map (fun d -> (n, repr d)) @@ (S.threadenter ~multiple) ctx' lval f a in let css = map f @@ spec_list ctx.local in do_sideg ctx !sides; (* TODO: this do_emits is now different from everything else *) map (fun d -> do_emits ctx !emits d false) @@ map topo_sort_an @@ n_cartesian_product css - let threadspawn (ctx:(D.t, G.t, C.t, V.t) ctx) lval f a fctx = + let threadspawn (ctx:(D.t, G.t, C.t, V.t) ctx) ~multiple lval f a fctx = let sides = ref [] in let emits = ref [] in let ctx'' = outer_ctx "threadspawn" ~sides ~emits ctx in @@ -586,7 +588,7 @@ struct let f post_all (n,(module S:MCPSpec),(d,fd)) = let ctx' : (S.D.t, S.G.t, S.C.t, S.V.t) ctx = inner_ctx "threadspawn" ~post_all ctx'' n d in let fctx' : (S.D.t, S.G.t, S.C.t, S.V.t) ctx = inner_ctx "threadspawn" ~post_all fctx'' n fd in - n, repr @@ S.threadspawn ctx' lval f a fctx' + n, repr @@ S.threadspawn ~multiple ctx' lval f a fctx' in let d, q = map_deadcode f @@ spec_list2 ctx.local fctx.local in do_sideg ctx !sides; diff --git a/src/analyses/mCPRegistry.ml b/src/analyses/mCPRegistry.ml index d1311e0427..5d0174d44c 100644 --- a/src/analyses/mCPRegistry.ml +++ b/src/analyses/mCPRegistry.ml @@ -149,20 +149,21 @@ struct let unop_map f x = List.rev @@ unop_fold (fun a n s d -> (n, f s d) :: a) [] x - let pretty () x = - let f a n (module S : Printable.S) x = Pretty.dprintf "%s:%a" (S.name ()) S.pretty (obj x) :: a in - let xs = unop_fold f [] x in - match xs with - | [] -> text "[]" - | x :: [] -> x - | x :: y -> - let rest = List.fold_left (fun p n->p ++ text "," ++ break ++ n) nil y in - text "[" ++ align ++ x ++ rest ++ unalign ++ text "]" + let pretty () xs = + let pretty_one a n (module S: Printable.S) x = + let doc = Pretty.dprintf "%s:%a" (find_spec_name n) S.pretty (obj x) in + match a with + | None -> Some doc + | Some a -> Some (a ++ text "," ++ line ++ doc) + in + let doc = Option.default Pretty.nil (unop_fold pretty_one None xs) in + Pretty.dprintf "[@[%a@]]" Pretty.insert doc let show x = let xs = unop_fold (fun a n (module S : Printable.S) x -> let analysis_name = find_spec_name n in - (analysis_name ^ ":(" ^ S.show (obj x) ^ ")") :: a) [] x + (analysis_name ^ ":(" ^ S.show (obj x) ^ ")") :: a + ) [] x in IO.to_string (List.print ~first:"[" ~last:"]" ~sep:", " String.print) (rev xs) @@ -214,7 +215,7 @@ struct let arbitrary () = let arbs = map (fun (n, (module D: Printable.S)) -> QCheck.map ~rev:(fun (_, o) -> obj o) (fun x -> (n, repr x)) @@ D.arbitrary ()) @@ domain_list () in - MyCheck.Arbitrary.sequence arbs + GobQCheck.Arbitrary.sequence arbs let relift = unop_map (fun (module S: Printable.S) x -> Obj.repr (S.relift (Obj.obj x))) end @@ -317,6 +318,7 @@ struct open Obj include DomListPrintable (PrintableOfRepresentativeSpec (DLSpec)) + let name () = "MCP.P" type elt = (int * unknown) list @@ -343,6 +345,7 @@ struct open Obj include DomListPrintable (PrintableOfLatticeSpec (DLSpec)) + let name () = "MCP.D" let binop_fold f a (x:t) (y:t) = GobList.fold_left3 (fun a (n,d) (n',d') (n'',s) -> assert (n = n' && n = n''); f a n s d d') a x y (domain_list ()) @@ -369,12 +372,19 @@ struct let top () = map (fun (n,(module S : Lattice.S)) -> (n,repr @@ S.top ())) @@ domain_list () let bot () = map (fun (n,(module S : Lattice.S)) -> (n,repr @@ S.bot ())) @@ domain_list () - let pretty_diff () (x,y) = - let f a n (module S : Lattice.S) x y = - if S.leq (obj x) (obj y) then a - else a ++ S.pretty_diff () (obj x, obj y) ++ text ". " + let pretty_diff () (xs, ys) = + let pretty_one a n (module S: Lattice.S) x y = + if S.leq (obj x) (obj y) then + a + else ( + let doc = Pretty.dprintf "%s:%a" (find_spec_name n) S.pretty_diff (obj x, obj y) in + match a with + | None -> Some doc + | Some a -> Some (a ++ text "," ++ line ++ doc) + ) in - binop_fold f nil x y + let doc = Option.default Pretty.nil (binop_fold pretty_one None xs ys) in + Pretty.dprintf "[@[%a@]]" Pretty.insert doc end module DomVariantLattice0 (DLSpec : DomainListLatticeSpec) diff --git a/src/analyses/mallocFresh.ml b/src/analyses/mallocFresh.ml index c4a0c035f2..d1314d5009 100644 --- a/src/analyses/mallocFresh.ml +++ b/src/analyses/mallocFresh.ml @@ -19,12 +19,12 @@ struct let assign_lval (ask: Queries.ask) lval local = match ask.f (MayPointTo (AddrOf lval)) with - | ls when Queries.LS.is_top ls || Queries.LS.mem (dummyFunDec.svar, `NoOffset) ls -> - D.empty () - | ls when Queries.LS.exists (fun (v, _) -> not (D.mem v local) && (v.vglob || ThreadEscape.has_escaped ask v)) ls -> - D.empty () - | _ -> - local + | ad when Queries.AD.is_top ad -> D.empty () + | ad when Queries.AD.exists (function + | Queries.AD.Addr.Addr (v,_) -> not (D.mem v local) && (v.vglob || ThreadEscape.has_escaped ask v) + | _ -> false + ) ad -> D.empty () + | _ -> local let assign ctx lval rval = assign_lval (Analyses.ask_of_ctx ctx) lval ctx.local @@ -43,7 +43,7 @@ struct | Malloc _ | Calloc _ | Realloc _ -> - begin match ctx.ask HeapVar with + begin match ctx.ask (AllocVar {on_stack = false}) with | `Lifted var -> D.add var ctx.local | _ -> ctx.local end @@ -52,10 +52,10 @@ struct | None -> ctx.local | Some lval -> assign_lval (Analyses.ask_of_ctx ctx) lval ctx.local - let threadenter ctx lval f args = + let threadenter ctx ~multiple lval f args = [D.empty ()] - let threadspawn ctx lval f args fctx = + let threadspawn ctx ~multiple lval f args fctx = D.empty () module A = diff --git a/src/analyses/malloc_null.ml b/src/analyses/malloc_null.ml index 656e1e6f14..f993db0c6e 100644 --- a/src/analyses/malloc_null.ml +++ b/src/analyses/malloc_null.ml @@ -38,9 +38,11 @@ struct match e with | Lval (Var v, offs) -> begin match a.f (Queries.MayPointTo (mkAddrOf (Var v,offs))) with - | a when not (Queries.LS.is_top a) - && not (Queries.LS.mem (dummyFunDec.svar,`NoOffset) a) -> - Queries.LS.iter (fun (v,o) -> warn_lval st (v, Offs.of_exp o)) a + | ad when not (Queries.AD.is_top ad) -> + Queries.AD.iter (function + | Queries.AD.Addr.Addr mval -> warn_lval st mval + | _ -> () + ) ad | _ -> () end | _ -> () @@ -92,31 +94,33 @@ struct (* Remove null values from state that are unreachable from exp.*) let remove_unreachable (ask: Queries.ask) (args: exp list) (st: D.t) : D.t = let reachable = - let do_exp e = + let do_exp e a = match ask.f (Queries.ReachableFrom e) with - | a when not (Queries.LS.is_top a) -> - let to_extra (v,o) xs = AD.of_mval (v, Offs.of_exp o) :: xs in - Queries.LS.fold to_extra (Queries.LS.remove (dummyFunDec.svar, `NoOffset) a) [] + | ad when not (Queries.AD.is_top ad) -> + ad + |> Queries.AD.filter (function + | Queries.AD.Addr.Addr _ -> true + | _ -> false) + |> Queries.AD.join a (* Ignore soundness warnings, as invalidation proper will raise them. *) - | _ -> [] + | _ -> AD.empty () in - List.concat_map do_exp args + List.fold_right do_exp args (AD.empty ()) in - let add_exploded_struct (one: AD.t) (many: AD.t) : AD.t = - let vars = AD.to_var_may one in - List.fold_right AD.add (List.concat_map to_addrs vars) many + let vars = + reachable + |> AD.to_var_may + |> List.concat_map to_addrs + |> AD.of_list in - let vars = List.fold_right add_exploded_struct reachable (AD.empty ()) in if D.is_top st then D.top () else D.filter (fun x -> AD.mem x vars) st let get_concrete_lval (ask: Queries.ask) (lval:lval) = match ask.f (Queries.MayPointTo (mkAddrOf lval)) with - | a when Queries.LS.cardinal a = 1 - && not (Queries.LS.mem (dummyFunDec.svar,`NoOffset) a) -> - let v, o = Queries.LS.choose a in - Some (Var v, Offs.of_exp o) + | ad when Queries.AD.cardinal ad = 1 && not (Queries.AD.mem UnknownPtr ad) -> + Queries.AD.Addr.to_mval (Queries.AD.choose ad) | _ -> None let get_concrete_exp (exp:exp) gl (st:D.t) = @@ -127,11 +131,13 @@ struct let might_be_null (ask: Queries.ask) lv gl st = match ask.f (Queries.MayPointTo (mkAddrOf lv)) with - | a when not (Queries.LS.is_top a) && not (Queries.LS.mem (dummyFunDec.svar,`NoOffset) a) -> - let one_addr_might (v,o) = - D.exists (fun x -> GobOption.exists (fun x -> is_prefix_of (v, Offs.of_exp o) x) (Addr.to_mval x)) st + | ad when not (Queries.AD.is_top ad) -> + let one_addr_might = function + | Queries.AD.Addr.Addr mval -> + D.exists (fun addr -> GobOption.exists (fun x -> is_prefix_of mval x) (Addr.to_mval addr)) st + | _ -> false in - Queries.LS.exists one_addr_might a + Queries.AD.exists one_addr_might ad | _ -> false (* @@ -143,8 +149,8 @@ struct warn_deref_exp (Analyses.ask_of_ctx ctx) ctx.local (Lval lval) ; warn_deref_exp (Analyses.ask_of_ctx ctx) ctx.local rval; match get_concrete_exp rval ctx.global ctx.local, get_concrete_lval (Analyses.ask_of_ctx ctx) lval with - | Some rv , Some (Var vt,ot) when might_be_null (Analyses.ask_of_ctx ctx) rv ctx.global ctx.local -> - D.add (Addr.of_mval (vt,ot)) ctx.local + | Some rv, Some mval when might_be_null (Analyses.ask_of_ctx ctx) rv ctx.global ctx.local -> + D.add (Addr.of_mval mval) ctx.local | _ -> ctx.local let branch ctx (exp:exp) (tv:bool) : D.t = @@ -185,7 +191,7 @@ struct match lval, D.mem (return_addr ()) au with | Some lv, true -> begin match get_concrete_lval (Analyses.ask_of_ctx ctx) lv with - | Some (Var v,ofs) -> D.add (Addr.of_mval (v,ofs)) ctx.local + | Some mval -> D.add (Addr.of_mval mval) ctx.local | _ -> ctx.local end | _ -> ctx.local @@ -198,9 +204,9 @@ struct | Malloc _, Some lv -> begin match get_concrete_lval (Analyses.ask_of_ctx ctx) lv with - | Some (Var v, offs) -> + | Some mval -> ctx.split ctx.local [Events.SplitBranch ((Lval lv), true)]; - ctx.split (D.add (Addr.of_mval (v,offs)) ctx.local) [Events.SplitBranch ((Lval lv), false)]; + ctx.split (D.add (Addr.of_mval mval) ctx.local) [Events.SplitBranch ((Lval lv), false)]; raise Analyses.Deadcode | _ -> ctx.local end @@ -209,8 +215,8 @@ struct let name () = "malloc_null" let startstate v = D.empty () - let threadenter ctx lval f args = [D.empty ()] - let threadspawn ctx lval f args fctx = ctx.local + let threadenter ctx ~multiple lval f args = [D.empty ()] + let threadspawn ctx ~multiple lval f args fctx = ctx.local let exitstate v = D.empty () let init marshal = diff --git a/src/analyses/memLeak.ml b/src/analyses/memLeak.ml new file mode 100644 index 0000000000..456d434be7 --- /dev/null +++ b/src/analyses/memLeak.ml @@ -0,0 +1,262 @@ +(** An analysis for the detection of memory leaks ([memLeak]). *) + +open GoblintCil +open Analyses +open MessageCategory +open AnalysisStateUtil + +module ToppedVarInfoSet = SetDomain.ToppedSet(CilType.Varinfo)(struct let topname = "All Heap Variables" end) +module WasMallocCalled = BoolDomain.MayBool +module Spec : Analyses.MCPSpec = +struct + include Analyses.IdentitySpec + + let name () = "memLeak" + + module D = ToppedVarInfoSet + module C = D + module P = IdentityP (D) + + module V = UnitV + module G = WasMallocCalled + + let context _ d = d + + let must_be_single_threaded ~since_start ctx = + ctx.ask (Queries.MustBeSingleThreaded { since_start }) + + let was_malloc_called ctx = + ctx.global () + + (* HELPER FUNCTIONS *) + let get_global_vars () = + List.filter_map (function GVar (v, _, _) | GVarDecl (v, _) -> Some v | _ -> None) !Cilfacade.current_file.globals + + let get_global_struct_ptr_vars () = + get_global_vars () + |> List.filter (fun v -> + match unrollType v.vtype with + | TPtr (TComp (ci,_), _) + | TPtr ((TNamed ({ttype = TComp (ci, _); _}, _)), _) -> ci.cstruct + | TComp (_, _) + | (TNamed ({ttype = TComp _; _}, _)) -> false + | _ -> false) + + let get_global_struct_non_ptr_vars () = + get_global_vars () + |> List.filter (fun v -> + match unrollType v.vtype with + | TComp (ci, _) + | (TNamed ({ttype = TComp (ci,_); _}, _)) -> ci.cstruct + | _ -> false) + + let get_reachable_mem_from_globals (global_vars:varinfo list) ctx = + global_vars + |> List.map (fun v -> Lval (Var v, NoOffset)) + |> List.filter_map (fun exp -> + match ctx.ask (Queries.MayPointTo exp) with + | a when not (Queries.AD.is_top a) && Queries.AD.cardinal a = 1 -> + begin match List.hd @@ Queries.AD.elements a with + | Queries.AD.Addr.Addr (v, _) when (ctx.ask (Queries.IsHeapVar v)) && not (ctx.ask (Queries.IsMultiple v)) -> Some v + | _ -> None + end + | _ -> None) + + let rec get_reachable_mem_from_str_ptr_globals (global_struct_ptr_vars:varinfo list) ctx = + let eval_value_of_heap_var heap_var = + match ctx.ask (Queries.EvalValue (Lval (Var heap_var, NoOffset))) with + | a when not (Queries.VD.is_top a) -> + begin match a with + | Struct s -> + List.fold_left (fun acc f -> + if isPointerType f.ftype then + begin match ValueDomain.Structs.get s f with + | Queries.VD.Address a when not (Queries.AD.is_top a) && Queries.AD.cardinal a = 1 -> + let reachable_from_addr_set = + Queries.AD.fold (fun addr acc -> + match addr with + | Queries.AD.Addr.Addr (v, _) -> (v :: get_reachable_mem_from_str_ptr_globals [v] ctx) @ acc + | _ -> acc + ) a [] + in + reachable_from_addr_set @ acc + | _ -> acc + end + else acc + ) [] (ValueDomain.Structs.keys s) + | _ -> [] + end + | _ -> [] + in + let get_pts_of_non_heap_ptr_var var = + match ctx.ask (Queries.MayPointTo (Lval (Var var, NoOffset))) with + | a when not (Queries.AD.is_top a) && Queries.AD.cardinal a = 1 -> + begin match List.hd @@ Queries.AD.elements a with + | Queries.AD.Addr.Addr (v, _) when (ctx.ask (Queries.IsHeapVar v)) && not (ctx.ask (Queries.IsMultiple v)) -> v :: (eval_value_of_heap_var v) + | Queries.AD.Addr.Addr (v, _) when not (ctx.ask (Queries.IsAllocVar v)) && isPointerType v.vtype -> get_reachable_mem_from_str_ptr_globals [v] ctx + | _ -> [] + end + | _ -> [] + in + global_struct_ptr_vars + |> List.fold_left (fun acc var -> + if ctx.ask (Queries.IsHeapVar var) then (eval_value_of_heap_var var) @ acc + else if not (ctx.ask (Queries.IsAllocVar var)) && isPointerType var.vtype then (get_pts_of_non_heap_ptr_var var) @ acc + else acc + ) [] + + let get_reachable_mem_from_str_non_ptr_globals (global_struct_non_ptr_vars:varinfo list) ctx = + global_struct_non_ptr_vars + (* Filter out global struct vars that don't have pointer fields *) + |> List.filter_map (fun v -> + match ctx.ask (Queries.EvalValue (Lval (Var v, NoOffset))) with + | a when not (Queries.VD.is_top a) -> + begin match a with + | Queries.VD.Struct s -> + let struct_fields = ValueDomain.Structs.keys s in + let ptr_struct_fields = List.filter (fun f -> isPointerType f.ftype) struct_fields in + if ptr_struct_fields = [] then None else Some (s, ptr_struct_fields) + | _ -> None + end + | _ -> None + ) + |> List.fold_left (fun acc_struct (s, fields) -> + let reachable_from_fields = + List.fold_left (fun acc_field field -> + match ValueDomain.Structs.get s field with + | Queries.VD.Address a -> + let reachable_from_addr_set = + Queries.AD.fold (fun addr acc_addr -> + match addr with + | Queries.AD.Addr.Addr (v, _) -> + let reachable_from_v = Queries.AD.of_list (List.map (fun v -> Queries.AD.Addr.Addr (v, `NoOffset)) (get_reachable_mem_from_str_ptr_globals [v] ctx)) in + Queries.AD.join (Queries.AD.add addr reachable_from_v) acc_addr + | _ -> acc_addr + ) a (Queries.AD.empty ()) + in (Queries.AD.to_var_may reachable_from_addr_set) @ acc_field + | _ -> acc_field + ) [] fields + in + reachable_from_fields @ acc_struct + ) [] + + let warn_for_multi_threaded_due_to_abort ctx = + let malloc_called = was_malloc_called ctx in + if not (must_be_single_threaded ctx ~since_start:true) && malloc_called then ( + set_mem_safety_flag InvalidMemTrack; + set_mem_safety_flag InvalidMemcleanup; + M.warn ~category:(Behavior (Undefined MemoryLeak)) ~tags:[CWE 401] "Program aborted while running in multi-threaded mode. A memory leak might occur" + ) + + (* If [is_return] is set to [true], then a thread return occurred, else a thread exit *) + let warn_for_thread_return_or_exit ctx is_return = + if not (ToppedVarInfoSet.is_empty ctx.local) then ( + set_mem_safety_flag InvalidMemTrack; + set_mem_safety_flag InvalidMemcleanup; + let current_thread = ctx.ask (Queries.CurrentThreadId) in + M.warn ~category:(Behavior (Undefined MemoryLeak)) ~tags:[CWE 401] "Memory may be leaked at thread %s for thread %a" (if is_return then "return" else "exit") ThreadIdDomain.ThreadLifted.pretty current_thread + ) + + let check_for_mem_leak ?(assert_exp_imprecise = false) ?(exp = None) ctx = + let allocated_mem = ctx.local in + if not (D.is_empty allocated_mem) then + let reachable_mem_from_non_struct_globals = D.of_list (get_reachable_mem_from_globals (get_global_vars ()) ctx) in + let reachable_mem_from_struct_ptr_globals = D.of_list (get_reachable_mem_from_str_ptr_globals (get_global_struct_ptr_vars ()) ctx) in + let reachable_mem_from_struct_non_ptr_globals = D.of_list (get_reachable_mem_from_str_non_ptr_globals (get_global_struct_non_ptr_vars ()) ctx) in + let reachable_mem_from_struct_globals = D.join reachable_mem_from_struct_ptr_globals reachable_mem_from_struct_non_ptr_globals in + let reachable_mem = D.join reachable_mem_from_non_struct_globals reachable_mem_from_struct_globals in + (* Check and warn if there's unreachable allocated memory at program exit *) + let allocated_and_unreachable_mem = D.diff allocated_mem reachable_mem in + if not (D.is_empty allocated_and_unreachable_mem) then ( + set_mem_safety_flag InvalidMemTrack; + M.warn ~category:(Behavior (Undefined MemoryLeak)) ~tags:[CWE 401] "There is unreachable allocated heap memory at program exit. A memory leak might occur for the alloc vars %a\n" (Pretty.d_list ", " CilType.Varinfo.pretty) (D.elements allocated_and_unreachable_mem) + ); + (* Check and warn if some of the allocated memory is not deallocated at program exit *) + match assert_exp_imprecise, exp with + | true, Some exp -> + set_mem_safety_flag InvalidMemcleanup; + M.warn ~category:(Behavior (Undefined MemoryLeak)) ~tags:[CWE 401] "Assert expression %a is unknown. Memory leak might possibly occur for heap variables: %a" d_exp exp D.pretty allocated_mem + | _ -> + set_mem_safety_flag InvalidMemcleanup; + M.warn ~category:(Behavior (Undefined MemoryLeak)) ~tags:[CWE 401] "Memory leak detected for heap variables" + + (* TRANSFER FUNCTIONS *) + let return ctx (exp:exp option) (f:fundec) : D.t = + (* Check for a valid-memcleanup and memtrack violation in a multi-threaded setting *) + (* The check for multi-threadedness is to ensure that valid-memtrack and valid-memclenaup are treated separately for single-threaded programs *) + if (ctx.ask (Queries.MayBeThreadReturn) && not (must_be_single_threaded ctx ~since_start:true)) then ( + warn_for_thread_return_or_exit ctx true + ); + (* Returning from "main" is one possible program exit => need to check for memory leaks *) + if f.svar.vname = "main" then ( + check_for_mem_leak ctx; + if not (must_be_single_threaded ctx ~since_start:false) && was_malloc_called ctx then begin + set_mem_safety_flag InvalidMemTrack; + set_mem_safety_flag InvalidMemcleanup; + M.warn ~category:(Behavior (Undefined MemoryLeak)) ~tags:[CWE 401] "Possible memory leak: Memory was allocated in a multithreaded program, but not all threads are joined." + end + ); + ctx.local + + let special ctx (lval:lval option) (f:varinfo) (arglist:exp list) : D.t = + let state = ctx.local in + let desc = LibraryFunctions.find f in + match desc.special arglist with + | Malloc _ + | Calloc _ + | Realloc _ -> + ctx.sideg () true; + begin match ctx.ask (Queries.AllocVar {on_stack = false}) with + | `Lifted var -> + ToppedVarInfoSet.add var state + | _ -> state + end + | Free ptr -> + begin match ctx.ask (Queries.MayPointTo ptr) with + | ad when (not (Queries.AD.is_top ad)) && Queries.AD.cardinal ad = 1 -> + (* Note: Need to always set "ana.malloc.unique_address_count" to a value > 0 *) + begin match Queries.AD.choose ad with + | Queries.AD.Addr.Addr (v,_) when ctx.ask (Queries.IsAllocVar v) && ctx.ask (Queries.IsHeapVar v) && not @@ ctx.ask (Queries.IsMultiple v) -> + ToppedVarInfoSet.remove v ctx.local + | _ -> ctx.local + end + | _ -> ctx.local + end + | Abort -> + check_for_mem_leak ctx; + (* Upon a call to the "Abort" special function in the multi-threaded case, we give up and conservatively warn *) + warn_for_multi_threaded_due_to_abort ctx; + state + | Assert { exp; _ } -> + begin match ctx.ask (Queries.EvalInt exp) with + | a when Queries.ID.is_bot a -> M.warn ~category:Assert "assert expression %a is bottom" d_exp exp + | a -> + begin match Queries.ID.to_bool a with + | Some true -> () + | Some false -> + (* If we know for sure that the expression in "assert" is false => need to check for memory leaks *) + warn_for_multi_threaded_due_to_abort ctx; + check_for_mem_leak ctx + | None -> + warn_for_multi_threaded_due_to_abort ctx; + check_for_mem_leak ctx ~assert_exp_imprecise:true ~exp:(Some exp) + end + end; + state + | ThreadExit _ -> + begin match ctx.ask (Queries.CurrentThreadId) with + | `Lifted tid -> + warn_for_thread_return_or_exit ctx false + | _ -> () + end; + state + | _ -> state + + let startstate v = D.bot () + let exitstate v = D.top () + + let threadenter ctx ~multiple lval f args = [D.bot ()] +end + +let _ = + MCP.register_analysis (module Spec : MCPSpec) diff --git a/src/analyses/memOutOfBounds.ml b/src/analyses/memOutOfBounds.ml new file mode 100644 index 0000000000..9dccf77ff9 --- /dev/null +++ b/src/analyses/memOutOfBounds.ml @@ -0,0 +1,510 @@ +(** An analysis for the detection of out-of-bounds memory accesses ([memOutOfBounds]).*) + +open GoblintCil +open Analyses +open MessageCategory +open AnalysisStateUtil + +module AS = AnalysisState +module VDQ = ValueDomainQueries +module ID = IntDomain.IntDomTuple + +(* + Note: + * This functionality is implemented as an analysis solely for the sake of maintaining + separation of concerns, as well as for having the ablility to conveniently turn it on or off + * It doesn't track any internal state +*) +module Spec = +struct + include Analyses.IdentitySpec + + module D = Lattice.Unit + module C = D + + let context _ _ = () + + let name () = "memOutOfBounds" + + (* HELPER FUNCTIONS *) + + let intdom_of_int x = + ID.of_int (Cilfacade.ptrdiff_ikind ()) (Z.of_int x) + + let size_of_type_in_bytes typ = + let typ_size_in_bytes = (bitsSizeOf typ) / 8 in + intdom_of_int typ_size_in_bytes + + let rec exp_contains_a_ptr (exp:exp) = + match exp with + | Const _ + | SizeOf _ + | SizeOfStr _ + | AlignOf _ + | AddrOfLabel _ -> false + | Real e + | Imag e + | SizeOfE e + | AlignOfE e + | UnOp (_, e, _) + | CastE (_, e) -> exp_contains_a_ptr e + | BinOp (_, e1, e2, _) -> + exp_contains_a_ptr e1 || exp_contains_a_ptr e2 + | Question (e1, e2, e3, _) -> + exp_contains_a_ptr e1 || exp_contains_a_ptr e2 || exp_contains_a_ptr e3 + | Lval lval + | AddrOf lval + | StartOf lval -> lval_contains_a_ptr lval + + and lval_contains_a_ptr (lval:lval) = + let (host, offset) = lval in + let host_contains_a_ptr = function + | Var v -> isPointerType v.vtype + | Mem e -> exp_contains_a_ptr e + in + let rec offset_contains_a_ptr = function + | NoOffset -> false + | Index (e, o) -> exp_contains_a_ptr e || offset_contains_a_ptr o + | Field (f, o) -> isPointerType f.ftype || offset_contains_a_ptr o + in + host_contains_a_ptr host || offset_contains_a_ptr offset + + let points_to_alloc_only ctx ptr = + match ctx.ask (Queries.MayPointTo ptr) with + | a when not (Queries.AD.is_top a)-> + Queries.AD.for_all (function + | Addr (v, o) -> ctx.ask (Queries.IsAllocVar v) + | _ -> false + ) a + | _ -> false + + let get_size_of_ptr_target ctx ptr = + if points_to_alloc_only ctx ptr then + (* Ask for BlobSize from the base address (the second component being set to true) in order to avoid BlobSize giving us bot *) + ctx.ask (Queries.BlobSize {exp = ptr; base_address = true}) + else + match ctx.ask (Queries.MayPointTo ptr) with + | a when not (Queries.AD.is_top a) -> + let pts_list = Queries.AD.elements a in + let pts_elems_to_sizes (addr: Queries.AD.elt) = + begin match addr with + | Addr (v, _) -> + if hasAttribute "goblint_cil_nested" v.vattr then ( + set_mem_safety_flag InvalidDeref; + M.warn "Var %a is potentially accessed out-of-scope. Invalid memory access may occur" CilType.Varinfo.pretty v + ); + begin match v.vtype with + | TArray (item_typ, _, _) -> + let item_typ_size_in_bytes = size_of_type_in_bytes item_typ in + begin match ctx.ask (Queries.EvalLength ptr) with + | `Lifted arr_len -> + let arr_len_casted = ID.cast_to (Cilfacade.ptrdiff_ikind ()) arr_len in + begin + try `Lifted (ID.mul item_typ_size_in_bytes arr_len_casted) + with IntDomain.ArithmeticOnIntegerBot _ -> `Bot + end + | `Bot -> `Bot + | `Top -> `Top + end + | _ -> + let type_size_in_bytes = size_of_type_in_bytes v.vtype in + `Lifted type_size_in_bytes + end + | _ -> `Top + end + in + (* Map each points-to-set element to its size *) + let pts_sizes = List.map pts_elems_to_sizes pts_list in + (* Take the smallest of all sizes that ptr's contents may have *) + begin match pts_sizes with + | [] -> `Bot + | [x] -> x + | x::xs -> List.fold_left VDQ.ID.join x xs + end + | _ -> + (set_mem_safety_flag InvalidDeref; + M.warn "Pointer %a has a points-to-set of top. An invalid memory access might occur" d_exp ptr; + `Top) + + let get_ptr_deref_type ptr_typ = + match ptr_typ with + | TPtr (t, _) -> Some t + | _ -> None + + let eval_ptr_offset_in_binop ctx exp ptr_contents_typ = + let eval_offset = ctx.ask (Queries.EvalInt exp) in + let ptr_contents_typ_size_in_bytes = size_of_type_in_bytes ptr_contents_typ in + match eval_offset with + | `Lifted eo -> + let casted_eo = ID.cast_to (Cilfacade.ptrdiff_ikind ()) eo in + begin + try `Lifted (ID.mul casted_eo ptr_contents_typ_size_in_bytes) + with IntDomain.ArithmeticOnIntegerBot _ -> `Bot + end + | `Top -> `Top + | `Bot -> `Bot + + let rec offs_to_idx typ offs = + match offs with + | `NoOffset -> intdom_of_int 0 + | `Field (field, o) -> + let field_as_offset = Field (field, NoOffset) in + let bits_offset, _size = GoblintCil.bitsOffset (TComp (field.fcomp, [])) field_as_offset in + let bytes_offset = intdom_of_int (bits_offset / 8) in + let remaining_offset = offs_to_idx field.ftype o in + begin + try ID.add bytes_offset remaining_offset + with IntDomain.ArithmeticOnIntegerBot _ -> ID.bot_of @@ Cilfacade.ptrdiff_ikind () + end + | `Index (x, o) -> + begin try + let typ_size_in_bytes = size_of_type_in_bytes typ in + let bytes_offset = ID.mul typ_size_in_bytes x in + let remaining_offset = offs_to_idx typ o in + ID.add bytes_offset remaining_offset + with IntDomain.ArithmeticOnIntegerBot _ -> ID.bot_of @@ Cilfacade.ptrdiff_ikind () + end + + let cil_offs_to_idx ctx typ offs = + (* TODO: Some duplication with convert_offset in base.ml, unclear how to immediately get more reuse *) + let rec convert_offset (ofs: offset) = + match ofs with + | NoOffset -> `NoOffset + | Field (fld, ofs) -> `Field (fld, convert_offset ofs) + | Index (exp, ofs) when CilType.Exp.equal exp Offset.Index.Exp.any -> (* special offset added by convertToQueryLval *) + `Index (ID.top (), convert_offset ofs) + | Index (exp, ofs) -> + let i = match ctx.ask (Queries.EvalInt exp) with + | `Lifted x -> x + | _ -> ID.top_of @@ Cilfacade.ptrdiff_ikind () + in + `Index (i, convert_offset ofs) + in + PreValueDomain.Offs.to_index (convert_offset offs) + + + let check_unknown_addr_deref ctx ptr = + let may_contain_unknown_addr = + match ctx.ask (Queries.EvalValue ptr) with + | a when not (Queries.VD.is_top a) -> + begin match a with + | Address a -> ValueDomain.AD.may_be_unknown a + | _ -> false + end + (* Intuition: if ptr evaluates to top, it could potentially evaluate to the unknown address *) + | _ -> true + in + if may_contain_unknown_addr then begin + set_mem_safety_flag InvalidDeref; + M.warn ~category:(Behavior (Undefined Other)) "Pointer %a contains an unknown address. Invalid dereference may occur" d_exp ptr + end + + let ptr_only_has_str_addr ctx ptr = + match ctx.ask (Queries.EvalValue ptr) with + | a when not (Queries.VD.is_top a) -> + begin match a with + | Address a -> ValueDomain.AD.for_all (fun addr -> match addr with | StrPtr _ -> true | _ -> false) a + | _ -> false + end + (* Intuition: if ptr evaluates to top, it could all sorts of things and not only string addresses *) + | _ -> false + + let rec get_addr_offs ctx ptr = + match ctx.ask (Queries.MayPointTo ptr) with + | a when not (VDQ.AD.is_top a) -> + let ptr_deref_type = get_ptr_deref_type @@ typeOf ptr in + begin match ptr_deref_type with + | Some t -> + begin match VDQ.AD.is_empty a with + | true -> + M.warn "Pointer %a has an empty points-to-set" d_exp ptr; + ID.top_of @@ Cilfacade.ptrdiff_ikind () + | false -> + if VDQ.AD.exists (function + | Addr (_, o) -> ID.is_bot @@ offs_to_idx t o + | _ -> false + ) a then ( + set_mem_safety_flag InvalidDeref; + M.warn "Pointer %a has a bot address offset. An invalid memory access may occur" d_exp ptr + ) else if VDQ.AD.exists (function + | Addr (_, o) -> ID.is_top_of (Cilfacade.ptrdiff_ikind ()) (offs_to_idx t o) + | _ -> false + ) a then ( + set_mem_safety_flag InvalidDeref; + M.warn "Pointer %a has a top address offset. An invalid memory access may occur" d_exp ptr + ); + (* Get the address offsets of all points-to set elements *) + let addr_offsets = + VDQ.AD.filter (function Addr (v, o) -> true | _ -> false) a + |> VDQ.AD.to_mval + |> List.map (fun (_, o) -> offs_to_idx t o) + in + begin match addr_offsets with + | [] -> ID.bot_of @@ Cilfacade.ptrdiff_ikind () + | [x] -> x + | x::xs -> List.fold_left ID.join x xs + end + end + | None -> + M.error "Expression %a doesn't have pointer type" d_exp ptr; + ID.top_of @@ Cilfacade.ptrdiff_ikind () + end + | _ -> + set_mem_safety_flag InvalidDeref; + M.warn "Pointer %a has a points-to-set of top. An invalid memory access might occur" d_exp ptr; + ID.top_of @@ Cilfacade.ptrdiff_ikind () + + and check_lval_for_oob_access ctx ?(is_implicitly_derefed = false) lval = + (* If the lval does not contain a pointer or if it does contain a pointer, but only points to string addresses, then no need to WARN *) + if (not @@ lval_contains_a_ptr lval) || ptr_only_has_str_addr ctx (Lval lval) then () + else + (* If the lval doesn't indicate an explicit dereference, we still need to check for an implicit dereference *) + (* An implicit dereference is, e.g., printf("%p", ptr), where ptr is a pointer *) + match lval, is_implicitly_derefed with + | (Var _, _), false -> () + | (Var v, _), true -> check_no_binop_deref ctx (Lval lval) + | (Mem e, o), _ -> + let ptr_deref_type = get_ptr_deref_type @@ typeOf e in + let offs_intdom = begin match ptr_deref_type with + | Some t -> cil_offs_to_idx ctx t o + | None -> ID.bot_of @@ Cilfacade.ptrdiff_ikind () + end in + let e_size = get_size_of_ptr_target ctx e in + let () = begin match e_size with + | `Top -> + (set_mem_safety_flag InvalidDeref; + M.warn "Size of lval dereference expression %a is top. Out-of-bounds memory access may occur" d_exp e) + | `Bot -> + (set_mem_safety_flag InvalidDeref; + M.warn "Size of lval dereference expression %a is bot. Out-of-bounds memory access may occur" d_exp e) + | `Lifted es -> + let casted_es = ID.cast_to (Cilfacade.ptrdiff_ikind ()) es in + let one = intdom_of_int 1 in + let casted_es = ID.sub casted_es one in + let casted_offs = ID.cast_to (Cilfacade.ptrdiff_ikind ()) offs_intdom in + let ptr_size_lt_offs = + begin try ID.lt casted_es casted_offs + with IntDomain.ArithmeticOnIntegerBot _ -> ID.bot_of @@ Cilfacade.ptrdiff_ikind () + end + in + let behavior = Undefined MemoryOutOfBoundsAccess in + let cwe_number = 823 in + begin match ID.to_bool ptr_size_lt_offs with + | Some true -> + (set_mem_safety_flag InvalidDeref; + M.warn ~category:(Behavior behavior) ~tags:[CWE cwe_number] "Size of lval dereference expression is %a (in bytes). It is offset by %a (in bytes). Memory out-of-bounds access must occur" ID.pretty casted_es ID.pretty casted_offs) + | Some false -> () + | None -> + (set_mem_safety_flag InvalidDeref; + M.warn ~category:(Behavior behavior) ~tags:[CWE cwe_number] "Could not compare size of lval dereference expression (%a) (in bytes) with offset by (%a) (in bytes). Memory out-of-bounds access might occur" ID.pretty casted_es ID.pretty casted_offs) + end + end in + begin match e with + | Lval (Var v, _) as lval_exp -> check_no_binop_deref ctx lval_exp + | BinOp (binop, e1, e2, t) when binop = PlusPI || binop = MinusPI || binop = IndexPI -> + check_binop_exp ctx binop e1 e2 t; + check_exp_for_oob_access ctx ~is_implicitly_derefed e1; + check_exp_for_oob_access ctx ~is_implicitly_derefed e2 + | _ -> check_exp_for_oob_access ctx ~is_implicitly_derefed e + end + + and check_no_binop_deref ctx lval_exp = + check_unknown_addr_deref ctx lval_exp; + let behavior = Undefined MemoryOutOfBoundsAccess in + let cwe_number = 823 in + let ptr_size = get_size_of_ptr_target ctx lval_exp in + let addr_offs = get_addr_offs ctx lval_exp in + let ptr_type = typeOf lval_exp in + let ptr_contents_type = get_ptr_deref_type ptr_type in + match ptr_contents_type with + | Some t -> + begin match ptr_size, addr_offs with + | `Top, _ -> + set_mem_safety_flag InvalidDeref; + M.warn ~category:(Behavior behavior) ~tags:[CWE cwe_number] "Size of pointer %a is top. Memory out-of-bounds access might occur due to pointer arithmetic" d_exp lval_exp + | `Bot, _ -> + set_mem_safety_flag InvalidDeref; + M.warn ~category:(Behavior behavior) ~tags:[CWE cwe_number] "Size of pointer %a is bot. Memory out-of-bounds access might occur due to pointer arithmetic" d_exp lval_exp + | `Lifted ps, ao -> + let casted_ps = ID.cast_to (Cilfacade.ptrdiff_ikind ()) ps in + let casted_ao = ID.cast_to (Cilfacade.ptrdiff_ikind ()) ao in + let ptr_size_lt_offs = ID.lt casted_ps casted_ao in + begin match ID.to_bool ptr_size_lt_offs with + | Some true -> + set_mem_safety_flag InvalidDeref; + M.warn ~category:(Behavior behavior) ~tags:[CWE cwe_number] "Size of pointer is %a (in bytes). It is offset by %a (in bytes) due to pointer arithmetic. Memory out-of-bounds access must occur" ID.pretty casted_ps ID.pretty casted_ao + | Some false -> () + | None -> + set_mem_safety_flag InvalidDeref; + M.warn ~category:(Behavior behavior) ~tags:[CWE cwe_number] "Could not compare size of pointer (%a) (in bytes) with offset by (%a) (in bytes). Memory out-of-bounds access might occur" ID.pretty casted_ps ID.pretty casted_ao + end + end + | _ -> M.error "Expression %a is not a pointer" d_exp lval_exp + + and check_exp_for_oob_access ctx ?(is_implicitly_derefed = false) exp = + match exp with + | Const _ + | SizeOf _ + | SizeOfStr _ + | AlignOf _ + | AddrOfLabel _ -> () + | Real e + | Imag e + | SizeOfE e + | AlignOfE e + | UnOp (_, e, _) + | CastE (_, e) -> check_exp_for_oob_access ctx ~is_implicitly_derefed e + | BinOp (bop, e1, e2, t) -> + check_exp_for_oob_access ctx ~is_implicitly_derefed e1; + check_exp_for_oob_access ctx ~is_implicitly_derefed e2 + | Question (e1, e2, e3, _) -> + check_exp_for_oob_access ctx ~is_implicitly_derefed e1; + check_exp_for_oob_access ctx ~is_implicitly_derefed e2; + check_exp_for_oob_access ctx ~is_implicitly_derefed e3 + | Lval lval + | StartOf lval + | AddrOf lval -> check_lval_for_oob_access ctx ~is_implicitly_derefed lval + + and check_binop_exp ctx binop e1 e2 t = + check_unknown_addr_deref ctx e1; + let binopexp = BinOp (binop, e1, e2, t) in + let behavior = Undefined MemoryOutOfBoundsAccess in + let cwe_number = 823 in + match binop with + | PlusPI + | IndexPI + | MinusPI -> + let ptr_size = get_size_of_ptr_target ctx e1 in + let addr_offs = get_addr_offs ctx e1 in + let ptr_type = typeOf e1 in + let ptr_contents_type = get_ptr_deref_type ptr_type in + begin match ptr_contents_type with + | Some t -> + let offset_size = eval_ptr_offset_in_binop ctx e2 t in + (* Make sure to add the address offset to the binop offset *) + let offset_size_with_addr_size = match offset_size with + | `Lifted os -> + let casted_os = ID.cast_to (Cilfacade.ptrdiff_ikind ()) os in + let casted_ao = ID.cast_to (Cilfacade.ptrdiff_ikind ()) addr_offs in + begin + try `Lifted (ID.add casted_os casted_ao) + with IntDomain.ArithmeticOnIntegerBot _ -> `Bot + end + | `Top -> `Top + | `Bot -> `Bot + in + begin match ptr_size, offset_size_with_addr_size with + | `Top, _ -> + set_mem_safety_flag InvalidDeref; + M.warn ~category:(Behavior behavior) ~tags:[CWE cwe_number] "Size of pointer %a in expression %a is top. Memory out-of-bounds access might occur" d_exp e1 d_exp binopexp + | _, `Top -> + set_mem_safety_flag InvalidDeref; + M.warn ~category:(Behavior behavior) ~tags:[CWE cwe_number] "Operand value for pointer arithmetic in expression %a is top. Memory out-of-bounds access might occur" d_exp binopexp + | `Bot, _ -> + set_mem_safety_flag InvalidDeref; + M.warn ~category:(Behavior behavior) ~tags:[CWE cwe_number] "Size of pointer %a in expression %a is bottom. Memory out-of-bounds access might occur" d_exp e1 d_exp binopexp + | _, `Bot -> + set_mem_safety_flag InvalidDeref; + M.warn ~category:(Behavior behavior) ~tags:[CWE cwe_number] "Operand value for pointer arithmetic in expression %a is bottom. Memory out-of-bounds access might occur" d_exp binopexp + | `Lifted ps, `Lifted o -> + let casted_ps = ID.cast_to (Cilfacade.ptrdiff_ikind ()) ps in + let casted_o = ID.cast_to (Cilfacade.ptrdiff_ikind ()) o in + let ptr_size_lt_offs = ID.lt casted_ps casted_o in + begin match ID.to_bool ptr_size_lt_offs with + | Some true -> + set_mem_safety_flag InvalidDeref; + M.warn ~category:(Behavior behavior) ~tags:[CWE cwe_number] "Size of pointer in expression %a is %a (in bytes). It is offset by %a (in bytes). Memory out-of-bounds access must occur" d_exp binopexp ID.pretty casted_ps ID.pretty casted_o + | Some false -> () + | None -> + set_mem_safety_flag InvalidDeref; + M.warn ~category:(Behavior behavior) ~tags:[CWE cwe_number] "Could not compare pointer size (%a) with offset (%a). Memory out-of-bounds access may occur" ID.pretty casted_ps ID.pretty casted_o + end + end + | _ -> M.error "Binary expression %a doesn't have a pointer" d_exp binopexp + end + | _ -> () + + (* For memset() and memcpy() *) + let check_count ctx fun_name ptr n = + let (behavior:MessageCategory.behavior) = Undefined MemoryOutOfBoundsAccess in + let cwe_number = 823 in + let ptr_size = get_size_of_ptr_target ctx ptr in + let eval_n = ctx.ask (Queries.EvalInt n) in + let addr_offs = get_addr_offs ctx ptr in + match ptr_size, eval_n with + | `Top, _ -> + set_mem_safety_flag InvalidDeref; + M.warn ~category:(Behavior behavior) ~tags:[CWE cwe_number] "Size of dest %a in function %s is unknown. Memory out-of-bounds access might occur" d_exp ptr fun_name + | _, `Top -> + set_mem_safety_flag InvalidDeref; + M.warn ~category:(Behavior behavior) ~tags:[CWE cwe_number] "Count parameter, passed to function %s is unknown. Memory out-of-bounds access might occur" fun_name + | `Bot, _ -> + set_mem_safety_flag InvalidDeref; + M.warn ~category:(Behavior behavior) ~tags:[CWE cwe_number] "Size of dest %a in function %s is bottom. Memory out-of-bounds access might occur" d_exp ptr fun_name + | _, `Bot -> + set_mem_safety_flag InvalidDeref; + M.warn ~category:(Behavior behavior) ~tags:[CWE cwe_number] "Count parameter, passed to function %s is bottom" fun_name + | `Lifted ds, `Lifted en -> + let casted_ds = ID.cast_to (Cilfacade.ptrdiff_ikind ()) ds in + let casted_en = ID.cast_to (Cilfacade.ptrdiff_ikind ()) en in + let casted_ao = ID.cast_to (Cilfacade.ptrdiff_ikind ()) addr_offs in + let dest_size_lt_count = ID.lt casted_ds (ID.add casted_en casted_ao) in + begin match ID.to_bool dest_size_lt_count with + | Some true -> + set_mem_safety_flag InvalidDeref; + M.warn ~category:(Behavior behavior) ~tags:[CWE cwe_number] "Size of %a in function %s is %a (in bytes) with an address offset of %a (in bytes). Count is %a (in bytes). Memory out-of-bounds access must occur" d_exp ptr fun_name ID.pretty casted_ds ID.pretty casted_ao ID.pretty casted_en + | Some false -> () + | None -> + set_mem_safety_flag InvalidDeref; + M.warn ~category:(Behavior behavior) ~tags:[CWE cwe_number] "Could not compare size of dest (%a) with address offset (%a) count (%a) in function %s. Memory out-of-bounds access may occur" ID.pretty casted_ds ID.pretty casted_ao ID.pretty casted_en fun_name + end + + + (* TRANSFER FUNCTIONS *) + + let assign ctx (lval:lval) (rval:exp) : D.t = + check_lval_for_oob_access ctx lval; + check_exp_for_oob_access ctx rval; + ctx.local + + let branch ctx (exp:exp) (tv:bool) : D.t = + check_exp_for_oob_access ctx exp; + ctx.local + + let return ctx (exp:exp option) (f:fundec) : D.t = + Option.iter (fun x -> check_exp_for_oob_access ctx x) exp; + ctx.local + + let special ctx (lval:lval option) (f:varinfo) (arglist:exp list) : D.t = + let desc = LibraryFunctions.find f in + let is_arg_implicitly_derefed arg = + let read_shallow_args = LibraryDesc.Accesses.find desc.accs { kind = Read; deep = false } arglist in + let read_deep_args = LibraryDesc.Accesses.find desc.accs { kind = Read; deep = true } arglist in + let write_shallow_args = LibraryDesc.Accesses.find desc.accs { kind = Write; deep = false } arglist in + let write_deep_args = LibraryDesc.Accesses.find desc.accs { kind = Write; deep = true } arglist in + List.mem arg read_shallow_args || List.mem arg read_deep_args || List.mem arg write_shallow_args || List.mem arg write_deep_args + in + Option.iter (fun x -> check_lval_for_oob_access ctx x) lval; + List.iter (fun arg -> check_exp_for_oob_access ctx ~is_implicitly_derefed:(is_arg_implicitly_derefed arg) arg) arglist; + (* Check calls to memset and memcpy for out-of-bounds-accesses *) + match desc.special arglist with + | Memset { dest; ch; count; } -> check_count ctx f.vname dest count; + | Memcpy { dest; src; n = count; } -> + (check_count ctx f.vname src count; + check_count ctx f.vname dest count;) + | _ -> ctx.local + + let enter ctx (lval: lval option) (f:fundec) (args:exp list) : (D.t * D.t) list = + List.iter (fun arg -> check_exp_for_oob_access ctx arg) args; + [ctx.local, ctx.local] + + let combine_assign ctx (lval:lval option) fexp (f:fundec) (args:exp list) fc (callee_local:D.t) (f_ask:Queries.ask) : D.t = + Option.iter (fun x -> check_lval_for_oob_access ctx x) lval; + ctx.local + + let startstate v = () + let exitstate v = () +end + +let _ = + MCP.register_analysis (module Spec : MCPSpec) diff --git a/src/analyses/modifiedSinceLongjmp.ml b/src/analyses/modifiedSinceSetjmp.ml similarity index 75% rename from src/analyses/modifiedSinceLongjmp.ml rename to src/analyses/modifiedSinceSetjmp.ml index f489b08fe9..93e55b2a17 100644 --- a/src/analyses/modifiedSinceLongjmp.ml +++ b/src/analyses/modifiedSinceSetjmp.ml @@ -1,6 +1,4 @@ -(** Analysis of variables modified since [setjmp] ([modifiedSinceLongjmp]). *) - -(* TODO: this name is wrong *) +(** Analysis of variables modified since [setjmp] ([modifiedSinceSetjmp]). *) open GoblintCil open Analyses @@ -9,7 +7,7 @@ module Spec = struct include Analyses.IdentitySpec - let name () = "modifiedSinceLongjmp" + let name () = "modifiedSinceSetjmp" module D = JmpBufDomain.LocallyModifiedMap module VS = D.VarSet module C = Lattice.Unit @@ -23,18 +21,23 @@ struct (* Only checks for v.vglob on purpose, acessing espaced locals after longjmp is UB like for any local *) not v.vglob (* *) && not (BaseUtil.is_volatile v) && v.vstorage <> Static - let relevants_from_ls ls = - if Queries.LS.is_top ls then + let relevants_from_ad ls = + (* TODO: what about AD with both known and unknown pointers? *) + if Queries.AD.is_top ls then VS.top () else - Queries.LS.fold (fun (v, _) acc -> if is_relevant v then VS.add v acc else acc) ls (VS.empty ()) + Queries.AD.fold (fun addr acc -> + match addr with + | Queries.AD.Addr.Addr (v, _) when is_relevant v -> VS.add v acc + | _ -> acc + ) ls (VS.empty ()) (* transfer functions *) let enter ctx (lval: lval option) (f:fundec) (args:exp list) : (D.t * D.t) list = [ctx.local, D.bot ()] (* enter with bot as opposed to IdentitySpec *) let combine_env ctx lval fexp f args fc au (f_ask: Queries.ask) = - let taintedcallee = relevants_from_ls (f_ask.f Queries.MayBeTainted) in + let taintedcallee = relevants_from_ad (f_ask.f Queries.MayBeTainted) in add_to_all_defined taintedcallee ctx.local let combine_assign ctx (lval:lval option) fexp (f:fundec) (args:exp list) fc (au:D.t) (f_ask:Queries.ask) : D.t = @@ -52,7 +55,7 @@ struct ctx.local let startstate v = D.bot () - let threadenter ctx lval f args = [D.bot ()] + let threadenter ctx ~multiple lval f args = [D.bot ()] let exitstate v = D.top () let query ctx (type a) (q: a Queries.t): a Queries.result = @@ -62,8 +65,8 @@ struct let event ctx (e: Events.t) octx = match e with - | Access {lvals; kind = Write; _} -> - add_to_all_defined (relevants_from_ls lvals) ctx.local + | Access {ad; kind = Write; _} -> + add_to_all_defined (relevants_from_ad ad) ctx.local | _ -> ctx.local end diff --git a/src/analyses/mutexAnalysis.ml b/src/analyses/mutexAnalysis.ml index d9cdef9286..ee050f55ca 100644 --- a/src/analyses/mutexAnalysis.ml +++ b/src/analyses/mutexAnalysis.ml @@ -30,6 +30,8 @@ struct 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 @@ -155,7 +157,7 @@ struct 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 which may not be held"; + 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 @@ -233,21 +235,15 @@ struct Mutexes.leq mutex_lockset protecting | Queries.MustLockset -> let held_locks = Lockset.export_locks (Lockset.filter snd ls) in - let ls = Mutexes.fold (fun addr ls -> - match Addr.to_mval addr with - | Some (var, offs) -> Queries.LS.add (var, Addr.Offs.to_exp offs) ls - | None -> ls - ) held_locks (Queries.LS.empty ()) - in - ls + Mutexes.fold (fun addr ls -> Queries.AD.add addr ls) held_locks (Queries.AD.empty ()) | 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 VarSet.fold (fun v acc -> - Queries.LS.add (v, `NoOffset) acc - ) protected (Queries.LS.empty ()) + Queries.VS.add v acc + ) protected (Queries.VS.empty ()) | Queries.IterSysVars (Global g, f) -> f (Obj.repr (V.protecting g)) (* TODO: something about V.protected? *) | WarnGlobal g -> @@ -293,10 +289,10 @@ struct let event ctx e octx = match e with - | Events.Access {exp; lvals; kind; _} when ThreadFlag.has_ever_been_multi (Analyses.ask_of_ctx ctx) -> (* threadflag query in post-threadspawn ctx *) + | 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 (* must use original (pre-assign, etc) ctx queries *) - let old_access var_opt offs_opt = + let old_access var_opt = (* TODO: this used to use ctx instead of octx, why? *) (*privatization*) match var_opt with @@ -306,6 +302,7 @@ struct let write = match kind with | Write | Free -> true | Read -> false + | Call | Spawn -> false (* TODO: nonsense? *) in let s = GProtecting.make ~write ~recovered:is_recovered_to_st locks in @@ -324,24 +321,21 @@ struct ) | None -> M.info ~category:Unsound "Write to unknown address: privatization is unsound." in - let module LS = Queries.LS in + let module AD = Queries.AD in let has_escaped g = octx.ask (Queries.MayEscape g) in - let on_lvals ls = - let ls = LS.filter (fun (g,_) -> g.vglob || has_escaped g) ls in - let f (var, offs) = - let coffs = Offset.Exp.to_cil offs in - if CilType.Varinfo.equal var dummyFunDec.svar then - old_access None (Some coffs) - else - old_access (Some var) (Some coffs) + let on_ad ad = + let f = function + | AD.Addr.Addr (g,_) when g.vglob || has_escaped g -> old_access (Some g) + | UnknownPtr -> old_access None + | _ -> () in - LS.iter f ls + AD.iter f ad in - begin match lvals with - | ls when not (LS.is_top ls) && not (Queries.LS.mem (dummyFunDec.svar,`NoOffset) ls) -> + begin match ad with + | ad when not (AD.is_top ad) -> (* the case where the points-to set is non top and does not contain unknown values *) - on_lvals ls - | ls when not (LS.is_top ls) -> + on_ad ad + | ad -> (* the case where the points-to set is non top and contains unknown values *) (* now we need to access all fields that might be pointed to: is this correct? *) begin match octx.ask (ReachableUkTypes exp) with @@ -353,11 +347,11 @@ struct | _ -> false in if Queries.TS.exists f ts then - old_access None None + old_access None end; - on_lvals ls - | _ -> - old_access None None + on_ad ad + (* | _ -> + old_access None None *) (* TODO: what about this case? *) end; ctx.local | _ -> diff --git a/src/analyses/mutexEventsAnalysis.ml b/src/analyses/mutexEventsAnalysis.ml index 2c57fa360b..162527b32b 100644 --- a/src/analyses/mutexEventsAnalysis.ml +++ b/src/analyses/mutexEventsAnalysis.ml @@ -18,24 +18,12 @@ struct include UnitAnalysis.Spec let name () = "mutexEvents" - (* TODO: Use AddressDomain for queries *) - let eval_exp_addr (a: Queries.ask) exp = - let gather_addr (v,o) b = ValueDomain.Addr.of_mval (v, Addr.Offs.of_exp o) :: b in - match a.f (Queries.MayPointTo exp) with - | a when Queries.LS.is_top a -> - [Addr.UnknownPtr] - | a -> - let top_elt = (dummyFunDec.svar, `NoOffset) in - let addrs = Queries.LS.fold gather_addr (Queries.LS.remove top_elt a) [] in - if Queries.LS.mem top_elt a then - Addr.UnknownPtr :: addrs - else - addrs + 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 arg = - match lv with + let lock ctx rw may_fail nonzero_return_when_aquired a lv_opt arg = + match lv_opt with | None -> - List.iter (fun e -> + Queries.AD.iter (fun e -> ctx.split () [Events.Lock (e, rw)] ) (eval_exp_addr a arg); if may_fail then @@ -43,7 +31,7 @@ struct raise Analyses.Deadcode | Some lv -> let sb = Events.SplitBranch (Lval lv, nonzero_return_when_aquired) in - List.iter (fun e -> + Queries.AD.iter (fun e -> ctx.split () [sb; Events.Lock (e, rw)]; ) (eval_exp_addr a arg); if may_fail then ( @@ -67,7 +55,7 @@ struct let special (ctx: (unit, _, _, _) ctx) lv f arglist : D.t = let remove_rw x = x in let unlock arg remove_fn = - List.iter (fun e -> + Queries.AD.iter (fun e -> ctx.split () [Events.Unlock (remove_fn e)] ) (eval_exp_addr (Analyses.ask_of_ctx ctx) arg); raise Analyses.Deadcode @@ -83,7 +71,7 @@ struct (* mutex is unlocked while waiting but relocked when returns *) (* emit unlock-lock events for privatization *) let ms = eval_exp_addr (Analyses.ask_of_ctx ctx) m_arg in - List.iter (fun m -> + Queries.AD.iter (fun m -> (* unlock-lock each possible mutex as a split to be dependent *) (* otherwise may-point-to {a, b} might unlock a, but relock b *) ctx.split () [Events.Unlock m; Events.Lock (m, true)]; diff --git a/src/analyses/mutexTypeAnalysis.ml b/src/analyses/mutexTypeAnalysis.ml index 00e49260b4..e640a261cd 100644 --- a/src/analyses/mutexTypeAnalysis.ml +++ b/src/analyses/mutexTypeAnalysis.ml @@ -18,7 +18,7 @@ struct module O = Offset.Unit module V = struct - include Printable.Prod(CilType.Varinfo)(O) + include Printable.Prod(CilType.Varinfo)(O) (* TODO: use Mval.Unit *) let is_write_only _ = false end @@ -56,13 +56,17 @@ struct let attr = ctx.ask (Queries.EvalMutexAttr attr) in let mutexes = ctx.ask (Queries.MayPointTo mutex) in (* It is correct to iter over these sets here, as mutexes need to be intialized before being used, and an analysis that detects usage before initialization is a different analysis. *) - Queries.LS.iter (function (v, o) -> ctx.sideg (v,O.of_offs o) attr) mutexes; + Queries.AD.iter (function addr -> + match addr with + | Queries.AD.Addr.Addr (v,o) -> ctx.sideg (v,O.of_offs o) attr + | _ -> () + ) mutexes; ctx.local | _ -> ctx.local let startstate v = D.bot () - let threadenter ctx lval f args = [D.top ()] - let threadspawn ctx lval f args fctx = ctx.local + let threadenter ctx ~multiple lval f args = [D.top ()] + let threadspawn ctx ~multiple lval f args fctx = ctx.local let exitstate v = D.top () let query ctx (type a) (q: a Queries.t): a Queries.result = diff --git a/src/analyses/poisonVariables.ml b/src/analyses/poisonVariables.ml index 5cb34baa26..865cb928aa 100644 --- a/src/analyses/poisonVariables.ml +++ b/src/analyses/poisonVariables.ml @@ -15,12 +15,16 @@ struct let context _ _ = () - let check_lval tainted ((v, offset): Queries.LS.elt) = - if not v.vglob && VS.mem v tainted then - M.warn ~category:(Behavior (Undefined Other)) "Reading poisonous variable %a" CilType.Varinfo.pretty v + let check_mval tainted (addr: Queries.AD.elt) = + match addr with + | Queries.AD.Addr.Addr (v,_) -> + if not v.vglob && VS.mem v tainted then + M.warn ~category:(Behavior (Undefined Other)) "Reading poisonous variable %a" CilType.Varinfo.pretty v + | _ -> () - let rem_lval tainted ((v, offset): Queries.LS.elt) = match offset with - | `NoOffset -> VS.remove v tainted + let rem_mval tainted (addr: Queries.AD.elt) = + match addr with + | Queries.AD.Addr.Addr (v,`NoOffset) -> VS.remove v tainted | _ -> tainted (* If there is an offset, it is a bit harder to remove, as we don't know where the indeterminate value is *) @@ -38,18 +42,21 @@ struct ) ctx.local ) - let enter ctx (lval: lval option) (f:fundec) (args:exp list) : (D.t * D.t) list = + let enter ctx (_:lval option) (_:fundec) (args:exp list) : (D.t * D.t) list = if VS.is_empty ctx.local then [ctx.local,ctx.local] else ( - let reachable_from_args = List.fold (fun ls e -> Queries.LS.join ls (ctx.ask (ReachableFrom e))) (Queries.LS.empty ()) args in - if Queries.LS.is_top reachable_from_args || VS.is_top ctx.local then + let reachable_from_args = List.fold (fun ad e -> Queries.AD.join ad (ctx.ask (ReachableFrom e))) (Queries.AD.empty ()) args in + if Queries.AD.is_top reachable_from_args || VS.is_top ctx.local then [ctx.local, ctx.local] else let reachable_vars = - Queries.LS.elements reachable_from_args - |> List.map fst - |> VS.of_list + let get_vars addr vs = + match addr with + | Queries.AD.Addr.Addr (v,_) -> VS.add v vs + | _ -> vs + in + Queries.AD.fold get_vars reachable_from_args (VS.empty ()) in [VS.diff ctx.local reachable_vars, VS.inter reachable_vars ctx.local] ) @@ -58,7 +65,7 @@ struct VS.join au ctx.local let startstate v = D.bot () - let threadenter ctx lval f args = [D.bot ()] + let threadenter ctx ~multiple lval f args = [D.bot ()] let exitstate v = D.top () let event ctx e octx = @@ -79,26 +86,26 @@ struct () ) longjmp_nodes; D.join modified_locals ctx.local - | Access {lvals; kind = Read; _} -> - if Queries.LS.is_top lvals then ( - if not (VS.is_empty octx.local) then + | Access {ad; kind = Read; _} -> + (* TODO: what about AD with both known and unknown pointers? *) + begin match ad with + | ad when Queries.AD.is_top ad && not (VS.is_empty octx.local) -> M.warn ~category:(Behavior (Undefined Other)) "reading unknown memory location, may be tainted!" - ) - else ( - Queries.LS.iter (fun lv -> - (* Use original access state instead of current with removed written vars. *) - check_lval octx.local lv - ) lvals - ); + | ad -> + (* Use original access state instead of current with removed written vars. *) + Queries.AD.iter (check_mval octx.local) ad + end; ctx.local - | Access {lvals; kind = Write; _} -> - if Queries.LS.is_top lvals then - ctx.local - else ( - Queries.LS.fold (fun lv acc -> - rem_lval acc lv - ) lvals ctx.local - ) + | Access {ad; kind = Write; _} -> + (* TODO: what about AD with both known and unknown pointers? *) + begin match ad with + | ad when Queries.AD.is_top ad -> + ctx.local + | ad -> + Queries.AD.fold (fun addr vs -> + rem_mval vs addr + ) ad ctx.local + end | _ -> ctx.local end diff --git a/src/analyses/pthreadSignals.ml b/src/analyses/pthreadSignals.ml index 036d1bd2c6..70f1624922 100644 --- a/src/analyses/pthreadSignals.ml +++ b/src/analyses/pthreadSignals.ml @@ -17,16 +17,8 @@ struct module C = MustSignals module G = SetDomain.ToppedSet (MHP) (struct let topname = "All Threads" end) - (* TODO: Use AddressDomain for queries *) - let eval_exp_addr (a: Queries.ask) exp = - let gather_addr (v,o) b = ValueDomain.Addr.of_mval (v, ValueDomain.Addr.Offs.of_exp o) :: b in - match a.f (Queries.MayPointTo exp) with - | a when not (Queries.LS.is_top a) && not (Queries.LS.mem (dummyFunDec.svar,`NoOffset) a) -> - Queries.LS.fold gather_addr (Queries.LS.remove (dummyFunDec.svar, `NoOffset) a) [] - | _ -> [] - - let possible_vinfos a cv_arg = - List.filter_map ValueDomain.Addr.to_var_may (eval_exp_addr a cv_arg) + let possible_vinfos (a: Queries.ask) cv_arg = + Queries.AD.to_var_may (a.f (Queries.MayPointTo cv_arg)) (* transfer functions *) @@ -81,7 +73,7 @@ struct | _ -> ctx.local let startstate v = Signals.empty () - let threadenter ctx lval f args = [ctx.local] + let threadenter ctx ~multiple lval f args = [ctx.local] let exitstate v = Signals.empty () end diff --git a/src/analyses/raceAnalysis.ml b/src/analyses/raceAnalysis.ml index 3ed5a5acbe..9c2272fabb 100644 --- a/src/analyses/raceAnalysis.ml +++ b/src/analyses/raceAnalysis.ml @@ -3,6 +3,128 @@ open GoblintCil open Analyses +(** Data race analysis with tries for offsets and type-based memory locations for open code. + + Accesses are to memory locations ({{!Access.Memo} memos}) which consist of a root and offset. + {{!Access.MemoRoot} Root} can be: + + variable, if access is to known global variable or alloc-variable; + + type, if access is to unknown pointer. + + Accesses are (now) collected to sets for each corresponding memo, + after points-to sets are resolved, during postsolving. + + Race checking is performed per-memo, + except must additionally account for accesses to other memos (see diagram below): + + access to [s.f] can race with access to a prefix like [s], which writes an entire struct at once; + + access to [s.f] can race with type-based access like [(struct S).f]; + + access to [(struct S).f] can race with type-based access to a suffix like [(int)]. + + access to [(struct T).s.f] can race with type-based access like [(struct S)], which is a combination of the above. + + These are accounted for lazily (unlike in the past). + + Prefixes (a.k.a. inner distribution) are handled using a trie data structure enriched with lattice properties. + Race checking starts at the root and passes accesses to ancestor nodes down to children. + + Type suffixes (a.k.a. outer distribution) are handled by computing successive immediate type suffixes transitively + and accessing corresponding offsets from corresponding root tries in the global invariant. + + Type suffix prefixes (for the combination of the two) are handled by passing type suffix accesses down when traversing the prefix trie. + + Race checking happens at each trie node with the above three access sets at hand using {!Access.group_may_race}. + All necessary combinations between the four classes are handled, but unnecessary repeated work is carefully avoided. + E.g. accesses which are pairwise checked at some prefix are not re-checked pairwise at a node. + Thus, races (with prefixes or type suffixes) are reported for most precise memos with actual accesses: + at the longest prefix and longest type suffix. + + Additionally, accesses between prefix and type suffix intersecting at a node are checked. + These races are reported at the unique memo at the intersection of the prefix and the type suffix. + This requires an implementation hack to still eagerly do outer distribution, but only of empty access sets. + It ensures that corresponding trie nodes exist for traversal later. *) + +(** Given C declarations: + {@c[ + struct S { + int f; + }; + + struct T { + struct S s; + }; + + struct T t; + ]} + + Example structure of related memos for race checking: + {v + (int) (S) (T) + \ / \ / \ + f s t + \ / \ / + f s + \ / + f + v} + where: + - [(int)] is a type-based memo root for the primitive [int] type; + - [(S)] and [(T)] are short for [(struct S)] and [(struct T)], which are type-based memo roots; + - prefix relations are indicated by [/], so access paths run diagonally from top-right to bottom-left; + - type suffix relations are indicated by [\ ]. + + All same-node races: + - Race between [t.s.f] and [t.s.f] is checked/reported at [t.s.f]. + - Race between [t.s] and [t.s] is checked/reported at [t.s]. + - Race between [t] and [t] is checked/reported at [t]. + - Race between [(T).s.f] and [(T).s.f] is checked/reported at [(T).s.f]. + - Race between [(T).s] and [(T).s] is checked/reported at [(T).s]. + - Race between [(T)] and [(T)] is checked/reported at [(T)]. + - Race between [(S).f] and [(S).f] is checked/reported at [(S).f]. + - Race between [(S)] and [(S)] is checked/reported at [(S)]. + - Race between [(int)] and [(int)] is checked/reported at [(int)]. + + All prefix races: + - Race between [t.s.f] and [t.s] is checked/reported at [t.s.f]. + - Race between [t.s.f] and [t] is checked/reported at [t.s.f]. + - Race between [t.s] and [t] is checked/reported at [t.s]. + - Race between [(T).s.f] and [(T).s] is checked/reported at [(T).s.f]. + - Race between [(T).s.f] and [(T)] is checked/reported at [(T).s.f]. + - Race between [(T).s] and [(T)] is checked/reported at [(T).s]. + - Race between [(S).f] and [(S)] is checked/reported at [(S).f]. + + All type suffix races: + - Race between [t.s.f] and [(T).s.f] is checked/reported at [t.s.f]. + - Race between [t.s.f] and [(S).f] is checked/reported at [t.s.f]. + - Race between [t.s.f] and [(int)] is checked/reported at [t.s.f]. + - Race between [(T).s.f] and [(S).f] is checked/reported at [(T).s.f]. + - Race between [(T).s.f] and [(int)] is checked/reported at [(T).s.f]. + - Race between [(S).f] and [(int)] is checked/reported at [(S).f]. + - Race between [t.s] and [(T).s] is checked/reported at [t.s]. + - Race between [t.s] and [(S)] is checked/reported at [t.s]. + - Race between [(T).s] and [(S)] is checked/reported at [(T).s]. + - Race between [t] and [(T)] is checked/reported at [t]. + + All type suffix prefix races: + - Race between [t.s.f] and [(T).s] is checked/reported at [t.s.f]. + - Race between [t.s.f] and [(T)] is checked/reported at [t.s.f]. + - Race between [t.s.f] and [(S)] is checked/reported at [t.s.f]. + - Race between [(T).s.f] and [(S)] is checked/reported at [(T).s.f]. + - Race between [t.s] and [(T)] is checked/reported at [t.s]. + + All prefix-type suffix races: + - Race between [t.s] and [(T).s.f] is checked/reported at [t.s.f]. + - Race between [t.s] and [(S).f] is checked/reported at [t.s.f]. + - Race between [t.s] and [(int)] is checked/reported at [t.s.f]. + - Race between [t] and [(T).s.f] is checked/reported at [t.s.f]. + - Race between [t] and [(S).f] is checked/reported at [t.s.f]. + - Race between [t] and [(int)] is checked/reported at [t.s.f]. + - Race between [t] and [(T).s] is checked/reported at [t.s]. + - Race between [t] and [(S)] is checked/reported at [t.s]. + - Race between [(T).s] and [(S).f] is checked/reported at [(T).s.f]. + - Race between [(T).s] and [(int)] is checked/reported at [(T).s.f]. + - Race between [(T)] and [(S).f] is checked/reported at [(T).s.f]. + - Race between [(T)] and [(int)] is checked/reported at [(T).s.f]. + - Race between [(T)] and [(S)] is checked/reported at [(T).s]. + - Race between [(S)] and [(int)] is checked/reported at [(S).f]. *) + (** Data race analyzer without base --- this is the new standard *) module Spec = @@ -12,7 +134,7 @@ struct let name () = "race" (* Two global invariants: - 1. memoroot -> (offset -> accesses) -- used for warnings + 1. memoroot -> (offset --trie--> accesses) -- used for warnings 2. varinfo -> set of memo -- used for IterSysVars Global *) module V = @@ -52,13 +174,22 @@ struct module OffsetTrie = struct - include TrieDomain.Make (OneOffset) (Access.AS) + (* LiftBot such that add_distribute_outer can side-effect empty set to indicate + all offsets that exist for prefix-type_suffix race checking. + Otherwise, there are no trie nodes to traverse to where this check must happen. *) + include TrieDomain.Make (OneOffset) (Lattice.LiftBot (Access.AS)) + + let rec find (offset : Offset.Unit.t) ((accs, children) : t) : value = + match offset with + | `NoOffset -> accs + | `Field (f, offset') -> find offset' (ChildMap.find (Field f) children) + | `Index ((), offset') -> find offset' (ChildMap.find Index children) let rec singleton (offset : Offset.Unit.t) (value : value) : t = match offset with | `NoOffset -> (value, ChildMap.empty ()) - | `Field (f, offset') -> (Access.AS.empty (), ChildMap.singleton (Field f) (singleton offset' value)) - | `Index ((), offset') -> (Access.AS.empty (), ChildMap.singleton Index (singleton offset' value)) + | `Field (f, offset') -> (`Bot, ChildMap.singleton (Field f) (singleton offset' value)) + | `Index ((), offset') -> (`Bot, ChildMap.singleton Index (singleton offset' value)) end module G = @@ -94,32 +225,74 @@ struct | _ -> () - let side_access ctx (conf, w, loc, e, a) ((memoroot, offset) as memo) = + let side_access ctx acc ((memoroot, offset) as memo) = if !AnalysisState.should_warn then - ctx.sideg (V.access memoroot) (G.create_access (OffsetTrie.singleton offset (Access.AS.singleton (conf, w, loc, e, a)))); + ctx.sideg (V.access memoroot) (G.create_access (OffsetTrie.singleton offset (`Lifted (Access.AS.singleton acc)))); side_vars ctx memo + (** Side-effect empty access set for prefix-type_suffix race checking. *) + let side_access_empty ctx ((memoroot, offset) as memo) = + if !AnalysisState.should_warn then + ctx.sideg (V.access memoroot) (G.create_access (OffsetTrie.singleton offset (`Lifted (Access.AS.empty ())))); + side_vars ctx memo + + (** Get immediate type_suffix memo. *) + let type_suffix_memo ((root, offset) : Access.Memo.t) : Access.Memo.t option = + (* No need to make ana.race.direct-arithmetic return None here, + because (int) is empty anyway since Access.add_distribute_outer isn't called. *) + match root, offset with + | `Var v, _ -> Some (`Type (Cil.typeSig v.vtype), offset) (* global.foo.bar -> (struct S).foo.bar *) (* TODO: Alloc variables void type *) + | _, `NoOffset -> None (* primitive type *) + | _, `Field (f, offset') -> Some (`Type (Cil.typeSig f.ftype), offset') (* (struct S).foo.bar -> (struct T).bar *) + | `Type (TSArray (ts, _, _)), `Index ((), offset') -> Some (`Type ts, offset') (* (int[])[*] -> int *) + | _, `Index ((), offset') -> None (* TODO: why indexing on non-array? *) + + let rec find_type_suffix' ctx ((root, offset) as memo : Access.Memo.t) : Access.AS.t = + let trie = G.access (ctx.global (V.access root)) in + let accs = + match OffsetTrie.find offset trie with + | `Lifted accs -> accs + | `Bot -> Access.AS.empty () + in + let type_suffix = find_type_suffix ctx memo in + Access.AS.union accs type_suffix + + (** Find accesses from all type_suffixes transitively. *) + and find_type_suffix ctx (memo : Access.Memo.t) : Access.AS.t = + match type_suffix_memo memo with + | Some type_suffix_memo -> find_type_suffix' ctx type_suffix_memo + | None -> Access.AS.empty () + 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' -> (* accesses *) - (* ignore (Pretty.printf "WarnGlobal %a\n" CilType.Varinfo.pretty g); *) + (* ignore (Pretty.printf "WarnGlobal %a\n" Access.MemoRoot.pretty g'); *) let trie = G.access (ctx.global g) in (** Distribute access to contained fields. *) - let rec distribute_inner offset (accs, children) ancestor_accs = - let ancestor_accs' = Access.AS.union ancestor_accs accs in - OffsetTrie.ChildMap.iter (fun child_key child_trie -> - distribute_inner (Offset.Unit.add_offset offset (OneOffset.to_offset child_key)) child_trie ancestor_accs' - ) children; - if not (Access.AS.is_empty accs) then ( + let rec distribute_inner offset (accs, children) ~prefix ~type_suffix_prefix = + let accs = + match accs with + | `Lifted accs -> accs + | `Bot -> Access.AS.empty () + in + let type_suffix = find_type_suffix ctx (g', offset) in + if not (Access.AS.is_empty accs) || (not (Access.AS.is_empty prefix) && not (Access.AS.is_empty type_suffix)) then ( let memo = (g', offset) in let mem_loc_str = GobPretty.sprint Access.Memo.pretty memo in - Timing.wrap ~args:[("memory location", `String mem_loc_str)] "race" (Access.warn_global ~safe ~vulnerable ~unsafe ~ancestor_accs memo) accs - ) + Timing.wrap ~args:[("memory location", `String mem_loc_str)] "race" (Access.warn_global ~safe ~vulnerable ~unsafe {node=accs; prefix; type_suffix; type_suffix_prefix}) memo + ); + + (* Recurse to children. *) + let prefix' = Access.AS.union prefix accs in + let type_suffix_prefix' = Access.AS.union type_suffix_prefix type_suffix in + OffsetTrie.ChildMap.iter (fun child_key child_trie -> + distribute_inner (Offset.Unit.add_offset offset (OneOffset.to_offset child_key)) child_trie ~prefix:prefix' ~type_suffix_prefix:type_suffix_prefix' + ) children; in - distribute_inner `NoOffset trie (Access.AS.empty ()) + distribute_inner `NoOffset trie ~prefix:(Access.AS.empty ()) ~type_suffix_prefix:(Access.AS.empty ()) | `Right _ -> (* vars *) () end @@ -131,48 +304,48 @@ struct let event ctx e octx = match e with - | Events.Access {exp=e; lvals; kind; reach} when ThreadFlag.is_currently_multi (Analyses.ask_of_ctx ctx) -> (* threadflag query in post-threadspawn ctx *) + | Events.Access {exp; ad; kind; reach} when ThreadFlag.is_currently_multi (Analyses.ask_of_ctx ctx) -> (* threadflag query in post-threadspawn ctx *) (* must use original (pre-assign, etc) ctx queries *) let conf = 110 in - let module LS = Queries.LS in + let module AD = Queries.AD in let part_access (vo:varinfo option): MCPAccess.A.t = (*partitions & locks*) - Obj.obj (octx.ask (PartAccess (Memory {exp=e; var_opt=vo; kind}))) + Obj.obj (octx.ask (PartAccess (Memory {exp; var_opt=vo; kind}))) in - let loc = Option.get !Node.current_node in + let node = Option.get !Node.current_node in let add_access conf voffs = - let a = part_access (Option.map fst voffs) in - Access.add (side_access octx (conf, kind, loc, e, a)) e voffs; + let acc = part_access (Option.map fst voffs) in + Access.add ~side:(side_access octx {conf; kind; node; exp; acc}) ~side_empty:(side_access_empty octx) exp voffs; in let add_access_struct conf ci = - let a = part_access None in - Access.add_one (side_access octx (conf, kind, loc, e, a)) (`Type (TComp (ci, [])), `NoOffset) + let acc = part_access None in + Access.add_one ~side:(side_access octx {conf; kind; node; exp; acc}) (`Type (TSComp (ci.cstruct, ci.cname, [])), `NoOffset) in let has_escaped g = octx.ask (Queries.MayEscape g) in (* The following function adds accesses to the lval-set ls -- this is the common case if we have a sound points-to set. *) - let on_lvals ls includes_uk = - let ls = LS.filter (fun (g,_) -> g.vglob || has_escaped g) ls in + let on_ad ad includes_uk = let conf = if reach then conf - 20 else conf in let conf = if includes_uk then conf - 10 else conf in - let f (var, offs) = - let coffs = Offset.Exp.to_cil offs in - if CilType.Varinfo.equal var dummyFunDec.svar then - add_access conf None - else - add_access conf (Some (var, coffs)) + let f addr = + match addr with + | AD.Addr.Addr (g,o) when g.vglob || has_escaped g -> + let coffs = ValueDomain.Offs.to_cil o in + add_access conf (Some (g, coffs)) + | UnknownPtr -> add_access conf None + | _ -> () in - LS.iter f ls + AD.iter f ad in - begin match lvals with - | ls when not (LS.is_top ls) && not (Queries.LS.mem (dummyFunDec.svar,`NoOffset) ls) -> + begin match ad with + | ad when not (AD.is_top ad) -> (* the case where the points-to set is non top and does not contain unknown values *) - on_lvals ls false - | ls when not (LS.is_top ls) -> + on_ad ad false + | ad -> (* the case where the points-to set is non top and contains unknown values *) let includes_uk = ref false in (* now we need to access all fields that might be pointed to: is this correct? *) - begin match octx.ask (ReachableUkTypes e) with + begin match octx.ask (ReachableUkTypes exp) with | ts when Queries.TS.is_top ts -> includes_uk := true | ts -> @@ -185,14 +358,28 @@ struct in Queries.TS.iter f ts end; - on_lvals ls !includes_uk - | _ -> - add_access (conf - 60) None + on_ad ad !includes_uk + (* | _ -> + add_access (conf - 60) None *) (* TODO: what about this case? *) end; ctx.local | _ -> ctx.local + let special ctx (lvalOpt: lval option) (f:varinfo) (arglist:exp list) : D.t = + (* perform shallow and deep invalidate according to Library descriptors *) + let desc = LibraryFunctions.find f in + if List.mem LibraryDesc.ThreadUnsafe desc.attrs then ( + let exp = Lval (Var f, NoOffset) in + let conf = 110 in + let kind = AccessKind.Call in + let node = Option.get !Node.current_node in + let vo = Some f in + let acc = Obj.obj (ctx.ask (PartAccess (Memory {exp; var_opt=vo; kind}))) in + side_access ctx {conf; kind; node; exp; acc} ((`Var f), `NoOffset) ; + ); + ctx.local + let finalize () = let total = !safe + !unsafe + !vulnerable in if total > 0 then ( diff --git a/src/analyses/region.ml b/src/analyses/region.ml index 6d2ae246c3..5b10586aba 100644 --- a/src/analyses/region.ml +++ b/src/analyses/region.ml @@ -175,9 +175,17 @@ struct let startstate v = `Lifted (RegMap.bot ()) - let threadenter ctx lval f args = + let threadenter ctx ~multiple lval f args = [`Lifted (RegMap.bot ())] - let threadspawn ctx lval f args fctx = ctx.local + let threadspawn ctx ~multiple lval f args fctx = + match ctx.local with + | `Lifted reg -> + let old_regpart = ctx.global () in + let regpart, reg = List.fold_right Reg.assign_escape args (old_regpart, reg) in + if not (RegPart.leq regpart old_regpart) then + ctx.sideg () regpart; + `Lifted reg + | x -> x let exitstate v = `Lifted (RegMap.bot ()) diff --git a/src/analyses/spec.ml b/src/analyses/spec.ml deleted file mode 100644 index d7328310dd..0000000000 --- a/src/analyses/spec.ml +++ /dev/null @@ -1,497 +0,0 @@ -(** Analysis using finite automaton specification file ([spec]). - - @author Ralf Vogler - - @see Vogler, R. Verifying Regular Safety Properties of C Programs Using the Static Analyzer Goblint. Section 4. *) - -open Batteries -open GoblintCil -open Analyses - -module SC = SpecCore - -module Spec = -struct - include Analyses.DefaultSpec - - let name() = "spec" - module D = SpecDomain.Dom - module C = SpecDomain.Dom - - (* special variables *) - let return_var = Cilfacade.create_var @@ Cil.makeVarinfo false "@return" Cil.voidType, `NoOffset - let global_var = Cilfacade.create_var @@ Cil.makeVarinfo false "@global" Cil.voidType, `NoOffset - - (* spec data *) - let nodes = ref [] - let edges = ref [] - - let load_specfile () = - let specfile = GobConfig.get_string "ana.spec.file" in - if String.length specfile < 1 then failwith "You need to specify a specification file using --set ana.spec.file path/to/file.spec when using the spec analysis!"; - if not (Sys.file_exists specfile) then failwith @@ "The given spec-file ("^specfile^") doesn't exist (CWD is "^Sys.getcwd ()^")."; - let _nodes, _edges = SpecUtil.parseFile specfile in - nodes := _nodes; edges := _edges (* don't change -> no need to save them in domain *) - - (* module for encapsulating general spec checking functions used in multiple transfer functions (assign, special) *) - (* - .spec-format: - - The file contains two types of definitions: nodes and edges. The labels of nodes are output. The labels of edges are the constraints. - - The given nodes are warnings, which have an implicit back edge to the previous node if used as a target. - - Alternatively warnings can be specified like this: "node1 -w1,w2,w3> node2 ...1" (w1, w2 and w3 will be output when the transition is taken). - - The start node of the first transition is the start node of the automaton. - - End nodes are specified by "node -> end _". - - "_end" is the local warning for nodes that are not in an end state, _END is the warning at return ($ is the list of keys). - - An edge with '_' matches everything. - - Edges with "->>" (or "-w1,w2>>" etc.) are forwarding edges, which will continue matching the same statement for the target node. - *) - module SpecCheck = - struct - (* custom goto (D.goto is just for modifying) that checks if the target state is a warning and acts accordingly *) - let goto ?may:(may=false) ?change_state:(change_state=true) key state m ws = - let loc = (Option.get !Node.current_node)::(D.callstack m) in - let warn key m msg = - Str.global_replace (Str.regexp_string "$") (D.string_of_key key) msg - |> D.warn ~may:(D.is_may key m || D.is_unknown key m) - in - (* do transition warnings *) - List.iter (fun state -> match SC.warning state !nodes with Some msg -> warn key m msg | _ -> ()) ws; - match SC.warning state !nodes with - | Some msg -> - warn key m msg; - m (* no goto == implicit back edge *) - | None -> - M.debug ~category:Analyzer "GOTO %s: %s -> %s" (D.string_of_key key) (D.string_of_state key m) state; - if not change_state then m - else if may then D.may_goto key loc state m else D.goto key loc state m - - let equal_exp ctx spec_exp cil_exp = match spec_exp, cil_exp with - (* TODO match constants right away to avoid queries? *) - | `String a, Const(CStr (b,_)) -> a=b - (* | `String a, Const(CWStr xs as c) -> failwith "not implemented" *) - (* CWStr is done in base.ml, query only returns `Str if it's safe *) - | `String a, e -> (match ctx.ask (Queries.EvalStr e) with - | `Lifted b -> a = b - | _ -> M.debug ~category:Analyzer "EQUAL String Query: no result!"; false - ) - | `Regex a, e -> (match ctx.ask (Queries.EvalStr e) with - | `Lifted b -> Str.string_match (Str.regexp a) b 0 - | _ -> M.debug ~category:Analyzer "EQUAL Regex String Query: no result!"; false - ) - | `Bool a, e -> (match ctx.ask (Queries.EvalInt e) with - | b -> (match Queries.ID.to_bool b with Some b -> a=b | None -> false) - ) - | `Int a, e -> (match ctx.ask (Queries.EvalInt e) with - | b -> (match Queries.ID.to_int b with Some b -> (Int64.of_int a)=(IntOps.BigIntOps.to_int64 b) | None -> false) - ) - | `Float a, Const(CReal (b, fkind, str_opt)) -> a=b - | `Float a, _ -> M.debug ~category:Analyzer "EQUAL Float: unsupported!"; false - (* arg is a key. currently there can only be one key per constraint, so we already used it for lookup. TODO multiple keys? *) - | `Var a, b -> true - (* arg is a identifier we use for matching constraints. TODO save in domain *) - | `Ident a, b -> true - | `Error s, b -> failwith @@ "Spec error: "^s - (* wildcard matches anything *) - | `Free, b -> true - | a,b -> M.info ~category:Unsound "EQUAL? Unmatched case - assume true..."; true - - let check_constraint ctx get_key matches m new_a old_key (a,ws,fwd,b,c as edge) = - (* If we have come to a wildcard, we match it instantly, but since there is no way of determining a key - this only makes sense if fwd is true (TODO wildcard for global. TODO use old_key). We pass a state replacement as 'new_a', - which will be applied in the following checks. - Multiple forwarding wildcards are not allowed, i.e. new_a must be None, otherwise we end up in a loop. *) - if SC.is_wildcard c && fwd && new_a=None then Some (m,fwd,Some (b,a),old_key) (* replace b with a in the following checks *) - else - (* save original start state of the constraint (needed to detect reflexive edges) *) - let old_a = a in - (* Assume new_a *) - let a = match new_a with - | Some (x,y) when a=x -> y - | _ -> a - in - (* if we forward, we have to replace the starting state for the following constraints *) - let new_a = if fwd then Some (b,a) else None in - (* TODO how to detect the key?? use "$foo" as key, "foo" as var in constraint and "_" for anything we're not interested in. - What to do for multiple keys (e.g. $foo, $bar)? -> Only allow one key & one map per spec-file (e.g. only $ as a key) or implement multiple maps? *) - (* look inside the constraint if there is a key and if yes, return what it corresponds to *) - (* if we can't find a matching key, we use the global key *) - let key = get_key c |? Cil.var (fst global_var) in - (* ignore(printf "KEY: %a\n" d_plainlval key); *) - (* get possible keys that &lval may point to *) - let keys = D.keys_from_lval key (Analyses.ask_of_ctx ctx) in (* does MayPointTo query *) - let check_key (m,n) var = - (* M.debug ~category:Analyzer @@ "check_key: "^f.vname^"(...): "^D.string_of_entry var m; *) - let wildcard = SC.is_wildcard c && fwd && b<>"end" in - (* skip transitions we can't take b/c we're not in the right state *) - (* i.e. if not in map, we must be at the start node or otherwise we must be in one of the possible saved states *) - if not (D.mem var m) && a<>SC.startnode !edges || D.mem var m && not (D.may_in_state var a m) then ( - (* ignore(printf "SKIP %s: state: %s, a: %s at %i\n" f.vname (D.string_of_state var m) a (!Tracing.current_loc.line)); *) - (m,n) (* not in map -> initial state. TODO save initial state? *) - ) - (* edge must match the current state or be a wildcard transition (except those for end) *) - else if not (matches edge) && not wildcard then (m,n) - (* everything matches the constraint -> go to new state and increase counter *) - else - (* TODO if #Queries.MayPointTo > 1: each result is May, but all combined are Must *) - let may = (List.compare_length_with keys 1 > 0) in - (* do not change state for reflexive edges where the key is not assigned to (e.g. *$p = _) *) - let change_state = not (old_a=b && SC.get_lval c <> Some `Var) in - M.debug ~category:Analyzer "GOTO ~may:%B ~change_state:%B. %s -> %s: %s" may change_state a b (SC.stmt_to_string c); - let new_m = goto ~may:may ~change_state:change_state var b m ws in - (new_m,n+1) - in - (* do check for each varinfo and return the resulting domain if there has been at least one matching constraint *) - let new_m,n = List.fold_left check_key (m,0) keys in (* start with original domain and #transitions=0 *) - if n==0 then None (* no constraint matched the current state *) - else Some (new_m,fwd,new_a,Some key) (* return new domain and forwarding info *) - - let check ctx get_key matches = - let m = ctx.local in - (* go through constraints and return resulting domain for the first match *) - (* if no constraint matches, the unchanged domain is returned *) - (* repeat for target node if it is a forwarding edge *) - (* TODO what should be done if multiple constraints would match? *) - (* TODO ^^ for May-Sets multiple constraints could match and should be taken! *) - try - let rec check_fwd_loop m new_a old_key = (* TODO cycle detection? *) - let new_m,fwd,new_a,key = List.find_map (check_constraint ctx get_key matches m new_a old_key) !edges in - (* List.iter (fun x -> M.debug ~category:Analyzer (x^"\n")) (D.string_of_map new_m); *) - if fwd then M.debug ~category:Analyzer "FWD: %B, new_a: %s, old_key: %s" fwd (dump new_a) (dump old_key); - if fwd then check_fwd_loop new_m new_a key else new_m,key - in - (* now we get the new domain and the latest key that was used *) - let new_m,key = check_fwd_loop m None None in - (* List.iter (fun x -> M.debug ~category:Analyzer (x^"\n")) (D.string_of_map new_m); *) - (* next we have to check if there is a branch() transition we could take *) - let branch_edges = List.filter (fun (a,ws,fwd,b,c) -> SC.is_branch c) !edges in - (* just for the compiler: key is initialized with None, but changes once some constaint matches. If none match, we wouldn't be here but at catch Not_found. *) - match key with - | Some key -> - (* we need to pass the key to the branch function. There is no scheme for getting the key from the constraint, but we should have been forwarded and can use the old key. *) - let check_branch branches var = - (* only keep those branch_edges for which our key might be in the right state *) - let branch_edges = List.filter (fun (a,ws,fwd,b,c) -> D.may_in_state var a new_m) branch_edges in - (* M.debug ~category:Analyzer @@ D.string_of_entry var new_m^" -> branch_edges: "^String.concat "\n " @@ List.map (fun x -> SC.def_to_string (SC.Edge x)) branch_edges; *) - (* count should be a multiple of 2 (true/false), otherwise the spec is malformed *) - if List.length branch_edges mod 2 <> 0 then failwith "Spec is malformed: branch-transitions always need a true and a false case!" else - (* if nothing matches, just return new_m without branching *) - (* if List.is_empty branch_edges then Set.of_list new_m else *) - if List.is_empty branch_edges then Set.of_list ([new_m, Cil.integer 1, true]) else (* XX *) - (* unique set of (dom,exp,tv) used in branch *) - let do_branch branches (a,ws,fwd,b,c) = - let c_str = match SC.branch_exp c with Some (exp,tv) -> SC.exp_to_string exp | _ -> "" in - let c_str = Str.global_replace (Str.regexp_string "$key") "%e:key" c_str in (* TODO what should be used to specify the key? *) - (* TODO this somehow also prints the expression!? why?? *) - let c_exp = Formatcil.cExp c_str [("key", Fe (D.K.to_cil_exp var))] in (* use Fl for Lval instead? *) - (* TODO encode key in exp somehow *) - (* ignore(printf "BRANCH %a\n" d_plainexp c_exp); *) - ctx.split new_m [Events.SplitBranch (c_exp, true)]; - Set.add (new_m,c_exp,true) (Set.add (new_m,c_exp,false) branches) - in - List.fold_left do_branch branches branch_edges - in - let keys = D.keys_from_lval key (Analyses.ask_of_ctx ctx) in - let new_set = List.fold_left check_branch Set.empty keys in ignore(new_set); (* TODO refactor *) - (* List.of_enum (Set.enum new_set) *) - new_m (* XX *) - | None -> new_m - with Not_found -> m (* nothing matched -> no change *) - end - - (* queries *) - let query ctx (type a) (q: a Queries.t) = - match q with - | _ -> Queries.Result.top q - - let query_lv ask exp = - match ask (Queries.MayPointTo exp) with - | l when not (Queries.LS.is_top l) -> - Queries.LS.elements l - | _ -> [] - - let eval_fv ask exp: varinfo option = - match query_lv ask exp with - | [(v,_)] -> Some v - | _ -> None - - - (* transfer functions *) - let assign ctx (lval:lval) (rval:exp) : D.t = - (* ignore(printf "%a = %a\n" d_plainlval lval d_plainexp rval); *) - let get_key c = match SC.get_key_variant c with - | `Lval s -> - M.debug ~category:Analyzer "Key variant assign `Lval %s; %s" s (SC.stmt_to_string c); - (match SC.get_lval c, lval with - | Some `Var, _ -> Some lval - | Some `Ptr, (Mem Lval x, o) -> Some x (* TODO offset? *) - | _ -> None) - | _ -> None - in - let matches (a,ws,fwd,b,c) = - SC.equal_form (Some lval) c && - (* check for constraints *p = _ where p is the key *) - match lval, SC.get_lval c with - | (Mem Lval x, o), Some `Ptr when SpecCheck.equal_exp ctx (SC.get_rval c) rval -> - let keys = D.keys_from_lval x (Analyses.ask_of_ctx ctx) in - if List.compare_length_with keys 1 <> 0 then failwith "not implemented" - else true - | _ -> false (* nothing to do *) - in - let m = SpecCheck.check ctx get_key matches in - let key_from_exp = function - | Lval (Var v,o) -> Some (v, Offset.Exp.of_cil o) - | _ -> None - in - match key_from_exp (Lval lval), key_from_exp (stripCasts rval) with (* TODO for now we just care about Lval assignments -> should use Queries.MayPointTo *) - | Some k1, Some k2 when k1=k2 -> m (* do nothing on self-assignment *) - | Some k1, Some k2 when D.mem k1 m && D.mem k2 m -> (* both in D *) - M.debug ~category:Analyzer "assign (both in D): %s = %s" (D.string_of_key k1) (D.string_of_key k2); - (* saveOpened k1 *) m |> D.remove' k1 |> D.alias k1 k2 - | Some k1, Some k2 when D.mem k1 m -> (* only k1 in D *) - M.debug ~category:Analyzer "assign (only k1 in D): %s = %s" (D.string_of_key k1) (D.string_of_key k2); - (* saveOpened k1 *) m |> D.remove' k1 - | Some k1, Some k2 when D.mem k2 m -> (* only k2 in D *) - M.debug ~category:Analyzer "assign (only k2 in D): %s = %s" (D.string_of_key k1) (D.string_of_key k2); - let m = D.alias k1 k2 m in (* point k1 to k2 *) - if Basetype.Variables.to_group (fst k2) = Temp (* check if k2 is a temporary Lval introduced by CIL *) - then D.remove' k2 m (* if yes we need to remove it from our map *) - else m (* otherwise no change *) - | Some k1, _ when D.mem k1 m -> (* k1 in D and assign something unknown *) - M.debug ~category:Analyzer "assign (only k1 in D): %s = %a" (D.string_of_key k1) d_exp rval; - D.warn @@ "changed pointer "^D.string_of_key k1^" (no longer safe)"; - (* saveOpened ~unknown:true k1 *) m |> D.unknown k1 - | _ -> (* no change in D for other things *) - M.debug ~category:Analyzer "assign (none in D): %a = %a [%a]" d_lval lval d_exp rval d_plainexp rval; - m - - (* - - branch-transitions in the spec-file come in pairs: e.g. true-branch goes to node a, false-branch to node b - - branch is called for both possibilities - - TODO query the exp and take/don't take the transition - - in case of `Top we take the transition - - both branches get joined after (e.g. for fopen: May [open; error]) - - if there is a branch in the code, branch is also called - -> get the key from exp and backtrack to the corresponding branch-transitions - -> reevaluate with current exp and meet domain with result - *) - (* - - get key from exp - - ask EvalInt - - if result is `Top and we are in a state that is the starting node of a branch edge, we have to: - - go to target node and modify the state in specDomain - - find out which value of key makes exp equal to tv - - save this value and answer queries for EvalInt with it - - if not, compare it with tv and take the corresponding branch - *) - let branch ctx (exp:exp) (tv:bool) : D.t = - let m = ctx.local in - (* ignore(printf "if %a = %B (line %i)\n" d_plainexp exp tv (!Tracing.current_loc).line); *) - let check a b tv = - (* ignore(printf "check: %a = %a\n" d_plainexp a d_plainexp b); *) - match a, b with - | Const (CInt(i, kind, str)), Lval lval - | Lval lval, Const (CInt(i, kind, str)) -> - (* let binop = BinOp (Eq, a, b, Cil.intType) in *) - (* standardize the format of the expression to 'lval==i'. -> spec needs to follow that format, the code is mapped to it. *) - let binop = BinOp (Eq, Lval lval, Const (CInt(i, kind, str)), Cil.intType) in - let key = D.key_from_lval lval in - let value = D.find key m in - if Z.equal i Z.zero && tv then ( - M.debug ~category:Analyzer "error-branch"; - (* D.remove key m *) - )else( - M.debug ~category:Analyzer "success-branch"; - (* m *) - ); - (* there should always be an entry in our domain for key *) - if not (D.mem key m) then m else - (* TODO for now we just assume that a Binop is used and Lval is the key *) - (* get the state(s) that key is/might be in *) - let states = D.get_states key m in - (* compare SC.exp with Cil.exp and tv *) - let branch_exp_eq c exp tv = - (* let c_str = match SC.branch_exp c with Some (exp,tv) -> SC.exp_to_string exp | _ -> "" in - let c_str = Str.global_replace (Str.regexp_string "$key") "%e:key" c_str in - let c_exp = Formatcil.cExp c_str [("key", Fe (D.K.to_exp key))] in *) - (* c_exp=exp *) (* leads to Out_of_memory *) - match SC.branch_exp c with - | Some (c_exp,c_tv) -> - (* let exp_str = CilType.Exp.show exp in *) (* contains too many casts, so that matching fails *) - let exp_str = CilType.Exp.show binop in - let c_str = SC.exp_to_string c_exp in - let c_str = Str.global_replace (Str.regexp_string "$key") (D.string_of_key key) c_str in - (* ignore(printf "branch_exp_eq: '%s' '%s' -> %B\n" c_str exp_str (c_str=exp_str)); *) - c_str=exp_str && c_tv=tv - | _ -> false - in - (* filter those edges that are branches, start with a state from states and have the same branch expression and the same tv *) - let branch_edges = List.filter (fun (a,ws,fwd,b,c) -> SC.is_branch c && List.mem a states && branch_exp_eq c exp tv) !edges in - (* there should be only one such edge or none *) - if List.compare_length_with branch_edges 1 <> 0 then ( (* call of branch for an actual branch *) - M.debug ~category:Analyzer "branch: branch_edges length is not 1! -> actual branch"; - M.debug ~category:Analyzer "%s -> branch_edges1: %a" (D.string_of_entry key m) (Pretty.d_list "\n " (fun () x -> Pretty.text (SC.def_to_string (SC.Edge x)))) branch_edges; - (* filter those edges that are branches, end with a state from states have the same branch expression and the same tv *) - (* TODO they should end with any predecessor of the current state, not only the direct predecessor *) - let branch_edges = List.filter (fun (a,ws,fwd,b,c) -> SC.is_branch c && List.mem b states && branch_exp_eq c exp tv) !edges in - M.debug ~category:Analyzer "%s -> branch_edges2: %a" (D.string_of_entry key m) (Pretty.d_list "\n " (fun () x -> Pretty.text (SC.def_to_string (SC.Edge x)))) branch_edges; - if List.compare_length_with branch_edges 1 <> 0 then m else - (* meet current value with the target state. this is tricky: we can not simply take the target state, since there might have been more than one element already before the branching. - -> find out what the alternative branch target was and remove it *) - let (a,ws,fwd,b,c) = List.hd branch_edges in - (* the alternative branch has the same start node, the same branch expression and the negated tv *) - let (a,ws,fwd,b,c) = List.find (fun (a2,ws,fwd,b,c) -> SC.is_branch c && a2=a && branch_exp_eq c exp (not tv)) !edges in - (* now b is the state the alternative branch goes to -> remove it *) - (* TODO may etc. *) - (* being explicit: check how many records there are. if the value is Must b, then we're sure that it is so and we don't remove anything. *) - if D.V.length value = (1,1) then m else (* XX *) - (* there are multiple possible states -> remove b *) - let v2 = D.V.remove_state b value in - (* M.debug ~category:Analyzer @@ "branch: changed state from "^D.V.string_of value^" to "^D.V.string_of v2; *) - D.add key v2 m - ) else (* call of branch directly after splitting *) - let (a,ws,fwd,b,c) = List.hd branch_edges in - (* TODO may etc. *) - let v2 = D.V.set_state b value in - (* M.debug ~category:Analyzer @@ "branch: changed state from "^D.V.string_of value^" to "^D.V.string_of v2; *) - D.add key v2 m - | _ -> M.debug ~category:Analyzer "nothing matched the given BinOp: %a = %a" d_plainexp a d_plainexp b; m - in - match stripCasts (constFold true exp) with - (* somehow there are a lot of casts inside the BinOp which stripCasts only removes when called on the subparts - -> matching as in flagMode didn't work *) - | BinOp (Eq, a, b, _) -> check (stripCasts a) (stripCasts b) tv - | BinOp (Ne, a, b, _) -> check (stripCasts a) (stripCasts b) (not tv) - | UnOp (LNot, a, _) -> check (stripCasts a) (integer 0) tv - (* TODO makes 2 tests fail. probably check changes something it shouldn't *) - (* | Lval _ as a -> check (stripCasts a) (integer 0) (not tv) *) - | e -> M.debug ~category:Analyzer "branch: nothing matched the given exp: %a" d_plainexp e; m - - let body ctx (f:fundec) : D.t = - ctx.local - - let return ctx (exp:exp option) (f:fundec) : D.t = - let m = ctx.local in - (* M.debug ~category:Analyzer @@ "return: ctx.local="^D.short 50 m^D.string_of_callstack m; *) - (* if f.svar.vname <> "main" && BatList.is_empty (D.callstack m) then M.debug ~category:Analyzer @@ "\n\t!!! call stack is empty for function "^f.svar.vname^" !!!"; *) - if f.svar.vname = "main" then ( - let warn_main msg_loc msg_end = (* there is an end warning for local, return or both *) - (* find edges that have 'end' as a target *) - (* we ignore the constraint, TODO maybe find a better syntax for declaring end states *) - let end_states = BatList.filter_map (fun (a,ws,fwd,b,c) -> if b="end" then Some a else None) !edges in - let must_not, may_not = D.filter_values (fun r -> not @@ List.exists (fun end_state -> D.V.in_state end_state r) end_states) m in - let may_not = Set.diff may_not must_not in - (match msg_loc with (* local warnings for entries that must/may not be in an end state *) - | Some msg -> - Set.iter (fun r -> D.warn ~loc:(D.V.loc r) msg) must_not; - Set.iter (fun r -> D.warn ~may:true ~loc:(D.V.loc r) msg) may_not - | None -> ()); - (match msg_end with - | Some msg -> (* warnings at return for entries that must/may not be in an end state *) - let f msg rs = Str.global_replace (Str.regexp_string "$") (D.string_of_keys rs) msg in - if Set.cardinal must_not > 0 then D.warn (f msg must_not); - if Set.cardinal may_not > 0 then D.warn ~may:true (f msg may_not) - | _ -> ()) - in - (* check if there is a warning for entries that are not in an end state *) - match SC.warning "_end" !nodes, SC.warning "_END" !nodes with - | None, None -> () (* nothing to do here *) - | msg_loc,msg_end -> warn_main msg_loc msg_end - ); - (* take care of return value *) - let au = match exp with - | Some(Lval lval) when D.mem (D.key_from_lval lval) m -> (* we return a var in D *) - let k = D.key_from_lval lval in - let varinfo,offset = k in - if varinfo.vglob then - D.alias return_var k m (* if var is global, we alias it *) - else - D.add return_var (D.find' k m) m (* if var is local, we make a copy *) - | _ -> m - in - (* remove formals and locals *) - (* TODO only keep globals like in fileUse *) - List.fold_left (fun m var -> D.remove' (var, `NoOffset) m) au (f.sformals @ f.slocals) - - let enter ctx (lval: lval option) (f:fundec) (args:exp list) : (D.t * D.t) list = - (* M.debug ~category:Analyzer @@ "entering function "^f.vname^D.string_of_callstack ctx.local; *) - if f.svar.vname = "main" then load_specfile (); - let m = if f.svar.vname <> "main" then - D.edit_callstack (BatList.cons (Option.get !Node.current_node)) ctx.local - else ctx.local in [m, m] - - let combine_env ctx lval fexp f args fc au f_ask = - (* M.debug ~category:Analyzer @@ "leaving function "^f.vname^D.string_of_callstack au; *) - let au = D.edit_callstack List.tl au in - (* remove special return var *) - D.remove' return_var au - - let combine_assign ctx (lval:lval option) fexp (f:fundec) (args:exp list) fc (au:D.t) (f_ask: Queries.ask) : D.t = - let return_val = D.find_option return_var au in - match lval, return_val with - | Some lval, Some v -> - let k = D.key_from_lval lval in - (* handle potential overwrites *) - (* |> check_overwrite_open k *) - (* if v.key is still in D, then it must be a global and we need to alias instead of rebind *) - (* TODO what if there is a local with the same name as the global? *) - if D.V.is_top v then (* returned a local that was top -> just add k as top *) - D.add' k v ctx.local - else (* v is now a local which is not top or a global which is aliased *) - let vvar = D.V.get_alias v in (* this is also ok if v is not an alias since it chooses an element from the May-Set which is never empty (global top gets aliased) *) - if D.mem vvar au then (* returned variable was a global TODO what if local had the same name? -> seems to work *) - (* let _ = M.debug ~category:Analyzer @@ vvar.vname^" was a global -> alias" in *) - D.alias k vvar ctx.local - else (* returned variable was a local *) - let v = D.V.set_key k v in (* adjust var-field to lval *) - (* M.debug ~category:Analyzer @@ vvar.vname^" was a local -> rebind"; *) - D.add' k v ctx.local - | _ -> ctx.local - - let special ctx (lval: lval option) (f:varinfo) (arglist:exp list) : D.t = - let arglist = List.map (Cil.stripCasts) arglist in (* remove casts, TODO safe? *) - let get_key c = match SC.get_key_variant c with - | `Lval s -> - M.debug ~category:Analyzer "Key variant special `Lval %s; %s" s (SC.stmt_to_string c); - lval - | `Arg(s, i) -> - M.debug ~category:Analyzer "Key variant special `Arg(%s, %d). %s" s i (SC.stmt_to_string c); - (try - let arg = List.at arglist i in - match arg with - | Lval x -> Some x (* TODO enough to just assume the arg is already there as a Lval? *) - | AddrOf x -> Some x - | _ -> None - with Invalid_argument s -> - M.debug ~category:Analyzer "Key out of bounds! Msg: %s" s; (* TODO what to do if spec says that there should be more args... *) - None - ) - | _ -> None (* `Rval or `None *) - in - let matches (a,ws,fwd,b,c) = - let equal_args spec_args cil_args = - if List.compare_length_with spec_args 1 = 0 && List.hd spec_args = `Free then - true (* wildcard as an argument matches everything *) - else if List.compare_lengths arglist spec_args <> 0 then ( - M.debug ~category:Analyzer "SKIP the number of arguments doesn't match the specification!"; - false - )else - List.for_all2 (SpecCheck.equal_exp ctx) spec_args cil_args (* TODO Cil.constFold true arg. Test: Spec and c-file: 1+1 *) - in - (* function name must fit the constraint *) - SC.fname_is f.vname c && - (* right form (assignment or not) *) - SC.equal_form lval c && - (* function arguments match those of the constraint *) - equal_args (SC.get_fun_args c) arglist - in - SpecCheck.check ctx get_key matches - - - let startstate v = D.bot () - let threadenter ctx lval f args = [D.bot ()] - let threadspawn ctx lval f args fctx = ctx.local - let exitstate v = D.bot () -end - -let _ = - MCP.register_analysis (module Spec : MCPSpec) diff --git a/src/analyses/stackTrace.ml b/src/analyses/stackTrace.ml index 4dc62f1873..dd2cedf871 100644 --- a/src/analyses/stackTrace.ml +++ b/src/analyses/stackTrace.ml @@ -21,7 +21,7 @@ struct ctx.local (* keep local as opposed to IdentitySpec *) let startstate v = D.bot () - let threadenter ctx lval f args = [D.bot ()] + let threadenter ctx ~multiple lval f args = [D.bot ()] let exitstate v = D.top () end @@ -36,7 +36,7 @@ struct (* transfer functions *) let enter ctx (lval: lval option) (f:fundec) (args:exp list) : (D.t * D.t) list = - [ctx.local, D.push !Tracing.current_loc ctx.local] + [ctx.local, D.push !Goblint_tracing.current_loc ctx.local] let combine_env ctx lval fexp f args fc au f_ask = ctx.local (* keep local as opposed to IdentitySpec *) @@ -45,8 +45,8 @@ struct let startstate v = D.bot () let exitstate v = D.top () - let threadenter ctx lval f args = - [D.push !Tracing.current_loc ctx.local] + let threadenter ctx ~multiple lval f args = + [D.push !Goblint_tracing.current_loc ctx.local] end diff --git a/src/analyses/symbLocks.ml b/src/analyses/symbLocks.ml index d8cebf51d2..f6fdd96c2e 100644 --- a/src/analyses/symbLocks.ml +++ b/src/analyses/symbLocks.ml @@ -29,8 +29,8 @@ struct let name () = "symb_locks" let startstate v = D.top () - let threadenter ctx lval f args = [D.top ()] - let threadspawn ctx lval f args fctx = ctx.local + let threadenter ctx ~multiple lval f args = [D.top ()] + let threadspawn ctx ~multiple lval f args fctx = ctx.local let exitstate v = D.top () let branch ctx exp tv = ctx.local diff --git a/src/analyses/taintPartialContexts.ml b/src/analyses/taintPartialContexts.ml index 76f4af8f9e..85dabd1c9d 100644 --- a/src/analyses/taintPartialContexts.ml +++ b/src/analyses/taintPartialContexts.ml @@ -6,21 +6,19 @@ open GoblintCil open Analyses +module AD = ValueDomain.AD + module Spec = struct include Analyses.IdentitySpec let name () = "taintPartialContexts" - module D = SetDomain.ToppedSet (Mval.Exp) (struct let topname = "All" end) + module D = AD module C = Lattice.Unit (* Add Lval or any Lval which it may point to to the set *) let taint_lval ctx (lval:lval) : D.t = - let d = ctx.local in - (match lval with - | (Var v, offs) -> D.add (v, Offset.Exp.of_cil offs) d - | (Mem e, _) -> D.union (ctx.ask (Queries.MayPointTo e)) d - ) + D.union (ctx.ask (Queries.MayPointTo (AddrOf lval))) ctx.local (* this analysis is context insensitive*) let context _ _ = () @@ -35,14 +33,12 @@ struct let d_return = if D.is_top d then d - else ( + else let locals = f.sformals @ f.slocals in - D.filter (fun (v, _) -> - not (List.exists (fun local -> - CilType.Varinfo.equal v local && not (ctx.ask (Queries.IsMultiple local)) - ) locals) + D.filter (function + | AD.Addr.Addr (v,_) -> not (List.exists (fun local -> CilType.Varinfo.equal v local && not (ctx.ask (Queries.IsMultiple local))) locals) + | _ -> false ) d - ) in if M.tracing then M.trace "taintPC" "returning from %s: tainted vars: %a\n without locals: %a\n" f.svar.vname D.pretty d D.pretty d_return; d_return @@ -84,6 +80,7 @@ struct else deep_addrs in + (* TODO: should one handle ad with unknown pointers separately like in (all) other analyses? *) let d = List.fold_left (fun accD addr -> D.union accD (ctx.ask (Queries.MayPointTo addr))) d shallow_addrs in let d = List.fold_left (fun accD addr -> D.union accD (ctx.ask (Queries.ReachableFrom addr))) d deep_addrs @@ -91,9 +88,9 @@ struct d let startstate v = D.bot () - let threadenter ctx lval f args = + let threadenter ctx ~multiple lval f args = [D.bot ()] - let threadspawn ctx lval f args fctx = + let threadspawn ctx ~multiple lval f args fctx = match lval with | Some lv -> taint_lval ctx lv | None -> ctx.local @@ -101,7 +98,7 @@ struct let query ctx (type a) (q: a Queries.t) : a Queries.result = match q with - | MayBeTainted -> (ctx.local : Queries.LS.t) + | MayBeTainted -> (ctx.local : Queries.AD.t) | _ -> Queries.Result.top q end @@ -112,5 +109,8 @@ let _ = module VS = SetDomain.ToppedSet(Basetype.Variables) (struct let topname = "All" end) (* Convert Lval set to (less precise) Varinfo set. *) -let conv_varset (lval_set : Spec.D.t) : VS.t = - if Spec.D.is_top lval_set then VS.top () else VS.of_list (List.map (fun (v, _) -> v) (Spec.D.elements lval_set)) +let conv_varset (addr_set : Spec.D.t) : VS.t = + if Spec.D.is_top addr_set then + VS.top () + else + VS.of_list (Spec.D.to_var_may addr_set) diff --git a/src/analyses/termination.ml b/src/analyses/termination.ml deleted file mode 100644 index 6da9225d3f..0000000000 --- a/src/analyses/termination.ml +++ /dev/null @@ -1,239 +0,0 @@ -(** Termination analysis of loops using counter variables ([term]). *) - -open Batteries -open GoblintCil -open Analyses - -module M = Messages -let (||?) a b = match a,b with Some x,_ | _, Some x -> Some x | _ -> None - -module TermDomain = struct - include SetDomain.ToppedSet (Basetype.Variables) (struct let topname = "All Variables" end) -end - -(* some kind of location string suitable for variable names? *) -let show_location_id l = - string_of_int l.line ^ "_" ^ string_of_int l.column - -class loopCounterVisitor (fd : fundec) = object(self) - inherit nopCilVisitor - method! vstmt s = - let action s = match s.skind with - | Loop (b, loc, eloc, _, _) -> - (* insert loop counter variable *) - let name = "term"^show_location_id loc in - let typ = intType in (* TODO the type should be the same as the one of the original loop counter *) - let v = Cilfacade.create_var (makeLocalVar fd name ~init:(SingleInit zero) typ) in - (* make an init stmt since the init above is apparently ignored *) - let init_stmt = mkStmtOneInstr @@ Set (var v, zero, loc, eloc) in - (* increment it every iteration *) - let inc_stmt = mkStmtOneInstr @@ Set (var v, increm (Lval (var v)) 1, loc, eloc) in - b.bstmts <- inc_stmt :: b.bstmts; - let nb = mkBlock [init_stmt; mkStmt s.skind] in - s.skind <- Block nb; - s - | _ -> s - in ChangeDoChildrenPost (s, action) -end - -let loopBreaks : (int, location) Hashtbl.t = Hashtbl.create 13 (* break stmt sid -> corresponding loop *) -class loopBreaksVisitor (fd : fundec) = object(self) - inherit nopCilVisitor - method! vstmt s = - (match s.skind with - | Loop (b, loc, eloc, Some continue, Some break) -> Hashtbl.add loopBreaks break.sid loc (* TODO: use eloc? *) - | Loop _ -> failwith "Termination.preprocess: every loop should have a break and continue stmt after prepareCFG" - | _ -> ()); - DoChildren -end - -(* if the given block contains a goto while_break.* we have the termination condition for a loop *) -let exits = function - | { bstmts = [{ skind = Goto (stmt, loc); _ }]; _ } -> Hashtbl.find_option loopBreaks !stmt.sid - | _ -> None (* TODO handle return (need to find out what loop we are in) *) - -let lvals_of_expr = - let rec f a = function - | Const _ | SizeOf _ | SizeOfStr _ | AlignOf _ | AddrOfLabel _ -> a - | Lval l | AddrOf l | StartOf l -> l :: a - | SizeOfE e | AlignOfE e | UnOp (_,e,_) | CastE (_,e) | Imag e | Real e -> f a e - | BinOp (_,e1,e2,_) -> f a e1 @ f a e2 - | Question (c,t,e,_) -> f a c @ f a t @ f a e - in f [] - -let loopVars : (location, lval) Hashtbl.t = Hashtbl.create 13 (* loop location -> lval used for exit *) -class loopVarsVisitor (fd : fundec) = object - inherit nopCilVisitor - method! vstmt s = - let add_exit_cond e loc = - match lvals_of_expr e with - | [lval] when Cilfacade.typeOf e |> isArithmeticType -> Hashtbl.add loopVars loc lval - | _ -> () - in - (match s.skind with - | If (e, tb, fb, loc, eloc) -> Option.map_default (add_exit_cond e) () (exits tb ||? exits fb) - | _ -> ()); - DoChildren -end - -let stripCastsDeep e = - let v = object - inherit nopCilVisitor - method! vexpr e = ChangeTo (stripCasts e) - end - in visitCilExpr v e - -(* keep the enclosing loop for statements *) -let cur_loop = ref None (* current loop *) -let cur_loop' = ref None (* for nested loops *) -let makeVar fd loc name = - let id = name ^ "__" ^ show_location_id loc in - try List.find (fun v -> v.vname = id) fd.slocals - with Not_found -> - let typ = intType in (* TODO the type should be the same as the one of the original loop counter *) - Cilfacade.create_var (makeLocalVar fd id ~init:(SingleInit zero) typ) -let f_assume = Lval (var (emptyFunction "__goblint_assume").svar) -let f_check = Lval (var (emptyFunction "__goblint_check").svar) -class loopInstrVisitor (fd : fundec) = object(self) - inherit nopCilVisitor - method! vstmt s = - (* TODO: use Loop eloc? *) - (match s.skind with - | Loop (_, loc, eloc, _, _) -> - cur_loop' := !cur_loop; - cur_loop := Some loc - | _ -> ()); - let action s = - (* first, restore old cur_loop *) - (match s.skind with - | Loop (_, loc, eloc, _, _) -> - cur_loop := !cur_loop'; - | _ -> ()); - let in_loop () = Option.is_some !cur_loop && Hashtbl.mem loopVars (Option.get !cur_loop) in - match s.skind with - | Loop (b, loc, eloc, Some continue, Some break) when Hashtbl.mem loopVars loc -> - (* find loop var for current loop *) - let x = Hashtbl.find loopVars loc in - (* insert loop counter and diff to loop var *) - let t = var @@ makeVar fd loc "t" in - let d1 = var @@ makeVar fd loc "d1" in - let d2 = var @@ makeVar fd loc "d2" in - (* make init stmts *) - let t_init = mkStmtOneInstr @@ Set (t, zero, loc, eloc) in - let d1_init = mkStmtOneInstr @@ Set (d1, Lval x, loc, eloc) in - let d2_init = mkStmtOneInstr @@ Set (d2, Lval x, loc, eloc) in - (* increment/decrement in every iteration *) - let t_inc = mkStmtOneInstr @@ Set (t, increm (Lval t) 1, loc, eloc) in - let d1_inc = mkStmtOneInstr @@ Set (d1, increm (Lval d1) (-1), loc, eloc) in - let d2_inc = mkStmtOneInstr @@ Set (d2, increm (Lval d2) 1 , loc, eloc) in - let typ = intType in - let e1 = BinOp (Eq, Lval t, BinOp (MinusA, Lval x, Lval d1, typ), typ) in - let e2 = BinOp (Eq, Lval t, BinOp (MinusA, Lval d2, Lval x, typ), typ) in - let inv1 = mkStmtOneInstr @@ Call (None, f_assume, [e1], loc, eloc) in - let inv2 = mkStmtOneInstr @@ Call (None, f_assume, [e2], loc, eloc) in - (match b.bstmts with - | cont :: cond :: ss -> - (* changing succs/preds directly doesn't work -> need to replace whole stmts *) - b.bstmts <- cont :: cond :: inv1 :: inv2 :: d1_inc :: d2_inc :: t_inc :: ss; - let nb = mkBlock [t_init; d1_init; d2_init; mkStmt s.skind] in - s.skind <- Block nb; - | _ -> ()); - s - | Loop (b, loc, eloc, Some continue, Some break) -> - print_endline @@ "WARN: Could not determine loop variable for loop at " ^ CilType.Location.show loc; - s - | _ when Hashtbl.mem loopBreaks s.sid -> (* after a loop, we check that t is bounded/positive (no overflow happened) *) - let loc = Hashtbl.find loopBreaks s.sid in - let t = var @@ makeVar fd loc "t" in - let e3 = BinOp (Ge, Lval t, zero, intType) in - let inv3 = mkStmtOneInstr @@ Call (None, f_check, [e3], loc, locUnknown) in - let nb = mkBlock [mkStmt s.skind; inv3] in - s.skind <- Block nb; - s - | Instr [Set (lval, e, loc, eloc)] when in_loop () -> - (* find loop var for current loop *) - let cur_loop = Option.get !cur_loop in - let x = Hashtbl.find loopVars cur_loop in - if x <> lval then - s - else (* we only care about the loop var *) - let d1 = makeVar fd cur_loop "d1" in - let d2 = makeVar fd cur_loop "d2" in - (match stripCastsDeep e with - | BinOp (op, Lval x', e2, typ) when (op = PlusA || op = MinusA) && x' = x && isArithmeticType typ -> (* TODO x = 1 + x, MinusA! *) - (* increase diff by same expr *) - let d1_inc = mkStmtOneInstr @@ Set (var d1, BinOp (PlusA, Lval (var d1), e2, typ), loc, eloc) in - let d2_inc = mkStmtOneInstr @@ Set (var d2, BinOp (PlusA, Lval (var d2), e2, typ), loc, eloc) in - let nb = mkBlock [d1_inc; d2_inc; mkStmt s.skind] in - s.skind <- Block nb; - s - | _ -> - (* otherwise diff is e - counter *) - let t = makeVar fd cur_loop "t" in - let te = Cilfacade.typeOf e in - let dt1 = mkStmtOneInstr @@ Set (var d1, BinOp (MinusA, Lval x, Lval (var t), te), loc, eloc) in - let dt2 = mkStmtOneInstr @@ Set (var d2, BinOp (MinusA, Lval x, Lval (var t), te), loc, eloc) in - let nb = mkBlock [mkStmt s.skind; dt1; dt2] in - s.skind <- Block nb; - s - ) - | _ -> s - in - ChangeDoChildrenPost (s, action) -end - - -module Spec = -struct - include Analyses.IdentitySpec - - let name () = "term" - module D = TermDomain - module C = TermDomain - - (* queries *) - (*let query ctx (q:Queries.t) : Queries.Result.t =*) - (*match q with*) - (*| Queries.MustTerm loc -> `Bool (D.mem v ctx.local)*) - (*| _ -> Queries.Result.top ()*) - - (* transfer functions *) - - let branch ctx (exp:exp) (tv:bool) : D.t = - ctx.local - (* if the then-block contains a goto while_break.* we have the termination condition for a loop *) - (* match !MyCFG.current_node with *) - (* | Some (MyCFG.Statement({ skind = If (e, tb, fb, loc) })) -> *) - (* let str_exit b = match exits b with Some loc -> string_of_int loc.line | None -> "None" in *) - (* M.debug @@ *) - (* "\nCil-exp: " ^ sprint d_exp e *) - (* (*^ "; Goblint-exp: " ^ sprint d_exp exp*) *) - (* ^ "; Goblint: " ^ sprint Queries.Result.pretty (ctx.ask (Queries.EvalInt exp)) *) - (* ^ "\nCurrent block: " ^ (if tv then "Then" else "Else") *) - (* ^ "\nThen block (exits " ^ str_exit tb ^ "): " ^ sprint d_block tb *) - (* ^ "\nElse block (exits " ^ str_exit fb ^ "): " ^ sprint d_block fb *) - (* ; *) - (* ctx.local *) - (* | _ -> ctx.local *) - - let startstate v = D.bot () - let threadenter ctx lval f args = [D.bot ()] - let exitstate v = D.bot () -end - -class recomputeVisitor (fd : fundec) = object(self) - inherit nopCilVisitor - method! vfunc fd = - computeCFGInfo fd true; - SkipChildren -end - -let _ = - (* Cilfacade.register_preprocess Spec.name (new loopCounterVisitor); *) - Cilfacade.register_preprocess (Spec.name ()) (new loopBreaksVisitor); - Cilfacade.register_preprocess (Spec.name ()) (new loopVarsVisitor); - Cilfacade.register_preprocess (Spec.name ()) (new loopInstrVisitor); - Cilfacade.register_preprocess (Spec.name ()) (new recomputeVisitor); - Hashtbl.clear loopBreaks; (* because the sids are now different *) - Cilfacade.register_preprocess (Spec.name ()) (new loopBreaksVisitor); - MCP.register_analysis (module Spec : MCPSpec) diff --git a/src/analyses/threadAnalysis.ml b/src/analyses/threadAnalysis.ml index d6a93744bc..01c5dd87fa 100644 --- a/src/analyses/threadAnalysis.ml +++ b/src/analyses/threadAnalysis.ml @@ -22,23 +22,34 @@ struct module P = IdentityP (D) (* transfer functions *) - let return ctx (exp:exp option) (f:fundec) : D.t = + let handle_thread_return ctx (exp: exp option) = let tid = ThreadId.get_current (Analyses.ask_of_ctx ctx) in - begin match tid with + match tid with | `Lifted tid -> ctx.sideg tid (false, TS.bot (), not (D.is_empty ctx.local)) | _ -> () - end; + + let return ctx (exp:exp option) _ : D.t = + if ctx.ask Queries.MayBeThreadReturn then + handle_thread_return ctx exp; ctx.local let rec is_not_unique ctx tid = let (rep, parents, _) = ctx.global tid in - let n = TS.cardinal parents in - (* A thread is not unique if it is - * a) repeatedly created, - * b) created in multiple threads, or - * c) created by a thread that is itself multiply created. - * Note that starting threads have empty ancestor sets! *) - rep || n > 1 || n > 0 && is_not_unique ctx (TS.choose parents) + if rep then + true (* repeatedly created *) + else ( + let n = TS.cardinal parents in + if n > 1 then + true (* created in multiple threads *) + else if n > 0 then ( + (* created by single thread *) + let parent = TS.choose parents in + (* created by itself thread-recursively or by a thread that is itself multiply created *) + T.equal tid parent || is_not_unique ctx parent (* equal check needed to avoid infinte self-recursion *) + ) + else + false (* no ancestors, starting thread *) + ) let special ctx (lval: lval option) (f:varinfo) (arglist:exp list) : D.t = let desc = LibraryFunctions.find f in @@ -53,8 +64,12 @@ struct s in match TS.elements (ctx.ask (Queries.EvalThread id)) with - | threads -> List.fold_left join_thread ctx.local threads + | [t] -> join_thread ctx.local t (* single thread *) + | _ -> ctx.local (* if several possible threads are may-joined, none are must-joined *) | exception SetDomain.Unsupported _ -> ctx.local) + | ThreadExit { ret_val } -> + handle_thread_return ctx (Some ret_val); + ctx.local | _ -> ctx.local let query ctx (type a) (q: a Queries.t): a Queries.result = @@ -76,8 +91,14 @@ struct | _ -> Queries.Result.top q let startstate v = D.bot () - let threadenter ctx lval f args = [D.bot ()] - let threadspawn ctx lval f args fctx = + + 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)); + [D.bot ()] + + let threadspawn ctx ~multiple lval f args fctx = let creator = ThreadId.get_current (Analyses.ask_of_ctx ctx) in let tid = ThreadId.get_current_unlift (Analyses.ask_of_ctx fctx) in let repeated = D.mem tid ctx.local in diff --git a/src/analyses/threadEscape.ml b/src/analyses/threadEscape.ml index 8a14f4102e..21a8b69c93 100644 --- a/src/analyses/threadEscape.ml +++ b/src/analyses/threadEscape.ml @@ -4,6 +4,7 @@ open GoblintCil open Analyses module M = Messages +module AD = Queries.AD let has_escaped (ask: Queries.ask) (v: varinfo): bool = assert (not v.vglob); @@ -26,22 +27,30 @@ struct let reachable (ask: Queries.ask) e: D.t = match ask.f (Queries.ReachableFrom e) with - | a when not (Queries.LS.is_top a) -> - let to_extra (v,o) set = D.add v set in - Queries.LS.fold to_extra (Queries.LS.remove (dummyFunDec.svar, `NoOffset) a) (D.empty ()) + | ad when not (Queries.AD.is_top ad) -> + let to_extra addr set = + match addr with + | Queries.AD.Addr.Addr (v,_) -> D.add v set + | _ -> set + in + Queries.AD.fold to_extra ad (D.empty ()) (* Ignore soundness warnings, as invalidation proper will raise them. *) - | a -> - if M.tracing then M.tracel "escape" "reachable %a: %a\n" d_exp e Queries.LS.pretty a; + | ad -> + if M.tracing then M.tracel "escape" "reachable %a: %a\n" d_exp e Queries.AD.pretty ad; D.empty () let mpt (ask: Queries.ask) e: D.t = match ask.f (Queries.MayPointTo e) with - | a when not (Queries.LS.is_top a) -> - let to_extra (v,o) set = D.add v set in - Queries.LS.fold to_extra (Queries.LS.remove (dummyFunDec.svar, `NoOffset) a) (D.empty ()) + | ad when not (AD.is_top ad) -> + let to_extra addr set = + match addr with + | AD.Addr.Addr (v,_) -> D.add v set + | _ -> set + in + AD.fold to_extra (AD.remove UnknownPtr ad) (D.empty ()) (* Ignore soundness warnings, as invalidation proper will raise them. *) - | a -> - if M.tracing then M.tracel "escape" "mpt %a: %a\n" d_exp e Queries.LS.pretty a; + | ad -> + if M.tracing then M.tracel "escape" "mpt %a: %a\n" d_exp e AD.pretty ad; D.empty () let thread_id ctx = @@ -141,10 +150,10 @@ struct let startstate v = D.bot () let exitstate v = D.bot () - let threadenter ctx lval f args = + let threadenter ctx ~multiple lval f args = [D.bot ()] - let threadspawn ctx lval f args fctx = + let threadspawn ctx ~multiple lval f args fctx = D.join ctx.local @@ match args with | [ptc_arg] -> diff --git a/src/analyses/threadFlag.ml b/src/analyses/threadFlag.ml index f2ebf82be1..a751ae074a 100644 --- a/src/analyses/threadFlag.ml +++ b/src/analyses/threadFlag.ml @@ -21,6 +21,8 @@ struct module D = Flag module C = Flag module P = IdentityP (D) + module V = UnitV + module G = BoolDomain.MayBool let name () = "threadflag" @@ -44,6 +46,7 @@ struct match x with | Queries.MustBeSingleThreaded _ -> not (Flag.is_multi ctx.local) (* If this analysis can tell, it is the case since the start *) | Queries.MustBeUniqueThread -> not (Flag.is_not_main ctx.local) + | Queries.IsEverMultiThreaded -> (ctx.global () : bool) (* requires annotation to compile *) (* This used to be in base but also commented out. *) (* | Queries.MayBePublic _ -> Flag.is_multi ctx.local *) | _ -> Queries.Result.top x @@ -58,12 +61,13 @@ struct let access ctx _ = is_currently_multi (Analyses.ask_of_ctx ctx) - let threadenter ctx lval f args = + let threadenter ctx ~multiple lval f args = if not (has_ever_been_multi (Analyses.ask_of_ctx ctx)) then ctx.emit Events.EnterMultiThreaded; [create_tid f] - let threadspawn ctx lval f args fctx = + let threadspawn ctx ~multiple lval f args fctx = + ctx.sideg () true; if not (has_ever_been_multi (Analyses.ask_of_ctx ctx)) then ctx.emit Events.EnterMultiThreaded; D.join ctx.local (Flag.get_main ()) diff --git a/src/analyses/threadId.ml b/src/analyses/threadId.ml index 4acf88a7ef..da2c688ad1 100644 --- a/src/analyses/threadId.ml +++ b/src/analyses/threadId.ml @@ -29,11 +29,30 @@ module Spec = struct include Analyses.IdentitySpec - module N = Lattice.Flat (VNI) (struct let bot_name = "unknown node" let top_name = "unknown node" end) + module N = + struct + include Lattice.Flat (VNI) (struct let bot_name = "unknown node" let top_name = "unknown node" end) + let name () = "wrapper call" + end module TD = Thread.D + module Created = + struct + module Current = + struct + include TD + let name () = "current function" + end + module Callees = + struct + include TD + let name () = "callees" + end + include Lattice.Prod (Current) (Callees) + let name () = "created" + end (** 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) (Lattice.Prod(TD)(TD)) + module D = Lattice.Prod3 (N) (ThreadLifted) (Created) module C = D module P = IdentityP (D) @@ -56,10 +75,10 @@ struct Hashtbl.replace !tids tid (); (N.bot (), `Lifted (tid), (TD.bot (), TD.bot ())) - let create_tid (_, current, (td, _)) ((node, index): Node.t * int option) v = + let create_tid ?(multiple=false) (_, current, (td, _)) ((node, index): Node.t * int option) v = match current with | `Lifted current -> - let+ tid = Thread.threadenter (current, td) node index v in + let+ tid = Thread.threadenter ~multiple (current, td) node index v in if GobConfig.get_bool "dbg.print_tids" then Hashtbl.replace !tids tid (); `Lifted tid @@ -133,15 +152,15 @@ struct | `Lifted node, count -> node, Some count | (`Bot | `Top), _ -> ctx.prev_node, None - let threadenter ctx lval f args:D.t list = + let threadenter ctx ~multiple lval f args:D.t list = let n, i = indexed_node_for_ctx ctx in - let+ tid = create_tid ctx.local (n, i) f in + let+ tid = create_tid ~multiple ctx.local (n, i) f in (`Lifted (f, n, i), tid, (TD.bot (), TD.bot ())) - let threadspawn ctx lval f args fctx = + let threadspawn ctx ~multiple lval f args fctx = let (current_n, current, (td,tdl)) = ctx.local in let v, n, i = match fctx.local with `Lifted vni, _, _ -> vni | _ -> failwith "ThreadId.threadspawn" in - (current_n, current, (Thread.threadspawn td n i v, Thread.threadspawn tdl n i v)) + (current_n, current, (Thread.threadspawn ~multiple td n i v, Thread.threadspawn ~multiple tdl n i v)) type marshal = (Thread.t,unit) Hashtbl.t (* TODO: don't use polymorphic Hashtbl *) let init (m:marshal option): unit = diff --git a/src/analyses/threadJoins.ml b/src/analyses/threadJoins.ml index f2cd36619f..862711073c 100644 --- a/src/analyses/threadJoins.ml +++ b/src/analyses/threadJoins.ml @@ -81,7 +81,7 @@ struct ) | _, _ -> ctx.local - let threadspawn ctx lval f args fctx = + let threadspawn ctx ~multiple lval f args fctx = if D.is_bot ctx.local then ( (* bot is All threads *) M.info ~category:Imprecise "Thread created while ALL threads must-joined, continuing with no threads joined."; D.top () (* top is no threads *) diff --git a/src/analyses/threadReturn.ml b/src/analyses/threadReturn.ml index 470c4ceaa8..0aed06851a 100644 --- a/src/analyses/threadReturn.ml +++ b/src/analyses/threadReturn.ml @@ -28,7 +28,7 @@ struct ctx.local (* keep local as opposed to IdentitySpec *) let startstate v = true - let threadenter ctx lval f args = [true] + let threadenter ctx ~multiple lval f args = [true] let exitstate v = D.top () let query (ctx: (D.t, _, _, _) ctx) (type a) (x: a Queries.t): a Queries.result = diff --git a/src/analyses/tmpSpecial.ml b/src/analyses/tmpSpecial.ml new file mode 100644 index 0000000000..9ed6da7c60 --- /dev/null +++ b/src/analyses/tmpSpecial.ml @@ -0,0 +1,97 @@ +(** Analysis that tracks which variables hold the results of calls to math library functions ([tmpSpecial]). *) + +(** For each equivalence a set of expressions is tracked, that contains the arguments of the corresponding call as well as the Lval it is assigned to, so an equivalence can be removed if one of these expressions may be changed. *) + +module VarEq = VarEq.Spec + +open GoblintCil +open Analyses + +module Spec = +struct + include Analyses.IdentitySpec + + let name () = "tmpSpecial" + module ML = LibraryDesc.MathLifted + module Deps = SetDomain.Reverse (SetDomain.ToppedSet (CilType.Exp) (struct let topname = "All" end)) + module MLDeps = Lattice.Prod (ML) (Deps) + module D = MapDomain.MapBot (Mval.Exp) (MLDeps) + module C = Lattice.Unit + + let invalidate ask exp_w st = + D.filter (fun _ (ml, deps) -> (Deps.for_all (fun arg -> not (VarEq.may_change ask exp_w arg)) deps)) st + + let context _ _ = () + + (* transfer functions *) + let assign ctx (lval:lval) (rval:exp) : D.t = + if M.tracing then M.tracel "tmpSpecial" "assignment of %a\n" d_lval lval; + (* Invalidate all entrys from the map that are possibly written by the assignment *) + invalidate (Analyses.ask_of_ctx ctx) (mkAddrOf lval) ctx.local + + let enter ctx (lval: lval option) (f:fundec) (args:exp list) : (D.t * D.t) list = + (* For now we only track relationships intraprocedurally. *) + [ctx.local, D.bot ()] + + let combine ctx (lval:lval option) fexp (f:fundec) (args:exp list) fc (au:D.t) f_ask : D.t = + (* For now we only track relationships intraprocedurally. *) + D.bot () + + let special ctx (lval: lval option) (f:varinfo) (arglist:exp list) : D.t = + let d = ctx.local in + let ask = Analyses.ask_of_ctx ctx in + + (* Just dbg prints *) + (if M.tracing then + match lval with + | Some lv -> if M.tracing then M.tracel "tmpSpecial" "Special: %s with lval %a\n" f.vname d_lval lv + | _ -> if M.tracing then M.tracel "tmpSpecial" "Special: %s\n" f.vname); + + + let desc = LibraryFunctions.find f in + + (* remove entrys, dependent on lvals that were possibly written by the special function *) + let write_args = LibraryDesc.Accesses.find_kind desc.accs Write arglist in + (* TODO similar to symbLocks->Spec->special: why doesn't invalidate involve any reachable for deep write? *) + let d = List.fold_left (fun d e -> invalidate ask e d) d write_args in + + (* same for lval assignment of the call*) + let d = + match lval with + | Some lv -> invalidate ask (mkAddrOf lv) ctx.local + | None -> d + in + + (* add new math fun desc*) + let d = + match lval, desc.special arglist with + | Some ((Var v, offs) as lv), (Math { fun_args; }) -> + (* only add descriptor, if none of the args is written by the assignment, invalidating the equivalence *) + (* actually it would be necessary to check here, if one of the arguments is written by the call. However this is not the case for any of the math functions and no other functions are covered so far *) + if List.exists (fun arg -> VarEq.may_change ask (mkAddrOf lv) arg) arglist then + d + else + D.add (v, Offset.Exp.of_cil offs) ((ML.lift fun_args, Deps.of_list ((Lval lv)::arglist))) d + | _ -> d + + in + + if M.tracing then M.tracel "tmpSpecial" "Result: %a\n\n" D.pretty d; + d + + + let query ctx (type a) (q: a Queries.t) : a Queries.result = + match q with + | TmpSpecial lv -> let ml = fst (D.find lv ctx.local) in + if ML.is_bot ml then Queries.Result.top q + else ml + | _ -> Queries.Result.top q + + let startstate v = D.bot () + let threadenter ctx ~multiple lval f args = [D.bot ()] + let threadspawn ctx ~multiple lval f args fctx = ctx.local + let exitstate v = D.bot () +end + +let _ = + MCP.register_analysis (module Spec : MCPSpec) diff --git a/src/analyses/tutorials/taint.ml b/src/analyses/tutorials/taint.ml index 3067449e31..a978d0faf4 100644 --- a/src/analyses/tutorials/taint.ml +++ b/src/analyses/tutorials/taint.ml @@ -129,8 +129,8 @@ struct (* You may leave these alone *) let startstate v = D.bot () - let threadenter ctx lval f args = [D.top ()] - let threadspawn ctx lval f args fctx = ctx.local + let threadenter ctx ~multiple lval f args = [D.top ()] + let threadspawn ctx ~multiple lval f args fctx = ctx.local let exitstate v = D.top () end diff --git a/src/analyses/tutorials/unitAnalysis.ml b/src/analyses/tutorials/unitAnalysis.ml index d3b8c69bfd..dc377cdd97 100644 --- a/src/analyses/tutorials/unitAnalysis.ml +++ b/src/analyses/tutorials/unitAnalysis.ml @@ -39,8 +39,8 @@ struct ctx.local let startstate v = D.bot () - let threadenter ctx lval f args = [D.top ()] - let threadspawn ctx lval f args fctx = ctx.local + let threadenter ctx ~multiple lval f args = [D.top ()] + let threadspawn ctx ~multiple lval f args fctx = ctx.local let exitstate v = D.top () end diff --git a/src/analyses/unassumeAnalysis.ml b/src/analyses/unassumeAnalysis.ml index 6b719c57b9..5895f242c9 100644 --- a/src/analyses/unassumeAnalysis.ml +++ b/src/analyses/unassumeAnalysis.ml @@ -111,7 +111,7 @@ struct Locator.ES.iter (fun n -> let fundec = Node.find_fundec n in - match InvariantParser.parse_cil inv_parser ~fundec ~loc inv_cabs with + 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} @@ -157,12 +157,12 @@ struct Locator.ES.iter (fun n -> let fundec = Node.find_fundec n in - match InvariantParser.parse_cil inv_parser ~fundec ~loc pre_cabs with + match InvariantParser.parse_cil inv_parser ~check:false ~fundec ~loc pre_cabs with | Ok pre_exp -> M.debug ~category:Witness ~loc:msgLoc "located precondition to %a: %a" CilType.Fundec.pretty fundec Cil.d_exp pre_exp; FH.add fun_pres fundec pre_exp; - begin match InvariantParser.parse_cil inv_parser ~fundec ~loc inv_cabs with + begin 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; if not (NH.mem pre_invs n) then @@ -200,6 +200,46 @@ struct M.warn ~category:Witness ~loc:msgLoc "couldn't locate invariant: %s" inv in + 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 inv = location_invariant.value in + let msgLoc: M.Location.t = CilLocation loc in + + match Locator.find_opt locator loc with + | Some nodes -> + unassume_nodes_invariant ~loc ~nodes 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 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 + | None -> + M.warn ~category:Witness ~loc:msgLoc "couldn't locate invariant: %s" inv + in + + let validate_invariant (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 + | true, LoopInvariant x -> + unassume_loop_invariant x + | false, (LocationInvariant _ | LoopInvariant _) -> + M.info_noloc ~category:Witness "disabled invariant of type %s" target_type + in + + List.iter validate_invariant invariant_set.content + in + match YamlWitness.entry_type_enabled target_type, entry.entry_type with | true, LocationInvariant x -> unassume_location_invariant x @@ -207,7 +247,9 @@ struct unassume_loop_invariant x | true, PreconditionLoopInvariant x -> unassume_precondition_loop_invariant x - | false, (LocationInvariant _ | LoopInvariant _ | PreconditionLoopInvariant _) -> + | true, InvariantSet x -> + unassume_invariant_set x + | 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 diff --git a/src/analyses/uninit.ml b/src/analyses/uninit.ml index 850bd677bd..8693599a4d 100644 --- a/src/analyses/uninit.ml +++ b/src/analyses/uninit.ml @@ -25,16 +25,19 @@ struct let name () = "uninit" let startstate v : D.t = D.empty () - let threadenter ctx lval f args = [D.empty ()] - let threadspawn ctx lval f args fctx = ctx.local + let threadenter ctx ~multiple lval f args = [D.empty ()] + let threadspawn ctx ~multiple lval f args fctx = ctx.local let exitstate v : D.t = D.empty () - (* TODO: Use AddressDomain for queries *) let access_address (ask: Queries.ask) write lv = match ask.f (Queries.MayPointTo (AddrOf lv)) with - | a when not (Queries.LS.is_top a) -> - let to_extra (v,o) xs = (v, Addr.Offs.of_exp o, write) :: xs in - Queries.LS.fold to_extra a [] + | ad when not (Queries.AD.is_top ad) -> + let to_extra addr xs = + match addr with + | Queries.AD.Addr.Addr (v,o) -> (v, o, write) :: xs + | _ -> xs + in + Queries.AD.fold to_extra ad [] | _ -> M.info ~category:Unsound "Access to unknown address could be global"; [] @@ -165,9 +168,10 @@ struct List.fold_right remove_if_prefix (get_pfx v `NoOffset ofs v.vtype v.vtype) st in match a.f (Queries.MayPointTo (AddrOf lv)) with - | a when Queries.LS.cardinal a = 1 -> begin - let var, ofs = Queries.LS.choose a in - init_vo var (Addr.Offs.of_exp ofs) + | ad when Queries.AD.cardinal ad = 1 -> + begin match Queries.AD.Addr.to_mval (Queries.AD.choose ad) with + | Some (var, ofs) -> init_vo var ofs + | None -> st end | _ -> st @@ -189,21 +193,25 @@ struct let remove_unreachable (ask: Queries.ask) (args: exp list) (st: D.t) : D.t = let reachable = - let do_exp e = + let do_exp e a = match ask.f (Queries.ReachableFrom e) with - | a when not (Queries.LS.is_top a) -> - let to_extra (v,o) xs = AD.of_mval (v, Addr.Offs.of_exp o) :: xs in - Queries.LS.fold to_extra (Queries.LS.remove (dummyFunDec.svar, `NoOffset) a) [] + | ad when not (Queries.AD.is_top ad) -> + ad + |> Queries.AD.filter (function + | Queries.AD.Addr.Addr _ -> true + | _ -> false) + |> Queries.AD.join a (* Ignore soundness warnings, as invalidation proper will raise them. *) - | _ -> [] + | _ -> AD.empty () in - List.concat_map do_exp args + List.fold_right do_exp args (AD.empty ()) in - let add_exploded_struct (one: AD.t) (many: AD.t) : AD.t = - let vars = AD.to_var_may one in - List.fold_right AD.add (List.concat_map to_addrs vars) many + let vars = + reachable + |> AD.to_var_may + |> List.concat_map to_addrs + |> AD.of_list in - let vars = List.fold_right add_exploded_struct reachable (AD.empty ()) in if D.is_top st then D.top () else D.filter (fun x -> AD.mem x vars) st diff --git a/src/analyses/useAfterFree.ml b/src/analyses/useAfterFree.ml index c3aebc985e..a901a8d2e5 100644 --- a/src/analyses/useAfterFree.ml +++ b/src/analyses/useAfterFree.ml @@ -3,22 +3,27 @@ open GoblintCil open Analyses open MessageCategory +open AnalysisStateUtil -module ToppedVarInfoSet = SetDomain.ToppedSet(CilType.Varinfo)(struct let topname = "All Heap Variables" end) -module ThreadIdSet = SetDomain.Make(ThreadIdDomain.ThreadLifted) +module AllocaVars = SetDomain.ToppedSet(CilType.Varinfo)(struct let topname = "All alloca() Variables" end) +module HeapVars = SetDomain.ToppedSet(CilType.Varinfo)(struct let topname = "All Heap Variables" end) + +(* Heap vars created by alloca() and deallocated at function exit * Heap vars deallocated by free() *) +module StackAndHeapVars = Lattice.Prod(AllocaVars)(HeapVars) + +module ThreadIdToJoinedThreadsMap = MapDomain.MapBot(ThreadIdDomain.ThreadLifted)(ConcDomain.MustThreadSet) module Spec : Analyses.MCPSpec = struct - include Analyses.DefaultSpec + include Analyses.IdentitySpec let name () = "useAfterFree" - module D = ToppedVarInfoSet + module D = StackAndHeapVars module C = Lattice.Unit - module G = ThreadIdSet + module G = ThreadIdToJoinedThreadsMap module V = VarinfoV - (** TODO: Try out later in benchmarks to see how we perform with and without context-sensititivty *) let context _ _ = () @@ -27,78 +32,103 @@ struct let get_current_threadid ctx = ctx.ask Queries.CurrentThreadId - let warn_for_multi_threaded_access ctx (heap_var:varinfo) behavior cwe_number = + let get_joined_threads ctx = + ctx.ask Queries.MustJoinedThreads + + let warn_for_multi_threaded_access ctx ?(is_double_free = false) (heap_var:varinfo) behavior cwe_number = let freeing_threads = ctx.global heap_var in (* If we're single-threaded or there are no threads freeing the memory, we have nothing to WARN about *) - if ctx.ask (Queries.MustBeSingleThreaded { since_start = true }) || ThreadIdSet.is_empty freeing_threads then () + if ctx.ask (Queries.MustBeSingleThreaded { since_start = true }) || G.is_empty freeing_threads then () else begin - let possibly_started current = function + let possibly_started current tid joined_threads = + match tid with | `Lifted tid -> - let threads = ctx.ask Queries.CreatedThreads in - let not_started = MHP.definitely_not_started (current, threads) tid in + let created_threads = ctx.ask Queries.CreatedThreads in + let not_started = MHP.definitely_not_started (current, created_threads) tid in let possibly_started = not not_started in - possibly_started + (* If [current] is possibly running together with [tid], but is also joined before the free() in [tid], then no need to WARN *) + let current_joined_before_free = ConcDomain.MustThreadSet.mem current joined_threads in + possibly_started && not current_joined_before_free | `Top -> true | `Bot -> false in - let equal_current current = function + let equal_current current tid joined_threads = + match tid with | `Lifted tid -> ThreadId.Thread.equal current tid | `Top -> true | `Bot -> false in + let bug_name = if is_double_free then "Double Free" else "Use After Free" in match get_current_threadid ctx with | `Lifted current -> - let possibly_started = ThreadIdSet.exists (possibly_started current) freeing_threads in - if possibly_started then - M.warn ~category:(Behavior behavior) ~tags:[CWE cwe_number] "There's a thread that's been started in parallel with the memory-freeing threads for heap variable %a. Use-After-Free might occur" CilType.Varinfo.pretty heap_var + let possibly_started = G.exists (possibly_started current) freeing_threads in + if possibly_started then begin + if is_double_free then set_mem_safety_flag InvalidFree else set_mem_safety_flag InvalidDeref; + M.warn ~category:(Behavior behavior) ~tags:[CWE cwe_number] "There's a thread that's been started in parallel with the memory-freeing threads for heap variable %a. %s might occur" CilType.Varinfo.pretty heap_var bug_name + end else begin let current_is_unique = ThreadId.Thread.is_unique current in - let any_equal_current threads = ThreadIdSet.exists (equal_current current) threads in - if not current_is_unique && any_equal_current freeing_threads then - M.warn ~category:(Behavior behavior) ~tags:[CWE cwe_number] "Current thread is not unique and a Use-After-Free might occur for heap variable %a" CilType.Varinfo.pretty heap_var - else if D.mem heap_var ctx.local then - M.warn ~category:(Behavior behavior) ~tags:[CWE cwe_number] "Use-After-Free might occur in current unique thread %a for heap variable %a" ThreadIdDomain.FlagConfiguredTID.pretty current CilType.Varinfo.pretty heap_var + let any_equal_current threads = G.exists (equal_current current) threads in + if not current_is_unique && any_equal_current freeing_threads then begin + if is_double_free then set_mem_safety_flag InvalidFree else set_mem_safety_flag InvalidDeref; + M.warn ~category:(Behavior behavior) ~tags:[CWE cwe_number] "Current thread is not unique and a %s might occur for heap variable %a" bug_name CilType.Varinfo.pretty heap_var + end + else if HeapVars.mem heap_var (snd ctx.local) then begin + if is_double_free then set_mem_safety_flag InvalidFree else set_mem_safety_flag InvalidDeref; + M.warn ~category:(Behavior behavior) ~tags:[CWE cwe_number] "%s might occur in current unique thread %a for heap variable %a" bug_name ThreadIdDomain.FlagConfiguredTID.pretty current CilType.Varinfo.pretty heap_var + end end | `Top -> - M.warn ~category:(Behavior behavior) ~tags:[CWE cwe_number] "CurrentThreadId is top. A Use-After-Free might occur for heap variable %a" CilType.Varinfo.pretty heap_var + if is_double_free then set_mem_safety_flag InvalidFree else set_mem_safety_flag InvalidDeref; + M.warn ~category:(Behavior behavior) ~tags:[CWE cwe_number] "CurrentThreadId is top. %s might occur for heap variable %a" bug_name CilType.Varinfo.pretty heap_var | `Bot -> M.warn ~category:MessageCategory.Analyzer "CurrentThreadId is bottom" end - let rec warn_lval_might_contain_freed ?(is_double_free = false) (transfer_fn_name:string) ctx (lval:lval) = - let state = ctx.local in - let undefined_behavior = if is_double_free then Undefined DoubleFree else Undefined UseAfterFree in - let cwe_number = if is_double_free then 415 else 416 in - let rec offset_might_contain_freed offset = - match offset with - | NoOffset -> () - | Field (f, o) -> offset_might_contain_freed o - | Index (e, o) -> warn_exp_might_contain_freed transfer_fn_name ctx e; offset_might_contain_freed o - in - let (lval_host, o) = lval in offset_might_contain_freed o; (* Check the lval's offset *) - let lval_to_query = - match lval_host with - | Var _ -> Lval lval - | Mem _ -> mkAddrOf lval (* Take the lval's address if its lhost is of the form *p, where p is a ptr *) - in - match ctx.ask (Queries.MayPointTo lval_to_query) with - | a when not (Queries.LS.is_top a) && not (Queries.LS.mem (dummyFunDec.svar, `NoOffset) a) -> - let warn_for_heap_var var = - if D.mem var state then - M.warn ~category:(Behavior undefined_behavior) ~tags:[CWE cwe_number] "lval (%s) in \"%s\" points to a maybe freed memory region" var.vname transfer_fn_name + let rec warn_lval_might_contain_freed ?(is_implicitly_derefed = false) ?(is_double_free = false) (transfer_fn_name:string) ctx (lval:lval) = + match is_implicitly_derefed, is_double_free, lval with + (* If we're not checking for a double-free and there's no deref happening, then there's no need to check for an invalid deref or an invalid free *) + | false, false, (Var _, NoOffset) -> () + | _ -> + let state = ctx.local in + let undefined_behavior = if is_double_free then Undefined DoubleFree else Undefined UseAfterFree in + let cwe_number = if is_double_free then 415 else 416 in + let rec offset_might_contain_freed offset = + match offset with + | NoOffset -> () + | Field (f, o) -> offset_might_contain_freed o + | Index (e, o) -> warn_exp_might_contain_freed transfer_fn_name ctx e; offset_might_contain_freed o in - let pointed_to_heap_vars = - Queries.LS.elements a - |> List.map fst - |> List.filter (fun var -> ctx.ask (Queries.IsHeapVar var)) + let (lval_host, o) = lval in offset_might_contain_freed o; (* Check the lval's offset *) + let lval_to_query = + match lval_host with + | Var _ -> Lval lval + | Mem _ -> mkAddrOf lval (* Take the lval's address if its lhost is of the form *p, where p is a ptr *) in - List.iter warn_for_heap_var pointed_to_heap_vars; (* Warn for all heap vars that the lval possibly points to *) - (* Warn for a potential multi-threaded UAF for all heap vars that the lval possibly points to *) - List.iter (fun heap_var -> warn_for_multi_threaded_access ctx heap_var undefined_behavior cwe_number) pointed_to_heap_vars - | _ -> () + begin match ctx.ask (Queries.MayPointTo lval_to_query) with + | ad when not (Queries.AD.is_top ad) -> + let warn_for_heap_var v = + if HeapVars.mem v (snd state) then begin + if is_double_free then set_mem_safety_flag InvalidFree else set_mem_safety_flag InvalidDeref; + M.warn ~category:(Behavior undefined_behavior) ~tags:[CWE cwe_number] "lval (%s) in \"%s\" points to a maybe freed memory region" v.vname transfer_fn_name + end + in + let pointed_to_heap_vars = + Queries.AD.fold (fun addr vars -> + match addr with + | Queries.AD.Addr.Addr (v,_) when ctx.ask (Queries.IsAllocVar v) -> v :: vars + | _ -> vars + ) ad [] + in + (* Warn for all heap vars that the lval possibly points to *) + List.iter warn_for_heap_var pointed_to_heap_vars; + (* Warn for a potential multi-threaded UAF for all heap vars that the lval possibly points to *) + List.iter (fun heap_var -> warn_for_multi_threaded_access ctx ~is_double_free heap_var undefined_behavior cwe_number) pointed_to_heap_vars + | _ -> () + end - and warn_exp_might_contain_freed ?(is_double_free = false) (transfer_fn_name:string) ctx (exp:exp) = + and warn_exp_might_contain_freed ?(is_implicitly_derefed = false) ?(is_double_free = false) (transfer_fn_name:string) ctx (exp:exp) = match exp with (* Base recursion cases *) | Const _ @@ -112,22 +142,26 @@ struct | SizeOfE e | AlignOfE e | UnOp (_, e, _) - | CastE (_, e) -> warn_exp_might_contain_freed ~is_double_free transfer_fn_name ctx e + | CastE (_, e) -> warn_exp_might_contain_freed ~is_implicitly_derefed ~is_double_free transfer_fn_name ctx e | BinOp (_, e1, e2, _) -> - warn_exp_might_contain_freed ~is_double_free transfer_fn_name ctx e1; - warn_exp_might_contain_freed ~is_double_free transfer_fn_name ctx e2 + warn_exp_might_contain_freed ~is_implicitly_derefed ~is_double_free transfer_fn_name ctx e1; + warn_exp_might_contain_freed ~is_implicitly_derefed ~is_double_free transfer_fn_name ctx e2 | Question (e1, e2, e3, _) -> - warn_exp_might_contain_freed ~is_double_free transfer_fn_name ctx e1; - warn_exp_might_contain_freed ~is_double_free transfer_fn_name ctx e2; - warn_exp_might_contain_freed ~is_double_free transfer_fn_name ctx e3 + warn_exp_might_contain_freed ~is_implicitly_derefed ~is_double_free transfer_fn_name ctx e1; + warn_exp_might_contain_freed ~is_implicitly_derefed ~is_double_free transfer_fn_name ctx e2; + warn_exp_might_contain_freed ~is_implicitly_derefed ~is_double_free transfer_fn_name ctx e3 (* Lval cases (need [warn_lval_might_contain_freed] for them) *) | Lval lval | StartOf lval - | AddrOf lval -> warn_lval_might_contain_freed ~is_double_free transfer_fn_name ctx lval + | AddrOf lval -> warn_lval_might_contain_freed ~is_implicitly_derefed ~is_double_free transfer_fn_name ctx lval - let side_effect_mem_free ctx freed_heap_vars threadid = - let threadid = G.singleton threadid in - D.iter (fun var -> ctx.sideg var threadid) freed_heap_vars + let side_effect_mem_free ctx freed_heap_vars threadid joined_threads = + let side_effect_globals_to_heap_var heap_var = + let current_globals = ctx.global heap_var in + let globals_to_side_effect = G.add threadid joined_threads current_globals in + ctx.sideg heap_var globals_to_side_effect + in + HeapVars.iter side_effect_globals_to_heap_var freed_heap_vars (* TRANSFER FUNCTIONS *) @@ -141,9 +175,6 @@ struct warn_exp_might_contain_freed "branch" ctx exp; ctx.local - let body ctx (f:fundec) : D.t = - ctx.local - let return ctx (exp:exp option) (f:fundec) : D.t = Option.iter (fun x -> warn_exp_might_contain_freed "return" ctx x) exp; ctx.local @@ -151,21 +182,19 @@ struct let enter ctx (lval:lval option) (f:fundec) (args:exp list) : (D.t * D.t) list = let caller_state = ctx.local in List.iter (fun arg -> warn_exp_might_contain_freed "enter" ctx arg) args; - if D.is_empty caller_state then - [caller_state, caller_state] - else ( - let reachable_from_args = List.fold_left (fun acc arg -> Queries.LS.join acc (ctx.ask (ReachableFrom arg))) (Queries.LS.empty ()) args in - if Queries.LS.is_top reachable_from_args || D.is_top caller_state then - [caller_state, caller_state] - else - let reachable_vars = List.map fst (Queries.LS.elements reachable_from_args) in - let callee_state = D.filter (fun var -> List.mem var reachable_vars) caller_state in - [caller_state, callee_state] - ) + (* TODO: The 2nd component of the callee state needs to contain only the heap vars from the caller state which are reachable from: *) + (* * Global program variables *) + (* * The callee arguments *) + [caller_state, (AllocaVars.empty (), snd caller_state)] let combine_env ctx (lval:lval option) fexp (f:fundec) (args:exp list) fc (callee_local:D.t) (f_ask:Queries.ask) : D.t = - let caller_state = ctx.local in - D.join caller_state callee_local + let (caller_stack_state, caller_heap_state) = ctx.local in + let callee_stack_state = fst callee_local in + let callee_heap_state = snd callee_local in + (* Put all alloca()-vars together with all freed() vars in the caller's second component *) + (* Don't change caller's first component => caller hasn't exited yet *) + let callee_combined_state = HeapVars.join callee_stack_state callee_heap_state in + (caller_stack_state, HeapVars.join caller_heap_state callee_combined_state) let combine_assign ctx (lval:lval option) fexp (f:fundec) (args:exp list) fc (callee_local:D.t) (f_ask: Queries.ask): D.t = Option.iter (fun x -> warn_lval_might_contain_freed "enter" ctx x) lval; @@ -173,33 +202,45 @@ struct let special ctx (lval:lval option) (f:varinfo) (arglist:exp list) : D.t = let state = ctx.local in - Option.iter (fun x -> warn_lval_might_contain_freed ("special: " ^ f.vname) ctx x) lval; - List.iter (fun arg -> warn_exp_might_contain_freed ~is_double_free:(f.vname = "free") ("special: " ^ f.vname) ctx arg) arglist; let desc = LibraryFunctions.find f in + let is_arg_implicitly_derefed arg = + let read_shallow_args = LibraryDesc.Accesses.find desc.accs { kind = Read; deep = false } arglist in + let read_deep_args = LibraryDesc.Accesses.find desc.accs { kind = Read; deep = true } arglist in + let write_shallow_args = LibraryDesc.Accesses.find desc.accs { kind = Write; deep = false } arglist in + let write_deep_args = LibraryDesc.Accesses.find desc.accs { kind = Write; deep = true } arglist in + List.mem arg read_shallow_args || List.mem arg read_deep_args || List.mem arg write_shallow_args || List.mem arg write_deep_args + in + Option.iter (fun x -> warn_lval_might_contain_freed ("special: " ^ f.vname) ctx x) lval; + List.iter (fun arg -> warn_exp_might_contain_freed ~is_implicitly_derefed:(is_arg_implicitly_derefed arg) ~is_double_free:(match desc.special arglist with Free _ -> true | _ -> false) ("special: " ^ f.vname) ctx arg) arglist; match desc.special arglist with | Free ptr -> begin match ctx.ask (Queries.MayPointTo ptr) with - | a when not (Queries.LS.is_top a) && not (Queries.LS.mem (dummyFunDec.svar, `NoOffset) a) -> + | ad when not (Queries.AD.is_top ad) -> let pointed_to_heap_vars = - Queries.LS.elements a - |> List.map fst - |> List.filter (fun var -> ctx.ask (Queries.IsHeapVar var)) - |> D.of_list + Queries.AD.fold (fun addr state -> + match addr with + | Queries.AD.Addr.Addr (var,_) when ctx.ask (Queries.IsAllocVar var) && ctx.ask (Queries.IsHeapVar var) -> HeapVars.add var state + | _ -> state + ) ad (HeapVars.empty ()) in (* Side-effect the tid that's freeing all the heap vars collected here *) - side_effect_mem_free ctx pointed_to_heap_vars (get_current_threadid ctx); - D.join state (pointed_to_heap_vars) (* Add all heap vars, which ptr points to, to the state *) + side_effect_mem_free ctx pointed_to_heap_vars (get_current_threadid ctx) (get_joined_threads ctx); + (* Add all heap vars, which ptr points to, to the state *) + (fst state, HeapVars.join (snd state) pointed_to_heap_vars) + | _ -> state + end + | Alloca _ -> + (* Create fresh heap var for the alloca() call *) + begin match ctx.ask (Queries.AllocVar {on_stack = true}) with + | `Lifted v -> (AllocaVars.add v (fst state), snd state) | _ -> state end | _ -> state - let threadenter ctx lval f args = [ctx.local] - let threadspawn ctx lval f args fctx = ctx.local - let startstate v = D.bot () let exitstate v = D.top () end let _ = - MCP.register_analysis (module Spec : MCPSpec) \ No newline at end of file + MCP.register_analysis (module Spec : MCPSpec) diff --git a/src/analyses/varEq.ml b/src/analyses/varEq.ml index 99307d5d37..30b36404af 100644 --- a/src/analyses/varEq.ml +++ b/src/analyses/varEq.ml @@ -43,8 +43,8 @@ struct let name () = "var_eq" let startstate v = D.top () - let threadenter ctx lval f args = [D.top ()] - let threadspawn ctx lval f args fctx = ctx.local + let threadenter ctx ~multiple lval f args = [D.top ()] + let threadspawn ctx ~multiple lval f args fctx = ctx.local let exitstate v = D.top () let typ_equal = CilType.Typ.equal (* TODO: Used to have equality checking, which ignores attributes. Is that needed? *) @@ -176,7 +176,7 @@ struct let may_change (ask: Queries.ask) (b:exp) (a:exp) : bool = (*b should be an address of something that changes*) let pt e = ask.f (Queries.MayPointTo e) in - let bls = pt b in + let bad = pt b in let bt = match unrollTypeDeep (Cilfacade.typeOf b) with | TPtr (t,_) -> t @@ -208,26 +208,26 @@ struct | at -> at in bt = voidType || (isIntegralType at && isIntegralType bt) || (deref && typ_equal (TPtr (at,[]) ) bt) || typ_equal at bt || - match a with - | Const _ - | SizeOf _ - | SizeOfE _ - | SizeOfStr _ - | AlignOf _ - | AlignOfE _ - | AddrOfLabel _ -> false (* TODO: some may contain exps? *) - | UnOp (_,e,_) - | Real e - | Imag e -> type_may_change_t deref e - | BinOp (_,e1,e2,_) -> type_may_change_t deref e1 || type_may_change_t deref e2 - | Lval (Var _,o) - | AddrOf (Var _,o) - | StartOf (Var _,o) -> may_change_t_offset o - | Lval (Mem e,o) -> may_change_t_offset o || type_may_change_t true e - | AddrOf (Mem e,o) -> may_change_t_offset o || type_may_change_t false e - | StartOf (Mem e,o) -> may_change_t_offset o || type_may_change_t false e - | CastE (t,e) -> type_may_change_t deref e - | Question (b, t, f, _) -> type_may_change_t deref b || type_may_change_t deref t || type_may_change_t deref f + match a with + | Const _ + | SizeOf _ + | SizeOfE _ + | SizeOfStr _ + | AlignOf _ + | AlignOfE _ + | AddrOfLabel _ -> false (* TODO: some may contain exps? *) + | UnOp (_,e,_) + | Real e + | Imag e -> type_may_change_t deref e + | BinOp (_,e1,e2,_) -> type_may_change_t deref e1 || type_may_change_t deref e2 + | Lval (Var _,o) + | AddrOf (Var _,o) + | StartOf (Var _,o) -> may_change_t_offset o + | Lval (Mem e,o) -> may_change_t_offset o || type_may_change_t true e + | AddrOf (Mem e,o) -> may_change_t_offset o || type_may_change_t false e + | StartOf (Mem e,o) -> may_change_t_offset o || type_may_change_t false e + | CastE (t,e) -> type_may_change_t deref e + | Question (b, t, f, _) -> type_may_change_t deref b || type_may_change_t deref t || type_may_change_t deref f and lval_may_change_pt a bl : bool = let rec may_change_pt_offset o = @@ -247,7 +247,7 @@ struct | CastE (t,e) -> addrOfExp e | _ -> None in - let lval_is_not_disjoint (v,o) als = + let lval_is_not_disjoint (v,o) aad = let rec oleq o s = match o, s with | `NoOffset, _ -> true @@ -255,18 +255,21 @@ struct | `Index (i1,o), `Index (i2,s) when exp_equal i1 i2 -> oleq o s | _ -> false in - if Queries.LS.is_top als + if Queries.AD.is_top aad then false - else Queries.LS.exists (fun (u,s) -> CilType.Varinfo.equal v u && oleq o s) als + else Queries.AD.exists (function + | Addr (u,s) -> CilType.Varinfo.equal v u && oleq o (Addr.Offs.to_exp s) (* TODO: avoid conversion? *) + | _ -> false + ) aad in - let (als, test) = + let (aad, test) = match addrOfExp a with - | None -> (Queries.LS.bot (), false) + | None -> (Queries.AD.bot (), false) | Some e -> - let als = pt e in - (als, lval_is_not_disjoint bl als) + let aad = pt e in + (aad, lval_is_not_disjoint bl aad) in - if (Queries.LS.is_top als) || Queries.LS.mem (dummyFunDec.svar, `NoOffset) als + if Queries.AD.is_top aad then type_may_change_apt a else test || match a with @@ -291,10 +294,13 @@ struct | Question (b, t, f, _) -> lval_may_change_pt b bl || lval_may_change_pt t bl || lval_may_change_pt f bl in let r = - if Cil.isConstant b then false - else if Queries.LS.is_top bls || Queries.LS.mem (dummyFunDec.svar, `NoOffset) bls + if Cil.isConstant b || Cil.isConstant a then false + else if Queries.AD.is_top bad then ((*Messages.warn ~category:Analyzer "No PT-set: switching to types ";*) type_may_change_apt a ) - else Queries.LS.exists (lval_may_change_pt a) bls + else Queries.AD.exists (function + | Addr (v,o) -> lval_may_change_pt a (v, Addr.Offs.to_exp o) (* TODO: avoid conversion? *) + | _ -> false + ) bad in (* if r then (Messages.warn ~category:Analyzer ~msg:("Kill " ^sprint 80 (Exp.pretty () a)^" because of "^sprint 80 (Exp.pretty () b)) (); r) @@ -339,8 +345,11 @@ struct Some (v.vglob || (ask.f (Queries.IsMultiple v) || BaseUtil.is_global ask v)) | Lval (Mem e, _) -> begin match ask.f (Queries.MayPointTo e) with - | ls when not (Queries.LS.is_top ls) && not (Queries.LS.mem (dummyFunDec.svar, `NoOffset) ls) -> - Some (Queries.LS.exists (fun (v, _) -> is_global_var ask (Lval (var v)) = Some true) ls) + | ad when not (Queries.AD.is_top ad) -> + Some (Queries.AD.exists (function + | Addr (v,_) -> is_global_var ask (Lval (var v)) = Some true + | _ -> false + ) ad) | _ -> Some true end | CastE (t,e) -> is_global_var ask e @@ -380,17 +389,11 @@ struct (* Give the set of reachables from argument. *) let reachables ~deep (ask: Queries.ask) es = let reachable e st = - match st with - | None -> None - | Some st -> - let q = if deep then Queries.ReachableFrom e else Queries.MayPointTo e in - let vs = ask.f q in - if Queries.LS.is_top vs then - None - else - Some (Queries.LS.join vs st) + let q = if deep then Queries.ReachableFrom e else Queries.MayPointTo e in + let ad = ask.f q in + Queries.AD.join ad st in - List.fold_right reachable es (Some (Queries.LS.empty ())) + List.fold_right reachable es (Queries.AD.empty ()) (* Probably ok as is. *) @@ -434,10 +437,14 @@ struct let d_local = (* if we are multithreaded, we run the risk, that some mutex protected variables got unlocked, so in this case caller state goes to top TODO: !!Unsound, this analysis does not handle this case -> regtest 63 08!! *) - if Queries.LS.is_top tainted || not (ctx.ask (Queries.MustBeSingleThreaded {since_start = true})) then + if Queries.AD.is_top tainted || not (ctx.ask (Queries.MustBeSingleThreaded {since_start = true})) then D.top () else - let taint_exp = Queries.ES.of_list (List.map Mval.Exp.to_cil_exp (Queries.LS.elements tainted)) in + let taint_exp = + Queries.AD.to_mval tainted + |> List.map Addr.Mval.to_cil_exp + |> Queries.ES.of_list + in D.filter (fun exp -> not (Queries.ES.mem exp taint_exp)) ctx.local in let d = D.meet au d_local in @@ -451,15 +458,17 @@ struct | None -> ctx.local let remove_reachable ~deep ask es st = - match reachables ~deep ask es with - | None -> D.top () - | Some rs -> - (* Prior to https://github.com/goblint/analyzer/pull/694 checks were done "in the other direction": - each expression in st was checked for reachability from es/rs using very conservative but also unsound reachable_from. - It is unknown, why that was necessary. *) - Queries.LS.fold (fun lval st -> - remove ask (Mval.Exp.to_cil lval) st - ) rs st + let rs = reachables ~deep ask es in + if M.tracing then M.tracel "var_eq" "remove_reachable %a: %a\n" (Pretty.d_list ", " d_exp) es AD.pretty rs; + (* Prior to https://github.com/goblint/analyzer/pull/694 checks were done "in the other direction": + each expression in st was checked for reachability from es/rs using very conservative but also unsound reachable_from. + It is unknown, why that was necessary. *) + Queries.AD.fold (fun addr st -> + match addr with + | Queries.AD.Addr.Addr mval -> remove ask (ValueDomain.Mval.to_cil mval) st + | UnknownPtr -> D.top () + | _ -> st + ) rs st let unknown_fn ctx lval f args = let desc = LF.find f in @@ -489,8 +498,8 @@ struct end | ThreadCreate { arg; _ } -> begin match D.is_bot ctx.local with - | true -> raise Analyses.Deadcode - | false -> remove_reachable ~deep:true (Analyses.ask_of_ctx ctx) [arg] ctx.local + | true -> raise Analyses.Deadcode + | false -> remove_reachable ~deep:true (Analyses.ask_of_ctx ctx) [arg] ctx.local end | _ -> unknown_fn ctx lval f args (* query stuff *) diff --git a/src/analyses/vla.ml b/src/analyses/vla.ml index 865f22b20a..665612aa99 100644 --- a/src/analyses/vla.ml +++ b/src/analyses/vla.ml @@ -33,7 +33,7 @@ struct ctx.local || Cilfacade.isVLAType v.vtype let startstate v = D.bot () - let threadenter ctx lval f args = [D.top ()] + let threadenter ctx ~multiple lval f args = [D.top ()] let exitstate v = D.top () end diff --git a/src/analyses/wrapperFunctionAnalysis.ml b/src/analyses/wrapperFunctionAnalysis.ml index d9bbdb6197..2e068329ea 100644 --- a/src/analyses/wrapperFunctionAnalysis.ml +++ b/src/analyses/wrapperFunctionAnalysis.ml @@ -33,11 +33,20 @@ struct Introduce a function for this to keep things consistent. *) let node_for_ctx ctx = ctx.prev_node + module NodeFlatLattice = + struct + include NodeFlatLattice + let name () = "wrapper call" + end + module UniqueCount = UniqueCount (* Map for counting function call node visits up to n (of the current thread). *) module UniqueCallCounter = - MapDomain.MapBot_LiftTop(NodeFlatLattice)(UniqueCount) + struct + include MapDomain.MapBot_LiftTop(NodeFlatLattice)(UniqueCount) + let name () = "unique calls" + end (* Increase counter for given node. If it does not exist yet, create it. *) let add_unique_call counter node = @@ -87,7 +96,7 @@ struct let startstate v = D.bot () - let threadenter ctx lval f args = + let threadenter ctx ~multiple lval f args = (* The new thread receives a fresh counter *) [D.bot ()] @@ -133,7 +142,7 @@ module MallocWrapper : MCPSpec = struct let query (ctx: (D.t, G.t, C.t, V.t) ctx) (type a) (q: a Q.t): a Q.result = let wrapper_node, counter = ctx.local in match q with - | Q.HeapVar -> + | Q.AllocVar {on_stack = on_stack} -> let node = match wrapper_node with | `Lifted wrapper_node -> wrapper_node | _ -> node_for_ctx ctx @@ -141,8 +150,11 @@ module MallocWrapper : MCPSpec = struct let count = UniqueCallCounter.find (`Lifted node) counter in let var = NodeVarinfoMap.to_varinfo (ctx.ask Q.CurrentThreadId, node, count) in var.vdecl <- UpdateCil.getLoc node; (* TODO: does this do anything bad for incremental? *) + if on_stack then var.vattr <- addAttribute (Attr ("stack_alloca", [])) var.vattr; (* If the call was for stack allocation, add an attr to mark the heap var *) `Lifted var | Q.IsHeapVar v -> + NodeVarinfoMap.mem_varinfo v && not @@ hasAttribute "stack_alloca" v.vattr + | Q.IsAllocVar v -> NodeVarinfoMap.mem_varinfo v | Q.IsMultiple v -> begin match NodeVarinfoMap.from_varinfo v with diff --git a/src/autoTune.ml b/src/autoTune.ml index a267c3bf9b..3cda36a302 100644 --- a/src/autoTune.ml +++ b/src/autoTune.ml @@ -99,7 +99,9 @@ let rec setCongruenceRecursive fd depth neigbourFunction = FunctionSet.iter (fun vinfo -> print_endline (" " ^ vinfo.vname); - setCongruenceRecursive (Cilfacade.find_varinfo_fundec vinfo) (depth -1) neigbourFunction + match Cilfacade.find_varinfo_fundec vinfo with + | fd -> setCongruenceRecursive fd (depth -1) 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")) @@ -180,11 +182,11 @@ let enableAnalyses anas = List.iter (GobConfig.set_auto "ana.activated[+]") anas (*If only one thread is used in the program, we can disable most thread analyses*) -(*The exceptions are analyses that are depended on by others: base -> mutex -> mutexEvents, access*) +(*The exceptions are analyses that are depended on by others: base -> mutex -> mutexEvents, access; termination -> threadflag *) (*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"] +let notNeccessaryThreadAnalyses = ["race"; "deadlock"; "maylocks"; "symb_locks"; "thread"; "threadid"; "threadJoins"; "threadreturn"; "mhp"; "region"] let reduceThreadAnalyses () = let isThreadCreate = function | LibraryDesc.ThreadCreate _ -> true @@ -198,7 +200,7 @@ let reduceThreadAnalyses () = (* 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"; "modifiedSinceLongjmp"; "poisonVariables"; "expsplit"; "vla"] +let longjmpAnalyses = ["activeLongjmp"; "activeSetjmp"; "taintPartialContexts"; "modifiedSinceSetjmp"; "poisonVariables"; "expsplit"; "vla"] let activateLongjmpAnalysesWhenRequired () = let isLongjmp = function @@ -210,8 +212,50 @@ let activateLongjmpAnalysesWhenRequired () = enableAnalyses longjmpAnalyses; ) -let focusOnSpecification () = - match Svcomp.Specification.of_option () with +let focusOnMemSafetySpecification (spec: Svcomp.Specification.t) = + match spec with + | ValidFree -> (* Enable the useAfterFree analysis *) + let uafAna = ["useAfterFree"] in + print_endline @@ "Specification: ValidFree -> enabling useAfterFree analysis \"" ^ (String.concat ", " uafAna) ^ "\""; + enableAnalyses uafAna + | ValidDeref -> (* Enable the memOutOfBounds analysis *) + let memOobAna = ["memOutOfBounds"] in + set_bool "ana.arrayoob" true; + print_endline "Setting \"cil.addNestedScopeAttr\" to true"; + set_bool "cil.addNestedScopeAttr" true; + print_endline @@ "Specification: ValidDeref -> enabling memOutOfBounds analysis \"" ^ (String.concat ", " memOobAna) ^ "\""; + enableAnalyses memOobAna; + | ValidMemtrack + | ValidMemcleanup -> (* Enable the memLeak analysis *) + let memLeakAna = ["memLeak"] in + if (get_int "ana.malloc.unique_address_count") < 1 then ( + print_endline "Setting \"ana.malloc.unique_address_count\" to 5"; + set_int "ana.malloc.unique_address_count" 5; + ); + print_endline @@ "Specification: ValidMemtrack and ValidMemcleanup -> enabling memLeak analysis \"" ^ (String.concat ", " memLeakAna) ^ "\""; + enableAnalyses memLeakAna + | _ -> () + +let focusOnMemSafetySpecification () = + List.iter focusOnMemSafetySpecification (Svcomp.Specification.of_option ()) + +let focusOnTermination (spec: Svcomp.Specification.t) = + match spec with + | Termination -> + let terminationAnas = ["termination"; "threadflag"; "apron"] in + print_endline @@ "Specification: Termination -> enabling termination analyses \"" ^ (String.concat ", " terminationAnas) ^ "\""; + enableAnalyses terminationAnas; + set_string "sem.int.signed_overflow" "assume_none"; + set_bool "ana.int.interval" true; + set_string "ana.apron.domain" "polyhedra"; (* TODO: Needed? *) + () + | _ -> () + +let focusOnTermination () = + List.iter focusOnTermination (Svcomp.Specification.of_option ()) + +let focusOnSpecification (spec: Svcomp.Specification.t) = + match spec with | UnreachCall s -> () | NoDataRace -> (*enable all thread analyses*) print_endline @@ "Specification: NoDataRace -> enabling thread analyses \"" ^ (String.concat ", " notNeccessaryThreadAnalyses) ^ "\""; @@ -219,6 +263,10 @@ let focusOnSpecification () = | NoOverflow -> (*We focus on integer analysis*) set_bool "ana.int.def_exc" true; set_bool "ana.int.interval" true + | _ -> () + +let focusOnSpecification () = + List.iter focusOnSpecification (Svcomp.Specification.of_option ()) (*Detect enumerations and enable the "ana.int.enums" option*) exception EnumFound @@ -376,9 +424,10 @@ let congruenceOption factors file = let apronOctagonOption factors file = let locals = if List.mem "specification" (get_string_list "ana.autotune.activated" ) && get_string "ana.specification" <> "" then - match Svcomp.Specification.of_option () with - | NoOverflow -> 12 - | _ -> 8 + if List.mem Svcomp.Specification.NoOverflow (Svcomp.Specification.of_option ()) then + 12 + else + 8 else 8 in let globals = 2 in let selectedLocals = @@ -426,6 +475,17 @@ let wideningOption factors file = print_endline "Enabled widening thresholds"; } +let activateTmpSpecialAnalysis () = + let isMathFun = function + | LibraryDesc.Math _ -> true + | _ -> false + in + let hasMathFunctions = hasFunction isMathFun in + if hasMathFunctions then ( + print_endline @@ "math function -> enabling tmpSpecial analysis and floating-point domain"; + enableAnalyses ["tmpSpecial"]; + set_bool "ana.float.interval" true; + ) let estimateComplexity factors file = let pathsEstimate = factors.loops + factors.controlFlowStatements / 90 in @@ -455,6 +515,14 @@ let chooseFromOptions costTarget options = let isActivated a = get_bool "ana.autotune.enabled" && List.mem a @@ get_string_list "ana.autotune.activated" +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 @@ -474,7 +542,7 @@ let chooseConfig file = if isActivated "mallocWrappers" then findMallocWrappers (); - if isActivated "specification" && get_string "ana.specification" <> "" then + if specificationIsActivated () then focusOnSpecification (); if isActivated "enums" && hasEnums file then @@ -486,12 +554,15 @@ let chooseConfig file = if isActivated "arrayDomain" then selectArrayDomains file; + if isActivated "tmpSpecialAnalysis" then + activateTmpSpecialAnalysis (); + let options = [] in let options = if isActivated "congruence" then (congruenceOption factors file)::options else options in - let options = if isActivated "octagon" then (apronOctagonOption factors file)::options else options in + (* Termination analysis uses apron in a different configuration. *) + let options = if isActivated "octagon" && not (isTerminationTask ()) then (apronOctagonOption factors file)::options else options in let options = if isActivated "wideningThresholds" then (wideningOption factors file)::options else options in List.iter (fun o -> o.activate ()) @@ chooseFromOptions (totalTarget - fileCompplexity) options - let reset_lazy () = ResettableLazy.reset functionCallMaps diff --git a/src/build-info/.gitignore b/src/build-info/.gitignore new file mode 100644 index 0000000000..8afff91d71 --- /dev/null +++ b/src/build-info/.gitignore @@ -0,0 +1 @@ +config*.ml diff --git a/src/build-info/build_info_dune/goblint_build_info.ml b/src/build-info/build_info_dune/dune_build_info.ml similarity index 100% rename from src/build-info/build_info_dune/goblint_build_info.ml rename to src/build-info/build_info_dune/dune_build_info.ml diff --git a/src/build-info/build_info_js/goblint_build_info.ml b/src/build-info/build_info_js/dune_build_info.ml similarity index 100% rename from src/build-info/build_info_js/goblint_build_info.ml rename to src/build-info/build_info_js/dune_build_info.ml diff --git a/src/build-info/dune b/src/build-info/dune index 89ae841778..ff8d68671b 100644 --- a/src/build-info/dune +++ b/src/build-info/dune @@ -8,4 +8,25 @@ (library (name goblint_build_info) (public_name goblint.build-info) - (virtual_modules goblint_build_info)) + (libraries batteries.unthreaded) + (virtual_modules 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\"'"))))) + +(rule + (target configProfile.ml) + (mode (promote (until-clean) (only configProfile.ml))) ; replace existing file in source tree, even if releasing (only overrides) + (action (write-file %{target} "(* Automatically regenerated, changes do not persist! *)\nlet profile = \"%{profile}\""))) + +(rule + (target configOcaml.ml) + (mode (promote (until-clean) (only configOcaml.ml))) ; replace existing file in source tree, even if releasing (only overrides) + (action (write-file %{target} "(* Automatically regenerated, changes do not persist! *)\nlet flambda = \"%{ocaml-config:flambda}\""))) + +(env + (_ + (flags (:standard -w -no-cmx-file)))) ; suppress warning from flambda compiler bug: https://github.com/ocaml/dune/issues/3277 diff --git a/src/build-info/goblint_build_info.mli b/src/build-info/dune_build_info.mli similarity index 100% rename from src/build-info/goblint_build_info.mli rename to src/build-info/dune_build_info.mli diff --git a/src/build-info/goblint_build_info.ml b/src/build-info/goblint_build_info.ml new file mode 100644 index 0000000000..cf5165d51c --- /dev/null +++ b/src/build-info/goblint_build_info.ml @@ -0,0 +1,34 @@ +(** Goblint build info. *) + +(** OCaml compiler flambda status. *) +let ocaml_flambda = ConfigOcaml.flambda + +(** Dune profile. *) +let dune_profile = ConfigProfile.profile + +(** Goblint version from git. *) +let git_version = ConfigVersion.version + +(** Goblint version from release archive. *) +let release_version = "%%VERSION_NUM%%" + +(** Goblint git commit from release archive. *) +let release_commit = "%%VCS_COMMIT_ID%%" + +(** Goblint version. *) +let version = + let commit = ConfigVersion.version in + if BatString.starts_with release_version "%" then + commit + else ( + let commit = + if commit = "n/a" then (* released archive has no .git *) + release_commit + else + commit + in + Format.sprintf "%s (%s)" release_version commit + ) + +(** Statically linked libraries with versions. *) +let statically_linked_libraries = Dune_build_info.statically_linked_libraries diff --git a/src/cdomains/addressDomain.ml b/src/cdomains/addressDomain.ml index 9f6ee56cbf..55b1aceefc 100644 --- a/src/cdomains/addressDomain.ml +++ b/src/cdomains/addressDomain.ml @@ -5,6 +5,7 @@ open IntOps module M = Messages module Mval_outer = Mval +module SD = StringDomain module AddressBase (Mval: Printable.S) = @@ -14,23 +15,14 @@ struct | Addr of Mval.t | NullPtr | UnknownPtr - | StrPtr of string option + | StrPtr of SD.t [@@deriving eq, ord, hash] (* TODO: StrPtr equal problematic if the same literal appears more than once *) let name () = Format.sprintf "address (%s)" (Mval.name ()) - let hash x = match x with - | StrPtr _ -> - if GobConfig.get_bool "ana.base.limit-string-addresses" then - 13859 - else - hash x - | _ -> hash x - let show = function | Addr m -> Mval.show m - | StrPtr (Some x) -> "\"" ^ x ^ "\"" - | StrPtr None -> "(unknown string)" + | StrPtr s -> StringDomain.show s | UnknownPtr -> "?" | NullPtr -> "NULL" @@ -42,31 +34,18 @@ struct ) (* strings *) - let of_string x = StrPtr (Some x) + let of_string x = StrPtr (SD.of_string x) let to_string = function - | StrPtr (Some x) -> Some x + | StrPtr s -> SD.to_string s | _ -> None - (* only keep part before first null byte *) let to_c_string = function - | StrPtr (Some x) -> - begin match String.split_on_char '\x00' x with - | s::_ -> Some s - | [] -> None - end + | StrPtr s -> SD.to_c_string s | _ -> None - let to_n_c_string n x = - match to_c_string x with - | Some x -> - if n > String.length x then - Some x - else if n < 0 then - None - else - Some (String.sub x 0 n) + let to_n_c_string n = function + | StrPtr s -> SD.to_n_c_string n s | _ -> None - let to_string_length x = - match to_c_string x with - | Some x -> Some (String.length x) + let to_string_length = function + | StrPtr s -> SD.to_string_length s | _ -> None let arbitrary () = QCheck.always UnknownPtr (* S TODO: non-unknown *) @@ -101,8 +80,7 @@ struct (* TODO: seems to be unused *) let to_exp = function | Addr m -> AddrOf (Mval.to_cil m) - | StrPtr (Some x) -> mkString x - | StrPtr None -> raise (Lattice.Unsupported "Cannot express unknown string pointer as expression.") + | StrPtr s -> SD.to_exp s | NullPtr -> integer 0 | UnknownPtr -> raise Lattice.TopValue (* TODO: unused *) @@ -123,9 +101,7 @@ struct let semantic_equal x y = match x, y with | Addr x, Addr y -> Mval.semantic_equal x y - | StrPtr None, StrPtr _ - | StrPtr _, StrPtr None -> Some true - | StrPtr (Some a), StrPtr (Some b) -> if a = b then None else Some false + | StrPtr s1, StrPtr s2 -> SD.semantic_equal s1 s2 | NullPtr, NullPtr -> Some true | UnknownPtr, UnknownPtr | UnknownPtr, Addr _ @@ -135,8 +111,7 @@ struct | _, _ -> Some false let leq x y = match x, y with - | StrPtr _, StrPtr None -> true - | StrPtr a, StrPtr b -> a = b + | StrPtr s1, StrPtr s2 -> SD.leq s1 s2 | Addr x, Addr y -> Mval.leq x y | _ -> x = y @@ -144,26 +119,6 @@ struct | Addr x -> Addr (Mval.top_indices x) | x -> x - let join_string_ptr x y = match x, y with - | None, _ - | _, None -> None - | Some a, Some b when a = b -> Some a - | Some a, Some b (* when a <> b *) -> - if GobConfig.get_bool "ana.base.limit-string-addresses" then - None - else - raise Lattice.Uncomparable - - let meet_string_ptr x y = match x, y with - | None, a - | a, None -> a - | Some a, Some b when a = b -> Some a - | Some a, Some b (* when a <> b *) -> - if GobConfig.get_bool "ana.base.limit-string-addresses" then - raise Lattice.BotValue - else - raise Lattice.Uncomparable - let merge mop sop x y = match x, y with | UnknownPtr, UnknownPtr -> UnknownPtr @@ -172,10 +127,10 @@ struct | Addr x, Addr y -> Addr (mop x y) | _ -> raise Lattice.Uncomparable - let join = merge Mval.join join_string_ptr - let widen = merge Mval.widen join_string_ptr - let meet = merge Mval.meet meet_string_ptr - let narrow = merge Mval.narrow meet_string_ptr + let join = merge Mval.join SD.join + let widen = merge Mval.widen SD.join + let meet = merge Mval.meet SD.meet + let narrow = merge Mval.narrow SD.meet include Lattice.NoBotTop @@ -194,8 +149,7 @@ struct let of_elt (x: elt): t = match x with | Addr (v, o) -> Addr v - | StrPtr _ when GobConfig.get_bool "ana.base.limit-string-addresses" -> StrPtr None (* all strings together if limited *) - | StrPtr x -> StrPtr x (* everything else is kept separate, including strings if not limited *) + | StrPtr s -> StrPtr (SD.repr s) | NullPtr -> NullPtr | UnknownPtr -> UnknownPtr end @@ -211,8 +165,7 @@ struct let of_elt (x: elt): t = match x with | Addr (v, o) -> Addr (v, Offset.Unit.of_offs o) (* addrs grouped by var and part of offset *) - | StrPtr _ when GobConfig.get_bool "ana.base.limit-string-addresses" -> StrPtr None (* all strings together if limited *) - | StrPtr x -> StrPtr x (* everything else is kept separate, including strings if not limited *) + | StrPtr s -> StrPtr (SD.repr s) | NullPtr -> NullPtr | UnknownPtr -> UnknownPtr end @@ -440,4 +393,6 @@ struct let r = narrow x y in if M.tracing then M.traceu "ad" "-> %a\n" pretty r; r + + let filter f ad = fold (fun addr ad -> if f addr then add addr ad else ad) ad (empty ()) end diff --git a/src/cdomains/addressDomain_intf.ml b/src/cdomains/addressDomain_intf.ml index 0ef3d6dd8d..f65b2977c4 100644 --- a/src/cdomains/addressDomain_intf.ml +++ b/src/cdomains/addressDomain_intf.ml @@ -7,7 +7,7 @@ sig | Addr of Mval.t (** Pointer to mvalue. *) | NullPtr (** NULL pointer. *) | UnknownPtr (** Unknown pointer. Could point to globals, heap and escaped variables. *) - | StrPtr of string option (** String literal pointer. [StrPtr None] abstracts any string pointer *) + | StrPtr of StringDomain.t (** String literal pointer. [StrPtr None] abstracts any string pointer *) include Printable.S with type t := t (** @closed *) val of_string: string -> t @@ -16,8 +16,6 @@ sig val to_string: t -> string option (** Convert {!StrPtr} to string if possible. *) - (** C strings are different from OCaml strings as they are not processed after the first [NUL] byte, even though the OCaml string (and a C string literal) may be longer. *) - val to_c_string: t -> string option (** Convert {!StrPtr} to C string if possible. *) @@ -71,7 +69,7 @@ sig - Each {!Addr}, modulo precise index expressions in the offset, is a sublattice with ordering induced by {!Mval}. - {!NullPtr} is a singleton sublattice. - {!UnknownPtr} is a singleton sublattice. - - If [ana.base.limit-string-addresses] is enabled, then all {!StrPtr} are together in one sublattice with flat ordering. If [ana.base.limit-string-addresses] is disabled, then each {!StrPtr} is a singleton sublattice. *) + - If [ana.base.strings.domain] is disjoint, then each {!StrPtr} is a singleton sublattice. Otherwise, all {!StrPtr} are together in one sublattice with flat ordering. *) module AddressLattice (Mval: Mval.Lattice): sig include module type of AddressPrintable (Mval) diff --git a/src/cdomains/apron/affineEqualityDomain.apron.ml b/src/cdomains/apron/affineEqualityDomain.apron.ml index a6f00fdba0..5aa1090dd4 100644 --- a/src/cdomains/apron/affineEqualityDomain.apron.ml +++ b/src/cdomains/apron/affineEqualityDomain.apron.ml @@ -10,7 +10,7 @@ open Batteries open GoblintCil open Pretty module M = Messages -open Apron +open GobApron open VectorMatrix module Mpqf = struct @@ -26,14 +26,12 @@ module Mpqf = struct let hash x = 31 * (Z.hash (get_den x)) + Z.hash (get_num x) end -module Var = SharedFunctions.Var -module V = RelationDomain.V(Var) +module V = RelationDomain.V (** It defines the type t of the affine equality domain (a struct 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. Furthermore, it provides the function get_coeff_vec that parses an apron expression into a vector of coefficients if the apron expression has an affine form. *) module VarManagement (Vec: AbstractVector) (Mx: AbstractMatrix)= struct - include SharedFunctions.EnvOps module Vector = Vec (Mpqf) module Matrix = Mx(Mpqf) (Vec) @@ -78,16 +76,18 @@ struct let change_d t new_env add del = timing_wrap "dimension change" (change_d t new_env add) del + let vars x = Environment.ivars_only x.env + let add_vars t vars = let t = copy t in - let env' = add_vars t.env vars in + let env' = Environment.add_vars t.env vars in change_d t env' true false let add_vars t vars = timing_wrap "add_vars" (add_vars t) vars let drop_vars t vars del = let t = copy t in - let env' = remove_vars t.env vars in + let env' = Environment.remove_vars t.env vars in change_d t env' false del let drop_vars t vars = timing_wrap "drop_vars" (drop_vars t) vars @@ -102,7 +102,7 @@ struct t.env <- t'.env let remove_filter t f = - let env' = remove_filter t.env f in + let env' = Environment.remove_filter t.env f in change_d t env' false false let remove_filter t f = timing_wrap "remove_filter" (remove_filter t) f @@ -114,19 +114,18 @@ struct let keep_filter t f = let t = copy t in - let env' = keep_filter t.env f in + let env' = Environment.keep_filter t.env f in change_d t env' false false let keep_filter t f = timing_wrap "keep_filter" (keep_filter t) f let keep_vars t vs = let t = copy t in - let env' = keep_vars t.env vs in + let env' = Environment.keep_vars t.env vs in change_d t env' false false let keep_vars t vs = timing_wrap "keep_vars" (keep_vars t) vs - let vars t = vars t.env let mem_var t var = Environment.mem_var t.env var @@ -462,6 +461,7 @@ struct let assign_exp (t: VarManagement(Vc)(Mx).t) var exp (no_ov: bool Lazy.t) = let t = if not @@ Environment.mem_var t.env var then add_vars t [var] else t in + (* TODO: Do we need to do a constant folding here? It happens for texpr1_of_cil_exp *) match Convert.texpr1_expr_of_cil_exp t t.env exp (Lazy.force no_ov) with | exp -> assign_texpr t var exp | exception Convert.Unsupported_CilExp _ -> diff --git a/src/cdomains/apron/apronDomain.apron.ml b/src/cdomains/apron/apronDomain.apron.ml index d9928df597..03b9558621 100644 --- a/src/cdomains/apron/apronDomain.apron.ml +++ b/src/cdomains/apron/apronDomain.apron.ml @@ -4,7 +4,7 @@ open Batteries open GoblintCil open Pretty (* A binding to a selection of Apron-Domains *) -open Apron +open GobApron open RelationDomain open SharedFunctions @@ -29,8 +29,7 @@ let widening_thresholds_apron = ResettableLazy.from_fun (fun () -> let reset_lazy () = ResettableLazy.reset widening_thresholds_apron -module Var = SharedFunctions.Var -module V = RelationDomain.V(Var) +module V = RelationDomain.V module type Manager = @@ -209,7 +208,6 @@ module type AOpsExtra = sig type t val copy : t -> t - val vars_as_array : t -> Var.t array val vars : t -> Var.t list type marshal val unmarshal : marshal -> t @@ -248,15 +246,6 @@ struct let copy = A.copy Man.mgr - let vars_as_array d = - let ivs, fvs = Environment.vars (A.env d) in - assert (Array.length fvs = 0); (* shouldn't ever contain floats *) - ivs - - let vars d = - let ivs = vars_as_array d in - List.of_enum (Array.enum ivs) - (* marshal type: Abstract0.t and an array of var names *) type marshal = Man.mt Abstract0.t * string array @@ -266,31 +255,24 @@ struct let env = Environment.make vars [||] in {abstract0; env} + let vars x = Environment.ivars_only @@ A.env x + let marshal (x: t): marshal = - let vars = Array.map Var.to_string (vars_as_array x) in + let vars = Array.map Var.to_string (Array.of_list (vars x)) in x.abstract0, vars let mem_var d v = Environment.mem_var (A.env d) v - let add_vars_with nd vs = - let env' = EnvOps.add_vars (A.env nd) vs in + let envop f nd a = + let env' = f (A.env nd) a in A.change_environment_with Man.mgr nd env' false - let remove_vars_with nd vs = - let env' = EnvOps.remove_vars (A.env nd) vs in - A.change_environment_with Man.mgr nd env' false + let add_vars_with = envop Environment.add_vars + let remove_vars_with = envop Environment.remove_vars + let remove_filter_with = envop Environment.remove_filter + let keep_vars_with = envop Environment.keep_vars + let keep_filter_with = envop Environment.keep_filter - let remove_filter_with nd f = - let env' = EnvOps.remove_filter (A.env nd) f in - A.change_environment_with Man.mgr nd env' false - - let keep_vars_with nd vs = - let env' = EnvOps.keep_vars (A.env nd) vs in - A.change_environment_with Man.mgr nd env' false - - let keep_filter_with nd f = - let env' = EnvOps.keep_filter (A.env nd) f in - A.change_environment_with Man.mgr nd env' false let forget_vars_with nd vs = (* Unlike keep_vars_with, this doesn't check mem_var, but assumes valid vars, like assigns *) @@ -497,9 +479,9 @@ struct let to_yojson (x: t) = let constraints = A.to_lincons_array Man.mgr x - |> SharedFunctions.Lincons1Set.of_earray - |> SharedFunctions.Lincons1Set.elements - |> List.map (fun lincons1 -> `String (SharedFunctions.Lincons1.show lincons1)) + |> Lincons1Set.of_earray + |> 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 @@ -693,16 +675,16 @@ struct let join x y = (* just to optimize joining folds, which start with bot *) - if is_bot x then + if is_bot x then (* TODO: also for non-empty env *) y - else if is_bot y then + else if is_bot y then (* TODO: also for non-empty env *) x else ( if M.tracing then M.traceli "apron" "join %a %a\n" pretty x pretty y; let j = join x y in if M.tracing then M.trace "apron" "j = %a\n" pretty j; let j = - if strengthening_enabled then + if strengthening_enabled then (* TODO: skip if same envs? *) strengthening j x y else j @@ -886,7 +868,6 @@ struct let unmarshal (b, d) = (BoxD.unmarshal b, D.unmarshal d) let mem_var (_, d) v = D.mem_var d v - let vars_as_array (_, d) = D.vars_as_array d let vars (_, d) = D.vars d let pretty_diff () ((_, d1), (_, d2)) = D.pretty_diff () (d1, d2) diff --git a/src/cdomains/apron/gobApron.apron.ml b/src/cdomains/apron/gobApron.apron.ml new file mode 100644 index 0000000000..c39a3e42db --- /dev/null +++ b/src/cdomains/apron/gobApron.apron.ml @@ -0,0 +1,98 @@ +open Batteries +include Apron + +module Var = +struct + include Var + let equal x y = Var.compare x y = 0 +end + +module Lincons1 = +struct + include Lincons1 + + let show = Format.asprintf "%a" print + let compare x y = String.compare (show x) (show y) (* HACK *) + + let num_vars x = + (* Apron.Linexpr0.get_size returns some internal nonsense, so we count ourselves. *) + let size = ref 0 in + Lincons1.iter (fun coeff var -> + if not (Apron.Coeff.is_zero coeff) then + incr size + ) x; + !size +end + +module Lincons1Set = +struct + include Set.Make (Lincons1) + + let of_earray ({lincons0_array; array_env}: Lincons1.earray): t = + Array.enum lincons0_array + |> Enum.map (fun (lincons0: Lincons0.t) -> + Lincons1.{lincons0; env = array_env} + ) + |> of_enum +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 ivars_only env = + let ivs, fvs = Environment.vars env in + assert (Array.length fvs = 0); (* shouldn't ever contain floats *) + List.of_enum (Array.enum ivs) + + let add_vars env vs = + let vs' = + vs + |> List.enum + |> Enum.filter (fun v -> not (Environment.mem_var env v)) + |> Array.of_enum + in + Environment.add env vs' [||] + + let remove_vars env vs = + let vs' = + vs + |> List.enum + |> Enum.filter (fun v -> Environment.mem_var env v) + |> Array.of_enum + in + Environment.remove env vs' + + let remove_filter env f = + let vs' = + ivars_only env + |> List.enum + |> Enum.filter f + |> Array.of_enum + in + Environment.remove env vs' + + let keep_vars env vs = + (* Instead of iterating over all vars in env and doing a linear lookup in vs just to remove them, + make a new env with just the desired vs. *) + let vs' = + vs + |> List.enum + |> Enum.filter (fun v -> Environment.mem_var env v) + |> Array.of_enum + in + Environment.make vs' [||] + + let keep_filter env f = + (* Instead of removing undesired vars, + make a new env with just the desired vars. *) + let vs' = + ivars_only env + |> List.enum + |> Enum.filter f + |> Array.of_enum + in + Environment.make vs' [||] +end diff --git a/src/cdomains/apron/gobApron.no-apron.ml b/src/cdomains/apron/gobApron.no-apron.ml new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/cdomains/apron/relationDomain.apron.ml b/src/cdomains/apron/relationDomain.apron.ml index c5b6a0a89b..48720b0382 100644 --- a/src/cdomains/apron/relationDomain.apron.ml +++ b/src/cdomains/apron/relationDomain.apron.ml @@ -2,42 +2,16 @@ See {!ApronDomain} and {!AffineEqualityDomain}. *) +open GobApron open Batteries open GoblintCil -(** Abstracts the extended apron Var. *) -module type Var = -sig - type t - val compare : t -> t -> int - val of_string : string -> t - val to_string : t -> string - val hash : t -> int - val equal : t -> t -> bool -end - module type VarMetadata = sig type t val var_name: t -> string end -module VarMetadataTbl (VM: VarMetadata) (Var: Var) = -struct - module VH = Hashtbl.Make (Var) - - let vh = VH.create 113 - - let make_var ?name metadata = - let name = Option.default_delayed (fun () -> VM.var_name metadata) name in - let var = Var.of_string name in - VH.replace vh var metadata; - var - - let find_metadata (var: Var.t) = - VH.find_option vh var -end - module VM = struct type t = @@ -55,10 +29,26 @@ struct | Global g -> g.vname end +module VarMetadataTbl (VM: VarMetadata) = +struct + module VH = Hashtbl.Make (Var) + + let vh = VH.create 113 + + let make_var ?name metadata = + let name = Option.default_delayed (fun () -> VM.var_name metadata) name in + let var = Var.of_string name in + VH.replace vh var metadata; + var + + let find_metadata (var: Var.t) = + VH.find_option vh var +end + module type RV = sig - type t - type vartable + type t = Var.t + type vartable = VM.t VarMetadataTbl (VM).VH.t val vh: vartable val make_var: ?name:string -> VM.t -> t @@ -70,12 +60,13 @@ sig val to_cil_varinfo: t -> varinfo Option.t end -module V (Var: Var): (RV with type t = Var.t and type vartable = VM.t VarMetadataTbl (VM) (Var).VH.t) = +module V: RV = struct + open VM + type t = Var.t - module VMT = VarMetadataTbl (VM) (Var) + module VMT = VarMetadataTbl (VM) include VMT - open VM type vartable = VM.t VMT.VH.t @@ -90,12 +81,6 @@ struct | _ -> None end -module type LinCons = -sig - type t - val num_vars: t -> int -end - module type Tracked = sig val type_tracked: typ -> bool @@ -105,7 +90,7 @@ end module type S2 = sig type t - type var + type var = Var.t type marshal module Tracked: Tracked @@ -144,8 +129,8 @@ module type S3 = sig include S2 - val cil_exp_of_lincons1: Apron.Lincons1.t -> exp option - val invariant: t -> Apron.Lincons1.t list + val cil_exp_of_lincons1: Lincons1.t -> exp option + val invariant: t -> Lincons1.t list end type ('a, 'b) relcomponents_t = { @@ -184,10 +169,9 @@ struct let name () = RD.name () ^ " * " ^ PrivD.name () - let of_tuple(rel, priv):t = {rel; priv} - let to_tuple r = (r.rel, r.priv) - let arbitrary () = + let to_tuple r = (r.rel, r.priv) in + let of_tuple (rel, priv) = {rel; priv} in let tr = QCheck.pair (RD.arbitrary ()) (PrivD.arbitrary ()) in QCheck.map ~rev:to_tuple of_tuple tr @@ -216,7 +200,6 @@ end module type RD = sig - module Var : Var - module V : module type of struct include V(Var) end - include S3 with type var = Var.t + module V : RV + include S3 end diff --git a/src/cdomains/apron/sharedFunctions.apron.ml b/src/cdomains/apron/sharedFunctions.apron.ml index 059a7f8264..e66be00ae4 100644 --- a/src/cdomains/apron/sharedFunctions.apron.ml +++ b/src/cdomains/apron/sharedFunctions.apron.ml @@ -8,42 +8,6 @@ module M = Messages module BI = IntOps.BigIntOps -module Var = -struct - include Var - - let equal x y = Var.compare x y = 0 -end - -module Lincons1 = -struct - include Lincons1 - - let show = Format.asprintf "%a" print - let compare x y = String.compare (show x) (show y) (* HACK *) - - let num_vars x = - (* Apron.Linexpr0.get_size returns some internal nonsense, so we count ourselves. *) - let size = ref 0 in - Lincons1.iter (fun coeff var -> - if not (Apron.Coeff.is_zero coeff) then - incr size - ) x; - !size -end - -module Lincons1Set = -struct - include Set.Make (Lincons1) - - let of_earray ({lincons0_array; array_env}: Lincons1.earray): t = - Array.enum lincons0_array - |> Enum.map (fun (lincons0: Lincons0.t) -> - Lincons1.{lincons0; env = array_env} - ) - |> of_enum -end - let int_of_scalar ?round (scalar: Scalar.t) = if Scalar.is_infty scalar <> 0 then (* infinity means unbounded *) None @@ -291,66 +255,6 @@ struct include CilOfApron (V) 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 EnvOps = -struct - let vars env = - let ivs, fvs = Environment.vars env in - assert (Array.length fvs = 0); (* shouldn't ever contain floats *) - List.of_enum (Array.enum ivs) - - let add_vars env vs = - let vs' = - vs - |> List.enum - |> Enum.filter (fun v -> not (Environment.mem_var env v)) - |> Array.of_enum - in - Environment.add env vs' [||] - - let remove_vars env vs = - let vs' = - vs - |> List.enum - |> Enum.filter (fun v -> Environment.mem_var env v) - |> Array.of_enum - in - Environment.remove env vs' - - let remove_filter env f = - let vs' = - vars env - |> List.enum - |> Enum.filter f - |> Array.of_enum - in - Environment.remove env vs' - - let keep_vars env vs = - (* Instead of iterating over all vars in env and doing a linear lookup in vs just to remove them, - make a new env with just the desired vs. *) - let vs' = - vs - |> List.enum - |> Enum.filter (fun v -> Environment.mem_var env v) - |> Array.of_enum - in - Environment.make vs' [||] - - let keep_filter env f = - (* Instead of removing undesired vars, - make a new env with just the desired vars. *) - let vs' = - vars env - |> List.enum - |> Enum.filter f - |> Array.of_enum - in - Environment.make vs' [||] - -end - (** A more specific module type for RelationDomain.RelD2 with ConvBounds integrated and various apron elements. It is designed to be the interface for the D2 modules in affineEqualityDomain and apronDomain and serves as a functor argument for AssertionModule. *) module type AssertionRelS = diff --git a/src/cdomains/arrayDomain.ml b/src/cdomains/arrayDomain.ml index c099a94f96..2f91e47663 100644 --- a/src/cdomains/arrayDomain.ml +++ b/src/cdomains/arrayDomain.ml @@ -787,14 +787,19 @@ let array_oob_check ( type a ) (module Idx: IntDomain.Z with type t = a) (x, l) | Some true, Some true -> (* Certainly in bounds on both sides.*) () | Some true, Some false -> (* The following matching differentiates the must and may cases*) + AnalysisStateUtil.set_mem_safety_flag InvalidDeref; M.error ~category:M.Category.Behavior.Undefined.ArrayOutOfBounds.past_end "Must access array past end" | Some true, None -> + AnalysisStateUtil.set_mem_safety_flag InvalidDeref; M.warn ~category:M.Category.Behavior.Undefined.ArrayOutOfBounds.past_end "May access array past end" | Some false, Some true -> + AnalysisStateUtil.set_mem_safety_flag InvalidDeref; M.error ~category:M.Category.Behavior.Undefined.ArrayOutOfBounds.before_start "Must access array before start" | None, Some true -> + AnalysisStateUtil.set_mem_safety_flag InvalidDeref; M.warn ~category:M.Category.Behavior.Undefined.ArrayOutOfBounds.before_start "May access array before start" | _ -> + AnalysisStateUtil.set_mem_safety_flag InvalidDeref; M.warn ~category:M.Category.Behavior.Undefined.ArrayOutOfBounds.unknown "May access array out of bounds" else () diff --git a/src/cdomains/fileDomain.ml b/src/cdomains/fileDomain.ml deleted file mode 100644 index ca585b8bce..0000000000 --- a/src/cdomains/fileDomain.ml +++ /dev/null @@ -1,81 +0,0 @@ -(** Domains for file handles. *) - -open Batteries - -module D = MvalMapDomain - - -module Val = -struct - type mode = Read | Write [@@deriving eq, ord, hash] - type s = Open of string*mode | Closed | Error [@@deriving eq, ord, hash] - let name = "File handles" - let var_state = Closed - let string_of_mode = function Read -> "Read" | Write -> "Write" - let string_of_state = function - | Open(filename, m) -> "open("^filename^", "^string_of_mode m^")" - | Closed -> "closed" - | Error -> "error" - - (* properties of records (e.g. used by Dom.warn_each) *) - let opened s = s <> Closed && s <> Error - let closed s = s = Closed - let writable s = match s with Open((_,Write)) -> true | _ -> false -end - - -module Dom = -struct - include D.Domain (D.Value (Val)) - - (* returns a tuple (thunk, result) *) - let report_ ?(neg=false) k p msg m = - let f ?(may=false) msg = - let f () = warn ~may msg in - f, if may then `May true else `Must true in - let mf = (fun () -> ()), `Must false in - if mem k m then - let p = if neg then not % p else p in - let v = find' k m in - if V.must p v then f msg (* must *) - else if V.may p v then f ~may:true msg (* may *) - else mf (* none *) - else if neg then f msg else mf - - let report ?(neg=false) k p msg m = (fst (report_ ~neg k p msg m)) () (* evaluate thunk *) - - let reports k xs m = - let uncurry (neg, p, msg) = report_ ~neg:neg k p msg m in - let f result x = if snd (uncurry x) = result then Some (fst (uncurry x)) else None in - let must_true = BatList.filter_map (f (`Must true)) xs in - let may_true = BatList.filter_map (f (`May true)) xs in - (* output first must and first may *) - if must_true <> [] then (List.hd must_true) (); - if may_true <> [] then (List.hd may_true) () - - (* handling state *) - let opened r = V.state r |> Val.opened - let closed r = V.state r |> Val.closed - let writable r = V.state r |> Val.writable - - let fopen k loc filename mode m = - if is_unknown k m then m else - let mode = match String.lowercase_ascii mode with "r" -> Val.Read | _ -> Val.Write in - let v = V.make k loc (Val.Open(filename, mode)) in - add' k v m - let fclose k loc m = - if is_unknown k m then m else - let v = V.make k loc Val.Closed in - change k v m - let error k m = - if is_unknown k m then m else - let loc = if mem k m then find' k m |> V.split |> snd |> Set.choose |> V.loc else [] in - let v = V.make k loc Val.Error in - change k v m - let success k m = - if is_unknown k m then m else - match find_option k m with - | Some v when V.may (Val.opened%V.state) v && V.may (V.in_state Val.Error) v -> - change k (V.filter (Val.opened%V.state) v) m (* TODO what about must-set? *) - | _ -> m -end diff --git a/src/cdomains/flagModeDomain.ml b/src/cdomains/flagModeDomain.ml deleted file mode 100644 index 70ee6d0015..0000000000 --- a/src/cdomains/flagModeDomain.ml +++ /dev/null @@ -1,52 +0,0 @@ -(* TODO: unused *) - -module Eq = IntDomain.MakeBooleans (struct let truename="==" let falsename="!=" end) -module Method = IntDomain.MakeBooleans (struct let truename="guard" let falsename="assign" end) - -module L_names = -struct - let bot_name = "unreachable" - let top_name = "unknown" -end - -module P = -struct - include Lattice.Flat (Printable.Prod3 (Method) (Eq) (IntDomain.FlatPureIntegers)) (L_names) - let show x = match x with - | `Lifted (m,b,e) -> Method.show m ^"ed "^ Eq.show b ^ " " ^ IntDomain.FlatPureIntegers.show e - | `Top -> top_name - | `Bot -> bot_name - - let join x y = match x,y with - | `Bot , z | z , `Bot -> z - | `Lifted (false,_,c1),`Lifted (false,_,c2) when c1=c2 -> y - | `Lifted (true,false,c1),`Lifted (true,false,c2) when c1=c2 -> y - | `Lifted (true,true,c1),`Lifted (true, true, c2) when c1=c2 -> y - | `Lifted (true,true,c1),`Lifted (true, false, c2) when not(c1=c2) -> y - | `Lifted (true,false,c1),`Lifted (true, true, c2) when not(c1=c2) -> x - | _ -> `Top - - - let leq (x:t) (y:t) = match x,y with - | `Bot , _ -> true - | _ , `Top -> true - | _, `Bot -> false - | `Top ,_ -> false - | `Lifted (false,_,c1), `Lifted (false,_,c2) -> c1=c2 - | _, `Lifted (false,_,_) -> false - | `Lifted (false,_,_), _ -> true - | `Lifted (true,true,c1),`Lifted (true, true, c2) -> c1=c2 - | _, `Lifted (true,true,_) -> false - | `Lifted (true, true, _), _ -> true - | `Lifted (true,false,c1),`Lifted (true,false,c2) -> c1=c2 - (* | _, `Lifted (true,false,c1) -> false - | `Lifted (true,false,_), _ -> true *) - (* | _ -> false *) -end - -module Dom = -struct - include MapDomain.MapTop_LiftBot (Basetype.Variables) (P) - - (* let find k x = if mem k x then find k x else P.top() *) -end diff --git a/src/cdomains/floatDomain.ml b/src/cdomains/floatDomain.ml index 4eb024adf9..39d3744401 100644 --- a/src/cdomains/floatDomain.ml +++ b/src/cdomains/floatDomain.ml @@ -40,7 +40,16 @@ module type FloatArith = sig (** sin(x) *) val tan : t -> t (** tan(x) *) + val sqrt : t -> t + (** sqrt(x) *) + (** {inversions of unary functions}*) + val inv_ceil : ?asPreciseAsConcrete:bool -> t -> t + (** (inv_ceil z -> x) if (z = ceil(x)) *) + val inv_floor : ?asPreciseAsConcrete:bool -> t -> t + (** (inv_floor z -> x) if (z = floor(x)) *) + val inv_fabs : t -> t + (** (inv_fabs z -> x) if (z = fabs(x)) *) (** {b Comparison operators} *) val lt : t -> t -> IntDomain.IntDomTuple.t @@ -88,6 +97,7 @@ module type FloatDomainBase = sig val starting : float -> t val ending_before : float -> t val starting_after : float -> t + val finite : t val minimal: t -> float option val maximal: t -> float option @@ -210,6 +220,7 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct let ending_before e = of_interval' (Float_t.lower_bound, Float_t.pred @@ Float_t.of_float Up e) let starting s = of_interval' (Float_t.of_float Down s, Float_t.upper_bound) let starting_after s = of_interval' (Float_t.succ @@ Float_t.of_float Down s, Float_t.upper_bound) + let finite = of_interval' (Float_t.lower_bound, Float_t.upper_bound) let minimal = function | Bot -> raise (ArithmeticOnFloatBot (Printf.sprintf "minimal %s" (show Bot))) @@ -312,13 +323,13 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct warn_on_special "Second operand" "comparison" op2 (** evaluation of the unary and binary operations *) - let eval_unop onTop eval_operation op = - warn_on_specials_unop op; + let eval_unop ?(warn=false) eval_operation op = + if warn then warn_on_specials_unop op; match op with | Bot -> raise (ArithmeticOnFloatBot (Printf.sprintf "unop %s" (show op))) | Interval v -> eval_operation v - | Top -> onTop - | _ -> onTop (* TODO: Do better *) + | Top -> top () + | _ -> top () (* TODO: Do better *) let eval_binop eval_operation v1 v2 = let is_exact_before = is_exact (Interval v1) && is_exact (Interval v2) in @@ -661,6 +672,56 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct | (l, h) when l = h && l = Float_t.zero -> of_const 0. (*tan(0) = 0*) | _ -> top () (**could be exact for intervals where l=h, or even for some intervals *) + let eval_sqrt = function + | (l, h) when l = Float_t.zero && h = Float_t.zero -> of_const 0. + | (l, h) when l >= Float_t.zero -> + let low = Float_t.sqrt Down l in + let high = Float_t.sqrt Up h in + Interval (low, high) + | _ -> top () + + let eval_inv_ceil ?(asPreciseAsConcrete=false) = function + | (l, h) -> + if (Float_t.sub Up (Float_t.ceil l) (Float_t.sub Down (Float_t.ceil l) (Float_t.of_float Nearest 1.0)) = (Float_t.of_float Nearest 1.0)) then ( + (* if [ceil(l) - (ceil(l) - 1.0) = 1.0], then we are in a range, where each int is expressable as float. + With that we can say, that [(ceil(x) >= l) => (x > (ceil(l) - 1.0)] *) + if asPreciseAsConcrete then + (* in case abstract and concrete precision are the same, [succ(l - 1.0), h] is more precise *) + Interval (Float_t.succ (Float_t.sub Down (Float_t.ceil l) (Float_t.of_float Nearest 1.0)), h) + else + Interval (Float_t.sub Down (Float_t.ceil l) (Float_t.of_float Nearest 1.0), h) + ) + else ( + (* if we know the abstract and concrete precision are the same, we return [l, h] as an interval, since no x in [l - 1.0, l] could exist such that ceil(x) = l appart from l itself *) + if asPreciseAsConcrete then + Interval (l, h) + else + Interval (Float_t.pred l, h) + ) + + let eval_inv_floor ?(asPreciseAsConcrete=false) = function + | (l, h) -> + if (Float_t.sub Up (Float_t.add Up (Float_t.floor h) (Float_t.of_float Nearest 1.0)) (Float_t.floor h) = (Float_t.of_float Nearest 1.0)) then ( + (* if [(floor(h) + 1.0) - floor(h) = 1.0], then we are in a range, where each int is expressable as float. + With that we can say, that [(floor(x) <= h) => (x < (floor(h) + 1.0)] *) + if asPreciseAsConcrete then + (* in case abstract and concrete precision are the same, [l, pred(floor(h) + 1.0)] is more precise than [l, floor(h) + 1.0] *) + Interval (l, Float_t.pred (Float_t.add Up (Float_t.floor h) (Float_t.of_float Nearest 1.0))) + else + Interval (l, Float_t.add Up (Float_t.floor h) (Float_t.of_float Nearest 1.0)) + ) + else ( + (* if we know the abstract and concrete precision are the same, we return [l, h] as an interval, since no x in [h, h + 1.0] could exist such that floor(x) = h appart from h itself *) + if asPreciseAsConcrete then + Interval (l, h) + else + Interval (l, Float_t.succ h) + ) + + let eval_inv_fabs = function + | (_, h) when h < Float_t.zero -> Bot (* Result of fabs cannot be negative *) + | (_, h) -> Interval (Float_t.neg h, h) + let isfinite op = match op with | Bot -> raise (ArithmeticOnFloatBot (Printf.sprintf "unop %s" (show op))) @@ -727,12 +788,24 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct | PlusInfinity -> PlusInfinity | MinusInfinity -> MinusInfinity - let acos = eval_unop (top ()) eval_acos - let asin = eval_unop (top ()) eval_asin - let atan = eval_unop (top ()) eval_atan - let cos = eval_unop (top ()) eval_cos - let sin = eval_unop (top ()) eval_sin - let tan = eval_unop (top ()) eval_tan + let acos = eval_unop eval_acos + let asin = eval_unop eval_asin + let atan = eval_unop eval_atan + let cos = eval_unop eval_cos + let sin = eval_unop eval_sin + let tan = eval_unop eval_tan + let sqrt = eval_unop eval_sqrt + + let inv_ceil ?(asPreciseAsConcrete=false) = eval_unop ~warn:false (eval_inv_ceil ~asPreciseAsConcrete:asPreciseAsConcrete) + let inv_floor ?(asPreciseAsConcrete=false) = eval_unop ~warn:false (eval_inv_floor ~asPreciseAsConcrete:asPreciseAsConcrete) + let inv_fabs op = + match op with + | Bot -> raise (ArithmeticOnFloatBot (Printf.sprintf "unop %s" (show op))) + | Top -> Top + | Interval v -> eval_inv_fabs v + | NaN -> NaN (* so we assume, fabs(NaN) = NaN?)*) + | PlusInfinity -> Top (* +/-inf *) + | MinusInfinity -> Bot end module F64Interval = FloatIntervalImpl(CDouble) @@ -761,6 +834,7 @@ module type FloatDomain = sig val starting : Cil.fkind -> float -> t val ending_before : Cil.fkind -> float -> t val starting_after : Cil.fkind -> float -> t + val finite : Cil.fkind -> t val minimal: t -> float option val maximal: t -> float option @@ -836,6 +910,21 @@ module FloatIntervalImplLifted = struct let cos = lift (F1.cos, F2.cos) let sin = lift (F1.sin, F2.sin) let tan = lift (F1.tan, F2.tan) + let sqrt = lift (F1.sqrt, F2.sqrt) + + let inv_ceil ?(asPreciseAsConcrete=BoolDomain.MustBool.top ()) = function + | F32 a -> F32 (F1.inv_ceil ~asPreciseAsConcrete:true a) + | F64 a -> F64 (F2.inv_ceil ~asPreciseAsConcrete:true a) + | FLong a -> FLong (F2.inv_ceil a) + | FFloat128 a -> FFloat128 (F2.inv_ceil a) + + let inv_floor ?(asPreciseAsConcrete=BoolDomain.MustBool.top ()) = function + | F32 a -> F32 (F1.inv_floor ~asPreciseAsConcrete:true a) + | F64 a -> F64 (F2.inv_floor ~asPreciseAsConcrete:true a) + | FLong a -> FLong (F2.inv_floor a) + | FFloat128 a -> FFloat128 (F2.inv_floor a) + + let inv_fabs = lift (F1.inv_fabs, F2.inv_fabs) let add = lift2 (F1.add, F2.add) let sub = lift2 (F1.sub, F2.sub) let mul = lift2 (F1.mul, F2.mul) @@ -860,7 +949,7 @@ module FloatIntervalImplLifted = struct let is_bot = dispatch (F1.is_bot, F2.is_bot) let top_of fkind = dispatch_fkind fkind (F1.top, F2.top) let top () = failwith "top () is not implemented for FloatIntervalImplLifted." - let is_top = dispatch (F1.is_bot, F2.is_bot) + let is_top = dispatch (F1.is_top, F2.is_top) let nan_of fkind = dispatch_fkind fkind (F1.nan, F2.nan) let is_nan = dispatch (F1.is_nan, F2.is_nan) @@ -900,6 +989,7 @@ module FloatIntervalImplLifted = struct let of_interval fkind i = dispatch_fkind fkind ((fun () -> F1.of_interval i), (fun () -> F2.of_interval i)) let starting fkind s = dispatch_fkind fkind ((fun () -> F1.starting s), (fun () -> F2.starting s)) let starting_after fkind s = dispatch_fkind fkind ((fun () -> F1.starting_after s), (fun () -> F2.starting_after s)) + let finite fkind = dispatch_fkind fkind ((fun () -> F1.finite), (fun () -> F2.finite)) let ending fkind e = dispatch_fkind fkind ((fun () -> F1.ending e), (fun () -> F2.ending e)) let ending_before fkind e = dispatch_fkind fkind ((fun () -> F1.ending_before e), (fun () -> F2.ending_before e)) let minimal = dispatch (F1.minimal, F2.minimal) @@ -1003,6 +1093,8 @@ module FloatDomTupleImpl = struct create { fi= (fun (type a) (module F : FloatDomain with type t = a) -> F.ending_before fkind); } let starting_after fkind = create { fi= (fun (type a) (module F : FloatDomain with type t = a) -> F.starting_after fkind); } + let finite = + create { fi= (fun (type a) (module F : FloatDomain with type t = a) -> F.finite); } let of_string fkind = create { fi= (fun (type a) (module F : FloatDomain with type t = a) -> F.of_string fkind); } @@ -1079,6 +1171,17 @@ module FloatDomTupleImpl = struct map { f1= (fun (type a) (module F : FloatDomain with type t = a) -> F.sin); } let tan = map { f1= (fun (type a) (module F : FloatDomain with type t = a) -> F.tan); } + let sqrt = + map { f1= (fun (type a) (module F : FloatDomain with type t = a) -> F.sqrt); } + + (*"asPreciseAsConcrete" has no meaning here*) + let inv_ceil ?(asPreciseAsConcrete=BoolDomain.MustBool.top ()) = + map { f1= (fun (type a) (module F : FloatDomain with type t = a) -> F.inv_ceil ~asPreciseAsConcrete:(BoolDomain.MustBool.top ())); } + (*"asPreciseAsConcrete" has no meaning here*) + let inv_floor ?(asPreciseAsConcrete=BoolDomain.MustBool.top ()) = + map { f1= (fun (type a) (module F : FloatDomain with type t = a) -> F.inv_floor ~asPreciseAsConcrete:(BoolDomain.MustBool.top ())); } + let inv_fabs = + map { f1= (fun (type a) (module F : FloatDomain with type t = a) -> F.inv_fabs); } (* f2: binary ops *) let join = diff --git a/src/cdomains/floatDomain.mli b/src/cdomains/floatDomain.mli index 13df16aba6..d958e1ee81 100644 --- a/src/cdomains/floatDomain.mli +++ b/src/cdomains/floatDomain.mli @@ -57,6 +57,17 @@ module type FloatArith = sig val tan : t -> t (** tan(x) *) + val sqrt : t -> t + (** sqrt(x) *) + + (** {inversions of unary functions}*) + val inv_ceil : ?asPreciseAsConcrete:bool -> t -> t + (** (inv_ceil z -> x) if (z = ceil(x)) *) + val inv_floor : ?asPreciseAsConcrete:bool -> t -> t + (** (inv_floor z -> x) if (z = floor(x)) *) + val inv_fabs : t -> t + (** (inv_fabs z -> x) if (z = fabs(x)) *) + (** {b Comparison operators} *) @@ -116,6 +127,7 @@ module type FloatDomainBase = sig val starting : float -> t val ending_before : float -> t val starting_after : float -> t + val finite : t val minimal: t -> float option val maximal: t -> float option @@ -150,6 +162,7 @@ module type FloatDomain = sig val starting : Cil.fkind -> float -> t val ending_before : Cil.fkind -> float -> t val starting_after : Cil.fkind -> float -> t + val finite : Cil.fkind -> t val minimal: t -> float option val maximal: t -> float option diff --git a/src/cdomains/floatOps/floatOps.ml b/src/cdomains/floatOps/floatOps.ml index a951ec08fe..a4e39d930e 100644 --- a/src/cdomains/floatOps/floatOps.ml +++ b/src/cdomains/floatOps/floatOps.ml @@ -35,6 +35,7 @@ module type CFloatType = sig val sub: round_mode -> t -> t -> t val mul: round_mode -> t -> t -> t val div: round_mode -> t -> t -> t + val sqrt: round_mode -> t -> t val atof: round_mode -> string -> t end @@ -74,6 +75,7 @@ module CDouble = struct external sub: round_mode -> t -> t -> t = "sub_double" external mul: round_mode -> t -> t -> t = "mul_double" external div: round_mode -> t -> t -> t = "div_double" + external sqrt: round_mode -> t -> t = "sqrt_double" external atof: round_mode -> string -> t = "atof_double" end @@ -107,6 +109,7 @@ module CFloat = struct external sub: round_mode -> t -> t -> t = "sub_float" external mul: round_mode -> t -> t -> t = "mul_float" external div: round_mode -> t -> t -> t = "div_float" + external sqrt: round_mode -> t -> t = "sqrt_float" external atof: round_mode -> string -> t = "atof_float" diff --git a/src/cdomains/floatOps/floatOps.mli b/src/cdomains/floatOps/floatOps.mli index 05bf363872..cf24f75ed5 100644 --- a/src/cdomains/floatOps/floatOps.mli +++ b/src/cdomains/floatOps/floatOps.mli @@ -38,6 +38,7 @@ module type CFloatType = sig val sub: round_mode -> t -> t -> t val mul: round_mode -> t -> t -> t val div: round_mode -> t -> t -> t + val sqrt: round_mode -> t -> t val atof: round_mode -> string -> t end diff --git a/src/cdomains/floatOps/stubs.c b/src/cdomains/floatOps/stubs.c index e0485883dd..50e4a2fb31 100644 --- a/src/cdomains/floatOps/stubs.c +++ b/src/cdomains/floatOps/stubs.c @@ -36,6 +36,20 @@ static void change_round_mode(int mode) } } +#define UNARY_OP(name, type, op) \ + CAMLprim value name##_##type(value mode, value x) \ + { \ + int old_roundingmode = fegetround(); \ + change_round_mode(Int_val(mode)); \ + volatile type r, x1 = Double_val(x); \ + r = op(x1); \ + fesetround(old_roundingmode); \ + return caml_copy_double(r); \ + } + +UNARY_OP(sqrt, double, sqrt); +UNARY_OP(sqrt, float, sqrtf); + #define BINARY_OP(name, type, op) \ CAMLprim value name##_##type(value mode, value x, value y) \ { \ diff --git a/src/cdomains/intDomain.ml b/src/cdomains/intDomain.ml index b1db3796a8..5d5174744f 100644 --- a/src/cdomains/intDomain.ml +++ b/src/cdomains/intDomain.ml @@ -514,7 +514,7 @@ module Size = struct (* size in bits as int, range as int64 *) let cast t x = (* TODO: overflow is implementation-dependent! *) if t = IBool then - (* C11 6.3.1.2 Boolean type *) + (* 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 @@ -610,6 +610,11 @@ module IntervalArith(Ints_t : IntOps.IntOps) = struct 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 @@ -760,7 +765,7 @@ struct norm ik @@ Some (l2,u2) |> fst let widen ik x y = let r = widen ik x y in - if M.tracing then M.tracel "int" "interval widen %a %a -> %a\n" pretty x pretty y pretty r; + if M.tracing && not (equal x y) then M.tracel "int" "interval widen %a %a -> %a\n" pretty x pretty y pretty r; assert (leq x y); (* TODO: remove for performance reasons? *) r @@ -826,7 +831,19 @@ struct | _ -> (top_of ik,{underflow=true; overflow=true}) let bitxor = bit (fun _ik -> Ints_t.bitxor) - let bitand = bit (fun _ik -> Ints_t.bitand) + + let bitand 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.bitand 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 bitor = bit (fun _ik -> Ints_t.bitor) let bit1 f ik i1 = @@ -839,7 +856,6 @@ struct let bitnot = bit1 (fun _ik -> Ints_t.bitnot) let shift_right = bitcomp (fun _ik x y -> Ints_t.shift_right x (Ints_t.to_int y)) - let shift_left = bitcomp (fun _ik x y -> Ints_t.shift_left 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) @@ -852,6 +868,20 @@ struct 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))) @@ -966,12 +996,12 @@ struct let arbitrary ik = let open QCheck.Iter in - (* let int_arb = QCheck.map ~rev:Ints_t.to_bigint Ints_t.of_bigint MyCheck.Arbitrary.big_int 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 MyCheck.Arbitrary.int64 in + 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) <+> (MyCheck.shrink pair_arb (l, u) >|= of_interval ik >|= fst) + | 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) @@ -1571,13 +1601,13 @@ struct let arbitrary ik = let open QCheck.Iter in - (* let int_arb = QCheck.map ~rev:Ints_t.to_bigint Ints_t.of_bigint MyCheck.Arbitrary.big_int 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 MyCheck.Arbitrary.int64 in + 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 = MyCheck.shrink list_pair_arb xs >|= canonize_randomly_generated_list + 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 @@ -1665,7 +1695,7 @@ struct let logand n1 n2 = of_bool ((to_bool' n1) && (to_bool' n2)) let logor n1 n2 = of_bool ((to_bool' n1) || (to_bool' n2)) let cast_to ?torg t x = failwith @@ "Cast_to not implemented for " ^ (name ()) ^ "." - let arbitrary ik = QCheck.map ~rev:Ints_t.to_int64 Ints_t.of_int64 MyCheck.Arbitrary.int64 (* TODO: use ikind *) + 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 @@ -1978,7 +2008,7 @@ struct let top_of ik = `Excluded (S.empty (), size ik) let cast_to ?torg ?no_ov ik = function | `Excluded (s,r) -> - let r' = size ik in + 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 *) @@ -1986,7 +2016,7 @@ struct `Definite (BI.one) else `Excluded (S.empty(), r') - else + else (* downcast: may overflow *) (* let s' = S.map (BigInt.cast_to ik) s in *) (* We want to filter out all i in s' where (t)x with x in r could be i. *) @@ -2282,7 +2312,28 @@ struct let ge ik x y = le ik y x let bitnot = lift1 BigInt.bitnot - let bitand = lift2 BigInt.bitand + + let bitand 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 BigInt.equal i BigInt.zero then + `Definite BigInt.zero + else if BigInt.equal i BigInt.one then + of_interval IBool (BigInt.zero, BigInt.one) + else + top () + | `Definite _, `Excluded _ + | `Excluded _, `Excluded _ -> top () + (* The good case: *) + | `Definite x, `Definite y -> + (try `Definite (BigInt.bitand 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 bitor = lift2 BigInt.bitor let bitxor = lift2 BigInt.bitxor @@ -2351,8 +2402,8 @@ struct let excluded s = from_excl ik s in let definite x = of_int ik x in let shrink = function - | `Excluded (s, _) -> MyCheck.shrink (S.arbitrary ()) s >|= excluded (* S TODO: possibly shrink excluded to definite *) - | `Definite x -> (return `Bot) <+> (MyCheck.shrink (BigInt.arbitrary ()) x >|= definite) + | `Excluded (s, _) -> GobQCheck.shrink (S.arbitrary ()) s >|= excluded (* S TODO: possibly shrink excluded to definite *) + | `Definite x -> (return `Bot) <+> (GobQCheck.shrink (BigInt.arbitrary ()) x >|= definite) | `Bot -> empty in QCheck.frequency ~shrink ~print:show [ @@ -2765,8 +2816,8 @@ module Enums : S with type int_t = BigInt.t = struct let neg s = of_excl_list ik (BISet.elements s) in let pos s = norm ik (Inc s) in let shrink = function - | Exc (s, _) -> MyCheck.shrink (BISet.arbitrary ()) s >|= neg (* S TODO: possibly shrink neg to pos *) - | Inc s -> MyCheck.shrink (BISet.arbitrary ()) s >|= pos + | 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 ()); @@ -3134,7 +3185,7 @@ struct (** 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: https://www.dsi.unive.it/~avp/domains.pdf *) + 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))) @@ -3144,7 +3195,19 @@ struct let bitor ik x y = bit2 Ints_t.bitor ik x y - let bitand ik x y = bit2 Ints_t.bitand ik x y + let bitand 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 =: Ints_t.zero && m' =: Ints_t.zero) then + (* both arguments constant *) + Some (Ints_t.bitand c c', Ints_t.zero) + else if m' =: Ints_t.zero && c' =: Ints_t.one && Ints_t.rem m (Ints_t.of_int 2) =: Ints_t.zero then + (* x & 1 and x == c (mod 2*z) *) + (* Value is equal to LSB of c *) + Some (Ints_t.bitand c c', Ints_t.zero) + else + top () let bitxor ik x y = bit2 Ints_t.bitxor ik x y @@ -3154,8 +3217,8 @@ struct | None, _ | _, None -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) | Some (c1, m1), Some(c2, m2) -> if m2 =: Ints_t.zero then - if (c2 |: m1) then - Some(c1 %: c2,Ints_t.zero) + if (c2 |: m1) && (c1 %: c2 =: Ints_t.zero || m1 =: Ints_t.zero || not (Cil.isSigned ik)) then + Some(c1 %: c2, Ints_t.zero) else normalize ik (Some(c1, (Ints_t.gcd m1 c2))) else @@ -3229,6 +3292,7 @@ struct let invariant_ikind e ik x = match x with + | x when is_top x -> Invariant.top () | Some (c, m) when m =: Ints_t.zero -> if get_bool "witness.invariant.exact" then let c = Ints_t.to_bigint c in @@ -3243,7 +3307,7 @@ struct let arbitrary ik = let open QCheck in - let int_arb = map ~rev:Ints_t.to_int64 Ints_t.of_int64 MyCheck.Arbitrary.int64 in + let int_arb = map ~rev:Ints_t.to_int64 Ints_t.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 @@ -3364,14 +3428,14 @@ module IntDomTupleImpl = struct | Some(_, {underflow; overflow}) -> not (underflow || overflow) | _ -> false - let check_ov ik intv intv_set = + let check_ov ~cast ik intv intv_set = let no_ov = (no_overflow ik intv) || (no_overflow ik intv_set) in if not no_ov && ( 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:false ~underflow ~overflow ik; + set_overflow_flag ~cast ~underflow ~overflow ik; ); no_ov @@ -3380,7 +3444,7 @@ module IntDomTupleImpl = struct 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 ik intv intv_set); + 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 *) @@ -3561,7 +3625,7 @@ module IntDomTupleImpl = struct 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 ik intv intv_set in + let no_ov = check_ov ~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 @@ -3571,10 +3635,10 @@ module IntDomTupleImpl = struct , BatOption.map fst intv_set ) (* map2 with overflow check *) - let map2ovc ik r (xa, xb, xc, xd, xe) (ya, yb, yc, yd, ye) = + 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 ik intv intv_set 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 diff --git a/src/cdomains/lockDomain.ml b/src/cdomains/lockDomain.ml index 0de5afc32c..107c1c0692 100644 --- a/src/cdomains/lockDomain.ml +++ b/src/cdomains/lockDomain.ml @@ -7,7 +7,7 @@ module IdxDom = ValueDomain.IndexDomain open GoblintCil -module Mutexes = SetDomain.ToppedSet (Addr) (struct let topname = "All mutexes" end) (* TODO HoareDomain? *) +module Mutexes = SetDomain.ToppedSet (Addr) (struct let topname = "All mutexes" end) (* TODO: AD? *) module Simple = Lattice.Reverse (Mutexes) module Priorities = IntDomain.Lifted @@ -37,6 +37,7 @@ struct end include SetDomain.Reverse(SetDomain.ToppedSet (Lock) (struct let topname = "All mutexes" end)) + let name () = "lockset" let may_be_same_offset of1 of2 = (* Only reached with definite of2 and indefinite of1. *) diff --git a/src/cdomains/mvalMapDomain.ml b/src/cdomains/mvalMapDomain.ml deleted file mode 100644 index 9d7625c4f5..0000000000 --- a/src/cdomains/mvalMapDomain.ml +++ /dev/null @@ -1,293 +0,0 @@ -(** Domains for {{!Mval} mvalue} maps. *) - -open Batteries -open GoblintCil - -module M = Messages - - -exception Unknown -exception Error - -(* signature for map entries *) -module type S = -sig - include Lattice.S - type k = Mval.Exp.t (* key *) - type s (* state is defined by Impl *) - type r (* record *) - - (* printing *) - val string_of: t -> string - val string_of_key: k -> string - val string_of_record: r -> string - - (* constructing *) - val make: k -> Node.t list -> s -> t - - (* manipulation *) - val map: (r -> r) -> t -> t - val filter: (r -> bool) -> t -> t - val union: t -> t -> t - val set_key: k -> t -> t - val set_state: s -> t -> t - val remove_state: s -> t -> t - - (* deconstructing *) - val split: t -> r Set.t * r Set.t - val map': (r -> 'a) -> t -> 'a Set.t * 'a Set.t - val filter': (r -> bool) -> t -> r Set.t * r Set.t - val length: t -> int * int - - (* predicates *) - val must: (r -> bool) -> t -> bool - val may: (r -> bool) -> t -> bool - (* properties of records *) - val key: r -> k - val loc: r -> Node.t list - val edit_loc: (Node.t list -> Node.t list) -> r -> r - val state: r -> s - val in_state: s -> r -> bool - - (* special variables *) - val get_record: t -> r option - (* val make_record: k -> location list -> s -> r *) - val make_var: k -> t - val from_tuple: r Set.t * r Set.t -> t - - (* aliasing *) - val is_alias: t -> bool - val get_alias: t -> k - val make_alias: k -> t -end - -module Value (Impl: sig - type s (* state *) [@@deriving eq, ord, hash] - val name: string - val var_state: s - val string_of_state: s -> string - end) : S with type s = Impl.s = -struct - type k = Mval.Exp.t [@@deriving eq, ord, hash] - type s = Impl.s [@@deriving eq, ord, hash] - module R = struct - include Printable.StdLeaf - type t = { key: k; loc: Node.t list; state: s } [@@deriving eq, ord, hash] - let name () = "MValMapDomainValue" - - let pretty () {key; loc; state} = - Pretty.dprintf "{key=%a; loc=%a; state=%s}" Mval.Exp.pretty key (Pretty.d_list ", " Node.pretty) loc (Impl.string_of_state state) - - include Printable.SimplePretty ( - struct - type nonrec t = t - let pretty = pretty - end - ) - end - type r = R.t - open R - (* TODO: use SetDomain.Reverse? *) - module Must' = SetDomain.ToppedSet (R) (struct let topname = "top" end) - module Must = Lattice.Reverse (Must') - module May = SetDomain.ToppedSet (R) (struct let topname = "top" end) - include Lattice.Prod (Must) (May) - let name () = Impl.name - - (* converts to polymorphic sets *) - let split (x,y) = try Must'.elements x |> Set.of_list, May.elements y |> Set.of_list with SetDomain.Unsupported _ -> Set.empty, Set.empty - - (* special variable used for indirection *) - let alias_var = Cilfacade.create_var @@ Cil.makeVarinfo false "@alias" Cil.voidType, `NoOffset - (* alias structure: x[0].key=alias_var, y[0].key=linked_var *) - let is_alias (x,y) = neg Must'.is_empty x && (Must'.choose x).key=alias_var - let get_alias (x,y) = (May.choose y).key - - (* Printing *) - let string_of_key k = Mval.Exp.show k - let string_of_loc xs = String.concat ", " (List.map (CilType.Location.show % Node.location) xs) - let string_of_record r = Impl.string_of_state r.state^" ("^string_of_loc r.loc^")" - let string_of (x,y) = - if is_alias (x,y) then - "alias for "^string_of_key @@ get_alias (x,y) - else - let x, y = split (x,y) in - let z = Set.diff y x in - "{ "^String.concat ", " (List.map string_of_record (Set.elements x))^" }, "^ - "{ "^String.concat ", " (List.map string_of_record (Set.elements z))^" }" - let show x = string_of x - include Printable.SimpleShow (struct - type nonrec t = t - let show = show - end) - (* constructing & manipulation *) - let make_record k l s = { key=k; loc=l; state=s } - let make k l s = let v = make_record k l s in Must'.singleton v, May.singleton v - let map f (x,y) = Must'.map f x, May.map f y - let filter p (x,y) = Must'.filter p x, May.filter p y (* retains top *) - let union (a,b) (c,d) = Must'.union a c, May.union b d - let set_key k v = map (fun x -> {x with key=k}) v (* changes key for all elements *) - let set_state s v = map (fun x -> {x with state=s}) v - let remove_state s v = filter (fun x -> x.state<>s) v - - (* deconstructing *) - let length = split %> Tuple2.mapn Set.cardinal - let map' f = split %> Tuple2.mapn (Set.map f) - let filter' f = split %> Tuple2.mapn (Set.filter f) - - (* predicates *) - let must p (x,y) = Must'.exists p x || May.for_all p y - let may p (x,y) = May.exists p y - - (* properties of records *) - let key r = r.key - let loc r = r.loc - let edit_loc f r = {r with loc=f r.loc} - let state r = r.state - let in_state s r = r.state = s - - (* special variables *) - let get_record (x,y) = if Must'.is_empty x then None else Some (Must'.choose x) - let make_var_record k = make_record k [] Impl.var_state - let make_var k = Must'.singleton (make_var_record k), May.singleton (make_var_record k) - let make_alias k = Must'.singleton (make_var_record alias_var), May.singleton (make_var_record k) - let from_tuple (x,y) = Set.to_list x |> Must'.of_list, Set.to_list y |> May.of_list -end - - -module Domain (V: S) = -struct - module K = Mval.Exp - module V = V - module MD = MapDomain.MapBot (Mval.Exp) (V) - include MD - - (* Map functions *) - (* find that resolves aliases *) - let find' k m = let v = find k m in if V.is_alias v then find (V.get_alias v) m else v - let find_option k m = if mem k m then Some(find' k m) else None - let get_alias k m = (* target: returns Some k' if k links to k' *) - if mem k m && V.is_alias (find k m) then Some (V.get_alias (find k m)) else None - let get_aliased k m = (* sources: get list of keys that link to k *) - (* iter (fun k' (x,y) -> if V.is_alias (x,y) then print_endline ("alias "^V.string_of_key k'^" -> "^V.string_of_key (Set.choose y).key)) m; *) - (* TODO V.get_alias v=k somehow leads to Out_of_memory... *) - filter (fun k' v -> V.is_alias v && V.string_of_key (V.get_alias v)=V.string_of_key k) m |> bindings |> List.map fst - let get_aliases k m = (* get list of all other keys that have the same pointee *) - match get_alias k m with - | Some k' -> [k] (* k links to k' *) - | None -> get_aliased k m (* k' that link to k *) - let alias a b m = (* link a to b *) - (* if b is already an alias, follow it... *) - let b' = get_alias b m |? b in - (* add an entry for key a, that points to b' *) - add a (V.make_alias b') m - let remove' k m = (* fixes keys that link to k before removing it *) - if mem k m && not (V.is_alias (find k m)) then (* k might be aliased *) - let v = find k m in - match get_aliased k m with - | [] -> remove k m (* nothing links to k *) - | k'::xs -> let m = add k' v m in (* set k' to v, link xs to k', finally remove k *) - (* List.map (fun x -> x.vname) (k'::xs) |> String.concat ", " |> print_endline; *) - List.fold_left (fun m x -> alias x k' m) m xs |> remove k - else remove k m (* k not in m or an alias *) - let add' k v m = - remove' k m (* fixes keys that might have linked to k *) - |> add k v (* set new value *) - let change k v m = (* if k is an alias, replace its pointee *) - add (get_alias k m |? k) v m - - (* special variables *) - let get_record k m = Option.bind (find_option k m) V.get_record - let edit_record k f m = - let v = find_option k m |? V.make_var k in - add k (V.map f v) m - let get_value k m = find_option k m |> Option.map_default V.split (Set.empty,Set.empty) - let extend_value k v' m = - let v = V.from_tuple v' in - if mem k m then - add k (V.union (find k m) v) m - else - add k v m - let union (a,b) (c,d) = Set.union a c, Set.union b d - let is_special_var k = String.get (V.string_of_key k) 0 = '@' - let without_special_vars m = filter (fun k v -> not @@ is_special_var k) m - - (* functions needed for enter & combine *) - (* only keep globals, aliases to them and special variables *) - let only_globals m = filter (fun k v -> (fst k).vglob || V.is_alias v && (fst (V.get_alias v)).vglob || is_special_var k) m - (* adds all the bindings from m2 to m1 (overwrites!) *) - let add_all m1 m2 = add_list (bindings m2) m1 - - (* callstack for locations *) - let callstack_var = Cilfacade.create_var @@ Cil.makeVarinfo false "@callstack" Cil.voidType, `NoOffset - let callstack m = get_record callstack_var m |> Option.map_default V.loc [] - let string_of_callstack m = " [call stack: "^String.concat ", " (List.map (CilType.Location.show % Node.location) (callstack m))^"]" - let edit_callstack f m = edit_record callstack_var (V.edit_loc f) m - - - (* predicates *) - let must k p m = mem k m && V.must p (find' k m) - let may k p m = mem k m && V.may p (find' k m) - let is_may k m = mem k m && let x,y = V.length (find' k m) in x=0 && y>0 - - let filter_values p m = (* filters all values in the map and flattens result *) - let flatten_sets = List.fold_left Set.union Set.empty in - without_special_vars m - |> filter (fun k v -> V.may p v && not (V.is_alias v)) - |> bindings |> List.map (fun (k,v) -> V.filter' p v) - |> List.split |> (fun (x,y) -> flatten_sets x, flatten_sets y) - let filter_records k p m = (* filters both sets of k *) - if mem k m then V.filter' p (find' k m) else Set.empty, Set.empty - - let unknown k m = add' k (V.top ()) m - let is_unknown k m = if mem k m then V.is_top (find' k m) else false - - (* printing *) - let string_of_state k m = if not (mem k m) then "?" else V.string_of (find' k m) - let string_of_key k = V.string_of_key k - let string_of_keys rs = Set.map (V.string_of_key % V.key) rs |> Set.elements |> String.concat ", " - let string_of_entry k m = string_of_key k ^ ": " ^ string_of_state k m - let string_of_map m = List.map (fun (k,v) -> string_of_entry k m) (bindings m) - - let warn ?may:(may=false) ?loc:(loc=[Option.get !Node.current_node]) msg = - let split_category s = - if Str.string_partial_match (Str.regexp {|\[\([^]]*\)\]|}) s 0 then - (Some (Str.matched_group 1 s), Str.string_after s (Str.match_end ())) - else - (None, s) - in - let rec split_categories s = - match split_category s with - | (Some category, s') -> - let (categories, s'') = split_categories s' in - (category :: categories, s'') - | (None, s') -> ([], s') - in - match split_categories msg with - | ([], msg) -> (if may then Messages.warn else Messages.error) ~loc:(Node (List.last loc)) "%s" msg - | (category :: categories, msg) -> - let category_of_string s = Messages.Category.from_string_list [String.lowercase_ascii s] in (* TODO: doesn't split subcategories, not used and no defined syntax even *) - let category = category_of_string category in - let tags = List.map (fun category -> Messages.Tag.Category (category_of_string category)) categories in - (if may then Messages.warn else Messages.error) ~loc:(Node (List.last loc)) ~category ~tags "%s" msg - - (* getting keys from Cil Lvals *) - - let key_from_lval lval = match lval with (* TODO try to get a Mval.Exp from Cil.Lval *) - | Var v1, o1 -> v1, Offset.Exp.of_cil o1 - | Mem Lval(Var v1, o1), o2 -> v1, Offset.Exp.of_cil (addOffset o1 o2) - (* | Mem exp, o1 -> failwith "not implemented yet" (* TODO use query_lv *) *) - | _ -> Cilfacade.create_var @@ Cil.makeVarinfo false ("?"^CilType.Lval.show lval) Cil.voidType, `NoOffset (* TODO *) - - let keys_from_lval lval (ask: Queries.ask) = (* use MayPointTo query to get all possible pointees of &lval *) - (* print_query_lv ctx.ask (AddrOf lval); *) - let query_lv (ask: Queries.ask) exp = match ask.f (Queries.MayPointTo exp) with - | l when not (Queries.LS.is_top l) -> Queries.LS.elements l - | _ -> [] - in - let exp = AddrOf lval in - let xs = query_lv ask exp in (* MayPointTo -> LValSet *) - let pretty_key k = Pretty.text (string_of_key k) in - Messages.debug ~category:Analyzer "MayPointTo %a = [%a]" d_exp exp (Pretty.docList ~sep:(Pretty.text ", ") pretty_key) xs; - xs -end diff --git a/src/cdomains/offset.ml b/src/cdomains/offset.ml index eca85e08a4..52cfe9eb41 100644 --- a/src/cdomains/offset.ml +++ b/src/cdomains/offset.ml @@ -22,7 +22,7 @@ struct include CilType.Exp let name () = "exp index" - let any = CastE (TInt (Cilfacade.ptrdiff_ikind (), []), mkString "any_index") + let any = Cilfacade.any_index_exp let all = CastE (TInt (Cilfacade.ptrdiff_ikind (), []), mkString "all_index") (* Override output *) diff --git a/src/cdomains/regionDomain.ml b/src/cdomains/regionDomain.ml index 143ba086a6..681eb79007 100644 --- a/src/cdomains/regionDomain.ml +++ b/src/cdomains/regionDomain.ml @@ -9,6 +9,15 @@ module B = Printable.UnitConf (struct let name = "•" end) module VFB = struct include Printable.Either (VF) (B) + let name () = "region" + + let pretty () = function + | `Right () -> Pretty.text "•" + | `Left x -> VF.pretty () x + + let show = function + | `Right () -> "•" + | `Left x -> VF.show x let printXml f = function | `Right () -> @@ -51,6 +60,7 @@ end module RS = struct include PartitionDomain.Set (VFB) + let name () = "regions" let single_vf vf = singleton (VFB.of_vf vf) let single_bullet = singleton (VFB.bullet) let remove_bullet x = remove VFB.bullet x @@ -147,13 +157,13 @@ struct (* This is the main logic for dealing with the bullet and finding it an * owner... *) - let add_set (s:set) llist (p,m:t): t = + let add_set ?(escape=false) (s:set) llist (p,m:t): t = if RS.has_bullet s then let f key value (ys, x) = if RS.has_bullet value then key::ys, RS.join value x else ys,x in let ys,x = RegMap.fold f m (llist, RS.remove_bullet s) in let x = RS.remove_bullet x in - if RS.is_empty x then + if not escape && RS.is_empty x then p, RegMap.add_list_set llist RS.single_bullet m else RegPart.add x p, RegMap.add_list_set ys x m @@ -205,6 +215,25 @@ struct | Some (_,x,_) -> p, RegMap.add x RS.single_bullet m | _ -> p,m + (* Copied & modified from assign. *) + let assign_escape (rval: exp) (st: t): t = + (* let _ = printf "%a = %a\n" (printLval plainCilPrinter) lval (printExp plainCilPrinter) rval in *) + let t = Cilfacade.typeOf rval in + if isPointerType t then begin (* TODO: this currently allows function pointers, e.g. in iowarrior, but should it? *) + match eval_exp rval with + (* TODO: should offs_x matter? *) + | Some (deref_y,y,offs_y) -> + let (p,m) = st in begin + match is_global y with + | true -> + add_set ~escape:true (RS.single_vf y) [] st + | false -> + add_set ~escape:true (RegMap.find y m) [y] st + end + | _ -> st + end else + st + let related_globals (deref_vfd: eval_t) (p,m: t): elt list = let add_o o2 (v,o) = (v, F.add_offset o o2) in match deref_vfd with diff --git a/src/cdomains/specDomain.ml b/src/cdomains/specDomain.ml deleted file mode 100644 index 75a9d8edc5..0000000000 --- a/src/cdomains/specDomain.ml +++ /dev/null @@ -1,34 +0,0 @@ -(** Domains for finite automaton specification file analysis. *) - -open Batteries - -module D = MvalMapDomain - - -module Val = -struct - type s = string [@@deriving eq, ord, hash] - let name = "Spec value" - let var_state = "" - let string_of_state s = s - - (* transforms May-Sets of length 1 to Must. NOTE: this should only be done if the original set had more than one element! *) - (* let maybe_must = function May xs when Set.cardinal xs = 1 -> Must (Set.choose xs) | x -> x *) - (* let may = function Must x -> May (Set.singleton x) | xs -> xs *) - (* let records = function Must x -> (Set.singleton x) | May xs -> xs *) - (* let list_of_records = function Must x -> [x] | May xs -> List.of_enum (Set.enum xs) *) - (* let vnames x = String.concat ", " (List.map (fun r -> string_of_key r.var) (list_of_records x)) *) -end - - -module Dom = -struct - include D.Domain (D.Value (Val)) - - (* handling state *) - let goto k loc state m = add' k (V.make k loc state) m - let may_goto k loc state m = let v = V.join (find' k m) (V.make k loc state) in add' k v m - let in_state k s m = must k (V.in_state s) m - let may_in_state k s m = may k (V.in_state s) m - let get_states k m = if not (mem k m) then [] else find' k m |> V.map' V.state |> snd |> Set.elements -end diff --git a/src/cdomains/stringDomain.ml b/src/cdomains/stringDomain.ml new file mode 100644 index 0000000000..0621f37eb6 --- /dev/null +++ b/src/cdomains/stringDomain.ml @@ -0,0 +1,114 @@ +include Printable.StdLeaf + +let name () = "string" + +type string_domain = Unit | Disjoint | Flat + +let string_domain: string_domain ResettableLazy.t = + ResettableLazy.from_fun (fun () -> + match GobConfig.get_string "ana.base.strings.domain" with + | "unit" -> Unit + | "disjoint" -> Disjoint + | "flat" -> Flat + | _ -> failwith "ana.base.strings.domain: illegal value" + ) + +let get_string_domain () = ResettableLazy.force string_domain + +let reset_lazy () = + ResettableLazy.reset string_domain + + +type t = string option [@@deriving eq, ord, hash] + +let hash x = + if get_string_domain () = Disjoint then + hash x + else + 13859 + +let show = function + | Some x -> "\"" ^ x ^ "\"" + | None -> "(unknown string)" + +include Printable.SimpleShow ( + struct + type nonrec t = t + let show = show + end + ) + +let of_string x = + if get_string_domain () = Unit then + None + else + Some x +let to_string x = x + +(* only keep part before first null byte *) +let to_c_string = function + | Some x -> + begin match String.split_on_char '\x00' x with + | s::_ -> Some s + | [] -> None + end + | None -> None + +let to_n_c_string n x = + match to_c_string x with + | Some x -> + if n > String.length x then + Some x + else if n < 0 then + None + else + Some (String.sub x 0 n) + | None -> None + +let to_string_length x = + match to_c_string x with + | Some x -> Some (String.length x) + | None -> None + +let to_exp = function + | Some x -> GoblintCil.mkString x + | None -> raise (Lattice.Unsupported "Cannot express unknown string pointer as expression.") + +let semantic_equal x y = + match x, y with + | None, _ + | _, None -> Some true + | Some a, Some b -> if a = b then None else Some false + +let leq x y = + match x, y with + | _, None -> true + | a, b -> a = b + +let join x y = + match x, y with + | None, _ + | _, 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 + +let meet x y = + match x, y with + | None, a + | 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 + +let repr x = + if get_string_domain () = Disjoint then + x (* everything else is kept separate, including strings if not limited *) + else + None (* all strings together if limited *) diff --git a/src/cdomains/stringDomain.mli b/src/cdomains/stringDomain.mli new file mode 100644 index 0000000000..66423caa0b --- /dev/null +++ b/src/cdomains/stringDomain.mli @@ -0,0 +1,40 @@ +(** String literals domain. *) + +include Printable.S + +val reset_lazy: unit -> unit +(** Reset the cached configuration of the string domain. *) + +val of_string: string -> t +(** Convert from string. *) + +val to_string: t -> string option +(** Convert to string if possible. *) + +(** C strings are different from OCaml strings as they are not processed after the first [NUL] byte, even though the OCaml string (and a C string literal) may be longer. *) + +val to_c_string: t -> string option +(** Convert to C string if possible. *) + +val to_n_c_string: int -> t -> string option +(** Convert to C string of given maximum length if possible. *) + +val to_string_length: t -> int option +(** Find length of C string if possible. *) + +val to_exp: t -> GoblintCil.exp +(** Convert to CIL expression. *) + +val semantic_equal: t -> t -> bool option +(** Check semantic equality of two strings. + + @return [Some true] if definitely equal, [Some false] if definitely not equal, [None] if unknown. *) + +(** Some {!Lattice.S} operations. *) + +val leq: t -> t -> bool +val join: t -> t -> t +val meet: t -> t -> t + +val repr : t -> t +(** Representative for address lattice. *) diff --git a/src/cdomains/threadIdDomain.ml b/src/cdomains/threadIdDomain.ml index 7193552048..a9646cffd2 100644 --- a/src/cdomains/threadIdDomain.ml +++ b/src/cdomains/threadIdDomain.ml @@ -23,7 +23,7 @@ module type Stateless = sig include S - val threadenter: Node.t -> int option -> varinfo -> t + val threadenter: multiple:bool -> Node.t -> int option -> varinfo -> t end module type Stateful = @@ -32,8 +32,8 @@ sig module D: Lattice.S - val threadenter: t * D.t -> Node.t -> int option -> varinfo -> t list - val threadspawn: D.t -> Node.t -> int option -> varinfo -> D.t + val threadenter: multiple:bool -> t * D.t -> Node.t -> int option -> varinfo -> t list + val threadspawn: multiple:bool -> D.t -> Node.t -> int option -> varinfo -> D.t (** If it is possible to get a list of threads created thus far, get it *) val created: t -> D.t -> (t list) option @@ -71,9 +71,10 @@ struct let threadinit v ~multiple: t = (v, None) - let threadenter l i v: t = + let threadenter ~multiple l i v: t = if GobConfig.get_bool "ana.thread.include-node" then - (v, Some (l, i)) + let counter = Option.map (fun x -> if multiple then WrapperFunctionAnalysis0.ThreadCreateUniqueCount.top () else x) i in + (v, Some (l, counter)) else (v, None) @@ -93,8 +94,8 @@ struct module D = Lattice.Unit - let threadenter _ n i v = [threadenter n i v] - let threadspawn () _ _ _ = () + let threadenter ~multiple _ n i v = [threadenter ~multiple n i v] + let threadspawn ~multiple () _ _ _ = () let created _ _ = None end @@ -162,10 +163,10 @@ struct else ([base_tid], S.empty ()) - let threadenter ((p, _ ) as current, (cs,_)) (n: Node.t) i v = - let ni = Base.threadenter n i v in + let threadenter ~multiple ((p, _ ) as current, (cs,_)) (n: Node.t) i v = + let ni = Base.threadenter ~multiple n i v in let ((p', s') as composed) = compose current ni in - if is_unique composed && S.mem ni cs then + if is_unique composed && (S.mem ni cs || multiple) then [(p, S.singleton ni); composed] (* also respawn unique version of the thread to keep it reachable while thread ID sets refer to it *) else [composed] @@ -182,12 +183,12 @@ struct in Some (List.concat_map map_one els) - let threadspawn (cs,cms) l i v = - let e = Base.threadenter l i v in + let threadspawn ~multiple (cs,cms) l i v = + let e = Base.threadenter ~multiple l i v in if S.mem e cs then (cs, S.add e cms) else - (S.add e cs, cms) + (S.add e cs, if multiple then S.add e cms else cms) let is_main = function | ([fl], s) when S.is_empty s && Base.is_main fl -> true @@ -257,24 +258,24 @@ struct | (None, Some x'), `Top -> liftp x' (P.D.top ()) | _ -> None - let threadenter x n i v = + let threadenter ~multiple x n i v = match x with - | ((Some x', None), `Lifted1 d) -> H.threadenter (x',d) n i v |> List.map (fun t -> (Some t, None)) - | ((Some x', None), `Bot) -> H.threadenter (x',H.D.bot ()) n i v |> List.map (fun t -> (Some t, None)) - | ((Some x', None), `Top) -> H.threadenter (x',H.D.top ()) n i v |> List.map (fun t -> (Some t, None)) - | ((None, Some x'), `Lifted2 d) -> P.threadenter (x',d) n i v |> List.map (fun t -> (None, Some t)) - | ((None, Some x'), `Bot) -> P.threadenter (x',P.D.bot ()) n i v |> List.map (fun t -> (None, Some t)) - | ((None, Some x'), `Top) -> P.threadenter (x',P.D.top ()) n i v |> List.map (fun t -> (None, Some t)) + | ((Some x', None), `Lifted1 d) -> H.threadenter ~multiple (x',d) n i v |> List.map (fun t -> (Some t, None)) + | ((Some x', None), `Bot) -> H.threadenter ~multiple (x',H.D.bot ()) n i v |> List.map (fun t -> (Some t, None)) + | ((Some x', None), `Top) -> H.threadenter ~multiple (x',H.D.top ()) n i v |> List.map (fun t -> (Some t, None)) + | ((None, Some x'), `Lifted2 d) -> P.threadenter ~multiple (x',d) n i v |> List.map (fun t -> (None, Some t)) + | ((None, Some x'), `Bot) -> P.threadenter ~multiple (x',P.D.bot ()) n i v |> List.map (fun t -> (None, Some t)) + | ((None, Some x'), `Top) -> P.threadenter ~multiple (x',P.D.top ()) n i v |> List.map (fun t -> (None, Some t)) | _ -> failwith "FlagConfiguredTID received a value where not exactly one component is set" - let threadspawn x n i v = + let threadspawn ~multiple x n i v = match x with - | `Lifted1 x' -> `Lifted1 (H.threadspawn x' n i v) - | `Lifted2 x' -> `Lifted2 (P.threadspawn x' n i v) - | `Bot when history_enabled () -> `Lifted1 (H.threadspawn (H.D.bot ()) n i v) - | `Bot -> `Lifted2 (P.threadspawn (P.D.bot ()) n i v) - | `Top when history_enabled () -> `Lifted1 (H.threadspawn (H.D.top ()) n i v) - | `Top -> `Lifted2 (P.threadspawn (P.D.top ()) n i v) + | `Lifted1 x' -> `Lifted1 (H.threadspawn ~multiple x' n i v) + | `Lifted2 x' -> `Lifted2 (P.threadspawn ~multiple x' n i v) + | `Bot when history_enabled () -> `Lifted1 (H.threadspawn ~multiple (H.D.bot ()) n i v) + | `Bot -> `Lifted2 (P.threadspawn ~multiple (P.D.bot ()) n i v) + | `Top when history_enabled () -> `Lifted1 (H.threadspawn ~multiple (H.D.top ()) n i v) + | `Top -> `Lifted2 (P.threadspawn ~multiple (P.D.top ()) n i v) let name () = "FlagConfiguredTID: " ^ if history_enabled () then H.name () else P.name () end diff --git a/src/cdomains/valueDomain.ml b/src/cdomains/valueDomain.ml index 20c4f3bf21..e6f3122cb0 100644 --- a/src/cdomains/valueDomain.ml +++ b/src/cdomains/valueDomain.ml @@ -10,7 +10,7 @@ module M = Messages module BI = IntOps.BigIntOps module MutexAttr = MutexAttrDomain module VDQ = ValueDomainQueries -module LS = VDQ.LS +module AD = VDQ.AD module AddrSetDomain = SetDomain.ToppedSet(Addr)(struct let topname = "All" end) module ArrIdxDomain = IndexDomain @@ -115,7 +115,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" + | TNamed (info, attr) -> info.tname = "pthread_mutex_t" || info.tname = "spinlock_t" || info.tname = "pthread_spinlock_t" || info.tname = "pthread_cond_t" | TInt (IInt, attr) -> hasAttribute "mutex" attr | _ -> false @@ -502,7 +502,7 @@ struct let warn_type op x y = if GobConfig.get_bool "dbg.verbose" then - ignore @@ printf "warn_type %s: incomparable abstr. values %s and %s at %a: %a and %a\n" op (tag_name (x:t)) (tag_name (y:t)) CilType.Location.pretty !Tracing.current_loc pretty x pretty y + ignore @@ printf "warn_type %s: incomparable abstr. values %s and %s at %a: %a and %a\n" op (tag_name (x:t)) (tag_name (y:t)) CilType.Location.pretty !Goblint_tracing.current_loc pretty x pretty y let rec leq x y = match (x,y) with @@ -756,9 +756,9 @@ struct match exp, start_of_array_lval with | BinOp(IndexPI, Lval lval, add, _), (Var arr_start_var, NoOffset) when not (contains_pointer add) -> begin match ask.may_point_to (Lval lval) with - | v when LS.cardinal v = 1 && not (LS.is_top v) -> - begin match LS.choose v with - | (var,`Index (i,`NoOffset)) when Cil.isZero (Cil.constFold true i) && CilType.Varinfo.equal var arr_start_var -> + | v when AD.cardinal v = 1 && not (AD.is_top v) -> + begin match AD.choose v with + | AD.Addr.Addr (var,`Index (i,`NoOffset)) when ID.equal_to Z.zero i = `Eq && CilType.Varinfo.equal var arr_start_var -> (* The idea here is that if a must(!) point to arr and we do sth like a[i] we don't want arr to be partitioned according to (arr+i)-&a but according to i instead *) add | _ -> BinOp(MinusPP, exp, StartOf start_of_array_lval, !ptrdiffType) @@ -824,6 +824,8 @@ struct (* Funny, this does not compile without the final type annotation! *) let rec eval_offset (ask: VDQ.t) f (x: t) (offs:offs) (exp:exp option) (v:lval option) (t:typ): t = let rec do_eval_offset (ask:VDQ.t) f (x:t) (offs:offs) (exp:exp option) (l:lval option) (o:offset option) (v:lval option) (t:typ): t = + if M.tracing then M.traceli "eval_offset" "do_eval_offset %a %a (%a)\n" pretty x Offs.pretty offs (Pretty.docOpt (CilType.Exp.pretty ())) exp; + let r = match x, offs with | Blob((va, _, orig) as c), `Index (_, ox) -> begin @@ -886,6 +888,9 @@ struct | Top -> M.info ~category:Imprecise "Trying to read an index, but the array is unknown"; top () | _ -> M.warn ~category:Imprecise ~tags:[Category Program] "Trying to read an index, but was not given an array (%a)" pretty x; top () end + in + if M.tracing then M.traceu "eval_offset" "do_eval_offset -> %a\n" pretty r; + r in let l, o = match exp with | Some(Lval (x,o)) -> Some ((x, NoOffset)), Some(o) diff --git a/src/cdomains/basetype.ml b/src/common/cdomains/basetype.ml similarity index 92% rename from src/cdomains/basetype.ml rename to src/common/cdomains/basetype.ml index 55b5dbde07..1b846309aa 100644 --- a/src/cdomains/basetype.ml +++ b/src/common/cdomains/basetype.ml @@ -20,8 +20,6 @@ struct | _ -> Local let name () = "variables" let printXml f x = BatPrintf.fprintf f "\n\n%s\n\n\n" (XmlUtil.escape (show x)) - - let arbitrary () = MyCheck.Arbitrary.varinfo end module RawStrings: Printable.S with type t = string = @@ -35,12 +33,6 @@ struct let printXml f x = BatPrintf.fprintf f "\n\n%s\n\n\n" (XmlUtil.escape (show x)) end -module Strings: Lattice.S with type t = [`Bot | `Lifted of string | `Top] = - Lattice.Flat (RawStrings) (struct - let top_name = "?" - let bot_name = "-" - end) - module RawBools: Printable.S with type t = bool = struct include Printable.StdLeaf @@ -52,12 +44,6 @@ struct let printXml f x = BatPrintf.fprintf f "\n\n%s\n\n\n" (show x) end -module Bools: Lattice.S with type t = [`Bot | `Lifted of bool | `Top] = - Lattice.Flat (RawBools) (struct - let top_name = "?" - let bot_name = "-" - end) - module CilExp = struct include CilType.Exp diff --git a/src/common/common.mld b/src/common/common.mld new file mode 100644 index 0000000000..2ad88c3758 --- /dev/null +++ b/src/common/common.mld @@ -0,0 +1,62 @@ +{0 Library goblint.common} +This library is unwrapped and provides the following top-level modules. +For better context, see {!Goblint_lib} which also documents these modules. + + +{1 Framework} + +{2 CFG} +{!modules: +Node +Edge +MyCFG +CfgTools +} + +{2 Specification} +{!modules: +AnalysisState +ControlSpecC +} + + +{1 Domains} +{!modules: +Printable +} + +{2 Analysis-specific} + +{3 Other} +{!modules:Basetype} + + +{1 I/O} +{!modules: +Messages +} + + +{1 Utilities} +{!modules:Timing} + +{2 General} +{!modules: +LazyEval +ResettableLazy +MessageUtil +XmlUtil +} + +{2 CIL} +{!modules: +CilType +Cilfacade +RichVarinfo +} + + +{1 Library extensions} + +{2 Standard library} +{!modules:GobFormat} diff --git a/src/domains/printable.ml b/src/common/domains/printable.ml similarity index 89% rename from src/domains/printable.ml rename to src/common/domains/printable.ml index 1207d35db2..cc01718ee8 100644 --- a/src/domains/printable.ml +++ b/src/common/domains/printable.ml @@ -233,9 +233,9 @@ struct let arbitrary () = let open QCheck.Iter in let shrink = function - | `Lifted x -> (return `Bot) <+> (MyCheck.shrink (Base.arbitrary ()) x >|= lift) + | `Lifted x -> (return `Bot) <+> (GobQCheck.shrink (Base.arbitrary ()) x >|= lift) | `Bot -> empty - | `Top -> MyCheck.Iter.of_arbitrary ~n:20 (Base.arbitrary ()) >|= lift + | `Top -> GobQCheck.Iter.of_arbitrary ~n:20 (Base.arbitrary ()) >|= lift in QCheck.frequency ~shrink ~print:show [ 20, QCheck.map lift (Base.arbitrary ()); @@ -273,6 +273,40 @@ struct | `Right x -> `Right (Base2.relift x) end +module Either3 (Base1: S) (Base2: S) (Base3: S) = +struct + type t = [`Left of Base1.t | `Middle of Base2.t | `Right of Base3.t] [@@deriving eq, ord, hash] + include Std + + let pretty () (state:t) = + match state with + | `Left n -> Pretty.dprintf "%s:%a" (Base1.name ()) Base1.pretty n + | `Middle n -> Pretty.dprintf "%s:%a" (Base2.name ()) Base2.pretty n + | `Right n -> Pretty.dprintf "%s:%a" (Base3.name ()) Base3.pretty n + + let show state = + match state with + | `Left n -> (Base1.name ()) ^ ":" ^ Base1.show n + | `Middle n -> (Base2.name ()) ^ ":" ^ Base2.show n + | `Right n -> (Base3.name ()) ^ ":" ^ Base3.show n + + let name () = "either " ^ Base1.name () ^ " or " ^ Base2.name () ^ " or " ^ Base3.name () + let printXml f = function + | `Left x -> BatPrintf.fprintf f "\n\nLeft\n\n%a\n\n" Base1.printXml x + | `Middle x -> BatPrintf.fprintf f "\n\nMiddle\n\n%a\n\n" Base2.printXml x + | `Right x -> BatPrintf.fprintf f "\n\nRight\n\n%a\n\n" Base3.printXml x + + let to_yojson = function + | `Left x -> `Assoc [ Base1.name (), Base1.to_yojson x ] + | `Middle x -> `Assoc [ Base2.name (), Base2.to_yojson x ] + | `Right x -> `Assoc [ Base3.name (), Base3.to_yojson x ] + + let relift = function + | `Left x -> `Left (Base1.relift x) + | `Middle x -> `Middle (Base2.relift x) + | `Right x -> `Right (Base3.relift x) +end + module Option (Base: S) (N: Name) = struct type t = Base.t option [@@deriving eq, ord, hash] @@ -366,9 +400,17 @@ struct let pretty () (x,y) = if expand_fst || expand_snd then text "(" + ++ text (Base1.name ()) + ++ text ":" + ++ align ++ (if expand_fst then Base1.pretty () x else text (Base1.show x)) + ++ unalign ++ text ", " + ++ text (Base2.name ()) + ++ text ":" + ++ align ++ (if expand_snd then Base2.pretty () y else text (Base2.show y)) + ++ unalign ++ text ")" else text (show (x,y)) @@ -403,12 +445,24 @@ struct "(" ^ !first ^ ", " ^ !second ^ ", " ^ !third ^ ")" let pretty () (x,y,z) = - text "(" ++ - Base1.pretty () x - ++ text ", " ++ - Base2.pretty () y - ++ text ", " ++ - Base3.pretty () z + text "(" + ++ text (Base1.name ()) + ++ text ":" + ++ align + ++ Base1.pretty () x + ++ unalign + ++ text ", " + ++ text (Base2.name ()) + ++ text ":" + ++ align + ++ Base2.pretty () y + ++ unalign + ++ text ", " + ++ text (Base3.name ()) + ++ text ":" + ++ align + ++ Base3.pretty () z + ++ unalign ++ text ")" let printXml f (x,y,z) = @@ -572,8 +626,8 @@ struct let arbitrary () = let open QCheck.Iter in let shrink = function - | `Lifted x -> MyCheck.shrink (Base.arbitrary ()) x >|= lift - | `Top -> MyCheck.Iter.of_arbitrary ~n:20 (Base.arbitrary ()) >|= lift + | `Lifted x -> GobQCheck.shrink (Base.arbitrary ()) x >|= lift + | `Top -> GobQCheck.Iter.of_arbitrary ~n:20 (Base.arbitrary ()) >|= lift in QCheck.frequency ~shrink ~print:show [ 20, QCheck.map lift (Base.arbitrary ()); diff --git a/src/common/dune b/src/common/dune new file mode 100644 index 0000000000..7994798579 --- /dev/null +++ b/src/common/dune @@ -0,0 +1,25 @@ +(include_subdirs unqualified) + +(library + (name goblint_common) + (public_name goblint.common) + (wrapped false) ; TODO: wrap + (libraries + batteries.unthreaded + zarith + goblint_std + goblint_config + goblint_tracing + goblint-cil + fpath + yojson + goblint_timing + qcheck-core.runner) + (flags :standard -open Goblint_std) + (preprocess + (pps + ppx_deriving.std + ppx_deriving_hash + ppx_deriving_yojson))) + +(documentation) diff --git a/src/framework/analysisState.ml b/src/common/framework/analysisState.ml similarity index 57% rename from src/framework/analysisState.ml rename to src/common/framework/analysisState.ml index 0f3a9f55bc..fd76e1bb67 100644 --- a/src/framework/analysisState.ml +++ b/src/common/framework/analysisState.ml @@ -7,6 +7,20 @@ let should_warn = ref false (** Whether signed overflow or underflow happened *) let svcomp_may_overflow = ref false +(** Whether the termination analysis detects the program as non-terminating *) +let svcomp_may_not_terminate = ref false +(** Whether an invalid free happened *) +let svcomp_may_invalid_free = ref false + +(** Whether an invalid pointer dereference happened *) +let svcomp_may_invalid_deref = ref false + +(** Whether a memory leak occurred and there's no reference to the leaked memory *) +let svcomp_may_invalid_memtrack = ref false + +(** Whether a memory leak occurred *) +let svcomp_may_invalid_memcleanup = ref false + (** A hack to see if we are currently doing global inits *) let global_initialization = ref false diff --git a/src/framework/cfgTools.ml b/src/common/framework/cfgTools.ml similarity index 98% rename from src/framework/cfgTools.ml rename to src/common/framework/cfgTools.ml index 8f98a48e84..78aba17060 100644 --- a/src/framework/cfgTools.ml +++ b/src/common/framework/cfgTools.ml @@ -122,10 +122,6 @@ let rec pretty_edges () = function | [_,x] -> Edge.pretty_plain () x | (_,x)::xs -> Pretty.dprintf "%a; %a" Edge.pretty_plain x pretty_edges xs -let get_pseudo_return_id fd = - let start_id = 10_000_000_000 in (* TODO get max_sid? *) - let sid = Hashtbl.hash fd.svar.vid in (* Need pure sid instead of Cil.new_sid for incremental, similar to vid in Cilfacade.create_var. We only add one return stmt per loop, so the hash from the functions vid should be unique. *) - if sid < start_id then sid + start_id else sid let node_scc_global = NH.create 113 @@ -260,7 +256,7 @@ let createCFG (file: file) = if Messages.tracing then Messages.trace "cfg" "adding pseudo-return to the function %s.\n" fd.svar.vname; let fd_end_loc = {fd_loc with line = fd_loc.endLine; byte = fd_loc.endByte; column = fd_loc.endColumn} in let newst = mkStmt (Return (None, fd_end_loc)) in - newst.sid <- get_pseudo_return_id fd; + newst.sid <- Cilfacade.get_pseudo_return_id fd; Cilfacade.StmtH.add Cilfacade.pseudo_return_to_fun newst fd; Cilfacade.IntH.replace Cilfacade.pseudo_return_stmt_sids newst.sid newst; let newst_node = Statement newst in @@ -475,7 +471,7 @@ let createCFG (file: file) = ); if Messages.tracing then Messages.trace "cfg" "CFG building finished.\n\n"; if get_bool "dbg.verbose" then - ignore (Pretty.eprintf "cfgF (%a), cfgB (%a)\n" GobHashtbl.pretty_statistics (GobHashtbl.magic_stats cfgF) GobHashtbl.pretty_statistics (GobHashtbl.magic_stats cfgB)); + ignore (Pretty.eprintf "cfgF (%a), cfgB (%a)\n" GobHashtbl.pretty_statistics (NH.stats cfgF) GobHashtbl.pretty_statistics (NH.stats cfgB)); cfgF, cfgB, skippedByEdge let createCFG = Timing.wrap "createCFG" createCFG @@ -685,7 +681,7 @@ let getGlobalInits (file: file) : edges = lval in let rec any_index_offset = function - | Index (e,o) -> Index (Offset.Index.Exp.any, any_index_offset o) + | Index (e,o) -> Index (Cilfacade.any_index_exp, any_index_offset o) | Field (f,o) -> Field (f, any_index_offset o) | NoOffset -> NoOffset in diff --git a/src/framework/controlSpecC.ml b/src/common/framework/controlSpecC.ml similarity index 100% rename from src/framework/controlSpecC.ml rename to src/common/framework/controlSpecC.ml diff --git a/src/framework/controlSpecC.mli b/src/common/framework/controlSpecC.mli similarity index 100% rename from src/framework/controlSpecC.mli rename to src/common/framework/controlSpecC.mli diff --git a/src/framework/edge.ml b/src/common/framework/edge.ml similarity index 100% rename from src/framework/edge.ml rename to src/common/framework/edge.ml diff --git a/src/framework/myCFG.ml b/src/common/framework/myCFG.ml similarity index 100% rename from src/framework/myCFG.ml rename to src/common/framework/myCFG.ml diff --git a/src/framework/node.ml b/src/common/framework/node.ml similarity index 100% rename from src/framework/node.ml rename to src/common/framework/node.ml diff --git a/src/framework/node0.ml b/src/common/framework/node0.ml similarity index 100% rename from src/framework/node0.ml rename to src/common/framework/node0.ml diff --git a/src/incremental/updateCil0.ml b/src/common/incremental/updateCil0.ml similarity index 100% rename from src/incremental/updateCil0.ml rename to src/common/incremental/updateCil0.ml diff --git a/src/util/cilType.ml b/src/common/util/cilType.ml similarity index 100% rename from src/util/cilType.ml rename to src/common/util/cilType.ml diff --git a/src/util/cilfacade.ml b/src/common/util/cilfacade.ml similarity index 78% rename from src/util/cilfacade.ml rename to src/common/util/cilfacade.ml index d0dcc428ad..eff97da404 100644 --- a/src/util/cilfacade.ml +++ b/src/common/util/cilfacade.ml @@ -40,7 +40,8 @@ let is_first_field x = match x.fcomp.cfields with 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" + Cil.gnu89inline := get_bool "cil.gnu89inline"; + Cabs2cil.addNestedScopeAttr := get_bool "cil.addNestedScopeAttr" let init () = initCIL (); @@ -51,7 +52,8 @@ let init () = (* lineDirectiveStyle := None; *) RmUnused.keepUnused := true; print_CIL_Input := true; - Cabs2cil.allowDuplication := false + Cabs2cil.allowDuplication := false; + Cabs2cil.silenceLongDoubleWarning := true let current_file = ref dummyFile @@ -72,19 +74,18 @@ let print (fileAST: file) = let rmTemps fileAST = RmUnused.removeUnused fileAST - let visitors = ref [] let register_preprocess name visitor_fun = visitors := !visitors @ [name, visitor_fun] let do_preprocess ast = - let f fd (name, visitor_fun) = - (* this has to be done here, since the settings aren't available when register_preprocess is called *) - if List.mem name (get_string_list "ana.activated") then - ignore @@ visitCilFunction (visitor_fun fd) fd - in - iterGlobals ast (function GFun (fd,_) -> List.iter (f fd) !visitors | _ -> ()) - + (* this has to be done here, since the settings aren't available when register_preprocess is called *) + let active_visitors = List.filter_map (fun (name, visitor_fun) -> if List.mem name (get_string_list "ana.activated") then Some visitor_fun else None) !visitors in + let f fd visitor_fun = ignore @@ visitCilFunction (visitor_fun fd) fd in + if active_visitors <> [] then + iterGlobals ast (function GFun (fd,_) -> List.iter (f fd) active_visitors | _ -> ()) + else + () (** @raise GoblintCil.FrontC.ParseError @raise GoblintCil.Errormsg.Error *) @@ -322,6 +323,15 @@ and typeOffset basetyp = | t -> raise (TypeOfError (Field_NonCompound (fi, t))) +let typeBlendAttributes baseAttrs = (* copied from Cilfacade.typeOffset *) + let (_, _, contageous) = partitionAttributes ~default:AttrName baseAttrs in + typeAddAttributes contageous + +let typeSigBlendAttributes baseAttrs = + let (_, _, contageous) = partitionAttributes ~default:AttrName baseAttrs in + typeSigAddAttrs contageous + + (** {!Cil.mkCast} using our {!typeOf}. *) let mkCast ~(e: exp) ~(newt: typ) = let oldt = @@ -342,22 +352,118 @@ let makeBinOp binop e1 e2 = let (_, e) = Cabs2cil.doBinOp binop e1 t1 e2 t2 in e -let anoncomp_name_regexp = Str.regexp {|^__anon\(struct\|union\)_\(.+\)_\([0-9]+\)$|} +let anoncomp_name_regexp = Str.regexp {|^__anon\(struct\|union\)\(_\(.+\)\)?_\([0-9]+\)$|} let split_anoncomp_name name = (* __anonunion_pthread_mutexattr_t_488594144 *) + (* __anonunion_50 *) if Str.string_match anoncomp_name_regexp name 0 then ( let struct_ = match Str.matched_group 1 name with | "struct" -> true | "union" -> false | _ -> assert false in - let name' = Str.matched_group 2 name in - let id = int_of_string (Str.matched_group 3 name) in + let name' = try Some (Str.matched_group 3 name) with Not_found -> None in + let id = int_of_string (Str.matched_group 4 name) in (struct_, name', id) ) else - invalid_arg "Cilfacade.split_anoncomp_name" + invalid_arg ("Cilfacade.split_anoncomp_name: " ^ name) + +(** Pretty-print typsig like typ, because + {!d_typsig} prints with CIL constructors. *) +let rec pretty_typsig_like_typ (nameOpt: Pretty.doc option) () ts = + (* Copied & modified from Cil.defaultCilPrinterClass#pType. *) + let open Pretty in + let name = match nameOpt with None -> nil | Some d -> d in + let printAttributes (a: attributes) = + let pa = d_attrlist () a in + match nameOpt with + | None when not !print_CIL_Input -> + (* Cannot print the attributes in this case because gcc does not + like them here, except if we are printing for CIL. *) + if pa = nil then nil else + text "/*" ++ pa ++ text "*/" + | _ -> pa + in + match ts with + | TSBase t -> defaultCilPrinter#pType nameOpt () t + | TSComp (cstruct, cname, a) -> + let su = if cstruct then "struct" else "union" in + text (su ^ " " ^ cname ^ " ") + ++ d_attrlist () a + ++ name + | TSEnum (ename, a) -> + text ("enum " ^ ename ^ " ") + ++ d_attrlist () a + ++ name + | TSPtr (bt, a) -> + (* Parenthesize the ( * attr name) if a pointer to a function or an + array. *) + let (paren: doc option), (bt': typsig) = + match bt with + | TSFun _ | TSArray _ -> Some (text "("), bt + | _ -> None, bt + in + let name' = text "*" ++ printAttributes a ++ name in + let name'' = (* Put the parenthesis *) + match paren with + Some p -> p ++ name' ++ text ")" + | _ -> name' + in + pretty_typsig_like_typ + (Some name'') + () + bt' + + | TSArray (elemt, lo, a) -> + (* 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 + text "(" ++ printAttributes a' ++ name ++ text ")" + in + pretty_typsig_like_typ + (Some (name' + ++ text "[" + ++ (match lo with None -> nil | Some e -> text (Z.to_string e)) + ++ text "]")) + () + elemt + + | TSFun (restyp, args, isvararg, a) -> + let name' = + if a == [] then name else + if nameOpt == None then printAttributes a else + text "(" ++ printAttributes a ++ name ++ text ")" + in + pretty_typsig_like_typ + (Some + (name' + ++ text "(" + ++ (align + ++ + (if args = Some [] && isvararg then + text "..." + else + (if args = None then nil + else if args = Some [] then text "void" + else + let pArg atype = + (pretty_typsig_like_typ None () atype) + in + (docList ~sep:(chr ',' ++ break) pArg) () + (match args with None -> [] | Some args -> args)) + ++ (if isvararg then break ++ text ", ..." else nil)) + ++ unalign) + ++ text ")")) + () + restyp + +(** Pretty-print typsig like typ, because + {!d_typsig} prints with CIL constructors. *) +let pretty_typsig_like_typ = pretty_typsig_like_typ None (** HashSet of line numbers *) let locs = Hashtbl.create 200 @@ -425,6 +531,12 @@ let stmt_fundecs: fundec StmtH.t ResettableLazy.t = h ) + +let get_pseudo_return_id fd = + let start_id = 10_000_000_000 in (* TODO get max_sid? *) + let sid = Hashtbl.hash fd.svar.vid in (* Need pure sid instead of Cil.new_sid for incremental, similar to vid in Cilfacade.create_var. We only add one return stmt per loop, so the hash from the functions vid should be unique. *) + if sid < start_id then sid + start_id else sid + let pseudo_return_to_fun = StmtH.create 113 (** Find [fundec] which the [stmt] is in. *) @@ -560,9 +672,16 @@ let find_stmt_sid sid = try IntH.find pseudo_return_stmt_sids sid with Not_found -> IntH.find (ResettableLazy.force stmt_sids) sid +module FunLocH = Hashtbl.Make(CilType.Fundec) +module LocSet = Hashtbl.Make(CilType.Location) + +(** Contains the locations of the upjumping gotos and the respective functions + * they are being called in. *) +let funs_with_upjumping_gotos: unit LocSet.t FunLocH.t = FunLocH.create 13 -let reset_lazy () = +let reset_lazy ?(keepupjumpinggotos=false) () = StmtH.clear pseudo_return_to_fun; + if not keepupjumpinggotos then FunLocH.clear funs_with_upjumping_gotos; ResettableLazy.reset stmt_fundecs; ResettableLazy.reset varinfo_fundecs; ResettableLazy.reset name_fundecs; @@ -594,3 +713,9 @@ let add_function_declarations (file: Cil.file): unit = let fun_decls = List.filter_map declaration_from_GFun functions in let globals = upto_last_type @ fun_decls @ non_types @ functions in file.globals <- globals + + +(** Special index expression for some unknown index. + Weakly updates array in assignment. + Used for [exp.fast_global_inits]. *) +let any_index_exp = CastE (TInt (ptrdiff_ikind (), []), mkString "any_index") (* TODO: move back to Offset *) diff --git a/src/util/cilfacade0.ml b/src/common/util/cilfacade0.ml similarity index 100% rename from src/util/cilfacade0.ml rename to src/common/util/cilfacade0.ml diff --git a/src/util/gobFormat.ml b/src/common/util/gobFormat.ml similarity index 100% rename from src/util/gobFormat.ml rename to src/common/util/gobFormat.ml diff --git a/src/util/lazyEval.ml b/src/common/util/lazyEval.ml similarity index 79% rename from src/util/lazyEval.ml rename to src/common/util/lazyEval.ml index e49a5f4693..9007cdd089 100644 --- a/src/util/lazyEval.ml +++ b/src/common/util/lazyEval.ml @@ -5,10 +5,10 @@ Node -> CilType -> Printable -> Goblintutil -> GobConfig -> Tracing -> Node *) module Make (M : sig - type t - type result - val eval : t -> result -end) : sig + type t + type result + val eval : t -> result + end) : sig type t val make : M.t -> t val force : t -> M.result @@ -20,8 +20,8 @@ end = struct let force l = match l.value with | `Closure arg -> - let v = M.eval arg in - l.value <- `Computed v; - v + let v = M.eval arg in + l.value <- `Computed v; + v | `Computed v -> v end diff --git a/src/util/messageCategory.ml b/src/common/util/messageCategory.ml similarity index 92% rename from src/util/messageCategory.ml rename to src/common/util/messageCategory.ml index 5452225c26..41c9bc08e1 100644 --- a/src/util/messageCategory.ml +++ b/src/common/util/messageCategory.ml @@ -11,8 +11,10 @@ type undefined_behavior = | ArrayOutOfBounds of array_oob | NullPointerDereference | UseAfterFree + | MemoryOutOfBoundsAccess | DoubleFree | InvalidMemoryDeallocation + | MemoryLeak | Uninitialized | DoubleLocking | Other @@ -44,6 +46,7 @@ type category = | Imprecise | Witness | Program + | Termination [@@deriving eq, ord, hash] type t = category [@@deriving eq, ord, hash] @@ -65,8 +68,10 @@ struct let array_out_of_bounds e: category = create @@ ArrayOutOfBounds e let nullpointer_dereference: category = create @@ NullPointerDereference let use_after_free: category = create @@ UseAfterFree + let memory_out_of_bounds_access: category = create @@ MemoryOutOfBoundsAccess let double_free: category = create @@ DoubleFree let invalid_memory_deallocation: category = create @@ InvalidMemoryDeallocation + let memory_leak: category = create @@ MemoryLeak let uninitialized: category = create @@ Uninitialized let double_locking: category = create @@ DoubleLocking let other: category = create @@ Other @@ -103,6 +108,7 @@ struct | "array_out_of_bounds" -> ArrayOutOfBounds.from_string_list t | "nullpointer_dereference" -> nullpointer_dereference | "use_after_free" -> use_after_free + | "memory_out_of_bounds_access" -> memory_out_of_bounds_access | "double_free" -> double_free | "invalid_memory_deallocation" -> invalid_memory_deallocation | "uninitialized" -> uninitialized @@ -115,8 +121,10 @@ struct | ArrayOutOfBounds e -> "ArrayOutOfBounds" :: ArrayOutOfBounds.path_show e | NullPointerDereference -> ["NullPointerDereference"] | UseAfterFree -> ["UseAfterFree"] + | MemoryOutOfBoundsAccess -> ["MemoryOutOfBoundsAccess"] | DoubleFree -> ["DoubleFree"] | InvalidMemoryDeallocation -> ["InvalidMemoryDeallocation"] + | MemoryLeak -> ["MemoryLeak"] | Uninitialized -> ["Uninitialized"] | DoubleLocking -> ["DoubleLocking"] | Other -> ["Other"] @@ -197,6 +205,7 @@ let should_warn e = | Imprecise -> "imprecise" | Witness -> "witness" | Program -> "program" + | Termination -> "termination" (* Don't forget to add option to schema! *) in get_bool ("warn." ^ (to_string e)) @@ -217,6 +226,7 @@ let path_show e = | Imprecise -> ["Imprecise"] | Witness -> ["Witness"] | Program -> ["Program"] + | Termination -> ["Termination"] let show x = String.concat " > " (path_show x) @@ -226,8 +236,10 @@ let behaviorName = function |Undefined u -> match u with |NullPointerDereference -> "NullPointerDereference" |UseAfterFree -> "UseAfterFree" + |MemoryOutOfBoundsAccess -> "MemoryOutOfBoundsAccess" |DoubleFree -> "DoubleFree" |InvalidMemoryDeallocation -> "InvalidMemoryDeallocation" + |MemoryLeak -> "MemoryLeak" |Uninitialized -> "Uninitialized" |DoubleLocking -> "DoubleLocking" |Other -> "Other" @@ -251,9 +263,10 @@ let categoryName = function | Behavior x -> behaviorName x | Integer x -> (match x with - | Overflow -> "Overflow"; - | DivByZero -> "DivByZero") + | Overflow -> "Overflow"; + | DivByZero -> "DivByZero") | Float -> "Float" + | Termination -> "Termination" let from_string_list (s: string list) = @@ -274,6 +287,7 @@ let from_string_list (s: string list) = | "imprecise" -> Imprecise | "witness" -> Witness | "program" -> Program + | "termination" -> Termination | _ -> Unknown let to_yojson x = `List (List.map (fun x -> `String x) (path_show x)) diff --git a/src/util/messageUtil.ml b/src/common/util/messageUtil.ml similarity index 100% rename from src/util/messageUtil.ml rename to src/common/util/messageUtil.ml diff --git a/src/util/messages.ml b/src/common/util/messages.ml similarity index 89% rename from src/util/messages.ml rename to src/common/util/messages.ml index a06a183eee..d7afec43c5 100644 --- a/src/util/messages.ml +++ b/src/common/util/messages.ml @@ -248,12 +248,24 @@ let add m = Table.add m ) +let final_table: unit Table.MH.t = Table.MH.create 13 + +let add_final m = + Table.MH.replace final_table m () + let finalize () = if get_bool "warn.deterministic" then ( !Table.messages_list |> List.sort Message.compare |> List.iter print - ) + ); + Table.MH.to_seq_keys final_table + |> List.of_seq + |> List.sort Message.compare + |> List.iter (fun m -> + print m; + Table.add m + ) let current_context: ControlSpecC.t option ref = ref None @@ -282,7 +294,7 @@ let msg_noloc severity ?(tags=[]) ?(category=Category.Unknown) fmt = if !AnalysisState.should_warn && Severity.should_warn severity && (Category.should_warn category || Tags.should_warn tags) then ( let finish doc = let text = GobPretty.show doc in - add {tags = Category category :: tags; severity; multipiece = Single {loc = None; text; context = msg_context ()}} + add {tags = Category category :: tags; severity; multipiece = Single {loc = None; text; context = None}} in Pretty.gprintf finish fmt ) @@ -316,4 +328,34 @@ let debug_noloc ?tags = msg_noloc Debug ?tags let success ?loc = msg Success ?loc let success_noloc ?tags = msg_noloc Success ?tags -include Tracing +let msg_final severity ?(tags=[]) ?(category=Category.Unknown) fmt = + if !AnalysisState.should_warn then ( + let finish doc = + let text = GobPretty.show doc in + add_final {tags = Category category :: tags; severity; multipiece = Single {loc = None; text; context = None}} + in + Pretty.gprintf finish fmt + ) + else + GobPretty.igprintf () fmt + + +include Goblint_tracing + +open Pretty + +let tracel sys ?var fmt = + let loc = !current_loc in + let docloc sys doc = + printtrace sys (dprintf "(%a)@?" CilType.Location.pretty loc ++ indent 2 doc); + in + gtrace true docloc sys var ~loc ignore fmt + +let traceli sys ?var ?(subsys=[]) fmt = + let loc = !current_loc in + let g () = activate sys subsys in + let docloc sys doc: unit = + printtrace sys (dprintf "(%a)" CilType.Location.pretty loc ++ indent 2 doc); + traceIndent () + in + gtrace true docloc sys var ~loc g fmt diff --git a/src/util/resettableLazy.ml b/src/common/util/resettableLazy.ml similarity index 100% rename from src/util/resettableLazy.ml rename to src/common/util/resettableLazy.ml diff --git a/src/util/resettableLazy.mli b/src/common/util/resettableLazy.mli similarity index 100% rename from src/util/resettableLazy.mli rename to src/common/util/resettableLazy.mli diff --git a/src/util/richVarinfo.ml b/src/common/util/richVarinfo.ml similarity index 100% rename from src/util/richVarinfo.ml rename to src/common/util/richVarinfo.ml diff --git a/src/util/richVarinfo.mli b/src/common/util/richVarinfo.mli similarity index 100% rename from src/util/richVarinfo.mli rename to src/common/util/richVarinfo.mli diff --git a/src/util/timing.ml b/src/common/util/timing.ml similarity index 100% rename from src/util/timing.ml rename to src/common/util/timing.ml diff --git a/src/util/xmlUtil.ml b/src/common/util/xmlUtil.ml similarity index 100% rename from src/util/xmlUtil.ml rename to src/common/util/xmlUtil.ml diff --git a/src/util/afterConfig.ml b/src/config/afterConfig.ml similarity index 100% rename from src/util/afterConfig.ml rename to src/config/afterConfig.ml diff --git a/src/config/config.mld b/src/config/config.mld new file mode 100644 index 0000000000..160eaa9a11 --- /dev/null +++ b/src/config/config.mld @@ -0,0 +1,14 @@ +{0 Library goblint.config} +This library is unwrapped and provides the following top-level modules. +For better context, see {!Goblint_lib} which also documents these modules. + + +{1 Framework} + +{2 Configuration} +{!modules: +GobConfig +AfterConfig +JsonSchema +Options +} diff --git a/src/config/dune b/src/config/dune new file mode 100644 index 0000000000..1508e2553e --- /dev/null +++ b/src/config/dune @@ -0,0 +1,23 @@ +(include_subdirs no) + +(library + (name goblint_config) + (public_name goblint.config) + (wrapped false) ; TODO: wrap + (libraries + batteries.unthreaded + goblint_std + goblint_tracing + fpath + yojson + json-data-encoding + cpu + goblint.sites + qcheck-core.runner) + (flags :standard -open Goblint_std) + (preprocess + (pps + ppx_blob)) + (preprocessor_deps (file options.schema.json))) + +(documentation) diff --git a/src/util/gobConfig.ml b/src/config/gobConfig.ml similarity index 96% rename from src/util/gobConfig.ml rename to src/config/gobConfig.ml index c517ba150d..24a1701ce6 100644 --- a/src/util/gobConfig.ml +++ b/src/config/gobConfig.ml @@ -21,7 +21,6 @@ *) open Batteries -open Tracing open Printf exception ConfigError of string @@ -300,7 +299,7 @@ struct try let st = String.trim st in let x = get_value !json_conf (parse_path st) in - if tracing then trace "conf-reads" "Reading '%s', it is %a.\n" st GobYojson.pretty x; + if Goblint_tracing.tracing then Goblint_tracing.trace "conf-reads" "Reading '%s', it is %a.\n" st GobYojson.pretty x; try f x with Yojson.Safe.Util.Type_error (s, _) -> eprintf "The value for '%s' has the wrong type: %s\n" st s; @@ -332,7 +331,7 @@ struct let wrap_get f x = (* self-observe options, which Spec construction depends on *) - if !building_spec && Tracing.tracing then Tracing.trace "config" "get during building_spec: %s\n" x; + if !building_spec && Goblint_tracing.tracing then Goblint_tracing.trace "config" "get during building_spec: %s\n" x; (* TODO: blacklist such building_spec option from server mode modification since it will have no effect (spec is already built) *) f x @@ -352,7 +351,7 @@ struct (** Helper function for writing values. Handles the tracing. *) let set_path_string st v = - if tracing then trace "conf" "Setting '%s' to %a.\n" st GobYojson.pretty v; + if Goblint_tracing.tracing then Goblint_tracing.trace "conf" "Setting '%s' to %a.\n" st GobYojson.pretty v; set_value v json_conf (parse_path st) let set_json st j = @@ -402,7 +401,7 @@ struct | Some fn -> let v = Yojson.Safe.from_channel % BatIO.to_input_channel |> File.with_file_in (Fpath.to_string fn) in merge v; - if tracing then trace "conf" "Merging with '%a', resulting\n%a.\n" GobFpath.pretty fn GobYojson.pretty !json_conf + if Goblint_tracing.tracing then Goblint_tracing.trace "conf" "Merging with '%a', resulting\n%a.\n" GobFpath.pretty fn GobYojson.pretty !json_conf | None -> raise (Sys_error (Printf.sprintf "%s: No such file or diretory" (Fpath.to_string fn))) end diff --git a/src/util/jsonSchema.ml b/src/config/jsonSchema.ml similarity index 100% rename from src/util/jsonSchema.ml rename to src/config/jsonSchema.ml diff --git a/src/util/options.ml b/src/config/options.ml similarity index 98% rename from src/util/options.ml rename to src/config/options.ml index d352c86465..125da3330b 100644 --- a/src/util/options.ml +++ b/src/config/options.ml @@ -1,4 +1,4 @@ -(** [src/util/options.schema.json] low-level access. *) +(** [src/config/options.schema.json] low-level access. *) open Json_schema diff --git a/src/util/options.schema.json b/src/config/options.schema.json similarity index 93% rename from src/util/options.schema.json rename to src/config/options.schema.json index c2b895d608..ba43953fea 100644 --- a/src/util/options.schema.json +++ b/src/config/options.schema.json @@ -288,6 +288,12 @@ "type": "boolean", "description": "Indicates whether gnu89 semantic should be used for inline functions.", "default": false + }, + "addNestedScopeAttr": { + "title": "cil.addNestedScopeAttr", + "type": "boolean", + "description": "Indicates whether variables that CIL pulls out of their scope should be marked.", + "default": false } }, "additionalProperties": false @@ -346,7 +352,7 @@ "description": "List of path-sensitive analyses", "type": "array", "items": { "type": "string" }, - "default": [ "mutex", "malloc_null", "uninit", "expsplit","activeSetjmp" ] + "default": [ "mutex", "malloc_null", "uninit", "expsplit","activeSetjmp","memLeak" ] }, "ctx_insens": { "title": "ana.ctx_insens", @@ -461,32 +467,6 @@ }, "additionalProperties": false }, - "file": { - "title": "ana.file", - "type": "object", - "properties": { - "optimistic": { - "title": "ana.file.optimistic", - "description": "Assume fopen never fails.", - "type": "boolean", - "default": false - } - }, - "additionalProperties": false - }, - "spec": { - "title": "ana.spec", - "type": "object", - "properties": { - "file": { - "title": "ana.spec.file", - "description": "Path to the specification file.", - "type": "string", - "default": "" - } - }, - "additionalProperties": false - }, "pml": { "title": "ana.pml", "type": "object", @@ -536,9 +516,39 @@ "title": "ana.autotune.activated", "description": "Lists of activated tuning options.", "type": "array", - "items": { "type": "string" }, + "items": { + "type": "string", + "enum": [ + "congruence", + "singleThreaded", + "specification", + "mallocWrappers", + "noRecursiveIntervals", + "enums", + "loopUnrollHeuristic", + "forceLoopUnrollForFewLoops", + "arrayDomain", + "octagon", + "wideningThresholds", + "memsafetySpecification", + "termination", + "tmpSpecialAnalysis" + ] + }, "default": [ - "congruence", "singleThreaded", "specification", "mallocWrappers", "noRecursiveIntervals", "enums", "loopUnrollHeuristic", "arrayDomain", "octagon", "wideningThresholds" + "congruence", + "singleThreaded", + "specification", + "mallocWrappers", + "noRecursiveIntervals", + "enums", + "loopUnrollHeuristic", + "arrayDomain", + "octagon", + "wideningThresholds", + "memsafetySpecification", + "termination", + "tmpSpecialAnalysis" ] } }, @@ -619,11 +629,19 @@ }, "additionalProperties": false }, - "limit-string-addresses": { - "title": "ana.base.limit-string-addresses", - "description": "Limit abstract address sets to keep at most one distinct string pointer.", - "type": "boolean", - "default": true + "strings": { + "title": "ana.base.strings", + "type": "object", + "properties": { + "domain": { + "title": "ana.base.strings.domain", + "description": "Domain for string literals.", + "type": "string", + "enum": ["unit", "flat", "disjoint"], + "default": "flat" + } + }, + "additionalProperties": false }, "partition-arrays": { "title": "ana.base.partition-arrays", @@ -1002,6 +1020,12 @@ "type": "boolean", "default": true }, + "call": { + "title": "ana.race.call", + "description": "Report races for thread-unsafe function calls.", + "type": "boolean", + "default": true + }, "direct-arithmetic": { "title": "ana.race.direct-arithmetic", "description": "Collect and distribute direct (i.e. not in a field) accesses to arithmetic types.", @@ -1284,7 +1308,10 @@ "goblint", "sv-comp", "ncurses", - "zstd" + "zstd", + "pcre", + "zlib", + "liblzma" ] }, "default": [ @@ -1317,6 +1344,13 @@ "type": "boolean", "default": true }, + "call": { + "title": "sem.unknown_function.call", + "description": + "Unknown function call calls reachable functions", + "type": "boolean", + "default": true + }, "invalidate": { "title": "sem.unknown_function.invalidate", "type": "object", @@ -2103,6 +2137,12 @@ "type": "boolean", "default": true }, + "termination": { + "title": "warn.termination", + "description": "Non-Termination warning", + "type": "boolean", + "default": true + }, "unknown": { "title": "warn.unknown", "description": "Unknown (of string) warnings", @@ -2156,6 +2196,25 @@ "description": "Output messages in deterministic order. Useful for cram testing.", "type": "boolean", "default": false + }, + "memleak": { + "title": "warn.memleak", + "type":"object", + "properties": { + "memcleanup": { + "title": "warn.memleak.memcleanup", + "description": "Enable memory leak warnings only for violations of the SV-COMP \"valid-memcleanup\" category", + "type": "boolean", + "default": false + }, + "memtrack": { + "title": "warn.memleak.memtrack", + "description": "Enable memory leak warnings only for violations of the SV-COMP \"valid-memtrack\" category", + "type": "boolean", + "default": false + } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -2277,24 +2336,56 @@ "title": "witness", "type": "object", "properties": { - "enabled": { - "title": "witness.enabled", - "description": "Output witness", - "type": "boolean", - "default": true - }, - "path": { - "title": "witness.path", - "description": "Witness output path", - "type": "string", - "default": "witness.graphml" - }, - "id": { - "title": "witness.id", - "description": "Which witness node IDs to use? node/enumerate", - "type": "string", - "enum": ["node", "enumerate"], - "default": "node" + "graphml": { + "title": "witness.graphml", + "type": "object", + "properties": { + "enabled": { + "title": "witness.graphml.enabled", + "description": "Output GraphML witness", + "type": "boolean", + "default": false + }, + "path": { + "title": "witness.graphml.path", + "description": "GraphML witness output path", + "type": "string", + "default": "witness.graphml" + }, + "id": { + "title": "witness.graphml.id", + "description": "Which witness node IDs to use? node/enumerate", + "type": "string", + "enum": ["node", "enumerate"], + "default": "node" + }, + "minimize": { + "title": "witness.graphml.minimize", + "description": "Try to minimize the witness", + "type": "boolean", + "default": false + }, + "uncil": { + "title": "witness.graphml.uncil", + "description": + "Try to undo CIL control flow transformations in witness", + "type": "boolean", + "default": false + }, + "stack": { + "title": "witness.graphml.stack", + "description": "Construct stacktrace-based witness nodes", + "type": "boolean", + "default": true + }, + "unknown": { + "title": "witness.graphml.unknown", + "description": "Output witness for unknown result", + "type": "boolean", + "default": true + } + }, + "additionalProperties": false }, "invariant": { "title": "witness.invariant", @@ -2331,7 +2422,7 @@ "title": "witness.invariant.accessed", "description": "Only emit invariants for locally accessed variables", "type": "boolean", - "default": true + "default": false }, "full": { "title": "witness.invariant.full", @@ -2374,31 +2465,6 @@ }, "additionalProperties": false }, - "minimize": { - "title": "witness.minimize", - "description": "Try to minimize the witness", - "type": "boolean", - "default": false - }, - "uncil": { - "title": "witness.uncil", - "description": - "Try to undo CIL control flow transformations in witness", - "type": "boolean", - "default": false - }, - "stack": { - "title": "witness.stack", - "description": "Construct stacktrace-based witness nodes", - "type": "boolean", - "default": true - }, - "unknown": { - "title": "witness.unknown", - "description": "Output witness for unknown result", - "type": "boolean", - "default": true - }, "yaml": { "title": "witness.yaml", "type": "object", @@ -2409,6 +2475,16 @@ "type": "boolean", "default": false }, + "format-version": { + "title": "witness.yaml.format-version", + "description": "YAML witness format version", + "type": "string", + "enum": [ + "0.1", + "2.0" + ], + "default": "0.1" + }, "entry-types": { "title": "witness.yaml.entry-types", "description": "YAML witness entry types to output/input.", @@ -2421,7 +2497,8 @@ "flow_insensitive_invariant", "precondition_loop_invariant", "loop_invariant_certificate", - "precondition_loop_invariant_certificate" + "precondition_loop_invariant_certificate", + "invariant_set" ] }, "default": [ @@ -2429,7 +2506,24 @@ "loop_invariant", "flow_insensitive_invariant", "loop_invariant_certificate", - "precondition_loop_invariant_certificate" + "precondition_loop_invariant_certificate", + "invariant_set" + ] + }, + "invariant-types": { + "title": "witness.yaml.invariant-types", + "description": "YAML witness invariant types to output/input.", + "type": "array", + "items": { + "type": "string", + "enum": [ + "location_invariant", + "loop_invariant" + ] + }, + "default": [ + "location_invariant", + "loop_invariant" ] }, "path": { @@ -2444,6 +2538,12 @@ "type": "string", "default": "" }, + "strict": { + "title": "witness.yaml.strict", + "description": "", + "type": "boolean", + "default": false + }, "unassume": { "title": "witness.yaml.unassume", "description": "YAML witness input path", diff --git a/src/domains/boolDomain.ml b/src/domain/boolDomain.ml similarity index 72% rename from src/domains/boolDomain.ml rename to src/domain/boolDomain.ml index e088c3605c..08be66a602 100644 --- a/src/domains/boolDomain.ml +++ b/src/domain/boolDomain.ml @@ -4,10 +4,10 @@ module Bool = struct include Basetype.RawBools (* type t = bool - let equal = Bool.equal - let compare = Bool.compare - let relift x = x - let arbitrary () = QCheck.bool *) + let equal = Bool.equal + let compare = Bool.compare + let relift x = x + let arbitrary () = QCheck.bool *) let pretty_diff () (x,y) = GoblintCil.Pretty.dprintf "%s: %a not leq %a" (name ()) pretty x pretty y end @@ -38,4 +38,10 @@ struct let widen = (&&) let meet = (||) let narrow = (||) -end \ No newline at end of file +end + +module FlatBool: Lattice.S with type t = [`Bot | `Lifted of bool | `Top] = + Lattice.Flat (Bool) (struct + let top_name = "?" + let bot_name = "-" + end) diff --git a/src/domains/disjointDomain.ml b/src/domain/disjointDomain.ml similarity index 100% rename from src/domains/disjointDomain.ml rename to src/domain/disjointDomain.ml diff --git a/src/domain/domain.mld b/src/domain/domain.mld new file mode 100644 index 0000000000..ce7e1a5859 --- /dev/null +++ b/src/domain/domain.mld @@ -0,0 +1,21 @@ +{0 Library goblint.domain} +This library is unwrapped and provides the following top-level modules. +For better context, see {!Goblint_lib} which also documents these modules. + + +{1 Domains} +{!modules: +Lattice +} + +{2 General} +{!modules: +BoolDomain +SetDomain +MapDomain +TrieDomain +DisjointDomain +HoareDomain +PartitionDomain +FlagHelper +} diff --git a/src/domain/dune b/src/domain/dune new file mode 100644 index 0000000000..169f4a1d5c --- /dev/null +++ b/src/domain/dune @@ -0,0 +1,19 @@ +(include_subdirs no) + +(library + (name goblint_domain) + (public_name goblint.domain) + (wrapped false) ; TODO: wrap + (libraries + batteries.unthreaded + goblint_std + goblint_common + goblint-cil) + (flags :standard -open Goblint_std) + (preprocess + (pps + ppx_deriving.std + ppx_deriving_hash + ppx_deriving_yojson))) + +(documentation) diff --git a/src/domains/flagHelper.ml b/src/domain/flagHelper.ml similarity index 100% rename from src/domains/flagHelper.ml rename to src/domain/flagHelper.ml diff --git a/src/domains/hoareDomain.ml b/src/domain/hoareDomain.ml similarity index 97% rename from src/domains/hoareDomain.ml rename to src/domain/hoareDomain.ml index 23b1a92240..37b8231b92 100644 --- a/src/domains/hoareDomain.ml +++ b/src/domain/hoareDomain.ml @@ -134,13 +134,15 @@ struct let equal x y = leq x y && leq y x let hash xs = fold (fun v a -> a + E.hash v) xs 0 let compare x y = - if equal x y - then 0 + if equal x y then + 0 + else ( + let caridnality_comp = compare (cardinal x) (cardinal y) in + if caridnality_comp <> 0 then + caridnality_comp else - let caridnality_comp = compare (cardinal x) (cardinal y) in - if caridnality_comp <> 0 - then caridnality_comp - else Map.compare (List.compare E.compare) x y + Map.compare (List.compare E.compare) x y + ) let show x : string = let all_elems : string list = List.map E.show (elements x) in Printable.get_short_list "{" "}" all_elems @@ -234,8 +236,8 @@ struct ) s2 nil with Not_found -> dprintf "choose failed b/c of empty set s1: %d s2: %d" - (cardinal s1) - (cardinal s2) + (cardinal s1) + (cardinal s2) end end @@ -339,8 +341,8 @@ struct ) s2 nil with Not_found -> dprintf "choose failed b/c of empty set s1: %d s2: %d" - (cardinal s1) - (cardinal s2) + (cardinal s1) + (cardinal s2) end end [@@deprecated] diff --git a/src/domains/lattice.ml b/src/domain/lattice.ml similarity index 96% rename from src/domains/lattice.ml rename to src/domain/lattice.ml index 4cdaa8fb9f..9ea3f74635 100644 --- a/src/domains/lattice.ml +++ b/src/domain/lattice.ml @@ -4,12 +4,12 @@ module Pretty = GoblintCil.Pretty (* module type Rel = -sig - type t - type relation = Less | Equal | Greater | Uncomparable - val rel : t -> t -> relation - val in_rel : t -> relation -> t -> bool -end *) + sig + type t + type relation = Less | Equal | Greater | Uncomparable + val rel : t -> t -> relation + val in_rel : t -> relation -> t -> bool + end *) (* partial order: elements might not be comparable and no bot/top -> join etc. might fail with exception Uncomparable *) exception Uncomparable @@ -148,13 +148,14 @@ struct end (* HAS SIDE-EFFECTS ---- PLEASE INSTANCIATE ONLY ONCE!!! *) -module HConsed (Base:S) = +module HConsed (Base:S) (Arg: sig val assume_idempotent: bool end) = struct include Printable.HConsed (Base) + let lift_f2 f x y = f (unlift x) (unlift y) - let narrow x y = if x.BatHashcons.tag == y.BatHashcons.tag then x else lift (lift_f2 Base.narrow 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 x.BatHashcons.tag == y.BatHashcons.tag then x else lift (lift_f2 Base.meet 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 @@ -324,14 +325,14 @@ struct match (x,y) with | (`Lifted x, `Lifted y) -> (try `Lifted (Base.widen x y) - with Uncomparable -> `Top) + with Uncomparable -> `Top) | _ -> y let narrow x y = match (x,y) with | (`Lifted x, `Lifted y) -> (try `Lifted (Base.narrow x y) - with Uncomparable -> `Bot) + with Uncomparable -> `Bot) | _ -> x end diff --git a/src/domains/mapDomain.ml b/src/domain/mapDomain.ml similarity index 98% rename from src/domains/mapDomain.ml rename to src/domain/mapDomain.ml index 6c40ab9792..740da9969e 100644 --- a/src/domains/mapDomain.ml +++ b/src/domain/mapDomain.ml @@ -68,11 +68,14 @@ end module Print (D: Printable.S) (R: Printable.S) (M: Bindings with type key = D.t and type value = R.t) = struct let pretty () map = - let pretty_bindings () = M.fold (fun k v acc -> - acc ++ dprintf "%a ->@? @[%a@]\n" D.pretty k R.pretty v + let doc = M.fold (fun k v acc -> + acc ++ dprintf "%a ->@?@[%a@]\n" D.pretty k R.pretty v ) map nil in - dprintf "@[{\n @[%t@]}@]" pretty_bindings + if doc = Pretty.nil then + text "{}" + else + dprintf "@[{\n @[%a@]}@]" Pretty.insert doc let show map = GobPretty.sprint pretty map @@ -256,11 +259,12 @@ struct end (* TODO: this is very slow because every add/remove in a fold-loop relifts *) +(* TODO: currently hardcoded to assume_idempotent *) module HConsed (M: S) : S with type key = M.key and type value = M.value = struct - include Lattice.HConsed (M) + include Lattice.HConsed (M) (struct let assume_idempotent = false end) type key = M.key type value = M.value @@ -714,8 +718,8 @@ struct let singleton k v = `Lifted (M.singleton k v) let empty () = `Lifted (M.empty ()) let is_empty = function - | `Bot -> false - | `Lifted x -> M.is_empty x + | `Bot -> false + | `Lifted x -> M.is_empty x let exists f = function | `Bot -> raise (Fn_over_All "exists") | `Lifted x -> M.exists f x diff --git a/src/domains/partitionDomain.ml b/src/domain/partitionDomain.ml similarity index 90% rename from src/domains/partitionDomain.ml rename to src/domain/partitionDomain.ml index eab15e1b05..9675e9bfce 100644 --- a/src/domains/partitionDomain.ml +++ b/src/domain/partitionDomain.ml @@ -115,18 +115,23 @@ struct for_all (fun p -> exists (B.leq p) y) x let pretty_diff () (y, x) = - (* based on DisjointDomain.PairwiseSet *) - let x_not_leq = filter (fun p -> - not (exists (fun q -> B.leq p q) y) - ) x - in - let p_not_leq = choose x_not_leq in - GoblintCil.Pretty.( - dprintf "%a:\n" B.pretty p_not_leq - ++ - fold (fun q acc -> - dprintf "not leq %a because %a\n" B.pretty q B.pretty_diff (p_not_leq, q) ++ acc - ) y nil + if E.is_top x then ( + GoblintCil.Pretty.(dprintf "%a not leq bot" pretty y) + ) + else ( + (* based on DisjointDomain.PairwiseSet *) + let x_not_leq = filter (fun p -> + not (exists (fun q -> B.leq p q) y) + ) x + in + let p_not_leq = choose x_not_leq in + GoblintCil.Pretty.( + dprintf "%a:\n" B.pretty p_not_leq + ++ + fold (fun q acc -> + dprintf "not leq %a because %a\n" B.pretty q B.pretty_diff (p_not_leq, q) ++ acc + ) y nil + ) ) let meet xs ys = if is_bot xs || is_bot ys then bot () else diff --git a/src/domains/setDomain.ml b/src/domain/setDomain.ml similarity index 100% rename from src/domains/setDomain.ml rename to src/domain/setDomain.ml diff --git a/src/domains/trieDomain.ml b/src/domain/trieDomain.ml similarity index 100% rename from src/domains/trieDomain.ml rename to src/domain/trieDomain.ml diff --git a/src/domains/access.ml b/src/domains/access.ml index a183a32633..baa9d34220 100644 --- a/src/domains/access.ml +++ b/src/domains/access.ml @@ -10,28 +10,105 @@ module M = Messages (* Some helper functions to avoid flagging race warnings on atomic types, and * other irrelevant stuff, such as mutexes and functions. *) -let is_ignorable_type (t: typ): bool = - match t with - | TNamed ({ tname = "atomic_t" | "pthread_mutex_t" | "pthread_rwlock_t" | "pthread_spinlock_t" | "spinlock_t" | "pthread_cond_t"; _ }, _) -> true - | TComp ({ cname = "__pthread_mutex_s" | "__pthread_rwlock_arch_t" | "__jmp_buf_tag" | "_pthread_cleanup_buffer" | "__pthread_cleanup_frame" | "__cancel_jmp_buf_tag"; _}, _) -> true - | TComp ({ cname; _}, _) when String.starts_with_stdlib ~prefix:"__anon" cname -> +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 -> begin match Cilfacade.split_anoncomp_name cname with - | (true, ("__once_flag" | "__pthread_unwind_buf_t" | "__cancel_jmp_buf"), _) -> true (* anonstruct *) - | (false, ("pthread_mutexattr_t" | "pthread_condattr_t" | "pthread_barrierattr_t"), _) -> true (* anonunion *) + | (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 *) | _ -> false end - | TComp ({ cname = "lock_class_key"; _ }, _) -> true - | TInt (IInt, attr) when hasAttribute "mutex" attr -> true - | t when hasAttribute "atomic" (typeAttrs t) -> true (* C11 _Atomic *) + | "lock_class_key" -> true (* kernel? *) | _ -> false -let is_ignorable = function - | None -> false - | Some (v,os) when hasAttribute "thread" v.vattr && not (v.vaddrof) -> true (* Thread-Local Storage *) - | Some (v,os) when BaseUtil.is_volatile v && not (get_bool "ana.race.volatile") -> true (* volatile & races on volatiles should not be reported *) - | Some (v,os) -> - try isFunctionType v.vtype || is_ignorable_type v.vtype - with Not_found -> false +let is_ignorable_attrs attrs = + let is_ignorable_attr = function + | Attr ("volatile", _) when not (get_bool "ana.race.volatile") -> true (* volatile & races on volatiles should not be reported *) + | Attr ("atomic", _) -> true (* C11 _Atomic *) + | _ -> false + in + List.exists is_ignorable_attr attrs + +let rec is_ignorable_type (t: typ): bool = + (* efficient pattern matching first *) + match t with + | TNamed ({ tname = "atomic_t" | "pthread_mutex_t" | "pthread_rwlock_t" | "pthread_spinlock_t" | "spinlock_t" | "pthread_cond_t" | "atomic_flag" | "FILE" | "__FILE"; _ }, _) -> true + | TComp ({ cname; _}, _) when is_ignorable_comp_name cname -> true + | TInt (IInt, attr) when hasAttribute "mutex" attr -> true (* kernel? *) + | TFun _ -> true + | _ -> + if is_ignorable_attrs (typeAttrsOuter t) then (* only outer because we unroll TNamed ourselves *) + true + else ( + (* unroll TNamed once *) + (* can't use unrollType because we want to check TNamed-s at all intermediate typedefs as well *) + match t with + | TNamed ({ttype; _}, attrs) -> is_ignorable_type (typeAddAttributes attrs ttype) + | _ -> false + ) + +let rec is_ignorable_type_offset (t: typ) (o: _ Offset.t): bool = + (* similar to Cilfacade.typeOffset but we want to check types at all intermediate offsets as well *) + if is_ignorable_type t then + true (* type at offset so far ignorable, no need to recurse *) + else ( + match o with + | `NoOffset -> false (* already checked t *) + | `Index (_, o') -> + begin match unrollType t with + | TArray (et, _, attrs) -> + let t' = Cilfacade.typeBlendAttributes attrs et in + is_ignorable_type_offset t' o' + | _ -> false (* index on non-array *) + end + | `Field (f, o') -> + begin match unrollType t with + | TComp (_, attrs) -> + let t' = Cilfacade.typeBlendAttributes attrs f.ftype in + is_ignorable_type_offset t' o' + | _ -> false (* field on non-compound *) + end + ) + +(** {!is_ignorable_type} for {!typsig}. *) +let is_ignorable_typsig (ts: typsig): bool = + (* efficient pattern matching first *) + match ts with + | TSComp (_, cname, _) when is_ignorable_comp_name cname -> true + | TSFun _ -> true + | TSBase t -> is_ignorable_type t + | _ -> is_ignorable_attrs (typeSigAttrs ts) + +(** {!is_ignorable_type_offset} for {!typsig}. *) +let rec is_ignorable_typsig_offset (ts: typsig) (o: _ Offset.t): bool = + if is_ignorable_typsig ts then + true (* type at offset so far ignorable, no need to recurse *) + else ( + match o with + | `NoOffset -> false (* already checked t *) + | `Index (_, o') -> + begin match ts with + | TSArray (ets, _, attrs) -> + let ts' = Cilfacade.typeSigBlendAttributes attrs ets in + is_ignorable_typsig_offset ts' o' + | _ -> false (* index on non-array *) + end + | `Field (f, o') -> + begin match ts with + | TSComp (_, _, attrs) -> + let t' = Cilfacade.typeBlendAttributes attrs f.ftype in + is_ignorable_type_offset t' o' (* switch to type because it is more precise with TNamed *) + | _ -> false (* field on non-compound *) + end + ) + +let is_ignorable_mval = function + | ({vaddrof = false; vattr; _}, _) when hasAttribute "thread" vattr -> true (* Thread-Local Storage *) + | (v, o) -> is_ignorable_type_offset v.vtype o (* can't use Cilfacade.typeOffset because we want to check types at all intermediate offsets as well *) + +let is_ignorable_memo = function + | (`Type ts, o) -> is_ignorable_typsig_offset ts o + | (`Var v, o) -> is_ignorable_mval (v, o) module TSH = Hashtbl.Make (CilType.Typsig) @@ -84,8 +161,7 @@ type acc_typ = [ `Type of CilType.Typ.t | `Struct of CilType.Compinfo.t * Offset module MemoRoot = struct include Printable.StdLeaf - type t = [`Var of CilType.Varinfo.t | `Type of CilType.Typ.t] [@@deriving eq, ord, hash] - (* Can't use typsig for `Type because there's no function to follow offsets on typsig. *) + type t = [`Var of CilType.Varinfo.t | `Type of CilType.Typsig.t] [@@deriving eq, ord, hash] let name () = "memoroot" @@ -93,8 +169,8 @@ struct (* Imitate old printing for now *) match vt with | `Var v -> CilType.Varinfo.pretty () v - | `Type (TComp (c, _)) -> Pretty.dprintf "(struct %s)" c.cname - | `Type t -> Pretty.dprintf "(%a)" CilType.Typ.pretty t + | `Type (TSComp (_, name, _)) -> Pretty.dprintf "(struct %s)" name + | `Type t -> Pretty.dprintf "(%a)" Cilfacade.pretty_typsig_like_typ t include Printable.SimplePretty ( struct @@ -109,7 +185,6 @@ module Memo = struct include Printable.StdLeaf type t = MemoRoot.t * Offset.Unit.t [@@deriving eq, ord, hash] - (* Can't use typsig for `Type because there's no function to follow offsets on typsig. *) let name () = "memo" @@ -117,8 +192,8 @@ struct (* Imitate old printing for now *) match vt with | `Var v -> Pretty.dprintf "%a%a" CilType.Varinfo.pretty v Offset.Unit.pretty o - | `Type (TComp (c, _)) -> Pretty.dprintf "(struct %s)%a" c.cname Offset.Unit.pretty o - | `Type t -> Pretty.dprintf "(%a)%a" CilType.Typ.pretty t Offset.Unit.pretty o + | `Type (TSComp (_, name, _)) -> Pretty.dprintf "(struct %s)%a" name Offset.Unit.pretty o + | `Type t -> Pretty.dprintf "(%a)%a" Cilfacade.pretty_typsig_like_typ t Offset.Unit.pretty o include Printable.SimplePretty ( struct @@ -129,23 +204,14 @@ struct let of_ty (ty: acc_typ): t = match ty with - | `Struct (c, o) -> (`Type (TComp (c, [])), o) - | `Type t -> (`Type t, `NoOffset) + | `Struct (c, o) -> (`Type (TSComp (c.cstruct, c.cname, [])), o) + | `Type t -> (`Type (Cil.typeSig t), `NoOffset) let to_mval: t -> Mval.Unit.t option = function | (`Var v, o) -> Some (v, o) | (`Type _, _) -> None let add_offset ((vt, o): t) o2: t = (vt, Offset.Unit.add_offset o o2) - - let type_of_base ((vt, _): t): typ = - match vt with - | `Var v -> v.vtype - | `Type t -> t - - (** @raise Offset.Type_of_error *) - let type_of ((vt, o) as memo: t): typ = - Offset.Unit.type_of ~base:(type_of_base memo) o end (* TODO: What is the logic for get_type? *) @@ -205,42 +271,42 @@ let get_val_type e: acc_typ = (** Add access to {!Memo} after distributing. *) -let add_one side memo: unit = - let mv = Memo.to_mval memo in - let ignorable = is_ignorable mv in +let add_one ~side memo: unit = + let ignorable = is_ignorable_memo memo in if M.tracing then M.trace "access" "add_one %a (ignorable = %B)\n" Memo.pretty memo ignorable; if not ignorable then side memo -(** Distribute type-based access to variables and containing fields. *) -let rec add_distribute_outer side (t: typ) (o: Offset.Unit.t) = - let memo = (`Type t, o) in +(** Distribute empty access set for type-based access to variables and containing fields. + Empty access sets are needed for prefix-type_suffix race checking. *) +let rec add_distribute_outer ~side ~side_empty (ts: typsig) (o: Offset.Unit.t) = + let memo = (`Type ts, o) in if M.tracing then M.tracei "access" "add_distribute_outer %a\n" Memo.pretty memo; - add_one side memo; + add_one ~side memo; (* Add actual access for non-recursive call, or empty access for recursive call when side is side_empty. *) (* distribute to variables of the type *) - let ts = typeSig t in let vars = TSH.find_all typeVar ts in List.iter (fun v -> - add_one side (`Var v, o) (* same offset, but on variable *) + (* same offset, but on variable *) + add_one ~side:side_empty (`Var v, o) (* Switch to side_empty. *) ) vars; (* recursively distribute to fields containing the type *) let fields = TSH.find_all typeIncl ts in List.iter (fun f -> (* prepend field and distribute to outer struct *) - add_distribute_outer side (TComp (f.fcomp, [])) (`Field (f, o)) + add_distribute_outer ~side:side_empty ~side_empty (TSComp (f.fcomp.cstruct, f.fcomp.cname, [])) (`Field (f, o)) (* Switch to side_empty. *) ) fields; if M.tracing then M.traceu "access" "add_distribute_outer\n" (** Add access to known variable with offsets or unknown variable from expression. *) -let add side e voffs = +let add ~side ~side_empty e voffs = begin match voffs with | Some (v, o) -> (* known variable *) if M.tracing then M.traceli "access" "add var %a%a\n" CilType.Varinfo.pretty v CilType.Offset.pretty o; let memo = (`Var v, Offset.Unit.of_cil o) in - add_one side memo + add_one ~side memo | None -> (* unknown variable *) if M.tracing then M.traceli "access" "add type %a\n" CilType.Exp.pretty e; let ty = get_val_type e in (* extract old acc_typ from expression *) @@ -250,7 +316,7 @@ let add side e voffs = in match o with | `NoOffset when not !collect_direct_arithmetic && isArithmeticType t -> () - | _ -> add_distribute_outer side t o (* distribute to variables and outer offsets *) + | _ -> add_distribute_outer ~side ~side_empty (Cil.typeSig t) o (* distribute to variables and outer offsets *) end; if M.tracing then M.traceu "access" "add\n" @@ -339,12 +405,18 @@ and distribute_access_type f = function module A = struct include Printable.Std - type t = int * AccessKind.t * Node.t * CilType.Exp.t * MCPAccess.A.t [@@deriving eq, ord, hash] + type t = { + conf : int; + kind : AccessKind.t; + node : Node.t; + exp : CilType.Exp.t; + acc : MCPAccess.A.t; + } [@@deriving eq, ord, hash] let name () = "access" - let pretty () (conf, kind, node, e, lp) = - Pretty.dprintf "%d, %a, %a, %a, %a" conf AccessKind.pretty kind CilType.Location.pretty (Node.location node) CilType.Exp.pretty e MCPAccess.A.pretty lp + let pretty () {conf; kind; node; exp; acc} = + Pretty.dprintf "%d, %a, %a, %a, %a" conf AccessKind.pretty kind CilType.Location.pretty (Node.location node) CilType.Exp.pretty exp MCPAccess.A.pretty acc include Printable.SimplePretty ( struct @@ -353,10 +425,8 @@ struct end ) - let conf (conf, _, _, _, _) = conf - - let relift (conf, kind, node, e, a) = - (conf, kind, node, e, MCPAccess.A.relift a) + let relift {conf; kind; node; exp; acc} = + {conf; kind; node; exp; acc = MCPAccess.A.relift acc} end module AS = @@ -364,25 +434,62 @@ struct include SetDomain.Make (A) let max_conf accs = - accs |> elements |> List.map A.conf |> (List.max ~cmp:Int.compare) + accs |> elements |> List.map (fun {A.conf; _} -> conf) |> (List.max ~cmp:Int.compare) end -(* Check if two accesses may race and if yes with which confidence *) -let may_race (conf,(kind: AccessKind.t),loc,e,a) (conf2,(kind2: AccessKind.t),loc2,e2,a2) = - if kind = Read && kind2 = Read then - false (* two read/read accesses do not race *) - else if not (get_bool "ana.race.free") && (kind = Free || kind2 = Free) then - false - else if not (MCPAccess.A.may_race a a2) then - false (* analysis-specific information excludes race *) - else - true +(** Check if two accesses may race. *) +let may_race A.{kind; acc; _} A.{kind=kind2; acc=acc2; _} = + match kind, kind2 with + | Read, Read -> false (* two read/read accesses do not race *) + | Free, _ + | _, Free when not (get_bool "ana.race.free") -> false + | Call, _ + | _, Call when not (get_bool "ana.race.call") -> false + | _, _ -> MCPAccess.A.may_race acc acc2 (* analysis-specific information excludes race *) -let group_may_race ~ancestor_accs accs = +(** Access sets for race detection and warnings. *) +module WarnAccs = +struct + type t = { + node: AS.t; (** Accesses for current memo. From accesses at trie node corresponding to memo offset. *) + prefix: AS.t; (** Accesses for all prefixes. From accesses to trie node ancestors. *) + type_suffix: AS.t; (** Accesses for all type suffixes. From offset suffixes in other tries. *) + type_suffix_prefix: AS.t; (** Accesses to all prefixes of all type suffixes. *) + } + + let diff w1 w2 = { + node = AS.diff w1.node w2.node; + prefix = AS.diff w1.prefix w2.prefix; + type_suffix = AS.diff w1.type_suffix w2.type_suffix; + type_suffix_prefix = AS.diff w1.type_suffix_prefix w2.type_suffix_prefix; + } + + let union_all w = + AS.union + (AS.union w.node w.prefix) + (AS.union w.type_suffix w.type_suffix_prefix) + + let is_empty w = + AS.is_empty w.node && AS.is_empty w.prefix && AS.is_empty w.type_suffix && AS.is_empty w.type_suffix_prefix + + let empty () = + {node=AS.empty (); prefix=AS.empty (); type_suffix=AS.empty (); type_suffix_prefix=AS.empty ()} + + let pretty () w = + Pretty.dprintf "{node = %a; prefix = %a; type_suffix = %a; type_suffix_prefix = %a}" + AS.pretty w.node AS.pretty w.prefix AS.pretty w.type_suffix AS.pretty w.type_suffix_prefix +end + +let group_may_race (warn_accs:WarnAccs.t) = + if M.tracing then M.tracei "access" "group_may_race %a\n" WarnAccs.pretty warn_accs; (* BFS to traverse one component with may_race edges *) - let rec bfs' ~ancestor_accs ~accs ~todo ~visited = - let may_race_accs ~accs ~todo = + let rec bfs' warn_accs ~todo ~visited = + let todo_all = WarnAccs.union_all todo in + let visited' = AS.union visited todo_all in (* Add all todo accesses to component. *) + let warn_accs' = WarnAccs.diff warn_accs todo in (* Todo accesses don't need to be considered as step targets, because they're already in the component. *) + + let step_may_race ~todo ~accs = (* step from todo to accs if may_race *) AS.fold (fun acc todo' -> AS.fold (fun acc' todo' -> if may_race acc acc' then @@ -392,37 +499,74 @@ let group_may_race ~ancestor_accs accs = ) accs todo' ) todo (AS.empty ()) in - let accs' = AS.diff accs todo in - let ancestor_accs' = AS.diff ancestor_accs todo in - let todo_accs = may_race_accs ~accs:accs' ~todo in - let todo_ancestor_accs = may_race_accs ~accs:ancestor_accs' ~todo:(AS.diff todo ancestor_accs') in - let todo' = AS.union todo_accs todo_ancestor_accs in - let visited' = AS.union visited todo in - if AS.is_empty todo' then - (accs', visited') + (* Undirected graph of may_race checks: + + type_suffix_prefix + | + | + type_suffix --+-- prefix + \ | / + \ | / + node + / \ + \_/ + + Each undirected edge is handled by two opposite step_may_race-s. + All missing edges are checked at other nodes by other group_may_race calls. *) + let todo' : WarnAccs.t = { + node = step_may_race ~todo:todo_all ~accs:warn_accs'.node; + prefix = step_may_race ~todo:(AS.union todo.node todo.type_suffix) ~accs:warn_accs'.prefix; + type_suffix = step_may_race ~todo:(AS.union todo.node todo.prefix) ~accs:warn_accs'.type_suffix; + type_suffix_prefix = step_may_race ~todo:todo.node ~accs:warn_accs'.type_suffix_prefix + } + in + + if WarnAccs.is_empty todo' then + (warn_accs', visited') else - (bfs' [@tailcall]) ~ancestor_accs:ancestor_accs' ~accs:accs' ~todo:todo' ~visited:visited' + (bfs' [@tailcall]) warn_accs' ~todo:todo' ~visited:visited' in - let bfs accs acc = bfs' ~ancestor_accs ~accs ~todo:(AS.singleton acc) ~visited:(AS.empty ()) in - (* repeat BFS to find all components *) - let rec components comps accs = - if AS.is_empty accs then - comps + let bfs warn_accs todo = bfs' warn_accs ~todo ~visited:(AS.empty ()) in + (* repeat BFS to find all components starting from node accesses *) + let rec components comps (warn_accs:WarnAccs.t) = + if AS.is_empty warn_accs.node then + (comps, warn_accs) else ( - let acc = AS.choose accs in - let (accs', comp) = bfs accs acc in + let acc = AS.choose warn_accs.node in + let (warn_accs', comp) = bfs warn_accs {(WarnAccs.empty ()) with node=AS.singleton acc} in let comps' = comp :: comps in - components comps' accs' + components comps' warn_accs' + ) + in + let (comps, warn_accs) = components [] warn_accs in + if M.tracing then M.trace "access" "components %a\n" WarnAccs.pretty warn_accs; + (* repeat BFS to find all prefix-type_suffix-only components starting from prefix accesses (symmetric) *) + let rec components_cross comps ~prefix ~type_suffix = + if AS.is_empty prefix then + comps + else ( + let prefix_acc = AS.choose prefix in + let (warn_accs', comp) = bfs {(WarnAccs.empty ()) with prefix; type_suffix} {(WarnAccs.empty ()) with prefix=AS.singleton prefix_acc} in + if M.tracing then M.trace "access" "components_cross %a\n" WarnAccs.pretty warn_accs'; + let comps' = + if AS.cardinal comp > 1 then + comp :: comps + else + comps (* ignore self-race prefix_acc component, self-race checked at prefix's level *) + in + components_cross comps' ~prefix:warn_accs'.prefix ~type_suffix:warn_accs'.type_suffix ) in - components [] accs + let components_cross = components_cross comps ~prefix:warn_accs.prefix ~type_suffix:warn_accs.type_suffix in + if M.tracing then M.traceu "access" "group_may_race\n"; + components_cross let race_conf accs = assert (not (AS.is_empty accs)); (* group_may_race should only construct non-empty components *) if AS.cardinal accs = 1 then ( (* singleton component *) let acc = AS.choose accs in if may_race acc acc then (* self-race *) - Some (A.conf acc) + Some (acc.conf) else None ) @@ -451,9 +595,8 @@ let print_accesses memo grouped_accs = let allglobs = get_bool "allglobs" in let race_threshold = get_int "warn.race-threshold" in let msgs race_accs = - let h (conf,kind,node,e,a) = - let d_msg () = dprintf "%a with %a (conf. %d)" AccessKind.pretty kind MCPAccess.A.pretty a conf in - let doc = dprintf "%t (exp: %a)" d_msg d_exp e in + let h A.{conf; kind; node; exp; acc} = + let doc = dprintf "%a with %a (conf. %d) (exp: %a)" AccessKind.pretty kind MCPAccess.A.pretty acc conf d_exp exp in (doc, Some (Messages.Location.Node node)) in AS.elements race_accs @@ -483,7 +626,7 @@ let print_accesses memo grouped_accs = M.msg_group Success ?loc:group_loc ~category:Race "Memory location %a (safe)" Memo.pretty memo (msgs safe_accs) ) -let warn_global ~safe ~vulnerable ~unsafe ~ancestor_accs memo accs = - let grouped_accs = group_may_race ~ancestor_accs accs in (* do expensive component finding only once *) +let warn_global ~safe ~vulnerable ~unsafe warn_accs memo = + let grouped_accs = group_may_race warn_accs in (* do expensive component finding only once *) incr_summary ~safe ~vulnerable ~unsafe grouped_accs; print_accesses memo grouped_accs diff --git a/src/domains/events.ml b/src/domains/events.ml index 2141ad17dd..06561bddbe 100644 --- a/src/domains/events.ml +++ b/src/domains/events.ml @@ -10,7 +10,7 @@ type t = | EnterMultiThreaded | SplitBranch of exp * bool (** Used to simulate old branch-based split. *) | AssignSpawnedThread of lval * ThreadIdDomain.Thread.t (** Assign spawned thread's ID to lval. *) - | Access of {exp: CilType.Exp.t; lvals: Queries.LS.t; kind: AccessKind.t; reach: bool} + | Access of {exp: CilType.Exp.t; ad: Queries.AD.t; kind: AccessKind.t; reach: bool} | 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 @@ -41,7 +41,7 @@ let pretty () = function | EnterMultiThreaded -> text "EnterMultiThreaded" | SplitBranch (exp, tv) -> dprintf "SplitBranch (%a, %B)" d_exp exp tv | AssignSpawnedThread (lval, tid) -> dprintf "AssignSpawnedThread (%a, %a)" d_lval lval ThreadIdDomain.Thread.pretty tid - | Access {exp; lvals; kind; reach} -> dprintf "Access {exp=%a; lvals=%a; kind=%a; reach=%B}" CilType.Exp.pretty exp Queries.LS.pretty lvals AccessKind.pretty kind reach + | Access {exp; ad; kind; reach} -> dprintf "Access {exp=%a; ad=%a; kind=%a; reach=%B}" CilType.Exp.pretty exp Queries.AD.pretty ad AccessKind.pretty kind reach | 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 diff --git a/src/domains/queries.ml b/src/domains/queries.ml index 97d30d8c8c..228320bef3 100644 --- a/src/domains/queries.ml +++ b/src/domains/queries.ml @@ -15,6 +15,7 @@ module NFL = WrapperFunctionAnalysis0.NodeFlatLattice module TC = WrapperFunctionAnalysis0.ThreadCreateUniqueCount module ThreadNodeLattice = Lattice.Prod (NFL) (TC) +module ML = LibraryDesc.MathLifted module VI = Lattice.Flat (Basetype.Variables) (struct let top_name = "Unknown line" @@ -31,8 +32,13 @@ module FlatYojson = Lattice.Flat (Printable.Yojson) (struct let bot_name = "bot yojson" end) -module SD = Basetype.Strings +module SD: Lattice.S with type t = [`Bot | `Lifted of string | `Top] = + Lattice.Flat (Basetype.RawStrings) (struct + let top_name = "?" + let bot_name = "-" + end) module VD = ValueDomain.Compound +module AD = ValueDomain.AD module MayBool = BoolDomain.MayBool module MustBool = BoolDomain.MustBool @@ -70,34 +76,39 @@ type invariant_context = Invariant.context = { (** GADT for queries with specific result type. *) type _ t = | EqualSet: exp -> ES.t t - | MayPointTo: exp -> LS.t t - | ReachableFrom: exp -> LS.t t + | MayPointTo: exp -> AD.t t + | ReachableFrom: exp -> AD.t t | ReachableUkTypes: exp -> TS.t t | Regions: exp -> LS.t t | MayEscape: varinfo -> MayBool.t t | MayBePublic: maybepublic -> MayBool.t t (* old behavior with write=false *) | MayBePublicWithout: maybepublicwithout -> MayBool.t t | MustBeProtectedBy: mustbeprotectedby -> MustBool.t t - | MustLockset: LS.t t + | MustLockset: AD.t t | MustBeAtomic: MustBool.t t | MustBeSingleThreaded: {since_start: bool} -> MustBool.t t | MustBeUniqueThread: MustBool.t t | CurrentThreadId: ThreadIdDomain.ThreadLifted.t t | ThreadCreateIndexedNode: ThreadNodeLattice.t t | MayBeThreadReturn: MayBool.t t - | EvalFunvar: exp -> LS.t t + | EvalFunvar: exp -> AD.t t | EvalInt: exp -> ID.t t | EvalStr: exp -> SD.t t | EvalLength: exp -> ID.t t (* length of an array or string *) | EvalValue: exp -> VD.t t - | BlobSize: exp -> ID.t t (* size of a dynamically allocated `Blob pointed to by exp *) + | BlobSize: {exp: Cil.exp; base_address: bool} -> ID.t t + (* Size of a dynamically allocated `Blob pointed to by exp. *) + (* If the record's second field is set to true, then address offsets are discarded and the size of the `Blob is asked for the base address. *) | CondVars: exp -> ES.t t | PartAccess: access -> Obj.t t (** Only queried by access and deadlock analysis. [Obj.t] represents [MCPAccess.A.t], needed to break dependency cycle. *) | IterPrevVars: iterprevvar -> Unit.t t | IterVars: itervar -> Unit.t t | PathQuery: int * 'a t -> 'a t (** Query only one path under witness lifter. *) | DYojson: FlatYojson.t t (** Get local state Yojson of one path under [PathQuery]. *) - | HeapVar: VI.t t + | AllocVar: {on_stack: bool} -> VI.t t + (* Create a variable representing a dynamic allocation-site *) + (* If on_stack is [true], then the dynamic allocation is on the stack (i.e., alloca() or a similar function was called). Otherwise, allocation is on the heap *) + | IsAllocVar: varinfo -> MayBool.t t (* [true] if variable represents dynamically allocated memory *) | IsHeapVar: varinfo -> MayBool.t t (* TODO: is may or must? *) | IsMultiple: varinfo -> MustBool.t t (* For locals: Is another copy of this local variable reachable via pointers? *) @@ -112,14 +123,18 @@ type _ t = | CreatedThreads: ConcDomain.ThreadSet.t t | MustJoinedThreads: ConcDomain.MustThreadSet.t t | ThreadsJoinedCleanly: MustBool.t t - | MustProtectedVars: mustprotectedvars -> LS.t t + | MustProtectedVars: mustprotectedvars -> VS.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]. *) | IterSysVars: VarQuery.t * Obj.t VarQuery.f -> Unit.t t (** [iter_vars] for [Constraints.FromSpec]. [Obj.t] represents [Spec.V.t]. *) | MayAccessed: AccessDomain.EventSet.t t - | MayBeTainted: LS.t t + | MayBeTainted: AD.t t | MayBeModifiedSinceSetjmp: JmpBufDomain.BufferEntry.t -> VS.t t + | MustTermLoop: stmt -> MustBool.t t + | MustTermAllLoops: MustBool.t t + | IsEverMultiThreaded: MayBool.t t + | TmpSpecial: Mval.Exp.t -> ML.t t type 'a result = 'a @@ -138,17 +153,18 @@ struct (* Cannot group these GADTs... *) | EqualSet _ -> (module ES) | CondVars _ -> (module ES) - | MayPointTo _ -> (module LS) - | ReachableFrom _ -> (module LS) + | MayPointTo _ -> (module AD) + | ReachableFrom _ -> (module AD) | Regions _ -> (module LS) - | MustLockset -> (module LS) - | EvalFunvar _ -> (module LS) + | MustLockset -> (module AD) + | EvalFunvar _ -> (module AD) | ReachableUkTypes _ -> (module TS) | MayEscape _ -> (module MayBool) | MayBePublic _ -> (module MayBool) | MayBePublicWithout _ -> (module MayBool) | MayBeThreadReturn -> (module MayBool) | IsHeapVar _ -> (module MayBool) + | IsAllocVar _ -> (module MayBool) | MustBeProtectedBy _ -> (module MustBool) | MustBeAtomic -> (module MustBool) | MustBeSingleThreaded _ -> (module MustBool) @@ -160,7 +176,7 @@ struct | BlobSize _ -> (module ID) | CurrentThreadId -> (module ThreadIdDomain.ThreadLifted) | ThreadCreateIndexedNode -> (module ThreadNodeLattice) - | HeapVar -> (module VI) + | AllocVar _ -> (module VI) | EvalStr _ -> (module SD) | IterPrevVars _ -> (module Unit) | IterVars _ -> (module Unit) @@ -176,14 +192,18 @@ struct | CreatedThreads -> (module ConcDomain.ThreadSet) | MustJoinedThreads -> (module ConcDomain.MustThreadSet) | ThreadsJoinedCleanly -> (module MustBool) - | MustProtectedVars _ -> (module LS) + | MustProtectedVars _ -> (module VS) | Invariant _ -> (module Invariant) | InvariantGlobal _ -> (module Invariant) | WarnGlobal _ -> (module Unit) | IterSysVars _ -> (module Unit) | MayAccessed -> (module AccessDomain.EventSet) - | MayBeTainted -> (module LS) + | MayBeTainted -> (module AD) | MayBeModifiedSinceSetjmp _ -> (module VS) + | MustTermLoop _ -> (module MustBool) + | MustTermAllLoops -> (module MustBool) + | IsEverMultiThreaded -> (module MayBool) + | TmpSpecial _ -> (module ML) (** Get bottom result for query. *) let bot (type a) (q: a t): a result = @@ -201,17 +221,18 @@ struct (* Cannot group these GADTs... *) | EqualSet _ -> ES.top () | CondVars _ -> ES.top () - | MayPointTo _ -> LS.top () - | ReachableFrom _ -> LS.top () + | MayPointTo _ -> AD.top () + | ReachableFrom _ -> AD.top () | Regions _ -> LS.top () - | MustLockset -> LS.top () - | EvalFunvar _ -> LS.top () + | MustLockset -> AD.top () + | EvalFunvar _ -> AD.top () | ReachableUkTypes _ -> TS.top () | MayEscape _ -> MayBool.top () | MayBePublic _ -> MayBool.top () | MayBePublicWithout _ -> MayBool.top () | MayBeThreadReturn -> MayBool.top () | IsHeapVar _ -> MayBool.top () + | IsAllocVar _ -> MayBool.top () | MutexType _ -> MutexAttrDomain.top () | MustBeProtectedBy _ -> MustBool.top () | MustBeAtomic -> MustBool.top () @@ -224,7 +245,7 @@ struct | BlobSize _ -> ID.top () | CurrentThreadId -> ThreadIdDomain.ThreadLifted.top () | ThreadCreateIndexedNode -> ThreadNodeLattice.top () - | HeapVar -> VI.top () + | AllocVar _ -> VI.top () | EvalStr _ -> SD.top () | IterPrevVars _ -> Unit.top () | IterVars _ -> Unit.top () @@ -239,14 +260,18 @@ struct | CreatedThreads -> ConcDomain.ThreadSet.top () | MustJoinedThreads -> ConcDomain.MustThreadSet.top () | ThreadsJoinedCleanly -> MustBool.top () - | MustProtectedVars _ -> LS.top () + | MustProtectedVars _ -> VS.top () | Invariant _ -> Invariant.top () | InvariantGlobal _ -> Invariant.top () | WarnGlobal _ -> Unit.top () | IterSysVars _ -> Unit.top () | MayAccessed -> AccessDomain.EventSet.top () - | MayBeTainted -> LS.top () + | MayBeTainted -> AD.top () | MayBeModifiedSinceSetjmp _ -> VS.top () + | MustTermLoop _ -> MustBool.top () + | MustTermAllLoops -> MustBool.top () + | IsEverMultiThreaded -> MayBool.top () + | TmpSpecial _ -> ML.top () end (* The type any_query can't be directly defined in Any as t, @@ -283,7 +308,7 @@ struct | Any (PartAccess _) -> 23 | Any (IterPrevVars _) -> 24 | Any (IterVars _) -> 25 - | Any HeapVar -> 29 + | Any (AllocVar _) -> 29 | Any (IsHeapVar _) -> 30 | Any (IsMultiple _) -> 31 | Any (EvalThread _) -> 32 @@ -307,6 +332,11 @@ struct | Any (EvalMutexAttr _ ) -> 50 | Any ThreadCreateIndexedNode -> 51 | Any ThreadsJoinedCleanly -> 52 + | Any (MustTermLoop _) -> 53 + | Any MustTermAllLoops -> 54 + | Any IsEverMultiThreaded -> 55 + | Any (TmpSpecial _) -> 56 + | Any (IsAllocVar _) -> 57 let rec compare a b = let r = Stdlib.compare (order a) (order b) in @@ -329,7 +359,12 @@ struct | Any (EvalLength e1), Any (EvalLength e2) -> CilType.Exp.compare e1 e2 | Any (EvalMutexAttr e1), Any (EvalMutexAttr e2) -> CilType.Exp.compare e1 e2 | Any (EvalValue e1), Any (EvalValue e2) -> CilType.Exp.compare e1 e2 - | Any (BlobSize e1), Any (BlobSize e2) -> CilType.Exp.compare e1 e2 + | Any (BlobSize {exp = e1; base_address = b1}), Any (BlobSize {exp = e2; base_address = b2}) -> + let r = CilType.Exp.compare e1 e2 in + if r <> 0 then + r + else + Stdlib.compare b1 b2 | Any (CondVars e1), Any (CondVars e2) -> CilType.Exp.compare e1 e2 | Any (PartAccess p1), Any (PartAccess p2) -> compare_access p1 p2 | Any (IterPrevVars ip1), Any (IterPrevVars ip2) -> compare_iterprevvar ip1 ip2 @@ -341,7 +376,9 @@ struct else compare (Any q1) (Any q2) | Any (IsHeapVar v1), Any (IsHeapVar v2) -> CilType.Varinfo.compare v1 v2 + | Any (IsAllocVar v1), Any (IsAllocVar v2) -> CilType.Varinfo.compare v1 v2 | Any (IsMultiple v1), Any (IsMultiple v2) -> CilType.Varinfo.compare v1 v2 + | Any (MustTermLoop s1), Any (MustTermLoop s2) -> CilType.Stmt.compare s1 s2 | Any (EvalThread e1), Any (EvalThread e2) -> CilType.Exp.compare e1 e2 | Any (EvalJumpBuf e1), Any (EvalJumpBuf e2) -> CilType.Exp.compare e1 e2 | Any (WarnGlobal vi1), Any (WarnGlobal vi2) -> Stdlib.compare (Hashtbl.hash vi1) (Hashtbl.hash vi2) @@ -352,6 +389,7 @@ struct | Any (MustProtectedVars m1), Any (MustProtectedVars m2) -> compare_mustprotectedvars m1 m2 | 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 (* only argumentless queries should remain *) | _, _ -> Stdlib.compare (order a) (order b) @@ -373,13 +411,15 @@ struct | Any (EvalLength e) -> CilType.Exp.hash e | Any (EvalMutexAttr e) -> CilType.Exp.hash e | Any (EvalValue e) -> CilType.Exp.hash e - | Any (BlobSize e) -> CilType.Exp.hash e + | Any (BlobSize {exp = e; base_address = b}) -> CilType.Exp.hash e + Hashtbl.hash b | Any (CondVars e) -> CilType.Exp.hash e | Any (PartAccess p) -> hash_access p | Any (IterPrevVars i) -> 0 | Any (IterVars i) -> 0 | Any (PathQuery (i, q)) -> 31 * i + hash (Any q) | Any (IsHeapVar v) -> CilType.Varinfo.hash v + | Any (MustTermLoop s) -> CilType.Stmt.hash s + | Any (IsAllocVar v) -> CilType.Varinfo.hash v | Any (IsMultiple v) -> CilType.Varinfo.hash v | Any (EvalThread e) -> CilType.Exp.hash e | Any (EvalJumpBuf e) -> CilType.Exp.hash e @@ -390,6 +430,7 @@ struct | Any (MustProtectedVars m) -> hash_mustprotectedvars m | Any (MayBeModifiedSinceSetjmp e) -> JmpBufDomain.BufferEntry.hash e | Any (MustBeSingleThreaded {since_start}) -> Hashtbl.hash since_start + | Any (TmpSpecial lv) -> Mval.Exp.hash lv (* 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. *) @@ -420,14 +461,15 @@ struct | Any (EvalStr e) -> Pretty.dprintf "EvalStr %a" CilType.Exp.pretty e | Any (EvalLength e) -> Pretty.dprintf "EvalLength %a" CilType.Exp.pretty e | Any (EvalValue e) -> Pretty.dprintf "EvalValue %a" CilType.Exp.pretty e - | Any (BlobSize e) -> Pretty.dprintf "BlobSize %a" CilType.Exp.pretty e + | Any (BlobSize {exp = e; base_address = b}) -> Pretty.dprintf "BlobSize %a (base_address: %b)" CilType.Exp.pretty e b | Any (CondVars e) -> Pretty.dprintf "CondVars %a" CilType.Exp.pretty e | Any (PartAccess p) -> Pretty.dprintf "PartAccess _" | Any (IterPrevVars i) -> Pretty.dprintf "IterPrevVars _" | Any (IterVars i) -> Pretty.dprintf "IterVars _" | Any (PathQuery (i, q)) -> Pretty.dprintf "PathQuery (%d, %a)" i pretty (Any q) - | Any HeapVar -> Pretty.dprintf "HeapVar" + | Any (AllocVar {on_stack = on_stack}) -> Pretty.dprintf "AllocVar %b" on_stack | Any (IsHeapVar v) -> Pretty.dprintf "IsHeapVar %a" CilType.Varinfo.pretty v + | Any (IsAllocVar v) -> Pretty.dprintf "IsAllocVar %a" CilType.Varinfo.pretty v | Any (IsMultiple v) -> Pretty.dprintf "IsMultiple %a" CilType.Varinfo.pretty v | Any (EvalThread e) -> Pretty.dprintf "EvalThread %a" CilType.Exp.pretty e | Any (EvalJumpBuf e) -> Pretty.dprintf "EvalJumpBuf %a" CilType.Exp.pretty e @@ -447,6 +489,10 @@ struct | Any MayBeTainted -> Pretty.dprintf "MayBeTainted" | Any DYojson -> Pretty.dprintf "DYojson" | Any MayBeModifiedSinceSetjmp buf -> Pretty.dprintf "MayBeModifiedSinceSetjmp %a" JmpBufDomain.BufferEntry.pretty buf + | Any (MustTermLoop s) -> Pretty.dprintf "MustTermLoop %a" CilType.Stmt.pretty s + | Any MustTermAllLoops -> Pretty.dprintf "MustTermAllLoops" + | Any IsEverMultiThreaded -> Pretty.dprintf "IsEverMultiThreaded" + | Any (TmpSpecial lv) -> Pretty.dprintf "TmpSpecial %a" Mval.Exp.pretty lv end let to_value_domain_ask (ask: ask) = diff --git a/src/domains/valueDomainQueries.ml b/src/domains/valueDomainQueries.ml index d366e6dda3..8266582ac2 100644 --- a/src/domains/valueDomainQueries.ml +++ b/src/domains/valueDomainQueries.ml @@ -4,6 +4,7 @@ open GoblintCil open BoolDomain module LS = SetDomain.ToppedSet (Mval.Exp) (struct let topname = "All" end) +module AD = PreValueDomain.AD module ID = struct @@ -44,7 +45,7 @@ struct end type eval_int = exp -> ID.t -type may_point_to = exp -> LS.t +type may_point_to = exp -> AD.t type is_multiple = varinfo -> bool (** Subset of queries used by the valuedomain, using a simpler representation. *) diff --git a/src/dune b/src/dune index 85944375ea..eac6640451 100644 --- a/src/dune +++ b/src/dune @@ -6,11 +6,15 @@ (library (name goblint_lib) (public_name goblint.lib) - (modules :standard \ goblint mainspec privPrecCompare apronPrecCompare messagesCompare) - (libraries goblint.sites goblint.build-info goblint-cil.all-features batteries.unthreaded qcheck-core.runner sha json-data-encoding jsonrpc cpu arg-complete fpath yaml yaml.unix uuidm goblint_timing catapult goblint_backtrace fileutils + (modules :standard \ goblint privPrecCompare apronPrecCompare messagesCompare) + (libraries goblint.sites goblint.build-info goblint-cil.all-features batteries.unthreaded qcheck-core.runner sha json-data-encoding jsonrpc cpu arg-complete fpath yaml yaml.unix uuidm goblint_timing catapult goblint_backtrace fileutils goblint_std goblint_config goblint_common goblint_domain goblint_library goblint_incremental goblint_tracing ; Conditionally compile based on whether apron optional dependency is installed or not. ; Alternative dependencies seem like the only way to optionally depend on optional dependencies. ; See: https://dune.readthedocs.io/en/stable/concepts.html#alternative-dependencies. + (select gobApron.ml from + (apron -> gobApron.apron.ml) + (-> gobApron.no-apron.ml) + ) (select apronDomain.ml from (apron apron.octD apron.boxD apron.polkaMPQ zarith_mlgmpidl -> apronDomain.apron.ml) (-> apronDomain.no-apron.ml) @@ -56,11 +60,11 @@ (-> violationZ3.no-z3.ml) ) ) + (flags :standard -open Goblint_std) (foreign_stubs (language c) (names stubs)) (ocamlopt_flags :standard -no-float-const-prop) (preprocess (pps ppx_deriving.std ppx_deriving_hash ppx_deriving_yojson ppx_blob)) - (preprocessor_deps (file util/options.schema.json)) (instrumentation (backend bisect_ppx)) ) @@ -73,55 +77,39 @@ (copy_files# witness/z3/*.ml) (executables - (names goblint mainspec) - (public_names goblint -) + (names goblint) + (public_names goblint) (modes byte native) ; https://dune.readthedocs.io/en/stable/dune-files.html#linking-modes - (modules goblint mainspec) - (libraries goblint.lib goblint.sites.dune goblint.build-info.dune) + (modules goblint) + (libraries goblint.lib goblint.sites.dune goblint.build-info.dune goblint_std) (preprocess (pps ppx_deriving.std ppx_deriving_hash ppx_deriving_yojson)) - (flags :standard -linkall) + (flags :standard -linkall -open Goblint_std) ) (executable (name privPrecCompare) (modules privPrecCompare) - (libraries goblint.lib goblint.sites.dune goblint.build-info.dune) + (libraries goblint.lib goblint.sites.dune goblint.build-info.dune goblint_std) (preprocess (pps ppx_deriving.std ppx_deriving_hash ppx_deriving_yojson)) - (flags :standard -linkall) + (flags :standard -linkall -open Goblint_std) ) (executable (name apronPrecCompare) (modules apronPrecCompare) - (libraries goblint.lib goblint.sites.dune goblint.build-info.dune) + (libraries goblint.lib goblint.sites.dune goblint.build-info.dune goblint_std) (preprocess (pps ppx_deriving.std ppx_deriving_hash ppx_deriving_yojson)) - (flags :standard -linkall) + (flags :standard -linkall -open Goblint_std) ) (executable (name messagesCompare) (modules messagesCompare) - (libraries goblint.lib goblint.sites.dune goblint.build-info.dune) + (libraries goblint.lib goblint.sites.dune goblint.build-info.dune goblint_std) (preprocess (pps ppx_deriving.std ppx_deriving_hash ppx_deriving_yojson)) - (flags :standard -linkall) + (flags :standard -linkall -open Goblint_std) ) -(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\"'"))))) - -(rule - (target configProfile.ml) - (mode (promote (until-clean) (only configProfile.ml))) ; replace existing file in source tree, even if releasing (only overrides) - (action (write-file %{target} "(* Automatically regenerated, changes do not persist! *)\nlet profile = \"%{profile}\""))) - -(rule - (target configOcaml.ml) - (mode (promote (until-clean) (only configOcaml.ml))) ; replace existing file in source tree, even if releasing (only overrides) - (action (write-file %{target} "(* Automatically regenerated, changes do not persist! *)\nlet flambda = \"%{ocaml-config:flambda}\""))) - (rule (alias runtest) (deps ../goblint ../scripts/update_suite.rb ../Makefile ../make.sh (source_tree ../tests/regression) (source_tree ../includes) (source_tree ../linux-headers)) @@ -141,3 +129,5 @@ (flags (:standard -warn-error -A -w -unused-var-strict -w -unused-functor-parameter -w +9)) ; https://dune.readthedocs.io/en/stable/faq.html#how-to-make-warnings-non-fatal ) ) + +(documentation) diff --git a/src/framework/analyses.ml b/src/framework/analyses.ml index dd57f40c70..a37a3043c2 100644 --- a/src/framework/analyses.ml +++ b/src/framework/analyses.ml @@ -75,6 +75,7 @@ end module GVarF (V: SpecSysVar) = struct include Printable.Either (V) (CilType.Fundec) + let name () = "FromSpec" let spec x = `Left x let contexts x = `Right x @@ -87,6 +88,22 @@ struct | `Right _ -> true end +module GVarFC (V:SpecSysVar) (C:Printable.S) = +struct + include Printable.Either (V) (Printable.Prod (CilType.Fundec) (C)) + let name () = "FromSpec" + let spec x = `Left x + let call (x, c) = `Right (x, c) + + (* from Basetype.Variables *) + let var_id = show + let node _ = MyCFG.Function Cil.dummyFunDec + let pretty_trace = pretty + let is_write_only = function + | `Left x -> V.is_write_only x + | `Right _ -> true +end + module GVarG (G: Lattice.S) (C: Printable.S) = struct module CSet = @@ -341,7 +358,7 @@ type ('d,'g,'c,'v) ctx = ; edge : MyCFG.edge ; local : 'd ; global : 'v -> 'g - ; spawn : lval option -> varinfo -> exp list -> unit + ; spawn : ?multiple:bool -> lval option -> varinfo -> exp list -> unit ; split : 'd -> Events.t list -> unit ; sideg : 'v -> 'g -> unit } @@ -443,10 +460,10 @@ sig val paths_as_set : (D.t, G.t, C.t, V.t) ctx -> D.t list (** Returns initial state for created thread. *) - val threadenter : (D.t, G.t, C.t, V.t) ctx -> lval option -> varinfo -> exp list -> D.t list + val threadenter : (D.t, G.t, C.t, V.t) ctx -> multiple:bool -> lval option -> varinfo -> exp list -> D.t list (** Updates the local state of the creator thread using initial state of created thread. *) - val threadspawn : (D.t, G.t, C.t, V.t) ctx -> lval option -> varinfo -> exp list -> (D.t, G.t, C.t, V.t) ctx -> D.t + val threadspawn : (D.t, G.t, C.t, V.t) ctx -> multiple:bool -> lval option -> varinfo -> exp list -> (D.t, G.t, C.t, V.t) ctx -> D.t 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 @@ -595,6 +612,12 @@ struct let is_write_only _ = false end +module UnitV = +struct + include Printable.Unit + include StdV +end + module VarinfoV = struct include CilType.Varinfo (* TODO: or Basetype.Variables? *) @@ -642,7 +665,8 @@ struct let vdecl ctx _ = ctx.local let asm x = - ignore (M.info ~category:Unsound "ASM statement ignored."); + M.msg_final Info ~category:Unsound "ASM ignored"; + M.info ~category:Unsound "ASM statement ignored."; x.local (* Just ignore. *) let skip x = x.local (* Just ignore. *) @@ -695,8 +719,8 @@ struct let special ctx (lval: lval option) (f:varinfo) (arglist:exp list) = ctx.local - let threadenter ctx lval f args = [ctx.local] - let threadspawn ctx lval f args fctx = ctx.local + let threadenter ctx ~multiple lval f args = [ctx.local] + let threadspawn ctx ~multiple lval f args fctx = ctx.local end diff --git a/src/framework/constraints.ml b/src/framework/constraints.ml index 740d1f85a9..77d3a38186 100644 --- a/src/framework/constraints.ml +++ b/src/framework/constraints.ml @@ -12,12 +12,17 @@ module M = Messages (** Lifts a [Spec] so that the domain is [Hashcons]d *) module HashconsLifter (S:Spec) - : Spec with module D = Lattice.HConsed (S.D) - and module G = S.G + : Spec with module G = S.G and module C = S.C = struct - module D = Lattice.HConsed (S.D) + 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 @@ -83,11 +88,11 @@ struct 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 lval f args = - List.map D.lift @@ S.threadenter (conv ctx) lval f args + let threadenter ctx ~multiple lval f args = + List.map D.lift @@ S.threadenter (conv ctx) ~multiple lval f args - let threadspawn ctx lval f args fctx = - D.lift @@ S.threadspawn (conv ctx) lval f args (conv fctx) + 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) @@ -167,11 +172,11 @@ struct 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 lval f args = - S.threadenter (conv ctx) lval f args + let threadenter ctx ~multiple lval f args = + S.threadenter (conv ctx) ~multiple lval f args - let threadspawn ctx lval f args fctx = - S.threadspawn (conv ctx) lval f args (conv fctx) + 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) @@ -249,8 +254,8 @@ struct 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 lval f args = lift_fun ctx (List.map lift_start_level) S.threadenter ((|>) args % (|>) f % (|>) lval) - let threadspawn ctx lval f args fctx = lift_fun ctx (lift ctx) S.threadspawn ((|>) (conv fctx) % (|>) args % (|>) f % (|>) lval) + 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 @@ -302,7 +307,7 @@ struct | Queries.EvalFunvar e -> let (d,l) = ctx.local in if leq0 l then - Queries.LS.empty () + Queries.AD.empty () else query' ctx (Queries.EvalFunvar e) | q -> query' ctx q @@ -394,8 +399,8 @@ struct let event ctx e octx = lift_fun ctx S.event ((|>) (conv octx) % (|>) e) - let threadenter ctx lval f args = S.threadenter (conv ctx) lval f args |> List.map (fun d -> (d, snd ctx.local)) - let threadspawn ctx lval f args fctx = lift_fun ctx S.threadspawn ((|>) (conv fctx) % (|>) args % (|>) f % (|>) lval) + 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 @@ -485,8 +490,8 @@ struct 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 lval f args = lift_fun ctx (List.map D.lift) S.threadenter ((|>) args % (|>) f % (|>) lval) [] - let threadspawn ctx lval f args fctx = lift_fun ctx D.lift S.threadspawn ((|>) (conv fctx) % (|>) args % (|>) f % (|>) lval) `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 @@ -563,7 +568,7 @@ struct if !AnalysisState.postsolving then sideg (GVar.contexts f) (G.create_contexts (G.CSet.singleton c)) - let common_ctx var edge prev_node pval (getl:lv -> ld) sidel getg sideg : (D.t, S.G.t, S.C.t, S.V.t) ctx * D.t list ref * (lval option * varinfo * exp list * D.t) list ref = + let common_ctx var edge prev_node pval (getl:lv -> ld) sidel getg sideg : (D.t, S.G.t, S.C.t, S.V.t) ctx * D.t list ref * (lval option * varinfo * exp list * D.t * bool) list ref = let r = ref [] in let spawns = ref [] in (* now watch this ... *) @@ -581,12 +586,12 @@ struct ; split = (fun (d:D.t) es -> assert (List.is_empty es); r := d::!r) ; sideg = (fun g d -> sideg (GVar.spec g) (G.create_spec d)) } - and spawn lval f args = + and spawn ?(multiple=false) lval f args = (* TODO: adjust ctx node/edge? *) (* TODO: don't repeat for all paths that spawn same *) - let ds = S.threadenter ctx lval f args in + let ds = S.threadenter ~multiple ctx lval f args in List.iter (fun d -> - spawns := (lval, f, args, d) :: !spawns; + spawns := (lval, f, args, d, multiple) :: !spawns; match Cilfacade.find_varinfo_fundec f with | fd -> let c = S.context fd d in @@ -618,14 +623,14 @@ struct } in (* TODO: don't forget path dependencies *) - let one_spawn (lval, f, args, fd) = + let one_spawn (lval, f, args, fd, multiple) = let rec fctx = { ctx with ask = (fun (type a) (q: a Queries.t) -> S.query fctx q) ; local = fd } in - S.threadspawn ctx' lval f args fctx + S.threadspawn ctx' ~multiple lval f args fctx in bigsqcup (List.map one_spawn spawns) @@ -754,8 +759,8 @@ struct [v] | _ -> (* Depends on base for query. *) - let ls = ctx.ask (Queries.EvalFunvar e) in - Queries.LS.fold (fun ((x,_)) xs -> x::xs) ls [] + let ad = ctx.ask (Queries.EvalFunvar e) in + Queries.AD.to_var_may ad (* TODO: don't convert, handle UnknownPtr below *) in let one_function f = match f.vtype with @@ -776,16 +781,17 @@ struct end else begin let geq = if var_arg then ">=" else "" in - M.warn ~tags:[CWE 685] "Potential call to function %a with wrong number of arguments (expected: %s%d, actual: %d). This call will be ignored." CilType.Varinfo.pretty f geq p_length arg_length; + M.warn ~category:Unsound ~tags:[Category Call; CWE 685] "Potential call to function %a with wrong number of arguments (expected: %s%d, actual: %d). This call will be ignored." CilType.Varinfo.pretty f geq p_length arg_length; None end | _ -> - M.warn ~category:Call "Something that is not a function (%a) is called." CilType.Varinfo.pretty f; + M.warn ~category:Call "Something that is not a function (%a) is called." CilType.Varinfo.pretty f; None in let funs = List.filter_map one_function functions in if [] = funs then begin - M.warn ~category:Unsound "No suitable function to be called at call site. Continuing with state before call."; + M.msg_final Warning ~category:Unsound ~tags:[Category Call] "No suitable function to call"; + M.warn ~category:Unsound ~tags:[Category Call] "No suitable function to be called at call site. Continuing with state before call."; d (* because LevelSliceLifter *) end else common_joins ctx funs !r !spawns @@ -819,13 +825,13 @@ struct ) let tf var getl sidel getg sideg prev_node (_,edge) d (f,t) = - let old_loc = !Tracing.current_loc in - let old_loc2 = !Tracing.next_loc in - Tracing.current_loc := f; - Tracing.next_loc := t; + let old_loc = !Goblint_tracing.current_loc in + let old_loc2 = !Goblint_tracing.next_loc in + Goblint_tracing.current_loc := f; + Goblint_tracing.next_loc := t; Goblint_backtrace.protect ~mark:(fun () -> TfLocation f) ~finally:(fun () -> - Tracing.current_loc := old_loc; - Tracing.next_loc := old_loc2 + Goblint_tracing.current_loc := old_loc; + Goblint_tracing.next_loc := old_loc2 ) (fun () -> let d = tf var getl sidel getg sideg prev_node edge d in d @@ -898,7 +904,7 @@ struct ; edge = MyCFG.Skip ; local = S.startstate Cil.dummyFunDec.svar (* bot and top both silently raise and catch Deadcode in DeadcodeLifter *) ; global = (fun g -> G.spec (getg (GVar.spec g))) - ; spawn = (fun v d -> failwith "Cannot \"spawn\" in query context.") + ; spawn = (fun ?(multiple=false) v d -> failwith "Cannot \"spawn\" in query context.") ; split = (fun d es -> failwith "Cannot \"split\" in query context.") ; sideg = (fun v g -> failwith "Cannot \"split\" in query context.") } @@ -998,7 +1004,7 @@ struct let dummy_pseudo_return_node f = (* not the same as in CFG, but compares equal because of sid *) - Node.Statement ({Cil.dummyStmt with sid = CfgTools.get_pseudo_return_id f}) + Node.Statement ({Cil.dummyStmt with sid = Cilfacade.get_pseudo_return_id f}) in let add_nodes_of_fun (functions: fundec list) (withEntry: fundec -> bool) = let add_stmts (f: fundec) = @@ -1262,12 +1268,13 @@ struct let fd1 = D.choose octx.local in map ctx Spec.event (fun h -> h e (conv octx fd1)) - let threadenter ctx lval f args = + 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 (fun h -> h lval f args) g [] - let threadspawn ctx lval f args fctx = + 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 (fun h -> h lval f args (conv fctx fd1)) + 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) @@ -1342,7 +1349,7 @@ struct module EM = struct - include MapDomain.MapBot (Basetype.CilExp) (Basetype.Bools) + include MapDomain.MapBot (Basetype.CilExp) (BoolDomain.FlatBool) let name () = "branches" end @@ -1390,6 +1397,7 @@ struct 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 *) @@ -1449,7 +1457,7 @@ struct 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 lv f args fctx = S.threadspawn (conv ctx) lv f args (conv fctx) + 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) @@ -1464,13 +1472,14 @@ struct module V = struct - include Printable.Either (S.V) (Printable.Either (Printable.Prod (Node) (C)) (Printable.Prod (CilType.Fundec) (C))) + include Printable.Either3 (S.V) (Printable.Prod (Node) (C)) (Printable.Prod (CilType.Fundec) (C)) + let name () = "longjmp" let s x = `Left x - let longjmpto x = `Right (`Left x) - let longjmpret x = `Right (`Right x) + let longjmpto x = `Middle x + let longjmpret x = `Right x let is_write_only = function | `Left x -> S.V.is_write_only x - | `Right _ -> false + | _ -> false end module G = @@ -1507,7 +1516,7 @@ struct begin match g with | `Left g -> S.query (conv ctx) (WarnGlobal (Obj.repr g)) - | `Right g -> + | _ -> Queries.Result.top q end | InvariantGlobal g -> @@ -1515,7 +1524,7 @@ struct begin match g with | `Left g -> S.query (conv ctx) (InvariantGlobal (Obj.repr g)) - | `Right g -> + | _ -> Queries.Result.top q end | IterSysVars (vq, vf) -> @@ -1661,7 +1670,8 @@ struct if M.tracing then Messages.tracel "longjmp" "Jumping to %a\n" 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.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 ( @@ -1682,10 +1692,157 @@ struct ) 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 lv f args fctx = S.threadspawn (conv ctx) lv f args (conv fctx) + 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) (Printable.DefaultNames) + + 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) diff --git a/src/framework/control.ml b/src/framework/control.ml index 5ceddf2870..00a6034e27 100644 --- a/src/framework/control.ml +++ b/src/framework/control.ml @@ -14,7 +14,8 @@ module type S2S = functor (X : Spec) -> Spec (* 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 "ana.sv-comp.enabled" || get_bool "exp.arg" in + let arg_enabled = get_bool "witness.graphml.enabled" || get_bool "exp.arg" 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 *) let lift opt (module F : S2S) (module X : Spec) = (module (val if opt then (module F (X)) else (module X) : Spec) : Spec) in @@ -36,6 +37,7 @@ let spec_module: (module Spec) Lazy.t = lazy ( 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*) ) in GobConfig.building_spec := false; ControlSpecC.control_spec_c := (module S1.C); @@ -103,6 +105,8 @@ struct let module StringMap = BatMap.Make (String) in let live_lines = ref StringMap.empty in let dead_lines = ref StringMap.empty in + let module FunSet = Hashtbl.Make (CilType.Fundec) in + let live_funs: unit FunSet.t = FunSet.create 13 in let add_one n v = match n with | Statement s when Cilfacade.(StmtH.mem pseudo_return_to_fun s) -> @@ -113,6 +117,7 @@ struct See: https://github.com/goblint/analyzer/issues/290#issuecomment-881258091. *) let l = UpdateCil.getLoc n in let f = Node.find_fundec n in + FunSet.replace live_funs f (); let add_fun = BatISet.add l.line in let add_file = StringMap.modify_def BatISet.empty f.svar.vname add_fun in let is_dead = LT.for_all (fun (_,x,f) -> Spec.D.is_bot x) v in @@ -134,6 +139,21 @@ struct try StringMap.find fn (StringMap.find file !live_lines) with Not_found -> BatISet.empty in + if List.mem "termination" @@ get_string_list "ana.activated" then ( + (* check if we have upjumping gotos *) + let open Cilfacade in + let warn_for_upjumps fundec gotos = + if FunSet.mem live_funs fundec then ( + (* set nortermiantion flag *) + AnalysisState.svcomp_may_not_terminate := true; + (* iterate through locations to produce warnings *) + LocSet.iter (fun l _ -> + M.warn ~loc:(M.Location.CilLocation l) ~category:Termination "The program might not terminate! (Upjumping Goto)" + ) gotos + ) + in + FunLocH.iter warn_for_upjumps funs_with_upjumping_gotos + ); dead_lines := StringMap.mapi (fun fi -> StringMap.mapi (fun fu ded -> BatISet.diff ded (live fi fu))) !dead_lines; dead_lines := StringMap.map (StringMap.filter (fun _ x -> not (BatISet.is_empty x))) !dead_lines; dead_lines := StringMap.filter (fun _ x -> not (StringMap.is_empty x)) !dead_lines; @@ -280,7 +300,7 @@ struct ; edge = MyCFG.Skip ; local = Spec.D.top () ; global = (fun g -> EQSys.G.spec (getg (EQSys.GVar.spec g))) - ; spawn = (fun _ -> failwith "Global initializers should never spawn threads. What is going on?") + ; spawn = (fun ?(multiple=false) _ -> failwith "Global initializers should never spawn threads. What is going on?") ; split = (fun _ -> failwith "Global initializers trying to split paths.") ; sideg = (fun g d -> sideg (EQSys.GVar.spec g) (EQSys.G.create_spec d)) } @@ -293,7 +313,7 @@ struct if M.tracing then M.trace "con" "Initializer %a\n" CilType.Location.pretty loc; (*incr count; if (get_bool "dbg.verbose")&& (!count mod 1000 = 0) then Printf.printf "%d %!" !count; *) - Tracing.current_loc := loc; + Goblint_tracing.current_loc := loc; match edge with | MyCFG.Entry func -> if M.tracing then M.trace "global_inits" "Entry %a\n" d_lval (var func.svar); @@ -315,9 +335,9 @@ struct in let with_externs = do_extern_inits ctx file in (*if (get_bool "dbg.verbose") then Printf.printf "Number of init. edges : %d\nWorking:" (List.length edges); *) - let old_loc = !Tracing.current_loc in + let old_loc = !Goblint_tracing.current_loc in let result : Spec.D.t = List.fold_left transfer_func with_externs edges in - Tracing.current_loc := old_loc; + Goblint_tracing.current_loc := old_loc; if M.tracing then M.trace "global_inits" "startstate: %a\n" Spec.D.pretty result; result, !funs in @@ -385,7 +405,7 @@ struct ; edge = MyCFG.Skip ; local = st ; global = (fun g -> EQSys.G.spec (getg (EQSys.GVar.spec g))) - ; spawn = (fun _ -> failwith "Bug1: Using enter_func for toplevel functions with 'otherstate'.") + ; 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)) } @@ -417,13 +437,13 @@ struct ; edge = MyCFG.Skip ; local = st ; global = (fun g -> EQSys.G.spec (getg (EQSys.GVar.spec g))) - ; spawn = (fun _ -> failwith "Bug1: Using enter_func for toplevel functions with 'otherstate'.") + ; 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 (* TODO: don't hd *) - List.hd (Spec.threadenter ctx None v []) + List.hd (Spec.threadenter ctx ~multiple:false None v []) (* TODO: do threadspawn to mainfuns? *) in let prestartstate = Spec.startstate MyCFG.dummy_func.svar in (* like in do_extern_inits *) @@ -529,7 +549,7 @@ struct GobConfig.write_file config; let module Meta = struct type t = { command : string; version: string; timestamp : float; localtime : string } [@@deriving to_yojson] - let json = to_yojson { command = GobSys.command_line; version = Version.goblint; timestamp = Unix.time (); localtime = GobUnix.localtime () } + let json = to_yojson { command = GobSys.command_line; version = Goblint_build_info.version; timestamp = Unix.time (); localtime = GobUnix.localtime () } end in (* Yojson.Safe.to_file meta Meta.json; *) diff --git a/src/framework/resultQuery.ml b/src/framework/resultQuery.ml index ce5839ef30..c676c41c14 100644 --- a/src/framework/resultQuery.ml +++ b/src/framework/resultQuery.ml @@ -18,7 +18,7 @@ struct ; edge = MyCFG.Skip ; local = local ; global = (fun g -> try EQSys.G.spec (GHT.find gh (EQSys.GVar.spec g)) with Not_found -> Spec.G.bot ()) (* see 29/29 on why fallback is needed *) - ; spawn = (fun v d -> failwith "Cannot \"spawn\" in witness context.") + ; spawn = (fun ?(multiple=false) v d -> failwith "Cannot \"spawn\" in witness context.") ; split = (fun d es -> failwith "Cannot \"split\" in witness context.") ; sideg = (fun v g -> failwith "Cannot \"sideg\" in witness context.") } @@ -37,7 +37,7 @@ struct ; edge = MyCFG.Skip ; local = local ; global = (fun g -> try EQSys.G.spec (GHT.find gh (EQSys.GVar.spec g)) with Not_found -> Spec.G.bot ()) (* TODO: how can be missing? *) - ; spawn = (fun v d -> failwith "Cannot \"spawn\" in witness context.") + ; spawn = (fun ?(multiple=false) v d -> failwith "Cannot \"spawn\" in witness context.") ; split = (fun d es -> failwith "Cannot \"split\" in witness context.") ; sideg = (fun v g -> failwith "Cannot \"sideg\" in witness context.") } @@ -57,7 +57,7 @@ struct ; edge = MyCFG.Skip ; local = Spec.startstate GoblintCil.dummyFunDec.svar (* bot and top both silently raise and catch Deadcode in DeadcodeLifter *) (* TODO: is this startstate bad? *) ; global = (fun v -> EQSys.G.spec (try GHT.find gh (EQSys.GVar.spec v) with Not_found -> EQSys.G.bot ())) (* TODO: how can be missing? *) - ; spawn = (fun v d -> failwith "Cannot \"spawn\" in query context.") + ; spawn = (fun ?(multiple=false) v d -> failwith "Cannot \"spawn\" in query context.") ; split = (fun d es -> failwith "Cannot \"split\" in query context.") ; sideg = (fun v g -> failwith "Cannot \"split\" in query context.") } diff --git a/src/goblint.ml b/src/goblint.ml index a73d0a9fad..25e809f9e9 100644 --- a/src/goblint.ml +++ b/src/goblint.ml @@ -38,6 +38,8 @@ let main () = print_endline (GobUnix.localtime ()); print_endline 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 (); let file = lazy (Fun.protect ~finally:GoblintDir.finalize preprocess_parse_merge) in if get_bool "server.enabled" then ( let file = @@ -73,7 +75,7 @@ let main () = exit 1 | Sys.Break -> (* raised on Ctrl-C if `Sys.catch_break true` *) do_stats (); - (* Printexc.print_backtrace BatInnerIO.stderr *) + Printexc.print_backtrace stderr; eprintf "%s\n" (MessageUtil.colorize ~fd:Unix.stderr ("{RED}Analysis was aborted by SIGINT (Ctrl-C)!")); Goblint_timing.teardown_tef (); exit 131 (* same exit code as without `Sys.catch_break true`, otherwise 0 *) diff --git a/src/goblint_lib.ml b/src/goblint_lib.ml index d8d74acc0f..5a2e0d3e0e 100644 --- a/src/goblint_lib.ml +++ b/src/goblint_lib.ml @@ -1,3 +1,4 @@ +(** Main library. *) (** {1 Framework} *) @@ -22,6 +23,7 @@ module CfgTools = CfgTools module Analyses = Analyses module Constraints = Constraints module AnalysisState = AnalysisState +module AnalysisStateUtil = AnalysisStateUtil module ControlSpecC = ControlSpecC (** Master control program (MCP) is the analysis specification for the dynamic product of activated analyses. *) @@ -49,7 +51,7 @@ module VarQuery = VarQuery (** {2 Configuration} Runtime configuration is represented as JSON. - Options are specified and documented by the JSON schema [src/util/options.schema.json]. *) + Options are specified and documented by the JSON schema [src/config/options.schema.json]. *) module GobConfig = GobConfig module AfterConfig = AfterConfig @@ -74,6 +76,7 @@ module ApronAnalysis = ApronAnalysis module AffineEqualityAnalysis = AffineEqualityAnalysis module VarEq = VarEq module CondVars = CondVars +module TmpSpecial = TmpSpecial (** {2 Heap} @@ -82,6 +85,9 @@ module CondVars = CondVars module Region = Region module MallocFresh = MallocFresh module Malloc_null = Malloc_null +module MemLeak = MemLeak +module UseAfterFree = UseAfterFree +module MemOutOfBounds = MemOutOfBounds (** {2 Concurrency} @@ -124,7 +130,7 @@ module ExtractPthread = ExtractPthread Analyses related to [longjmp] and [setjmp]. *) module ActiveSetjmp = ActiveSetjmp -module ModifiedSinceLongjmp = ModifiedSinceLongjmp +module ModifiedSinceSetjmp = ModifiedSinceSetjmp module ActiveLongjmp = ActiveLongjmp module PoisonVariables = PoisonVariables module Vla = Vla @@ -141,12 +147,10 @@ module UnitAnalysis = UnitAnalysis (** {2 Other} *) module Assert = Assert -module FileUse = FileUse +module LoopTermination = LoopTermination module Uninit = Uninit -module Termination = Termination module Expsplit = Expsplit module StackTrace = StackTrace -module Spec = Spec (** {2 Helper} @@ -207,6 +211,7 @@ module FloatDomain = FloatDomain module Mval = Mval module Offset = Offset +module StringDomain = StringDomain module AddressDomain = AddressDomain (** {5 Complex} *) @@ -256,12 +261,8 @@ module AccessDomain = AccessDomain module MusteqDomain = MusteqDomain module RegionDomain = RegionDomain -module FileDomain = FileDomain module StackDomain = StackDomain -module MvalMapDomain = MvalMapDomain -module SpecDomain = SpecDomain - (** {2 Testing} Modules related to (property-based) testing of domains. *) @@ -324,7 +325,6 @@ module SolverBox = SolverBox Various input/output interfaces and formats. *) module Messages = Messages -module Tracing = Tracing (** {2 Front-end} @@ -333,6 +333,7 @@ module Tracing = Tracing module Preprocessor = Preprocessor module CompilationDatabase = CompilationDatabase module MakefileUtil = MakefileUtil +module TerminationPreprocessing = TerminationPreprocessing (** {2 Witnesses} @@ -439,6 +440,7 @@ module WideningThresholds = WideningThresholds module VectorMatrix = VectorMatrix module SharedFunctions = SharedFunctions +module GobApron = GobApron (** {2 Precision comparison} *) @@ -448,46 +450,14 @@ module PrivPrecCompareUtil = PrivPrecCompareUtil module RelationPrecCompareUtil = RelationPrecCompareUtil module ApronPrecCompareUtil = ApronPrecCompareUtil -(** {2 Build info} *) - -(** OCaml compiler info. *) -module ConfigOcaml = ConfigOcaml - -(** Dune profile info. *) -module ConfigProfile = ConfigProfile - -(** Goblint version info. *) -module Version = Version - -(** Goblint git version info. *) -module ConfigVersion = ConfigVersion - - (** {1 Library extensions} - OCaml library extensions which are completely independent of Goblint. *) + OCaml library extensions which are completely independent of Goblint. + + See {!Goblint_std}. *) (** {2 Standard library} OCaml standard library extensions which are not provided by {!Batteries}. *) module GobFormat = GobFormat -module GobGc = GobGc -module GobHashtbl = GobHashtbl -module GobList = GobList -module GobRef = GobRef -module GobResult = GobResult -module GobOption = GobOption -module GobSys = GobSys -module GobUnix = GobUnix - -(** {2 Other libraries} - - External library extensions. *) - -module GobFpath = GobFpath -module GobPretty = GobPretty -module GobYaml = GobYaml -module GobYojson = GobYojson -module GobZ = GobZ -module MyCheck = MyCheck diff --git a/src/util/cilMaps.ml b/src/incremental/cilMaps.ml similarity index 100% rename from src/util/cilMaps.ml rename to src/incremental/cilMaps.ml diff --git a/src/incremental/compareCFG.ml b/src/incremental/compareCFG.ml index 225cbb1c76..55b3fa8fc5 100644 --- a/src/incremental/compareCFG.ml +++ b/src/incremental/compareCFG.ml @@ -17,7 +17,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 = CfgTools.get_pseudo_return_id f in + let pid = Cilfacade.get_pseudo_return_id f in sid == pid in match x,y with | Statement s1, Statement s2 -> diff --git a/src/incremental/dune b/src/incremental/dune new file mode 100644 index 0000000000..595dba22f7 --- /dev/null +++ b/src/incremental/dune @@ -0,0 +1,22 @@ +(include_subdirs no) + +(library + (name goblint_incremental) + (public_name goblint.incremental) + (wrapped false) ; TODO: wrap + (libraries + batteries.unthreaded + zarith + goblint_std + goblint_config + goblint_common + goblint-cil + fpath) + (flags :standard -open Goblint_std) + (preprocess + (pps + ppx_deriving.std + ppx_deriving_hash + ppx_deriving_yojson))) + +(documentation) diff --git a/src/incremental/incremental.mld b/src/incremental/incremental.mld new file mode 100644 index 0000000000..bf9b6e6a58 --- /dev/null +++ b/src/incremental/incremental.mld @@ -0,0 +1,16 @@ +{0 Library goblint.incremental} +This library is unwrapped and provides the following top-level modules. +For better context, see {!Goblint_lib} which also documents these modules. + + +{1 Incremental} + +{!modules: +CompareCIL +CompareAST +CompareCFG +UpdateCil +MaxIdUtil +Serialize +CilMaps +} diff --git a/src/index.mld b/src/index.mld new file mode 100644 index 0000000000..76b9d230dd --- /dev/null +++ b/src/index.mld @@ -0,0 +1,66 @@ +{0 goblint index} + +{1 Goblint} +The following libraries make up Goblint's main codebase. + +{2 Library goblint.lib} +{!modules:Goblint_lib} +This library currently contains the majority of Goblint and is in the process of being split into smaller libraries. + +{2 Library goblint.config} +This {{!page-config}unwrapped library} contains various configuration modules extracted from {!Goblint_lib}. + +{2 Library goblint.common} +This {{!page-common}unwrapped library} contains various common modules extracted from {!Goblint_lib}. + +{2 Library goblint.domain} +This {{!page-domain}unwrapped library} contains various domain modules extracted from {!Goblint_lib}. + +{2 Library goblint.library} +This {{!page-library}unwrapped library} contains various library specification modules extracted from {!Goblint_lib}. + +{2 Library goblint.incremental} +This {{!page-incremental}unwrapped library} contains various incremental modules extracted from {!Goblint_lib}. + + +{1 Library extensions} +The following libraries provide extensions to other OCaml libraries. + +{2 Library goblint.std} +{!modules:Goblint_std} + + +{1 Package utilities} +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} +The following libraries provide utilities which are completely independent of Goblint. + +{2 Library goblint.backtrace} +{!modules:Goblint_backtrace} + +{2 Library goblint.timing} +{!modules:Goblint_timing} + +{2 Library goblint.tracing} +{!modules:Goblint_tracing} + + +{1 Vendored} +The following libraries are vendored in Goblint. + +{2 Library goblint.zarith.mlgmpidl} +{!modules:Z_mlgmpidl} diff --git a/src/main.camldoc b/src/main.camldoc index ec08a14a7b..0a0e52035f 100644 --- a/src/main.camldoc +++ b/src/main.camldoc @@ -85,7 +85,6 @@ FlagModeDomain LockDomain StackDomain FileDomain -SpecDomain LvalMapDomain } @@ -106,7 +105,6 @@ Glob {!modules: MCP Base -Spec CondVars Contain diff --git a/src/maingoblint.ml b/src/maingoblint.ml index 8944d87ea0..2c7d353594 100644 --- a/src/maingoblint.ml +++ b/src/maingoblint.ml @@ -9,11 +9,11 @@ let writeconffile = ref None (** Print version and bail. *) let print_version ch = - printf "Goblint version: %s\n" Version.goblint; + printf "Goblint version: %s\n" Goblint_build_info.version; printf "Cil version: %s\n" Cil.cilVersion; - printf "Dune profile: %s\n" ConfigProfile.profile; + printf "Dune profile: %s\n" Goblint_build_info.dune_profile; printf "OCaml version: %s\n" Sys.ocaml_version; - printf "OCaml flambda: %s\n" ConfigOcaml.flambda; + printf "OCaml flambda: %s\n" Goblint_build_info.ocaml_flambda; if get_bool "dbg.verbose" then ( printf "Library versions:\n"; List.iter (fun (name, version) -> @@ -53,7 +53,7 @@ let rec option_spec_list: Arg_complete.speclist Lazy.t = lazy ( let add_string l = let f str = l := str :: !l in Arg_complete.String (f, Arg_complete.empty) in let add_int l = let f str = l := str :: !l in Arg_complete.Int (f, Arg_complete.empty) in let set_trace sys = - if Messages.tracing then Tracing.addsystem sys + if Messages.tracing then Goblint_tracing.addsystem sys else (prerr_endline "Goblint has been compiled without tracing, recompile in trace profile (./scripts/trace_on.sh)"; raise Stdlib.Exit) in let configure_html () = @@ -112,8 +112,8 @@ let rec option_spec_list: Arg_complete.speclist Lazy.t = lazy ( ; "--print_options" , Arg_complete.Unit (fun () -> Options.print_options (); exit 0), "" ; "--print_all_options" , Arg_complete.Unit (fun () -> Options.print_all_options (); exit 0), "" ; "--trace" , Arg_complete.String (set_trace, Arg_complete.empty), "" - ; "--tracevars" , add_string Tracing.tracevars, "" - ; "--tracelocs" , add_int Tracing.tracelocs, "" + ; "--tracevars" , add_string Goblint_tracing.tracevars, "" + ; "--tracelocs" , add_int Goblint_tracing.tracelocs, "" ; "--help" , Arg_complete.Unit (fun _ -> print_help stdout),"" ; "--html" , Arg_complete.Unit (fun _ -> configure_html ()),"" ; "--sarif" , Arg_complete.Unit (fun _ -> configure_sarif ()),"" @@ -135,6 +135,7 @@ let check_arguments () = if get_bool "allfuns" && not (get_bool "exp.earlyglobs") then (set_bool "exp.earlyglobs" true; warn "allfuns enables exp.earlyglobs.\n"); if not @@ List.mem "escape" @@ get_string_list "ana.activated" then warn "Without thread escape analysis, every local variable whose address is taken is considered escaped, i.e., global!"; if List.mem "malloc_null" @@ get_string_list "ana.activated" && not @@ get_bool "sem.malloc.fail" then (set_bool "sem.malloc.fail" true; warn "The malloc_null analysis enables sem.malloc.fail."); + if List.mem "memOutOfBounds" @@ get_string_list "ana.activated" && not @@ get_bool "cil.addNestedScopeAttr" then (set_bool "cil.addNestedScopeAttr" true; warn "The memOutOfBounds analysis enables cil.addNestedScopeAttr."); if get_bool "ana.base.context.int" && not (get_bool "ana.base.context.non-ptr") then (set_bool "ana.base.context.int" false; warn "ana.base.context.int implicitly disabled by ana.base.context.non-ptr"); (* order matters: non-ptr=false, int=true -> int=false cascades to interval=false with warning *) if get_bool "ana.base.context.interval" && not (get_bool "ana.base.context.int") then (set_bool "ana.base.context.interval" false; warn "ana.base.context.interval implicitly disabled by ana.base.context.int"); @@ -159,7 +160,14 @@ let check_arguments () = ^ String.concat " and " @@ List.map (fun s -> "'" ^ s ^ "'") imprecise_options) ); if get_bool "solvers.td3.space" && get_bool "solvers.td3.remove-wpoint" then fail "solvers.td3.space is incompatible with solvers.td3.remove-wpoint"; - if get_bool "solvers.td3.space" && get_string "solvers.td3.side_widen" = "sides-local" then fail "solvers.td3.space is incompatible with solvers.td3.side_widen = 'sides-local'" + if get_bool "solvers.td3.space" && get_string "solvers.td3.side_widen" = "sides-local" then fail "solvers.td3.space is incompatible with solvers.td3.side_widen = 'sides-local'"; + if List.mem "termination" @@ get_string_list "ana.activated" then ( + if GobConfig.get_bool "incremental.load" || GobConfig.get_bool "incremental.save" then fail "termination analysis is not compatible with incremental analysis"; + set_list "ana.activated" (GobConfig.get_list "ana.activated" @ [`String ("threadflag")]); + set_string "sem.int.signed_overflow" "assume_none"; + warn "termination analysis implicitly activates threadflag analysis and set sem.int.signed_overflow to assume_none"; + ); + if not (get_bool "ana.sv-comp.enabled") && get_bool "witness.graphml.enabled" then fail "witness.graphml.enabled: cannot generate GraphML witness without SV-COMP mode (ana.sv-comp.enabled)" (** Initialize some globals in other modules. *) let handle_flags () = @@ -183,8 +191,10 @@ let handle_flags () = let handle_options () = check_arguments (); - AfterConfig.run (); 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 + AutoTune.focusOnMemSafetySpecification (); + AfterConfig.run (); Cilfacade.init_options (); handle_flags () @@ -252,6 +262,15 @@ let preprocess_files () = (* Preprocessor flags *) let cppflags = ref (get_string_list "pre.cppflags") in + if get_bool "ana.sv-comp.enabled" then ( + let architecture_flag = match get_string "exp.architecture" with + | "32bit" -> "-m32" + | "64bit" -> "-m64" + | _ -> assert false + in + cppflags := architecture_flag :: !cppflags + ); + (* the base include directory *) (* TODO: any better way? dune executable promotion doesn't add _build sites *) let source_lib_dirs = @@ -410,6 +429,10 @@ let preprocess_files () = ); preprocessed +(** Regex for special "paths" in cpp output: + , , but also translations! *) +let special_path_regexp = Str.regexp "<.+>" + (** Parse preprocessed files *) let parse_preprocessed preprocessed = (* get the AST *) @@ -417,10 +440,10 @@ let parse_preprocessed preprocessed = let goblint_cwd = GobFpath.cwd () in let get_ast_and_record_deps (preprocessed_file, task_opt) = - let transform_file (path_str, system_header) = match path_str with - | "" | "" -> + let transform_file (path_str, system_header) = + if Str.string_match special_path_regexp path_str 0 then (path_str, system_header) (* ignore special "paths" *) - | _ -> + else let path = Fpath.v path_str in let path' = if get_bool "pre.transform-paths" then ( let cwd_opt = @@ -479,7 +502,7 @@ let merge_parsed parsed = Cilfacade.current_file := merged_AST; (* Set before createCFG, so Cilfacade maps can be computed for loop unrolling. *) CilCfg.createCFG merged_AST; (* Create CIL CFG from CIL AST. *) - Cilfacade.reset_lazy (); (* Reset Cilfacade maps, which need to be recomputer after loop unrolling. *) + Cilfacade.reset_lazy ~keepupjumpinggotos:true (); (* Reset Cilfacade maps, which need to be recomputer after loop unrolling but keep gotos. *) merged_AST let preprocess_parse_merge () = @@ -584,19 +607,19 @@ let do_gobview cilfile = let file_dir = Fpath.(run_dir / "files") in GobSys.mkdir_or_exists file_dir; let file_loc = Hashtbl.create 113 in - let counter = ref 0 in - let copy path = + let copy (path, i) = let name, ext = Fpath.split_ext (Fpath.base path) in - let unique_name = Fpath.add_ext ext (Fpath.add_ext (string_of_int !counter) name) in - counter := !counter + 1; + let unique_name = Fpath.add_ext ext (Fpath.add_ext (string_of_int i) name) in let dest = Fpath.(file_dir // unique_name) in let gobview_path = match Fpath.relativize ~root:run_dir dest with | Some p -> Fpath.to_string p | None -> failwith "The gobview directory should be a prefix of the paths of c files copied to the gobview directory" in Hashtbl.add file_loc (Fpath.to_string path) gobview_path; - FileUtil.cp [Fpath.to_string path] (Fpath.to_string dest) in + FileUtil.cp [Fpath.to_string path] (Fpath.to_string dest) + in let source_paths = Preprocessor.FpathH.to_list Preprocessor.dependencies |> List.concat_map (fun (_, m) -> Fpath.Map.fold (fun p _ acc -> p::acc) m []) in - List.iter copy source_paths; + let source_file_paths = List.filteri_map (fun i e -> if Fpath.is_file_path e then Some (e, i) else None) source_paths in + List.iter copy source_file_paths; Serialize.marshal file_loc (Fpath.(run_dir / "file_loc.marshalled")); (* marshal timing statistics *) let stats = Fpath.(run_dir / "stats.marshalled") in diff --git a/src/mainspec.ml b/src/mainspec.ml deleted file mode 100644 index 4509645f98..0000000000 --- a/src/mainspec.ml +++ /dev/null @@ -1,13 +0,0 @@ -open Goblint_lib -open Batteries (* otherwise open_in would return wrong type for SpecUtil *) -open SpecUtil - -let _ = - (* no arguments -> run interactively (= reading from stdin) *) - let args = Array.length Sys.argv > 1 in - if args && Sys.argv.(1) = "-" then - ignore(parse ~dot:true stdin) - else - let cin = if args then open_in Sys.argv.(1) else stdin in - ignore(parse ~repl:(not args) ~print:true cin) -(* exit 0 *) diff --git a/src/solvers/postSolver.ml b/src/solvers/postSolver.ml index 346ce312b1..e01560c752 100644 --- a/src/solvers/postSolver.ml +++ b/src/solvers/postSolver.ml @@ -82,10 +82,13 @@ module Verify: F = let complain_constraint x ~lhs ~rhs = AnalysisState.verified := Some false; + M.msg_final Error ~category:Unsound "Fixpoint not reached"; ignore (Pretty.printf "Fixpoint not reached at %a\n @[Solver computed:\n%a\nRight-Hand-Side:\n%a\nDifference: %a\n@]" S.Var.pretty_trace x S.Dom.pretty lhs S.Dom.pretty rhs S.Dom.pretty_diff (rhs, lhs)) let complain_side x y ~lhs ~rhs = AnalysisState.verified := Some false; + + M.msg_final Error ~category:Unsound "Fixpoint not reached"; ignore (Pretty.printf "Fixpoint not reached at %a\nOrigin: %a\n @[Solver computed:\n%a\nSide-effect:\n%a\nDifference: %a\n@]" S.Var.pretty_trace y S.Var.pretty_trace x S.Dom.pretty lhs S.Dom.pretty rhs S.Dom.pretty_diff (rhs, lhs)) let one_side ~vh ~x ~y ~d = @@ -151,13 +154,7 @@ struct module VH = Hashtbl.Make (S.Var) (* starts as Hashtbl for quick lookup *) - let starth = - (* VH.of_list S.starts *) (* TODO: BatHashtbl.Make.of_list is broken, use after new Batteries release *) - let starth = VH.create (List.length S.starts) in - List.iter (fun (x, d) -> - VH.replace starth x d - ) S.starts; - starth + let starth = VH.of_list S.starts let system x = match S.system x, VH.find_option starth x with diff --git a/src/solvers/sLRphased.ml b/src/solvers/sLRphased.ml index f4c3389f1d..c120a7bc6c 100644 --- a/src/solvers/sLRphased.ml +++ b/src/solvers/sLRphased.ml @@ -73,7 +73,7 @@ module Make = let effects = ref Set.empty in let side y d = assert (not (S.Dom.is_bot d)); - trace "sol" "SIDE: Var: %a\nVal: %a\n" S.Var.pretty_trace y S.Dom.pretty d; + if tracing then trace "sol" "SIDE: Var: %a\nVal: %a\n" S.Var.pretty_trace y S.Dom.pretty d; let first = not (Set.mem y !effects) in effects := Set.add y !effects; if first then ( @@ -109,11 +109,11 @@ module Make = if wpx then if b then let nar = narrow old tmp in - trace "sol" "NARROW: Var: %a\nOld: %a\nNew: %a\nWiden: %a\n" S.Var.pretty_trace x S.Dom.pretty old S.Dom.pretty tmp S.Dom.pretty nar; + if tracing then trace "sol" "NARROW: Var: %a\nOld: %a\nNew: %a\nWiden: %a\n" S.Var.pretty_trace x S.Dom.pretty old S.Dom.pretty tmp S.Dom.pretty nar; nar else let wid = S.Dom.widen old (S.Dom.join old tmp) in - trace "sol" "WIDEN: Var: %a\nOld: %a\nNew: %a\nWiden: %a\n" S.Var.pretty_trace x S.Dom.pretty old S.Dom.pretty tmp S.Dom.pretty wid; + if tracing then trace "sol" "WIDEN: Var: %a\nOld: %a\nNew: %a\nWiden: %a\n" S.Var.pretty_trace x S.Dom.pretty old S.Dom.pretty tmp S.Dom.pretty wid; wid else tmp @@ -163,7 +163,7 @@ module Make = and sides x = let w = try HM.find set x with Not_found -> VS.empty in let v = Enum.fold (fun d z -> try S.Dom.join d (HPM.find rho' (z,x)) with Not_found -> d) (S.Dom.bot ()) (VS.enum w) - in trace "sol" "SIDES: Var: %a\nVal: %a\n" S.Var.pretty_trace x S.Dom.pretty v; v + in if tracing then trace "sol" "SIDES: Var: %a\nVal: %a\n" S.Var.pretty_trace x S.Dom.pretty v; v and eq x get set = eval_rhs_event x; match S.system x with diff --git a/src/solvers/sLRterm.ml b/src/solvers/sLRterm.ml index d4f4671c46..eb11447d11 100644 --- a/src/solvers/sLRterm.ml +++ b/src/solvers/sLRterm.ml @@ -64,14 +64,14 @@ module SLR3term = HM.replace rho x (S.Dom.bot ()); HM.replace infl x (VS.add x VS.empty); let c = if side then count_side else count in - trace "sol" "INIT: Var: %a with prio %d\n" S.Var.pretty_trace x !c; + if tracing then trace "sol" "INIT: Var: %a with prio %d\n" S.Var.pretty_trace x !c; HM.replace key x !c; decr c end in let sides x = let w = try HM.find set x with Not_found -> VS.empty in let v = Enum.fold (fun d z -> try S.Dom.join d (HPM.find rho' (z,x)) with Not_found -> d) (S.Dom.bot ()) (VS.enum w) in - trace "sol" "SIDES: Var: %a\nVal: %a\n" S.Var.pretty_trace x S.Dom.pretty v; v + if tracing then trace "sol" "SIDES: Var: %a\nVal: %a\n" S.Var.pretty_trace x S.Dom.pretty v; v in let rec iterate b_old prio = if H.size !q = 0 || min_key q > prio then () @@ -122,7 +122,7 @@ module SLR3term = ) *) (* if S.Dom.is_bot d then print_endline "BOT" else *) - trace "sol" "SIDE: Var: %a\nVal: %a\n" S.Var.pretty_trace y S.Dom.pretty d; + if tracing then trace "sol" "SIDE: Var: %a\nVal: %a\n" S.Var.pretty_trace y S.Dom.pretty d; let first = not (Set.mem y !effects) in effects := Set.add y !effects; if first then ( @@ -156,17 +156,17 @@ module SLR3term = if wpx then if S.Dom.leq tmp old then ( let nar = narrow old tmp in - trace "sol" "NARROW1: Var: %a\nOld: %a\nNew: %a\nNarrow: %a" S.Var.pretty_trace x S.Dom.pretty old S.Dom.pretty tmp S.Dom.pretty nar; + if tracing then trace "sol" "NARROW1: Var: %a\nOld: %a\nNew: %a\nNarrow: %a" S.Var.pretty_trace x S.Dom.pretty old S.Dom.pretty tmp S.Dom.pretty nar; nar, true ) else if b_old then ( let nar = narrow old tmp in - trace "sol" "NARROW2: Var: %a\nOld: %a\nNew: %a\nNarrow: %a" S.Var.pretty_trace x S.Dom.pretty old S.Dom.pretty tmp S.Dom.pretty nar; + if tracing then trace "sol" "NARROW2: Var: %a\nOld: %a\nNew: %a\nNarrow: %a" S.Var.pretty_trace x S.Dom.pretty old S.Dom.pretty tmp S.Dom.pretty nar; nar, true ) else ( let wid = S.Dom.widen old (S.Dom.join old tmp) in - trace "sol" "WIDEN: Var: %a\nOld: %a\nNew: %a\nWiden: %a" S.Var.pretty_trace x S.Dom.pretty old S.Dom.pretty tmp S.Dom.pretty wid; + if tracing then trace "sol" "WIDEN: Var: %a\nOld: %a\nNew: %a\nWiden: %a" S.Var.pretty_trace x S.Dom.pretty old S.Dom.pretty tmp S.Dom.pretty wid; wid, false ) else diff --git a/src/solvers/td3.ml b/src/solvers/td3.ml index f0a728f73b..07edc632c7 100644 --- a/src/solvers/td3.ml +++ b/src/solvers/td3.ml @@ -327,13 +327,15 @@ module Base = else box old eqd in - if tracing then trace "sol" "Var: %a (wp: %b)\nOld value: %a\nNew value: %a\n" S.Var.pretty_trace x wp S.Dom.pretty old S.Dom.pretty wpd; + if tracing then trace "sol" "Var: %a (wp: %b)\nOld value: %a\nEqd: %a\nNew value: %a\n" S.Var.pretty_trace x wp S.Dom.pretty old S.Dom.pretty eqd S.Dom.pretty wpd; if cache then ( if tracing then trace "cache" "cache size %d for %a\n" (HM.length l) S.Var.pretty_trace x; cache_sizes := HM.length l :: !cache_sizes; ); if not (Timing.wrap "S.Dom.equal" (fun () -> S.Dom.equal old wpd) ()) then ( (* value changed *) if tracing then trace "sol" "Changed\n"; + (* if tracing && not (S.Dom.is_bot old) && HM.mem wpoint x then trace "solchange" "%a (wpx: %b): %a -> %a\n" S.Var.pretty_trace x (HM.mem wpoint x) S.Dom.pretty old S.Dom.pretty wpd; *) + if tracing && not (S.Dom.is_bot old) && HM.mem wpoint x then trace "solchange" "%a (wpx: %b): %a\n" S.Var.pretty_trace x (HM.mem wpoint x) S.Dom.pretty_diff (wpd, old); update_var_event x old wpd; HM.replace rho x wpd; destabilize x; @@ -429,7 +431,8 @@ module Base = if tracing then trace "sol2" "stable add %a\n" S.Var.pretty_trace y; HM.replace stable y (); if not (S.Dom.leq tmp old) then ( - if tracing && not (S.Dom.is_bot old) then trace "solside" "side to %a (wpx: %b) from %a\n" S.Var.pretty_trace y (HM.mem wpoint y) (Pretty.docOpt (S.Var.pretty_trace ())) x; + if tracing && not (S.Dom.is_bot old) then trace "solside" "side to %a (wpx: %b) from %a: %a -> %a\n" S.Var.pretty_trace y (HM.mem wpoint y) (Pretty.docOpt (S.Var.pretty_trace ())) x S.Dom.pretty old S.Dom.pretty tmp; + if tracing && not (S.Dom.is_bot old) then trace "solchange" "side to %a (wpx: %b) from %a: %a\n" S.Var.pretty_trace y (HM.mem wpoint y) (Pretty.docOpt (S.Var.pretty_trace ())) x S.Dom.pretty_diff (tmp, old); let sided = match x with | Some x -> let sided = VS.mem x old_sides in diff --git a/src/spec/dune b/src/spec/dune deleted file mode 100644 index 47c22a0d46..0000000000 --- a/src/spec/dune +++ /dev/null @@ -1,2 +0,0 @@ -(ocamllex specLexer) -(ocamlyacc specParser) diff --git a/src/spec/file.dot b/src/spec/file.dot deleted file mode 100644 index a78c64d3fc..0000000000 --- a/src/spec/file.dot +++ /dev/null @@ -1,37 +0,0 @@ -digraph file { - // changed file pointer {fp} (no longer safe) - - // file handle is not saved! - // overwriting still opened file handle - // file is never closed - // file may be never closed - // closeing unopened file handle - // closeing already closed file handle - // writing to closed file handle - // writing to unopened file handle - // writing to read-only file handle - - // unclosed files: ... - // maybe unclosed files: ... - - w1 [label="file handle is not saved!"]; - w2 [label="closeing unopened file handle"]; - w3 [label="writing to unopened file handle"]; - w4 [label="writing to read-only file handle"]; - w5 [label="closeing already closed file handle"]; - w6 [label="writing to closed file handle"]; - - 1 -> w1 [label="fopen(_)"]; - 1 -> w2 [label="fclose($fp)"]; - 1 -> w3 [label="fprintf($fp, _)"]; - 1 -> open_read [label="$fp = fopen($path, \"r\")"]; - 1 -> open_write [label="$fp = fopen($path, \"w\")"]; - 1 -> open_write [label="$fp = fopen($path, \"a\")"]; - open_read -> w4 [label="fprintf($fp, _)"]; - open_write -> open_write [label="fprintf($fp, _)"]; - open_read -> closed [label="fclose($fp)"]; - open_write -> closed [label="fclose($fp)"]; - closed -> w5 [label="fclose($fp)"]; - closed -> w6 [label="fprintf($fp, _)"]; - closed -> 1 [label="->"]; -} \ No newline at end of file diff --git a/src/spec/render.sh b/src/spec/render.sh deleted file mode 100755 index 91e486c247..0000000000 --- a/src/spec/render.sh +++ /dev/null @@ -1,31 +0,0 @@ -# command -v ls >&- || {echo >&2 bla; exit 1;} -function check(){ - set -e # needed to exit script from function - hash $1 2>&- || (echo >&2 "$1 is needed but not installed! $2"; exit 1;) - set +e # do not exit shell if some command fails (default) -} -check dot -mode=${1-"png"} -file=${2-"file"} -dst=graph -viewcmd=gpicview - -mkdir -p ${dst} -cp ${file}.dot ${dst} -file=${file##*/} # use basename in case the file was somewhere else -cd ${dst} -trap 'cd ..' EXIT # leave dst again on exit -case "$mode" in - png) dot -Tpng -o${file}.png ${file}.dot; - check ${viewcmd} "Please edit viewcmd accordingly." - pkill ${viewcmd}; - ${viewcmd} ${file}.png & - ;; - pdf) rm -f ${file}.tex; - check dot2tex - dot -Txdot ${file}.dot | dot2tex > ${file}.tex; - check pdflatex - pdflatex ${file}.tex - echo "generated $dst/$file.pdf" - ;; -esac diff --git a/src/spec/specCore.ml b/src/spec/specCore.ml deleted file mode 100644 index 9d0ce35624..0000000000 --- a/src/spec/specCore.ml +++ /dev/null @@ -1,152 +0,0 @@ -(* types used by specParser and functions for handling the constructed types *) - -open Batteries - -exception Endl -exception Eof - -(* type value = String of string | Bool of bool | Int of int | Float of float *) -type lval = Ptr of lval | Var of string | Ident of string -type fcall = {fname: string; args: exp list} -and exp = - Fun of fcall | - Exp_ | - Lval of lval | - Regex of string | - String of string | Bool of bool | Int of int | Float of float | - Binop of string * exp * exp | - Unop of string * exp -type stmt = {lval: lval option; exp: exp} -type def = Node of (string * string) (* node warning *) - | Edge of (string * string list * bool * string * stmt) (* start-node, warning-nodes, forwarding, target-node, constraint *) - -(* let stmts edges = List.map (fun (a,b,c) -> c) edges - let get_fun stmt = match stmt.exp with Fun x -> Some x | _ -> None - let fun_records edges = List.filter_map get_fun (stmts edges) - let fun_names edges = fun_records edges |> List.map (fun x -> x.fname) - let fun_by_fname fname edges = List.filter (fun x -> x.fname=fname) (fun_records edges) *) -let fname_is fname stmt = - match stmt.exp with - | Fun x -> x.fname=fname - | _ -> false - -let is_wildcard stmt = stmt.exp = Exp_ - -let branch_exp stmt = - match stmt.exp with - | Fun { fname="branch"; args=[exp; Bool tv] } -> Some (exp,tv) - | _ -> None - -let is_branch stmt = branch_exp stmt <> None - -let startnode edges = - (* The start node of the first transition is the start node of the automaton. *) - let a,ws,fwd,b,c = List.hd edges in a - -let warning state nodes = - try - Some (snd (List.find (fun x -> fst x = state) nodes)) (* find node for state and return its warning *) - with - | Not_found -> None (* no node for state *) - -let get_lval stmt = - let f = function - | Ptr x -> `Ptr (* TODO recursive *) - | Var s -> `Var - | Ident s -> `Ident - in - Option.map f stmt.lval - -let get_exp = function - | Regex x -> `Regex x - | String x -> `String x - | Bool x -> `Bool x - | Int x -> `Int x - | Float x -> `Float x - | Lval (Var x) -> `Var x - | Lval (Ident x) -> `Ident x - | Fun x -> `Error "Functions aren't allowed to have functions as an argument (put the function as a previous state instead)" - | Exp_ -> `Free - | Unop ("!", Bool x) -> `Bool (not x) - | _ -> `Error "Unsupported operation inside function argument, use a simpler expression instead." - -let get_rval stmt = get_exp stmt.exp - -let get_key_variant stmt = - let rec get_from_exp = function - | Fun f -> get_from_args f.args (* TODO for special we only consider constraints where the root of the exp is Fun (see fname_is) *) - | Lval (Var s) -> `Rval s - | _ -> `None - (* walks over arguments until it finds something or returns `None *) - and get_from_argsi i = function - | [] -> `None - | x::xs -> - match get_from_exp x with - | `Rval s -> `Arg(s, i) - | _ -> get_from_argsi (i+1) xs (* matches `None and `Arg -> `Arg of `Arg not supported *) - and get_from_args args = get_from_argsi 0 args (* maybe better use List.findi *) - in - let rec get_from_lval = function - | Ptr x -> get_from_lval x - | Var s -> Some s - | Ident s -> None - in - match stmt.lval with - | Some lval when Option.is_some (get_from_lval lval) -> `Lval (Option.get (get_from_lval lval)) - | _ -> get_from_exp stmt.exp - -let equal_form lval stmt = - match lval, stmt.lval with - | Some _, Some _ - | None, None -> true - | _ -> false - -(* get function arguments with tags corresponding to the type -> should only be called for functions, returns [] for everything else *) -let get_fun_args stmt = match stmt.exp with - | Fun f -> List.map get_exp f.args - | _ -> [] - -(* functions for output *) -let rec lval_to_string = function - | Ptr x -> "*"^(lval_to_string x) - | Var x -> "$"^x - | Ident x -> x -let rec exp_to_string = function - | Fun x -> x.fname^"("^String.concat ", " (List.map exp_to_string x.args)^")" - | Exp_ -> "_" - | Lval x -> lval_to_string x - | Regex x -> "r\""^x^"\"" - | String x -> "\""^x^"\"" - | Bool x -> string_of_bool x - | Int x -> string_of_int x - | Float x -> string_of_float x - | Binop (op, a, b) -> exp_to_string a ^ " " ^ op ^ " " ^ exp_to_string b - | Unop (op, a) -> op ^ " " ^ exp_to_string a -let stmt_to_string stmt = match stmt.lval, stmt.exp with - | Some lval, exp -> lval_to_string lval^" = "^exp_to_string exp - | None, exp -> exp_to_string exp -let arrow_to_string ws fwd = (String.concat "," ws)^if fwd then ">" else "" -let def_to_string = function - | Node(n, m) -> n^"\t\""^m^"\"" - | Edge(a, ws, fwd, b, s) -> a^" -"^arrow_to_string ws fwd^"> "^b^"\t"^stmt_to_string s - -let to_dot_graph defs = - let no_warnings = true in - let def_to_string = function - | Node(n, m) -> - if no_warnings then "" - else n^"\t[style=filled, fillcolor=orange, label=\""^n^": "^m^"\"];" - | Edge(a, ws, fwd, b, s) -> - let style = if fwd then "style=dotted, " else "" in - let ws = if List.is_empty ws then "" else (String.concat "," ws)^" | " in - a^" -> "^b^"\t["^style^"label=\""^ws^String.escaped (stmt_to_string s)^"\"];" - in - let ends,defs = List.partition (function Edge (a,ws,fwd,b,s) -> b="end" && s.exp=Exp_ | _ -> false) defs in - let endstates = List.filter_map (function Edge (a,ws,fwd,b,s) -> Some a | _ -> None) ends in - (* set the default style for nodes *) - let defaultstyle = "node [shape=box, style=rounded];" in - (* style end nodes and then reset *) - let endstyle = if List.is_empty endstates then "" else "node [peripheries=2]; "^(String.concat " " endstates)^"; node [peripheries=1];" in - let lines = "digraph file {"::defaultstyle::endstyle::(List.map def_to_string defs |> List.filter (fun s -> s<>"")) in - (* List.iter print_endline lines *) - String.concat "\n " lines ^ "\n}" diff --git a/src/spec/specLexer.mll b/src/spec/specLexer.mll deleted file mode 100644 index 64ac69359e..0000000000 --- a/src/spec/specLexer.mll +++ /dev/null @@ -1,67 +0,0 @@ -{ - open SpecParser (* The type token is defined in specParser.mli *) - exception Token of string - let line = ref 1 -} - -let digit = ['0'-'9'] -let alpha = ['a'-'z' 'A'-'Z'] -let nl = '\r'?'\n' (* new line *) -let s = [' ' '\t'] (* whitespace *) -let w = '_' | alpha | digit (* word *) -let endlinecomment = "//" [^'\n']* -let multlinecomment = "/*"([^'*']|('*'+[^'*''/'])|nl)*'*'+'/' -let comments = endlinecomment | multlinecomment -let str = ('\"'(([^'\"']|"\\\"")* as s)'\"') | ('\''(([^'\'']|"\\'")* as s)'\'') - -rule token = parse - | s { token lexbuf } (* skip blanks *) - | comments { token lexbuf } (* skip comments *) - | nl { incr line; EOL } - - (* operators *) - | '(' { LPAREN } - | ')' { RPAREN } - | '[' { LBRACK } - | ']' { RBRACK } - | '{' { LCURL } - | '}' { RCURL } - (*| '.' { DOT } *) - (*| "->" { ARROW } *) - | '+' { PLUS } - | '-' { MINUS } - | '*' { MUL } - | '/' { DIV } - | '%' { MOD } - | '<' { LT } - | '>' { GT } - | "==" { EQEQ } - | "!=" { NE } - | "<=" { LE } - | ">=" { GE } - | "&&" { AND } - | "||" { OR } - | '!' { NOT } - | '=' { EQ } - | ',' { COMMA } - | ';' { SEMICOLON } - - (* literals, identifiers *) - | "true" { BOOL(true) } - | "false" { BOOL(false) } - | "null" { NULL } - | digit+ as x { INT(int_of_string x) } - | str { STRING(s) } - | '_' { UNDERS } (* used for spec, but has to be before Ident! *) - | ('_'|alpha) w* as x { IDENT(x) } - - (* spec *) - | ':' { COLON } - | "$"(w+ as x) { VAR(x) } - | "r" str { REGEX(s) } - | (w+ as n) s+ str - { NODE(n, s) } - | (w+ as a) s* "-" ((w+ ("," w+)*)? as ws) (">"? as fwd) ">" s* (w+ as b) s+ - { EDGE(a, BatString.split_on_string ~by:"," ws, fwd=">", b) } - | eof { EOF } - | _ as x { raise(Token (Char.escaped x^": unknown token in line "^string_of_int !line)) } diff --git a/src/spec/specParser.mly b/src/spec/specParser.mly deleted file mode 100644 index fe8fe90ec8..0000000000 --- a/src/spec/specParser.mly +++ /dev/null @@ -1,116 +0,0 @@ -%{ - (* necessary to open a different compilation unit - because exceptions directly defined here aren't visible outside - (e.g. SpecParser.Eof is raised, but Error: Unbound constructor - if used to catch in a different module) *) - open SpecCore -%} - -%token EOL EOF -/* operators */ -%token LPAREN RPAREN LCURL RCURL LBRACK RBRACK -%token PLUS MINUS MUL DIV MOD -%token LT GT EQEQ NE LE GE AND OR NOT -%token EQ COMMA SEMICOLON -/* literals, identifiers */ -%token BOOL -%token NULL -%token INT -%token STRING -%token IDENT -/* spec */ -%token UNDERS COLON -%token VAR -%token REGEX -%token NODE -%token EDGE - -/* precedence groups from low to high */ -%right EQ -%left OR -%left AND -%left EQEQ NE -%left LT GT LE GE -%left PLUS MINUS -%left MUL DIV MOD -%right NOT UPLUS UMINUS DEREF - -%start file -%type file - -%% - -file: - | def EOL { $1 } - | def EOF { $1 } /* no need for an empty line at the end */ - | EOL { raise Endl } /* empty line */ - | EOF { raise Eof } /* end of file */ -; - -def: - | NODE { Node($1) } - | EDGE stmt { let a, ws, fwd, b = $1 in Edge(a, ws, fwd, b, $2) } -; - -stmt: - | lval EQ expr { {lval = Some $1; exp = $3} } /* TODO expression would be better */ - | expr { {lval = None; exp = $1} } -; - -lval: - | MUL lval %prec DEREF { Ptr $2 } - | IDENT { Ident $1 } /* C identifier, e.g. foo, _foo, _1, but not 1b */ - | VAR { Var $1 } /* spec variable, e.g. $foo, $123, $__ */ -; - -expr: - | LPAREN expr RPAREN { $2 } - | REGEX { Regex $1 } - | STRING { String $1 } - | BOOL { Bool $1 } - | lval { Lval $1 } - | IDENT args { Fun {fname=$1; args=$2} } /* function */ - | UNDERS { Exp_ } - | nexpr { Int $1 } - /* | nexpr LT nexpr { Bool ($1<$3) } - | nexpr GT nexpr { Bool ($1>$3) } - | nexpr EQEQ nexpr { Bool ($1=$3) } - | nexpr NE nexpr { Bool ($1<>$3) } - | nexpr LE nexpr { Bool ($1<=$3) } - | nexpr GE nexpr { Bool ($1>=$3) } */ - | expr OR expr { Binop ("||", $1, $3) } - | expr AND expr { Binop ("&&", $1, $3) } - | expr EQEQ expr { Binop ("==", $1, $3) } - | expr NE expr { Binop ("!=", $1, $3) } - | expr LT expr { Binop ("<", $1, $3) } - | expr GT expr { Binop (">", $1, $3) } - | expr LE expr { Binop ("<=", $1, $3) } - | expr GE expr { Binop (">=", $1, $3) } - | expr PLUS expr { Binop ("+", $1, $3) } - | expr MINUS expr { Binop ("-", $1, $3) } - | expr MUL expr { Binop ("*", $1, $3) } - | expr DIV expr { Binop ("/", $1, $3) } - | expr MOD expr { Binop ("%", $1, $3) } - | NOT expr { Unop ("!", $2) } -; - -nexpr: - | INT { $1 } - | MINUS nexpr %prec UMINUS { - $2 } - | PLUS nexpr %prec UPLUS { $2 } - /* | LPAREN nexpr RPAREN { $2 } - | nexpr PLUS nexpr { $1 + $3 } - | nexpr MINUS nexpr { $1 - $3 } - | nexpr MUL nexpr { $1 * $3 } - | nexpr DIV nexpr { $1 / $3 } */ -; - -args: - | LPAREN RPAREN { [] } - | LPAREN expr_list RPAREN { $2 } -; - -expr_list: - | expr { [$1] } - | expr COMMA expr_list { $1 :: $3 } -; diff --git a/src/spec/specUtil.ml b/src/spec/specUtil.ml deleted file mode 100644 index 55e0b51135..0000000000 --- a/src/spec/specUtil.ml +++ /dev/null @@ -1,52 +0,0 @@ -(* functions for driving specParser *) - -open Batteries - -(* config *) -let save_dot = true - -let line = ref 1 -exception Parse_error of string - -let parse ?repl:(repl=false) ?print:(print=false) ?dot:(dot=false) cin = - let lexbuf = Lexing.from_channel cin in - let defs = ref [] in - (* Printf.printf "\nrepl: %B, print: %B, dot: %B, save_dot: %B\n" repl print dot save_dot; *) - try - while true do (* loop over all lines *) - try - let result = SpecParser.file SpecLexer.token lexbuf in - defs := !defs@[result]; - incr line; - if print then (print_endline (SpecCore.def_to_string result); flush stdout) - with - (* just an empty line -> don't print *) - | SpecCore.Endl -> incr line - (* somehow gets raised in some cases instead of SpecCore.Eof *) - | BatInnerIO.Input_closed -> raise SpecCore.Eof - (* catch and print in repl-mode *) - | e when repl -> print_endline (Printexc.to_string e) - done; - ([], []) (* never happens, but ocaml needs it for type *) - with - (* done *) - | SpecCore.Eof -> - let nodes = List.filter_map (function SpecCore.Node x -> Some x | _ -> None) !defs in - let edges = List.filter_map (function SpecCore.Edge x -> Some x | _ -> None) !defs in - if print then Printf.printf "\n#Definitions: %i, #Nodes: %i, #Edges: %i\n" - (List.length !defs) (List.length nodes) (List.length edges); - if save_dot && not dot then ( - let dotgraph = SpecCore.to_dot_graph !defs in - output_file ~filename:"result/graph.dot" ~text:dotgraph; - print_endline ("saved graph as "^Sys.getcwd ()^"/result/graph.dot"); - ); - if dot then ( - print_endline (SpecCore.to_dot_graph !defs) - ); - (nodes, edges) - (* stop on parsing error if not in REPL and include line number *) - | e -> raise (Parse_error ("Line "^string_of_int !line^": "^Printexc.to_string e)) - -let parseFile filename = parse (open_in filename) - -(* print ~first:"[" ~sep:", " ~last:"]" print_any stdout @@ 5--10 *) diff --git a/src/util/analysisStateUtil.ml b/src/util/analysisStateUtil.ml new file mode 100644 index 0000000000..a34be33f18 --- /dev/null +++ b/src/util/analysisStateUtil.ml @@ -0,0 +1,13 @@ +type mem_safety_violation = + | InvalidFree + | InvalidDeref + | InvalidMemTrack + | InvalidMemcleanup + +let set_mem_safety_flag violation_type = + if !AnalysisState.postsolving then + match violation_type with + | InvalidFree -> AnalysisState.svcomp_may_invalid_free := true + | InvalidDeref -> AnalysisState.svcomp_may_invalid_deref := true + | InvalidMemTrack -> AnalysisState.svcomp_may_invalid_memtrack := true + | InvalidMemcleanup -> AnalysisState.svcomp_may_invalid_memcleanup := true \ No newline at end of file diff --git a/src/util/cilCfg.ml b/src/util/cilCfg.ml index 2c8ec646c3..923cf7600b 100644 --- a/src/util/cilCfg.ml +++ b/src/util/cilCfg.ml @@ -42,6 +42,7 @@ let loopCount file = let createCFG (fileAST: file) = + Cilfacade.do_preprocess fileAST; (* The analyzer keeps values only for blocks. So if you want a value for every program point, each instruction *) (* needs to be in its own block. end_basic_blocks does that. *) (* After adding support for VLAs, there are new VarDecl instructions at the point where a variable was declared and *) @@ -49,6 +50,7 @@ let createCFG (fileAST: file) = (* BB causes the output CIL file to no longer compile. *) (* Since we want the output of justcil to compile, we do not run allBB visitor if justcil is enable, regardless of *) (* exp.basic-blocks. This does not matter, as we will not run any analysis anyway, when justcil is enabled. *) + (* the preprocessing must be done here, to add the changes of CIL to the CFG*) if not (get_bool "exp.basic-blocks") && not (get_bool "justcil") then end_basic_blocks fileAST; (* We used to renumber vids but CIL already generates them fresh, so no need. @@ -66,6 +68,4 @@ let createCFG (fileAST: file) = computeCFGInfo fd true | _ -> () ); - if get_bool "dbg.run_cil_check" then assert (Check.checkFile [] fileAST); - - Cilfacade.do_preprocess fileAST + if get_bool "dbg.run_cil_check" then assert (Check.checkFile [] fileAST); \ No newline at end of file diff --git a/src/domains/accessKind.ml b/src/util/library/accessKind.ml similarity index 81% rename from src/domains/accessKind.ml rename to src/util/library/accessKind.ml index 576581af02..b36e8f3eca 100644 --- a/src/domains/accessKind.ml +++ b/src/util/library/accessKind.ml @@ -1,9 +1,10 @@ (** Kinds of memory accesses. *) type t = - | Write (** argument may be read or written to *) + | Write (** argument may be written to *) | Read (** argument may be read *) | Free (** argument may be freed *) + | Call (** argument may be called *) | Spawn (** argument may be spawned *) [@@deriving eq, ord, hash] (** Specifies what is known about an argument. *) @@ -12,6 +13,7 @@ let show: t -> string = function | Write -> "write" | Read -> "read" | Free -> "free" + | Call -> "call" | Spawn -> "spawn" include Printable.SimpleShow ( diff --git a/src/util/library/dune b/src/util/library/dune new file mode 100644 index 0000000000..075c01c35d --- /dev/null +++ b/src/util/library/dune @@ -0,0 +1,18 @@ +(include_subdirs no) + +(library + (name goblint_library) + (public_name goblint.library) + (wrapped false) ; TODO: wrap + (libraries + batteries.unthreaded + goblint_common + goblint_domain + goblint_config + goblint-cil) + (preprocess + (pps + ppx_deriving.std + ppx_deriving_hash))) + +(documentation) diff --git a/src/util/library/library.mld b/src/util/library/library.mld new file mode 100644 index 0000000000..f55db3f2ff --- /dev/null +++ b/src/util/library/library.mld @@ -0,0 +1,14 @@ +{0 Library goblint.library} +This library is unwrapped and provides the following top-level modules. +For better context, see {!Goblint_lib} which also documents these modules. + + +{1 Utilities} + +{2 Library specification} +{!modules: +AccessKind +LibraryDesc +LibraryDsl +LibraryFunctions +} diff --git a/src/util/library/libraryDesc.ml b/src/util/library/libraryDesc.ml new file mode 100644 index 0000000000..4997b306a9 --- /dev/null +++ b/src/util/library/libraryDesc.ml @@ -0,0 +1,190 @@ +(** Library function descriptor (specification). *) + +module Cil = GoblintCil +open Cil +(** Pointer argument access specification. *) +module Access = +struct + type t = { + kind: AccessKind.t; (** Kind of access. *) + deep: bool; (** Depth of access + - Shallow only accesses directly pointed values (may point to). + - Deep additionally follows all pointers in values (reachable). Rarely needed. *) + } +end + +type math = + | Nan of (CilType.Fkind.t * Basetype.CilExp.t) + | Inf of CilType.Fkind.t + | Isfinite of Basetype.CilExp.t + | Isinf of Basetype.CilExp.t + | Isnan of Basetype.CilExp.t + | Isnormal of Basetype.CilExp.t + | Signbit of Basetype.CilExp.t + | Isgreater of (Basetype.CilExp.t * Basetype.CilExp.t) + | Isgreaterequal of (Basetype.CilExp.t * Basetype.CilExp.t) + | Isless of (Basetype.CilExp.t * Basetype.CilExp.t) + | Islessequal of (Basetype.CilExp.t * Basetype.CilExp.t) + | Islessgreater of (Basetype.CilExp.t * Basetype.CilExp.t) + | Isunordered of (Basetype.CilExp.t * Basetype.CilExp.t) + | Abs of (CilType.Ikind.t * Basetype.CilExp.t) + | Ceil of (CilType.Fkind.t * Basetype.CilExp.t) + | Floor of (CilType.Fkind.t * Basetype.CilExp.t) + | Fabs of (CilType.Fkind.t * Basetype.CilExp.t) + | Fmax of (CilType.Fkind.t * Basetype.CilExp.t * Basetype.CilExp.t) + | Fmin of (CilType.Fkind.t * Basetype.CilExp.t * Basetype.CilExp.t) + | Acos of (CilType.Fkind.t * Basetype.CilExp.t) + | Asin of (CilType.Fkind.t * Basetype.CilExp.t) + | Atan of (CilType.Fkind.t * Basetype.CilExp.t) + | Atan2 of (CilType.Fkind.t * Basetype.CilExp.t * Basetype.CilExp.t) + | Cos of (CilType.Fkind.t * Basetype.CilExp.t) + | Sin of (CilType.Fkind.t * Basetype.CilExp.t) + | Tan of (CilType.Fkind.t * Basetype.CilExp.t) + | Sqrt of (CilType.Fkind.t * Basetype.CilExp.t) [@@deriving eq, ord, hash] + +(** Type of special function, or {!Unknown}. *) +(* Use inline record if not single {!Cil.exp} argument. *) +type special = + | Alloca of Cil.exp + | Malloc of Cil.exp + | Calloc of { count: Cil.exp; size: Cil.exp; } + | Realloc of { ptr: Cil.exp; size: Cil.exp; } + | Free of Cil.exp + | Assert of { exp: Cil.exp; check: bool; refine: bool; } + | Lock of { lock: Cil.exp; try_: bool; write: bool; return_on_success: bool; } + | Unlock of Cil.exp + | ThreadCreate of { thread: Cil.exp; start_routine: Cil.exp; arg: Cil.exp; } + | ThreadJoin of { thread: Cil.exp; ret_var: Cil.exp; } + | ThreadExit of { ret_val: Cil.exp; } + | Signal of Cil.exp + | Broadcast of Cil.exp + | MutexAttrSetType of { attr:Cil.exp; typ: Cil.exp; } + | MutexInit of { mutex:Cil.exp; attr: Cil.exp; } + (* All Sem specials are not used yet. *) + | SemInit of { sem: Cil.exp; pshared: Cil.exp; value: Cil.exp; } + | SemWait of { sem: Cil.exp; try_:bool; timeout: Cil.exp option;} + | SemPost of Cil.exp + | SemDestroy of Cil.exp + | Wait of { cond: Cil.exp; mutex: Cil.exp; } + | TimedWait of { cond: Cil.exp; mutex: Cil.exp; abstime: Cil.exp; (** Unused *) } + | Math of { fun_args: math; } + | Memset of { dest: Cil.exp; ch: Cil.exp; count: Cil.exp; } + | Bzero of { dest: Cil.exp; count: Cil.exp; } + | Memcpy of { dest: Cil.exp; src: Cil.exp; n: Cil.exp; } + | Strcpy of { dest: Cil.exp; src: Cil.exp; n: Cil.exp option; } + | Strcat of { dest: Cil.exp; src: Cil.exp; n: Cil.exp option; } + | Strlen of Cil.exp + | Strstr of { haystack: Cil.exp; needle: Cil.exp; } + | Strcmp of { s1: Cil.exp; s2: Cil.exp; n: Cil.exp option; } + | Abort + | Identity of Cil.exp (** Identity function. Some compiler optimization annotation functions map to this. *) + | Setjmp of { env: Cil.exp; } + | Longjmp of { env: Cil.exp; value: Cil.exp; } + | Bounded of { exp: Cil.exp} (** Used to check for bounds for termination analysis. *) + | Rand + | Unknown (** Anything not belonging to other types. *) (* TODO: rename to Other? *) + + +(** Pointer arguments access specification. *) +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) |? []) + + let find_kind (accs: t): AccessKind.t -> Cil.exp list -> Cil.exp list = fun kind args -> + let f a = find accs a args in + f { kind; deep = true; } @ f { kind; deep = false; } + + let iter (accs: t) (f: Access.t -> Cil.exp -> unit) args: unit = + accs args + |> List.iter (fun (acc, exps) -> + List.iter (fun exp -> f acc exp) exps + ) + + let fold (accs: t) (f: Access.t -> Cil.exp -> 'a -> 'a) args (a: 'a): 'a = + accs args + |> List.fold_left (fun a (acc, exps) -> + List.fold_left (fun a exp -> f acc exp a) a exps + ) a +end + +(** Function attribute. *) +type attr = + | ThreadUnsafe (** Function is not thread-safe to call, e.g. due to its own internal (global) state. + @see for list of thread-unsafe functions under POSIX. + @see for Goblint issue about the (future) use of this attribute. *) + | InvalidateGlobals (** Function invalidates all globals when called. *) (* TODO: AccessGlobals of Access.t list? *) + +(** Library function descriptor. *) +type t = { + special: Cil.exp list -> special; (** Conversion to {!type-special} using arguments. *) + accs: Accesses.t; (** Pointer arguments access specification. *) + 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] + + let name () = "MathPrintable" + + let pretty () = function + | Nan (fk, exp) -> Pretty.dprintf "(%a )nan(%a)" d_fkind fk d_exp exp + | Inf fk -> Pretty.dprintf "(%a )inf()" d_fkind fk + | Isfinite exp -> Pretty.dprintf "isFinite(%a)" d_exp exp + | Isinf exp -> Pretty.dprintf "isInf(%a)" d_exp exp + | Isnan exp -> Pretty.dprintf "isNan(%a)" d_exp exp + | Isnormal exp -> Pretty.dprintf "isNormal(%a)" d_exp exp + | Signbit exp -> Pretty.dprintf "signbit(%a)" d_exp exp + | Isgreater (exp1, exp2) -> Pretty.dprintf "isGreater(%a, %a)" d_exp exp1 d_exp exp2 + | Isgreaterequal (exp1, exp2) -> Pretty.dprintf "isGreaterEqual(%a, %a)" d_exp exp1 d_exp exp2 + | Isless (exp1, exp2) -> Pretty.dprintf "isLess(%a, %a)" d_exp exp1 d_exp exp2 + | Islessequal (exp1, exp2) -> Pretty.dprintf "isLessEqual(%a, %a)" d_exp exp1 d_exp exp2 + | Islessgreater (exp1, exp2) -> Pretty.dprintf "isLessGreater(%a, %a)" d_exp exp1 d_exp exp2 + | Isunordered (exp1, exp2) -> Pretty.dprintf "isUnordered(%a, %a)" d_exp exp1 d_exp exp2 + | Abs (ik, exp) -> Pretty.dprintf "(%a )abs(%a)" d_ikind ik d_exp exp + | Ceil (fk, exp) -> Pretty.dprintf "(%a )ceil(%a)" d_fkind fk d_exp exp + | Floor (fk, exp) -> Pretty.dprintf "(%a )floor(%a)" d_fkind fk d_exp exp + | Fabs (fk, exp) -> Pretty.dprintf "(%a )fabs(%a)" d_fkind fk d_exp exp + | Fmax (fk, exp1, exp2) -> Pretty.dprintf "(%a )fmax(%a, %a)" d_fkind fk d_exp exp1 d_exp exp2 + | Fmin (fk, exp1, exp2) -> Pretty.dprintf "(%a )fmin(%a, %a)" d_fkind fk d_exp exp1 d_exp exp2 + | Acos (fk, exp) -> Pretty.dprintf "(%a )acos(%a)" d_fkind fk d_exp exp + | Asin (fk, exp) -> Pretty.dprintf "(%a )asin(%a)" d_fkind fk d_exp exp + | Atan (fk, exp) -> Pretty.dprintf "(%a )atan(%a)" d_fkind fk d_exp exp + | Atan2 (fk, exp1, exp2) -> Pretty.dprintf "(%a )atan2(%a, %a)" d_fkind fk d_exp exp1 d_exp exp2 + | Cos (fk, exp) -> Pretty.dprintf "(%a )cos(%a)" d_fkind fk d_exp exp + | Sin (fk, exp) -> Pretty.dprintf "(%a )sin(%a)" d_fkind fk d_exp exp + | Tan (fk, exp) -> Pretty.dprintf "(%a )tan(%a)" d_fkind fk d_exp exp + | Sqrt (fk, exp) -> Pretty.dprintf "(%a )sqrt(%a)" d_fkind fk d_exp exp + + include Printable.SimplePretty ( + struct + type nonrec t = t + let pretty = pretty + end + ) +end + +module MathLifted = Lattice.Flat (MathPrintable) (struct + let top_name = "Unknown or no math desc" + let bot_name = "Nonexistent math desc" + end) diff --git a/src/analyses/libraryDsl.ml b/src/util/library/libraryDsl.ml similarity index 100% rename from src/analyses/libraryDsl.ml rename to src/util/library/libraryDsl.ml diff --git a/src/analyses/libraryDsl.mli b/src/util/library/libraryDsl.mli similarity index 100% rename from src/analyses/libraryDsl.mli rename to src/util/library/libraryDsl.mli diff --git a/src/util/library/libraryFunctions.ml b/src/util/library/libraryFunctions.ml new file mode 100644 index 0000000000..2c65f7ae61 --- /dev/null +++ b/src/util/library/libraryFunctions.ml @@ -0,0 +1,1385 @@ +(** Tools for dealing with library functions. *) + +open Batteries +open GoblintCil +open GobConfig + +module M = Messages + +(** C standard library functions. + These are specified by the C standard. *) +let c_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ + ("memset", special [__ "dest" [w]; __ "ch" []; __ "count" []] @@ fun dest ch count -> Memset { dest; ch; count; }); + ("__builtin_memset", special [__ "dest" [w]; __ "ch" []; __ "count" []] @@ fun dest ch count -> Memset { dest; ch; count; }); + ("__builtin___memset_chk", special [__ "dest" [w]; __ "ch" []; __ "count" []; drop "os" []] @@ fun dest ch count -> Memset { dest; ch; count; }); + ("memcpy", special [__ "dest" [w]; __ "src" [r]; __ "n" []] @@ fun dest src n -> Memcpy { dest; src; n; }); + ("__builtin_memcpy", special [__ "dest" [w]; __ "src" [r]; __ "n" []] @@ fun dest src n -> Memcpy { dest; src; n; }); + ("__builtin___memcpy_chk", special [__ "dest" [w]; __ "src" [r]; __ "n" []; drop "os" []] @@ fun dest src n -> Memcpy { dest; src; n; }); + ("memccpy", special [__ "dest" [w]; __ "src" [r]; drop "c" []; __ "n" []] @@ fun dest src n -> Memcpy {dest; src; n; }); (* C23 *) (* TODO: use c *) + ("memmove", special [__ "dest" [w]; __ "src" [r]; __ "count" []] @@ fun dest src count -> Memcpy { dest; src; n = count; }); + ("__builtin_memmove", special [__ "dest" [w]; __ "src" [r]; __ "count" []] @@ fun dest src count -> Memcpy { dest; src; n = count; }); + ("__builtin___memmove_chk", special [__ "dest" [w]; __ "src" [r]; __ "count" []; drop "os" []] @@ fun dest src count -> Memcpy { dest; src; n = count; }); + ("strcpy", special [__ "dest" [w]; __ "src" [r]] @@ fun dest src -> Strcpy { dest; src; n = None; }); + ("__builtin_strcpy", special [__ "dest" [w]; __ "src" [r]] @@ fun dest src -> Strcpy { dest; src; n = None; }); + ("__builtin___strcpy_chk", special [__ "dest" [w]; __ "src" [r]; drop "os" []] @@ fun dest src -> Strcpy { dest; src; n = None; }); + ("strncpy", special [__ "dest" [w]; __ "src" [r]; __ "n" []] @@ fun dest src n -> Strcpy { dest; src; n = Some n; }); + ("__builtin_strncpy", special [__ "dest" [w]; __ "src" [r]; __ "n" []] @@ fun dest src n -> Strcpy { dest; src; n = Some n; }); + ("__builtin___strncpy_chk", special [__ "dest" [w]; __ "src" [r]; __ "n" []; drop "os" []] @@ fun dest src n -> Strcpy { dest; src; n = Some n; }); + ("strcat", special [__ "dest" [r; w]; __ "src" [r]] @@ fun dest src -> Strcat { dest; src; n = None; }); + ("__builtin_strcat", special [__ "dest" [r; w]; __ "src" [r]] @@ fun dest src -> Strcat { dest; src; n = None; }); + ("__builtin___strcat_chk", special [__ "dest" [r; w]; __ "src" [r]; drop "os" []] @@ fun dest src -> Strcat { dest; src; n = None; }); + ("strncat", special [__ "dest" [r; w]; __ "src" [r]; __ "n" []] @@ fun dest src n -> Strcat { dest; src; n = Some n; }); + ("__builtin_strncat", special [__ "dest" [r; w]; __ "src" [r]; __ "n" []] @@ fun dest src n -> Strcat { dest; src; n = Some n; }); + ("__builtin___strncat_chk", special [__ "dest" [r; w]; __ "src" [r]; __ "n" []; drop "os" []] @@ fun dest src n -> Strcat { dest; src; n = Some n; }); + ("memcmp", unknown [drop "s1" [r]; drop "s2" [r]; drop "n" []]); + ("__builtin_memcmp", unknown [drop "s1" [r]; drop "s2" [r]; drop "n" []]); + ("memchr", unknown [drop "s" [r]; drop "c" []; drop "n" []]); + ("asctime", unknown ~attrs:[ThreadUnsafe] [drop "time_ptr" [r_deep]]); + ("fclose", unknown [drop "stream" [r_deep; w_deep; f_deep]]); + ("feof", unknown [drop "stream" [r_deep; w_deep]]); + ("ferror", unknown [drop "stream" [r_deep; w_deep]]); + ("fflush", unknown [drop "stream" [r_deep; w_deep]]); + ("fgetc", unknown [drop "stream" [r_deep; w_deep]]); + ("getc", unknown [drop "stream" [r_deep; w_deep]]); + ("fgets", unknown [drop "str" [w]; drop "count" []; drop "stream" [r_deep; w_deep]]); + ("fopen", unknown [drop "pathname" [r]; drop "mode" [r]]); + ("freopen", unknown [drop "pathname" [r]; drop "mode" [r]; drop "stream" [r_deep; w_deep]]); + ("printf", unknown (drop "format" [r] :: VarArgs (drop' [r]))); + ("fprintf", unknown (drop "stream" [r_deep; w_deep] :: drop "format" [r] :: VarArgs (drop' [r]))); + ("sprintf", unknown (drop "buffer" [w] :: drop "format" [r] :: VarArgs (drop' [r]))); + ("snprintf", unknown (drop "buffer" [w] :: drop "bufsz" [] :: drop "format" [r] :: VarArgs (drop' [r]))); + ("fputc", unknown [drop "ch" []; drop "stream" [r_deep; w_deep]]); + ("putc", unknown [drop "ch" []; drop "stream" [r_deep; w_deep]]); + ("fputs", unknown [drop "str" [r]; drop "stream" [r_deep; w_deep]]); + ("fread", unknown [drop "buffer" [w]; drop "size" []; drop "count" []; drop "stream" [r_deep; w_deep]]); + ("fseek", unknown [drop "stream" [r_deep; w_deep]; drop "offset" []; drop "origin" []]); + ("ftell", unknown [drop "stream" [r_deep]]); + ("fwrite", unknown [drop "buffer" [r]; drop "size" []; drop "count" []; drop "stream" [r_deep; w_deep]]); + ("rewind", unknown [drop "stream" [r_deep; w_deep]]); + ("setvbuf", unknown [drop "stream" [r_deep; w_deep]; drop "buffer" [r; w]; drop "mode" []; drop "size" []]); + (* TODO: if this is used to set an input buffer, the buffer (second argument) would need to remain TOP, *) + (* as any future write (or flush) of the stream could result in a write to the buffer *) + ("gmtime", unknown ~attrs:[ThreadUnsafe] [drop "timer" [r_deep]]); + ("localeconv", unknown ~attrs:[ThreadUnsafe] []); + ("localtime", unknown ~attrs:[ThreadUnsafe] [drop "time" [r]]); + ("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; }); + ("strtok", unknown ~attrs:[ThreadUnsafe] [drop "str" [r; w]; drop "delim" [r]]); + ("__builtin_strcmp", special [__ "s1" [r]; __ "s2" [r]] @@ fun s1 s2 -> Strcmp { s1; s2; n = None; }); + ("strncmp", special [__ "s1" [r]; __ "s2" [r]; __ "n" []] @@ fun s1 s2 n -> Strcmp { s1; s2; n = Some n; }); + ("strchr", unknown [drop "s" [r]; drop "c" []]); + ("__builtin_strchr", unknown [drop "s" [r]; drop "c" []]); + ("strrchr", unknown [drop "s" [r]; drop "c" []]); + ("malloc", special [__ "size" []] @@ fun size -> Malloc size); + ("calloc", special [__ "n" []; __ "size" []] @@ fun n size -> Calloc {count = n; size}); + ("realloc", special [__ "ptr" [r; f]; __ "size" []] @@ fun ptr size -> Realloc { ptr; size }); + ("free", special [__ "ptr" [f]] @@ fun ptr -> Free ptr); + ("abort", special [] Abort); + ("exit", special [drop "exit_code" []] Abort); + ("quick_exit", special [drop "exit_code" []] Abort); + ("ungetc", unknown [drop "c" []; drop "stream" [r; w]]); + ("scanf", unknown ((drop "format" [r]) :: (VarArgs (drop' [w])))); + ("fscanf", unknown ((drop "stream" [r_deep; w_deep]) :: (drop "format" [r]) :: (VarArgs (drop' [w])))); + ("sscanf", unknown ((drop "buffer" [r]) :: (drop "format" [r]) :: (VarArgs (drop' [w])))); + ("__freading", unknown [drop "stream" [r]]); + ("mbsinit", unknown [drop "ps" [r]]); + ("mbrtowc", unknown [drop "pwc" [w]; drop "s" [r]; drop "n" []; drop "ps" [r; w]]); + ("iswspace", unknown [drop "wc" []]); + ("iswalnum", unknown [drop "wc" []]); + ("iswprint", unknown [drop "wc" []]); + ("iswxdigit", unknown [drop "ch" []]); + ("rename" , unknown [drop "oldpath" [r]; drop "newpath" [r];]); + ("perror", unknown [drop "s" [r]]); + ("getchar", unknown []); + ("putchar", unknown [drop "ch" []]); + ("puts", unknown [drop "s" [r]]); + ("srand", unknown [drop "seed" []]); + ("rand", special ~attrs:[ThreadUnsafe] [] Rand); + ("strerror", unknown ~attrs:[ThreadUnsafe] [drop "errnum" []]); + ("strspn", unknown [drop "s" [r]; drop "accept" [r]]); + ("strcspn", unknown [drop "s" [r]; drop "accept" [r]]); + ("strftime", unknown [drop "str" [w]; drop "count" []; drop "format" [r]; drop "tp" [r]]); + ("strtod", unknown [drop "nptr" [r]; drop "endptr" [w]]); + ("strtol", unknown [drop "nptr" [r]; drop "endptr" [w]; drop "base" []]); + ("__strtol_internal", unknown [drop "nptr" [r]; drop "endptr" [w]; drop "base" []; drop "group" []]); + ("strtoll", unknown [drop "nptr" [r]; drop "endptr" [w]; drop "base" []]); + ("strtoul", unknown [drop "nptr" [r]; drop "endptr" [w]; drop "base" []]); + ("strtoull", unknown [drop "nptr" [r]; drop "endptr" [w]; drop "base" []]); + ("tolower", 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? *) + ("vfprintf", unknown [drop "stream" [r_deep; w_deep]; drop "format" [r]; drop "vlist" [r_deep]]); (* TODO: what to do with a va_list type? is r_deep correct? *) + ("vsprintf", unknown [drop "buffer" [w]; drop "format" [r]; drop "vlist" [r_deep]]); (* TODO: what to do with a va_list type? is r_deep correct? *) + ("asprintf", unknown (drop "strp" [w] :: drop "format" [r] :: VarArgs (drop' [r_deep]))); (* TODO: glibc section? *) + ("vasprintf", unknown [drop "strp" [w]; drop "format" [r]; drop "ap" [r_deep]]); (* TODO: what to do with a va_list type? is r_deep correct? *) + ("vsnprintf", unknown [drop "str" [w]; drop "size" []; drop "format" [r]; drop "ap" [r_deep]]); (* TODO: what to do with a va_list type? is r_deep correct? *) + ("mktime", unknown [drop "tm" [r;w]]); + ("ctime", unknown ~attrs:[ThreadUnsafe] [drop "rm" [r]]); + ("clearerr", unknown [drop "stream" [w]]); (* TODO: why only w? *) + ("setbuf", unknown [drop "stream" [w]; drop "buf" [w]]); + ("wprintf", unknown (drop "fmt" [r] :: VarArgs (drop' [r]))); + ("fwprintf", unknown (drop "stream" [r_deep; w_deep] :: drop "fmt" [r] :: VarArgs (drop' [r]))); + ("swprintf", unknown (drop "wcs" [w] :: drop "maxlen" [] :: drop "fmt" [r] :: VarArgs (drop' [r]))); + ("assert", special [__ "exp" []] @@ fun exp -> Assert { exp; check = true; refine = get_bool "sem.assert.refine" }); (* only used if assert is used without include, e.g. in transformed files *) + ("difftime", unknown [drop "time1" []; drop "time2" []]); + ("system", unknown ~attrs:[ThreadUnsafe] [drop "command" [r]]); + ("wcscat", unknown [drop "dest" [r; w]; drop "src" [r]]); + ("wctomb", unknown ~attrs:[ThreadUnsafe] [drop "s" [w]; drop "wc" []]); + ("wcrtomb", unknown ~attrs:[ThreadUnsafe] [drop "s" [w]; drop "wc" []; drop "ps" [r_deep; w_deep]]); + ("wcstombs", unknown ~attrs:[ThreadUnsafe] [drop "dst" [w]; drop "src" [r]; drop "size" []]); + ("wcsrtombs", unknown ~attrs:[ThreadUnsafe] [drop "dst" [w]; drop "src" [r_deep; w]; drop "size" []; drop "ps" [r_deep; w_deep]]); + ("mbstowcs", unknown [drop "dest" [w]; drop "src" [r]; drop "n" []]); + ("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" []]); + ("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]]); + ("atoi", unknown [drop "nptr" [r]]); + ("atol", unknown [drop "nptr" [r]]); + ("atoll", unknown [drop "nptr" [r]]); + ("setlocale", unknown [drop "category" []; drop "locale" [r]]); + ("clock", unknown []); + ("atomic_flag_clear", unknown [drop "obj" [w]]); + ("atomic_flag_clear_explicit", unknown [drop "obj" [w]; drop "order" []]); + ("atomic_flag_test_and_set", unknown [drop "obj" [r; w]]); + ("atomic_flag_test_and_set_explicit", unknown [drop "obj" [r; w]; drop "order" []]); + ("atomic_load", unknown [drop "obj" [r]]); + ("atomic_store", unknown [drop "obj" [w]; drop "desired" []]); + ("_Exit", special [drop "status" []] @@ Abort); + ("strcoll", unknown [drop "lhs" [r]; drop "rhs" [r]]); + ("wscanf", unknown (drop "fmt" [r] :: VarArgs (drop' [w]))); + ("fwscanf", unknown (drop "stream" [r_deep; w_deep] :: drop "fmt" [r] :: VarArgs (drop' [w]))); + ("swscanf", unknown (drop "buffer" [r] :: drop "fmt" [r] :: VarArgs (drop' [w]))); + ] + +(** C POSIX library functions. + These are {e not} specified by the C standard, but available on POSIX systems. *) +let posix_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ + ("bzero", special [__ "dest" [w]; __ "count" []] @@ fun dest count -> Bzero { dest; count; }); + ("__builtin_bzero", special [__ "dest" [w]; __ "count" []] @@ fun dest count -> Bzero { dest; count; }); + ("explicit_bzero", special [__ "dest" [w]; __ "count" []] @@ fun dest count -> Bzero { dest; count; }); + ("__explicit_bzero_chk", special [__ "dest" [w]; __ "count" []; drop "os" []] @@ fun dest count -> Bzero { dest; count; }); + ("catgets", unknown ~attrs:[ThreadUnsafe] [drop "catalog" [r_deep]; drop "set_number" []; drop "message_number" []; drop "message" [r]]); + ("crypt", unknown ~attrs:[ThreadUnsafe] [drop "key" [r]; drop "salt" [r]]); + ("ctermid", unknown ~attrs:[ThreadUnsafe] [drop "s" [w]]); + ("dbm_clearerr", unknown ~attrs:[ThreadUnsafe] [drop "db" [r_deep; w_deep]]); + ("dbm_close", unknown ~attrs:[ThreadUnsafe] [drop "db" [r_deep; w_deep; f_deep]]); + ("dbm_delete", unknown ~attrs:[ThreadUnsafe] [drop "db" [r_deep; w_deep]; drop "key" []]); + ("dbm_error", unknown ~attrs:[ThreadUnsafe] [drop "db" [r_deep]]); + ("dbm_fetch", unknown ~attrs:[ThreadUnsafe] [drop "db" [r_deep]; drop "key" []]); + ("dbm_firstkey", unknown ~attrs:[ThreadUnsafe] [drop "db" [r_deep]]); + ("dbm_nextkey", unknown ~attrs:[ThreadUnsafe] [drop "db" [r_deep]]); + ("dbm_open", unknown ~attrs:[ThreadUnsafe] [drop "file" [r; w]; drop "open_flags" []; drop "file_mode" []]); + ("dbm_store", unknown ~attrs:[ThreadUnsafe] [drop "db" [r_deep; w_deep]; drop "key" []; drop "content" []; drop "store_mode" []]); + ("drand48", unknown ~attrs:[ThreadUnsafe] []); + ("encrypt", unknown ~attrs:[ThreadUnsafe] [drop "block" [r; w]; drop "edflag" []]); + ("setkey", unknown ~attrs:[ThreadUnsafe] [drop "key" [r]]); + ("endgrent", unknown ~attrs:[ThreadUnsafe] []); + ("endpwent", unknown ~attrs:[ThreadUnsafe] []); + ("fcvt", unknown ~attrs:[ThreadUnsafe] [drop "number" []; drop "ndigits" []; drop "decpt" [w]; drop "sign" [w]]); + ("ecvt", unknown ~attrs:[ThreadUnsafe] [drop "number" []; drop "ndigits" []; drop "decpt" [w]; drop "sign" [w]]); + ("gcvt", unknown ~attrs:[ThreadUnsafe] [drop "number" []; drop "ndigit" []; drop "buf" [w]]); + ("getdate", unknown ~attrs:[ThreadUnsafe] [drop "string" [r]]); + ("getenv", unknown ~attrs:[ThreadUnsafe] [drop "name" [r]]); + ("getgrent", unknown ~attrs:[ThreadUnsafe] []); + ("getgrgid", unknown ~attrs:[ThreadUnsafe] [drop "gid" []]); + ("getgrnam", unknown ~attrs:[ThreadUnsafe] [drop "name" [r]]); + ("getlogin", unknown ~attrs:[ThreadUnsafe] []); + ("getnetbyaddr", unknown ~attrs:[ThreadUnsafe] [drop "net" []; drop "type" []]); + ("getnetbyname", unknown ~attrs:[ThreadUnsafe] [drop "name" [r]]); + ("getnetent", unknown ~attrs:[ThreadUnsafe] []); + ("getprotobyname", unknown ~attrs:[ThreadUnsafe] [drop "name" [r]]); + ("getprotobynumber", unknown ~attrs:[ThreadUnsafe] [drop "proto" []]); + ("getprotoent", unknown ~attrs:[ThreadUnsafe] []); + ("getpwent", unknown ~attrs:[ThreadUnsafe] []); + ("getpwnam", unknown ~attrs:[ThreadUnsafe] [drop "name" [r]]); + ("getpwuid", unknown ~attrs:[ThreadUnsafe] [drop "uid" []]); + ("getservbyname", unknown ~attrs:[ThreadUnsafe] [drop "name" [r]; drop "proto" [r]]); + ("getservbyport", unknown ~attrs:[ThreadUnsafe] [drop "port" []; drop "proto" [r]]); + ("getservent", unknown ~attrs:[ThreadUnsafe] []); + ("getutxent", unknown ~attrs:[ThreadUnsafe] []); + ("getutxid", unknown ~attrs:[ThreadUnsafe] [drop "utmpx" [r_deep]]); + ("getutxline", unknown ~attrs:[ThreadUnsafe] [drop "utmpx" [r_deep]]); + ("pututxline", unknown ~attrs:[ThreadUnsafe] [drop "utmpx" [r_deep]]); + ("hcreate", unknown ~attrs:[ThreadUnsafe] [drop "nel" []]); + ("hdestroy", unknown ~attrs:[ThreadUnsafe] []); + ("hsearch", unknown ~attrs:[ThreadUnsafe] [drop "item" [r_deep]; drop "action" [r_deep]]); + ("l64a", unknown ~attrs:[ThreadUnsafe] [drop "value" []]); + ("lrand48", unknown ~attrs:[ThreadUnsafe] []); + ("mrand48", unknown ~attrs:[ThreadUnsafe] []); + ("nl_langinfo", unknown ~attrs:[ThreadUnsafe] [drop "item" []]); + ("nl_langinfo_l", unknown [drop "item" []; drop "locale" [r_deep]]); + ("getc_unlocked", unknown ~attrs:[ThreadUnsafe] [drop "stream" [r_deep; w_deep]]); + ("getchar_unlocked", unknown ~attrs:[ThreadUnsafe] []); + ("ptsname", unknown ~attrs:[ThreadUnsafe] [drop "fd" []]); + ("putc_unlocked", unknown ~attrs:[ThreadUnsafe] [drop "c" []; drop "stream" [r_deep; w_deep]]); + ("putchar_unlocked", unknown ~attrs:[ThreadUnsafe] [drop "c" []]); + ("putenv", unknown ~attrs:[ThreadUnsafe] [drop "string" [r; w]]); + ("readdir", unknown ~attrs:[ThreadUnsafe] [drop "dirp" [r_deep]]); + ("setenv", unknown ~attrs:[ThreadUnsafe] [drop "name" [r]; drop "name" [r]; drop "overwrite" []]); + ("setgrent", unknown ~attrs:[ThreadUnsafe] []); + ("setpwent", unknown ~attrs:[ThreadUnsafe] []); + ("setutxent", unknown ~attrs:[ThreadUnsafe] []); + ("strsignal", unknown ~attrs:[ThreadUnsafe] [drop "sig" []]); + ("unsetenv", unknown ~attrs:[ThreadUnsafe] [drop "name" [r]]); + ("lseek", unknown [drop "fd" []; drop "offset" []; drop "whence" []]); + ("fcntl", unknown (drop "fd" [] :: drop "cmd" [] :: VarArgs (drop' [r; w]))); + ("__open_missing_mode", unknown []); + ("fseeko", unknown [drop "stream" [r_deep; w_deep]; drop "offset" []; drop "whence" []]); + ("fileno", unknown [drop "stream" [r_deep; w_deep]]); + ("fdopen", unknown [drop "fd" []; drop "mode" [r]]); + ("getopt", unknown ~attrs:[ThreadUnsafe] [drop "argc" []; drop "argv" [r_deep]; drop "optstring" [r]]); + ("getopt_long", unknown ~attrs:[ThreadUnsafe] [drop "argc" []; drop "argv" [r_deep]; drop "optstring" [r_deep]; drop "longopts" [r]; drop "longindex" [w]]); + ("iconv_open", unknown [drop "tocode" [r]; drop "fromcode" [r]]); + ("iconv", unknown [drop "cd" [r]; drop "inbuf" [r]; drop "inbytesleft" [r;w]; drop "outbuf" [w]; drop "outbytesleft" [r;w]]); + ("iconv_close", unknown [drop "cd" [f]]); + ("strnlen", unknown [drop "s" [r]; drop "maxlen" []]); + ("chmod", unknown [drop "pathname" [r]; drop "mode" []]); + ("fchmod", unknown [drop "fd" []; drop "mode" []]); + ("chown", unknown [drop "pathname" [r]; drop "owner" []; drop "group" []]); + ("fchown", unknown [drop "fd" []; drop "owner" []; drop "group" []]); + ("lchown", unknown [drop "pathname" [r]; drop "owner" []; drop "group" []]); + ("clock_gettime", unknown [drop "clockid" []; drop "tp" [w]]); + ("gettimeofday", unknown [drop "tv" [w]; drop "tz" [w]]); + ("futimens", unknown [drop "fd" []; drop "times" [r]]); + ("utimes", unknown [drop "filename" [r]; drop "times" [r]]); + ("utimensat", unknown [drop "dirfd" []; drop "pathname" [r]; drop "times" [r]; drop "flags" []]); + ("linkat", unknown [drop "olddirfd" []; drop "oldpath" [r]; drop "newdirfd" []; drop "newpath" [r]; drop "flags" []]); + ("dirfd", unknown [drop "dirp" [r]]); + ("fdopendir", unknown [drop "fd" []]); + ("pathconf", unknown [drop "path" [r]; drop "name" []]); + ("symlink" , unknown [drop "oldpath" [r]; drop "newpath" [r];]); + ("ftruncate", unknown [drop "fd" []; drop "length" []]); + ("mkfifo", unknown [drop "pathname" [r]; drop "mode" []]); + ("alarm", unknown [drop "seconds" []]); + ("pread", unknown [drop "fd" []; drop "buf" [w]; drop "count" []; drop "offset" []]); + ("pwrite", unknown [drop "fd" []; drop "buf" [r]; drop "count" []; drop "offset" []]); + ("hstrerror", unknown [drop "err" []]); + ("inet_ntoa", unknown ~attrs:[ThreadUnsafe] [drop "in" []]); + ("getsockopt", unknown [drop "sockfd" []; drop "level" []; drop "optname" []; drop "optval" [w]; drop "optlen" [w]]); + ("setsockopt", unknown [drop "sockfd" []; drop "level" []; drop "optname" []; drop "optval" [r]; drop "optlen" []]); + ("getsockname", unknown [drop "sockfd" []; drop "addr" [w_deep]; drop "addrlen" [w]]); + ("gethostbyaddr", unknown ~attrs:[ThreadUnsafe] [drop "addr" [r_deep]; drop "len" []; drop "type" []]); + ("gethostbyaddr_r", unknown [drop "addr" [r_deep]; drop "len" []; drop "type" []; drop "ret" [w_deep]; drop "buf" [w]; drop "buflen" []; drop "result" [w]; drop "h_errnop" [w]]); + ("gethostbyname", unknown ~attrs:[ThreadUnsafe] [drop "name" [r]]); + ("gethostbyname_r", unknown [drop "name" [r]; drop "result_buf" [w_deep]; drop "buf" [w]; drop "buflen" []; drop "result" [w]; drop "h_errnop" [w]]); + ("gethostname", unknown [drop "name" [w]; drop "len" []]); + ("getpeername", unknown [drop "sockfd" []; drop "addr" [w_deep]; drop "addrlen" [r; w]]); + ("socket", unknown [drop "domain" []; drop "type" []; drop "protocol" []]); + ("sigaction", unknown [drop "signum" []; drop "act" [r_deep; s_deep]; drop "oldact" [w_deep]]); + ("tcgetattr", unknown [drop "fd" []; drop "termios_p" [w_deep]]); + ("tcsetattr", unknown [drop "fd" []; drop "optional_actions" []; drop "termios_p" [r_deep]]); + ("access", unknown [drop "pathname" [r]; drop "mode" []]); + ("ttyname", unknown ~attrs:[ThreadUnsafe] [drop "fd" []]); + ("shm_open", unknown [drop "name" [r]; drop "oflag" []; drop "mode" []]); + ("shmget", unknown [drop "key" []; drop "size" []; drop "shmflag" []]); + ("shmat", unknown [drop "shmid" []; drop "shmaddr" []; drop "shmflag" []]) (* TODO: shmaddr? *); + ("shmdt", unknown [drop "shmaddr" []]) (* TODO: shmaddr? *); + ("sched_get_priority_max", unknown [drop "policy" []]); + ("mprotect", unknown [drop "addr" []; drop "len" []; drop "prot" []]); + ("ftime", unknown [drop "tp" [w]]); + ("timer_create", unknown [drop "clockid" []; drop "sevp" [r; w; s]; drop "timerid" [w]]); + ("timer_settime", unknown [drop "timerid" []; drop "flags" []; drop "new_value" [r_deep]; drop "old_value" [w_deep]]); + ("timer_gettime", unknown [drop "timerid" []; drop "curr_value" [w_deep]]); + ("timer_getoverrun", unknown [drop "timerid" []]); + ("lstat", unknown [drop "pathname" [r]; drop "statbuf" [w]]); + ("fstat", unknown [drop "fd" []; drop "buf" [w]]); + ("fstatat", unknown [drop "dirfd" []; drop "pathname" [r]; drop "buf" [w]; drop "flags" []]); + ("chdir", unknown [drop "path" [r]]); + ("closedir", unknown [drop "dirp" [r]]); + ("mkdir", unknown [drop "pathname" [r]; drop "mode" []]); + ("opendir", unknown [drop "name" [r]]); + ("rmdir", unknown [drop "path" [r]]); + ("open", unknown (drop "pathname" [r] :: drop "flags" [] :: VarArgs (drop "mode" []))); + ("read", unknown [drop "fd" []; drop "buf" [w]; drop "count" []]); + ("write", unknown [drop "fd" []; drop "buf" [r]; drop "count" []]); + ("recv", unknown [drop "sockfd" []; drop "buf" [w]; drop "len" []; drop "flags" []]); + ("recvfrom", unknown [drop "sockfd" []; drop "buf" [w]; drop "len" []; drop "flags" []; drop "src_addr" [w_deep]; drop "addrlen" [r; w]]); + ("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]]); + ("strndup", unknown [drop "s" [r]; drop "n" []]); + ("__strndup", unknown [drop "s" [r]; drop "n" []]); + ("syscall", unknown (drop "number" [] :: VarArgs (drop' [r; w]))); + ("sysconf", unknown [drop "name" []]); + ("syslog", unknown (drop "priority" [] :: drop "format" [r] :: VarArgs (drop' [r]))); (* TODO: is the VarArgs correct here? *) + ("vsyslog", unknown [drop "priority" []; drop "format" [r]; drop "ap" [r_deep]]); (* TODO: what to do with a va_list type? is r_deep correct? *) + ("freeaddrinfo", unknown [drop "res" [f_deep]]); + ("getgid", unknown []); + ("pselect", unknown [drop "nfds" []; drop "readdfs" [r]; drop "writedfs" [r]; drop "exceptfds" [r]; drop "timeout" [r]; drop "sigmask" [r]]); + ("getnameinfo", unknown [drop "addr" [r_deep]; drop "addrlen" []; drop "host" [w]; drop "hostlen" []; drop "serv" [w]; drop "servlen" []; drop "flags" []]); + ("strtok_r", unknown [drop "str" [r; w]; drop "delim" [r]; drop "saveptr" [r_deep; w_deep]]); (* deep accesses through saveptr if str is NULL: https://github.com/lattera/glibc/blob/895ef79e04a953cac1493863bcae29ad85657ee1/string/strtok_r.c#L31-L40 *) + ("kill", unknown [drop "pid" []; drop "sig" []]); + ("closelog", unknown []); + ("dirname", unknown ~attrs:[ThreadUnsafe] [drop "path" [r]]); + ("basename", unknown ~attrs:[ThreadUnsafe] [drop "path" [r]]); + ("setpgid", unknown [drop "pid" []; drop "pgid" []]); + ("dup2", unknown [drop "oldfd" []; drop "newfd" []]); + ("pclose", unknown [drop "stream" [w; f]]); + ("getcwd", unknown [drop "buf" [w]; drop "size" []]); + ("inet_pton", unknown [drop "af" []; drop "src" [r]; drop "dst" [w]]); + ("inet_ntop", unknown [drop "af" []; drop "src" [r]; drop "dst" [w]; drop "size" []]); + ("gethostent", unknown ~attrs:[ThreadUnsafe] []); + ("poll", unknown [drop "fds" [r]; drop "nfds" []; drop "timeout" []]); + ("semget", unknown [drop "key" []; drop "nsems" []; drop "semflg" []]); + ("semctl", unknown (drop "semid" [] :: drop "semnum" [] :: drop "cmd" [] :: VarArgs (drop "semun" [r_deep]))); + ("semop", unknown [drop "semid" []; drop "sops" [r]; drop "nsops" []]); + ("__sigsetjmp", special [__ "env" [w]; drop "savesigs" []] @@ fun env -> Setjmp { env }); (* has two underscores *) + ("sigsetjmp", special [__ "env" [w]; drop "savesigs" []] @@ fun env -> Setjmp { env }); + ("siglongjmp", special [__ "env" [r]; __ "value" []] @@ fun env value -> Longjmp { env; value }); + ("ftw", unknown ~attrs:[ThreadUnsafe] [drop "dirpath" [r]; drop "fn" [s]; drop "nopenfd" []]); (* TODO: use Call instead of Spawn *) + ("nftw", unknown ~attrs:[ThreadUnsafe] [drop "dirpath" [r]; drop "fn" [s]; drop "nopenfd" []; drop "flags" []]); (* TODO: use Call instead of Spawn *) + ("getaddrinfo", unknown [drop "node" [r]; drop "service" [r]; drop "hints" [r_deep]; drop "res" [w]]); (* only write res non-deep because it doesn't write to existing fields of res *) + ("fnmatch", unknown [drop "pattern" [r]; drop "string" [r]; drop "flags" []]); + ("realpath", unknown [drop "path" [r]; drop "resolved_path" [w]]); + ("dprintf", unknown (drop "fd" [] :: drop "format" [r] :: VarArgs (drop' [r]))); + ("vdprintf", unknown [drop "fd" []; drop "format" [r]; drop "ap" [r_deep]]); (* TODO: what to do with a va_list type? is r_deep correct? *) + ("mkdtemp", unknown [drop "template" [r; w]]); + ("mkstemp", unknown [drop "template" [r; w]]); + ("regcomp", unknown [drop "preg" [w_deep]; drop "regex" [r]; drop "cflags" []]); + ("regexec", unknown [drop "preg" [r_deep]; drop "string" [r]; drop "nmatch" []; drop "pmatch" [w_deep]; drop "eflags" []]); + ("regfree", unknown [drop "preg" [f_deep]]); + ("ffs", unknown [drop "i" []]); + ("_exit", special [drop "status" []] @@ Abort); + ("execvp", unknown [drop "file" [r]; drop "argv" [r_deep]]); + ("execl", unknown (drop "path" [r] :: drop "arg" [r] :: VarArgs (drop' [r]))); + ("statvfs", unknown [drop "path" [r]; drop "buf" [w]]); + ("readlink", unknown [drop "path" [r]; drop "buf" [w]; drop "bufsz" []]); + ("wcwidth", unknown [drop "c" []]); + ("wcswidth", unknown [drop "s" [r]; drop "n" []]); + ("link", unknown [drop "oldpath" [r]; drop "newpath" [r]]); + ("renameat", unknown [drop "olddirfd" []; drop "oldpath" [r]; drop "newdirfd" []; drop "newpath" [r]]); + ("posix_fadvise", unknown [drop "fd" []; drop "offset" []; drop "len" []; drop "advice" []]); + ("lockf", unknown [drop "fd" []; drop "cmd" []; drop "len" []]); + ("htonl", unknown [drop "hostlong" []]); + ("htons", unknown [drop "hostshort" []]); + ("ntohl", unknown [drop "netlong" []]); + ("ntohs", unknown [drop "netshort" []]); + ("sleep", unknown [drop "seconds" []]); + ("usleep", unknown [drop "usec" []]); + ("nanosleep", unknown [drop "req" [r]; drop "rem" [w]]); + ("setpriority", unknown [drop "which" []; drop "who" []; drop "prio" []]); + ("getpriority", unknown [drop "which" []; drop "who" []]); + ("sched_yield", unknown []); + ("getpid", unknown []); + ("getppid", unknown []); + ("getuid", unknown []); + ("geteuid", unknown []); + ("getpgrp", unknown []); + ("setrlimit", unknown [drop "resource" []; drop "rlim" [r]]); + ("getrlimit", unknown [drop "resource" []; drop "rlim" [w]]); + ("setsid", unknown []); + ("isatty", unknown [drop "fd" []]); + ("sigemptyset", unknown [drop "set" [w]]); + ("sigfillset", unknown [drop "set" [w]]); + ("sigaddset", unknown [drop "set" [r; w]; drop "signum" []]); + ("sigdelset", unknown [drop "set" [r; w]; drop "signum" []]); + ("sigismember", unknown [drop "set" [r]; drop "signum" []]); + ("sigprocmask", unknown [drop "how" []; drop "set" [r]; drop "oldset" [w]]); + ("sigwait", unknown [drop "set" [r]; drop "sig" [w]]); + ("sigwaitinfo", unknown [drop "set" [r]; drop "info" [w]]); + ("sigtimedwait", unknown [drop "set" [r]; drop "info" [w]; drop "timeout" [r]]); + ("fork", unknown []); + ("dlopen", unknown [drop "filename" [r]; drop "flag" []]); + ("dlerror", unknown ~attrs:[ThreadUnsafe] []); + ("dlsym", unknown [drop "handle" [r]; drop "symbol" [r]]); + ("dlclose", unknown [drop "handle" [r]]); + ("inet_addr", unknown [drop "cp" [r]]); + ("uname", unknown [drop "buf" [w_deep]]); + ("strcasecmp", unknown [drop "s1" [r]; drop "s2" [r]]); + ("strncasecmp", unknown [drop "s1" [r]; drop "s2" [r]; drop "n" []]); + ("connect", unknown [drop "sockfd" []; drop "sockaddr" [r_deep]; drop "addrlen" []]); + ("bind", unknown [drop "sockfd" []; drop "sockaddr" [r_deep]; drop "addrlen" []]); + ("listen", unknown [drop "sockfd" []; drop "backlog" []]); + ("select", unknown [drop "nfds" []; drop "readfds" [r; w]; drop "writefds" [r; w]; drop "exceptfds" [r; w]; drop "timeout" [r; w]]); + ("accept", unknown [drop "sockfd" []; drop "addr" [w_deep]; drop "addrlen" [r; w]]); + ("close", unknown [drop "fd" []]); + ("writev", unknown [drop "fd" []; drop "iov" [r_deep]; drop "iovcnt" []]); + ("readv", unknown [drop "fd" []; drop "iov" [w_deep]; drop "iovcnt" []]); + ("unlink", unknown [drop "pathname" [r]]); + ("popen", unknown [drop "command" [r]; drop "type" [r]]); + ("stat", unknown [drop "pathname" [r]; drop "statbuf" [w]]); + ("fsync", unknown [drop "fd" []]); + ("fdatasync", unknown [drop "fd" []]); + ("getrusage", unknown [drop "who" []; drop "usage" [w]]); + ("alphasort", unknown [drop "a" [r]; drop "b" [r]]); + ("gmtime_r", unknown [drop "timer" [r]; drop "result" [w]]); + ("rand_r", special [drop "seedp" [r; w]] Rand); + ("srandom", unknown [drop "seed" []]); + ("random", special [] Rand); + ("posix_memalign", unknown [drop "memptr" [w]; drop "alignment" []; drop "size" []]); (* TODO: Malloc *) + ("stpcpy", unknown [drop "dest" [w]; drop "src" [r]]); + ] + +(** Pthread functions. *) +let pthread_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ + ("pthread_create", special [__ "thread" [w]; drop "attr" [r]; __ "start_routine" [s]; __ "arg" []] @@ fun thread start_routine arg -> ThreadCreate { thread; start_routine; arg }); (* For precision purposes arg is not considered accessed here. Instead all accesses (if any) come from actually analyzing start_routine. *) + ("pthread_exit", special [__ "retval" []] @@ fun retval -> ThreadExit { ret_val = retval }); (* Doesn't dereference the void* itself, but just passes to pthread_join. *) + ("pthread_join", special [__ "thread" []; __ "retval" [w]] @@ fun thread retval -> ThreadJoin {thread; ret_var = retval}); + ("pthread_kill", unknown [drop "thread" []; drop "sig" []]); + ("pthread_equal", unknown [drop "t1" []; drop "t2" []]); + ("pthread_cond_init", unknown [drop "cond" [w]; drop "attr" [r]]); + ("__pthread_cond_init", unknown [drop "cond" [w]; drop "attr" [r]]); + ("pthread_cond_signal", special [__ "cond" []] @@ fun cond -> Signal cond); + ("__pthread_cond_signal", special [__ "cond" []] @@ fun cond -> Signal cond); + ("pthread_cond_broadcast", special [__ "cond" []] @@ fun cond -> Broadcast cond); + ("__pthread_cond_broadcast", special [__ "cond" []] @@ fun cond -> Broadcast cond); + ("pthread_cond_wait", special [__ "cond" []; __ "mutex" []] @@ fun cond mutex -> Wait {cond; mutex}); + ("__pthread_cond_wait", special [__ "cond" []; __ "mutex" []] @@ fun cond mutex -> Wait {cond; mutex}); + ("pthread_cond_timedwait", special [__ "cond" []; __ "mutex" []; __ "abstime" [r]] @@ fun cond mutex abstime -> TimedWait {cond; mutex; abstime}); + ("pthread_cond_destroy", unknown [drop "cond" [f]]); + ("__pthread_cond_destroy", unknown [drop "cond" [f]]); + ("pthread_mutexattr_settype", special [__ "attr" []; __ "type" []] @@ fun attr typ -> MutexAttrSetType {attr; typ}); + ("pthread_mutex_init", special [__ "mutex" []; __ "attr" []] @@ fun mutex attr -> MutexInit {mutex; attr}); + ("pthread_mutex_destroy", unknown [drop "mutex" [f]]); + ("pthread_mutex_lock", special [__ "mutex" []] @@ fun mutex -> Lock {lock = mutex; try_ = get_bool "sem.lock.fail"; write = true; return_on_success = false}); + ("__pthread_mutex_lock", special [__ "mutex" []] @@ fun mutex -> Lock {lock = mutex; try_ = get_bool "sem.lock.fail"; write = true; return_on_success = false}); + ("pthread_mutex_trylock", special [__ "mutex" []] @@ fun mutex -> Lock {lock = mutex; try_ = true; write = true; return_on_success = false}); + ("__pthread_mutex_trylock", special [__ "mutex" []] @@ fun mutex -> Lock {lock = mutex; try_ = true; write = true; return_on_success = false}); + ("pthread_mutex_unlock", special [__ "mutex" []] @@ fun mutex -> Unlock mutex); + ("__pthread_mutex_unlock", special [__ "mutex" []] @@ fun mutex -> Unlock mutex); + ("pthread_mutexattr_init", unknown [drop "attr" [w]]); + ("pthread_mutexattr_getpshared", unknown [drop "attr" [r]; drop "pshared" [w]]); + ("pthread_mutexattr_setpshared", unknown [drop "attr" [w]; drop "pshared" []]); + ("pthread_mutexattr_getrobust", unknown [drop "attr" [r]; drop "pshared" [w]]); + ("pthread_mutexattr_setrobust", unknown [drop "attr" [w]; drop "pshared" []]); + ("pthread_mutexattr_destroy", unknown [drop "attr" [f]]); + ("pthread_rwlock_init", unknown [drop "rwlock" [w]; drop "attr" [r]]); + ("pthread_rwlock_destroy", unknown [drop "rwlock" [f]]); + ("pthread_rwlock_rdlock", special [__ "rwlock" []] @@ fun rwlock -> Lock {lock = rwlock; try_ = get_bool "sem.lock.fail"; write = false; return_on_success = false}); + ("pthread_rwlock_tryrdlock", special [__ "rwlock" []] @@ fun rwlock -> Lock {lock = rwlock; try_ = true; write = false; return_on_success = false}); + ("pthread_rwlock_wrlock", special [__ "rwlock" []] @@ fun rwlock -> Lock {lock = rwlock; try_ = get_bool "sem.lock.fail"; write = true; return_on_success = false}); + ("pthread_rwlock_trywrlock", special [__ "rwlock" []] @@ fun rwlock -> Lock {lock = rwlock; try_ = true; write = true; return_on_success = false}); + ("pthread_rwlock_unlock", special [__ "rwlock" []] @@ fun rwlock -> Unlock rwlock); + ("pthread_rwlockattr_init", unknown [drop "attr" [w]]); + ("pthread_rwlockattr_destroy", unknown [drop "attr" [f]]); + ("pthread_spin_init", unknown [drop "lock" [w]; drop "pshared" []]); + ("pthread_spin_destroy", unknown [drop "lock" [f]]); + ("pthread_spin_lock", special [__ "lock" []] @@ fun lock -> Lock {lock = lock; try_ = get_bool "sem.lock.fail"; write = true; return_on_success = false}); + ("pthread_spin_trylock", special [__ "lock" []] @@ fun lock -> Lock {lock = lock; try_ = true; write = true; return_on_success = false}); + ("pthread_spin_unlock", special [__ "lock" []] @@ fun lock -> Unlock lock); + ("pthread_attr_init", unknown [drop "attr" [w]]); + ("pthread_attr_destroy", unknown [drop "attr" [f]]); + ("pthread_attr_getdetachstate", unknown [drop "attr" [r]; drop "detachstate" [w]]); + ("pthread_attr_setdetachstate", unknown [drop "attr" [w]; drop "detachstate" []]); + ("pthread_attr_getstacksize", unknown [drop "attr" [r]; drop "stacksize" [w]]); + ("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_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" []]); + ("pthread_key_create", unknown [drop "key" [w]; drop "destructor" [s]]); + ("pthread_key_delete", unknown [drop "key" [f]]); + ("pthread_cancel", unknown [drop "thread" []]); + ("pthread_testcancel", unknown []); + ("pthread_setcancelstate", unknown [drop "state" []; drop "oldstate" [w]]); + ("pthread_setcanceltype", unknown [drop "type" []; drop "oldtype" [w]]); + ("pthread_detach", unknown [drop "thread" []]); + ("pthread_attr_setschedpolicy", unknown [drop "attr" [r; w]; drop "policy" []]); + ("pthread_condattr_init", unknown [drop "attr" [w]]); + ("pthread_condattr_setclock", unknown [drop "attr" [w]; drop "clock_id" []]); + ("pthread_attr_setschedparam", unknown [drop "attr" [r; w]; drop "param" [r]]); + ("pthread_setaffinity_np", unknown [drop "thread" []; drop "cpusetsize" []; drop "cpuset" [r]]); + ("pthread_getaffinity_np", unknown [drop "thread" []; drop "cpusetsize" []; drop "cpuset" [w]]); + (* Not recording read accesses to sem as these are thread-safe anyway not to clutter messages (as for mutexes) **) + ("sem_init", special [__ "sem" []; __ "pshared" []; __ "value" []] @@ fun sem pshared value -> SemInit {sem; pshared; value}); + ("sem_wait", special [__ "sem" []] @@ fun sem -> SemWait {sem; try_ = false; timeout = None}); + ("sem_trywait", special [__ "sem" []] @@ fun sem -> SemWait {sem; try_ = true; timeout = None}); + ("sem_timedwait", special [__ "sem" []; __ "abs_timeout" [r]] @@ fun sem abs_timeout-> SemWait {sem; try_ = true; timeout = Some abs_timeout}); (* no write accesses to sem because sync primitive itself has no race *) + ("sem_post", special [__ "sem" []] @@ fun sem -> SemPost sem); + ("sem_destroy", special [__ "sem" []] @@ fun sem -> SemDestroy sem); + ] + +(** GCC builtin functions. + These are not builtin versions of functions from other lists. *) +let gcc_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ + ("__builtin_bswap16", unknown [drop "x" []]); + ("__builtin_bswap32", unknown [drop "x" []]); + ("__builtin_bswap64", unknown [drop "x" []]); + ("__builtin_bswap128", unknown [drop "x" []]); + ("__builtin_ctz", unknown [drop "x" []]); + ("__builtin_ctzl", unknown [drop "x" []]); + ("__builtin_ctzll", unknown [drop "x" []]); + ("__builtin_clz", unknown [drop "x" []]); + ("__builtin_clzl", unknown [drop "x" []]); + ("__builtin_clzll", unknown [drop "x" []]); + ("__builtin_object_size", unknown [drop "ptr" [r]; drop' []]); + ("__builtin_prefetch", unknown (drop "addr" [] :: VarArgs (drop' []))); + ("__builtin_expect", special [__ "exp" []; drop' []] @@ fun exp -> Identity exp); (* Identity, because just compiler optimization annotation. *) + ("__builtin_unreachable", special' [] @@ fun () -> if get_bool "sem.builtin_unreachable.dead_code" then Abort else Unknown); (* https://github.com/sosy-lab/sv-benchmarks/issues/1296 *) + ("__assert_rtn", special [drop "func" [r]; drop "file" [r]; drop "line" []; drop "exp" [r]] @@ Abort); (* MacOS's built-in assert *) + ("__assert_fail", special [drop "assertion" [r]; drop "file" [r]; drop "line" []; drop "function" [r]] @@ Abort); (* gcc's built-in assert *) + ("__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_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]]); + ("__builtin_saddll_overflow", unknown [drop "a" []; drop "b" []; drop "c" [w]]); + ("__builtin_uadd_overflow", unknown [drop "a" []; drop "b" []; drop "c" [w]]); + ("__builtin_uaddl_overflow", unknown [drop "a" []; drop "b" []; drop "c" [w]]); + ("__builtin_uaddll_overflow", unknown [drop "a" []; drop "b" []; drop "c" [w]]); + ("__builtin_sub_overflow", unknown [drop "a" []; drop "b" []; drop "c" [w]]); + ("__builtin_ssub_overflow", unknown [drop "a" []; drop "b" []; drop "c" [w]]); + ("__builtin_ssubl_overflow", unknown [drop "a" []; drop "b" []; drop "c" [w]]); + ("__builtin_ssubll_overflow", unknown [drop "a" []; drop "b" []; drop "c" [w]]); + ("__builtin_usub_overflow", unknown [drop "a" []; drop "b" []; drop "c" [w]]); + ("__builtin_usubl_overflow", unknown [drop "a" []; drop "b" []; drop "c" [w]]); + ("__builtin_usubll_overflow", unknown [drop "a" []; drop "b" []; drop "c" [w]]); + ("__builtin_mul_overflow", unknown [drop "a" []; drop "b" []; drop "c" [w]]); + ("__builtin_smul_overflow", unknown [drop "a" []; drop "b" []; drop "c" [w]]); + ("__builtin_smull_overflow", unknown [drop "a" []; drop "b" []; drop "c" [w]]); + ("__builtin_smulll_overflow", unknown [drop "a" []; drop "b" []; drop "c" [w]]); + ("__builtin_umul_overflow", unknown [drop "a" []; drop "b" []; drop "c" [w]]); + ("__builtin_umull_overflow", unknown [drop "a" []; drop "b" []; drop "c" [w]]); + ("__builtin_umulll_overflow", unknown [drop "a" []; drop "b" []; drop "c" [w]]); + ("__builtin_add_overflow_p", unknown [drop "a" []; drop "b" []; drop "c" []]); + ("__builtin_sub_overflow_p", unknown [drop "a" []; drop "b" []; drop "c" []]); + ("__builtin_mul_overflow_p", unknown [drop "a" []; drop "b" []; drop "c" []]); + ("__builtin_popcount", unknown [drop "x" []]); + ("__builtin_popcountl", unknown [drop "x" []]); + ("__builtin_popcountll", unknown [drop "x" []]); + ("__atomic_store_n", unknown [drop "ptr" [w]; drop "val" []; drop "memorder" []]); + ("__atomic_store", unknown [drop "ptr" [w]; drop "val" [r]; drop "memorder" []]); + ("__atomic_load_n", unknown [drop "ptr" [r]; drop "memorder" []]); + ("__atomic_load", unknown [drop "ptr" [r]; drop "ret" [w]; drop "memorder" []]); + ("__atomic_clear", unknown [drop "ptr" [w]; drop "memorder" []]); + ("__atomic_compare_exchange_n", unknown [drop "ptr" [r; w]; drop "expected" [r; w]; drop "desired" []; drop "weak" []; drop "success_memorder" []; drop "failure_memorder" []]); + ("__atomic_compare_exchange", unknown [drop "ptr" [r; w]; drop "expected" [r; w]; drop "desired" [r]; drop "weak" []; drop "success_memorder" []; drop "failure_memorder" []]); + ("__atomic_add_fetch", unknown [drop "ptr" [r; w]; drop "val" []; drop "memorder" []]); + ("__atomic_sub_fetch", unknown [drop "ptr" [r; w]; drop "val" []; drop "memorder" []]); + ("__atomic_and_fetch", unknown [drop "ptr" [r; w]; drop "val" []; drop "memorder" []]); + ("__atomic_xor_fetch", unknown [drop "ptr" [r; w]; drop "val" []; drop "memorder" []]); + ("__atomic_or_fetch", unknown [drop "ptr" [r; w]; drop "val" []; drop "memorder" []]); + ("__atomic_nand_fetch", unknown [drop "ptr" [r; w]; drop "val" []; drop "memorder" []]); + ("__atomic_fetch_add", unknown [drop "ptr" [r; w]; drop "val" []; drop "memorder" []]); + ("__atomic_fetch_sub", unknown [drop "ptr" [r; w]; drop "val" []; drop "memorder" []]); + ("__atomic_fetch_and", unknown [drop "ptr" [r; w]; drop "val" []; drop "memorder" []]); + ("__atomic_fetch_xor", unknown [drop "ptr" [r; w]; drop "val" []; drop "memorder" []]); + ("__atomic_fetch_or", unknown [drop "ptr" [r; w]; drop "val" []; drop "memorder" []]); + ("__atomic_fetch_nand", unknown [drop "ptr" [r; w]; drop "val" []; drop "memorder" []]); + ("__atomic_test_and_set", unknown [drop "ptr" [r; w]; drop "memorder" []]); + ("__atomic_thread_fence", unknown [drop "memorder" []]); + ("__sync_bool_compare_and_swap", unknown [drop "ptr" [r; w]; drop "oldval" []; drop "newval" []]); + ("__sync_fetch_and_add", unknown (drop "ptr" [r; w] :: drop "value" [] :: VarArgs (drop' []))); + ("__sync_fetch_and_sub", unknown (drop "ptr" [r; w] :: drop "value" [] :: VarArgs (drop' []))); + ("__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); + ] + +let glibc_desc_list: (string * LibraryDesc.t) list = LibraryDsl.[ + ("fputs_unlocked", unknown [drop "s" [r]; drop "stream" [w]]); + ("feof_unlocked", unknown [drop "stream" [r_deep; w_deep]]); + ("ferror_unlocked", unknown [drop "stream" [r_deep; w_deep]]); + ("fwrite_unlocked", unknown [drop "buffer" [r]; drop "size" []; drop "count" []; drop "stream" [r_deep; w_deep]]); + ("clearerr_unlocked", unknown [drop "stream" [w]]); (* TODO: why only w? *) + ("futimesat", unknown [drop "dirfd" []; drop "pathname" [r]; drop "times" [r]]); + ("error", unknown ((drop "status" []) :: (drop "errnum" []) :: (drop "format" [r]) :: (VarArgs (drop' [r])))); + ("warn", unknown (drop "format" [r] :: VarArgs (drop' [r]))); + ("gettext", unknown [drop "msgid" [r]]); + ("euidaccess", unknown [drop "pathname" [r]; drop "mode" []]); + ("rpmatch", unknown [drop "response" [r]]); + ("getpagesize", unknown []); + ("__fgets_alias", unknown [drop "__s" [w]; drop "__n" []; drop "__stream" [r_deep; w_deep]]); + ("__fgets_chk", unknown [drop "__s" [w]; drop "__size" []; drop "__n" []; drop "__stream" [r_deep; w_deep]]); + ("__fread_alias", unknown [drop "__ptr" [w]; drop "__size" []; drop "__n" []; drop "__stream" [r_deep; w_deep]]); + ("__fread_chk", unknown [drop "__ptr" [w]; drop "__ptrlen" []; drop "__size" []; drop "__n" []; drop "__stream" [r_deep; w_deep]]); + ("__fread_chk_warn", unknown [drop "buffer" [w]; drop "os" []; drop "size" []; drop "count" []; drop "stream" [r_deep; w_deep]]); + ("fread_unlocked", unknown ~attrs:[ThreadUnsafe] [drop "buffer" [w]; drop "size" []; drop "count" []; drop "stream" [r_deep; w_deep]]); + ("__fread_unlocked_alias", unknown ~attrs:[ThreadUnsafe] [drop "__ptr" [w]; drop "__size" []; drop "__n" []; drop "__stream" [r_deep; w_deep]]); + ("__fread_unlocked_chk", unknown ~attrs:[ThreadUnsafe] [drop "__ptr" [w]; drop "__ptrlen" []; drop "__size" []; drop "__n" []; drop "__stream" [r_deep; w_deep]]); + ("__fread_unlocked_chk_warn", unknown ~attrs:[ThreadUnsafe] [drop "__ptr" [w]; drop "__ptrlen" []; drop "__size" []; drop "__n" []; drop "__stream" [r_deep; w_deep]]); + ("__read_chk", unknown [drop "__fd" []; drop "__buf" [w]; drop "__nbytes" []; drop "__buflen" []]); + ("__read_alias", unknown [drop "__fd" []; drop "__buf" [w]; drop "__nbytes" []]); + ("__readlink_chk", unknown [drop "path" [r]; drop "buf" [w]; drop "len" []; drop "buflen" []]); + ("__readlink_alias", unknown [drop "path" [r]; drop "buf" [w]; drop "len" []]); + ("__overflow", unknown [drop "f" [r]; drop "ch" []]); + ("__ctype_get_mb_cur_max", unknown []); + ("__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]]); + ("xdr_string", unknown [drop "xdrs" [r_deep; w_deep]; drop "sp" [r; w]; drop "maxsize" []]); + ("xdr_enum", unknown [drop "xdrs" [r_deep; w_deep]; drop "ep" [r; w]]); + ("xdr_u_int", unknown [drop "xdrs" [r_deep; w_deep]; drop "up" [r; w]]); + ("xdr_opaque", unknown [drop "xdrs" [r_deep; w_deep]; drop "cp" [r; w]; drop "cnt" []]); + ("xdr_free", unknown [drop "proc" [s]; drop "objp" [f_deep]]); + ("svcerr_noproc", unknown [drop "xprt" [r_deep; w_deep]]); + ("svcerr_decode", unknown [drop "xprt" [r_deep; w_deep]]); + ("svcerr_systemerr", unknown [drop "xprt" [r_deep; w_deep]]); + ("svc_sendreply", unknown [drop "xprt" [r_deep; w_deep]; drop "outproc" [s]; drop "out" [r]]); + ("shutdown", unknown [drop "socket" []; drop "how" []]); + ("getaddrinfo_a", unknown [drop "mode" []; drop "list" [w_deep]; drop "nitems" []; drop "sevp" [r; w; s]]); + ("__uflow", unknown [drop "file" [r; w]]); + ("getservbyname_r", unknown [drop "name" [r]; drop "proto" [r]; drop "result_buf" [w_deep]; drop "buf" [w]; drop "buflen" []; drop "result" [w]]); + ("strsep", unknown [drop "stringp" [r_deep; w]; drop "delim" [r]]); + ("strcasestr", unknown [drop "haystack" [r]; drop "needle" [r]]); + ("inet_aton", unknown [drop "cp" [r]; drop "inp" [w]]); + ("fopencookie", unknown [drop "cookie" []; drop "mode" [r]; drop "io_funcs" [s_deep]]); (* doesn't access cookie but passes it to io_funcs *) + ("mempcpy", special [__ "dest" [w]; __ "src" [r]; __ "n" []] @@ fun dest src n -> Memcpy { dest; src; n; }); + ("__builtin___mempcpy_chk", special [__ "dest" [w]; __ "src" [r]; __ "n" []; drop "os" []] @@ fun dest src n -> Memcpy { dest; src; n; }); + ("rawmemchr", unknown [drop "s" [r]; drop "c" []]); + ("memrchr", unknown [drop "s" [r]; drop "c" []; drop "n" []]); + ("memmem", unknown [drop "haystack" [r]; drop "haystacklen" []; drop "needle" [r]; drop "needlelen" [r]]); + ("getifaddrs", unknown [drop "ifap" [w]]); + ("freeifaddrs", unknown [drop "ifa" [f_deep]]); + ("atoq", unknown [drop "nptr" [r]]); + ("strchrnul", unknown [drop "s" [r]; drop "c" []]); + ("getdtablesize", unknown []); + ("daemon", unknown [drop "nochdir" []; drop "noclose" []]); + ] + +let linux_userspace_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ + (* ("prctl", unknown [drop "option" []; drop "arg2" []; drop "arg3" []; drop "arg4" []; drop "arg5" []]); *) + ("prctl", unknown (drop "option" [] :: VarArgs (drop' []))); (* man page has 5 arguments, but header has varargs and real-world programs may call with <5 *) + ("__ctype_tolower_loc", unknown []); + ("__ctype_toupper_loc", unknown []); + ("endutxent", unknown ~attrs:[ThreadUnsafe] []); + ("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" []]); + ("__fprintf_chk", unknown (drop "stream" [r_deep; w_deep] :: drop "flag" [] :: drop "format" [r] :: VarArgs (drop' [r]))); + ("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 *) + ("madvise", unknown [drop "addr" []; drop "length" []; drop "advice" []]); + ("mremap", unknown (drop "old_address" [] :: drop "old_size" [] :: drop "new_size" [] :: drop "flags" [] :: VarArgs (drop "new_address" []))); + ("msync", unknown [drop "addr" []; drop "len" []; drop "flags" []]); + ("inotify_init1", unknown [drop "flags" []]); + ("inotify_add_watch", unknown [drop "fd" []; drop "pathname" [r]; drop "mask" []]); + ("inotify_rm_watch", unknown [drop "fd" []; drop "wd" []]); + ("fts_open", unknown [drop "path_argv" [r_deep]; drop "options" []; drop "compar" [s]]); (* TODO: use Call instead of Spawn *) + ("fts_read", unknown [drop "ftsp" [r_deep; w_deep]]); + ("fts_close", unknown [drop "ftsp" [f_deep]]); + ("mount", unknown [drop "source" [r]; drop "target" [r]; drop "filesystemtype" [r]; drop "mountflags" []; drop "data" [r]]); + ("umount", unknown [drop "target" [r]]); + ("umount2", unknown [drop "target" [r]; drop "flags" []]); + ("statfs", unknown [drop "path" [r]; drop "buf" [w]]); + ("fstatfs", unknown [drop "fd" []; drop "buf" [w]]); + ("cfmakeraw", unknown [drop "termios" [r; w]]); + ("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 []); + ] + +let big_kernel_lock = AddrOf (Cil.var (Cilfacade.create_var (makeGlobalVar "[big kernel lock]" intType))) +let console_sem = AddrOf (Cil.var (Cilfacade.create_var (makeGlobalVar "[console semaphore]" intType))) + +(** Linux kernel functions. *) +let linux_kernel_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ + ("down_trylock", special [__ "sem" []] @@ fun sem -> Lock { lock = sem; try_ = true; write = true; return_on_success = true }); + ("down_read", special [__ "sem" []] @@ fun sem -> Lock { lock = sem; try_ = get_bool "sem.lock.fail"; write = false; return_on_success = true }); + ("down_write", special [__ "sem" []] @@ fun sem -> Lock { lock = sem; try_ = get_bool "sem.lock.fail"; write = true; return_on_success = true }); + ("up", special [__ "sem" []] @@ fun sem -> Unlock sem); + ("up_read", special [__ "sem" []] @@ fun sem -> Unlock sem); + ("up_write", special [__ "sem" []] @@ fun sem -> Unlock sem); + ("mutex_init", unknown [drop "mutex" []]); + ("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", 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 }); + ("spin_trylock", special [__ "lock" []] @@ fun lock -> Lock { lock = lock; try_ = true; write = true; return_on_success = true }); + ("_spin_trylock", special [__ "lock" []] @@ fun lock -> Lock { lock = lock; try_ = true; write = true; return_on_success = true }); + ("spin_unlock", special [__ "lock" []] @@ fun lock -> Unlock lock); + ("_spin_unlock", special [__ "lock" []] @@ fun lock -> Unlock lock); + ("_spin_unlock_bh", special [__ "lock" []] @@ fun lock -> Unlock lock); + ("spin_lock_irqsave", special [__ "lock" []; drop "flags" []] @@ fun lock -> Lock { lock; try_ = get_bool "sem.lock.fail"; write = true; return_on_success = true }); + ("_spin_lock_irqsave", special [__ "lock" []] @@ fun lock -> Lock { lock; try_ = get_bool "sem.lock.fail"; write = true; return_on_success = true }); + ("_spin_trylock_irqsave", special [__ "lock" []; drop "flags" []] @@ fun lock -> Lock { lock; try_ = true; write = true; return_on_success = true }); + ("spin_unlock_irqrestore", special [__ "lock" []; drop "flags" []] @@ fun lock -> Unlock lock); + ("_spin_unlock_irqrestore", special [__ "lock" []; drop "flags" []] @@ fun lock -> Unlock lock); + ("raw_spin_unlock", special [__ "lock" []] @@ fun lock -> Unlock lock); + ("_raw_spin_unlock_irqrestore", special [__ "lock" []; drop "flags" []] @@ fun lock -> Unlock lock); + ("_raw_spin_lock", special [__ "lock" []] @@ fun lock -> Lock { lock = lock; try_ = get_bool "sem.lock.fail"; write = true; return_on_success = true }); + ("_raw_spin_lock_flags", special [__ "lock" []; drop "flags" []] @@ fun lock -> Lock { lock = lock; try_ = get_bool "sem.lock.fail"; write = true; return_on_success = true }); + ("_raw_spin_lock_irqsave", special [__ "lock" []] @@ fun lock -> Lock { lock = lock; try_ = get_bool "sem.lock.fail"; write = true; return_on_success = true }); + ("_raw_spin_lock_irq", special [__ "lock" []] @@ fun lock -> Lock { lock = lock; try_ = get_bool "sem.lock.fail"; write = true; return_on_success = true }); + ("_raw_spin_lock_bh", special [__ "lock" []] @@ fun lock -> Lock { lock = lock; try_ = get_bool "sem.lock.fail"; write = true; return_on_success = true }); + ("_raw_spin_unlock_bh", special [__ "lock" []] @@ fun lock -> Unlock lock); + ("_read_lock", special [__ "lock" []] @@ fun lock -> Lock { lock = lock; try_ = get_bool "sem.lock.fail"; write = false; return_on_success = true }); + ("_read_unlock", special [__ "lock" []] @@ fun lock -> Unlock lock); + ("_raw_read_lock", special [__ "lock" []] @@ fun lock -> Lock { lock = lock; try_ = get_bool "sem.lock.fail"; write = false; return_on_success = true }); + ("__raw_read_unlock", special [__ "lock" []] @@ fun lock -> Unlock lock); + ("_write_lock", special [__ "lock" []] @@ fun lock -> Lock { lock = lock; try_ = get_bool "sem.lock.fail"; write = true; return_on_success = true }); + ("_write_unlock", special [__ "lock" []] @@ fun lock -> Unlock lock); + ("_raw_write_lock", special [__ "lock" []] @@ fun lock -> Lock { lock = lock; try_ = get_bool "sem.lock.fail"; write = true; return_on_success = true }); + ("__raw_write_unlock", special [__ "lock" []] @@ fun lock -> Unlock lock); + ("spinlock_check", special [__ "lock" []] @@ fun lock -> Identity lock); (* Identity, because we don't want lock internals. *) + ("_lock_kernel", special [drop "func" [r]; drop "file" [r]; drop "line" []] @@ Lock { lock = big_kernel_lock; try_ = false; write = true; return_on_success = true }); + ("_unlock_kernel", special [drop "func" [r]; drop "file" [r]; drop "line" []] @@ Unlock big_kernel_lock); + ("acquire_console_sem", special [] @@ Lock { lock = console_sem; try_ = false; write = true; return_on_success = true }); + ("release_console_sem", special [] @@ Unlock console_sem); + ("misc_deregister", unknown [drop "misc" [r_deep]]); + ("__bad_percpu_size", special [] Abort); (* these do not have definitions so the linker will fail if they are actually called *) + ("__bad_size_call_parameter", special [] Abort); + ("__xchg_wrong_size", special [] Abort); + ("__cmpxchg_wrong_size", special [] Abort); + ("__xadd_wrong_size", special [] Abort); + ("__put_user_bad", special [] Abort); + ("kmalloc", special [__ "size" []; drop "flags" []] @@ fun size -> Malloc size); + ("__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); + ] + +(** Goblint functions. *) +let goblint_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ + ("__goblint_unknown", unknown [drop' [w]]); + ("__goblint_check", special [__ "exp" []] @@ fun exp -> Assert { exp; check = true; refine = false }); + ("__goblint_assume", special [__ "exp" []] @@ fun exp -> Assert { exp; check = false; refine = true }); + ("__goblint_assert", special [__ "exp" []] @@ fun exp -> Assert { exp; check = true; refine = get_bool "sem.assert.refine" }); + ("__goblint_split_begin", unknown [drop "exp" []]); + ("__goblint_split_end", unknown [drop "exp" []]); + ("__goblint_bounded", special [__ "exp"[]] @@ fun exp -> Bounded { exp }); + ] + +(** zstd functions. + Only used with extraspecials. *) +let zstd_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ + ("ZSTD_customMalloc", special [__ "size" []; drop "customMem" [r]] @@ fun size -> Malloc size); + ("ZSTD_customCalloc", special [__ "size" []; drop "customMem" [r]] @@ fun size -> Calloc { size; count = Cil.one }); + ("ZSTD_customFree", special [__ "ptr" [f]; drop "customMem" [r]] @@ fun ptr -> Free ptr); + ] + +(** math functions. + Functions and builtin versions of function and macros defined in math.h. *) +let math_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ + ("__builtin_nan", special [__ "str" []] @@ fun str -> Math { fun_args = (Nan (FDouble, str)) }); + ("nan", special [__ "str" []] @@ fun str -> Math { fun_args = (Nan (FDouble, str)) }); + ("__builtin_nanf", special [__ "str" []] @@ fun str -> Math { fun_args = (Nan (FFloat, str)) }); + ("nanf", special [__ "str" []] @@ fun str -> Math { fun_args = (Nan (FFloat, str)) }); + ("__builtin_nanl", special [__ "str" []] @@ fun str -> Math { fun_args = (Nan (FLongDouble, str)) }); + ("nanl", special [__ "str" []] @@ fun str -> Math { fun_args = (Nan (FLongDouble, str)) }); + ("__builtin_inf", special [] @@ Math { fun_args = Inf FDouble}); + ("__builtin_huge_val", special [] @@ Math { fun_args = Inf FDouble}); (* we assume the target format can represent infinities *) + ("__builtin_inff", special [] @@ Math { fun_args = Inf FFloat}); + ("__builtin_huge_valf", special [] @@ Math { fun_args = Inf FFloat}); (* we assume the target format can represent infinities *) + ("__builtin_infl", special [] @@ Math { fun_args = Inf FLongDouble}); + ("__builtin_huge_vall", special [] @@ Math { fun_args = Inf FLongDouble}); (* we assume the target format can represent infinities *) + ("__builtin_isfinite", special [__ "x" []] @@ fun x -> Math { fun_args = (Isfinite x) }); + ("__finite", special [__ "x" []] @@ fun x -> Math { fun_args = (Isfinite x) }); + ("__finitef", special [__ "x" []] @@ fun x -> Math { fun_args = (Isfinite x) }); + ("__finitel", special [__ "x" []] @@ fun x -> Math { fun_args = (Isfinite x) }); + ("__builtin_isinf", special [__ "x" []] @@ fun x -> Math { fun_args = (Isinf x) }); + ("__isinf", special [__ "x" []] @@ fun x -> Math { fun_args = (Isinf x) }); + ("__isinff", special [__ "x" []] @@ fun x -> Math { fun_args = (Isinf x) }); + ("__isinfl", special [__ "x" []] @@ fun x -> Math { fun_args = (Isinf x) }); + ("__builtin_isinf_sign", special [__ "x" []] @@ fun x -> Math { fun_args = (Isinf x) }); + ("__builtin_isnan", special [__ "x" []] @@ fun x -> Math { fun_args = (Isnan x) }); + ("__isnan", special [__ "x" []] @@ fun x -> Math { fun_args = (Isnan x) }); + ("__isnanf", special [__ "x" []] @@ fun x -> Math { fun_args = (Isnan x) }); + ("__isnanl", special [__ "x" []] @@ fun x -> Math { fun_args = (Isnan x) }); + ("__builtin_isnormal", special [__ "x" []] @@ fun x -> Math { fun_args = (Isnormal x) }); + ("__builtin_signbit", special [__ "x" []] @@ fun x -> Math { fun_args = (Signbit x) }); + ("__signbit", special [__ "x" []] @@ fun x -> Math { fun_args = (Signbit x) }); + ("__signbitf", special [__ "x" []] @@ fun x -> Math { fun_args = (Signbit x) }); + ("__signbitl", special [__ "x" []] @@ fun x -> Math { fun_args = (Signbit x) }); + ("__builtin_fabs", special [__ "x" []] @@ fun x -> Math { fun_args = (Fabs (FDouble, x)) }); + ("__builtin_fabsf", special [__ "x" []] @@ fun x -> Math { fun_args = (Fabs (FFloat, x)) }); + ("__builtin_fabsl", special [__ "x" []] @@ fun x -> Math { fun_args = (Fabs (FLongDouble, x)) }); + ("__builtin_isgreater", special [__ "x" []; __ "y" []] @@ fun x y -> Math { fun_args = (Isgreater (x,y)) }); + ("__builtin_isgreaterequal", special [__ "x" []; __ "y" []] @@ fun x y -> Math { fun_args = (Isgreaterequal (x,y)) }); + ("__builtin_isless", special [__ "x" []; __ "y" []] @@ fun x y -> Math { fun_args = (Isless (x,y)) }); + ("__builtin_islessequal", special [__ "x" []; __ "y" []] @@ fun x y -> Math { fun_args = (Islessequal (x,y)) }); + ("__builtin_islessgreater", special [__ "x" []; __ "y" []] @@ fun x y -> Math { fun_args = (Islessgreater (x,y)) }); + ("__builtin_isunordered", special [__ "x" []; __ "y" []] @@ fun x y -> Math { fun_args = (Isunordered (x,y)) }); + ("ceil", special [__ "x" []] @@ fun x -> Math { fun_args = (Ceil (FDouble, x)) }); + ("ceilf", special [__ "x" []] @@ fun x -> Math { fun_args = (Ceil (FFloat, x)) }); + ("ceill", special [__ "x" []] @@ fun x -> Math { fun_args = (Ceil (FLongDouble, x)) }); + ("floor", special [__ "x" []] @@ fun x -> Math { fun_args = (Floor (FDouble, x)) }); + ("floorf", special [__ "x" []] @@ fun x -> Math { fun_args = (Floor (FFloat, x)) }); + ("floorl", special [__ "x" []] @@ fun x -> Math { fun_args = (Floor (FLongDouble, x)) }); + ("fabs", special [__ "x" []] @@ fun x -> Math { fun_args = (Fabs (FDouble, x)) }); + ("fabsf", special [__ "x" []] @@ fun x -> Math { fun_args = (Fabs (FFloat, x)) }); + ("fabsl", special [__ "x" []] @@ fun x -> Math { fun_args = (Fabs (FLongDouble, x)) }); + ("fmax", special [__ "x" []; __ "y" []] @@ fun x y -> Math { fun_args = (Fmax (FDouble, x, y)) }); + ("fmaxf", special [__ "x" []; __ "y" []] @@ fun x y -> Math { fun_args = (Fmax (FFloat, x, y)) }); + ("fmaxl", special [__ "x" []; __ "y" []] @@ fun x y -> Math { fun_args = (Fmax (FLongDouble, x, y)) }); + ("fmin", special [__ "x" []; __ "y" []] @@ fun x y -> Math { fun_args = (Fmin (FDouble, x, y)) }); + ("fminf", special [__ "x" []; __ "y" []] @@ fun x y -> Math { fun_args = (Fmin (FFloat, x, y)) }); + ("fminl", special [__ "x" []; __ "y" []] @@ fun x y -> Math { fun_args = (Fmin (FLongDouble, x, y)) }); + ("__builtin_acos", special [__ "x" []] @@ fun x -> Math { fun_args = (Acos (FDouble, x)) }); + ("acos", special [__ "x" []] @@ fun x -> Math { fun_args = (Acos (FDouble, x)) }); + ("acosf", special [__ "x" []] @@ fun x -> Math { fun_args = (Acos (FFloat, x)) }); + ("acosl", special [__ "x" []] @@ fun x -> Math { fun_args = (Acos (FLongDouble, x)) }); + ("__builtin_asin", special [__ "x" []] @@ fun x -> Math { fun_args = (Asin (FDouble, x)) }); + ("asin", special [__ "x" []] @@ fun x -> Math { fun_args = (Asin (FDouble, x)) }); + ("asinf", special [__ "x" []] @@ fun x -> Math { fun_args = (Asin (FFloat, x)) }); + ("asinl", special [__ "x" []] @@ fun x -> Math { fun_args = (Asin (FLongDouble, x)) }); + ("__builtin_atan", special [__ "x" []] @@ fun x -> Math { fun_args = (Atan (FDouble, x)) }); + ("atan", special [__ "x" []] @@ fun x -> Math { fun_args = (Atan (FDouble, x)) }); + ("atanf", special [__ "x" []] @@ fun x -> Math { fun_args = (Atan (FFloat, x)) }); + ("atanl", special [__ "x" []] @@ fun x -> Math { fun_args = (Atan (FLongDouble, x)) }); + ("__builtin_atan2", special [__ "y" []; __ "x" []] @@ fun y x -> Math { fun_args = (Atan2 (FDouble, y, x)) }); + ("atan2", special [__ "y" []; __ "x" []] @@ fun y x -> Math { fun_args = (Atan2 (FDouble, y, x)) }); + ("atan2f", special [__ "y" []; __ "x" []] @@ fun y x -> Math { fun_args = (Atan2 (FFloat, y, x)) }); + ("atan2l", special [__ "y" []; __ "x" []] @@ fun y x -> Math { fun_args = (Atan2 (FLongDouble, y, x)) }); + ("__builtin_cos", special [__ "x" []] @@ fun x -> Math { fun_args = (Cos (FDouble, x)) }); + ("cos", special [__ "x" []] @@ fun x -> Math { fun_args = (Cos (FDouble, x)) }); + ("cosf", special [__ "x" []] @@ fun x -> Math { fun_args = (Cos (FFloat, x)) }); + ("cosl", special [__ "x" []] @@ fun x -> Math { fun_args = (Cos (FLongDouble, x)) }); + ("__builtin_sin", special [__ "x" []] @@ fun x -> Math { fun_args = (Sin (FDouble, x)) }); + ("sin", special [__ "x" []] @@ fun x -> Math { fun_args = (Sin (FDouble, x)) }); + ("sinf", special [__ "x" []] @@ fun x -> Math { fun_args = (Sin (FFloat, x)) }); + ("sinl", special [__ "x" []] @@ fun x -> Math { fun_args = (Sin (FLongDouble, x)) }); + ("__builtin_tan", special [__ "x" []] @@ fun x -> Math { fun_args = (Tan (FDouble, x)) }); + ("tan", special [__ "x" []] @@ fun x -> Math { fun_args = (Tan (FDouble, x)) }); + ("tanf", special [__ "x" []] @@ fun x -> Math { fun_args = (Tan (FFloat, x)) }); + ("tanl", special [__ "x" []] @@ fun x -> Math { fun_args = (Tan (FLongDouble, x)) }); + ("acosh", unknown [drop "x" []]); + ("acoshf", unknown [drop "x" []]); + ("acoshl", unknown [drop "x" []]); + ("asinh", unknown [drop "x" []]); + ("asinhf", unknown [drop "x" []]); + ("asinhl", unknown [drop "x" []]); + ("atanh", unknown [drop "x" []]); + ("atanhf", unknown [drop "x" []]); + ("atanhl", unknown [drop "x" []]); + ("cosh", unknown [drop "x" []]); + ("coshf", unknown [drop "x" []]); + ("coshl", unknown [drop "x" []]); + ("sinh", unknown [drop "x" []]); + ("sinhf", unknown [drop "x" []]); + ("sinhl", unknown [drop "x" []]); + ("tanh", unknown [drop "x" []]); + ("tanhf", unknown [drop "x" []]); + ("tanhl", unknown [drop "x" []]); + ("cbrt", unknown [drop "x" []]); + ("cbrtf", unknown [drop "x" []]); + ("cbrtl", unknown [drop "x" []]); + ("copysign", unknown [drop "x" []; drop "y" []]); + ("copysignf", unknown [drop "x" []; drop "y" []]); + ("copysignl", unknown [drop "x" []; drop "y" []]); + ("erf", unknown [drop "x" []]); + ("erff", unknown [drop "x" []]); + ("erfl", unknown [drop "x" []]); + ("erfc", unknown [drop "x" []]); + ("erfcf", unknown [drop "x" []]); + ("erfcl", unknown [drop "x" []]); + ("exp", unknown [drop "x" []]); + ("expf", unknown [drop "x" []]); + ("expl", unknown [drop "x" []]); + ("exp2", unknown [drop "x" []]); + ("exp2f", unknown [drop "x" []]); + ("exp2l", unknown [drop "x" []]); + ("expm1", unknown [drop "x" []]); + ("expm1f", unknown [drop "x" []]); + ("expm1l", unknown [drop "x" []]); + ("fdim", unknown [drop "x" []; drop "y" []]); + ("fdimf", unknown [drop "x" []; drop "y" []]); + ("fdiml", unknown [drop "x" []; drop "y" []]); + ("fma", unknown [drop "x" []; drop "y" []; drop "z" []]); + ("fmaf", unknown [drop "x" []; drop "y" []; drop "z" []]); + ("fmal", unknown [drop "x" []; drop "y" []; drop "z" []]); + ("fmod", unknown [drop "x" []; drop "y" []]); + ("fmodf", unknown [drop "x" []; drop "y" []]); + ("fmodl", unknown [drop "x" []; drop "y" []]); + ("frexp", unknown [drop "arg" []; drop "exp" [w]]); + ("frexpf", unknown [drop "arg" []; drop "exp" [w]]); + ("frexpl", unknown [drop "arg" []; drop "exp" [w]]); + ("hypot", unknown [drop "x" []; drop "y" []]); + ("hypotf", unknown [drop "x" []; drop "y" []]); + ("hypotl", unknown [drop "x" []; drop "y" []]); + ("ilogb", unknown [drop "x" []]); + ("ilogbf", unknown [drop "x" []]); + ("ilogbl", unknown [drop "x" []]); + ("ldexp", unknown [drop "arg" []; drop "exp" []]); + ("ldexpf", unknown [drop "arg" []; drop "exp" []]); + ("ldexpl", unknown [drop "arg" []; drop "exp" []]); + ("lgamma", unknown ~attrs:[ThreadUnsafe] [drop "x" []]); + ("lgammaf", unknown ~attrs:[ThreadUnsafe] [drop "x" []]); + ("lgammal", unknown ~attrs:[ThreadUnsafe] [drop "x" []]); + ("log", unknown [drop "x" []]); + ("logf", unknown [drop "x" []]); + ("logl", unknown [drop "x" []]); + ("log10", unknown [drop "x" []]); + ("log10f", unknown [drop "x" []]); + ("log10l", unknown [drop "x" []]); + ("log1p", unknown [drop "x" []]); + ("log1pf", unknown [drop "x" []]); + ("log1pl", unknown [drop "x" []]); + ("log2", unknown [drop "x" []]); + ("log2f", unknown [drop "x" []]); + ("log2l", unknown [drop "x" []]); + ("logb", unknown [drop "x" []]); + ("logbf", unknown [drop "x" []]); + ("logbl", unknown [drop "x" []]); + ("rint", unknown [drop "x" []]); + ("rintf", unknown [drop "x" []]); + ("rintl", unknown [drop "x" []]); + ("lrint", unknown [drop "x" []]); + ("lrintf", unknown [drop "x" []]); + ("lrintl", unknown [drop "x" []]); + ("llrint", unknown [drop "x" []]); + ("llrintf", unknown [drop "x" []]); + ("llrintl", unknown [drop "x" []]); + ("round", unknown [drop "x" []]); + ("roundf", unknown [drop "x" []]); + ("roundl", unknown [drop "x" []]); + ("lround", unknown [drop "x" []]); + ("lroundf", unknown [drop "x" []]); + ("lroundl", unknown [drop "x" []]); + ("llround", unknown [drop "x" []]); + ("llroundf", unknown [drop "x" []]); + ("llroundl", unknown [drop "x" []]); + ("modf", unknown [drop "arg" []; drop "iptr" [w]]); + ("modff", unknown [drop "arg" []; drop "iptr" [w]]); + ("modfl", unknown [drop "arg" []; drop "iptr" [w]]); + ("nearbyint", unknown [drop "x" []]); + ("nearbyintf", unknown [drop "x" []]); + ("nearbyintl", unknown [drop "x" []]); + ("nextafter", unknown [drop "from" []; drop "to" []]); + ("nextafterf", unknown [drop "from" []; drop "to" []]); + ("nextafterl", unknown [drop "from" []; drop "to" []]); + ("nexttoward", unknown [drop "from" []; drop "to" []]); + ("nexttowardf", unknown [drop "from" []; drop "to" []]); + ("nexttowardl", unknown [drop "from" []; drop "to" []]); + ("pow", unknown [drop "base" []; drop "exponent" []]); + ("powf", unknown [drop "base" []; drop "exponent" []]); + ("powl", unknown [drop "base" []; drop "exponent" []]); + ("remainder", unknown [drop "x" []; drop "y" []]); + ("remainderf", unknown [drop "x" []; drop "y" []]); + ("remainderl", unknown [drop "x" []; drop "y" []]); + ("remquo", unknown [drop "x" []; drop "y" []; drop "quo" [w]]); + ("remquof", unknown [drop "x" []; drop "y" []; drop "quo" [w]]); + ("remquol", unknown [drop "x" []; drop "y" []; drop "quo" [w]]); + ("scalbn", unknown [drop "arg" []; drop "exp" []]); + ("scalbnf", unknown [drop "arg" []; drop "exp" []]); + ("scalbnl", unknown [drop "arg" []; drop "exp" []]); + ("scalbln", unknown [drop "arg" []; drop "exp" []]); + ("scalblnf", unknown [drop "arg" []; drop "exp" []]); + ("scalblnl", unknown [drop "arg" []; drop "exp" []]); + ("sqrt", special [__ "x" []] @@ fun x -> Math { fun_args = (Sqrt (FDouble, x)) }); + ("sqrtf", special [__ "x" []] @@ fun x -> Math { fun_args = (Sqrt (FFloat, x)) }); + ("sqrtl", special [__ "x" []] @@ fun x -> Math { fun_args = (Sqrt (FLongDouble, x)) }); + ("tgamma", unknown [drop "x" []]); + ("tgammaf", unknown [drop "x" []]); + ("tgammal", unknown [drop "x" []]); + ("trunc", unknown [drop "x" []]); + ("truncf", unknown [drop "x" []]); + ("truncl", unknown [drop "x" []]); + ("j0", unknown [drop "x" []]); (* GNU C Library special function *) + ("j1", unknown [drop "x" []]); (* GNU C Library special function *) + ("jn", unknown [drop "n" []; drop "x" []]); (* GNU C Library special function *) + ("y0", unknown [drop "x" []]); (* GNU C Library special function *) + ("y1", unknown [drop "x" []]); (* GNU C Library special function *) + ("yn", unknown [drop "n" []; drop "x" []]); (* GNU C Library special function *) + ("fegetround", unknown []); + ("fesetround", unknown [drop "round" []]); (* Our float domain is rounding agnostic *) + ("__builtin_fpclassify", unknown [drop "nan" []; drop "infinite" []; drop "normal" []; drop "subnormal" []; drop "zero" []; drop "x" []]); (* TODO: We could do better here *) + ("__builtin_fpclassifyf", unknown [drop "nan" []; drop "infinite" []; drop "normal" []; drop "subnormal" []; drop "zero" []; drop "x" []]); + ("__builtin_fpclassifyl", unknown [drop "nan" []; drop "infinite" []; drop "normal" []; drop "subnormal" []; drop "zero" []; drop "x" []]); + ("__fpclassify", unknown [drop "x" []]); + ("__fpclassifyd", unknown [drop "x" []]); + ("__fpclassifyf", unknown [drop "x" []]); + ("__fpclassifyl", unknown [drop "x" []]); + ] + +let verifier_atomic_var = Cilfacade.create_var (makeGlobalVar "[__VERIFIER_atomic]" intType) +let verifier_atomic = AddrOf (Cil.var (Cilfacade.create_var verifier_atomic_var)) + +(** SV-COMP functions. + Just the ones that require special handling and cannot be stubbed. *) +let svcomp_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ + ("__VERIFIER_atomic_begin", special [] @@ Lock { lock = verifier_atomic; try_ = false; write = true; return_on_success = true }); + ("__VERIFIER_atomic_end", special [] @@ Unlock verifier_atomic); + ("__VERIFIER_nondet_loff_t", unknown []); (* cannot give it in sv-comp.c without including stdlib or similar *) + ("__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 *) + ] + +let ncurses_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ + ("echo", unknown []); + ("noecho", unknown []); + ("wattrset", unknown [drop "win" [r_deep; w_deep]; drop "attrs" []]); + ("endwin", unknown []); + ("wgetch", unknown [drop "win" [r_deep; w_deep]]); + ("wget_wch", unknown [drop "win" [r_deep; w_deep]; drop "wch" [w]]); + ("unget_wch", unknown [drop "wch" []]); + ("wmove", unknown [drop "win" [r_deep; w_deep]; drop "y" []; drop "x" []]); + ("waddch", unknown [drop "win" [r_deep; w_deep]; drop "ch" []]); + ("waddnstr", unknown [drop "win" [r_deep; w_deep]; drop "str" [r]; drop "n" []]); + ("waddnwstr", unknown [drop "win" [r_deep; w_deep]; drop "wstr" [r]; drop "n" []]); + ("wattr_on", unknown [drop "win" [r_deep; w_deep]; drop "attrs" []; drop "opts" []]); (* opts argument currently not used *) + ("wattr_off", unknown [drop "win" [r_deep; w_deep]; drop "attrs" []; drop "opts" []]); (* opts argument currently not used *) + ("wrefresh", unknown [drop "win" [r_deep; w_deep]]); + ("mvprintw", unknown (drop "win" [r_deep; w_deep] :: drop "y" [] :: drop "x" [] :: drop "fmt" [r] :: VarArgs (drop' [r]))); + ("initscr", unknown []); + ("curs_set", unknown [drop "visibility" []]); + ("wtimeout", unknown [drop "win" [r_deep; w_deep]; drop "delay" []]); + ("start_color", unknown []); + ("use_default_colors", unknown []); + ("wclear", unknown [drop "win" [r_deep; w_deep]]); + ("wclrtoeol", unknown [drop "win" [r_deep; w_deep]]); + ("can_change_color", unknown []); + ("init_color", unknown [drop "color" []; drop "red" []; drop "green" []; drop "blue" []]); + ("init_pair", unknown [drop "pair" []; drop "f" [r]; drop "b" [r]]); + ("wbkgd", unknown [drop "win" [r_deep; w_deep]; drop "ch" []]); + ("keyname", unknown [drop "c" []]); + ("newterm", unknown [drop "type" [r]; drop "outfd" [r_deep; w_deep]; drop "infd" [r_deep; w_deep]]); + ("cbreak", unknown []); + ("nonl", unknown []); + ("keypad", unknown [drop "win" [r_deep; w_deep]; drop "bf" []]); + ("set_escdelay", unknown [drop "size" []]); + ("printw", unknown (drop "fmt" [r] :: VarArgs (drop' [r]))); + ("werase", unknown [drop "win" [r_deep; w_deep]]); + ] + +let pcre_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ + ("pcre_compile", unknown [drop "pattern" [r]; drop "options" []; drop "errptr" [w]; drop "erroffset" [w]; drop "tableptr" [r]]); + ("pcre_compile2", unknown [drop "pattern" [r]; drop "options" []; drop "errorcodeptr" [w]; drop "errptr" [w]; drop "erroffset" [w]; drop "tableptr" [r]]); + ("pcre_config", unknown [drop "what" []; drop "where" [w]]); + ("pcre_exec", unknown [drop "code" [r_deep]; drop "extra" [r_deep]; drop "subject" [r]; drop "length" []; drop "startoffset" []; drop "options" []; drop "ovector" [w]; drop "ovecsize" []]); + ("pcre_study", unknown [drop "code" [r_deep]; drop "options" []; drop "errptr" [w]]); + ("pcre_version", unknown []); + ] + +let zlib_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ + ("inflate", unknown [drop "strm" [r_deep; w_deep]; drop "flush" []]); + ("inflateInit2", unknown [drop "strm" [r_deep; w_deep]; drop "windowBits" []]); + ("inflateInit2_", unknown [drop "strm" [r_deep; w_deep]; drop "windowBits" []; drop "version" [r]; drop "stream_size" []]); + ("inflateEnd", unknown [drop "strm" [f_deep]]); + ("deflate", unknown [drop "strm" [r_deep; w_deep]; drop "flush" []]); + ("deflateInit2", unknown [drop "strm" [r_deep; w_deep]; drop "level" []; drop "method" []; drop "windowBits" []; drop "memLevel" []; drop "strategy" []]); + ("deflateInit2_", unknown [drop "strm" [r_deep; w_deep]; drop "level" []; drop "method" []; drop "windowBits" []; drop "memLevel" []; drop "strategy" []; drop "version" [r]; drop "stream_size" []]); + ("deflateEnd", unknown [drop "strm" [f_deep]]); + ("zlibVersion", unknown []); + ("zError", unknown [drop "err" []]); + ("gzopen", unknown [drop "path" [r]; drop "mode" [r]]); + ("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]]); + ] + +let liblzma_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ + ("lzma_code", unknown [drop "strm" [r_deep; w_deep]; drop "action" []]); + ("lzma_auto_decoder", unknown [drop "strm" [r_deep; w_deep]; drop "memlimit" []; drop "flags" []]); + ("lzma_alone_decoder", unknown [drop "strm" [r_deep; w_deep]; drop "memlimit" []]); + ("lzma_stream_decoder", unknown [drop "strm" [r_deep; w_deep]; drop "memlimit" []; drop "flags" []]); + ("lzma_alone_encoder", unknown [drop "strm" [r_deep; w_deep]; drop "options" [r_deep]]); + ("lzma_easy_encoder", unknown [drop "strm" [r_deep; w_deep]; drop "preset" []; drop "check" []]); + ("lzma_end", unknown [drop "strm" [r_deep; w_deep; f_deep]]); + ("lzma_version_string", unknown []); + ("lzma_lzma_preset", unknown [drop "options" [w_deep]; drop "preset" []]); + ] + +let libraries = Hashtbl.of_list [ + ("c", c_descs_list @ math_descs_list); + ("posix", posix_descs_list); + ("pthread", pthread_descs_list); + ("gcc", gcc_descs_list); + ("glibc", glibc_desc_list); + ("linux-userspace", linux_userspace_descs_list); + ("linux-kernel", linux_kernel_descs_list); + ("goblint", goblint_descs_list); + ("sv-comp", svcomp_descs_list); + ("ncurses", ncurses_descs_list); + ("zstd", zstd_descs_list); + ("pcre", pcre_descs_list); + ("zlib", zlib_descs_list); + ("liblzma", liblzma_descs_list); + ] + +let libraries = + Hashtbl.map (fun library descs_list -> + let descs_tbl = Hashtbl.create 113 in + List.iter (fun (name, desc) -> + Hashtbl.modify_opt name (function + | None -> Some desc + | Some _ -> failwith (Format.sprintf "Library function %s specified multiple times in library %s" name library) + ) descs_tbl + ) descs_list; + descs_tbl + ) libraries + +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 + | Some _, Some _ -> failwith (Format.sprintf "Library function %s specified in multiple libraries" name) + | (Some _ as desc), None + | None, (Some _ as desc) -> desc + | None, None -> assert false + ) acc descs_tbl + ) libraries (Hashtbl.create 0) + +let activated_library_descs: (string, LibraryDesc.t) Hashtbl.t ResettableLazy.t = + let union = + Hashtbl.merge (fun _ desc1 desc2 -> + match desc1, desc2 with + | (Some _ as desc), None + | None, (Some _ as desc) -> desc + | _, _ -> assert false + ) + in + ResettableLazy.from_fun (fun () -> + GobConfig.get_string_list "lib.activated" + |> List.unique + |> List.map (Hashtbl.find libraries) + |> List.fold_left union (Hashtbl.create 0) + ) + +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*) + "readdir_r", writesAll;(*unsafe*) + "atoi__extinline", readsAll;(*safe*) + "_IO_getc", writesAll;(*unsafe*) + "pipe", writesAll;(*unsafe*) + "strerror_r", writesAll;(*unsafe*) + "raise", writesAll;(*unsafe*) + "_strlen", readsAll;(*safe*) + "stat__extinline", writesAllButFirst 1 readsAll;(*drop 1*) + "lstat__extinline", writesAllButFirst 1 readsAll;(*drop 1*) + "waitpid", readsAll;(*safe*) + "__open_alias", readsAll;(*safe*) + "__open_2", readsAll;(*safe*) + "ioctl", writesAll;(*unsafe*) + "fstat__extinline", writesAll;(*unsafe*) + "scandir", writes [1;3;4];(*keep [1;3;4]*) + "bindtextdomain", readsAll;(*safe*) + "textdomain", readsAll;(*safe*) + "dcgettext", readsAll;(*safe*) + "putw", readsAll;(*safe*) + "__getdelim", writes [3];(*keep [3]*) + "__h_errno_location", readsAll;(*safe*) + "__fxstat", readsAll;(*safe*) + "openlog", readsAll;(*safe*) + "umask", readsAll;(*safe*) + "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*) + "dup", readsAll; (*safe*) + "__builtin___vsnprintf", writesAllButFirst 3 readsAll; (*drop 3*) + "__builtin___vsnprintf_chk", writesAllButFirst 3 readsAll; (*drop 3*) + "__error", readsAll; (*safe*) + "__maskrune", writesAll; (*unsafe*) + "times", writesAll; (*unsafe*) + "timespec_get", writes [1]; + "__tolower", readsAll; (*safe*) + "signal", writesAll; (*unsafe*) + "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]*) + "remove", readsAll; + "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*) + "munmap", readsAll;(*safe*) + "mmap", 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 + +let kernel_safe_uncalled = Set.String.of_list ["__inittest"; "init_module"; "__exittest"; "cleanup_module"] +let kernel_safe_uncalled_regex = List.map Str.regexp ["__check_.*"] +let is_safe_uncalled fn_name = + Set.String.mem fn_name kernel_safe_uncalled || + 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 -> [] + in + let attrs: LibraryDesc.attr list = + if GobConfig.get_bool "sem.unknown_function.invalidate.globals" then + [InvalidateGlobals] + else + [] + in + (* TODO: remove hack when all classify are migrated *) + if not (CilType.Varinfo.equal f dummyFunDec.svar) && not (use_special f.vname) then ( + 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 + +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 + + +let is_special fv = + if use_special fv.vname then + true + else + match Cilfacade.find_varinfo_fundec fv with + | _ -> false + | exception Not_found -> true diff --git a/src/analyses/libraryFunctions.mli b/src/util/library/libraryFunctions.mli similarity index 100% rename from src/analyses/libraryFunctions.mli rename to src/util/library/libraryFunctions.mli diff --git a/src/util/loopUnrolling.ml b/src/util/loopUnrolling.ml index 62d0f662f3..e1a8ad542b 100644 --- a/src/util/loopUnrolling.ml +++ b/src/util/loopUnrolling.ml @@ -320,13 +320,13 @@ class loopUnrollingCallVisitor = object | Unlock _ | ThreadCreate _ | Assert _ + | Bounded _ | ThreadJoin _ -> raise Found; | _ -> if List.mem "specification" @@ get_string_list "ana.autotune.activated" && get_string "ana.specification" <> "" then ( - match SvcompSpec.of_option () with - | UnreachCall s -> if info.vname = s then raise Found - | _ -> () + if Svcomp.is_error_function' info (SvcompSpec.of_option ()) then + raise Found ); DoChildren ) diff --git a/src/util/sarif.ml b/src/util/sarif.ml index 4374da46d7..7620384cc4 100644 --- a/src/util/sarif.ml +++ b/src/util/sarif.ml @@ -26,7 +26,7 @@ let goblintTool: Tool.t = { fullName = "Goblint static analyser"; informationUri = "https://goblint.in.tum.de/home"; organization = "TUM - i2 and UTartu - SWS"; - version = Version.goblint; + version = Goblint_build_info.version; rules = List.map transformToReportingDescriptor (List.map (fun rule -> rule.name) rules) }; } diff --git a/src/util/server.ml b/src/util/server.ml index e133fb96c3..829ee92ee8 100644 --- a/src/util/server.ml +++ b/src/util/server.ml @@ -264,6 +264,7 @@ let node_locator: Locator.t ResettableLazy.t = let analyze ?(reset=false) (s: t) = Messages.Table.(MH.clear messages_table); + Messages.(Table.MH.clear final_table); Messages.Table.messages_list := []; let file, reparsed = reparse s in if reset then ( @@ -279,6 +280,7 @@ let analyze ?(reset=false) (s: t) = InvariantCil.reset_lazy (); WideningThresholds.reset_lazy (); IntDomain.reset_lazy (); + StringDomain.reset_lazy (); PrecisionUtil.reset_lazy (); ApronDomain.reset_lazy (); AutoTune.reset_lazy (); diff --git a/src/util/std/dune b/src/util/std/dune new file mode 100644 index 0000000000..b074a29937 --- /dev/null +++ b/src/util/std/dune @@ -0,0 +1,18 @@ +(include_subdirs no) + +(library + (name goblint_std) + (public_name goblint.std) + (libraries + batteries.unthreaded + zarith + goblint-cil + fpath + yojson + yaml + qcheck-core) + (preprocess + (pps + ppx_deriving.std + ppx_deriving_hash + ppx_deriving_yojson))) diff --git a/src/util/gobFpath.ml b/src/util/std/gobFpath.ml similarity index 100% rename from src/util/gobFpath.ml rename to src/util/std/gobFpath.ml diff --git a/src/util/gobGc.ml b/src/util/std/gobGc.ml similarity index 100% rename from src/util/gobGc.ml rename to src/util/std/gobGc.ml diff --git a/src/util/gobHashtbl.ml b/src/util/std/gobHashtbl.ml similarity index 65% rename from src/util/gobHashtbl.ml rename to src/util/std/gobHashtbl.ml index c14bafc0cb..c93244eb47 100644 --- a/src/util/gobHashtbl.ml +++ b/src/util/std/gobHashtbl.ml @@ -1,9 +1,5 @@ module Pretty = GoblintCil.Pretty -let magic_stats h = - let h: _ Hashtbl.t = Obj.magic h in (* Batteries Hashtables don't expose stats yet...: https://github.com/ocaml-batteries-team/batteries-included/pull/1079 *) - Hashtbl.stats h - let pretty_statistics () (s: Hashtbl.statistics) = let load_factor = float_of_int s.num_bindings /. float_of_int s.num_buckets in Pretty.dprintf "bindings=%d buckets=%d max_length=%d histo=%a load=%f" s.num_bindings s.num_buckets s.max_bucket_length (Pretty.docList (Pretty.dprintf "%d")) (Array.to_list s.bucket_histogram) load_factor diff --git a/src/util/gobList.ml b/src/util/std/gobList.ml similarity index 100% rename from src/util/gobList.ml rename to src/util/std/gobList.ml diff --git a/src/util/gobOption.ml b/src/util/std/gobOption.ml similarity index 100% rename from src/util/gobOption.ml rename to src/util/std/gobOption.ml diff --git a/src/util/gobPretty.ml b/src/util/std/gobPretty.ml similarity index 100% rename from src/util/gobPretty.ml rename to src/util/std/gobPretty.ml diff --git a/src/domains/myCheck.ml b/src/util/std/gobQCheck.ml similarity index 91% rename from src/domains/myCheck.ml rename to src/util/std/gobQCheck.ml index 98583cd2c3..12809d5b46 100644 --- a/src/domains/myCheck.ml +++ b/src/util/std/gobQCheck.ml @@ -56,7 +56,4 @@ struct let gens = List.map gen arbs in let shrinks = List.map shrink arbs in make ~shrink:(Shrink.sequence shrinks) (Gen.sequence gens) - - open GoblintCil - let varinfo: Cil.varinfo arbitrary = QCheck.always (Cil.makeGlobalVar "arbVar" Cil.voidPtrType) (* S TODO: how to generate this *) end diff --git a/src/util/gobRef.ml b/src/util/std/gobRef.ml similarity index 100% rename from src/util/gobRef.ml rename to src/util/std/gobRef.ml diff --git a/src/util/gobResult.ml b/src/util/std/gobResult.ml similarity index 100% rename from src/util/gobResult.ml rename to src/util/std/gobResult.ml diff --git a/src/util/gobSys.ml b/src/util/std/gobSys.ml similarity index 100% rename from src/util/gobSys.ml rename to src/util/std/gobSys.ml diff --git a/src/util/gobUnix.ml b/src/util/std/gobUnix.ml similarity index 100% rename from src/util/gobUnix.ml rename to src/util/std/gobUnix.ml diff --git a/src/util/gobYaml.ml b/src/util/std/gobYaml.ml similarity index 69% rename from src/util/gobYaml.ml rename to src/util/std/gobYaml.ml index a4f3e597aa..131daaaebb 100644 --- a/src/util/gobYaml.ml +++ b/src/util/std/gobYaml.ml @@ -1,3 +1,14 @@ +let to_string' ?(len=65535 * 4) ?encoding ?scalar_style ?layout_style v = + assert (len >= 1); + let rec aux len = + match Yaml.to_string ~len ?encoding ?scalar_style ?layout_style v with + | Ok _ as o -> o + | Error (`Msg ("scalar failed" | "doc_end failed")) when len < Sys.max_string_length / 2 -> + aux (len * 2) + | Error (`Msg _) as e -> e + in + aux len + include Yaml.Util include GobResult.Syntax diff --git a/src/util/gobYojson.ml b/src/util/std/gobYojson.ml similarity index 100% rename from src/util/gobYojson.ml rename to src/util/std/gobYojson.ml diff --git a/src/util/gobZ.ml b/src/util/std/gobZ.ml similarity index 100% rename from src/util/gobZ.ml rename to src/util/std/gobZ.ml diff --git a/src/util/std/goblint_std.ml b/src/util/std/goblint_std.ml new file mode 100644 index 0000000000..0d548cac08 --- /dev/null +++ b/src/util/std/goblint_std.ml @@ -0,0 +1,25 @@ +(** OCaml library extensions which are completely independent of Goblint. *) + +(** {1 Standard library} + + OCaml standard library extensions which are not provided by {!Batteries}. *) + +module GobGc = GobGc +module GobHashtbl = GobHashtbl +module GobList = GobList +module GobRef = GobRef +module GobResult = GobResult +module GobOption = GobOption +module GobSys = GobSys +module GobUnix = GobUnix + +(** {1 Other libraries} + + External library extensions. *) + +module GobFpath = GobFpath +module GobPretty = GobPretty +module GobQCheck = GobQCheck +module GobYaml = GobYaml +module GobYojson = GobYojson +module GobZ = GobZ diff --git a/src/util/terminationPreprocessing.ml b/src/util/terminationPreprocessing.ml new file mode 100644 index 0000000000..9023a68f8a --- /dev/null +++ b/src/util/terminationPreprocessing.ml @@ -0,0 +1,76 @@ +open GoblintCil +(* module Z = Big_int_Z *) + +module VarToStmt = Map.Make(CilType.Varinfo) (* maps varinfos (= loop counter variable) to the statement of the corresponding loop*) + +let counter_ikind = IULongLong +let counter_typ = TInt (counter_ikind, []) +let min_int_exp = + (* Currently only tested for IInt type, which is signed *) + if Cil.isSigned counter_ikind then + Const(CInt(Z.shift_left Cilint.mone_cilint ((bytesSizeOfInt counter_ikind)*8-1), IInt, None)) + else + Const(CInt(Z.zero, counter_ikind, None)) + +class loopCounterVisitor lc (fd : fundec) = object(self) + inherit nopCilVisitor + + (* Counter of variables inserted for termination *) + val mutable vcounter = ref 0 + + method! vfunc _ = + vcounter := 0; + DoChildren + + method! vstmt s = + + let specialFunction name = + { svar = makeGlobalVar name (TFun(voidType, Some [("exp", counter_typ, [])], false,[])); + smaxid = 0; + slocals = []; + sformals = []; + sbody = mkBlock []; + smaxstmtid = None; + sallstmts = []; + } in + + let f_bounded = Lval (var (specialFunction "__goblint_bounded").svar) in + + (* Yields increment expression e + 1 where the added "1" that has the same type as the expression [e]. + Using Cil.increm instead does not work for non-[IInt] ikinds. *) + let increment_expression e = + let et = typeOf e in + let bop = PlusA in + let one = Const (CInt (Cilint.one_cilint, counter_ikind, None)) in + constFold false (BinOp(bop, e, one, et)) in + + let action s = match s.skind with + | Loop (b, loc, eloc, _, _) -> + let vname = "term" ^ string_of_int loc.line ^ "_" ^ string_of_int loc.column ^ "_id" ^ (string_of_int !vcounter) in + incr vcounter; + let v = Cil.makeLocalVar fd vname counter_typ in (*Not tested for incremental mode*) + let lval = Lval (Var v, NoOffset) in + let init_stmt = mkStmtOneInstr @@ Set (var v, min_int_exp, loc, eloc) in + let inc_stmt = mkStmtOneInstr @@ Set (var v, increment_expression lval, loc, eloc) in + let exit_stmt = mkStmtOneInstr @@ Call (None, f_bounded, [lval], loc, locUnknown) in + b.bstmts <- exit_stmt :: inc_stmt :: b.bstmts; + lc := VarToStmt.add (v: varinfo) (s: stmt) !lc; + let nb = mkBlock [init_stmt; mkStmt s.skind] in + s.skind <- Block nb; + s + | Goto (sref, l) -> + let goto_jmp_stmt = sref.contents.skind in + let loc_stmt = Cil.get_stmtLoc goto_jmp_stmt in + if CilType.Location.compare l loc_stmt >= 0 then ( + (* is pos if first loc is greater -> below the second loc *) + (* problem: the program might not terminate! *) + let open Cilfacade in + let current = FunLocH.find_opt funs_with_upjumping_gotos fd in + let current = BatOption.default (LocSet.create 13) current in + LocSet.replace current l (); + FunLocH.replace funs_with_upjumping_gotos fd current; + ); + s + | _ -> s + in ChangeDoChildrenPost (s, action); +end diff --git a/src/util/tracing/dune b/src/util/tracing/dune new file mode 100644 index 0000000000..7e37139567 --- /dev/null +++ b/src/util/tracing/dune @@ -0,0 +1,9 @@ +(include_subdirs no) + +(library + (name goblint_tracing) + (public_name goblint.tracing) + (libraries + goblint_std + goblint-cil + goblint_build_info)) diff --git a/src/util/tracing.ml b/src/util/tracing/goblint_tracing.ml similarity index 83% rename from src/util/tracing.ml rename to src/util/tracing/goblint_tracing.ml index f9dff2c2cf..0e5580b036 100644 --- a/src/util/tracing.ml +++ b/src/util/tracing/goblint_tracing.ml @@ -4,13 +4,14 @@ * large domains we output. The original code generated the document object * even when the subsystem is not activated. *) +open Goblint_std open GoblintCil open Pretty module Strs = Set.Make (String) -let tracing = ConfigProfile.profile = "trace" +let tracing = Goblint_build_info.dune_profile = "trace" let current_loc = ref locUnknown let next_loc = ref locUnknown @@ -67,13 +68,6 @@ let trace sys ?var fmt = gtrace true printtrace sys var ignore fmt * c: continue/normal print w/o indent-change *) -let tracel sys ?var fmt = - let loc = !current_loc in - let docloc sys doc = - printtrace sys (dprintf "(%a)@?" CilType.Location.pretty loc ++ indent 2 doc); - in - gtrace true docloc sys var ~loc ignore fmt - let tracei (sys:string) ?var ?(subsys=[]) fmt = let f sys d = printtrace sys d; traceIndent () in let g () = activate sys subsys in @@ -85,13 +79,3 @@ let traceu sys fmt = let f sys d = printtrace sys d; traceOutdent () in let g () = deactivate sys in gtrace true f sys None g fmt - - -let traceli sys ?var ?(subsys=[]) fmt = - let loc = !current_loc in - let g () = activate sys subsys in - let docloc sys doc: unit = - printtrace sys (dprintf "(%a)" CilType.Location.pretty loc ++ indent 2 doc); - traceIndent () - in - gtrace true docloc sys var ~loc g fmt diff --git a/src/util/wideningTokens.ml b/src/util/wideningTokens.ml index 75f0e4f81d..1816de90c7 100644 --- a/src/util/wideningTokens.ml +++ b/src/util/wideningTokens.ml @@ -179,7 +179,7 @@ struct let combine_env ctx r fe f args fc es f_ask = lift_fun ctx lift' S.combine_env (fun p -> p r fe f args fc (D.unlift es) f_ask) (* TODO: use tokens from es *) let combine_assign ctx r fe f args fc es f_ask = lift_fun ctx lift' S.combine_assign (fun p -> p r fe f args fc (D.unlift es) f_ask) (* TODO: use tokens from es *) - let threadenter ctx lval f args = lift_fun ctx (fun l ts -> List.map (Fun.flip lift' ts) l) S.threadenter ((|>) args % (|>) f % (|>) lval) - let threadspawn ctx lval f args fctx = lift_fun ctx lift' S.threadspawn ((|>) (conv fctx) % (|>) args % (|>) f % (|>) lval) + let threadenter ctx ~multiple lval f args = lift_fun ctx (fun l ts -> List.map (Fun.flip lift' ts) l) (S.threadenter ~multiple) ((|>) args % (|>) f % (|>) lval ) + let threadspawn ctx ~multiple lval f args fctx = lift_fun ctx lift' (S.threadspawn ~multiple) ((|>) (conv fctx) % (|>) args % (|>) f % (|>) lval) let event ctx e octx = lift_fun ctx lift' S.event ((|>) (conv octx) % (|>) e) end diff --git a/src/version.ml b/src/version.ml deleted file mode 100644 index cbe2874608..0000000000 --- a/src/version.ml +++ /dev/null @@ -1,16 +0,0 @@ -let release = "%%VERSION_NUM%%" -let release_commit = "%%VCS_COMMIT_ID%%" - -let goblint = - let commit = ConfigVersion.version in - if BatString.starts_with release "%" then - commit - else ( - let commit = - if commit = "n/a" then (* released archive has no .git *) - release_commit - else - commit - in - Format.sprintf "%s (%s)" release commit - ) diff --git a/src/witness/myARG.ml b/src/witness/myARG.ml index 62c705f5b1..373a66d3d6 100644 --- a/src/witness/myARG.ml +++ b/src/witness/myARG.ml @@ -141,7 +141,7 @@ struct let equal_node_context _ _ = failwith "StackNode: equal_node_context" end -module Stack (Cfg:CfgForward) (Arg: S): +module Stack (Arg: S with module Edge = InlineEdge): S with module Node = StackNode (Arg.Node) and module Edge = Arg.Edge = struct module Node = StackNode (Arg.Node) @@ -156,45 +156,30 @@ struct | n :: stack -> let cfgnode = Arg.Node.cfgnode n in match cfgnode with - | Function _ -> (* TODO: can this be done without Cfg? *) + | Function _ -> (* TODO: can this be done without cfgnode? *) begin match stack with (* | [] -> failwith "StackArg.next: return stack empty" *) | [] -> [] (* main return *) | call_n :: call_stack -> - let call_cfgnode = Arg.Node.cfgnode call_n in let call_next = - Cfg.next call_cfgnode + Arg.next call_n (* filter because infinite loops starting with function call will have another Neg(1) edge from the head *) - |> List.filter (fun (locedges, to_node) -> - List.exists (function - | (_, Proc _) -> true - | (_, _) -> false - ) locedges + |> List.filter_map (fun (edge, to_n) -> + match edge with + | InlinedEdge _ -> Some to_n + | _ -> None ) in - begin match call_next with - | [] -> failwith "StackArg.next: call next empty" - | [(_, return_node)] -> - begin match Arg.Node.move_opt call_n return_node with - (* TODO: Is it possible to have a calling node without a returning node? *) - (* | None -> [] *) - | None -> failwith "StackArg.next: no return node" - | Some return_n -> - (* TODO: Instead of next & filter, construct unique return_n directly. Currently edge missing. *) - Arg.next n - |> List.filter (fun (edge, to_n) -> - (* let to_cfgnode = Arg.Node.cfgnode to_n in - MyCFG.Node.equal to_cfgnode return_node *) - Arg.Node.equal_node_context to_n return_n - ) - |> List.map (fun (edge, to_n) -> - let to_n' = to_n :: call_stack in - (edge, to_n') - ) - end - | _ :: _ :: _ -> failwith "StackArg.next: call next ambiguous" - end + Arg.next n + |> List.filter_map (fun (edge, to_n) -> + if BatList.mem_cmp Arg.Node.compare to_n call_next then ( + let to_n' = to_n :: call_stack in + Some (edge, to_n') + ) + else + None + ) end | _ -> let+ (edge, to_n) = Arg.next n in @@ -320,7 +305,7 @@ struct let rec next_opt' n = match n with - | Statement {sid; skind=If (_, _, _, loc, eloc); _} when GobConfig.get_bool "witness.uncil" -> (* TODO: use elocs instead? *) + | Statement {sid; skind=If (_, _, _, loc, eloc); _} when GobConfig.get_bool "witness.graphml.uncil" -> (* TODO: use elocs instead? *) 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? *) @@ -373,7 +358,7 @@ 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.uncil" -> (* TODO: use eloc instead? *) + | Statement {skind=If (_, _, _, loc, eloc); _} when GobConfig.get_bool "witness.graphml.uncil" -> (* TODO: use eloc instead? *) 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 match Arg.next if_true_next_n, Arg.next if_false_next_n with diff --git a/src/witness/observerAnalysis.ml b/src/witness/observerAnalysis.ml index c8d8563909..e8daf56155 100644 --- a/src/witness/observerAnalysis.ml +++ b/src/witness/observerAnalysis.ml @@ -76,8 +76,8 @@ struct step_ctx ctx let startstate v = `Lifted Automaton.initial - let threadenter ctx lval f args = [D.top ()] - let threadspawn ctx lval f args fctx = ctx.local + let threadenter ctx ~multiple lval f args = [D.top ()] + let threadspawn ctx ~multiple lval f args fctx = ctx.local let exitstate v = D.top () end diff --git a/src/witness/svcomp.ml b/src/witness/svcomp.ml index a5a572d1c2..6d22a51166 100644 --- a/src/witness/svcomp.ml +++ b/src/witness/svcomp.ml @@ -8,7 +8,7 @@ module Specification = SvcompSpec module type Task = sig val file: Cil.file - val specification: Specification.t + val specification: Specification.multi module Cfg: MyCFG.CfgBidir end @@ -16,11 +16,15 @@ end let task: (module Task) option ref = ref None +let is_error_function' f spec = + List.exists (function + | Specification.UnreachCall f_spec -> f.vname = f_spec + | _ -> false + ) spec + let is_error_function f = let module Task = (val (Option.get !task)) in - match Task.specification with - | UnreachCall f_spec -> f.vname = f_spec - | _ -> false + is_error_function' f Task.specification (* TODO: unused, but should be used? *) let is_special_function f = @@ -28,11 +32,7 @@ let is_special_function f = 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_verifier = match f.vname with | fname when String.starts_with fname "__VERIFIER" -> true - | fname -> - let module Task = (val (Option.get !task)) in - match Task.specification with - | UnreachCall f_spec -> fname = f_spec - | _ -> false + | fname -> is_error_function f in is_svcomp && is_verifier @@ -52,6 +52,11 @@ struct | UnreachCall _ -> "unreach-call" | NoOverflow -> "no-overflow" | NoDataRace -> "no-data-race" (* not yet in SV-COMP/Benchexec *) + | Termination -> "termination" + | ValidFree -> "valid-free" + | ValidDeref -> "valid-deref" + | ValidMemtrack -> "valid-memtrack" + | ValidMemcleanup -> "valid-memcleanup" in "false(" ^ result_spec ^ ")" | Unknown -> "unknown" @@ -71,9 +76,9 @@ sig val is_sink: Arg.Node.t -> bool end -module StackTaskResult (Cfg:MyCFG.CfgForward) (TaskResult: TaskResult) = +module StackTaskResult (TaskResult: TaskResult with module Arg.Edge = MyARG.InlineEdge) = struct - module Arg = MyARG.Stack (Cfg) (TaskResult.Arg) + module Arg = MyARG.Stack (TaskResult.Arg) let result = TaskResult.result diff --git a/src/witness/svcompSpec.ml b/src/witness/svcompSpec.ml index 464c170251..c7601ef637 100644 --- a/src/witness/svcompSpec.ml +++ b/src/witness/svcompSpec.ml @@ -6,11 +6,20 @@ type t = | UnreachCall of string | NoDataRace | NoOverflow + | Termination + | ValidFree + | ValidDeref + | ValidMemtrack + | ValidMemcleanup + +type multi = t list let of_string s = let s = String.strip s in - let regexp = Str.regexp "CHECK( init(main()), LTL(G ! \\(.*\\)) )" in - if Str.string_match regexp s 0 then + let regexp_single = Str.regexp "CHECK( init(main()), LTL(G \\(.*\\)) )" in + let regexp_negated = Str.regexp "CHECK( init(main()), LTL(G ! \\(.*\\)) )" in + let regexp_finally = Str.regexp "CHECK( init(main()), LTL(F \\(.*\\)) )" in + if Str.string_match regexp_negated s 0 then let global_not = Str.matched_group 1 s in if global_not = "data-race" then NoDataRace @@ -23,9 +32,36 @@ let of_string s = UnreachCall f else failwith "Svcomp.Specification.of_string: unknown global not expression" + else if Str.string_match regexp_single s 0 then + let global = Str.matched_group 1 s in + if global = "valid-free" then + ValidFree + else if global = "valid-deref" then + ValidDeref + else if global = "valid-memtrack" then + ValidMemtrack + else if global = "valid-memcleanup" then + ValidMemcleanup + else + failwith "Svcomp.Specification.of_string: unknown global expression" + else if Str.string_match regexp_finally s 0 then + let finally = Str.matched_group 1 s in + if finally = "end" then + Termination + else + failwith "Svcomp.Specification.of_string: unknown finally expression" else failwith "Svcomp.Specification.of_string: unknown expression" +let of_string s: multi = + List.filter_map (fun line -> + let line = String.strip line in + if line = "" then + None + else + Some (of_string line) + ) (String.split_on_char '\n' s) + let of_file path = let s = BatFile.with_file_in path BatIO.read_all in of_string s @@ -38,9 +74,32 @@ let of_option () = of_string s let to_string spec = - let global_not = match spec with - | UnreachCall f -> "call(" ^ f ^ "())" - | NoDataRace -> "data-race" - | NoOverflow -> "overflow" + let module Prop = struct + type prop = F | G + let string_of_prop = function + | F -> "F" + | G -> "G" + end in - "CHECK( init(main()), LTL(G ! " ^ global_not ^ ") )" + let open Prop in + let print_output prop spec_str is_neg = + let prop = string_of_prop prop in + if is_neg then + Printf.sprintf "CHECK( init(main()), LTL(%s ! %s) )" prop spec_str + else + Printf.sprintf "CHECK( init(main()), LTL(%s %s) )" prop spec_str + in + let prop, spec_str, is_neg = match spec with + | UnreachCall f -> G, "call(" ^ f ^ "())", true + | NoDataRace -> G, "data-race", true + | NoOverflow -> G, "overflow", true + | ValidFree -> G, "valid-free", false + | ValidDeref -> G, "valid-deref", false + | ValidMemtrack -> G, "valid-memtrack", false + | ValidMemcleanup -> G, "valid-memcleanup", false + | Termination -> F, "end", false + in + print_output prop spec_str is_neg + +let to_string spec = + String.concat "\n" (List.map to_string spec) diff --git a/src/witness/witness.ml b/src/witness/witness.ml index aff9a01383..29f337a668 100644 --- a/src/witness/witness.ml +++ b/src/witness/witness.ml @@ -13,8 +13,8 @@ let write_file filename (module Task:Task) (module TaskResult:WitnessTaskResult) let module Invariant = WitnessUtil.Invariant (Task) in let module TaskResult = - (val if get_bool "witness.stack" then - (module StackTaskResult (Task.Cfg) (TaskResult) : WitnessTaskResult) + (val if get_bool "witness.graphml.stack" then + (module StackTaskResult (TaskResult) : WitnessTaskResult) else (module TaskResult) ) @@ -24,7 +24,7 @@ let write_file filename (module Task:Task) (module TaskResult:WitnessTaskResult) struct (* type node = N.t type edge = TaskResult.Arg.Edge.t *) - let minwitness = get_bool "witness.minimize" + let minwitness = get_bool "witness.graphml.minimize" let is_interesting_real from_node edge to_node = (* TODO: don't duplicate this logic with write_node, write_edge *) (* startlines aren't currently interesting because broken, see below *) @@ -58,12 +58,12 @@ let write_file filename (module Task:Task) (module TaskResult:WitnessTaskResult) let module N = Arg.Node in let module GML = XmlGraphMlWriter in let module GML = - (val match get_string "witness.id" with + (val match get_string "witness.graphml.id" with | "node" -> (module ArgNodeGraphMlWriter (N) (GML) : GraphMlWriter with type node = N.t) | "enumerate" -> (module EnumerateNodeGraphMlWriter (N) (GML)) - | _ -> failwith "witness.id: illegal value" + | _ -> failwith "witness.graphml.id: illegal value" ) in let module GML = DeDupGraphMlWriter (N) (GML) in @@ -118,7 +118,7 @@ let write_file filename (module Task:Task) (module TaskResult:WitnessTaskResult) | Result.Unknown -> "unknown_witness" ); GML.write_metadata g "sourcecodelang" "C"; - GML.write_metadata g "producer" (Printf.sprintf "Goblint (%s)" Version.goblint); + GML.write_metadata g "producer" (Printf.sprintf "Goblint (%s)" Goblint_build_info.version); GML.write_metadata g "specification" (Svcomp.Specification.to_string Task.specification); let programfile = (Node.location (N.cfgnode main_entry)).file in GML.write_metadata g "programfile" programfile; @@ -297,15 +297,48 @@ struct module ArgTool = ArgTools.Make (R) module NHT = ArgTool.NHT - let determine_result entrystates (module Task:Task): (module WitnessTaskResult) = - let module Arg = (val ArgTool.create entrystates) in + module type BiArgInvariant = + sig + include ArgTools.BiArg + val find_invariant: Node.t -> Invariant.t + end - let find_invariant (n, c, i) = - let context = {Invariant.default_context with path = Some i} in - ask_local (n, c) (Invariant context) + let determine_result entrystates (module Task:Task) (spec: Svcomp.Specification.t): (module WitnessTaskResult) = + let module Arg: BiArgInvariant = + (val if GobConfig.get_bool "witness.graphml.enabled" then ( + let module Arg = (val ArgTool.create entrystates) in + let module Arg = + struct + include Arg + + let find_invariant (n, c, i) = + let context = {Invariant.default_context with path = Some i} in + ask_local (n, c) (Invariant context) + end + in + (module Arg: BiArgInvariant) + ) + else ( + let module Arg = + struct + module Node = ArgTool.Node + module Edge = MyARG.InlineEdge + let next _ = [] + let prev _ = [] + let find_invariant _ = Invariant.none + let main_entry = + let lvar = WitnessUtil.find_main_entry entrystates in + (fst lvar, snd lvar, -1) + let iter_nodes f = f main_entry + let query _ q = Queries.Result.top q + end + in + (module Arg: BiArgInvariant) + ) + ) in - match Task.specification with + match spec with | UnreachCall _ -> (* error function name is globally known through Svcomp.task *) let is_unreach_call = @@ -324,7 +357,7 @@ struct struct module Arg = Arg let result = Result.True - let invariant = find_invariant + let invariant = Arg.find_invariant let is_violation _ = false let is_sink _ = false end @@ -332,13 +365,13 @@ struct (module TaskResult:WitnessTaskResult) ) else ( let is_violation = function - | FunctionEntry f, _, _ when Svcomp.is_error_function f.svar -> true - | _, _, _ -> false + | FunctionEntry f when Svcomp.is_error_function f.svar -> true + | _ -> false in (* redefine is_violation to shift violations back by one, so enterFunction __VERIFIER_error is never used *) let is_violation n = Arg.next n - |> List.exists (fun (_, to_n) -> is_violation to_n) + |> List.exists (fun (_, to_n) -> is_violation (Arg.Node.cfgnode to_n)) in let violations = (* TODO: fold_nodes?s *) @@ -363,7 +396,7 @@ struct struct module Arg = Arg let result = Result.Unknown - let invariant = find_invariant + let invariant = Arg.find_invariant let is_violation = is_violation let is_sink = is_sink end @@ -377,7 +410,7 @@ struct let module TaskResult = struct module Arg = PathArg - let result = Result.False (Some Task.specification) + let result = Result.False (Some spec) let invariant _ = Invariant.none let is_violation = is_violation let is_sink _ = false @@ -442,6 +475,36 @@ struct in (module TaskResult:WitnessTaskResult) ) + | Termination -> + let module TrivialArg = + struct + include Arg + let next _ = [] + end + in + if not !AnalysisState.svcomp_may_not_terminate then + let module TaskResult = + struct + module Arg = TrivialArg + let result = Result.True + let invariant _ = Invariant.none + let is_violation _ = false + let is_sink _ = false + end + in + (module TaskResult:WitnessTaskResult) + else ( + let module TaskResult = + struct + module Arg = TrivialArg + let result = Result.Unknown + let invariant _ = Invariant.none + let is_violation _ = false + let is_sink _ = false + end + in + (module TaskResult:WitnessTaskResult) + ) | NoOverflow -> let module TrivialArg = struct @@ -454,7 +517,7 @@ struct struct module Arg = Arg let result = Result.True - let invariant = find_invariant + let invariant = Arg.find_invariant let is_violation _ = false let is_sink _ = false end @@ -472,18 +535,159 @@ struct in (module TaskResult:WitnessTaskResult) ) + | ValidFree -> + let module TrivialArg = + struct + include Arg + let next _ = [] + end + in + if not !AnalysisState.svcomp_may_invalid_free then ( + let module TaskResult = + struct + module Arg = Arg + let result = Result.True + let invariant _ = Invariant.none + let is_violation _ = false + let is_sink _ = false + end + in + (module TaskResult:WitnessTaskResult) + ) else ( + let module TaskResult = + struct + module Arg = TrivialArg + let result = Result.Unknown + let invariant _ = Invariant.none + let is_violation _ = false + let is_sink _ = false + end + in + (module TaskResult:WitnessTaskResult) + ) + | ValidDeref -> + let module TrivialArg = + struct + include Arg + let next _ = [] + end + in + if not !AnalysisState.svcomp_may_invalid_deref then ( + let module TaskResult = + struct + module Arg = Arg + let result = Result.True + let invariant _ = Invariant.none + let is_violation _ = false + let is_sink _ = false + end + in + (module TaskResult:WitnessTaskResult) + ) else ( + let module TaskResult = + struct + module Arg = TrivialArg + let result = Result.Unknown + let invariant _ = Invariant.none + let is_violation _ = false + let is_sink _ = false + end + in + (module TaskResult:WitnessTaskResult) + ) + | ValidMemtrack -> + let module TrivialArg = + struct + include Arg + let next _ = [] + end + in + if not !AnalysisState.svcomp_may_invalid_memtrack then ( + let module TaskResult = + struct + module Arg = Arg + let result = Result.True + let invariant _ = Invariant.none + let is_violation _ = false + let is_sink _ = false + end + in + (module TaskResult:WitnessTaskResult) + ) else ( + let module TaskResult = + struct + module Arg = TrivialArg + let result = Result.Unknown + let invariant _ = Invariant.none + let is_violation _ = false + let is_sink _ = false + end + in + (module TaskResult:WitnessTaskResult) + ) + | ValidMemcleanup -> + let module TrivialArg = + struct + include Arg + let next _ = [] + end + in + if not !AnalysisState.svcomp_may_invalid_memcleanup then ( + let module TaskResult = + struct + module Arg = Arg + let result = Result.True + let invariant _ = Invariant.none + let is_violation _ = false + let is_sink _ = false + end + in + (module TaskResult:WitnessTaskResult) + ) else ( + let module TaskResult = + struct + module Arg = TrivialArg + let result = Result.Unknown + let invariant _ = Invariant.none + let is_violation _ = false + let is_sink _ = false + end + in + (module TaskResult:WitnessTaskResult) + ) + let determine_result entrystates (module Task:Task): (module WitnessTaskResult) = + Task.specification + |> List.fold_left (fun acc spec -> + let module TaskResult = (val determine_result entrystates (module Task) spec) in + match acc with + | None -> Some (module TaskResult: WitnessTaskResult) + | Some (module Acc: WitnessTaskResult) -> + match Acc.result, TaskResult.result with + (* keep old violation/unknown *) + | False _, True + | False _, Unknown + | Unknown, True -> Some (module Acc: WitnessTaskResult) + (* use new violation/unknown *) + | True, False _ + | Unknown, False _ + | True, Unknown -> Some (module TaskResult: WitnessTaskResult) + (* both same, arbitrarily keep old *) + | True, True -> Some (module Acc: WitnessTaskResult) + | Unknown, Unknown -> Some (module Acc: WitnessTaskResult) + | False _, False _ -> failwith "multiple violations" + ) None + |> Option.get let write entrystates = let module Task = (val (BatOption.get !task)) in - let module TaskResult = (val (Timing.wrap "determine" (determine_result entrystates) (module Task))) in + let module TaskResult = (val (Timing.wrap "sv-comp result" (determine_result entrystates) (module Task))) in print_task_result (module TaskResult); - (* TODO: use witness.enabled elsewhere as well *) - if get_bool "witness.enabled" && (TaskResult.result <> Result.Unknown || get_bool "witness.unknown") then ( - let witness_path = get_string "witness.path" in - Timing.wrap "write" (write_file witness_path (module Task)) (module TaskResult) + if get_bool "witness.graphml.enabled" && (TaskResult.result <> Result.Unknown || get_bool "witness.graphml.unknown") then ( + let witness_path = get_string "witness.graphml.path" in + Timing.wrap "graphml witness" (write_file witness_path (module Task)) (module TaskResult) ) let write entrystates = @@ -491,16 +695,20 @@ struct | Some false -> print_svcomp_result "ERROR (verify)" | _ -> if get_string "witness.yaml.validate" <> "" then ( - if !YamlWitness.cnt_refuted > 0 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)) - else if !YamlWitness.cnt_unconfirmed > 0 then + | _ when !YamlWitness.cnt_unconfirmed > 0 -> print_svcomp_result (Result.to_string Unknown) - else + | _ -> write entrystates ) else write entrystates - - let write entrystates = - Timing.wrap "witness" write entrystates end diff --git a/src/witness/witnessConstraints.ml b/src/witness/witnessConstraints.ml index 2ce16a5997..8dedf77a79 100644 --- a/src/witness/witnessConstraints.ml +++ b/src/witness/witnessConstraints.ml @@ -199,7 +199,7 @@ struct let r = Dom.bindings a in List.map (fun (x,v) -> (Dom.singleton x v, b)) r - let threadenter ctx lval f args = + let threadenter ctx ~multiple lval f args = let g xs x' ys = let ys' = List.map (fun y -> let yr = step ctx.prev_node (ctx.context ()) x' (ThreadEntry (lval, f, args)) (nosync x') in (* threadenter called on before-sync state *) @@ -208,10 +208,10 @@ struct in ys' @ xs in - fold' ctx Spec.threadenter (fun h -> h lval f args) g [] - let threadspawn ctx lval f args fctx = + fold' ctx (Spec.threadenter ~multiple) (fun h -> h lval f args) g [] + let threadspawn ctx ~multiple lval f args fctx = let fd1 = Dom.choose_key (fst fctx.local) in - map ctx Spec.threadspawn (fun h -> h lval f args (conv fctx fd1)) + map ctx (Spec.threadspawn ~multiple) (fun h -> h lval f args (conv fctx fd1)) let sync ctx reason = fold'' ctx Spec.sync (fun h -> h reason) (fun (a, async) x r a' -> diff --git a/src/witness/yamlWitness.ml b/src/witness/yamlWitness.ml index c7106a57b5..253ee5eecd 100644 --- a/src/witness/yamlWitness.ml +++ b/src/witness/yamlWitness.ml @@ -17,7 +17,7 @@ struct (* let yaml_conf: Yaml.value = Json_repr.convert (module Json_repr.Yojson) (module Json_repr.Ezjsonm) (!GobConfig.json_conf) in *) let producer: Producer.t = { name = "Goblint"; - version = Version.goblint; + version = Goblint_build_info.version; command_line = Some GobSys.command_line; } @@ -25,7 +25,7 @@ struct let uuid = Uuidm.v4_gen uuid_random_state () in let creation_time = TimeUtil.iso8601_now () in { - format_version = "0.1"; + format_version = GobConfig.get_string "witness.yaml.format-version"; uuid = Uuidm.to_string uuid; creation_time; producer; @@ -91,6 +91,29 @@ struct metadata = metadata ~task (); } + let location_invariant' ~location ~(invariant): InvariantSet.Invariant.t = { + invariant_type = LocationInvariant { + location; + value = invariant; + format = "c_expression"; + }; + } + + let loop_invariant' ~location ~(invariant): InvariantSet.Invariant.t = { + invariant_type = LoopInvariant { + location; + value = invariant; + format = "c_expression"; + }; + } + + let invariant_set ~task ~(invariants): Entry.t = { + entry_type = InvariantSet { + content = invariants; + }; + metadata = metadata ~task (); + } + let target ~uuid ~type_ ~(file_name): Target.t = { uuid; type_; @@ -113,9 +136,9 @@ struct let precondition_loop_invariant_certificate ~target ~(certification): Entry.t = { entry_type = PreconditionLoopInvariantCertificate { - target; - certification; - }; + target; + certification; + }; metadata = metadata (); } end @@ -124,8 +147,7 @@ let yaml_entries_to_file yaml_entries file = let yaml = `A yaml_entries in (* Yaml_unix.to_file_exn file yaml *) (* to_file/to_string uses a fixed-size buffer... *) - (* estimate how big it should be + extra in case empty *) - let text = match Yaml.to_string ~len:(List.length yaml_entries * 4096 + 2048) yaml with + let text = match GobYaml.to_string' yaml with | Ok text -> text | Error (`Msg m) -> failwith ("Yaml.to_string: " ^ m) in @@ -134,6 +156,9 @@ let yaml_entries_to_file yaml_entries file = let entry_type_enabled entry_type = List.mem entry_type (GobConfig.get_string_list "witness.yaml.entry-types") +let invariant_type_enabled invariant_type = + List.mem invariant_type (GobConfig.get_string_list "witness.yaml.invariant-types") + module Make (R: ResultQuery.SpecSysSol2) = struct open R @@ -145,6 +170,16 @@ struct module FCMap = BatHashtbl.Make (Printable.Prod (CilType.Fundec) (Spec.C)) type con_inv = {node: Node.t; context: Spec.C.t; invariant: Invariant.t; state: Spec.D.t} + (* TODO: fix location hack *) + module LH = BatHashtbl.Make (CilType.Location) + let location2nodes: Node.t list LH.t Lazy.t = lazy ( + let lh = LH.create 113 in + NH.iter (fun n _ -> + LH.modify_def [] (Node.location n) (List.cons n) lh + ) (Lazy.force nh); + lh + ) + let write () = let input_files = GobConfig.get_string_list "files" in let data_model = match GobConfig.get_string "exp.architecture" with @@ -208,16 +243,21 @@ struct (* Generate location invariants (without precondition) *) let entries = if entry_type_enabled YamlWitnessType.LocationInvariant.entry_type then ( - NH.fold (fun n local acc -> - let loc = Node.location n in - if is_invariant_node n then ( - let lvals = local_lvals n local in - match R.ask_local_node n ~local (Invariant {Invariant.default_context with lvals}) with + LH.fold (fun loc ns acc -> + if List.exists is_invariant_node ns then ( + let inv = List.fold_left (fun acc n -> + let local = try NH.find (Lazy.force nh) n with Not_found -> Spec.D.bot () in + let lvals = local_lvals n local in + Invariant.(acc || R.ask_local_node n ~local (Invariant {Invariant.default_context with lvals})) [@coverage off] (* bisect_ppx cannot handle redefined (||) *) + ) (Invariant.bot ()) ns + in + match inv with | `Lifted inv -> + let fundec = Node.find_fundec (List.hd ns) in (* TODO: fix location hack *) + let location_function = fundec.svar.vname in + let location = Entry.location ~location:loc ~location_function in let invs = WitnessUtil.InvariantExp.process_exp inv in List.fold_left (fun acc inv -> - let location_function = (Node.find_fundec n).svar.vname in - let location = Entry.location ~location:loc ~location_function in let invariant = Entry.invariant (CilType.Exp.show inv) in let entry = Entry.location_invariant ~task ~location ~invariant in entry :: acc @@ -227,7 +267,7 @@ struct ) else acc - ) (Lazy.force nh) entries + ) (Lazy.force location2nodes) entries ) else entries @@ -236,15 +276,20 @@ struct (* Generate loop invariants (without precondition) *) let entries = if entry_type_enabled YamlWitnessType.LoopInvariant.entry_type then ( - NH.fold (fun n local acc -> - let loc = Node.location n in - if WitnessInvariant.emit_loop_head && WitnessUtil.NH.mem WitnessInvariant.loop_heads n then ( - match R.ask_local_node n ~local (Invariant Invariant.default_context) with + LH.fold (fun loc ns acc -> + if WitnessInvariant.emit_loop_head && List.exists (WitnessUtil.NH.mem WitnessInvariant.loop_heads) ns then ( + let inv = List.fold_left (fun acc n -> + let local = try NH.find (Lazy.force nh) n with Not_found -> Spec.D.bot () in + Invariant.(acc || R.ask_local_node n ~local (Invariant Invariant.default_context)) [@coverage off] (* bisect_ppx cannot handle redefined (||) *) + ) (Invariant.bot ()) ns + in + match inv with | `Lifted inv -> + let fundec = Node.find_fundec (List.hd ns) in (* TODO: fix location hack *) + let location_function = fundec.svar.vname in + let location = Entry.location ~location:loc ~location_function in let invs = WitnessUtil.InvariantExp.process_exp inv in List.fold_left (fun acc inv -> - let location_function = (Node.find_fundec n).svar.vname in - let location = Entry.location ~location:loc ~location_function in let invariant = Entry.invariant (CilType.Exp.show inv) in let entry = Entry.loop_invariant ~task ~location ~invariant in entry :: acc @@ -254,7 +299,7 @@ struct ) else acc - ) (Lazy.force nh) entries + ) (Lazy.force location2nodes) entries ) else entries @@ -385,6 +430,84 @@ struct entries in + (* Generate invariant set *) + let entries = + if entry_type_enabled YamlWitnessType.InvariantSet.entry_type then ( + let invariants = [] in + + (* Generate location invariants *) + let invariants = + if invariant_type_enabled YamlWitnessType.InvariantSet.LocationInvariant.invariant_type then ( + LH.fold (fun loc ns acc -> + if List.exists is_invariant_node ns then ( + let inv = List.fold_left (fun acc n -> + let local = try NH.find (Lazy.force nh) n with Not_found -> Spec.D.bot () in + let lvals = local_lvals n local in + Invariant.(acc || R.ask_local_node n ~local (Invariant {Invariant.default_context with lvals})) [@coverage off] (* bisect_ppx cannot handle redefined (||) *) + ) (Invariant.bot ()) ns + in + match inv with + | `Lifted inv -> + let fundec = Node.find_fundec (List.hd ns) in (* TODO: fix location hack *) + let location_function = fundec.svar.vname in + let location = Entry.location ~location:loc ~location_function in + let invs = WitnessUtil.InvariantExp.process_exp inv in + List.fold_left (fun acc inv -> + let invariant = CilType.Exp.show inv in + let invariant = Entry.location_invariant' ~location ~invariant in + invariant :: acc + ) acc invs + | `Bot | `Top -> (* TODO: 0 for bot (dead code)? *) + acc + ) + else + acc + ) (Lazy.force location2nodes) invariants + ) + else + invariants + in + + (* Generate loop invariants *) + let invariants = + if invariant_type_enabled YamlWitnessType.InvariantSet.LoopInvariant.invariant_type then ( + LH.fold (fun loc ns acc -> + if WitnessInvariant.emit_loop_head && List.exists (WitnessUtil.NH.mem WitnessInvariant.loop_heads) ns then ( + let inv = List.fold_left (fun acc n -> + let local = try NH.find (Lazy.force nh) n with Not_found -> Spec.D.bot () in + Invariant.(acc || R.ask_local_node n ~local (Invariant Invariant.default_context)) [@coverage off] (* bisect_ppx cannot handle redefined (||) *) + ) (Invariant.bot ()) ns + in + match inv with + | `Lifted inv -> + let fundec = Node.find_fundec (List.hd ns) in (* TODO: fix location hack *) + let location_function = fundec.svar.vname in + let location = Entry.location ~location:loc ~location_function in + let invs = WitnessUtil.InvariantExp.process_exp inv in + List.fold_left (fun acc inv -> + let invariant = CilType.Exp.show inv in + let invariant = Entry.loop_invariant' ~location ~invariant in + invariant :: acc + ) acc invs + | `Bot | `Top -> (* TODO: 0 for bot (dead code)? *) + acc + ) + else + acc + ) (Lazy.force location2nodes) invariants + ) + else + invariants + in + + let invariants = List.rev invariants in + let entry = Entry.invariant_set ~task ~invariants in + entry :: entries + ) + else + entries + in + 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" [ @@ -392,6 +515,9 @@ struct ]; yaml_entries_to_file yaml_entries (Fpath.v (GobConfig.get_string "witness.yaml.path")) + + let write () = + Timing.wrap "yaml witness" write () end @@ -636,6 +762,48 @@ struct None in + let validate_invariant_set (invariant_set: YamlWitnessType.InvariantSet.t) = + + let validate_location_invariant (location_invariant: YamlWitnessType.InvariantSet.LocationInvariant.t) = + let loc = loc_of_location location_invariant.location in + let inv = location_invariant.value in + + match Locator.find_opt locator loc with + | Some lvars -> + ignore (validate_lvars_invariant ~entry_certificate:None ~loc ~lvars inv) + | None -> + incr cnt_error; + M.warn ~category:Witness ~loc:(CilLocation loc) "couldn't locate invariant: %s" inv; + in + + let validate_loop_invariant (loop_invariant: YamlWitnessType.InvariantSet.LoopInvariant.t) = + let loc = loc_of_location loop_invariant.location in + let inv = loop_invariant.value in + + match Locator.find_opt loop_locator loc with + | Some lvars -> + ignore (validate_lvars_invariant ~entry_certificate:None ~loc ~lvars inv) + | None -> + incr cnt_error; + M.warn ~category:Witness ~loc:(CilLocation loc) "couldn't locate invariant: %s" inv; + in + + let validate_invariant (invariant: YamlWitnessType.InvariantSet.Invariant.t) = + let target_type = YamlWitnessType.InvariantSet.InvariantType.invariant_type invariant.invariant_type in + match invariant_type_enabled target_type, invariant.invariant_type with + | true, LocationInvariant x -> + validate_location_invariant x + | true, LoopInvariant x -> + validate_loop_invariant x + | false, (LocationInvariant _ | LoopInvariant _) -> + incr cnt_disabled; + M.info_noloc ~category:Witness "disabled invariant of type %s" target_type; + in + + List.iter validate_invariant invariant_set.content; + None + in + match entry_type_enabled target_type, entry.entry_type with | true, LocationInvariant x -> validate_location_invariant x @@ -643,7 +811,9 @@ struct validate_loop_invariant x | true, PreconditionLoopInvariant x -> validate_precondition_loop_invariant x - | false, (LocationInvariant _ | LoopInvariant _ | PreconditionLoopInvariant _) -> + | true, InvariantSet x -> + validate_invariant_set x + | false, (LocationInvariant _ | LoopInvariant _ | PreconditionLoopInvariant _ | InvariantSet _) -> incr cnt_disabled; M.info_noloc ~category:Witness "disabled entry of type %s" target_type; None diff --git a/src/witness/yamlWitnessType.ml b/src/witness/yamlWitnessType.ml index 3390c1e3ab..de9fa151d8 100644 --- a/src/witness/yamlWitnessType.ml +++ b/src/witness/yamlWitnessType.ml @@ -242,6 +242,103 @@ struct {location; loop_invariant; precondition} end +module InvariantSet = +struct + module LoopInvariant = + struct + type t = { + location: Location.t; + value: string; + format: string; + } + + let invariant_type = "loop_invariant" + + let to_yaml' {location; value; format} = + [ + ("location", Location.to_yaml location); + ("value", `String value); + ("format", `String format); + ] + + let of_yaml y = + let open GobYaml in + let+ location = y |> find "location" >>= Location.of_yaml + and+ value = y |> find "value" >>= to_string + and+ format = y |> find "format" >>= to_string in + {location; value; format} + end + + module LocationInvariant = + struct + include LoopInvariant + + let invariant_type = "location_invariant" + end + + (* TODO: could maybe use GADT, but adds ugly existential layer to entry type pattern matching *) + module InvariantType = + struct + type t = + | LocationInvariant of LocationInvariant.t + | LoopInvariant of LoopInvariant.t + + let invariant_type = function + | LocationInvariant _ -> LocationInvariant.invariant_type + | LoopInvariant _ -> LoopInvariant.invariant_type + + let to_yaml' = function + | LocationInvariant x -> LocationInvariant.to_yaml' x + | LoopInvariant x -> LoopInvariant.to_yaml' x + + let of_yaml y = + let open GobYaml in + let* invariant_type = y |> find "type" >>= to_string in + if invariant_type = LocationInvariant.invariant_type then + let+ x = y |> LocationInvariant.of_yaml in + LocationInvariant x + else if invariant_type = LoopInvariant.invariant_type then + let+ x = y |> LoopInvariant.of_yaml in + LoopInvariant x + else + Error (`Msg "type") + end + + module Invariant = + struct + type t = { + invariant_type: InvariantType.t; + } + + let to_yaml {invariant_type} = + `O [ + ("invariant", `O ([ + ("type", `String (InvariantType.invariant_type invariant_type)); + ] @ InvariantType.to_yaml' invariant_type) + ) + ] + + let of_yaml y = + let open GobYaml in + let+ invariant_type = y |> find "invariant" >>= InvariantType.of_yaml in + {invariant_type} + end + + type t = { + content: Invariant.t list; + } + + let entry_type = "invariant_set" + + let to_yaml' {content} = + [("content", `A (List.map Invariant.to_yaml content))] + + let of_yaml y = + let open GobYaml in + let+ content = y |> find "content" >>= list >>= list_map Invariant.of_yaml in + {content} +end + module Target = struct type t = { @@ -326,6 +423,7 @@ struct | PreconditionLoopInvariant of PreconditionLoopInvariant.t | LoopInvariantCertificate of LoopInvariantCertificate.t | PreconditionLoopInvariantCertificate of PreconditionLoopInvariantCertificate.t + | InvariantSet of InvariantSet.t let entry_type = function | LocationInvariant _ -> LocationInvariant.entry_type @@ -334,6 +432,7 @@ struct | PreconditionLoopInvariant _ -> PreconditionLoopInvariant.entry_type | LoopInvariantCertificate _ -> LoopInvariantCertificate.entry_type | PreconditionLoopInvariantCertificate _ -> PreconditionLoopInvariantCertificate.entry_type + | InvariantSet _ -> InvariantSet.entry_type let to_yaml' = function | LocationInvariant x -> LocationInvariant.to_yaml' x @@ -342,6 +441,7 @@ struct | PreconditionLoopInvariant x -> PreconditionLoopInvariant.to_yaml' x | LoopInvariantCertificate x -> LoopInvariantCertificate.to_yaml' x | PreconditionLoopInvariantCertificate x -> PreconditionLoopInvariantCertificate.to_yaml' x + | InvariantSet x -> InvariantSet.to_yaml' x let of_yaml y = let open GobYaml in @@ -364,6 +464,9 @@ struct else if entry_type = PreconditionLoopInvariantCertificate.entry_type then let+ x = y |> PreconditionLoopInvariantCertificate.of_yaml in PreconditionLoopInvariantCertificate x + else if entry_type = InvariantSet.entry_type then + let+ x = y |> InvariantSet.of_yaml in + InvariantSet x else Error (`Msg "entry_type") end diff --git a/sv-comp/archive.sh b/sv-comp/archive.sh index 9bab49f70d..5d8605dc70 100755 --- a/sv-comp/archive.sh +++ b/sv-comp/archive.sh @@ -4,7 +4,7 @@ make clean -git tag -m "SV-COMP 2023" svcomp23 +git tag -m "SV-COMP 2024" svcomp24 dune build --profile=release src/goblint.exe rm -f goblint @@ -18,7 +18,7 @@ cp _opam/share/apron/lib/libapron.so lib/ cp _opam/share/apron/lib/liboctD.so lib/ cp _opam/share/apron/lib/libboxD.so lib/ cp _opam/share/apron/lib/libpolkaMPQ.so lib/ -cp _opam/.opam-switch/sources/apron/COPYING lib/LICENSE.APRON +wget -O lib/LICENSE.APRON https://raw.githubusercontent.com/antoinemine/apron/master/COPYING # done outside to ensure archive contains goblint/ directory cd .. @@ -32,7 +32,8 @@ zip goblint/sv-comp/goblint.zip \ goblint/lib/libboxD.so \ goblint/lib/libpolkaMPQ.so \ goblint/lib/LICENSE.APRON \ - goblint/conf/svcomp23.json \ + goblint/conf/svcomp24.json \ + goblint/conf/svcomp24-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/sv-comp/sv-comp-run-no-overflow.py b/sv-comp/sv-comp-run-no-overflow.py index a3461b1a64..88ee2c0e53 100755 --- a/sv-comp/sv-comp-run-no-overflow.py +++ b/sv-comp/sv-comp-run-no-overflow.py @@ -13,7 +13,7 @@ OVERVIEW = False # with True Goblint isn't executed # TODO: don't hard-code specification -GOBLINT_COMMAND = "./goblint --conf conf/svcomp21.json --set ana.specification ./tests/sv-comp/no-overflow.prp --set witness.path {witness_filename} {code_filename} -v" +GOBLINT_COMMAND = "./goblint --conf conf/svcomp21.json --set ana.specification ./tests/sv-comp/no-overflow.prp --set witness.graphml.path {witness_filename} {code_filename} -v" TIMEOUT = 10 # with some int that's Goblint timeout for single execution START = 1 EXIT_ON_ERROR = True diff --git a/sv-comp/sv-comp-run.py b/sv-comp/sv-comp-run.py index af7cada051..977aa69ab6 100755 --- a/sv-comp/sv-comp-run.py +++ b/sv-comp/sv-comp-run.py @@ -13,7 +13,7 @@ OVERVIEW = False # with True Goblint isn't executed # TODO: don't hard-code specification -GOBLINT_COMMAND = "./goblint --conf conf/svcomp21.json --set ana.specification ./tests/sv-comp/unreach-call-__VERIFIER_error.prp --set witness.path {witness_filename} {code_filename}" +GOBLINT_COMMAND = "./goblint --conf conf/svcomp21.json --set ana.specification ./tests/sv-comp/unreach-call-__VERIFIER_error.prp --set witness.graphml.path {witness_filename} {code_filename}" TIMEOUT = 30 # with some int that's Goblint timeout for single execution START = 1 EXIT_ON_ERROR = True diff --git a/tests/regression/00-sanity/37-long-double.c b/tests/regression/00-sanity/37-long-double.c new file mode 100644 index 0000000000..01c9b8bb9b --- /dev/null +++ b/tests/regression/00-sanity/37-long-double.c @@ -0,0 +1,4 @@ +int main() { + long double l = 0.0L; + return (int)l; +} diff --git a/tests/regression/00-sanity/37-long-double.t b/tests/regression/00-sanity/37-long-double.t new file mode 100644 index 0000000000..567db89e5a --- /dev/null +++ b/tests/regression/00-sanity/37-long-double.t @@ -0,0 +1,6 @@ +Testing that there isn't a warning about treating long double as double constant. + $ goblint 37-long-double.c + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 3 + dead: 0 + total lines: 3 diff --git a/tests/regression/00-sanity/51-base-special-lval.c b/tests/regression/00-sanity/51-base-special-lval.c new file mode 100644 index 0000000000..8f74a1babe --- /dev/null +++ b/tests/regression/00-sanity/51-base-special-lval.c @@ -0,0 +1,13 @@ +// Making sure special function lval is not invalidated recursively +#include + +extern int * anIntPlease(); +int main() { + int x = 0; + int *p = &x; + p = anIntPlease(); + + __goblint_check(x == 0); + + return 0; +} diff --git a/tests/regression/01-cpa/33-asserts.c b/tests/regression/01-cpa/33-asserts.c index f8bf6c3132..26efad44fc 100644 --- a/tests/regression/01-cpa/33-asserts.c +++ b/tests/regression/01-cpa/33-asserts.c @@ -26,14 +26,14 @@ int main(){ check(j==6); // assert UNKNOWN unknown(&k); - assume(k==4); // TODO? assert SUCCESS + assume(k==4); check(k==4); // assert SUCCESS unknown(&k); - assume(k+1==n); // TODO? FAIL + assume(k+1==n); - assume(n==5); // TODO? NOWARN - assert(0); // NOWARN + assume(n==5); // contradiction + assert(0); // NOWARN (unreachable) return 0; } \ No newline at end of file diff --git a/tests/regression/02-base/88-string-ptrs-limited.c b/tests/regression/02-base/88-string-ptrs-limited.c index ab8b2fefe8..c4f39dc711 100644 --- a/tests/regression/02-base/88-string-ptrs-limited.c +++ b/tests/regression/02-base/88-string-ptrs-limited.c @@ -1,4 +1,4 @@ -//PARAM: --enable ana.base.limit-string-addresses +//PARAM: --set ana.base.strings.domain flat #include #include diff --git a/tests/regression/02-base/89-string-ptrs-not-limited.c b/tests/regression/02-base/89-string-ptrs-not-limited.c index 96100d230d..ab30e21fd8 100644 --- a/tests/regression/02-base/89-string-ptrs-not-limited.c +++ b/tests/regression/02-base/89-string-ptrs-not-limited.c @@ -1,4 +1,4 @@ -//PARAM: --disable ana.base.limit-string-addresses +//PARAM: --set ana.base.strings.domain disjoint #include #include diff --git a/tests/regression/03-practical/31-zstd-cctxpool-blobs.c b/tests/regression/03-practical/31-zstd-cctxpool-blobs.c new file mode 100644 index 0000000000..40e448eb22 --- /dev/null +++ b/tests/regression/03-practical/31-zstd-cctxpool-blobs.c @@ -0,0 +1,29 @@ +#include +#include + +struct ZSTD_CCtx_s { + int bmi2; +}; + +typedef struct ZSTD_CCtx_s ZSTD_CCtx; + +typedef struct { + ZSTD_CCtx* cctx[1]; +} ZSTDMT_CCtxPool; + +void *t_fun(void *arg) { + return NULL; +} + +int main() { + pthread_t id; + pthread_create(&id, NULL, t_fun, NULL); // enter multithreaded + + ZSTDMT_CCtxPool* const cctxPool = calloc(1, sizeof(ZSTDMT_CCtxPool)); + cctxPool->cctx[0] = malloc(sizeof(ZSTD_CCtx)); + if (!cctxPool->cctx[0]) // TODO NOWARN + __goblint_check(1); // TODO reachable + else + __goblint_check(1); // TODO reachable + return 0; +} diff --git a/tests/regression/03-practical/32-smtprc-tid.c b/tests/regression/03-practical/32-smtprc-tid.c new file mode 100644 index 0000000000..1d4810ee2e --- /dev/null +++ b/tests/regression/03-practical/32-smtprc-tid.c @@ -0,0 +1,38 @@ +#include +#include + +int threads_total = 4; +pthread_t *tids; + +void *cleaner(void *arg) { + while (1) { + for (int i = 0; i < threads_total; i++) { + if (tids[i]) { // RACE! + if (!pthread_join(tids[i], NULL)) // RACE! + tids[i] = 0; // RACE! + } + } + } + return NULL; +} + +void *thread(int i) { // wrong argument type is important + tids[i] = pthread_self(); // RACE! + return NULL; +} + +int main() { + pthread_t tid; + tids = malloc(threads_total * sizeof(pthread_t)); + + for(int i = 0; i < threads_total; i++) + tids[i] = 0; + + pthread_create(&tid, NULL, cleaner, NULL); + + for(int i = 0; i < threads_total; i++) { + pthread_create(&tid, NULL, thread, (int *)i); // cast is important + } + + return 0; +} diff --git a/tests/regression/04-mutex/49-type-invariants.c b/tests/regression/04-mutex/49-type-invariants.c index 4f69986478..e6ac17dcd9 100644 --- a/tests/regression/04-mutex/49-type-invariants.c +++ b/tests/regression/04-mutex/49-type-invariants.c @@ -1,4 +1,3 @@ -//PARAM: --enable ana.race.direct-arithmetic #include #include diff --git a/tests/regression/04-mutex/49-type-invariants.t b/tests/regression/04-mutex/49-type-invariants.t index 3d3f7442ef..4b8118eec1 100644 --- a/tests/regression/04-mutex/49-type-invariants.t +++ b/tests/regression/04-mutex/49-type-invariants.t @@ -1,47 +1,45 @@ $ goblint --enable warn.deterministic --enable ana.race.direct-arithmetic --enable allglobs 49-type-invariants.c - [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (49-type-invariants.c:22:3-22:21) - [Warning][Race] Memory location s.field (race with conf. 110): (49-type-invariants.c:9:10-9:11) - write with [mhp:{tid=[main]; created={[main, t_fun@49-type-invariants.c:21:3-21:40#top]}}, thread:[main]] (conf. 100) (exp: & tmp->field) (49-type-invariants.c:22:3-22:21) - read with [mhp:{tid=[main, t_fun@49-type-invariants.c:21:3-21:40#top]}, thread:[main, t_fun@49-type-invariants.c:21:3-21:40#top]] (conf. 110) (exp: & s.field) (49-type-invariants.c:12:3-12:23) + [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (49-type-invariants.c:21:3-21:21) + [Warning][Race] Memory location s.field (race with conf. 110): (49-type-invariants.c:8:10-8:11) + write with [mhp:{tid=[main]; created={[main, t_fun@49-type-invariants.c:20:3-20:40#top]}}, thread:[main]] (conf. 100) (exp: & tmp->field) (49-type-invariants.c:21:3-21:21) + read with [mhp:{tid=[main, t_fun@49-type-invariants.c:20:3-20:40#top]}, thread:[main, t_fun@49-type-invariants.c:20:3-20:40#top]] (conf. 110) (exp: & s.field) (49-type-invariants.c:11:3-11:23) [Info][Race] Memory locations race summary: safe: 1 vulnerable: 0 unsafe: 1 total memory locations: 2 [Success][Race] Memory location (struct S).field (safe): - write with [mhp:{tid=[main]; created={[main, t_fun@49-type-invariants.c:21:3-21:40#top]}}, thread:[main]] (conf. 100) (exp: & tmp->field) (49-type-invariants.c:22:3-22:21) + write with [mhp:{tid=[main]; created={[main, t_fun@49-type-invariants.c:20:3-20:40#top]}}, thread:[main]] (conf. 100) (exp: & tmp->field) (49-type-invariants.c:21:3-21:21) [Info][Deadcode] Logical lines of code (LLoC) summary: live: 7 dead: 0 total lines: 7 - [Info][Unsound] Unknown address in {&tmp} has escaped. (49-type-invariants.c:22:3-22:21) - [Info][Unsound] Unknown value in {?} could be an escaped pointer address! (49-type-invariants.c:22:3-22:21) - [Info][Unsound] Write to unknown address: privatization is unsound. (49-type-invariants.c:22:3-22:21) - [Info][Imprecise] INVALIDATING ALL GLOBALS! (49-type-invariants.c:22:3-22:21) - [Info][Imprecise] Invalidating expressions: AddrOf(Var(s, NoOffset)) (49-type-invariants.c:22:3-22:21) - [Info][Imprecise] Invalidating expressions: AddrOf(Var(tmp, NoOffset)) (49-type-invariants.c:22:3-22:21) - [Error][Imprecise][Unsound] Function definition missing for getS (49-type-invariants.c:22:3-22:21) + [Info][Unsound] Write to unknown address: privatization is unsound. (49-type-invariants.c:21:3-21:21) + [Info][Imprecise] INVALIDATING ALL GLOBALS! (49-type-invariants.c:21:3-21:21) + [Info][Imprecise] Invalidating expressions: AddrOf(Var(s, NoOffset)) (49-type-invariants.c:21:3-21:21) + [Info][Imprecise] Invalidating expressions: AddrOf(Var(tmp, NoOffset)) (49-type-invariants.c:21:3-21:21) + [Error][Imprecise][Unsound] Function definition missing for getS (49-type-invariants.c:21:3-21:21) + [Error][Imprecise][Unsound] Function definition missing $ goblint --enable warn.deterministic --disable ana.race.direct-arithmetic --enable allglobs 49-type-invariants.c - [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (49-type-invariants.c:22:3-22:21) - [Warning][Race] Memory location s.field (race with conf. 110): (49-type-invariants.c:9:10-9:11) - write with [mhp:{tid=[main]; created={[main, t_fun@49-type-invariants.c:21:3-21:40#top]}}, thread:[main]] (conf. 100) (exp: & tmp->field) (49-type-invariants.c:22:3-22:21) - read with [mhp:{tid=[main, t_fun@49-type-invariants.c:21:3-21:40#top]}, thread:[main, t_fun@49-type-invariants.c:21:3-21:40#top]] (conf. 110) (exp: & s.field) (49-type-invariants.c:12:3-12:23) + [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (49-type-invariants.c:21:3-21:21) + [Warning][Race] Memory location s.field (race with conf. 110): (49-type-invariants.c:8:10-8:11) + write with [mhp:{tid=[main]; created={[main, t_fun@49-type-invariants.c:20:3-20:40#top]}}, thread:[main]] (conf. 100) (exp: & tmp->field) (49-type-invariants.c:21:3-21:21) + read with [mhp:{tid=[main, t_fun@49-type-invariants.c:20:3-20:40#top]}, thread:[main, t_fun@49-type-invariants.c:20:3-20:40#top]] (conf. 110) (exp: & s.field) (49-type-invariants.c:11:3-11:23) [Info][Race] Memory locations race summary: safe: 1 vulnerable: 0 unsafe: 1 total memory locations: 2 [Success][Race] Memory location (struct S).field (safe): - write with [mhp:{tid=[main]; created={[main, t_fun@49-type-invariants.c:21:3-21:40#top]}}, thread:[main]] (conf. 100) (exp: & tmp->field) (49-type-invariants.c:22:3-22:21) + write with [mhp:{tid=[main]; created={[main, t_fun@49-type-invariants.c:20:3-20:40#top]}}, thread:[main]] (conf. 100) (exp: & tmp->field) (49-type-invariants.c:21:3-21:21) [Info][Deadcode] Logical lines of code (LLoC) summary: live: 7 dead: 0 total lines: 7 - [Info][Unsound] Unknown address in {&tmp} has escaped. (49-type-invariants.c:22:3-22:21) - [Info][Unsound] Unknown value in {?} could be an escaped pointer address! (49-type-invariants.c:22:3-22:21) - [Info][Unsound] Write to unknown address: privatization is unsound. (49-type-invariants.c:22:3-22:21) - [Info][Imprecise] INVALIDATING ALL GLOBALS! (49-type-invariants.c:22:3-22:21) - [Info][Imprecise] Invalidating expressions: AddrOf(Var(s, NoOffset)) (49-type-invariants.c:22:3-22:21) - [Info][Imprecise] Invalidating expressions: AddrOf(Var(tmp, NoOffset)) (49-type-invariants.c:22:3-22:21) - [Error][Imprecise][Unsound] Function definition missing for getS (49-type-invariants.c:22:3-22:21) + [Info][Unsound] Write to unknown address: privatization is unsound. (49-type-invariants.c:21:3-21:21) + [Info][Imprecise] INVALIDATING ALL GLOBALS! (49-type-invariants.c:21:3-21:21) + [Info][Imprecise] Invalidating expressions: AddrOf(Var(s, NoOffset)) (49-type-invariants.c:21:3-21:21) + [Info][Imprecise] Invalidating expressions: AddrOf(Var(tmp, NoOffset)) (49-type-invariants.c:21:3-21:21) + [Error][Imprecise][Unsound] Function definition missing for getS (49-type-invariants.c:21:3-21:21) + [Error][Imprecise][Unsound] Function definition missing diff --git a/tests/regression/04-mutex/58-pthread-lock-return.c b/tests/regression/04-mutex/58-pthread-lock-return.c new file mode 100644 index 0000000000..3e2a05c94e --- /dev/null +++ b/tests/regression/04-mutex/58-pthread-lock-return.c @@ -0,0 +1,118 @@ +// PARAM: --disable sem.lock.fail +#include + +int g_mutex = 0; +pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; + +int g_rwlock = 0; +pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER; + +// OS X has no spinlock +#ifndef __APPLE__ +int g_spin = 0; +pthread_spinlock_t spin; +#endif + +void *t_fun(void *arg) { + if (!pthread_mutex_lock(&mutex)) { + __goblint_check(1); // reachable + g_mutex++; // NORACE + pthread_mutex_unlock(&mutex); + } + else { + __goblint_check(0); // NOWARN (unreachable) + } + + if (!pthread_mutex_trylock(&mutex)) { + __goblint_check(1); // reachable + g_mutex++; // NORACE + pthread_mutex_unlock(&mutex); + } + else { + __goblint_check(1); // reachable + } + + if (!pthread_rwlock_wrlock(&mutex)) { + __goblint_check(1); // reachable + g_rwlock++; // NORACE + pthread_rwlock_unlock(&mutex); + } + else { + __goblint_check(0); // NOWARN (unreachable) + } + + if (!pthread_rwlock_trywrlock(&mutex)) { + __goblint_check(1); // reachable + g_rwlock++; // NORACE + pthread_rwlock_unlock(&mutex); + } + else { + __goblint_check(1); // reachable + } + + if (!pthread_rwlock_rdlock(&mutex)) { + __goblint_check(1); // reachable + g_rwlock++; // NORACE + pthread_rwlock_unlock(&mutex); + } + else { + __goblint_check(0); // NOWARN (unreachable) + } + + if (!pthread_rwlock_tryrdlock(&mutex)) { + __goblint_check(1); // reachable + g_rwlock++; // NORACE + pthread_rwlock_unlock(&mutex); + } + else { + __goblint_check(1); // reachable + } + +#ifndef __APPLE__ + if (!pthread_spin_lock(&spin)) { + __goblint_check(1); // TODO reachable (TODO for OSX) + g_spin++; // NORACE + pthread_spin_unlock(&spin); + } + else { + __goblint_check(0); // NOWARN (unreachable) + } + + if (!pthread_spin_trylock(&spin)) { + __goblint_check(1); // TODO reachable (TODO for OSX) + g_spin++; // NORACE + pthread_spin_unlock(&spin); + } + else { + __goblint_check(1); // TODO reachable (TODO for OSX) + } +#endif + + return NULL; +} + +int main() { +#ifndef __APPLE__ + pthread_spin_init(&spin, PTHREAD_PROCESS_PRIVATE); +#endif + + pthread_t id; + pthread_create(&id, NULL, &t_fun, NULL); + + pthread_mutex_lock(&mutex); + g_mutex++; // NORACE + pthread_mutex_unlock(&mutex); + + pthread_rwlock_wrlock(&mutex); + g_rwlock++; // NORACE + pthread_rwlock_unlock(&mutex); + +#ifndef __APPLE__ + pthread_spin_lock(&spin); + g_spin++; // NORACE + pthread_spin_unlock(&spin); +#endif + + pthread_join(id, NULL); + return 0; +} diff --git a/tests/regression/04-mutex/62-simple_atomic_nr.c b/tests/regression/04-mutex/62-simple_atomic_nr.c index d63f303251..fdef44bdd6 100644 --- a/tests/regression/04-mutex/62-simple_atomic_nr.c +++ b/tests/regression/04-mutex/62-simple_atomic_nr.c @@ -1,24 +1,83 @@ #include -#include #include -atomic_int myglobal; -pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER; -pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER; +atomic_int g1; +_Atomic int g2; +_Atomic(int) g3; + +atomic_int a1[1]; +_Atomic int a2[1]; +_Atomic(int) a3[1]; + +struct s { + int f0; + atomic_int f1; + _Atomic int f2; + _Atomic(int) f3; +}; + +struct s s1; +_Atomic struct s s2; +_Atomic(struct s) s3; + +typedef atomic_int t_int1; +typedef _Atomic int t_int2; +typedef _Atomic(int) t_int3; + +t_int1 t1; +t_int2 t2; +t_int3 t3; + +typedef int t_int0; + +_Atomic t_int0 t0; +_Atomic(t_int0) t00; + +atomic_int *p0 = &g1; +int x; +// int * _Atomic p1 = &x; // TODO: https://github.com/goblint/cil/issues/64 +// _Atomic(int*) p2 = &x; // TODO: https://github.com/goblint/cil/issues/64 +// atomic_int * _Atomic p3 = &g1; // TODO: https://github.com/goblint/cil/issues/64 + +atomic_flag flag = ATOMIC_FLAG_INIT; void *t_fun(void *arg) { - pthread_mutex_lock(&mutex1); - myglobal=myglobal+1; // NORACE - pthread_mutex_unlock(&mutex1); + g1++; // NORACE + g2++; // NORACE + g3++; // NORACE + a1[0]++; // NORACE + a2[0]++; // NORACE + a3[0]++; // NORACE + s1.f1++; // NORACE + s1.f2++; // NORACE + s1.f3++; // NORACE + s2.f0++; // NORACE + s3.f0++; // NORACE + t1++; // NORACE + t2++; // NORACE + t3++; // NORACE + t0++; // NORACE + t00++; // NORACE + (*p0)++; // NORACE + // p1++; // TODO NORACE: https://github.com/goblint/cil/issues/64 + // p2++; // TODO NORACE: https://github.com/goblint/cil/issues/64 + // p3++; // TODO NORACE: https://github.com/goblint/cil/issues/64 + // (*p3)++; // TODO NORACE: https://github.com/goblint/cil/issues/64 + + struct s ss = {0}; + s2 = ss; // NORACE + s3 = ss; // NORACE + + atomic_flag_clear(&flag); // NORACE + atomic_flag_test_and_set(&flag); // NORACE return NULL; } int main(void) { - pthread_t id; + pthread_t id, id2; pthread_create(&id, NULL, t_fun, NULL); - pthread_mutex_lock(&mutex2); - myglobal=myglobal+1; // NORACE - pthread_mutex_unlock(&mutex2); - pthread_join (id, NULL); + pthread_create(&id2, NULL, t_fun, NULL); + pthread_join(id, NULL); + pthread_join(id2, NULL); return 0; } diff --git a/tests/regression/04-mutex/77-type-nested-fields.c b/tests/regression/04-mutex/77-type-nested-fields.c index 6f173d6fec..00b21e3fcf 100644 --- a/tests/regression/04-mutex/77-type-nested-fields.c +++ b/tests/regression/04-mutex/77-type-nested-fields.c @@ -1,7 +1,14 @@ -//PARAM: --enable ana.race.direct-arithmetic #include #include +// (int) (S) (T) (U) +// \ / \ / \ / +// >f< s t +// \ / \ / +// >f< s +// \ / +// f + struct S { int field; }; diff --git a/tests/regression/04-mutex/77-type-nested-fields.t b/tests/regression/04-mutex/77-type-nested-fields.t new file mode 100644 index 0000000000..68d9cdb779 --- /dev/null +++ b/tests/regression/04-mutex/77-type-nested-fields.t @@ -0,0 +1,26 @@ + $ goblint --enable warn.deterministic --enable allglobs 77-type-nested-fields.c + [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (77-type-nested-fields.c:31:3-31:20) + [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (77-type-nested-fields.c:38:3-38:22) + [Warning][Race] Memory location (struct T).s.field (race with conf. 100): + write with [mhp:{tid=[main, t_fun@77-type-nested-fields.c:37:3-37:40#top]}, thread:[main, t_fun@77-type-nested-fields.c:37:3-37:40#top]] (conf. 100) (exp: & tmp->field) (77-type-nested-fields.c:31:3-31:20) + write with [mhp:{tid=[main]; created={[main, t_fun@77-type-nested-fields.c:37:3-37:40#top]}}, thread:[main]] (conf. 100) (exp: & tmp->s.field) (77-type-nested-fields.c:38:3-38:22) + [Info][Race] Memory locations race summary: + safe: 1 + vulnerable: 0 + unsafe: 1 + total memory locations: 2 + [Success][Race] Memory location (struct S).field (safe): + write with [mhp:{tid=[main, t_fun@77-type-nested-fields.c:37:3-37:40#top]}, thread:[main, t_fun@77-type-nested-fields.c:37:3-37:40#top]] (conf. 100) (exp: & tmp->field) (77-type-nested-fields.c:31:3-31:20) + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 7 + dead: 0 + total lines: 7 + [Info][Unsound] Write to unknown address: privatization is unsound. (77-type-nested-fields.c:31:3-31:20) + [Info][Unsound] Write to unknown address: privatization is unsound. (77-type-nested-fields.c:38:3-38:22) + [Info][Imprecise] INVALIDATING ALL GLOBALS! (77-type-nested-fields.c:31:3-31:20) + [Info][Imprecise] Invalidating expressions: AddrOf(Var(tmp, NoOffset)) (77-type-nested-fields.c:31:3-31:20) + [Info][Imprecise] INVALIDATING ALL GLOBALS! (77-type-nested-fields.c:38:3-38:22) + [Info][Imprecise] Invalidating expressions: AddrOf(Var(tmp, NoOffset)) (77-type-nested-fields.c:38:3-38:22) + [Error][Imprecise][Unsound] Function definition missing for getS (77-type-nested-fields.c:31:3-31:20) + [Error][Imprecise][Unsound] Function definition missing for getT (77-type-nested-fields.c:38:3-38:22) + [Error][Imprecise][Unsound] Function definition missing diff --git a/tests/regression/04-mutex/78-type-array.c b/tests/regression/04-mutex/78-type-array.c index cdffe244b9..835f6163a3 100644 --- a/tests/regression/04-mutex/78-type-array.c +++ b/tests/regression/04-mutex/78-type-array.c @@ -1,4 +1,3 @@ -//PARAM: --enable ana.race.direct-arithmetic #include #include diff --git a/tests/regression/04-mutex/79-type-nested-fields-deep1.c b/tests/regression/04-mutex/79-type-nested-fields-deep1.c index ee99c40973..e100404960 100644 --- a/tests/regression/04-mutex/79-type-nested-fields-deep1.c +++ b/tests/regression/04-mutex/79-type-nested-fields-deep1.c @@ -1,7 +1,14 @@ -//PARAM: --enable ana.race.direct-arithmetic #include #include +// (int) (S) (T) (U) +// \ / \ / \ / +// >f< s t +// \ / \ / +// f s +// \ / +// >f< + struct S { int field; }; diff --git a/tests/regression/04-mutex/79-type-nested-fields-deep1.t b/tests/regression/04-mutex/79-type-nested-fields-deep1.t new file mode 100644 index 0000000000..85f7bfb709 --- /dev/null +++ b/tests/regression/04-mutex/79-type-nested-fields-deep1.t @@ -0,0 +1,26 @@ + $ goblint --enable warn.deterministic --enable allglobs 79-type-nested-fields-deep1.c + [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (79-type-nested-fields-deep1.c:36:3-36:20) + [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (79-type-nested-fields-deep1.c:43:3-43:24) + [Warning][Race] Memory location (struct U).t.s.field (race with conf. 100): + write with [mhp:{tid=[main, t_fun@79-type-nested-fields-deep1.c:42:3-42:40#top]}, thread:[main, t_fun@79-type-nested-fields-deep1.c:42:3-42:40#top]] (conf. 100) (exp: & tmp->field) (79-type-nested-fields-deep1.c:36:3-36:20) + write with [mhp:{tid=[main]; created={[main, t_fun@79-type-nested-fields-deep1.c:42:3-42:40#top]}}, thread:[main]] (conf. 100) (exp: & tmp->t.s.field) (79-type-nested-fields-deep1.c:43:3-43:24) + [Info][Race] Memory locations race summary: + safe: 1 + vulnerable: 0 + unsafe: 1 + total memory locations: 2 + [Success][Race] Memory location (struct S).field (safe): + write with [mhp:{tid=[main, t_fun@79-type-nested-fields-deep1.c:42:3-42:40#top]}, thread:[main, t_fun@79-type-nested-fields-deep1.c:42:3-42:40#top]] (conf. 100) (exp: & tmp->field) (79-type-nested-fields-deep1.c:36:3-36:20) + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 7 + dead: 0 + total lines: 7 + [Info][Unsound] Write to unknown address: privatization is unsound. (79-type-nested-fields-deep1.c:36:3-36:20) + [Info][Unsound] Write to unknown address: privatization is unsound. (79-type-nested-fields-deep1.c:43:3-43:24) + [Info][Imprecise] INVALIDATING ALL GLOBALS! (79-type-nested-fields-deep1.c:36:3-36:20) + [Info][Imprecise] Invalidating expressions: AddrOf(Var(tmp, NoOffset)) (79-type-nested-fields-deep1.c:36:3-36:20) + [Info][Imprecise] INVALIDATING ALL GLOBALS! (79-type-nested-fields-deep1.c:43:3-43:24) + [Info][Imprecise] Invalidating expressions: AddrOf(Var(tmp, NoOffset)) (79-type-nested-fields-deep1.c:43:3-43:24) + [Error][Imprecise][Unsound] Function definition missing for getS (79-type-nested-fields-deep1.c:36:3-36:20) + [Error][Imprecise][Unsound] Function definition missing for getU (79-type-nested-fields-deep1.c:43:3-43:24) + [Error][Imprecise][Unsound] Function definition missing diff --git a/tests/regression/04-mutex/80-type-nested-fields-deep2.c b/tests/regression/04-mutex/80-type-nested-fields-deep2.c index 646acd9147..4ddd4684f7 100644 --- a/tests/regression/04-mutex/80-type-nested-fields-deep2.c +++ b/tests/regression/04-mutex/80-type-nested-fields-deep2.c @@ -1,7 +1,14 @@ -//PARAM: --enable ana.race.direct-arithmetic #include #include +// (int) (S) (T) (U) +// \ / \ / \ / +// f s t +// \ / \ / +// >f< s +// \ / +// >f< + struct S { int field; }; diff --git a/tests/regression/04-mutex/80-type-nested-fields-deep2.t b/tests/regression/04-mutex/80-type-nested-fields-deep2.t new file mode 100644 index 0000000000..a2e9e2ab15 --- /dev/null +++ b/tests/regression/04-mutex/80-type-nested-fields-deep2.t @@ -0,0 +1,26 @@ + $ goblint --enable warn.deterministic --enable allglobs 80-type-nested-fields-deep2.c + [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (80-type-nested-fields-deep2.c:36:3-36:22) + [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (80-type-nested-fields-deep2.c:43:3-43:24) + [Warning][Race] Memory location (struct U).t.s.field (race with conf. 100): + write with [mhp:{tid=[main, t_fun@80-type-nested-fields-deep2.c:42:3-42:40#top]}, thread:[main, t_fun@80-type-nested-fields-deep2.c:42:3-42:40#top]] (conf. 100) (exp: & tmp->s.field) (80-type-nested-fields-deep2.c:36:3-36:22) + write with [mhp:{tid=[main]; created={[main, t_fun@80-type-nested-fields-deep2.c:42:3-42:40#top]}}, thread:[main]] (conf. 100) (exp: & tmp->t.s.field) (80-type-nested-fields-deep2.c:43:3-43:24) + [Info][Race] Memory locations race summary: + safe: 1 + vulnerable: 0 + unsafe: 1 + total memory locations: 2 + [Success][Race] Memory location (struct T).s.field (safe): + write with [mhp:{tid=[main, t_fun@80-type-nested-fields-deep2.c:42:3-42:40#top]}, thread:[main, t_fun@80-type-nested-fields-deep2.c:42:3-42:40#top]] (conf. 100) (exp: & tmp->s.field) (80-type-nested-fields-deep2.c:36:3-36:22) + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 7 + dead: 0 + total lines: 7 + [Info][Unsound] Write to unknown address: privatization is unsound. (80-type-nested-fields-deep2.c:36:3-36:22) + [Info][Unsound] Write to unknown address: privatization is unsound. (80-type-nested-fields-deep2.c:43:3-43:24) + [Info][Imprecise] INVALIDATING ALL GLOBALS! (80-type-nested-fields-deep2.c:36:3-36:22) + [Info][Imprecise] Invalidating expressions: AddrOf(Var(tmp, NoOffset)) (80-type-nested-fields-deep2.c:36:3-36:22) + [Info][Imprecise] INVALIDATING ALL GLOBALS! (80-type-nested-fields-deep2.c:43:3-43:24) + [Info][Imprecise] Invalidating expressions: AddrOf(Var(tmp, NoOffset)) (80-type-nested-fields-deep2.c:43:3-43:24) + [Error][Imprecise][Unsound] Function definition missing for getT (80-type-nested-fields-deep2.c:36:3-36:22) + [Error][Imprecise][Unsound] Function definition missing for getU (80-type-nested-fields-deep2.c:43:3-43:24) + [Error][Imprecise][Unsound] Function definition missing diff --git a/tests/regression/04-mutex/90-distribute-fields-type-1.c b/tests/regression/04-mutex/90-distribute-fields-type-1.c new file mode 100644 index 0000000000..062b7421e6 --- /dev/null +++ b/tests/regression/04-mutex/90-distribute-fields-type-1.c @@ -0,0 +1,41 @@ +#include +#include + +// (int) (S) (T) (U) +// \ / \ / \ / +// >f< >s< t +// \ / \ / +// f s +// \ / +// f + +struct S { + int field; +}; + +struct T { + struct S s; +}; + +// struct S s; +// struct T t; + +extern struct S* getS(); +extern struct T* getT(); + +// getS could return the same struct as is contained in getT + +void *t_fun(void *arg) { + // should write to (struct T).s.field in addition to (struct S).field + // but easier to implement the other way around? + getS()->field = 1; // RACE! + return NULL; +} + +int main(void) { + pthread_t id; + pthread_create(&id, NULL, t_fun, NULL); + struct S s1; + getT()->s = s1; // RACE! + return 0; +} diff --git a/tests/regression/04-mutex/90-distribute-fields-type-1.t b/tests/regression/04-mutex/90-distribute-fields-type-1.t new file mode 100644 index 0000000000..a3b5faf083 --- /dev/null +++ b/tests/regression/04-mutex/90-distribute-fields-type-1.t @@ -0,0 +1,28 @@ + $ goblint --enable warn.deterministic --enable allglobs 90-distribute-fields-type-1.c + [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (90-distribute-fields-type-1.c:31:3-31:20) + [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (90-distribute-fields-type-1.c:39:3-39:17) + [Warning][Race] Memory location (struct T).s.field (race with conf. 100): + write with [mhp:{tid=[main, t_fun@90-distribute-fields-type-1.c:37:3-37:40#top]}, thread:[main, t_fun@90-distribute-fields-type-1.c:37:3-37:40#top]] (conf. 100) (exp: & tmp->field) (90-distribute-fields-type-1.c:31:3-31:20) + write with [mhp:{tid=[main]; created={[main, t_fun@90-distribute-fields-type-1.c:37:3-37:40#top]}}, thread:[main]] (conf. 100) (exp: & tmp->s) (90-distribute-fields-type-1.c:39:3-39:17) + [Info][Race] Memory locations race summary: + safe: 2 + vulnerable: 0 + unsafe: 1 + total memory locations: 3 + [Success][Race] Memory location (struct S).field (safe): + write with [mhp:{tid=[main, t_fun@90-distribute-fields-type-1.c:37:3-37:40#top]}, thread:[main, t_fun@90-distribute-fields-type-1.c:37:3-37:40#top]] (conf. 100) (exp: & tmp->field) (90-distribute-fields-type-1.c:31:3-31:20) + [Success][Race] Memory location (struct T).s (safe): + write with [mhp:{tid=[main]; created={[main, t_fun@90-distribute-fields-type-1.c:37:3-37:40#top]}}, thread:[main]] (conf. 100) (exp: & tmp->s) (90-distribute-fields-type-1.c:39:3-39:17) + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 7 + dead: 0 + total lines: 7 + [Info][Unsound] Write to unknown address: privatization is unsound. (90-distribute-fields-type-1.c:31:3-31:20) + [Info][Unsound] Write to unknown address: privatization is unsound. (90-distribute-fields-type-1.c:39:3-39:17) + [Info][Imprecise] INVALIDATING ALL GLOBALS! (90-distribute-fields-type-1.c:31:3-31:20) + [Info][Imprecise] Invalidating expressions: AddrOf(Var(tmp, NoOffset)) (90-distribute-fields-type-1.c:31:3-31:20) + [Info][Imprecise] INVALIDATING ALL GLOBALS! (90-distribute-fields-type-1.c:39:3-39:17) + [Info][Imprecise] Invalidating expressions: AddrOf(Var(tmp, NoOffset)) (90-distribute-fields-type-1.c:39:3-39:17) + [Error][Imprecise][Unsound] Function definition missing for getS (90-distribute-fields-type-1.c:31:3-31:20) + [Error][Imprecise][Unsound] Function definition missing for getT (90-distribute-fields-type-1.c:39:3-39:17) + [Error][Imprecise][Unsound] Function definition missing diff --git a/tests/regression/04-mutex/91-distribute-fields-type-2.c b/tests/regression/04-mutex/91-distribute-fields-type-2.c new file mode 100644 index 0000000000..01c945f730 --- /dev/null +++ b/tests/regression/04-mutex/91-distribute-fields-type-2.c @@ -0,0 +1,42 @@ +#include +#include + +// (int) >(S)< >(T)< (U) +// \ / \ / \ / +// f s t +// \ / \ / +// f s +// \ / +// f + +struct S { + int field; +}; + +struct T { + struct S s; +}; + +// struct S s; +// struct T t; + +extern struct S* getS(); +extern struct T* getT(); + +// getS could return the same struct as is contained in getT + +void *t_fun(void *arg) { + // should write to (struct T).s.field in addition to (struct S).field + // but easier to implement the other way around? + struct S s1; + *(getS()) = s1; // RACE! + return NULL; +} + +int main(void) { + pthread_t id; + pthread_create(&id, NULL, t_fun, NULL); + struct T t1; + *(getT()) = t1; // RACE! + return 0; +} diff --git a/tests/regression/04-mutex/91-distribute-fields-type-2.t b/tests/regression/04-mutex/91-distribute-fields-type-2.t new file mode 100644 index 0000000000..5773245114 --- /dev/null +++ b/tests/regression/04-mutex/91-distribute-fields-type-2.t @@ -0,0 +1,28 @@ + $ goblint --enable warn.deterministic --enable allglobs 91-distribute-fields-type-2.c + [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (91-distribute-fields-type-2.c:32:3-32:17) + [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (91-distribute-fields-type-2.c:40:3-40:17) + [Warning][Race] Memory location (struct T).s (race with conf. 100): + write with [mhp:{tid=[main, t_fun@91-distribute-fields-type-2.c:38:3-38:40#top]}, thread:[main, t_fun@91-distribute-fields-type-2.c:38:3-38:40#top]] (conf. 100) (exp: & *tmp) (91-distribute-fields-type-2.c:32:3-32:17) + write with [mhp:{tid=[main]; created={[main, t_fun@91-distribute-fields-type-2.c:38:3-38:40#top]}}, thread:[main]] (conf. 100) (exp: & *tmp) (91-distribute-fields-type-2.c:40:3-40:17) + [Info][Race] Memory locations race summary: + safe: 2 + vulnerable: 0 + unsafe: 1 + total memory locations: 3 + [Success][Race] Memory location (struct S) (safe): + write with [mhp:{tid=[main, t_fun@91-distribute-fields-type-2.c:38:3-38:40#top]}, thread:[main, t_fun@91-distribute-fields-type-2.c:38:3-38:40#top]] (conf. 100) (exp: & *tmp) (91-distribute-fields-type-2.c:32:3-32:17) + [Success][Race] Memory location (struct T) (safe): + write with [mhp:{tid=[main]; created={[main, t_fun@91-distribute-fields-type-2.c:38:3-38:40#top]}}, thread:[main]] (conf. 100) (exp: & *tmp) (91-distribute-fields-type-2.c:40:3-40:17) + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 7 + dead: 0 + total lines: 7 + [Info][Unsound] Write to unknown address: privatization is unsound. (91-distribute-fields-type-2.c:32:3-32:17) + [Info][Unsound] Write to unknown address: privatization is unsound. (91-distribute-fields-type-2.c:40:3-40:17) + [Info][Imprecise] INVALIDATING ALL GLOBALS! (91-distribute-fields-type-2.c:32:3-32:17) + [Info][Imprecise] Invalidating expressions: AddrOf(Var(tmp, NoOffset)) (91-distribute-fields-type-2.c:32:3-32:17) + [Info][Imprecise] INVALIDATING ALL GLOBALS! (91-distribute-fields-type-2.c:40:3-40:17) + [Info][Imprecise] Invalidating expressions: AddrOf(Var(tmp, NoOffset)) (91-distribute-fields-type-2.c:40:3-40:17) + [Error][Imprecise][Unsound] Function definition missing for getS (91-distribute-fields-type-2.c:32:3-32:17) + [Error][Imprecise][Unsound] Function definition missing for getT (91-distribute-fields-type-2.c:40:3-40:17) + [Error][Imprecise][Unsound] Function definition missing diff --git a/tests/regression/04-mutex/92-distribute-fields-type-deep.c b/tests/regression/04-mutex/92-distribute-fields-type-deep.c new file mode 100644 index 0000000000..59fb09a605 --- /dev/null +++ b/tests/regression/04-mutex/92-distribute-fields-type-deep.c @@ -0,0 +1,46 @@ +#include +#include + +// (int) (S) (T) (U) +// \ / \ / \ / +// >f< s >t< +// \ / \ / +// f s +// \ / +// f + +struct S { + int field; +}; + +struct T { + struct S s; +}; + +struct U { + struct T t; +}; + +// struct S s; +// struct T t; + +extern struct S* getS(); +extern struct T* getT(); +extern struct U* getU(); + +// getS could return the same struct as is contained in getT + +void *t_fun(void *arg) { + // should write to (struct U).t.s.field in addition to (struct T).s.field + // but easier to implement the other way around? + getS()->field = 1; // RACE! + return NULL; +} + +int main(void) { + pthread_t id; + pthread_create(&id, NULL, t_fun, NULL); + struct T t1; + getU()->t = t1; // RACE! + return 0; +} diff --git a/tests/regression/04-mutex/92-distribute-fields-type-deep.t b/tests/regression/04-mutex/92-distribute-fields-type-deep.t new file mode 100644 index 0000000000..798374d63c --- /dev/null +++ b/tests/regression/04-mutex/92-distribute-fields-type-deep.t @@ -0,0 +1,28 @@ + $ goblint --enable warn.deterministic --enable allglobs 92-distribute-fields-type-deep.c + [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (92-distribute-fields-type-deep.c:36:3-36:20) + [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (92-distribute-fields-type-deep.c:44:3-44:17) + [Warning][Race] Memory location (struct U).t.s.field (race with conf. 100): + write with [mhp:{tid=[main, t_fun@92-distribute-fields-type-deep.c:42:3-42:40#top]}, thread:[main, t_fun@92-distribute-fields-type-deep.c:42:3-42:40#top]] (conf. 100) (exp: & tmp->field) (92-distribute-fields-type-deep.c:36:3-36:20) + write with [mhp:{tid=[main]; created={[main, t_fun@92-distribute-fields-type-deep.c:42:3-42:40#top]}}, thread:[main]] (conf. 100) (exp: & tmp->t) (92-distribute-fields-type-deep.c:44:3-44:17) + [Info][Race] Memory locations race summary: + safe: 2 + vulnerable: 0 + unsafe: 1 + total memory locations: 3 + [Success][Race] Memory location (struct S).field (safe): + write with [mhp:{tid=[main, t_fun@92-distribute-fields-type-deep.c:42:3-42:40#top]}, thread:[main, t_fun@92-distribute-fields-type-deep.c:42:3-42:40#top]] (conf. 100) (exp: & tmp->field) (92-distribute-fields-type-deep.c:36:3-36:20) + [Success][Race] Memory location (struct U).t (safe): + write with [mhp:{tid=[main]; created={[main, t_fun@92-distribute-fields-type-deep.c:42:3-42:40#top]}}, thread:[main]] (conf. 100) (exp: & tmp->t) (92-distribute-fields-type-deep.c:44:3-44:17) + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 7 + dead: 0 + total lines: 7 + [Info][Unsound] Write to unknown address: privatization is unsound. (92-distribute-fields-type-deep.c:36:3-36:20) + [Info][Unsound] Write to unknown address: privatization is unsound. (92-distribute-fields-type-deep.c:44:3-44:17) + [Info][Imprecise] INVALIDATING ALL GLOBALS! (92-distribute-fields-type-deep.c:36:3-36:20) + [Info][Imprecise] Invalidating expressions: AddrOf(Var(tmp, NoOffset)) (92-distribute-fields-type-deep.c:36:3-36:20) + [Info][Imprecise] INVALIDATING ALL GLOBALS! (92-distribute-fields-type-deep.c:44:3-44:17) + [Info][Imprecise] Invalidating expressions: AddrOf(Var(tmp, NoOffset)) (92-distribute-fields-type-deep.c:44:3-44:17) + [Error][Imprecise][Unsound] Function definition missing for getS (92-distribute-fields-type-deep.c:36:3-36:20) + [Error][Imprecise][Unsound] Function definition missing for getU (92-distribute-fields-type-deep.c:44:3-44:17) + [Error][Imprecise][Unsound] Function definition missing diff --git a/tests/regression/04-mutex/93-distribute-fields-type-global.c b/tests/regression/04-mutex/93-distribute-fields-type-global.c new file mode 100644 index 0000000000..466d47e7fc --- /dev/null +++ b/tests/regression/04-mutex/93-distribute-fields-type-global.c @@ -0,0 +1,25 @@ +#include +#include + +struct S { + int field; +}; + +struct S s; + +extern struct S* getS(); + +void *t_fun(void *arg) { + printf("%d",getS()->field); // RACE! + + return NULL; +} + +int main(void) { + pthread_t id; + pthread_create(&id, NULL, t_fun, NULL); + struct S s1; + s = s1; // RACE! + return 0; +} + diff --git a/tests/regression/04-mutex/93-distribute-fields-type-global.t b/tests/regression/04-mutex/93-distribute-fields-type-global.t new file mode 100644 index 0000000000..07999854ff --- /dev/null +++ b/tests/regression/04-mutex/93-distribute-fields-type-global.t @@ -0,0 +1,24 @@ + $ goblint --enable warn.deterministic --enable allglobs 93-distribute-fields-type-global.c + [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (93-distribute-fields-type-global.c:13:3-13:29) + [Warning][Race] Memory location s.field (race with conf. 110): (93-distribute-fields-type-global.c:8:10-8:11) + read with [mhp:{tid=[main, t_fun@93-distribute-fields-type-global.c:20:3-20:40#top]}, thread:[main, t_fun@93-distribute-fields-type-global.c:20:3-20:40#top]] (conf. 100) (exp: & tmp->field) (93-distribute-fields-type-global.c:13:3-13:29) + write with [mhp:{tid=[main]; created={[main, t_fun@93-distribute-fields-type-global.c:20:3-20:40#top]}}, thread:[main]] (conf. 110) (exp: & s) (93-distribute-fields-type-global.c:22:3-22:9) + [Info][Race] Memory locations race summary: + safe: 2 + vulnerable: 0 + unsafe: 1 + total memory locations: 3 + [Success][Race] Memory location (struct S).field (safe): + read with [mhp:{tid=[main, t_fun@93-distribute-fields-type-global.c:20:3-20:40#top]}, thread:[main, t_fun@93-distribute-fields-type-global.c:20:3-20:40#top]] (conf. 100) (exp: & tmp->field) (93-distribute-fields-type-global.c:13:3-13:29) + [Success][Race] Memory location s (safe): (93-distribute-fields-type-global.c:8:10-8:11) + write with [mhp:{tid=[main]; created={[main, t_fun@93-distribute-fields-type-global.c:20:3-20:40#top]}}, thread:[main]] (conf. 110) (exp: & s) (93-distribute-fields-type-global.c:22:3-22:9) + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 7 + dead: 0 + total lines: 7 + [Info][Unsound] Write to unknown address: privatization is unsound. (93-distribute-fields-type-global.c:13:3-13:29) + [Info][Imprecise] INVALIDATING ALL GLOBALS! (93-distribute-fields-type-global.c:13:3-13:29) + [Info][Imprecise] Invalidating expressions: AddrOf(Var(s, NoOffset)) (93-distribute-fields-type-global.c:13:3-13:29) + [Info][Imprecise] Invalidating expressions: AddrOf(Var(tmp, NoOffset)) (93-distribute-fields-type-global.c:13:3-13:29) + [Error][Imprecise][Unsound] Function definition missing for getS (93-distribute-fields-type-global.c:13:3-13:29) + [Error][Imprecise][Unsound] Function definition missing diff --git a/tests/regression/19-spec/02-mutex_rc.c b/tests/regression/04-mutex/94-thread-unsafe_fun_rc.c similarity index 84% rename from tests/regression/19-spec/02-mutex_rc.c rename to tests/regression/04-mutex/94-thread-unsafe_fun_rc.c index 82c1642a93..8f2f01fc6d 100644 --- a/tests/regression/19-spec/02-mutex_rc.c +++ b/tests/regression/04-mutex/94-thread-unsafe_fun_rc.c @@ -1,13 +1,12 @@ #include #include -int myglobal; pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER; void *t_fun(void *arg) { pthread_mutex_lock(&mutex1); - myglobal=myglobal+1; // RACE! + rand(); // RACE! pthread_mutex_unlock(&mutex1); return NULL; } @@ -16,7 +15,7 @@ int main(void) { pthread_t id; pthread_create(&id, NULL, t_fun, NULL); pthread_mutex_lock(&mutex2); - myglobal=myglobal+1; // RACE! + rand(); // RACE! pthread_mutex_unlock(&mutex2); pthread_join (id, NULL); return 0; diff --git a/tests/regression/04-mutex/95-thread-unsafe_fun_nr.c b/tests/regression/04-mutex/95-thread-unsafe_fun_nr.c new file mode 100644 index 0000000000..df02d23db9 --- /dev/null +++ b/tests/regression/04-mutex/95-thread-unsafe_fun_nr.c @@ -0,0 +1,21 @@ +#include +#include + +pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER; + +void *t_fun(void *arg) { + pthread_mutex_lock(&mutex1); + rand(); // NORACE + pthread_mutex_unlock(&mutex1); + return NULL; +} + +int main(void) { + pthread_t id; + pthread_create(&id, NULL, t_fun, NULL); + pthread_mutex_lock(&mutex1); + rand(); // NORACE + pthread_mutex_unlock(&mutex1); + pthread_join (id, NULL); + return 0; +} diff --git a/tests/regression/04-mutex/99-volatile.c b/tests/regression/04-mutex/99-volatile.c index aaf81f13a1..7c2a255902 100644 --- a/tests/regression/04-mutex/99-volatile.c +++ b/tests/regression/04-mutex/99-volatile.c @@ -1,18 +1,53 @@ // PARAM: --disable ana.race.volatile #include -#include -volatile int myglobal; +volatile int g1; + +volatile int a1[1]; + +struct s { + int f0; + volatile int f1; +}; + +struct s s1; +volatile struct s s2; + +typedef volatile int t_int1; + +t_int1 t1; + +typedef int t_int0; + +volatile t_int0 t0; + +volatile int *p0 = &g1; +int x; +int * volatile p1 = &x; +volatile int * volatile p2 = &g1; void *t_fun(void *arg) { - myglobal= 8; //NORACE + g1++; // NORACE + a1[0]++; // NORACE + s1.f1++; // NORACE + s2.f0++; // NORACE + t1++; // NORACE + t0++; // NORACE + (*p0)++; // NORACE + p1++; // NORACE + p2++; // NORACE + (*p2)++; // NORACE + + struct s ss = {0}; + s2 = ss; // NORACE return NULL; } int main(void) { - pthread_t id; - pthread_create(&id, NULL, t_fun, (void*) &myglobal); - myglobal = 42; //NORACE - pthread_join (id, NULL); + pthread_t id, id2; + pthread_create(&id, NULL, t_fun, NULL); + pthread_create(&id2, NULL, t_fun, NULL); + pthread_join(id, NULL); + pthread_join(id2, NULL); return 0; -} \ No newline at end of file +} diff --git a/tests/regression/05-lval_ls/20-race-null-void.c b/tests/regression/05-lval_ls/20-race-null-void.c new file mode 100644 index 0000000000..1950ada73e --- /dev/null +++ b/tests/regression/05-lval_ls/20-race-null-void.c @@ -0,0 +1,54 @@ +#include +#include + +void *t_fun(void *arg) { + void **top; + free(top); // RACE + return NULL; +} + +int main(void) { + pthread_t id; + pthread_create(&id, NULL, t_fun, NULL); + + int r; // rand + int zero = 0; // IntDomain zero + void *null; + __goblint_assume(null == NULL); // AddressDomain NULL + int one = 1; // IntDomain one + void *unknown; + __goblint_assume(unknown != NULL); // AddressDomain unknown + void *top; + switch (r) { + case 0: + pthread_join(id, NULL); // NORACE + break; + case 1: + pthread_join(id, 0); // NORACE + break; + case 2: + pthread_join(id, zero); // NORACE + break; + case 3: + pthread_join(id, 1); // RACE + break; + case 4: + pthread_join(id, one); // RACE + break; + case 5: + pthread_join(id, r); // RACE + break; + case 6: + pthread_join(id, null); // NORACE + break; + case 7: + pthread_join(id, unknown); // RACE + break; + case 8: + pthread_join(id, top); // RACE + break; + default: + break; + } + return 0; +} diff --git a/tests/regression/05-lval_ls/21-race-null-type.c b/tests/regression/05-lval_ls/21-race-null-type.c new file mode 100644 index 0000000000..6b5e6e42fd --- /dev/null +++ b/tests/regression/05-lval_ls/21-race-null-type.c @@ -0,0 +1,55 @@ +// PARAM: --enable ana.race.direct-arithmetic +#include +#include + +void *t_fun(void *arg) { + void *top; + time(top); // RACE + return NULL; +} + +int main(void) { + pthread_t id; + pthread_create(&id, NULL, t_fun, NULL); + + int r; // rand + int zero = 0; // IntDomain zero + void *null; + __goblint_assume(null == NULL); // AddressDomain NULL + int one = 1; // IntDomain one + void *unknown; + __goblint_assume(unknown != NULL); // AddressDomain unknown + void *top; + switch (r) { + case 0: + time(NULL); // NORACE + break; + case 1: + time(0); // NORACE + break; + case 2: + time(zero); // NORACE + break; + case 3: + time(1); // RACE + break; + case 4: + time(one); // RACE + break; + case 5: + time(r); // RACE + break; + case 6: + time(null); // NORACE + break; + case 7: + time(unknown); // RACE + break; + case 8: + time(top); // RACE + break; + default: + break; + } + return 0; +} diff --git a/tests/regression/05-lval_ls/22-race-null-void-deep.c b/tests/regression/05-lval_ls/22-race-null-void-deep.c new file mode 100644 index 0000000000..7e99f286b6 --- /dev/null +++ b/tests/regression/05-lval_ls/22-race-null-void-deep.c @@ -0,0 +1,56 @@ +#include +#include + +pthread_key_t key; + +void *t_fun(void *arg) { + void *top; + pthread_setspecific(key, top); // RACE + return NULL; +} + +int main(void) { + pthread_t id; + pthread_create(&id, NULL, t_fun, NULL); + + int r; // rand + int zero = 0; // IntDomain zero + void *null; + __goblint_assume(null == NULL); // AddressDomain NULL + int one = 1; // IntDomain one + void *unknown; + __goblint_assume(unknown != NULL); // AddressDomain unknown + void *top; + switch (r) { + case 0: + pthread_setspecific(key, NULL); // NORACE + break; + case 1: + pthread_setspecific(key, 0); // NORACE + break; + case 2: + pthread_setspecific(key, zero); // NORACE + break; + case 3: + pthread_setspecific(key, 1); // RACE + break; + case 4: + pthread_setspecific(key, one); // RACE + break; + case 5: + pthread_setspecific(key, r); // RACE + break; + case 6: + pthread_setspecific(key, null); // NORACE + break; + case 7: + pthread_setspecific(key, unknown); // RACE + break; + case 8: + pthread_setspecific(key, top); // RACE + break; + default: + break; + } + return 0; +} diff --git a/tests/regression/05-lval_ls/23-race-null-type-deep.c b/tests/regression/05-lval_ls/23-race-null-type-deep.c new file mode 100644 index 0000000000..f7de758d8f --- /dev/null +++ b/tests/regression/05-lval_ls/23-race-null-type-deep.c @@ -0,0 +1,60 @@ +// PARAM: --disable sem.unknown_function.invalidate.globals --disable sem.unknown_function.spawn +#include + +struct s { + int f; +}; + +extern void magic(struct s *p); + +void *t_fun(void *arg) { + void *top; + magic(top); // RACE + return NULL; +} + +int main(void) { + pthread_t id; + pthread_create(&id, NULL, t_fun, NULL); + + int r; // rand + int zero = 0; // IntDomain zero + void *null; + __goblint_assume(null == NULL); // AddressDomain NULL + int one = 1; // IntDomain one + void *unknown; + __goblint_assume(unknown != NULL); // AddressDomain unknown + void *top; + switch (r) { + case 0: + magic(NULL); // NORACE + break; + case 1: + magic(0); // NORACE + break; + case 2: + magic(zero); // NORACE + break; + case 3: + magic(1); // RACE + break; + case 4: + magic(one); // RACE + break; + case 5: + magic(r); // RACE + break; + case 6: + magic(null); // NORACE + break; + case 7: + magic(unknown); // RACE + break; + case 8: + magic(top); // RACE + break; + default: + break; + } + return 0; +} diff --git a/tests/regression/06-symbeq/16-type_rc.c b/tests/regression/06-symbeq/16-type_rc.c index efeb6c768b..e9e7c7972b 100644 --- a/tests/regression/06-symbeq/16-type_rc.c +++ b/tests/regression/06-symbeq/16-type_rc.c @@ -1,6 +1,14 @@ // PARAM: --enable ana.race.direct-arithmetic --set ana.activated[+] "'var_eq'" --set ana.activated[+] "'symb_locks'" #include +//>(int)< (S) (T) (U) +// \ / \ / \ / +// >f< s t +// \ / \ / +// f s +// \ / +// f + struct s { int datum; pthread_mutex_t mutex; diff --git a/tests/regression/06-symbeq/16-type_rc.t b/tests/regression/06-symbeq/16-type_rc.t index 78c293b7ef..b63471a45e 100644 --- a/tests/regression/06-symbeq/16-type_rc.t +++ b/tests/regression/06-symbeq/16-type_rc.t @@ -1,22 +1,24 @@ Disable info messages because race summary contains (safe) memory location count, which is different on Linux and OSX. $ goblint --enable warn.deterministic --disable warn.info --enable ana.race.direct-arithmetic --set ana.activated[+] "'var_eq'" --set ana.activated[+] "'symb_locks'" 16-type_rc.c - [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (16-type_rc.c:13:3-13:15) - [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (16-type_rc.c:24:3-24:16) - [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (16-type_rc.c:25:3-25:16) - [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (16-type_rc.c:28:3-28:9) + [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (16-type_rc.c:21:3-21:15) + [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (16-type_rc.c:32:3-32:16) + [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (16-type_rc.c:33:3-33:16) + [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (16-type_rc.c:36:3-36:9) [Warning][Race] Memory location (struct s).datum (race with conf. 100): - write with [mhp:{tid=[main, t_fun@16-type_rc.c:27:3-27:37#top]}, thread:[main, t_fun@16-type_rc.c:27:3-27:37#top]] (conf. 100) (exp: & s->datum) (16-type_rc.c:13:3-13:15) - write with [mhp:{tid=[main]; created={[main, t_fun@16-type_rc.c:27:3-27:37#top]}}, thread:[main]] (conf. 100) (exp: & *d) (16-type_rc.c:28:3-28:9) - [Error][Imprecise][Unsound] Function definition missing for get_s (16-type_rc.c:12:12-12:24) - [Error][Imprecise][Unsound] Function definition missing for get_s (16-type_rc.c:23:3-23:14) + write with [mhp:{tid=[main, t_fun@16-type_rc.c:35:3-35:37#top]}, thread:[main, t_fun@16-type_rc.c:35:3-35:37#top]] (conf. 100) (exp: & s->datum) (16-type_rc.c:21:3-21:15) + write with [mhp:{tid=[main]; created={[main, t_fun@16-type_rc.c:35:3-35:37#top]}}, thread:[main]] (conf. 100) (exp: & *d) (16-type_rc.c:36:3-36:9) + [Error][Imprecise][Unsound] Function definition missing for get_s (16-type_rc.c:20:12-20:24) + [Error][Imprecise][Unsound] Function definition missing for get_s (16-type_rc.c:31:3-31:14) + [Error][Imprecise][Unsound] Function definition missing $ goblint --enable warn.deterministic --disable warn.info --disable ana.race.direct-arithmetic --set ana.activated[+] "'var_eq'" --set ana.activated[+] "'symb_locks'" --enable allglobs 16-type_rc.c - [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (16-type_rc.c:13:3-13:15) - [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (16-type_rc.c:24:3-24:16) - [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (16-type_rc.c:25:3-25:16) - [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (16-type_rc.c:28:3-28:9) + [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (16-type_rc.c:21:3-21:15) + [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (16-type_rc.c:32:3-32:16) + [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (16-type_rc.c:33:3-33:16) + [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (16-type_rc.c:36:3-36:9) [Success][Race] Memory location (struct s).datum (safe): - write with [mhp:{tid=[main, t_fun@16-type_rc.c:27:3-27:37#top]}, thread:[main, t_fun@16-type_rc.c:27:3-27:37#top]] (conf. 100) (exp: & s->datum) (16-type_rc.c:13:3-13:15) - [Error][Imprecise][Unsound] Function definition missing for get_s (16-type_rc.c:12:12-12:24) - [Error][Imprecise][Unsound] Function definition missing for get_s (16-type_rc.c:23:3-23:14) + write with [mhp:{tid=[main, t_fun@16-type_rc.c:35:3-35:37#top]}, thread:[main, t_fun@16-type_rc.c:35:3-35:37#top]] (conf. 100) (exp: & s->datum) (16-type_rc.c:21:3-21:15) + [Error][Imprecise][Unsound] Function definition missing for get_s (16-type_rc.c:20:12-20:24) + [Error][Imprecise][Unsound] Function definition missing for get_s (16-type_rc.c:31:3-31:14) + [Error][Imprecise][Unsound] Function definition missing diff --git a/tests/regression/06-symbeq/21-mult_accs_rc.t b/tests/regression/06-symbeq/21-mult_accs_rc.t index 7a4439141d..227c66058e 100644 --- a/tests/regression/06-symbeq/21-mult_accs_rc.t +++ b/tests/regression/06-symbeq/21-mult_accs_rc.t @@ -10,10 +10,12 @@ Disable info messages because race summary contains (safe) memory location count [Warning][Race] Memory location (struct s).data (race with conf. 100): write with [mhp:{tid=[main, t_fun@21-mult_accs_rc.c:31:3-31:37#top]}, thread:[main, t_fun@21-mult_accs_rc.c:31:3-31:37#top]] (conf. 100) (exp: & s->data) (21-mult_accs_rc.c:16:3-16:14) write with [symblock:{p-lock:*.mutex}, mhp:{tid=[main]; created={[main, t_fun@21-mult_accs_rc.c:31:3-31:37#top]}}, 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] 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) [Error][Imprecise][Unsound] Function definition missing for get_s (21-mult_accs_rc.c:27:3-27:14) + [Error][Imprecise][Unsound] Function definition missing $ 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 [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (21-mult_accs_rc.c:14:3-14:32) @@ -24,7 +26,9 @@ Disable info messages because race summary contains (safe) memory location count [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 [mhp:{tid=[main, t_fun@21-mult_accs_rc.c:31:3-31:37#top]}, thread:[main, t_fun@21-mult_accs_rc.c:31:3-31:37#top]] (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] 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) [Error][Imprecise][Unsound] Function definition missing for get_s (21-mult_accs_rc.c:27:3-27:14) + [Error][Imprecise][Unsound] Function definition missing diff --git a/tests/regression/09-regions/38-escape_malloc.c b/tests/regression/09-regions/38-escape_malloc.c index 9f5b44531e..c849d5bbe3 100644 --- a/tests/regression/09-regions/38-escape_malloc.c +++ b/tests/regression/09-regions/38-escape_malloc.c @@ -9,7 +9,7 @@ pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER; void *t_fun(void *arg) { int *p = (int *) arg; pthread_mutex_lock(&mutex1); - (*p)++; // TODO RACE! + (*p)++; // RACE! pthread_mutex_unlock(&mutex1); return NULL; } @@ -20,7 +20,7 @@ int main(void) { // TODO: q escapes as region owner pthread_create(&id, NULL, t_fun, (void *) q); pthread_mutex_lock(&mutex2); - (*q)++; // TODO RACE! + (*q)++; // RACE! pthread_mutex_unlock(&mutex2); pthread_join (id, NULL); return 0; diff --git a/tests/regression/09-regions/40-zstd-thread-pool-region.c b/tests/regression/09-regions/40-zstd-thread-pool-region.c new file mode 100644 index 0000000000..13baf5ec3f --- /dev/null +++ b/tests/regression/09-regions/40-zstd-thread-pool-region.c @@ -0,0 +1,34 @@ +// SKIP PARAM: --set ana.activated[+] region +// FIXPOINT +#include +#include +#include + +typedef struct POOL_job_s { + void *opaque; +} POOL_job; + +typedef struct POOL_ctx_s { + POOL_job *queue; +} POOL_ctx; + +POOL_ctx* ctx_global; + +POOL_ctx* POOL_create(size_t numThreads, size_t queueSize) +{ + POOL_ctx* ctx_create; + ctx_create = (POOL_ctx*)malloc(sizeof(POOL_ctx)); + ctx_create->queue = (POOL_job*)malloc(queueSize * sizeof(POOL_job)); + + int r; // rand + if (r) + ctx_global = ctx_create; // pretend escape + return ctx_create; +} + +int main() { + while (1) { + POOL_ctx *ctx_main; + ctx_main = POOL_create(20, 10); + } +} diff --git a/tests/regression/09-regions/41-per-thread-array-init-race.c b/tests/regression/09-regions/41-per-thread-array-init-race.c new file mode 100644 index 0000000000..f6d267495e --- /dev/null +++ b/tests/regression/09-regions/41-per-thread-array-init-race.c @@ -0,0 +1,40 @@ +// PARAM: --set ana.activated[+] region --enable ana.sv-comp.functions +// Per-thread array pointers passed via argument but initialized before thread create. +// Extracted from silver searcher. +#include +#include +extern void abort(void); +void assume_abort_if_not(int cond) { + if(!cond) {abort();} +} +extern int __VERIFIER_nondet_int(); + +void *thread(void *arg) { + int *p = arg; + int i = *p; // RACE! + return NULL; +} + +int main() { + int threads_total = __VERIFIER_nondet_int(); + assume_abort_if_not(threads_total >= 0); + + pthread_t *tids = malloc(threads_total * sizeof(pthread_t)); + int *is = calloc(threads_total, sizeof(int)); + + // create threads + for (int i = 0; i < threads_total; i++) { + pthread_create(&tids[i], NULL, &thread, &is[i]); // may fail but doesn't matter + is[i] = i; // RACE! + } + + // join threads + for (int i = 0; i < threads_total; i++) { + pthread_join(tids[i], NULL); + } + + free(tids); + free(is); + + return 0; +} diff --git a/tests/regression/10-synch/07-thread_self_create.c b/tests/regression/10-synch/07-thread_self_create.c new file mode 100644 index 0000000000..473a26a25b --- /dev/null +++ b/tests/regression/10-synch/07-thread_self_create.c @@ -0,0 +1,15 @@ +// PARAM: --set ana.activated[+] thread +// Checks termination of thread analysis with a thread who is its own single parent. +#include + +void *t_fun(void *arg) { + pthread_t id; + pthread_create(&id, NULL, t_fun, NULL); + return NULL; +} + +int main(void) { + pthread_t id; + pthread_create(&id, NULL, t_fun, NULL); + return 0; +} diff --git a/tests/regression/10-synch/20-race-2_1-container_of.c b/tests/regression/10-synch/20-race-2_1-container_of.c index 6083cf4ca0..04d5facbb7 100644 --- a/tests/regression/10-synch/20-race-2_1-container_of.c +++ b/tests/regression/10-synch/20-race-2_1-container_of.c @@ -1,4 +1,4 @@ -// PARAM: --set ana.activated[+] thread --set ana.path_sens[+] threadflag +// PARAM: --set ana.activated[+] thread --set ana.path_sens[+] threadflag --enable ana.sv-comp.functions #include #include #include diff --git a/tests/regression/10-synch/28-join-array.c b/tests/regression/10-synch/28-join-array.c new file mode 100644 index 0000000000..99813b9810 --- /dev/null +++ b/tests/regression/10-synch/28-join-array.c @@ -0,0 +1,25 @@ +// PARAM: --set ana.activated[+] thread +#include + +int data = 0; +pthread_mutex_t data_mutex; + +void *thread(void *arg) { + pthread_mutex_lock(&data_mutex); + data = 3; // RACE! + pthread_mutex_unlock(&data_mutex); + return NULL; +} + +int main() { + pthread_t tids[2]; + + pthread_create(&tids[0], NULL, &thread, NULL); + pthread_create(&tids[1], NULL, &thread, NULL); + + pthread_join(tids[0], NULL); + + data = 1; //RACE! + + return 1; +} diff --git a/tests/regression/18-file/01-ok.c b/tests/regression/18-file/01-ok.c deleted file mode 100644 index 5c1f21ff1c..0000000000 --- a/tests/regression/18-file/01-ok.c +++ /dev/null @@ -1,12 +0,0 @@ -// PARAM: --set ana.activated[+] "'file'" --enable ana.file.optimistic - -#include - -int main(){ - FILE *fp; - fp = fopen("test.txt", "a"); - fprintf(fp, "Testing...\n"); - fclose(fp); -} - -// All ok! diff --git a/tests/regression/18-file/02-function.c b/tests/regression/18-file/02-function.c deleted file mode 100644 index fc3157c264..0000000000 --- a/tests/regression/18-file/02-function.c +++ /dev/null @@ -1,17 +0,0 @@ -// PARAM: --set ana.activated[+] "'file'" --enable ana.file.optimistic - -#include - -FILE *fp; - -void f(){ - fp = fopen("test.txt", "a"); -} - -int main(){ - f(); - fprintf(fp, "Testing...\n"); - fclose(fp); -} - -// All ok! diff --git a/tests/regression/18-file/03-if-close.c b/tests/regression/18-file/03-if-close.c deleted file mode 100644 index b2bf1ebe97..0000000000 --- a/tests/regression/18-file/03-if-close.c +++ /dev/null @@ -1,15 +0,0 @@ -// PARAM: --set ana.activated[+] "'file'" --enable ana.file.optimistic - -#include - -FILE *fp; - -int main(){ - int b; - fp = fopen("test.txt", "a"); // WARN: MAYBE file is never closed - - fprintf(fp, "Testing...\n"); - - if (b) - fclose(fp); -} // WARN: MAYBE unclosed files: fp diff --git a/tests/regression/18-file/04-no-open.c b/tests/regression/18-file/04-no-open.c deleted file mode 100644 index 70683f3852..0000000000 --- a/tests/regression/18-file/04-no-open.c +++ /dev/null @@ -1,10 +0,0 @@ -// PARAM: --set ana.activated[+] "'file'" --enable ana.file.optimistic - -#include - -FILE *fp; - -int main(){ - fprintf(fp, "Testing...\n"); // WARN: writing to unopened file handle fp - fclose(fp); // WARN: closeing unopened file handle fp -} diff --git a/tests/regression/18-file/05-open-mode.c b/tests/regression/18-file/05-open-mode.c deleted file mode 100644 index 77326d7a70..0000000000 --- a/tests/regression/18-file/05-open-mode.c +++ /dev/null @@ -1,11 +0,0 @@ -// PARAM: --set ana.activated[+] "'file'" --enable ana.file.optimistic - -#include - -FILE *fp; - -int main(){ - fp = fopen("test.txt", "r"); - fprintf(fp, "Testing...\n"); // WARN: writing to read-only file handle fp - fclose(fp); -} diff --git a/tests/regression/18-file/06-2open.c b/tests/regression/18-file/06-2open.c deleted file mode 100644 index 2826c2f1dc..0000000000 --- a/tests/regression/18-file/06-2open.c +++ /dev/null @@ -1,12 +0,0 @@ -// PARAM: --set ana.activated[+] "'file'" --enable ana.file.optimistic - -#include - -FILE *fp; - -int main(){ - fp = fopen("test1.txt", "a"); // WARN: file is never closed - fp = fopen("test2.txt", "a"); // WARN: overwriting still opened file handle fp - fprintf(fp, "Testing...\n"); - fclose(fp); -} // WARN: unclosed files: fp diff --git a/tests/regression/18-file/07-2close.c b/tests/regression/18-file/07-2close.c deleted file mode 100644 index 0545bf9814..0000000000 --- a/tests/regression/18-file/07-2close.c +++ /dev/null @@ -1,11 +0,0 @@ -// PARAM: --set ana.activated[+] "'file'" --enable ana.file.optimistic - -#include - -int main(){ - FILE *fp; - fp = fopen("test.txt", "a"); - fprintf(fp, "Testing...\n"); - fclose(fp); - fclose(fp); // WARN: closeing already closed file handle fp -} diff --git a/tests/regression/18-file/08-var-reuse.c b/tests/regression/18-file/08-var-reuse.c deleted file mode 100644 index 1caa238517..0000000000 --- a/tests/regression/18-file/08-var-reuse.c +++ /dev/null @@ -1,15 +0,0 @@ -// PARAM: --set ana.activated[+] "'file'" --enable ana.file.optimistic - -#include - -int main(){ - FILE *fp; - fp = fopen("test.txt", "a"); - fprintf(fp, "Testing...\n"); - fclose(fp); - fp = fopen("test2.txt", "a"); - fprintf(fp, "Testing...\n"); - fclose(fp); -} - -// All ok! diff --git a/tests/regression/18-file/09-inf-loop-no-close.c b/tests/regression/18-file/09-inf-loop-no-close.c deleted file mode 100644 index e9563ef195..0000000000 --- a/tests/regression/18-file/09-inf-loop-no-close.c +++ /dev/null @@ -1,17 +0,0 @@ -// PARAM: --set ana.activated[+] "'file'" --enable ana.file.optimistic - -#include - -FILE *fp; - -int main(){ - int i; - fp = fopen("test.txt", "a"); // WARN: file is never closed - - while (i){ - fprintf(fp, "Testing...\n"); - i++; - } - - //fclose(fp); -} // WARN: unclosed files: fp diff --git a/tests/regression/18-file/10-inf-loop-ok.c b/tests/regression/18-file/10-inf-loop-ok.c deleted file mode 100644 index d88fde272e..0000000000 --- a/tests/regression/18-file/10-inf-loop-ok.c +++ /dev/null @@ -1,19 +0,0 @@ -// PARAM: --set ana.activated[+] "'file'" --enable ana.file.optimistic - -#include - -FILE *fp; - -int main(){ - int i; - fp = fopen("test.txt", "a"); - - while (i){ - fprintf(fp, "Testing...\n"); - i++; - } - - fclose(fp); -} - -// All ok. diff --git a/tests/regression/18-file/11-2if.c b/tests/regression/18-file/11-2if.c deleted file mode 100644 index e24fec6e46..0000000000 --- a/tests/regression/18-file/11-2if.c +++ /dev/null @@ -1,18 +0,0 @@ -// PARAM: --set ana.activated[+] "'file'" --enable ana.file.optimistic - -#include - -FILE *fp; - -int main(){ - int b; - fp = fopen("test.txt", "a"); // WARN: MAYBE file is never closed - - if (b) - fclose(fp); - - fprintf(fp, "Testing...\n"); // WARN: MAYBE writing to closed file handle fp - - if (!b) - fclose(fp); // WARN: MAYBE closeing already closed file handle fp -} // WARN: MAYBE unclosed files: fp diff --git a/tests/regression/18-file/12-2close-if.c b/tests/regression/18-file/12-2close-if.c deleted file mode 100644 index 4934b33114..0000000000 --- a/tests/regression/18-file/12-2close-if.c +++ /dev/null @@ -1,15 +0,0 @@ -// PARAM: --set ana.activated[+] "'file'" --enable ana.file.optimistic - -#include - -int main(){ - FILE *fp; - int b; - fp = fopen("test.txt", "a"); - fprintf(fp, "Testing...\n"); - - if (b) - fclose(fp); - - fclose(fp); // WARN: MAYBE closeing already closed file handle fp -} diff --git a/tests/regression/18-file/13-ptr-arith-ok.c b/tests/regression/18-file/13-ptr-arith-ok.c deleted file mode 100644 index f707110957..0000000000 --- a/tests/regression/18-file/13-ptr-arith-ok.c +++ /dev/null @@ -1,16 +0,0 @@ -// PARAM: --set ana.activated[+] "'file'" --enable ana.file.optimistic - -#include - -int main(){ - FILE *fp; - fp = fopen("test.txt", "a"); // WARN: MAYBE file is never closed - fprintf(fp, "Testing...\n"); - - fp++; // WARN: changed pointer fp (no longer safe) - fp--; // WARN: changed pointer fp (no longer safe) - - fclose(fp); // WARN: MAYBE closeing already closed file handle fp -} // WARN: MAYBE unclosed files: fp - -// OPT: All ok! diff --git a/tests/regression/18-file/14-ptr-arith-close.c b/tests/regression/18-file/14-ptr-arith-close.c deleted file mode 100644 index 3f9cd21ee2..0000000000 --- a/tests/regression/18-file/14-ptr-arith-close.c +++ /dev/null @@ -1,13 +0,0 @@ -// PARAM: --set ana.activated[+] "'file'" --enable ana.file.optimistic - -#include - -int main(){ - FILE *fp; - fp = fopen("test.txt", "a"); // WARN: MAYBE file is never closed - fprintf(fp, "Testing...\n"); - - fp++; // WARN: changed pointer fp (no longer safe) - - fclose(fp); // WARN: MAYBE closeing already closed file handle fp -} // WARN: MAYBE unclosed files: fp diff --git a/tests/regression/18-file/15-var-switch.c b/tests/regression/18-file/15-var-switch.c deleted file mode 100644 index d7f74b85db..0000000000 --- a/tests/regression/18-file/15-var-switch.c +++ /dev/null @@ -1,18 +0,0 @@ -// PARAM: --set ana.activated[+] "'file'" --enable ana.file.optimistic - -#include - -int main(){ - FILE *fp1; - fp1 = fopen("test.txt", "a"); - fprintf(fp1, "Testing...\n"); - - FILE *fp2; - fp2 = fopen("test.txt", "a"); // WARN: file is never closed - fprintf(fp2, "Testing...\n"); - - fp2 = fp1; - - fclose(fp1); - fclose(fp2); // WARN: closeing already closed file handle fp2 -} // WARN: unclosed files: fp2 diff --git a/tests/regression/18-file/16-var-reuse-close.c b/tests/regression/18-file/16-var-reuse-close.c deleted file mode 100644 index cb1fb5fd22..0000000000 --- a/tests/regression/18-file/16-var-reuse-close.c +++ /dev/null @@ -1,14 +0,0 @@ -// PARAM: --set ana.activated[+] "'file'" --enable ana.file.optimistic - -#include - -int main(){ - FILE *fp; - fp = fopen("test.txt", "a"); - fprintf(fp, "Testing...\n"); - fclose(fp); - - fp = fopen("test.txt", "a"); // WARN: file is never closed - fprintf(fp, "Testing...\n"); - // fclose(fp); -} // WARN: unclosed files: fp diff --git a/tests/regression/18-file/17-myfopen.c b/tests/regression/18-file/17-myfopen.c deleted file mode 100644 index 3e005c6e70..0000000000 --- a/tests/regression/18-file/17-myfopen.c +++ /dev/null @@ -1,21 +0,0 @@ -// PARAM: --set ana.activated[+] "'file'" --enable ana.file.optimistic - -#include - - -FILE* myfopen(){ - // FILE *fp_tmp = fopen("test.txt", "a"); // local! - return fopen("test.txt", "a"); -} - -int main(){ - FILE *fp1; - FILE *fp2; - fp1 = myfopen(); - fp2 = myfopen(); // WARN: file is never closed - - fprintf(fp1, "Testing...\n"); - fclose(fp1); - fprintf(fp2, "Testing...\n"); - // fclose(fp2); -} // WARN: unclosed files: fp2 diff --git a/tests/regression/18-file/18-myfopen-arg.c b/tests/regression/18-file/18-myfopen-arg.c deleted file mode 100644 index 5d98db4c53..0000000000 --- a/tests/regression/18-file/18-myfopen-arg.c +++ /dev/null @@ -1,20 +0,0 @@ -// PARAM: --set ana.activated[+] "'file'" --enable ana.file.optimistic - -#include - - -FILE* myfopen(char* f){ - return fopen(f, "a"); -} - -int main(){ - FILE *fp1; - FILE *fp2; - fp1 = myfopen("test1.txt"); - fp2 = myfopen("test2.txt"); // WARN: file is never closed - - fprintf(fp1, "Testing...\n"); - fclose(fp1); - fprintf(fp2, "Testing...\n"); - // fclose(fp2); -} // WARN: unclosed files: fp2 diff --git a/tests/regression/18-file/19-if-close-else.c b/tests/regression/18-file/19-if-close-else.c deleted file mode 100644 index 049e8454b4..0000000000 --- a/tests/regression/18-file/19-if-close-else.c +++ /dev/null @@ -1,17 +0,0 @@ -// PARAM: --set ana.activated[+] "'file'" --enable ana.file.optimistic - -#include - -FILE *fp; - -int main(){ - int b; - fp = fopen("test.txt", "a"); - - if (b) - fclose(fp); - else - fprintf(fp, "Testing...\n"); - - fclose(fp); // WARN: MAYBE closeing already closed file handle fp -} diff --git a/tests/regression/18-file/20-loop-close.c b/tests/regression/18-file/20-loop-close.c deleted file mode 100644 index 981248c152..0000000000 --- a/tests/regression/18-file/20-loop-close.c +++ /dev/null @@ -1,18 +0,0 @@ -// PARAM: --set ana.activated[+] "'file'" --enable ana.file.optimistic - -#include - -FILE *fp; - -int main(){ - int i; - fp = fopen("test.txt", "a"); // WARN: MAYBE file is never closed - - while (i){ // May closed (11, 3), open(test.txt, Write) (7, 3) - fprintf(fp, "Testing...\n"); // WARN: MAYBE writing to closed file handle fp - fclose(fp); // WARN: MAYBE closeing already closed file handle fp - i++; - } - // why: fp -> Must open(test.txt, Write) (7, 3) - // -> because loop wouldn't exit? -} // WARN: MAYBE unclosed files: fp diff --git a/tests/regression/18-file/21-for-i.c b/tests/regression/18-file/21-for-i.c deleted file mode 100644 index e41bb9b005..0000000000 --- a/tests/regression/18-file/21-for-i.c +++ /dev/null @@ -1,26 +0,0 @@ -// PARAM: --set ana.activated[+] "'file'" --enable ana.file.optimistic - -#include - -FILE *fp; - -int main(){ - int i; - fp = fopen("test.txt", "w"); // WARN: MAYBE file is never closed - - for(i=1; i<10; i++){ // join - // i -> Unknown int - if(i%2){ - // i -> Unknown int - // fprintf(fp, "Testing...%s\n", i); // Segmentation fault! - // actually shouldn't warn because open and close are always alternating... - fprintf(fp, "Testing...%i\n", i); // WARN: MAYBE writing to closed file handle fp - fclose(fp); // WARN: MAYBE closeing already closed file handle fp - }else{ - fp = fopen("test.txt", "a"); // WARN: MAYBE file is never closed - } - // why no join? - } - // fp opened or closed? (last i=9 -> open) - // widening -> Warn: might be unclosed -} // WARN: MAYBE unclosed files: fp diff --git a/tests/regression/18-file/22-f_int.c b/tests/regression/18-file/22-f_int.c deleted file mode 100644 index f0376fc5a9..0000000000 --- a/tests/regression/18-file/22-f_int.c +++ /dev/null @@ -1,13 +0,0 @@ -// PARAM: --set ana.activated[+] "'file'" --enable ana.file.optimistic - -#include - -int f(int x){ - return 2*x; -} - -int main(){ - int a = 1; - a = f(2); - return 0; -} diff --git a/tests/regression/18-file/23-f_str.c b/tests/regression/18-file/23-f_str.c deleted file mode 100644 index 81224d2e72..0000000000 --- a/tests/regression/18-file/23-f_str.c +++ /dev/null @@ -1,13 +0,0 @@ -// PARAM: --set ana.activated[+] "'file'" --enable ana.file.optimistic - -#include - -char* f(char* x){ - return x; -} - -int main(){ - char* a = "foo"; - a = f("bar"); - return 0; -} diff --git a/tests/regression/18-file/24-f_wstr.c b/tests/regression/18-file/24-f_wstr.c deleted file mode 100644 index 2379c1f718..0000000000 --- a/tests/regression/18-file/24-f_wstr.c +++ /dev/null @@ -1,14 +0,0 @@ -// PARAM: --set ana.activated[+] "'file'" --enable ana.file.optimistic - -#include -#include - -wchar_t* f(wchar_t* x){ - return x; -} - -int main(){ - wchar_t* a = L"foo"; - a = f(L"bar"); - return 0; -} diff --git a/tests/regression/18-file/25-mem-ok.c b/tests/regression/18-file/25-mem-ok.c deleted file mode 100644 index 00ba189b8d..0000000000 --- a/tests/regression/18-file/25-mem-ok.c +++ /dev/null @@ -1,29 +0,0 @@ -// PARAM: --set ana.activated[+] "'file'" --enable ana.file.optimistic - -#include - -int main(){ - FILE *fp[3]; - // Array -> varinfo with index-offset - fp[1] = fopen("test.txt", "a"); - fprintf(fp[1], "Testing...\n"); - fclose(fp[1]); - - - struct foo { - int i; - FILE *fp; - } bar; - // Struct -> varinfo with field-offset - bar.fp = fopen("test.txt", "a"); - fprintf(bar.fp, "Testing...\n"); - fclose(bar.fp); - - - // Pointer -> Mem exp - *(fp+2) = fopen("test.txt", "a"); - fprintf(*(fp+2), "Testing...\n"); - fclose(*(fp+2)); -} - -// All ok! diff --git a/tests/regression/18-file/26-open-error-ok.c b/tests/regression/18-file/26-open-error-ok.c deleted file mode 100644 index 5cf3aaf7bb..0000000000 --- a/tests/regression/18-file/26-open-error-ok.c +++ /dev/null @@ -1,15 +0,0 @@ -// PARAM: --set ana.activated[+] "'file'" --enable ana.file.optimistic - -#include - -int main (){ - FILE *fp; - fp = fopen("test.txt", "w"); - - if(fp!=NULL){ - fprintf(fp, "Testing..."); - fclose(fp); - } -} - -// All ok! diff --git a/tests/regression/18-file/27-open-error.c b/tests/regression/18-file/27-open-error.c deleted file mode 100644 index bd3048208f..0000000000 --- a/tests/regression/18-file/27-open-error.c +++ /dev/null @@ -1,13 +0,0 @@ -// PARAM: --set ana.activated[+] "'file'" --enable ana.file.optimistic - -#include - -int main (){ - FILE *fp; - fp = fopen("test.txt", "w"); // WARN: MAYBE file is never closed - - if(fp==NULL){ - fprintf(fp, "Testing..."); // WARN: writing to unopened file handle fp - fclose(fp); // WARN: closeing unopened file handle fp - } -} // WARN: MAYBE unclosed files: fp diff --git a/tests/regression/18-file/28-multiple-exits.c b/tests/regression/18-file/28-multiple-exits.c deleted file mode 100644 index 04fa5abab0..0000000000 --- a/tests/regression/18-file/28-multiple-exits.c +++ /dev/null @@ -1,14 +0,0 @@ -// PARAM: --set ana.activated[+] "'file'" --enable ana.file.optimistic - -#include - -int main(){ - FILE *fp; - fp = fopen("test.txt", "a"); // WARN: MAYBE file is never closed - fprintf(fp, "Testing...\n"); - int b; - if(b) - return 1; // WARN: unclosed files: fp - fclose(fp); - return 0; -} diff --git a/tests/regression/18-file/29-alias-global.c b/tests/regression/18-file/29-alias-global.c deleted file mode 100644 index 17b94748c0..0000000000 --- a/tests/regression/18-file/29-alias-global.c +++ /dev/null @@ -1,22 +0,0 @@ -// PARAM: --set ana.activated[+] "'file'" --enable ana.file.optimistic - -#include - -FILE* fp; -FILE* myfopen(char* f){ - fp = fopen(f, "a"); - return fp; -} - -int main(){ - FILE *fp1; - FILE *fp2; - fp1 = myfopen("test1.txt"); - fp2 = myfopen("test2.txt"); - fprintf(fp1, "Testing...\n"); - fclose(fp1); - fprintf(fp2, "Testing...\n"); - fclose(fp2); -} - -// All ok! diff --git a/tests/regression/18-file/30-ptr-of-ptr.c b/tests/regression/18-file/30-ptr-of-ptr.c deleted file mode 100644 index 5a8d1f97a9..0000000000 --- a/tests/regression/18-file/30-ptr-of-ptr.c +++ /dev/null @@ -1,14 +0,0 @@ -// PARAM: --set ana.activated[+] "'file'" --enable ana.file.optimistic - -#include - -int main(){ - FILE *fp1; - fp1 = fopen("test.txt", "a"); - FILE **fp2; - - fp2 = &fp1; - - fclose(fp1); - fclose(*fp2); // WARN: closeing already closed file handle fp1 -} diff --git a/tests/regression/18-file/31-var-reuse-fun.c b/tests/regression/18-file/31-var-reuse-fun.c deleted file mode 100644 index 9c0ccb16a2..0000000000 --- a/tests/regression/18-file/31-var-reuse-fun.c +++ /dev/null @@ -1,16 +0,0 @@ -// PARAM: --set ana.activated[+] "'file'" --enable ana.file.optimistic - -#include - -FILE* myfopen(char* f){ - FILE* fp; - fp = fopen(f, "a"); - return fp; -} - -int main(){ - FILE *fp; - fp = fopen("test1.txt", "a"); // WARN: file is never closed - fp = myfopen("test2.txt"); // WARN: overwriting still opened file handle fp - fclose(fp); -} // WARN: unclosed files: fp diff --git a/tests/regression/18-file/32-multi-ptr-close.c b/tests/regression/18-file/32-multi-ptr-close.c deleted file mode 100644 index e252d563a5..0000000000 --- a/tests/regression/18-file/32-multi-ptr-close.c +++ /dev/null @@ -1,25 +0,0 @@ -// PARAM: --set ana.activated[+] "'file'" --enable ana.file.optimistic - -#include - -int main(){ - FILE *fp1; - fp1 = fopen("test1.txt", "a"); - fprintf(fp1, "Testing...\n"); - - FILE *fp2; - fp2 = fopen("test2.txt", "a"); - fprintf(fp2, "Testing...\n"); - - FILE **fp; - int b; - if(b){ - fp = &fp1; - }else{ - fp = &fp2; - } - - fclose(*fp); - fclose(fp1); // WARN: MAYBE closeing already closed file handle fp1 - fclose(fp2); // WARN: MAYBE closeing already closed file handle fp2 -} diff --git a/tests/regression/18-file/33-multi-ptr-open.c b/tests/regression/18-file/33-multi-ptr-open.c deleted file mode 100644 index b3cfa0ade4..0000000000 --- a/tests/regression/18-file/33-multi-ptr-open.c +++ /dev/null @@ -1,23 +0,0 @@ -// PARAM: --set ana.activated[+] "'file'" --enable ana.file.optimistic - -#include - -int main(){ - FILE *fp1; - fp1 = fopen("test1.txt", "a"); // WARN: MAYBE file is never closed - - FILE *fp2; - fp2 = fopen("test2.txt", "r"); // WARN: MAYBE file is never closed - - FILE **fp; - int b; - if(b){ - fp = &fp1; - }else{ - fp = &fp2; - } - - fprintf(*fp, "Testing...\n"); // WARN: MAYBE writing to read-only file handle fp - - fclose(*fp); -} // WARN: MAYBE unclosed files: fp1, fp2 diff --git a/tests/regression/18-file/34-multi-alias-close.c b/tests/regression/18-file/34-multi-alias-close.c deleted file mode 100644 index 0ebb9ddd30..0000000000 --- a/tests/regression/18-file/34-multi-alias-close.c +++ /dev/null @@ -1,25 +0,0 @@ -// SKIP PARAM: --set ana.activated[+] "'file'" --enable ana.file.optimistic - -#include - -int main(){ - FILE *fp1; - fp1 = fopen("test1.txt", "a"); - fprintf(fp1, "Testing...\n"); - - FILE *fp2; - fp2 = fopen("test2.txt", "a"); - fprintf(fp2, "Testing...\n"); - - FILE *fp; - int b; - if(b){ - fp = fp1; - }else{ - fp = fp2; - } - - fclose(fp); - fclose(fp1); // WARN: MAYBE closeing already closed file handle fp1 - fclose(fp2); // WARN: MAYBE closeing already closed file handle fp2 -} diff --git a/tests/regression/18-file/35-multi-alias-open.c b/tests/regression/18-file/35-multi-alias-open.c deleted file mode 100644 index 21a4a9cca6..0000000000 --- a/tests/regression/18-file/35-multi-alias-open.c +++ /dev/null @@ -1,23 +0,0 @@ -// SKIP PARAM: --set ana.activated[+] "'file'" --enable ana.file.optimistic - -#include - -int main(){ - FILE *fp1; - fp1 = fopen("test1.txt", "a"); // WARN: MAYBE file is never closed - - FILE *fp2; - fp2 = fopen("test2.txt", "r"); // WARN: MAYBE file is never closed - - FILE *fp; - int b; - if(b){ - fp = fp1; - }else{ - fp = fp2; - } - - fprintf(fp, "Testing...\n"); // WARN: MAYBE writing to read-only file handle fp - - fclose(fp); -} // WARN: MAYBE unclosed files: fp1, fp2 diff --git a/tests/regression/18-file/36-fun-ptr.c b/tests/regression/18-file/36-fun-ptr.c deleted file mode 100644 index 4f70bf7382..0000000000 --- a/tests/regression/18-file/36-fun-ptr.c +++ /dev/null @@ -1,14 +0,0 @@ -// PARAM: --set ana.activated[+] "'file'" --enable ana.file.optimistic - -#include - -int main(){ - FILE *fp; - FILE* (*f)(const char *, const char*); - f = fopen; - fp = f("test.txt", "a"); - fprintf(fp, "Testing...\n"); - fclose(fp); -} - -// All ok! diff --git a/tests/regression/18-file/37-var-switch-alias.c b/tests/regression/18-file/37-var-switch-alias.c deleted file mode 100644 index 5dfde5a2d9..0000000000 --- a/tests/regression/18-file/37-var-switch-alias.c +++ /dev/null @@ -1,18 +0,0 @@ -// PARAM: --set ana.activated[+] "'file'" --enable ana.file.optimistic - -#include - -int main(){ - FILE *fp1; - fp1 = fopen("test.txt", "a"); - fprintf(fp1, "Testing...\n"); - - FILE *fp2; - fp2 = fopen("test.txt", "a"); // WARN: file is never closed - fprintf(fp2, "Testing...\n"); - - fp2 = fp1; - - fclose(fp2); - fclose(fp1); // WARN: closeing already closed file handle fp1 -} // WARN: unclosed files: fp2 diff --git a/tests/regression/18-file/README.md b/tests/regression/18-file/README.md new file mode 100644 index 0000000000..0e93e175c6 --- /dev/null +++ b/tests/regression/18-file/README.md @@ -0,0 +1,2 @@ +The file analysis has been removed from recent Goblint versions, please use Release 2.3.0 +Folder is left in place to avoid renumbering all tests diff --git a/tests/regression/18-file/file.c b/tests/regression/18-file/file.c deleted file mode 100644 index fc2ebe1699..0000000000 --- a/tests/regression/18-file/file.c +++ /dev/null @@ -1,44 +0,0 @@ -#include - -int main(){ - - // no errors - FILE *fp; - fp = fopen("test.txt", "a"); - if(fp!=0) { - fprintf(fp, "Testing...\n"); - fclose(fp); - } - - // missing fopen -> compiles, but leads to Segmentation fault - FILE *fp2; - // fp2 = fopen("test.txt", "a"); - fprintf(fp2, "Testing...\n"); // WARN - fclose(fp2); // WARN - - // writing to a read-only file -> doesn't do anything - FILE *fp3; - fp3 = fopen("test.txt", "r"); - fprintf(fp3, "Testing...\n"); // (WARN) - fclose(fp3); - - // accessing closed file -> write doesn't do anything - FILE *fp4; - fp4 = fopen("test.txt", "a"); - fclose(fp4); - fprintf(fp4, "Testing...\n"); // WARN - - // missing fclose - FILE *fp5; - fp5 = fopen("test.txt", "a"); // WARN - fprintf(fp5, "Testing...\n"); - - // missing assignment to file handle - fopen("test.txt", "a"); // WARN - - - // bad style: - // opening file but not doing anything - - return 0; // WARN about all unclosed files -} \ No newline at end of file diff --git a/tests/regression/18-file/file.optimistic.spec b/tests/regression/18-file/file.optimistic.spec deleted file mode 100644 index d42e2217b7..0000000000 --- a/tests/regression/18-file/file.optimistic.spec +++ /dev/null @@ -1,34 +0,0 @@ -w1 "file handle is not saved!" -w2 "closeing unopened file handle $" -w3 "writing to unopened file handle $" -w4 "writing to read-only file handle $" -w5 "closeing already closed file handle $" -w6 "writing to closed file handle $" -w7 "overwriting still opened file handle $" -w8 "unrecognized file open mode for file handle $" - -1 -> w1 fopen(_, _) -1 -> w2 fclose($fp) -1 -> w3 fprintf($fp, _) -1 -> open_read $fp = fopen(path, "r") -1 -> open_write $fp = fopen(path, r"[wa]") // see OCaml doc for details (e.g. \\| for alternatives) -1 -> w8 $fp = fopen(path, _) - -open_read -> w4 fprintf($fp, _) - -open_read -w7>> 1 $fp = fopen(path, _) -open_write -w7>> 1 $fp = fopen(path, _) - -open_read -> closed fclose($fp) -open_write -> closed fclose($fp) - -closed -> w5 fclose($fp) -closed -> w6 fprintf($fp, _) -closed ->> 1 _ // let state 1 handle the rest - -// setup which states are end states -1 -> end _ -closed -> end _ -// warning for all entries that are not in an end state -_end "file is never closed" -_END "unclosed files: $" \ No newline at end of file diff --git a/tests/regression/18-file/file.spec b/tests/regression/18-file/file.spec deleted file mode 100644 index aeb747abfd..0000000000 --- a/tests/regression/18-file/file.spec +++ /dev/null @@ -1,57 +0,0 @@ -w1 "file handle is not saved!" -w2 "closeing unopened file handle $" -w3 "writing to unopened file handle $" -w4 "writing to read-only file handle $" -w5 "closeing already closed file handle $" -w6 "writing to closed file handle $" -w7 "overwriting still opened file handle $" -w8 "unrecognized file open mode for file handle $" - -// TODO later add fputs and stuff -1 -> w1 fopen(_, _) -1 -> w2 fclose($fp) -1 -> w3 fprintf($fp, _) -//1 -> open_read $fp = fopen(path, "r") -//1 -> open_write $fp = fopen(path, r"[wa]") // see OCaml doc for details (e.g. \\| for alternatives) -//1 -> w8 $fp = fopen(path, _) - -// go to unchecked states first -1 -> u_open_read $fp = fopen(path, "r") -1 -> u_open_write $fp = fopen(path, r"[wa]") -1 -> w8 $fp = fopen(path, _) -// once branch(exp, tv) is matched, return dom with 1. arg (lval = exp) and true/false -// forwarding from branch is not possible (would need an extra map for storing states) -> ignore it -// warnings are also ignored -// then in branch take out lval, check exp and do the transition to a checked state -u_open_read -> 1 branch($key==0, true) -u_open_read -> open_read branch($key==0, false) -u_open_write -> 1 branch($key==0, true) -u_open_write -> open_write branch($key==0, false) - -// alternative: forward everything. Problem: saving arguments of call (special_fn -> branch -> special_fn) -// 1 ->> open_check $fp = fopen(path, _) -// open_check ->> 1 branch($fp==0, true) -// open_check ->> open branch($fp==0, false) -// open -> open_read $fp = fopen(path, "r") -// open -> open_write $fp = fopen(path, "[wa]") -// open -> w8 $fp = fopen(path, _) - -open_read -> w4 fprintf($fp, _) -// open_write -> open_write fprintf($fp, _) // not needed, but changes loc - -open_read -w7>> 1 $fp = fopen(path, _) -open_write -w7>> 1 $fp = fopen(path, _) - -open_read -> closed fclose($fp) -open_write -> closed fclose($fp) - -closed -> w5 fclose($fp) -closed -> w6 fprintf($fp, _) -closed ->> 1 _ // let state 1 handle the rest - -// setup which states are end states -1 -> end _ -closed -> end _ -// warning for all entries that are not in an end state -_end "file is never closed" -_END "unclosed files: $" \ No newline at end of file diff --git a/tests/regression/19-spec/01-malloc-free.c b/tests/regression/19-spec/01-malloc-free.c deleted file mode 100644 index 43ee527dba..0000000000 --- a/tests/regression/19-spec/01-malloc-free.c +++ /dev/null @@ -1,19 +0,0 @@ -#include -#include - -int main(){ - int *ip; - //*ip = 5; // segfault - //printf("%i", *ip); // segfault - ip = malloc(sizeof(int)); // assume malloc never fails - - // do stuff - //*ip = 5; - - free(ip); - //free(ip); // crash: double free or corruption - *ip = 5; // undefined but no crash - printf("%i", *ip); // undefined but printed 5 - ip = NULL; // make sure the pointer is not used anymore - *ip = 5; // segfault -} diff --git a/tests/regression/19-spec/README.md b/tests/regression/19-spec/README.md new file mode 100644 index 0000000000..d7e3ae3c8e --- /dev/null +++ b/tests/regression/19-spec/README.md @@ -0,0 +1,2 @@ +The spec analysis has been removed from recent Goblint versions, please use Release 2.3.0 +Folder is left in place to avoid renumbering all tests diff --git a/tests/regression/19-spec/malloc.optimistic.spec b/tests/regression/19-spec/malloc.optimistic.spec deleted file mode 100644 index 860c573814..0000000000 --- a/tests/regression/19-spec/malloc.optimistic.spec +++ /dev/null @@ -1,23 +0,0 @@ -w1 "pointer is not saved [leak]" -w2 "freeing unallocated pointer $ [segfault?]" -w3 "writing to unallocated pointer $ [segfault?]" -w4 "overwriting unfreed pointer $ [leak]" -w5 "freeing already freed pointer $ [double free!]" - -1 -w1> 1 malloc(_) -1 -w2> 1 free($p) -1 -w3> 1 *$p = _ -1 -> alloc $p = malloc(_) // TODO does compiler check size? - -alloc -w4> alloc $p = malloc(_) -alloc -> freed free($p) - -freed -w5> freed free($p) -freed ->> 1 _ // let state 1 handle the rest - -// setup which states are end states -1 -> end _ -freed -> end _ -// warning for all entries that are not in an end state -_end "pointer is never freed" -_END "unfreed pointers: $" \ No newline at end of file diff --git a/tests/regression/19-spec/malloc.spec b/tests/regression/19-spec/malloc.spec deleted file mode 100644 index 9f09430051..0000000000 --- a/tests/regression/19-spec/malloc.spec +++ /dev/null @@ -1,26 +0,0 @@ -w1 "pointer is not saved [leak]" -w2 "freeing unallocated pointer $ [segfault?]" -w3 "writing to unallocated pointer $ [segfault?]" -w4 "overwriting unfreed pointer $ [leak]" -w5 "freeing already freed pointer $ [double free!]" - -1 -w1> 1 malloc(_) -1 -w2> 1 free($p) -1 -w3> 1 *$p = _ -1 -> u_alloc $p = malloc(_) - -u_alloc -> 1 branch($key==0, true) -u_alloc -> alloc branch($key==0, false) - -alloc -w4> alloc $p = malloc(_) -alloc -> freed free($p) - -freed -w5> freed free($p) -freed ->> 1 _ // let state 1 handle the rest - -// setup which states are end states -1 -> end _ -freed -> end _ -// warning for all entries that are not in an end state -_end "pointer is never freed" -_END "unfreed pointers: $" \ No newline at end of file diff --git a/tests/regression/19-spec/mutex-lock.spec b/tests/regression/19-spec/mutex-lock.spec deleted file mode 100644 index 1ec8264078..0000000000 --- a/tests/regression/19-spec/mutex-lock.spec +++ /dev/null @@ -1,31 +0,0 @@ -w1 "unlocking not locked mutex" -w2 "locking already locked mutex" - -1 -w1> 1 pthread_mutex_unlock($p) -1 -> lock pthread_mutex_lock($p) - -lock -w2> lock pthread_mutex_lock($p) -lock -> 1 pthread_mutex_unlock($p) - -// setup which states are end states -1 -> end _ -// warning for all entries that are not in an end state -_end "mutex is never unlocked" -_END "locked mutexes: $" - - - -//w1 "joining not created thread" -//w2 "overwriting id of already created thread" -// -//1 -w1> 1 pthread_join ($p, _) -//1 -> created pthread_create($p, _, _, _) -// -//created -w2> created pthread_create($p, _, _, _) -//created -> 1 pthread_join ($p, _) -// -//// setup which states are end states -//1 -> end _ -//// warning for all entries that are not in an end state -//_end "thread is never joined" -//_END "unjoined threads: $" \ No newline at end of file diff --git a/tests/regression/28-race_reach/01-simple_racing.c b/tests/regression/28-race_reach/01-simple_racing.c index 16a0fb28c9..f444228690 100644 --- a/tests/regression/28-race_reach/01-simple_racing.c +++ b/tests/regression/28-race_reach/01-simple_racing.c @@ -1,3 +1,4 @@ +//PARAM: --set lib.activated[+] sv-comp #include #include "racemacros.h" @@ -19,4 +20,4 @@ int main(void) { pthread_mutex_unlock(&mutex2); join_threads(t); return 0; -} \ No newline at end of file +} diff --git a/tests/regression/28-race_reach/02-simple_racefree.c b/tests/regression/28-race_reach/02-simple_racefree.c index 4713ccd48d..0e35f8da67 100644 --- a/tests/regression/28-race_reach/02-simple_racefree.c +++ b/tests/regression/28-race_reach/02-simple_racefree.c @@ -1,3 +1,4 @@ +//PARAM: --set lib.activated[+] sv-comp #include #include "racemacros.h" @@ -19,4 +20,4 @@ int main(void) { pthread_mutex_unlock(&mutex1); join_threads(t); return 0; -} \ No newline at end of file +} diff --git a/tests/regression/28-race_reach/03-munge_racing.c b/tests/regression/28-race_reach/03-munge_racing.c index 3c279d597a..9b8536e540 100644 --- a/tests/regression/28-race_reach/03-munge_racing.c +++ b/tests/regression/28-race_reach/03-munge_racing.c @@ -1,3 +1,4 @@ +//PARAM: --set lib.activated[+] sv-comp #include #include "racemacros.h" @@ -26,4 +27,4 @@ int main(void) { create_threads(t1); create_threads(t2); join_threads(t1); join_threads(t2); return 0; -} \ No newline at end of file +} diff --git a/tests/regression/28-race_reach/04-munge_racefree.c b/tests/regression/28-race_reach/04-munge_racefree.c index 799477e6ae..86637da91f 100644 --- a/tests/regression/28-race_reach/04-munge_racefree.c +++ b/tests/regression/28-race_reach/04-munge_racefree.c @@ -1,3 +1,4 @@ +//PARAM: --set lib.activated[+] sv-comp #include #include "racemacros.h" @@ -26,4 +27,4 @@ int main(void) { create_threads(t1); create_threads(t2); join_threads(t1); join_threads(t2); return 0; -} \ No newline at end of file +} diff --git a/tests/regression/28-race_reach/05-lockfuns_racefree.c b/tests/regression/28-race_reach/05-lockfuns_racefree.c index 0faecd0217..0a904005f8 100644 --- a/tests/regression/28-race_reach/05-lockfuns_racefree.c +++ b/tests/regression/28-race_reach/05-lockfuns_racefree.c @@ -1,3 +1,4 @@ +//PARAM: --set lib.activated[+] sv-comp #include #include "racemacros.h" @@ -26,4 +27,4 @@ int main(void) { unlock(); join_threads(t); return 0; -} \ No newline at end of file +} diff --git a/tests/regression/28-race_reach/06-cond_racing1.c b/tests/regression/28-race_reach/06-cond_racing1.c index c3e87507d5..931b68f81f 100644 --- a/tests/regression/28-race_reach/06-cond_racing1.c +++ b/tests/regression/28-race_reach/06-cond_racing1.c @@ -1,3 +1,4 @@ +//PARAM: --set lib.activated[+] sv-comp #include #include @@ -31,7 +32,3 @@ int main() { join_threads(t); return 0; } - - - - diff --git a/tests/regression/28-race_reach/07-cond_racing2.c b/tests/regression/28-race_reach/07-cond_racing2.c index b13b52dd1c..5e0d3f77f5 100644 --- a/tests/regression/28-race_reach/07-cond_racing2.c +++ b/tests/regression/28-race_reach/07-cond_racing2.c @@ -1,3 +1,4 @@ +//PARAM: --set lib.activated[+] sv-comp #include #include #include "racemacros.h" diff --git a/tests/regression/28-race_reach/08-cond_racefree.c b/tests/regression/28-race_reach/08-cond_racefree.c index ce18620121..8d86e89cf5 100644 --- a/tests/regression/28-race_reach/08-cond_racefree.c +++ b/tests/regression/28-race_reach/08-cond_racefree.c @@ -1,3 +1,4 @@ +//PARAM: --set lib.activated[+] sv-comp #include #include #include "racemacros.h" diff --git a/tests/regression/28-race_reach/09-ptrmunge_racing.c b/tests/regression/28-race_reach/09-ptrmunge_racing.c index 3191ca3ead..eb6a098800 100644 --- a/tests/regression/28-race_reach/09-ptrmunge_racing.c +++ b/tests/regression/28-race_reach/09-ptrmunge_racing.c @@ -1,3 +1,4 @@ +//PARAM: --set lib.activated[+] sv-comp #include #include "racemacros.h" @@ -27,4 +28,4 @@ int main(void) { create_threads(t1); create_threads(t2); join_threads(t1); join_threads(t2); return 0; -} \ No newline at end of file +} diff --git a/tests/regression/28-race_reach/10-ptrmunge_racefree.c b/tests/regression/28-race_reach/10-ptrmunge_racefree.c index d4e2144971..b21a1e9480 100644 --- a/tests/regression/28-race_reach/10-ptrmunge_racefree.c +++ b/tests/regression/28-race_reach/10-ptrmunge_racefree.c @@ -1,3 +1,4 @@ +//PARAM: --set lib.activated[+] sv-comp #include #include "racemacros.h" @@ -27,4 +28,4 @@ int main(void) { create_threads(t1); create_threads(t2); join_threads(t1); join_threads(t2); return 0; -} \ No newline at end of file +} diff --git a/tests/regression/28-race_reach/11-ptr_racing.c b/tests/regression/28-race_reach/11-ptr_racing.c index dc3f3c1a21..d6851afa82 100644 --- a/tests/regression/28-race_reach/11-ptr_racing.c +++ b/tests/regression/28-race_reach/11-ptr_racing.c @@ -1,3 +1,4 @@ +// PARAM: --set lib.activated[+] sv-comp #include #include "racemacros.h" @@ -20,4 +21,4 @@ int main(void) { pthread_mutex_unlock(&mutex2); join_threads(t); return 0; -} \ No newline at end of file +} diff --git a/tests/regression/28-race_reach/12-ptr_racefree.c b/tests/regression/28-race_reach/12-ptr_racefree.c index bb7bcffa3d..b22a942eda 100644 --- a/tests/regression/28-race_reach/12-ptr_racefree.c +++ b/tests/regression/28-race_reach/12-ptr_racefree.c @@ -1,3 +1,4 @@ +// PARAM: --set lib.activated[+] sv-comp #include #include "racemacros.h" @@ -16,8 +17,8 @@ void *t_fun(void *arg) { int main(void) { create_threads(t); pthread_mutex_lock(&mutex1); - assert_racefree(global); + assert_racefree(global); pthread_mutex_unlock(&mutex1); join_threads(t); return 0; -} \ No newline at end of file +} diff --git a/tests/regression/28-race_reach/19-callback_racing.c b/tests/regression/28-race_reach/19-callback_racing.c index 798d1e2783..04eaaeef4f 100644 --- a/tests/regression/28-race_reach/19-callback_racing.c +++ b/tests/regression/28-race_reach/19-callback_racing.c @@ -1,3 +1,4 @@ +// PARAM: --set lib.activated[+] sv-comp extern int __VERIFIER_nondet_int(); #include diff --git a/tests/regression/28-race_reach/20-callback_racefree.c b/tests/regression/28-race_reach/20-callback_racefree.c index 6f30ef492d..e41896f31a 100644 --- a/tests/regression/28-race_reach/20-callback_racefree.c +++ b/tests/regression/28-race_reach/20-callback_racefree.c @@ -1,3 +1,4 @@ +// PARAM: --set lib.activated[+] sv-comp extern int __VERIFIER_nondet_int(); #include diff --git a/tests/regression/28-race_reach/21-deref_read_racing.c b/tests/regression/28-race_reach/21-deref_read_racing.c index 93166f8125..880a8a4d38 100644 --- a/tests/regression/28-race_reach/21-deref_read_racing.c +++ b/tests/regression/28-race_reach/21-deref_read_racing.c @@ -1,3 +1,4 @@ +// PARAM: --set lib.activated[+] sv-comp #include #include "racemacros.h" diff --git a/tests/regression/28-race_reach/22-deref_read_racefree.c b/tests/regression/28-race_reach/22-deref_read_racefree.c index 3386277083..9ed4fb03cf 100644 --- a/tests/regression/28-race_reach/22-deref_read_racefree.c +++ b/tests/regression/28-race_reach/22-deref_read_racefree.c @@ -1,3 +1,4 @@ +// PARAM: --set lib.activated[+] sv-comp #include #include "racemacros.h" @@ -17,7 +18,7 @@ int main() { create_threads(t); q = p; pthread_mutex_lock(&mutex); - assert_racefree(*q); // TODO + assert_racefree(*q); pthread_mutex_unlock(&mutex); return 0; } diff --git a/tests/regression/28-race_reach/23-sound_unlock_racing.c b/tests/regression/28-race_reach/23-sound_unlock_racing.c index da8db888db..c3ed280fbd 100644 --- a/tests/regression/28-race_reach/23-sound_unlock_racing.c +++ b/tests/regression/28-race_reach/23-sound_unlock_racing.c @@ -1,3 +1,4 @@ +// PARAM: --set lib.activated[+] sv-comp #include #include "racemacros.h" diff --git a/tests/regression/28-race_reach/24-sound_lock_racing.c b/tests/regression/28-race_reach/24-sound_lock_racing.c index 89ed5103dc..597bea716c 100644 --- a/tests/regression/28-race_reach/24-sound_lock_racing.c +++ b/tests/regression/28-race_reach/24-sound_lock_racing.c @@ -1,3 +1,4 @@ +// PARAM: --set lib.activated[+] sv-comp #include #include "racemacros.h" diff --git a/tests/regression/28-race_reach/27-funptr_racing.c b/tests/regression/28-race_reach/27-funptr_racing.c index 2c970deaf3..7210d0d56c 100644 --- a/tests/regression/28-race_reach/27-funptr_racing.c +++ b/tests/regression/28-race_reach/27-funptr_racing.c @@ -1,3 +1,4 @@ +// PARAM: --set lib.activated[+] sv-comp #include #include #include "racemacros.h" @@ -5,11 +6,11 @@ int global; pthread_mutex_t gm = PTHREAD_MUTEX_INITIALIZER; -void bad() { +void bad() { access(global); } -void good() { +void good() { pthread_mutex_lock(&gm); access(global); pthread_mutex_unlock(&gm); @@ -42,4 +43,4 @@ int main() { join_threads(t); return 0; -} \ No newline at end of file +} diff --git a/tests/regression/28-race_reach/28-funptr_racefree.c b/tests/regression/28-race_reach/28-funptr_racefree.c index 4e39156ecf..f36168aaaa 100644 --- a/tests/regression/28-race_reach/28-funptr_racefree.c +++ b/tests/regression/28-race_reach/28-funptr_racefree.c @@ -1,3 +1,4 @@ +// PARAM: --set lib.activated[+] sv-comp #include #include #include "racemacros.h" @@ -5,10 +6,10 @@ int global = 0; pthread_mutex_t gm = PTHREAD_MUTEX_INITIALIZER; -void bad() { +void bad() { access(global); -} -void good() { +} +void good() { pthread_mutex_lock(&gm); access(global); pthread_mutex_unlock(&gm); @@ -41,4 +42,4 @@ int main() { join_threads(t); return 0; -} \ No newline at end of file +} diff --git a/tests/regression/28-race_reach/36-indirect_racefree.c b/tests/regression/28-race_reach/36-indirect_racefree.c index 97dd10fc85..a2733f9df3 100644 --- a/tests/regression/28-race_reach/36-indirect_racefree.c +++ b/tests/regression/28-race_reach/36-indirect_racefree.c @@ -1,3 +1,4 @@ +// PARAM: --set lib.activated[+] sv-comp #include #include "racemacros.h" diff --git a/tests/regression/28-race_reach/37-indirect_racing.c b/tests/regression/28-race_reach/37-indirect_racing.c index e769a24836..6bf5757991 100644 --- a/tests/regression/28-race_reach/37-indirect_racing.c +++ b/tests/regression/28-race_reach/37-indirect_racing.c @@ -1,3 +1,4 @@ +// PARAM: --set lib.activated[+] sv-comp #include #include "racemacros.h" diff --git a/tests/regression/28-race_reach/40-trylock_racing.c b/tests/regression/28-race_reach/40-trylock_racing.c index 4d9c4acb98..94694bc1eb 100644 --- a/tests/regression/28-race_reach/40-trylock_racing.c +++ b/tests/regression/28-race_reach/40-trylock_racing.c @@ -1,3 +1,4 @@ +// PARAM: --set lib.activated[+] sv-comp #include #include "racemacros.h" @@ -25,4 +26,4 @@ int main(void) { pthread_mutex_unlock(&mutex); // no UB because ERRORCHECK join_threads(t); return 0; -} \ No newline at end of file +} diff --git a/tests/regression/28-race_reach/41-trylock_racefree.c b/tests/regression/28-race_reach/41-trylock_racefree.c index 0e521474c5..ce68d3abe2 100644 --- a/tests/regression/28-race_reach/41-trylock_racefree.c +++ b/tests/regression/28-race_reach/41-trylock_racefree.c @@ -1,3 +1,4 @@ +// PARAM: --set lib.activated[+] sv-comp #include #include "racemacros.h" @@ -22,4 +23,4 @@ int main(void) { pthread_mutex_unlock(&mutex); join_threads(t); return 0; -} \ No newline at end of file +} diff --git a/tests/regression/28-race_reach/42-trylock2_racefree.c b/tests/regression/28-race_reach/42-trylock2_racefree.c index 5f50097355..8b73328281 100644 --- a/tests/regression/28-race_reach/42-trylock2_racefree.c +++ b/tests/regression/28-race_reach/42-trylock2_racefree.c @@ -1,3 +1,4 @@ +// PARAM: --set lib.activated[+] sv-comp #include #include "racemacros.h" @@ -35,4 +36,4 @@ int main(void) { join_threads(t); return 0; -} \ No newline at end of file +} diff --git a/tests/regression/28-race_reach/45-escape_racing.c b/tests/regression/28-race_reach/45-escape_racing.c index a27db9a9df..31cb5fcacc 100644 --- a/tests/regression/28-race_reach/45-escape_racing.c +++ b/tests/regression/28-race_reach/45-escape_racing.c @@ -1,3 +1,4 @@ +// PARAM: --set lib.activated[+] sv-comp #include #include "racemacros.h" diff --git a/tests/regression/28-race_reach/46-escape_racefree.c b/tests/regression/28-race_reach/46-escape_racefree.c index af4874534e..731a61483e 100644 --- a/tests/regression/28-race_reach/46-escape_racefree.c +++ b/tests/regression/28-race_reach/46-escape_racefree.c @@ -1,3 +1,4 @@ +// PARAM: --set lib.activated[+] sv-comp #include #include "racemacros.h" diff --git a/tests/regression/28-race_reach/51-mutexptr_racefree.c b/tests/regression/28-race_reach/51-mutexptr_racefree.c index 972cd6e667..c57b58eb61 100644 --- a/tests/regression/28-race_reach/51-mutexptr_racefree.c +++ b/tests/regression/28-race_reach/51-mutexptr_racefree.c @@ -1,3 +1,4 @@ +// PARAM: --set lib.activated[+] sv-comp #include #include "racemacros.h" diff --git a/tests/regression/28-race_reach/60-invariant_racefree.c b/tests/regression/28-race_reach/60-invariant_racefree.c index d396e349bc..b8b86a86ca 100644 --- a/tests/regression/28-race_reach/60-invariant_racefree.c +++ b/tests/regression/28-race_reach/60-invariant_racefree.c @@ -1,3 +1,4 @@ +// PARAM: --set lib.activated[+] sv-comp #include #include "racemacros.h" diff --git a/tests/regression/28-race_reach/61-invariant_racing.c b/tests/regression/28-race_reach/61-invariant_racing.c index 3facd56d32..b61f34ba25 100644 --- a/tests/regression/28-race_reach/61-invariant_racing.c +++ b/tests/regression/28-race_reach/61-invariant_racing.c @@ -1,3 +1,4 @@ +// PARAM: --set lib.activated[+] sv-comp #include #include "racemacros.h" @@ -6,9 +7,12 @@ pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; void *t_fun(void *arg) { pthread_mutex_lock(&mutex); - if (x == 0) { + pthread_mutex_lock(&__global_lock); + if (x == 0) { // NORACE + pthread_mutex_unlock(&__global_lock); pthread_mutex_unlock(&mutex); } else { + pthread_mutex_unlock(&__global_lock); pthread_mutex_unlock(&mutex); access(x); } diff --git a/tests/regression/28-race_reach/70-funloop_racefree.c b/tests/regression/28-race_reach/70-funloop_racefree.c index 11f44100cd..2ff0cdf9e5 100644 --- a/tests/regression/28-race_reach/70-funloop_racefree.c +++ b/tests/regression/28-race_reach/70-funloop_racefree.c @@ -1,4 +1,4 @@ -// PARAM: --enable ana.race.direct-arithmetic --set ana.activated[+] "'var_eq'" --set ana.activated[+] "'symb_locks'" +// PARAM: --enable ana.race.direct-arithmetic --set ana.activated[+] "'var_eq'" --set ana.activated[+] "'symb_locks'" --set lib.activated[+] sv-comp #include #include #include "racemacros.h" diff --git a/tests/regression/28-race_reach/71-funloop_racing.c b/tests/regression/28-race_reach/71-funloop_racing.c index d34be23175..ac20711401 100644 --- a/tests/regression/28-race_reach/71-funloop_racing.c +++ b/tests/regression/28-race_reach/71-funloop_racing.c @@ -1,4 +1,4 @@ -// PARAM: --enable ana.race.direct-arithmetic --set ana.activated[+] "'var_eq'" --set ana.activated[+] "'symb_locks'" +// PARAM: --enable ana.race.direct-arithmetic --set ana.activated[+] "'var_eq'" --set ana.activated[+] "'symb_locks'" --set lib.activated[+] sv-comp #include #include #include "racemacros.h" diff --git a/tests/regression/28-race_reach/72-funloop_hard_racing.c b/tests/regression/28-race_reach/72-funloop_hard_racing.c index d913bb16a6..78e24279f9 100644 --- a/tests/regression/28-race_reach/72-funloop_hard_racing.c +++ b/tests/regression/28-race_reach/72-funloop_hard_racing.c @@ -1,4 +1,4 @@ -// PARAM: --enable ana.race.direct-arithmetic --set ana.activated[+] "'var_eq'" --set ana.activated[+] "'symb_locks'" +// PARAM: --enable ana.race.direct-arithmetic --set ana.activated[+] "'var_eq'" --set ana.activated[+] "'symb_locks'" --set lib.activated[+] sv-comp #include #include #include "racemacros.h" diff --git a/tests/regression/28-race_reach/73-funloop_hard_racefree.c b/tests/regression/28-race_reach/73-funloop_hard_racefree.c index 33571b8c4d..cc48865d24 100644 --- a/tests/regression/28-race_reach/73-funloop_hard_racefree.c +++ b/tests/regression/28-race_reach/73-funloop_hard_racefree.c @@ -1,4 +1,4 @@ -// PARAM: --enable ana.race.direct-arithmetic --set ana.activated[+] "'var_eq'" --set ana.activated[+] "'symb_locks'" +// PARAM: --enable ana.race.direct-arithmetic --set ana.activated[+] "'var_eq'" --set ana.activated[+] "'symb_locks'" --set lib.activated[+] sv-comp #include #include #include "racemacros.h" diff --git a/tests/regression/28-race_reach/74-tricky_address1_racefree.c b/tests/regression/28-race_reach/74-tricky_address1_racefree.c index 0fdacd23c2..f9ce5d8b4d 100644 --- a/tests/regression/28-race_reach/74-tricky_address1_racefree.c +++ b/tests/regression/28-race_reach/74-tricky_address1_racefree.c @@ -1,4 +1,4 @@ -// PARAM: --enable ana.race.direct-arithmetic --set ana.activated[+] "'var_eq'" --set ana.activated[+] "'symb_locks'" +// PARAM: --enable ana.race.direct-arithmetic --set ana.activated[+] "'var_eq'" --set ana.activated[+] "'symb_locks'" --set lib.activated[+] sv-comp #include #include #include "racemacros.h" diff --git a/tests/regression/28-race_reach/75-tricky_address2_racefree.c b/tests/regression/28-race_reach/75-tricky_address2_racefree.c index 76b3b3752a..162eb8d13a 100644 --- a/tests/regression/28-race_reach/75-tricky_address2_racefree.c +++ b/tests/regression/28-race_reach/75-tricky_address2_racefree.c @@ -1,4 +1,4 @@ -// PARAM: --enable ana.race.direct-arithmetic --set ana.activated[+] "'var_eq'" --set ana.activated[+] "'symb_locks'" +// PARAM: --enable ana.race.direct-arithmetic --set ana.activated[+] "'var_eq'" --set ana.activated[+] "'symb_locks'" --set lib.activated[+] sv-comp #include #include #include "racemacros.h" diff --git a/tests/regression/28-race_reach/76-tricky_address3_racefree.c b/tests/regression/28-race_reach/76-tricky_address3_racefree.c index 1a782b670e..70c3d033b2 100644 --- a/tests/regression/28-race_reach/76-tricky_address3_racefree.c +++ b/tests/regression/28-race_reach/76-tricky_address3_racefree.c @@ -1,4 +1,4 @@ -// PARAM: --enable ana.race.direct-arithmetic --set ana.activated[+] "'var_eq'" --set ana.activated[+] "'symb_locks'" +// PARAM: --enable ana.race.direct-arithmetic --set ana.activated[+] "'var_eq'" --set ana.activated[+] "'symb_locks'" --set lib.activated[+] sv-comp #include #include #include "racemacros.h" diff --git a/tests/regression/28-race_reach/77-tricky_address4_racing.c b/tests/regression/28-race_reach/77-tricky_address4_racing.c index 5b189aa221..5fea171553 100644 --- a/tests/regression/28-race_reach/77-tricky_address4_racing.c +++ b/tests/regression/28-race_reach/77-tricky_address4_racing.c @@ -1,4 +1,4 @@ -// PARAM: --enable ana.race.direct-arithmetic --set ana.activated[+] "'var_eq'" --set ana.activated[+] "'symb_locks'" +// PARAM: --enable ana.race.direct-arithmetic --set ana.activated[+] "'var_eq'" --set ana.activated[+] "'symb_locks'" --set lib.activated[+] sv-comp #include #include #include "racemacros.h" diff --git a/tests/regression/28-race_reach/78-equ_racing.c b/tests/regression/28-race_reach/78-equ_racing.c index 32e10d5a02..b21d76b889 100644 --- a/tests/regression/28-race_reach/78-equ_racing.c +++ b/tests/regression/28-race_reach/78-equ_racing.c @@ -1,4 +1,4 @@ -// PARAM: --enable ana.race.direct-arithmetic --set ana.activated[+] "'var_eq'" --set ana.activated[+] "'symb_locks'" +// PARAM: --enable ana.race.direct-arithmetic --set ana.activated[+] "'var_eq'" --set ana.activated[+] "'symb_locks'" --set lib.activated[+] sv-comp #include #include #include "racemacros.h" diff --git a/tests/regression/28-race_reach/79-equ_racefree.c b/tests/regression/28-race_reach/79-equ_racefree.c index ba9affb71f..5b8744c322 100644 --- a/tests/regression/28-race_reach/79-equ_racefree.c +++ b/tests/regression/28-race_reach/79-equ_racefree.c @@ -1,4 +1,4 @@ -// PARAM: --enable ana.race.direct-arithmetic --set ana.activated[+] "'var_eq'" --set ana.activated[+] "'symb_locks'" +// PARAM: --enable ana.race.direct-arithmetic --set ana.activated[+] "'var_eq'" --set ana.activated[+] "'symb_locks'" --set lib.activated[+] sv-comp #include #include #include "racemacros.h" diff --git a/tests/regression/28-race_reach/81-list_racing.c b/tests/regression/28-race_reach/81-list_racing.c index c131e5c2a1..8b231f843c 100644 --- a/tests/regression/28-race_reach/81-list_racing.c +++ b/tests/regression/28-race_reach/81-list_racing.c @@ -1,4 +1,4 @@ -// PARAM: --set ana.activated[+] "'region'" +// PARAM: --set ana.activated[+] "'region'" --set lib.activated[+] sv-comp #include #include "racemacros.h" diff --git a/tests/regression/28-race_reach/82-list_racefree.c b/tests/regression/28-race_reach/82-list_racefree.c index 67470cf8e0..5bab3c5c31 100644 --- a/tests/regression/28-race_reach/82-list_racefree.c +++ b/tests/regression/28-race_reach/82-list_racefree.c @@ -1,4 +1,4 @@ -// PARAM: --set ana.activated[+] "'region'" +// PARAM: --set ana.activated[+] "'region'" --set lib.activated[+] sv-comp #include #include "racemacros.h" diff --git a/tests/regression/28-race_reach/83-list2_racing1.c b/tests/regression/28-race_reach/83-list2_racing1.c index 6002bc73d4..f0d9f9744b 100644 --- a/tests/regression/28-race_reach/83-list2_racing1.c +++ b/tests/regression/28-race_reach/83-list2_racing1.c @@ -1,4 +1,4 @@ -// PARAM: --set ana.activated[+] "'region'" +// PARAM: --set ana.activated[+] "'region'" --set lib.activated[+] sv-comp #include #include "racemacros.h" diff --git a/tests/regression/28-race_reach/84-list2_racing2.c b/tests/regression/28-race_reach/84-list2_racing2.c index d807e2d0ac..ce15b2ddf5 100644 --- a/tests/regression/28-race_reach/84-list2_racing2.c +++ b/tests/regression/28-race_reach/84-list2_racing2.c @@ -1,4 +1,4 @@ -// PARAM: --set ana.activated[+] "'region'" +// PARAM: --set ana.activated[+] "'region'" --set lib.activated[+] sv-comp #include #include "racemacros.h" diff --git a/tests/regression/28-race_reach/85-list2_racefree.c b/tests/regression/28-race_reach/85-list2_racefree.c index b0fb1baa83..cef0e1cb08 100644 --- a/tests/regression/28-race_reach/85-list2_racefree.c +++ b/tests/regression/28-race_reach/85-list2_racefree.c @@ -1,4 +1,4 @@ -// PARAM: --set ana.activated[+] "'region'" +// PARAM: --set ana.activated[+] "'region'" --set lib.activated[+] sv-comp #include #include "racemacros.h" diff --git a/tests/regression/28-race_reach/86-lists_racing.c b/tests/regression/28-race_reach/86-lists_racing.c index 0548dcab37..e90b699212 100644 --- a/tests/regression/28-race_reach/86-lists_racing.c +++ b/tests/regression/28-race_reach/86-lists_racing.c @@ -1,4 +1,4 @@ -// PARAM: --set ana.activated[+] "'region'" +// PARAM: --set ana.activated[+] "'region'" --set lib.activated[+] sv-comp #include #include "racemacros.h" diff --git a/tests/regression/28-race_reach/87-lists_racefree.c b/tests/regression/28-race_reach/87-lists_racefree.c index 0d05e5b2c2..8e51670b61 100644 --- a/tests/regression/28-race_reach/87-lists_racefree.c +++ b/tests/regression/28-race_reach/87-lists_racefree.c @@ -1,4 +1,4 @@ -// PARAM: --set ana.activated[+] "'region'" +// PARAM: --set ana.activated[+] "'region'" --set lib.activated[+] sv-comp #include #include "racemacros.h" diff --git a/tests/regression/28-race_reach/90-arrayloop2_racing.c b/tests/regression/28-race_reach/90-arrayloop2_racing.c index 4859ed5a95..184d79af89 100644 --- a/tests/regression/28-race_reach/90-arrayloop2_racing.c +++ b/tests/regression/28-race_reach/90-arrayloop2_racing.c @@ -1,4 +1,4 @@ -// PARAM: --set ana.activated[+] "'var_eq'" --set ana.activated[+] "'symb_locks'" --set ana.activated[+] "'region'" --set exp.region-offsets true +// PARAM: --set ana.activated[+] "'var_eq'" --set ana.activated[+] "'symb_locks'" --set ana.activated[+] "'region'" --set exp.region-offsets true --set lib.activated[+] sv-comp #include #include #include "racemacros.h" diff --git a/tests/regression/28-race_reach/91-arrayloop2_racefree.c b/tests/regression/28-race_reach/91-arrayloop2_racefree.c index 30ffa83e78..4461e78459 100644 --- a/tests/regression/28-race_reach/91-arrayloop2_racefree.c +++ b/tests/regression/28-race_reach/91-arrayloop2_racefree.c @@ -1,4 +1,4 @@ -// PARAM: --set ana.activated[+] "'var_eq'" --set ana.activated[+] "'symb_locks'" --set ana.activated[+] "'region'" --set exp.region-offsets true +// PARAM: --set ana.activated[+] "'var_eq'" --set ana.activated[+] "'symb_locks'" --set ana.activated[+] "'region'" --set exp.region-offsets true --set lib.activated[+] sv-comp #include #include #include "racemacros.h" diff --git a/tests/regression/28-race_reach/92-evilcollapse_racing.c b/tests/regression/28-race_reach/92-evilcollapse_racing.c index af5bae0830..a33eb630f5 100644 --- a/tests/regression/28-race_reach/92-evilcollapse_racing.c +++ b/tests/regression/28-race_reach/92-evilcollapse_racing.c @@ -1,4 +1,4 @@ -// PARAM: --set ana.activated[+] "'var_eq'" --set ana.activated[+] "'symb_locks'" --set ana.activated[+] "'region'" --set exp.region-offsets true +// PARAM: --set ana.activated[+] "'var_eq'" --set ana.activated[+] "'symb_locks'" --set ana.activated[+] "'region'" --set exp.region-offsets true --set lib.activated[+] sv-comp #include #include #include "racemacros.h" diff --git a/tests/regression/28-race_reach/93-evilcollapse_racefree.c b/tests/regression/28-race_reach/93-evilcollapse_racefree.c index e4ca831199..dee7d67659 100644 --- a/tests/regression/28-race_reach/93-evilcollapse_racefree.c +++ b/tests/regression/28-race_reach/93-evilcollapse_racefree.c @@ -1,4 +1,4 @@ -// PARAM: --set ana.activated[+] "'var_eq'" --set ana.activated[+] "'symb_locks'" --set ana.activated[+] "'region'" --set exp.region-offsets true +// PARAM: --set ana.activated[+] "'var_eq'" --set ana.activated[+] "'symb_locks'" --set ana.activated[+] "'region'" --set exp.region-offsets true --set lib.activated[+] sv-comp #include #include #include "racemacros.h" diff --git a/tests/regression/28-race_reach/94-alloc_region_racing.c b/tests/regression/28-race_reach/94-alloc_region_racing.c index 74333bcab4..f985a9d91e 100644 --- a/tests/regression/28-race_reach/94-alloc_region_racing.c +++ b/tests/regression/28-race_reach/94-alloc_region_racing.c @@ -1,4 +1,4 @@ -// PARAM: --set ana.activated[+] "'region'" --set exp.region-offsets true +// PARAM: --set ana.activated[+] "'region'" --set exp.region-offsets true --set lib.activated[+] sv-comp #include #include #include diff --git a/tests/regression/29-svcomp/32-no-ov.c b/tests/regression/29-svcomp/32-no-ov.c new file mode 100644 index 0000000000..0167098c29 --- /dev/null +++ b/tests/regression/29-svcomp/32-no-ov.c @@ -0,0 +1,7 @@ +// PARAM: --enable ana.int.interval --enable ana.sv-comp.enabled --enable ana.sv-comp.functions --set ana.specification "CHECK( init(main()), LTL(G ! overflow) )" + +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)); + return 0; +} \ No newline at end of file diff --git a/tests/regression/29-svcomp/32-no-ov.t b/tests/regression/29-svcomp/32-no-ov.t new file mode 100644 index 0000000000..85eb90c185 --- /dev/null +++ b/tests/regression/29-svcomp/32-no-ov.t @@ -0,0 +1,16 @@ + $ 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 + 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) + [Warning][Integer > Overflow][CWE-191] Unsigned integer underflow (32-no-ov.c:5:6-5:159) + [Warning][Integer > Overflow][CWE-190] Signed integer overflow (32-no-ov.c:5:6-5:159) + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 3 + dead: 0 + total lines: 3 + SV-COMP result: true + [Info][Race] Memory locations race summary: + safe: 1 + vulnerable: 0 + unsafe: 0 + total memory locations: 1 diff --git a/tests/regression/29-svcomp/dune b/tests/regression/29-svcomp/dune new file mode 100644 index 0000000000..23c0dd3290 --- /dev/null +++ b/tests/regression/29-svcomp/dune @@ -0,0 +1,2 @@ +(cram + (deps (glob_files *.c))) diff --git a/tests/regression/34-localwn_restart/06-td-a2i.c b/tests/regression/34-localwn_restart/06-td-a2i.c new file mode 100644 index 0000000000..e1fe6b05d0 --- /dev/null +++ b/tests/regression/34-localwn_restart/06-td-a2i.c @@ -0,0 +1,22 @@ +// PARAM: --enable ana.int.interval --set solver td3 --enable solvers.td3.remove-wpoint +// Example from "The Top-Down Solver — An Exercise in A²I", Section 6. +#include + +int main() { + int i, j, x; + i = 0; + while (i < 42) { + j = 0; + while (j < 17) { + x = i + j; + j++; + } + __goblint_check(j == 17); + __goblint_check(i >= 0); + __goblint_check(i <= 41); + i++; + } + __goblint_check(i == 42); + __goblint_check(j == 17); // TODO + return 0; +} diff --git a/tests/regression/36-apron/15-globals-st.c b/tests/regression/36-apron/15-globals-st.c index 692d66f299..4c167ad742 100644 --- a/tests/regression/36-apron/15-globals-st.c +++ b/tests/regression/36-apron/15-globals-st.c @@ -1,4 +1,4 @@ -// SKIP PARAM: --set ana.activated[+] apron --set ana.path_sens[+] threadflag --disable ana.int.interval +// SKIP PARAM: --set ana.activated[+] apron --set ana.path_sens[+] threadflag --disable ana.int.interval --set lib.activated[+] sv-comp extern int __VERIFIER_nondet_int(); #include diff --git a/tests/regression/37-congruence/06-refinements.c b/tests/regression/37-congruence/06-refinements.c index c0b7b0564c..38bf9458cc 100644 --- a/tests/regression/37-congruence/06-refinements.c +++ b/tests/regression/37-congruence/06-refinements.c @@ -5,14 +5,15 @@ int main() { int top; int i = 0; if(top % 17 == 3) { - __goblint_check(top%17 ==3); + __goblint_check(top%17 ==3); //TODO (Refine top to be positive above, and reuse information in %) if(top %17 != 3) { i = 12; } else { } } - __goblint_check(i ==0); + __goblint_check(i ==0); // TODO + i = 0; if(top % 17 == 0) { __goblint_check(top%17 == 0); diff --git a/tests/regression/37-congruence/07-refinements-o.c b/tests/regression/37-congruence/07-refinements-o.c index 44f21b7c8c..49148d6683 100644 --- a/tests/regression/37-congruence/07-refinements-o.c +++ b/tests/regression/37-congruence/07-refinements-o.c @@ -32,15 +32,16 @@ int main() { int top; int i = 0; if(top % 17 == 3) { - __goblint_check(top%17 ==3); + __goblint_check(top%17 ==3); //TODO (Refine top to be positive above, and reuse information in %) if(top %17 != 3) { i = 12; } else { } } - __goblint_check(i ==0); + __goblint_check(i ==0); //TODO + i = 0; if(top % 17 == 0) { __goblint_check(top%17 == 0); if(top %17 != 0) { diff --git a/tests/regression/37-congruence/11-overflow-signed.c b/tests/regression/37-congruence/11-overflow-signed.c index 29599fe246..031d88ce45 100644 --- a/tests/regression/37-congruence/11-overflow-signed.c +++ b/tests/regression/37-congruence/11-overflow-signed.c @@ -12,8 +12,8 @@ int basic(){ { if (b % two_pow_16 == 5) { - __goblint_check(a % two_pow_16 == 3); - __goblint_check(b % two_pow_16 == 5); + __goblint_check(a % two_pow_16 == 3); //TODO (Refine a to be positive above, and reuse information in %) + __goblint_check(b % two_pow_16 == 5); //TODO (Refine a to be positive above, and reuse information in %) unsigned int e = a * b; __goblint_check(e % two_pow_16 == 15); // UNKNOWN! @@ -35,7 +35,7 @@ int special(){ if (a % two_pow_18 == two_pow_17) { - __goblint_check(a % two_pow_18 == two_pow_17); + __goblint_check(a % two_pow_18 == two_pow_17); //TODO (Refine a to be positive above, and reuse information in %) unsigned int e = a * a; __goblint_check(e == 0); //UNKNOWN! diff --git a/tests/regression/37-congruence/13-bitand.c b/tests/regression/37-congruence/13-bitand.c new file mode 100644 index 0000000000..500fb9d1cc --- /dev/null +++ b/tests/regression/37-congruence/13-bitand.c @@ -0,0 +1,46 @@ +// PARAM: --enable ana.int.congruence --set sem.int.signed_overflow assume_none +#include + +int main() +{ + // Assuming modulo expression + + unsigned long x; + __goblint_assume(x % 2 == 1); + __goblint_check(x % 2 == 1); + __goblint_check(x & 1); + + long xx; + __goblint_assume(xx % 2 == 1); + __goblint_check(xx % 2 == 1); //TODO (Refine xx to be positive above, and reuse information in %) + __goblint_check(xx & 1); + + long y; + __goblint_assume(y % 2 == 0); + __goblint_check(y % 2 == 0); + __goblint_check(y & 1); //FAIL + + long z; + __goblint_check(z & 1); //UNKNOWN! + __goblint_assume(z % 8 == 1); + __goblint_check(z & 1); + + long xz; + __goblint_assume(xz % 3 == 1); + __goblint_check(xz & 1); //UNKNOWN! + __goblint_assume(xz % 6 == 1); + __goblint_check(xz & 1); + + // Assuming bitwise expression + // Does NOT actually lead to modulo information, as negative values may also have their last bit set! + + long a; + __goblint_assume(a & 1); + __goblint_check(a % 2 == 1); //UNKNOWN! + __goblint_check(a & 1); + + int b; + __goblint_assume(b & 1); + __goblint_check(b % 2 == 1); //UNKNOWN! + __goblint_check(b & 1); +} diff --git a/tests/regression/37-congruence/14-negative.c b/tests/regression/37-congruence/14-negative.c new file mode 100644 index 0000000000..eae8307ab1 --- /dev/null +++ b/tests/regression/37-congruence/14-negative.c @@ -0,0 +1,15 @@ +// PARAM: --enable ana.int.congruence --set sem.int.signed_overflow assume_none +#include + +int main() +{ + int top; + + int c = -5; + if (top) + { + c = -7; + } + __goblint_check(c % 2 == 1); //UNKNOWN! (Does not hold at runtime) + __goblint_check(c % 2 == -1); //TODO (Track information that c is negative) +} diff --git a/tests/regression/38-int-refinements/06-narrow.c b/tests/regression/38-int-refinements/06-narrow.c new file mode 100644 index 0000000000..513e9dde60 --- /dev/null +++ b/tests/regression/38-int-refinements/06-narrow.c @@ -0,0 +1,18 @@ +// PARAM: --set ana.int.refinement fixpoint --enable ana.int.interval +// FIXPOINT +#include + +int g = 0; + +void main() +{ + int i = 0; + while (1) { + i++; + for (int j=0; j < 10; j++) { + if (i > 100) g = 1; + } + if (i>9) i=0; + } + return; +} diff --git a/tests/regression/39-signed-overflows/06-abs.c b/tests/regression/39-signed-overflows/06-abs.c new file mode 100644 index 0000000000..1323434cbc --- /dev/null +++ b/tests/regression/39-signed-overflows/06-abs.c @@ -0,0 +1,29 @@ +//PARAM: --enable ana.int.interval --set ana.activated[+] tmpSpecial +#include +int main() { + int data; + if (data > (-0x7fffffff - 1)) + { + if (abs(data) < 100) + { + __goblint_check(data < 100); + __goblint_check(-100 < data); + int result = data * data; //NOWARN + } + + if(abs(data) <= 100) + { + __goblint_check(data <= 100); + __goblint_check(-100 <= data); + int result = data * data; //NOWARN + } + + if(abs(data) - 1 <= 99) + { + __goblint_check(data <= 100); + __goblint_check(-100 <= data); + int result = data * data; //NOWARN + } + } + return 8; +} \ No newline at end of file diff --git a/tests/regression/39-signed-overflows/07-abs-sqrt.c b/tests/regression/39-signed-overflows/07-abs-sqrt.c new file mode 100644 index 0000000000..13ed863e51 --- /dev/null +++ b/tests/regression/39-signed-overflows/07-abs-sqrt.c @@ -0,0 +1,10 @@ +//PARAM: --enable ana.int.interval --enable ana.float.interval --set ana.activated[+] tmpSpecial +#include +int main() { + int data; + if (data > (-0x7fffffff - 1) && abs(data) < (long)sqrt((double)0x7fffffff)) + { + int result = data * data; //NOWARN + } + return 8; +} \ No newline at end of file diff --git a/tests/regression/39-signed-overflows/08-labs.c b/tests/regression/39-signed-overflows/08-labs.c new file mode 100644 index 0000000000..a9c6773d11 --- /dev/null +++ b/tests/regression/39-signed-overflows/08-labs.c @@ -0,0 +1,22 @@ +//PARAM: --enable ana.int.interval --set ana.activated[+] tmpSpecial +#include +int main() { + long data; + if (data > (-0xffffffff - 1)) + { + if (labs(data) < 100) + { + __goblint_check(data < 100); + __goblint_check(-100 < data); + int result = data * data; //NOWARN + } + + if(labs(data) <= 100) + { + __goblint_check(data <= 100); + __goblint_check(-100 <= data); + int result = data * data; //NOWARN + } + } + return 8; +} diff --git a/tests/regression/39-signed-overflows/09-labs-sqrt.c b/tests/regression/39-signed-overflows/09-labs-sqrt.c new file mode 100644 index 0000000000..3a4b20a82b --- /dev/null +++ b/tests/regression/39-signed-overflows/09-labs-sqrt.c @@ -0,0 +1,10 @@ +//PARAM: --enable ana.int.interval --enable ana.float.interval --set ana.activated[+] tmpSpecial +#include +int main() { + int data; + if (data > (-0x7fffffff - 1) && llabs(data) < (long)sqrt((double)0x7fffffff)) + { + int result = data * data; //NOWARN + } + return 8; +} diff --git a/tests/regression/39-signed-overflows/10-shiftleft.c b/tests/regression/39-signed-overflows/10-shiftleft.c new file mode 100644 index 0000000000..7e790306ca --- /dev/null +++ b/tests/regression/39-signed-overflows/10-shiftleft.c @@ -0,0 +1,31 @@ +// PARAM: --enable ana.int.interval +#include +#include +int main() +{ + int r; + int zero_or_one = 0; + int top; + char c; + r = c << 1U; //NOWARN + + r = c << 128U; //WARN + r = r << 1U; //WARN + r = 8 << -2; //WARN + + if(top) { zero_or_one = 1; } + + r = 8 << zero_or_one; + + __goblint_check(r >= 8); + __goblint_check(r <= 16); + + int regval = INT_MAX; + int shift = regval >> 6; //NOWARN + int blub = 1 << shift; //WARN + + int regval2; + unsigned long bla = (unsigned long )((1 << ((int )regval2 >> 6)) << 20); //WARN + + return 0; +} diff --git a/tests/regression/40-threadid/09-multiple.c b/tests/regression/40-threadid/09-multiple.c new file mode 100644 index 0000000000..5510e5ae07 --- /dev/null +++ b/tests/regression/40-threadid/09-multiple.c @@ -0,0 +1,15 @@ +#include +#include + +int myglobal; + +void *t_fun(void *arg) { + myglobal=40; //RACE + return NULL; +} + +int main(void) { + // This should spawn a non-unique thread + unknown(t_fun); + return 0; +} diff --git a/tests/regression/40-threadid/10-multiple-thread.c b/tests/regression/40-threadid/10-multiple-thread.c new file mode 100644 index 0000000000..0024d268ec --- /dev/null +++ b/tests/regression/40-threadid/10-multiple-thread.c @@ -0,0 +1,16 @@ +// PARAM: --set ana.activated[+] thread +#include +#include + +int myglobal; + +void *t_fun(void *arg) { + myglobal=40; //RACE + return NULL; +} + +int main(void) { + // This should spawn a non-unique thread + unknown(t_fun); + return 0; +} diff --git a/tests/regression/40-threadid/11-multiple-unique-counter.c b/tests/regression/40-threadid/11-multiple-unique-counter.c new file mode 100644 index 0000000000..37c08ae61a --- /dev/null +++ b/tests/regression/40-threadid/11-multiple-unique-counter.c @@ -0,0 +1,16 @@ +// PARAM: --set ana.thread.unique_thread_id_count 4 +#include +#include + +int myglobal; + +void *t_fun(void *arg) { + myglobal=40; //RACE + return NULL; +} + +int main(void) { + // This should spawn a non-unique thread + unknown(t_fun); + return 0; +} diff --git a/tests/regression/41-stdlib/07-atexit.c b/tests/regression/41-stdlib/07-atexit.c new file mode 100644 index 0000000000..4551400175 --- /dev/null +++ b/tests/regression/41-stdlib/07-atexit.c @@ -0,0 +1,13 @@ +#include +#include + +void bye() +{ + __goblint_check(1); // reachable +} + +int main() +{ + atexit(bye); + return 0; +} diff --git a/tests/regression/41-stdlib/08-atexit-no-spawn.c b/tests/regression/41-stdlib/08-atexit-no-spawn.c new file mode 100644 index 0000000000..7f25f42183 --- /dev/null +++ b/tests/regression/41-stdlib/08-atexit-no-spawn.c @@ -0,0 +1,14 @@ +// PARAM: --disable sem.unknown_function.spawn +#include +#include + +void bye() +{ + __goblint_check(0); // NOWARN (unreachable) +} + +int main() +{ + atexit(bye); + return 0; +} diff --git a/tests/regression/56-witness/37-hh-ex3.c b/tests/regression/56-witness/37-hh-ex3.c index c3f26b5cf1..e59fd53108 100644 --- a/tests/regression/56-witness/37-hh-ex3.c +++ b/tests/regression/56-witness/37-hh-ex3.c @@ -1,4 +1,4 @@ -// SKIP PARAM: --set ana.activated[+] apron --disable solvers.td3.remove-wpoint --set ana.activated[+] unassume --set witness.yaml.unassume 37-hh-ex3.yml +// SKIP PARAM: --set ana.activated[+] apron --enable ana.apron.strengthening --disable solvers.td3.remove-wpoint --set ana.activated[+] unassume --set witness.yaml.unassume 37-hh-ex3.yml #include int main() { int i = 0; diff --git a/tests/regression/56-witness/37-hh-ex3.yml b/tests/regression/56-witness/37-hh-ex3.yml index 9a4562d6d2..d6cd5150a4 100644 --- a/tests/regression/56-witness/37-hh-ex3.yml +++ b/tests/regression/56-witness/37-hh-ex3.yml @@ -20,10 +20,10 @@ location: file_name: 37-hh-ex3.c file_hash: 9c984e89a790b595d2b37ca8a05e5967a15130592cb2567fac2fae4aff668a4f - line: 7 + line: 6 column: 4 function: main location_invariant: - string: 0 <= i && i <= 3 && j == 0 + string: 0 <= i && i <= 3 type: assertion format: C diff --git a/tests/regression/56-witness/40-bh-ex1-poly.yml b/tests/regression/56-witness/40-bh-ex1-poly.yml index e219e1f877..cdbd8d666b 100644 --- a/tests/regression/56-witness/40-bh-ex1-poly.yml +++ b/tests/regression/56-witness/40-bh-ex1-poly.yml @@ -20,10 +20,10 @@ location: file_name: 40-bh-ex1-poly.c file_hash: 34f781dcae089ecb6b7b2811027395fcb501b8477b7e5016f7b38081724bea28 - line: 8 + line: 7 column: 4 function: main location_invariant: - string: 0 <= i && i <= 3 && j == 0 + string: 0 <= i && i <= 3 type: assertion format: C diff --git a/tests/regression/56-witness/52-witness-lifter-ps2.c b/tests/regression/56-witness/52-witness-lifter-ps2.c new file mode 100644 index 0000000000..bcb7c1410c --- /dev/null +++ b/tests/regression/56-witness/52-witness-lifter-ps2.c @@ -0,0 +1,35 @@ +// 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 +struct _twoIntsStruct { + int intOne ; + int intTwo ; +}; + +typedef struct _twoIntsStruct twoIntsStruct; + +void printStructLine(twoIntsStruct const *structTwoIntsStruct) +{ + return; +} + + +int main(int argc, char **argv) +{ + twoIntsStruct *data; + int tmp_1; + + + if (tmp_1 != 0) { + twoIntsStruct *dataBuffer = malloc(800UL); + data = dataBuffer; + } + else { + + twoIntsStruct *dataBuffer_0 = malloc(800UL); + data = dataBuffer_0; + } + + printStructLine((twoIntsStruct const *)data); + free((void *)data); + + return; +} diff --git a/tests/regression/56-witness/53-witness-lifter-ps3.c b/tests/regression/56-witness/53-witness-lifter-ps3.c new file mode 100644 index 0000000000..06b73b3888 --- /dev/null +++ b/tests/regression/56-witness/53-witness-lifter-ps3.c @@ -0,0 +1,39 @@ +// 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 +struct _twoIntsStruct { + int intOne ; + int intTwo ; +}; + +typedef struct _twoIntsStruct twoIntsStruct; + +void printStructLine(twoIntsStruct const *structTwoIntsStruct) +{ + return; +} + +twoIntsStruct *foo() { + twoIntsStruct *data; + int tmp_1; + + if (tmp_1 != 0) { + twoIntsStruct *dataBuffer = malloc(800UL); + data = dataBuffer; + } + else { + + twoIntsStruct *dataBuffer_0 = malloc(800UL); + data = dataBuffer_0; + } + return data; +} + +int main(int argc, char **argv) +{ + twoIntsStruct *data; + data = foo(); + + printStructLine((twoIntsStruct const *)data); + free((void *)data); + + return; +} diff --git a/tests/regression/56-witness/60-tm-inv-transfer-protection.c b/tests/regression/56-witness/60-tm-inv-transfer-protection.c index 3d5bcbc871..07260adbdd 100644 --- a/tests/regression/56-witness/60-tm-inv-transfer-protection.c +++ b/tests/regression/56-witness/60-tm-inv-transfer-protection.c @@ -35,12 +35,12 @@ int main(void) { __goblint_check(g >= 40); __goblint_check(g <= 41); // UNKNOWN (lacks expressivity) pthread_mutex_unlock(&C); - pthread_mutex_unlock(&C); - + pthread_mutex_unlock(&B); + pthread_mutex_lock(&C); __goblint_check(g >= 40); __goblint_check(g <= 42); // UNKNOWN (widen) pthread_mutex_unlock(&C); - + return 0; } diff --git a/tests/regression/56-witness/61-tm-inv-transfer-mine.c b/tests/regression/56-witness/61-tm-inv-transfer-mine.c index 8f912bc2d9..cd8301fb39 100644 --- a/tests/regression/56-witness/61-tm-inv-transfer-mine.c +++ b/tests/regression/56-witness/61-tm-inv-transfer-mine.c @@ -35,12 +35,12 @@ int main(void) { __goblint_check(g >= 40); __goblint_check(g <= 41); pthread_mutex_unlock(&C); - pthread_mutex_unlock(&C); - + pthread_mutex_unlock(&B); + pthread_mutex_lock(&C); - __goblint_check(g >= 40); + __goblint_check(g >= 40); // TODO why? __goblint_check(g <= 42); pthread_mutex_unlock(&C); - + return 0; } \ No newline at end of file diff --git a/tests/regression/56-witness/62-tm-inv-transfer-protection-witness.c b/tests/regression/56-witness/62-tm-inv-transfer-protection-witness.c index 7be5bcf53e..68aada7394 100644 --- a/tests/regression/56-witness/62-tm-inv-transfer-protection-witness.c +++ b/tests/regression/56-witness/62-tm-inv-transfer-protection-witness.c @@ -35,12 +35,12 @@ int main(void) { __goblint_check(g >= 40); __goblint_check(g <= 41); // UNKNOWN (lacks expressivity) pthread_mutex_unlock(&C); - pthread_mutex_unlock(&C); - + pthread_mutex_unlock(&B); + pthread_mutex_lock(&C); __goblint_check(g >= 40); __goblint_check(g <= 42); pthread_mutex_unlock(&C); - + return 0; } \ No newline at end of file diff --git a/tests/regression/56-witness/63-hh-ex3-term.c b/tests/regression/56-witness/63-hh-ex3-term.c new file mode 100644 index 0000000000..80913c3b9d --- /dev/null +++ b/tests/regression/56-witness/63-hh-ex3-term.c @@ -0,0 +1,27 @@ +// 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 +extern void __assert_fail (const char *__assertion, const char *__file, + unsigned int __line, const char *__function) + __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__noreturn__)); +extern void __assert_perror_fail (int __errnum, const char *__file, + unsigned int __line, const char *__function) + __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__noreturn__)); +extern void __assert (const char *__assertion, const char *__file, int __line) + __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__noreturn__)); + +extern void abort(void); +void reach_error() { ((void) sizeof ((0) ? 1 : 0), __extension__ ({ if (0) ; else __assert_fail ("0", "hh-ex3.c", 3, __extension__ __PRETTY_FUNCTION__); })); } +void __VERIFIER_assert(int cond) { if(!(cond)) { ERROR: {reach_error();abort();} } } +int main() { + int i = 0; + while (i < 4) { + int j = 0; + while (j < 4) { + i++; + j++; + __VERIFIER_assert(0 <= j); + } + __VERIFIER_assert(0 <= j); + i = i - j + 1; + } + return 0; +} diff --git a/tests/regression/56-witness/63-hh-ex3-term.yml b/tests/regression/56-witness/63-hh-ex3-term.yml new file mode 100644 index 0000000000..e635e24014 --- /dev/null +++ b/tests/regression/56-witness/63-hh-ex3-term.yml @@ -0,0 +1,25 @@ +- entry_type: location_invariant + metadata: + format_version: "0.1" + uuid: d834761a-d0d7-4fea-bf42-2ff2b9a19143 + creation_time: 2022-10-12T10:59:25Z + producer: + name: Simmo Saan + version: n/a + task: + input_files: + - /home/vagrant/eval-prec/prec/hh-ex3.i + input_file_hashes: + /home/vagrant/eval-prec/prec/hh-ex3.i: 9c984e89a790b595d2b37ca8a05e5967a15130592cb2567fac2fae4aff668a4f + data_model: LP64 + language: C + location: + file_name: 63-hh-ex3-term.c + file_hash: 9c984e89a790b595d2b37ca8a05e5967a15130592cb2567fac2fae4aff668a4f + line: 17 + column: 4 + function: main + location_invariant: + string: 0 <= i && i <= 3 + type: assertion + format: C diff --git a/tests/regression/57-floats/19-library-invariant.c b/tests/regression/57-floats/19-library-invariant.c new file mode 100644 index 0000000000..93c133ce19 --- /dev/null +++ b/tests/regression/57-floats/19-library-invariant.c @@ -0,0 +1,66 @@ +//PARAM: --enable ana.float.interval --set ana.activated[+] tmpSpecial +#include +#include +#include + +void main() { + double f, g; + double x; + int unk; + + // isnan, isfinite + if(__builtin_isfinite(f)) { + __goblint_check(__builtin_isfinite(f)); + __goblint_check(! __builtin_isnan(f)); + } + if(__builtin_isnan(f)) { + __goblint_check(__builtin_isnan(f)); + __goblint_check(! __builtin_isfinite(f)); + } + + // Comparison + x = (unk) ? -100. : 100.; + if(__builtin_isgreater(x, 0.)) { + __goblint_check(x > 0.); + } + if(__builtin_isgreaterequal(x, 0.)) { + __goblint_check(x >= 0.); + } + if(__builtin_isless(x, 0.)) { + __goblint_check(x < 0.); + } + if(__builtin_islessequal(x, 0.)) { + __goblint_check(x <= 0.); + } + if(__builtin_islessgreater(x, 0.)) { + __goblint_check(x < 0. || x > 0.); // UNKNOWN + } + + // fabs + if(__builtin_fabs(f) == 4.) { + __goblint_check(f >= -4.); + __goblint_check(f <= 4.); + } + g = (unk) ? (3.) : (5.); + if(__builtin_fabs(f) == g) { + __goblint_check(f >= -5.); + __goblint_check(f <= 5.); + } + if(__builtin_fabs(f) == -6.) { // WARN (dead branch) + g = 0.; + } + + // ceil, floor + if(ceil(f) == 5.) { + __goblint_check(f <= 5.); + __goblint_check(f >= 4.); + __goblint_check(f > 4.); + __goblint_check(f >= 4.5); // UNKNOWN! + } + if(floor(f) == 5.) { + __goblint_check(f >= 5.); + __goblint_check(f <= 6.); + __goblint_check(f < 6.); + __goblint_check(f <= 5.5); // UNKNOWN! + } +} diff --git a/tests/regression/57-floats/20-library-invariant-invalidate.c b/tests/regression/57-floats/20-library-invariant-invalidate.c new file mode 100644 index 0000000000..bc00279af3 --- /dev/null +++ b/tests/regression/57-floats/20-library-invariant-invalidate.c @@ -0,0 +1,34 @@ +//PARAM: --enable ana.float.interval --set ana.activated[+] tmpSpecial +#include +#include + +void main() { + double f1, g1; + double f2, g2; + double unk_double; + double f3; + + // example 1: + g1 = __builtin_fabs(f1); + f1 = 7.; + + if(g1 == 5.) { + __goblint_check(f1 <= 5.); // FAIL + } + + // example 2: + g2 = __builtin_fabs(f2); + g2 = unk_double; + + if(g2 == 5.) { + __goblint_check(f2 <= 5.); // UNKNOWN! + } + + // example 3: + // the check is not interesting, this only exists to make sure the analyzer can handle this case and terminates + f3 = __builtin_fabs(f3); + + if(f3 == 0.) { + __goblint_check(f3 <= 5.); + } +} diff --git a/tests/regression/57-floats/21-library-invariant-ceil-floor.c b/tests/regression/57-floats/21-library-invariant-ceil-floor.c new file mode 100644 index 0000000000..040f8c5566 --- /dev/null +++ b/tests/regression/57-floats/21-library-invariant-ceil-floor.c @@ -0,0 +1,122 @@ +//PARAM: --enable ana.float.interval --set ana.activated[+] tmpSpecial +#include +#include +#include + +void main() { + float f; + double d; + long double ld; + + if(ceilf(f) == 5.f) { + __goblint_check(f >= 4.f); + __goblint_check(f > 4.f); + __goblint_check(f >= 4.5f); // UNKNOWN! + } + if(floorf(f) == 5.f) { + __goblint_check(f <= 6.f); + __goblint_check(f < 6.f); + __goblint_check(f <= 5.5f); // UNKNOWN! + } + + if(ceil(d) == 5.) { + __goblint_check(d >= 4.); + __goblint_check(d > 4.); + __goblint_check(d <= 4.5); // UNKNOWN! + } + if(floor(d) == 5.) { + __goblint_check(d <= 6.); + __goblint_check(d < 6.); + __goblint_check(d <= 5.5); // UNKNOWN! + } + + if(ceill(ld) == 5.l) { + __goblint_check(ld >= 4.l); + __goblint_check(ld > 4.l); // UNKNOWN + __goblint_check(ld >= 4.5l); // UNKNOWN! + } + if(floorl(ld) == 5.l) { + __goblint_check(ld <= 6.l); + __goblint_check(ld < 6.l); // UNKNOWN + __goblint_check(ld <= 5.5l); // UNKNOWN! + } + + // Edge cases: + // 9007199254740992.0 = 2^53; up to here all integer values are representable in double. + // 2^53+1 is the first that is not representable as double, only as a long double + long double max_int_l = 9007199254740992.0l; + + if(floorl(ld) == max_int_l) { + //floorl(ld) == 2^53 => ld in [2^53, 2^53 + 1.0]. This is not representable in double, so Goblint computes with ld in [2^53, 2^53 + 2.0] + __goblint_check(ld <= (max_int_l + 2.0l)); + // as long as we abstract long doubles with intervals of doubles, the next should be UNKNOWN. + __goblint_check(ld <= (max_int_l + 1.0l)); // UNKNOWN + } + if(ceill(ld) == - max_int_l) { + // analogous to explanation above but with negative signbit + __goblint_check(ld >= (- max_int_l - 2.0l)); + // as long as we abstract long doubles with intervals of doubles, the next should be UNKNOWN + __goblint_check(ld >= (- max_int_l - 1.0l)); // UNKNOWN + } + + // 4503599627370496.0 = 2^52; from here up to 2^53 double is not able to represent any fractional part, i.e., only integers + // 2^52 + 0.5 is not representable as double, only as long double + long double no_fractional_l = 4503599627370496.0l; + + if(floorl(ld) == no_fractional_l) { + // floorl(ld) == 2^52 => ld < 2^52 + 1.0. + // If ld were a double, Goblint could compute with ld < pred(2^52 + 1.0), since we know no double can exist between pred(2^52 + 1.0) and 2^52 + 1.0. + // However for long double this does not hold, ase e.g. (2^52 + 0.5) is representable. + __goblint_check(ld <= (no_fractional_l + 1.0l)); + // as long as we abstract long doubles with intervals of doubles, the next should be UNKNOWN. + __goblint_check(ld < (no_fractional_l + 1.0l)); // UNKNOWN + } + if(ceill(ld) == - no_fractional_l) { + // analogous to explanation above but with negative signbit + __goblint_check(ld >= (- no_fractional_l - 1.0l)); + // as long as we abstract long doubles with intervals of doubles, the next should be UNKNOWN. + __goblint_check(ld > (- no_fractional_l - 1.0l)); // UNKNOWN + } + + // same tests, but this time with doubles. Here we can use the knowledge, which values are not representable + double max_int = (double)max_int_l; + if(floor(d) == max_int) { + __goblint_check(d <= (max_int + 2.0)); + __goblint_check(d <= (max_int + 1.0)); + } + if(ceil(d) == - max_int) { + __goblint_check(d >= (- max_int - 2.0)); + __goblint_check(d >= (- max_int - 1.0)); + } + + double no_fractional = (double)no_fractional_l; + if(floor(d) == no_fractional) { + __goblint_check(d <= (no_fractional + 1.0)); + __goblint_check(d < (no_fractional + 1.0)); + } + if(ceil(d) == - no_fractional) { + __goblint_check(d >= (- no_fractional - 1.0)); + __goblint_check(d > (- no_fractional - 1.0)); + } + + // same for float + float max_int_f = 16777216.0f; // 2^24 + if(floorf(f) == max_int_f) { + __goblint_check(f <= (max_int_f + 2.0f)); + __goblint_check(f <= (max_int_f + 1.0f)); + } + if(ceilf(f) == - max_int_f) { + __goblint_check(f >= (- max_int_f - 2.0f)); + __goblint_check(f >= (- max_int_f - 1.0f)); + } + + float no_fractional_f = 8388608.0f; // 2^23 + if(floorf(f) == no_fractional_f) { + __goblint_check(f <= (no_fractional_f + 1.0f)); + __goblint_check(f < (no_fractional_f + 1.0f)); + } + if(ceilf(f) == - no_fractional_f) { + __goblint_check(f >= (- no_fractional_f - 1.0f)); + __goblint_check(f > (- no_fractional_f - 1.0f)); + } +} 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 72eb1396b1..b086baf026 100644 --- a/tests/regression/66-interval-set-one/51-widen-sides.c +++ b/tests/regression/66-interval-set-one/51-widen-sides.c @@ -3,13 +3,13 @@ int further(int n) { // Even sides-local can not save us here :( - __goblint_check(n <= 1); //TODO + __goblint_check(n <= 2); //TODO } int fun(int n, const char* arg) { // Fails with solvers.td3.side_widen sides, needs sides-local - __goblint_check(n <= 1); + __goblint_check(n <= 2); further(n); } @@ -26,5 +26,5 @@ int main() { doIt("two"); // In the setting with solvers.td3.side_widen sides, widening happens and the bound is lost - fun(1, "org"); + fun(2, "org"); } diff --git a/tests/regression/68-longjmp/52-races.c b/tests/regression/68-longjmp/52-races.c new file mode 100644 index 0000000000..4cde97d954 --- /dev/null +++ b/tests/regression/68-longjmp/52-races.c @@ -0,0 +1,35 @@ +// PARAM: --enable ana.int.interval +#include +#include +#include +#include + +jmp_buf env_buffer; +int global = 0; +pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER; + +void *t_fun(void *arg) { + pthread_mutex_lock(&mutex1); + global = 3; // NORACE + pthread_mutex_unlock(&mutex1); + return NULL; +} + +int bar() { + pthread_mutex_lock(&mutex1); + longjmp(env_buffer, 2); + pthread_mutex_unlock(&mutex1); + return 8; +} + +int main() { + pthread_t id; + pthread_create(&id, NULL, t_fun, NULL); + + if(!setjmp( env_buffer )) { + bar(); + } + + global = 5; // NORACE + pthread_mutex_unlock(&mutex1); +} diff --git a/tests/regression/68-longjmp/53-races-no.c b/tests/regression/68-longjmp/53-races-no.c new file mode 100644 index 0000000000..4692f6ca18 --- /dev/null +++ b/tests/regression/68-longjmp/53-races-no.c @@ -0,0 +1,36 @@ +// PARAM: --enable ana.int.interval +#include +#include +#include +#include + +jmp_buf env_buffer; +int global = 0; +pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER; + +void *t_fun(void *arg) { + pthread_mutex_lock(&mutex1); + global = 3; // NORACE + pthread_mutex_unlock(&mutex1); + return NULL; +} + +int bar() { + pthread_mutex_lock(&mutex1); + if(global ==3) { + longjmp(env_buffer, 2); + } + return 8; +} + +int main() { + pthread_t id; + pthread_create(&id, NULL, t_fun, NULL); + + if(!setjmp( env_buffer )) { + bar(); + } + + global = 5; // NORACE + pthread_mutex_unlock(&mutex1); +} diff --git a/tests/regression/68-longjmp/54-races-actually.c b/tests/regression/68-longjmp/54-races-actually.c new file mode 100644 index 0000000000..62423cd884 --- /dev/null +++ b/tests/regression/68-longjmp/54-races-actually.c @@ -0,0 +1,50 @@ +// PARAM: --enable ana.int.interval +#include +#include +#include +#include + +jmp_buf env_buffer; +int global = 0; +pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER; + +void *t_fun(void *arg) { + pthread_mutex_lock(&mutex1); + global = 3; // RACE + pthread_mutex_unlock(&mutex1); + return NULL; +} + +int bar() { + pthread_mutex_lock(&mutex1); + if(global == 3) { + longjmp(env_buffer, 2); + } else { + longjmp(env_buffer, 4); + } + return 8; +} + +int main() { + pthread_t id; + pthread_create(&id, NULL, t_fun, NULL); + int n = 0; + + switch(setjmp( env_buffer )) { + case 0: + bar(); + break; + case 2: + n=1; + pthread_mutex_unlock(&mutex1); + break; + default: + break; + } + + global = 5; //RACE + + if(n == 0) { + pthread_mutex_unlock(&mutex1); + } +} diff --git a/tests/regression/68-longjmp/55-races-no-return.c b/tests/regression/68-longjmp/55-races-no-return.c new file mode 100644 index 0000000000..850fc54fa5 --- /dev/null +++ b/tests/regression/68-longjmp/55-races-no-return.c @@ -0,0 +1,50 @@ +// PARAM: --enable ana.int.interval +#include +#include +#include +#include + +jmp_buf env_buffer; +int global = 0; +pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER; + +void *t_fun(void *arg) { + pthread_mutex_lock(&mutex1); + global = 3; //NORACE + pthread_mutex_unlock(&mutex1); + return NULL; +} + +int bar() { + pthread_mutex_lock(&mutex1); + if(global == 7) { + longjmp(env_buffer, 2); + } else { + longjmp(env_buffer, 4); + } + return 8; +} + +int main() { + pthread_t id; + pthread_create(&id, NULL, t_fun, NULL); + int n = 0; + + switch(setjmp( env_buffer )) { + case 0: + bar(); + break; + case 2: + n=1; + pthread_mutex_unlock(&mutex1); + break; + default: + break; + } + + global = 5; //NORACE + + if(n == 0) { + pthread_mutex_unlock(&mutex1); + } +} diff --git a/tests/regression/68-longjmp/56-longjmp-top.c b/tests/regression/68-longjmp/56-longjmp-top.c new file mode 100644 index 0000000000..adb3a47476 --- /dev/null +++ b/tests/regression/68-longjmp/56-longjmp-top.c @@ -0,0 +1,21 @@ +// Extracted from concrat/pigz. +#include +#include +#include + +pthread_key_t buf_key; + +int main() { + jmp_buf buf; + pthread_setspecific(buf_key, &buf); + + if (!setjmp(buf)) { + jmp_buf *buf_ptr; + buf_ptr = pthread_getspecific(buf_key); + longjmp(*buf_ptr, 1); // NO CRASH: problem?! + } + else { + __goblint_check(1); // TODO reachable: https://github.com/goblint/analyzer/pull/1210#discussion_r1350021903 + } + return 0; +} diff --git a/tests/regression/70-transform/02-deadcode.t b/tests/regression/70-transform/02-deadcode.t index ed3cd80e17..03a46b891e 100644 --- a/tests/regression/70-transform/02-deadcode.t +++ b/tests/regression/70-transform/02-deadcode.t @@ -210,15 +210,15 @@ Transformation still works with 'exp.mincfg', but can not find all dead code; test against the diff. Macintosh's diff(1) adds whitespace after the function names, strip with sed. - $ diff -p -U0 "$(./transform.sh --file $args 02-deadcode.c)" "$(./transform.sh --file $args --enable exp.mincfg 02-deadcode.c)" | sed 's/[[:blank:]]*$//' | tail +3 - @@ -13,0 +14,3 @@ int basic1(int n ) + $ diff -U0 "$(./transform.sh --file $args 02-deadcode.c)" "$(./transform.sh --file $args --enable exp.mincfg 02-deadcode.c)" | sed 's/[[:blank:]]*$//' | tail -n +3 + @@ -13,0 +14,3 @@ + if (n < 0) { + return (0); + } - @@ -54,0 +58,2 @@ int one_branch_dead(int x ) + @@ -54,0 +58,2 @@ + } else { + return (7 - x); - @@ -65,0 +71,8 @@ int uncalled_but_referenced_function(int + @@ -65,0 +71,8 @@ +int uncalled1(void) +{ + @@ -227,17 +227,17 @@ Macintosh's diff(1) adds whitespace after the function names, strip with sed. + +} +} - @@ -79,0 +93,5 @@ int conditional_call_in_loop(int x ) + @@ -79,0 +93,5 @@ + if (i > 7) { + { + uncalled1(); + } + } - @@ -151,0 +170,4 @@ int loop_dead_on_break(int z ) + @@ -151,0 +170,4 @@ + { + s += s; + i ++; + } - @@ -203,0 +226,2 @@ int main(void) + @@ -203,0 +226,2 @@ + uncalled1(); + uncalled_but_referenced_function(3); diff --git a/tests/regression/73-strings/01-string_literals.c b/tests/regression/73-strings/01-string_literals.c index 36e4ed121c..42086e07b6 100644 --- a/tests/regression/73-strings/01-string_literals.c +++ b/tests/regression/73-strings/01-string_literals.c @@ -1,4 +1,4 @@ -// PARAM: --disable ana.base.limit-string-addresses --enable ana.int.interval +// PARAM: --set ana.base.strings.domain disjoint --enable ana.int.interval #include #include @@ -21,7 +21,7 @@ int main() { char* s1 = "abcde"; char* s2 = "abcdfg"; char* s3 = hello_world(); - + int i = strlen(s1); __goblint_check(i == 5); @@ -96,10 +96,10 @@ int main() { #define STRNCAT strncat(s1, "hi", 1) #endif STRNCAT; // WARN - + #ifdef __APPLE__ // do nothing => no warning - #else + #else char s4[] = "hello"; strcpy(s4, s2); // NOWARN strncpy(s4, s3, 2); // NOWARN diff --git a/tests/regression/73-strings/02-string_literals_with_null.c b/tests/regression/73-strings/02-string_literals_with_null.c index 75d000bbb8..cc41e9e287 100644 --- a/tests/regression/73-strings/02-string_literals_with_null.c +++ b/tests/regression/73-strings/02-string_literals_with_null.c @@ -1,4 +1,4 @@ -// PARAM: --disable ana.base.limit-string-addresses --enable ana.int.interval +// PARAM: --set ana.base.strings.domain disjoint --enable ana.int.interval #include #include diff --git a/tests/regression/73-strings/03-string_basics.c b/tests/regression/73-strings/03-string_basics.c index db196c64b4..a9d02d5e8b 100644 --- a/tests/regression/73-strings/03-string_basics.c +++ b/tests/regression/73-strings/03-string_basics.c @@ -1,4 +1,4 @@ -// PARAM: --disable ana.base.limit-string-addresses --enable ana.int.interval +// PARAM: --set ana.base.strings.domain disjoint --enable ana.int.interval #include #include diff --git a/tests/regression/73-strings/04-smtprc_strlen_fp.c b/tests/regression/73-strings/04-smtprc_strlen_fp.c new file mode 100644 index 0000000000..a046eac238 --- /dev/null +++ b/tests/regression/73-strings/04-smtprc_strlen_fp.c @@ -0,0 +1,21 @@ +// FIXPOINT extracted from smtprc_comb +#include // for optarg + +typedef unsigned int size_t; // size_t from 32bit cilly +extern size_t strlen(char const *__s ); + +void *s_malloc(unsigned long size) +{ + void *mymem; + mymem = malloc((unsigned int) size); + return mymem; +} + +int main() { + char const *p; + size_t s; + p = optarg; + s = strlen(optarg); + s_malloc((unsigned long) ((s + 1U) * sizeof(char))); + return 0; +} diff --git a/tests/regression/73-strings/05-string-unit-domain.c b/tests/regression/73-strings/05-string-unit-domain.c new file mode 100644 index 0000000000..70e6bed5bf --- /dev/null +++ b/tests/regression/73-strings/05-string-unit-domain.c @@ -0,0 +1,15 @@ +// PARAM: --set ana.base.strings.domain unit +#include +#include + +void foo(char *s) { + int l = strlen(s); + __goblint_check(l == 3 || l == 6); // UNKNOWN +} + +int main() { + foo("foo"); + foo("bar"); + foo("foobar"); + return 0; +} diff --git a/tests/regression/74-invalid_deref/01-oob-heap-simple.c b/tests/regression/74-invalid_deref/01-oob-heap-simple.c new file mode 100644 index 0000000000..10c7864184 --- /dev/null +++ b/tests/regression/74-invalid_deref/01-oob-heap-simple.c @@ -0,0 +1,14 @@ +// PARAM: --set ana.activated[+] memOutOfBounds --enable ana.int.interval +#include + +int main(int argc, char const *argv[]) { + char *ptr = malloc(5 * sizeof(char)); + + *ptr = 'a';//NOWARN + *(ptr + 1) = 'b';//NOWARN + *(ptr + 10) = 'c';//WARN + + free(ptr); + + return 0; +} diff --git a/tests/regression/74-use_after_free/02-conditional-uaf.c b/tests/regression/74-invalid_deref/02-conditional-uaf.c similarity index 100% rename from tests/regression/74-use_after_free/02-conditional-uaf.c rename to tests/regression/74-invalid_deref/02-conditional-uaf.c diff --git a/tests/regression/74-use_after_free/03-nested-ptr-uaf.c b/tests/regression/74-invalid_deref/03-nested-ptr-uaf.c similarity index 100% rename from tests/regression/74-use_after_free/03-nested-ptr-uaf.c rename to tests/regression/74-invalid_deref/03-nested-ptr-uaf.c diff --git a/tests/regression/74-use_after_free/04-function-call-uaf.c b/tests/regression/74-invalid_deref/04-function-call-uaf.c similarity index 80% rename from tests/regression/74-use_after_free/04-function-call-uaf.c rename to tests/regression/74-invalid_deref/04-function-call-uaf.c index f83f9966b4..d110db9edc 100644 --- a/tests/regression/74-use_after_free/04-function-call-uaf.c +++ b/tests/regression/74-invalid_deref/04-function-call-uaf.c @@ -17,7 +17,8 @@ int main() { free(ptr1); free(ptr2); - f(ptr1, ptr2, ptr3); //WARN + // No deref happening in the function call, hence nothing to warn about + f(ptr1, ptr2, ptr3); //NOWARN free(ptr3); //WARN diff --git a/tests/regression/74-invalid_deref/05-oob-implicit-deref.c b/tests/regression/74-invalid_deref/05-oob-implicit-deref.c new file mode 100644 index 0000000000..8bec6a72e0 --- /dev/null +++ b/tests/regression/74-invalid_deref/05-oob-implicit-deref.c @@ -0,0 +1,23 @@ +// PARAM: --set ana.activated[+] memOutOfBounds --enable ana.int.interval --disable warn.info +/* + Note: the "--disable warn.info" above is a temporary workaround, + since the GitHub CI seems to be considering Info messages as violations of NOWARN (cf. https://github.com/goblint/analyzer/issues/1151) +*/ +#include +#include +#include + +int main(int argc, char const *argv[]) { + int *ptr = malloc(4 * sizeof(int)); + + // Both lines below are considered derefs => no need to warn, since ptr is pointing within its bounds + memset(ptr, 0, 4 * sizeof(int)); //NOWARN + printf("%p", (void *) ptr); //NOWARN + ptr = ptr + 10; // ptr no longer points within its allocated bounds + + // Each of both lines below should now receive a WARN + memset(ptr, 0, 4 * sizeof(int)); //WARN + printf("%p", (void *) ptr); //WARN + + return 0; +} diff --git a/tests/regression/74-invalid_deref/06-memset-oob.c b/tests/regression/74-invalid_deref/06-memset-oob.c new file mode 100644 index 0000000000..931f7eaa8c --- /dev/null +++ b/tests/regression/74-invalid_deref/06-memset-oob.c @@ -0,0 +1,54 @@ +// PARAM: --set ana.activated[+] memOutOfBounds --enable ana.int.interval --disable warn.info +// TODO: The "--disable warn.info" part is a temporary fix and needs to be removed once the MacOS CI job is fixed +#include +#include +#include + +typedef struct s { + int a; + char b; +} s; + +int main(int argc, char const *argv[]) { + int *a = malloc(10 * sizeof(int)); //Size is 40 bytes, assuming a 4-byte int + + memset(a, 0, 40); //NOWARN + memset(a, 0, 10 * sizeof(int)); //NOWARN + memset(a, 0, 41); //WARN + memset(a, 0, 40000000); //WARN + + int d; + + if (argc == 15) { + int c = 55; + a = &c; + memset(a, 0, argv[5]); //WARN + } else if (argv[2] == 2) { + a = &d; + } + + memset(a, 0, 40); //WARN + + int input; + scanf("%d", &input); + memset(a, 0, input); //WARN + + + + int *b = malloc(15 * sizeof(int)); //Size is 60 bytes, assuming a 4-byte int + memset(b, 0, 60); //NOWARN + b += 1; + memset(b, 0, 60); //WARN + + + + s *s_ptr = malloc(sizeof(s)); + memset(s_ptr, 0, sizeof(s)); //NOWARN + memset(s_ptr->a, 0, sizeof(s)); //WARN + memset(s_ptr->b, 0, sizeof(s)); //WARN + + s_ptr = s_ptr->a; + memset(s_ptr, 0, sizeof(s)); //WARN + + return 0; +} diff --git a/tests/regression/74-invalid_deref/07-memcpy-oob.c b/tests/regression/74-invalid_deref/07-memcpy-oob.c new file mode 100644 index 0000000000..5605404a87 --- /dev/null +++ b/tests/regression/74-invalid_deref/07-memcpy-oob.c @@ -0,0 +1,53 @@ +// PARAM: --set ana.activated[+] memOutOfBounds --enable ana.int.interval --disable warn.info +// TODO: The "--disable warn.info" part is a temporary fix and needs to be removed once the MacOS CI job is fixed +#include +#include + +typedef struct s { + int a; + char b; +} s; + +int main(int argc, char const *argv[]) { + int *a = malloc(10 * sizeof(int)); //Size is 40 bytes, assuming a 4-byte int + int *b = malloc(15 * sizeof(int)); //Size is 60 bytes, assuming a 4-byte int + + memcpy(a, b, 40); //NOWARN + memcpy(a, b, 10 * sizeof(int)); //NOWARN + memcpy(a, b, 41); //WARN + memcpy(a, b, 40000000); //WARN + memcpy(a, b, 15 * sizeof(int)); //WARN + + int d; + + if (*argv == 42) { + a = &d; + } else if (*(argv + 5)) { + int random = rand(); + a = &random; + memcpy(a, b, 40); //WARN + } + + memcpy(a, b, 40); //WARN + memcpy(a, b, sizeof(a)); //WARN + + memcpy(b, a, 60); //WARN + b += 1; + memcpy(b, a, 60); //WARN + + + s *s_ptr = malloc(sizeof(s)); + memcpy(s_ptr, a, sizeof(s)); //WARN + memcpy(s_ptr->a, 0, sizeof(s)); //WARN + memcpy(s_ptr->b, 0, sizeof(s)); //WARN + + memcpy(s_ptr, a, 40); //WARN + memcpy(s_ptr, a, 60); //WARN + memcpy(s_ptr, b, 40); //WARN + memcpy(s_ptr, b, 60); //WARN + + s_ptr = s_ptr->b; + memcpy(s_ptr, a, sizeof(s)); //WARN + + return 0; +} diff --git a/tests/regression/74-invalid_deref/08-memset-memcpy-array.c b/tests/regression/74-invalid_deref/08-memset-memcpy-array.c new file mode 100644 index 0000000000..210a61d459 --- /dev/null +++ b/tests/regression/74-invalid_deref/08-memset-memcpy-array.c @@ -0,0 +1,44 @@ +// PARAM: --set ana.activated[+] memOutOfBounds --enable ana.int.interval --disable warn.info +// TODO: The "--disable warn.info" part is a temporary fix and needs to be removed once the MacOS CI job is fixed +#include +#include + +int main(int argc, char const *argv[]) { + int arr[42]; // Size should be 168 bytes (with 4 byte ints) + int *b = arr; + int random; + + + memset(b, 0, 168); //NOWARN + memset(b, 0, sizeof(arr)); //NOWARN + memset(b, 0, 169); //WARN + memset(b, 0, sizeof(arr) + 1); //WARN + + int *c = malloc(sizeof(arr)); // Size should be 168 bytes (with 4 byte ints) + memcpy(b, c, 168); //NOWARN + memcpy(b, c, sizeof(arr)); //NOWARN + memcpy(b, c, 169); //WARN + memcpy(b, c, sizeof(arr) + 1); //WARN + + int d; + + if (*argv == 42) { + b = &d; + memset(b, 0, 168); //WARN + memcpy(b, c, 168); //WARN + } else if (*(argv + 5)) { + random = rand(); + b = &random; + memset(b, 0, 168); //WARN + memcpy(b, c, 168); //WARN + } + + memset(b, 0, sizeof(arr)); //WARN + memcpy(b, c, sizeof(arr)); //WARN + memset(b, 0, sizeof(int)); //NOWARN + memcpy(b, c, sizeof(int)); //NOWARN + memset(b, 0, sizeof(int) + 1); //WARN + memcpy(b, c, sizeof(int) + 1); //WARN + + return 0; +} diff --git a/tests/regression/74-use_after_free/09-juliet-uaf.c b/tests/regression/74-invalid_deref/09-juliet-uaf.c similarity index 83% rename from tests/regression/74-use_after_free/09-juliet-uaf.c rename to tests/regression/74-invalid_deref/09-juliet-uaf.c index 5a5bf3ee32..e1a88508a6 100644 --- a/tests/regression/74-use_after_free/09-juliet-uaf.c +++ b/tests/regression/74-invalid_deref/09-juliet-uaf.c @@ -21,7 +21,8 @@ static char * helperBad(char * aString) reversedString[i] = '\0'; free(reversedString); - return reversedString; // WARN (Use After Free (CWE-416)) + // No need to warn in the line below, as there's no dereferencing happening + return reversedString; // NOWARN } else { @@ -67,8 +68,10 @@ void CWE416_Use_After_Free__return_freed_ptr_08_bad() if(staticReturnsTrue()) { { - char * reversedString = helperBad("BadSink"); // WARN (Use After Free (CWE-416)) - printf("%s\n", reversedString); // WARN (Use After Free (CWE-416)) + // No need to warn in the line below, since there's no dereferencing of the freed memory + char * reversedString = helperBad("BadSink"); // NOWARN + // printf() is considered an implicit deref => need to warn here + printf("%s\n", reversedString); // WARN } } } diff --git a/tests/regression/74-invalid_deref/10-oob-two-loops.c b/tests/regression/74-invalid_deref/10-oob-two-loops.c new file mode 100644 index 0000000000..303aac242e --- /dev/null +++ b/tests/regression/74-invalid_deref/10-oob-two-loops.c @@ -0,0 +1,22 @@ +// PARAM: --set ana.activated[+] memOutOfBounds --enable ana.int.interval --disable warn.info --set sem.int.signed_overflow assume_none +#include + +int main() { + int *p = malloc(1048 * sizeof(int)); + + for (int i = 0; i < 1048; ++i) { + p[i] = rand(); //NOWARN + } + + int *q = p; + + while (*q >= 0 && q < p + 1048 * sizeof(int)) { //WARN + if (rand()) { + q++; + } else { + (*q)--; //WARN + } + } + free(p); + return 0; +} diff --git a/tests/regression/74-invalid_deref/11-address-offset-oob.c b/tests/regression/74-invalid_deref/11-address-offset-oob.c new file mode 100644 index 0000000000..ba01a12873 --- /dev/null +++ b/tests/regression/74-invalid_deref/11-address-offset-oob.c @@ -0,0 +1,16 @@ +// PARAM: --set ana.activated[+] memOutOfBounds --enable ana.int.interval --disable warn.info --set sem.int.signed_overflow assume_none +int main() { + int *p = malloc(2 * sizeof(int)); + int *q = p; + int x; + + if (x) { + q++; + q++; + q++; + x = *q; //WARN + } + + x = *q; //WARN + return 0; +} diff --git a/tests/regression/74-invalid_deref/12-memcpy-oob-src.c b/tests/regression/74-invalid_deref/12-memcpy-oob-src.c new file mode 100644 index 0000000000..0f3a609fbe --- /dev/null +++ b/tests/regression/74-invalid_deref/12-memcpy-oob-src.c @@ -0,0 +1,43 @@ +// PARAM: --set ana.activated[+] memOutOfBounds --enable ana.int.interval --disable warn.info +// TODO: The "--disable warn.info" part is a temporary fix and needs to be removed once the MacOS CI job is fixed +#include +#include + +struct A { + unsigned char a; + unsigned char b:2; + unsigned char c:2; + unsigned char d:5; + unsigned char e; +} __attribute__((packed)); + +struct A d; +int main(void) +{ + struct A *p; + p = malloc(5); + d.a = 1; + d.b = 2; + d.c = 3; + d.d = 4; + d.e = 5; + // It's an OOB error, because sizeof(d) == 4 + memcpy(p, &d, 5); //WARN + if (p->a != 1) { + free(p); + } + if (p->b != 2) { + free(p); + } + if (p->c != 3) { + free(p); + } + if (p->d != 4) { + free(p); + } + if (p->e != 5) { + free(p); + } + free(p); +} + diff --git a/tests/regression/74-invalid_deref/13-mem-oob-packed-struct.c b/tests/regression/74-invalid_deref/13-mem-oob-packed-struct.c new file mode 100644 index 0000000000..552cd1bb0b --- /dev/null +++ b/tests/regression/74-invalid_deref/13-mem-oob-packed-struct.c @@ -0,0 +1,33 @@ +// PARAM: --set ana.activated[+] memOutOfBounds --enable ana.int.interval +#include + +struct A { + unsigned char a; + unsigned char b:2; + unsigned char c:2; + unsigned char d; +} __attribute__((packed)); + +int main(void) +{ + struct A *p; + p = malloc(2); + p->a = 1; + if (p->a != 1) { + free(p); + } + p->b = 2; + if (p->b != 2) { + free(p); + } + p->c = 3; + if (p->c != 3) { + free(p); + } + p->d = 4; //WARN + if (p->d != 4) {//WARN + free(p); + } + free(p); +} + diff --git a/tests/regression/74-invalid_deref/14-alloca-uaf.c b/tests/regression/74-invalid_deref/14-alloca-uaf.c new file mode 100644 index 0000000000..3dc494cb09 --- /dev/null +++ b/tests/regression/74-invalid_deref/14-alloca-uaf.c @@ -0,0 +1,16 @@ +//PARAM: --set ana.activated[+] useAfterFree +#include +#include + +int *f() { + int *c = alloca(sizeof(int)); + return c; +} + +int main(int argc, char const *argv[]) { + int *ps = alloca(sizeof(int)); + int *c = f(); + int a = *ps; //NOWARN + int b = *c; //WARN + return 0; +} 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 new file mode 100644 index 0000000000..cc9819950f --- /dev/null +++ b/tests/regression/74-invalid_deref/15-juliet-uaf-global-var.c @@ -0,0 +1,22 @@ +//PARAM: --set ana.activated[+] useAfterFree +#include + +int *global; + +void other(void) +{ + int *data = global; + free((void *)data); + return; +} + +int main(int argc, char **argv) +{ + int *data = (int *)malloc(400UL); + free((void *)data); + + global = data; + other(); + + return 0; +} \ No newline at end of file diff --git a/tests/regression/74-invalid_deref/16-uaf-packed-struct.c b/tests/regression/74-invalid_deref/16-uaf-packed-struct.c new file mode 100644 index 0000000000..e10aa28486 --- /dev/null +++ b/tests/regression/74-invalid_deref/16-uaf-packed-struct.c @@ -0,0 +1,40 @@ +// PARAM: --set ana.activated[+] useAfterFree +#include +#include + +struct A { + unsigned char a; + unsigned char b:2; + unsigned char c:2; + unsigned char pad1[2]; + unsigned int d; + unsigned char e; + unsigned char pad2[3]; +} __attribute__((packed)); + +struct A d; +int main(void) +{ + struct A *p; + p = malloc(12); + d.a = 1; + d.b = 2; + d.c = 3; + d.d = 4; + d.e = 5; + memcpy(p, &d, 4); + if (p->a != 1) { + free(p); + } + if (p->b != 2) {//WARN + free(p);//WARN + } + if (p->c != 3) {//WARN + free(p);//WARN + } + if (p->d != 4) { //WARN + free(p);//WARN + } + free(p);//WARN +} + diff --git a/tests/regression/74-invalid_deref/17-scopes-no-static.c b/tests/regression/74-invalid_deref/17-scopes-no-static.c new file mode 100644 index 0000000000..e0c4b47b73 --- /dev/null +++ b/tests/regression/74-invalid_deref/17-scopes-no-static.c @@ -0,0 +1,22 @@ +// PARAM: --set ana.activated[+] memOutOfBounds +// TODO: I haven't checked why, but we need memOutOfBounds for this case +extern int printf ( const char * format, ... ); + +int *foo2(void) +{ + int arr[1024]; + arr[194] = 13; + return arr + 1; +} + +int *foo(void) +{ + int arr[123]; + return foo2(); +} + +int main(void) { + int *a = foo(); + printf("%d\n", *a);//WARN + return 0; +} diff --git a/tests/regression/74-use_after_free/01-simple-uaf.c b/tests/regression/74-invalid_deref/18-simple-uaf.c similarity index 100% rename from tests/regression/74-use_after_free/01-simple-uaf.c rename to tests/regression/74-invalid_deref/18-simple-uaf.c diff --git a/tests/regression/74-invalid_deref/19-oob-stack-simple.c b/tests/regression/74-invalid_deref/19-oob-stack-simple.c new file mode 100644 index 0000000000..8d022feca4 --- /dev/null +++ b/tests/regression/74-invalid_deref/19-oob-stack-simple.c @@ -0,0 +1,12 @@ +// PARAM: --set ana.activated[+] memOutOfBounds --enable ana.int.interval +#include + +int main(int argc, char const *argv[]) { + int i = 42; + int *ptr = &i; + + *ptr = 5;//NOWARN + *(ptr + 10) = 55;//WARN + + return 0; +} diff --git a/tests/regression/74-invalid_deref/20-scopes-global-var.c b/tests/regression/74-invalid_deref/20-scopes-global-var.c new file mode 100644 index 0000000000..9491e1c574 --- /dev/null +++ b/tests/regression/74-invalid_deref/20-scopes-global-var.c @@ -0,0 +1,29 @@ +int array[10]; + +// function returns array of numbers +int* getNumbers(void) { + for (int i = 0; i < 10; ++i) { + array[i] = i;//NOWARN + } + + return array; +} + +int* getNumbers2(void) { + int* numbers = getNumbers(); + // numbers2 is local + int numbers2[10]; + + for (int i = 0; i < 10; ++i) { + numbers2[i] = numbers[i];//NOWARN + } + + return numbers2; +} + +int main(void) { + int *numbers = getNumbers2(); + numbers[0] = 100;//WARN + + return 0; +} diff --git a/tests/regression/74-invalid_deref/21-oob-loop.c b/tests/regression/74-invalid_deref/21-oob-loop.c new file mode 100644 index 0000000000..4f637d487e --- /dev/null +++ b/tests/regression/74-invalid_deref/21-oob-loop.c @@ -0,0 +1,16 @@ +// PARAM: --set ana.activated[+] memOutOfBounds --set exp.unrolling-factor 10 --enable ana.int.interval +#include +#include + +int main(int argc, char const *argv[]) { + char *ptr = malloc(5 * sizeof(char)); + + for (int i = 0; i < 10; i++) { + ptr++; + } + + printf("%s", *ptr); //WARN + free(ptr); //WARN + + return 0; +} diff --git a/tests/regression/74-invalid_deref/22-scopes-static.c b/tests/regression/74-invalid_deref/22-scopes-static.c new file mode 100644 index 0000000000..c13b665c84 --- /dev/null +++ b/tests/regression/74-invalid_deref/22-scopes-static.c @@ -0,0 +1,52 @@ +extern int printf (const char* format, ...); + +// function returns array of numbers +int* getNumbers() { + + static int array[10]; + + for (int i = 0; i < 10; ++i) { + array[i] = i;//NOWARN + } + + return array; +} + +int* getNumbers2() { + int* numbers = getNumbers(); + static int numbers2[10]; + for (int i = 0; i < 10; ++i) { + numbers2[i] = numbers[i];//NOWARN + } + return numbers2; +} + +int* getNumbers3() { + int* numbers = getNumbers2(); + int numbers3[10]; + for (int i = 0; i < 10; ++i) { + numbers3[i] = numbers[i];//NOWARN + } + + return numbers3; +} + +int* getNumbers4() { + int* numbers = getNumbers3(); + static int numbers4[10]; + for (int i = 0; i < 10; ++i) { + numbers4[i] = numbers[i];//WARN + } + return numbers4; +} + +int main (void) { + + int *numbers = getNumbers4(); + + for (int i = 0; i < 10; i++ ) { + printf( "%d\n", *(numbers + i));//NOWARN + } + + return 0; +} diff --git a/tests/regression/74-invalid_deref/23-oob-deref-after-ptr-arith.c b/tests/regression/74-invalid_deref/23-oob-deref-after-ptr-arith.c new file mode 100644 index 0000000000..5046a00664 --- /dev/null +++ b/tests/regression/74-invalid_deref/23-oob-deref-after-ptr-arith.c @@ -0,0 +1,18 @@ +// PARAM: --set ana.activated[+] memOutOfBounds --enable ana.int.interval +#include +#include + +int main(int argc, char const *argv[]) { + char *ptr = malloc(5 * sizeof(char)); + + ptr++;//NOWARN + printf("%s", *ptr);//NOWARN + ptr = ptr + 5;//NOWARN + printf("%s", *ptr);//WARN + *(ptr + 1) = 'b';//WARN + *(ptr + 10) = 'c';//WARN + + free(ptr); + + return 0; +} diff --git a/tests/regression/74-use_after_free/05-uaf-free-in-wrapper-fun.c b/tests/regression/74-invalid_deref/24-uaf-free-in-wrapper-fun.c similarity index 100% rename from tests/regression/74-use_after_free/05-uaf-free-in-wrapper-fun.c rename to tests/regression/74-invalid_deref/24-uaf-free-in-wrapper-fun.c diff --git a/tests/regression/74-use_after_free/06-uaf-struct.c b/tests/regression/74-invalid_deref/25-uaf-struct.c similarity index 80% rename from tests/regression/74-use_after_free/06-uaf-struct.c rename to tests/regression/74-invalid_deref/25-uaf-struct.c index 02c4f3e77a..fa3ffc7b56 100644 --- a/tests/regression/74-use_after_free/06-uaf-struct.c +++ b/tests/regression/74-invalid_deref/25-uaf-struct.c @@ -17,12 +17,15 @@ int main(int argc, char **argv) { char line[128]; while (1) { + // printf() is considered an implicit deref => need to warn here printf("[ auth = %p, service = %p ]\n", auth, service); //WARN if (fgets(line, sizeof(line), stdin) == NULL) break; if (strncmp(line, "auth ", 5) == 0) { - auth = malloc(sizeof(auth)); //WARN + // No deref happening in the line below => no need to warn + auth = malloc(sizeof(auth)); //NOWARN + // memset() is considered an implicit deref => need to warn memset(auth, 0, sizeof(auth)); //WARN if (strlen(line + 5) < 31) { strcpy(auth->name, line + 5); //WARN diff --git a/tests/regression/74-invalid_deref/26-memset-memcpy-addr-offs.c b/tests/regression/74-invalid_deref/26-memset-memcpy-addr-offs.c new file mode 100644 index 0000000000..725024946e --- /dev/null +++ b/tests/regression/74-invalid_deref/26-memset-memcpy-addr-offs.c @@ -0,0 +1,20 @@ +// PARAM: --set ana.activated[+] memOutOfBounds --enable ana.int.interval --disable warn.info +// TODO: The "--disable warn.info" part is a temporary fix and needs to be removed once the MacOS CI job is fixed +#include +#include + +int main(int argc, char const *argv[]) { + int *a = malloc(10 * sizeof(int)); //Size is 40 bytes, assuming a 4-byte int + int *b = malloc(15 * sizeof(int)); //Size is 60 bytes, assuming a 4-byte int + + memset(a, 0, 40); //NOWARN + memcpy(a, b, 40); //NOWARN + + a += 3; + + memset(a, 0, 40); //WARN + memcpy(a, b, 40); //WARN + + memset(a, 0, 37); //NOWARN + memcpy(a, b, 37); //NOWARN +} \ No newline at end of file diff --git a/tests/regression/74-use_after_free/11-wrapper-funs-uaf.c b/tests/regression/74-invalid_deref/27-wrapper-funs-uaf.c similarity index 58% rename from tests/regression/74-use_after_free/11-wrapper-funs-uaf.c rename to tests/regression/74-invalid_deref/27-wrapper-funs-uaf.c index 3ed540b53d..cc6539eff2 100644 --- a/tests/regression/74-use_after_free/11-wrapper-funs-uaf.c +++ b/tests/regression/74-invalid_deref/27-wrapper-funs-uaf.c @@ -27,12 +27,14 @@ int main(int argc, char const *argv[]) { my_free2(p); *(p + 42) = 'c'; //WARN + // printf() is considered an implicit deref => need to warn printf("%s", p); //WARN - char *p2 = p; //WARN - - my_free2(p); //WARN - my_free2(p2); //WARN + // No dereferencing happening in the lines below => no need to warn for an invalid-deref + // Also no need to warn for an invalid-free, as the call to free is within these functions and they're not the "free" function itself + char *p2 = p; //NOWARN + my_free2(p); //NOWARN + my_free2(p2); //NOWARN return 0; } diff --git a/tests/regression/74-use_after_free/12-multi-threaded-uaf.c b/tests/regression/74-invalid_deref/28-multi-threaded-uaf.c similarity index 87% rename from tests/regression/74-use_after_free/12-multi-threaded-uaf.c rename to tests/regression/74-invalid_deref/28-multi-threaded-uaf.c index 0c647eff76..f6d11ae098 100644 --- a/tests/regression/74-use_after_free/12-multi-threaded-uaf.c +++ b/tests/regression/74-invalid_deref/28-multi-threaded-uaf.c @@ -1,4 +1,4 @@ -//PARAM: --set ana.activated[+] useAfterFree +//PARAM: --set ana.activated[+] useAfterFree --set ana.activated[+] threadJoins #include #include #include diff --git a/tests/regression/74-invalid_deref/29-multi-threaded-uaf-with-joined-thread.c b/tests/regression/74-invalid_deref/29-multi-threaded-uaf-with-joined-thread.c new file mode 100644 index 0000000000..2ce291f9d1 --- /dev/null +++ b/tests/regression/74-invalid_deref/29-multi-threaded-uaf-with-joined-thread.c @@ -0,0 +1,33 @@ +//PARAM: --set ana.activated[+] useAfterFree --set ana.activated[+] threadJoins +#include +#include +#include + +int* gptr; + +// Mutex to ensure we don't get race warnings, but the UAF warnings we actually care about +pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER; + +void *t_use(void* p) { + pthread_mutex_lock(&mtx); + *gptr = 0; //NOWARN + pthread_mutex_unlock(&mtx); +} + +int main() { + gptr = malloc(sizeof(int)); + *gptr = 42; + + pthread_t using_thread; + pthread_create(&using_thread, NULL, t_use, NULL); + + // Join using_thread before freeing gptr in the main thread + pthread_join(using_thread, NULL); + + pthread_mutex_lock(&mtx); + *gptr = 43; //NOWARN + free(gptr); //NOWARN + pthread_mutex_unlock(&mtx); + + return 0; +} \ No newline at end of file diff --git a/tests/regression/74-invalid_deref/30-calloc.c b/tests/regression/74-invalid_deref/30-calloc.c new file mode 100644 index 0000000000..624e9c212d --- /dev/null +++ b/tests/regression/74-invalid_deref/30-calloc.c @@ -0,0 +1,9 @@ +//PARAM: --set ana.activated[+] useAfterFree --set ana.activated[+] threadJoins --set ana.activated[+] memOutOfBounds --enable ana.int.interval --set ana.base.arrays.domain partitioned +#include +#include + +int main(int argc, char **argv) +{ + int* ptrCalloc = calloc(100UL,8UL); + *ptrCalloc = 8; //NOWARN +} diff --git a/tests/regression/74-invalid_deref/31-multithreaded.c b/tests/regression/74-invalid_deref/31-multithreaded.c new file mode 100644 index 0000000000..8a0c12350b --- /dev/null +++ b/tests/regression/74-invalid_deref/31-multithreaded.c @@ -0,0 +1,21 @@ +//PARAM: --set ana.path_sens[+] threadflag --set ana.activated[+] memOutOfBounds --set ana.base.privatization mutex-meet-tid +#include + +int data; +int *p = &data, *q; +pthread_mutex_t mutex; +void *t_fun(void *arg) { + pthread_mutex_lock(&mutex); + *p = 8; + pthread_mutex_unlock(&mutex); + return ((void *)0); +} +int main() { + pthread_t id; + pthread_create(&id, ((void *)0), t_fun, ((void *)0)); + q = p; + pthread_mutex_lock(&mutex); + *q = 8; //NOWARN + pthread_mutex_unlock(&mutex); + return 0; +} diff --git a/tests/regression/75-invalid_dealloc/01-invalid-dealloc-simple.c b/tests/regression/75-invalid_free/01-invalid-dealloc-simple.c similarity index 100% rename from tests/regression/75-invalid_dealloc/01-invalid-dealloc-simple.c rename to tests/regression/75-invalid_free/01-invalid-dealloc-simple.c diff --git a/tests/regression/75-invalid_dealloc/02-invalid-dealloc-struct.c b/tests/regression/75-invalid_free/02-invalid-dealloc-struct.c similarity index 100% rename from tests/regression/75-invalid_dealloc/02-invalid-dealloc-struct.c rename to tests/regression/75-invalid_free/02-invalid-dealloc-struct.c diff --git a/tests/regression/75-invalid_dealloc/03-invalid-dealloc-array.c b/tests/regression/75-invalid_free/03-invalid-dealloc-array.c similarity index 100% rename from tests/regression/75-invalid_dealloc/03-invalid-dealloc-array.c rename to tests/regression/75-invalid_free/03-invalid-dealloc-array.c diff --git a/tests/regression/75-invalid_dealloc/04-invalid-realloc.c b/tests/regression/75-invalid_free/04-invalid-realloc.c similarity index 100% rename from tests/regression/75-invalid_dealloc/04-invalid-realloc.c rename to tests/regression/75-invalid_free/04-invalid-realloc.c diff --git a/tests/regression/75-invalid_free/05-free-at-offset.c b/tests/regression/75-invalid_free/05-free-at-offset.c new file mode 100644 index 0000000000..c9ec66c769 --- /dev/null +++ b/tests/regression/75-invalid_free/05-free-at-offset.c @@ -0,0 +1,9 @@ +#include + +int main(int argc, char const *argv[]) { + char *ptr = malloc(42 * sizeof(char)); + ptr = ptr + 7; + free(ptr); //WARN + + return 0; +} diff --git a/tests/regression/75-invalid_free/06-realloc-at-offset.c b/tests/regression/75-invalid_free/06-realloc-at-offset.c new file mode 100644 index 0000000000..64a42654e1 --- /dev/null +++ b/tests/regression/75-invalid_free/06-realloc-at-offset.c @@ -0,0 +1,11 @@ +#include + +#define MAX_SIZE 5000 + +int main(int argc, char const *argv[]) { + char *ptr = malloc(42 * sizeof(char)); + ptr = ptr + 7; + realloc(ptr, MAX_SIZE); //WARN + + return 0; +} diff --git a/tests/regression/75-invalid_free/07-free-at-struct-offset.c b/tests/regression/75-invalid_free/07-free-at-struct-offset.c new file mode 100644 index 0000000000..f64d66d8fc --- /dev/null +++ b/tests/regression/75-invalid_free/07-free-at-struct-offset.c @@ -0,0 +1,15 @@ +#include + +typedef struct custom_t { + char *x; + int y; +} custom_t; + +int main(int argc, char const *argv[]) { + custom_t *struct_ptr = malloc(sizeof(custom_t)); + struct_ptr->x = malloc(10 * sizeof(char)); + free(&struct_ptr->x); //NOWARN + free(&struct_ptr->y); //WARN + free(struct_ptr); //NOWARN + return 0; +} diff --git a/tests/regression/74-use_after_free/08-itc-no-double-free.c b/tests/regression/75-invalid_free/08-itc-no-double-free.c similarity index 100% rename from tests/regression/74-use_after_free/08-itc-no-double-free.c rename to tests/regression/75-invalid_free/08-itc-no-double-free.c diff --git a/tests/regression/75-invalid_free/09-juliet-invalid-dealloc-alloca.c b/tests/regression/75-invalid_free/09-juliet-invalid-dealloc-alloca.c new file mode 100644 index 0000000000..9a84d1e49a --- /dev/null +++ b/tests/regression/75-invalid_free/09-juliet-invalid-dealloc-alloca.c @@ -0,0 +1,75 @@ +#include +#include + +typedef struct twoIntsStruct { + int intOne ; + int intTwo ; +} twoIntsStruct; + +void CWE590_Free_Memory_Not_on_Heap__free_struct_alloca_54_bad(void) { + twoIntsStruct *data; + data = (twoIntsStruct *)0; + { + twoIntsStruct *dataBuffer = __builtin_alloca(800UL); + { + size_t i; + i = 0UL; + + goto ldv_3204; + ldv_3203: + ; + + (dataBuffer + i)->intOne = 1; + (dataBuffer + i)->intTwo = 1; + + i += 1UL; + ldv_3204: + ; + + if (i <= 99UL) + goto ldv_3203; + else + goto ldv_3205; + ldv_3205: + ; + } + + data = dataBuffer; + } + + CWE590_Free_Memory_Not_on_Heap__free_struct_alloca_54b_badSink(data); + return; +} + +void CWE590_Free_Memory_Not_on_Heap__free_struct_alloca_54b_badSink(twoIntsStruct *data) { + CWE590_Free_Memory_Not_on_Heap__free_struct_alloca_54c_badSink(data); + return; +} + +void CWE590_Free_Memory_Not_on_Heap__free_struct_alloca_54c_badSink(twoIntsStruct *data) { + CWE590_Free_Memory_Not_on_Heap__free_struct_alloca_54d_badSink(data); + return; +} + +void CWE590_Free_Memory_Not_on_Heap__free_struct_alloca_54d_badSink(twoIntsStruct *data) { + CWE590_Free_Memory_Not_on_Heap__free_struct_alloca_54e_badSink(data); + return; +} + +void CWE590_Free_Memory_Not_on_Heap__free_struct_alloca_54e_badSink(twoIntsStruct *data) { + free((void *)data); //WARN + return; +} + +int main(int argc, char **argv) { + int __retres; + { + CWE590_Free_Memory_Not_on_Heap__free_struct_alloca_54_bad(); + __retres = 0; + goto return_label; + } + + __retres = 0; + return_label: + return __retres; +} diff --git a/tests/regression/75-invalid_free/10-invalid-dealloc-union.c b/tests/regression/75-invalid_free/10-invalid-dealloc-union.c new file mode 100644 index 0000000000..be1eaa056d --- /dev/null +++ b/tests/regression/75-invalid_free/10-invalid-dealloc-union.c @@ -0,0 +1,42 @@ +extern void abort(void); +#include + +extern int __VERIFIER_nondet_int(void); + +int main() +{ + union { + void *p0; + + struct { + char c[2]; + int p1; + int p2; + } str; + + } data; + + // alloc 37B on heap + data.p0 = malloc(37U); + + // avoid introducing a memleak + void *ptr = data.p0; + + // this should be fine + if(__VERIFIER_nondet_int()) { + data.str.p2 = 20; + } else { + data.str.p2 = 30; + } + + if(25 > data.str.p2) { + // avoids memleak + data.str.c[1] = sizeof data.str.p1; + } + + // invalid free() + free(data.p0);//WARN + + free(ptr);//NOWARN + return 0; +} diff --git a/tests/regression/74-use_after_free/07-itc-double-free.c b/tests/regression/75-invalid_free/11-itc-double-free.c similarity index 100% rename from tests/regression/74-use_after_free/07-itc-double-free.c rename to tests/regression/75-invalid_free/11-itc-double-free.c diff --git a/tests/regression/75-invalid_free/12-realloc-at-struct-offset.c b/tests/regression/75-invalid_free/12-realloc-at-struct-offset.c new file mode 100644 index 0000000000..fddb8a7694 --- /dev/null +++ b/tests/regression/75-invalid_free/12-realloc-at-struct-offset.c @@ -0,0 +1,15 @@ +#include + +typedef struct custom_t { + char *x; + int y; +} custom_t; + +int main(int argc, char const *argv[]) { + custom_t *struct_ptr = malloc(sizeof(custom_t)); + struct_ptr->x = malloc(10 * sizeof(char)); + realloc(&struct_ptr->x, 50); //NOWARN + realloc(&struct_ptr->y, 50); //WARN + realloc(struct_ptr, 2 * sizeof(custom_t)); //NOWARN + return 0; +} diff --git a/tests/regression/74-use_after_free/10-juliet-double-free.c b/tests/regression/75-invalid_free/13-juliet-double-free.c similarity index 100% rename from tests/regression/74-use_after_free/10-juliet-double-free.c rename to tests/regression/75-invalid_free/13-juliet-double-free.c diff --git a/tests/regression/76-memleak/01-simple-no-mem-leak.c b/tests/regression/76-memleak/01-simple-no-mem-leak.c new file mode 100644 index 0000000000..da6cdacddb --- /dev/null +++ b/tests/regression/76-memleak/01-simple-no-mem-leak.c @@ -0,0 +1,9 @@ +//PARAM: --set ana.malloc.unique_address_count 1 --set ana.activated[+] memLeak +#include + +int main(int argc, char const *argv[]) { + int *p = malloc(sizeof(int)); + free(p); + + return 0; //NOWARN +} diff --git a/tests/regression/76-memleak/02-simple-mem-leak.c b/tests/regression/76-memleak/02-simple-mem-leak.c new file mode 100644 index 0000000000..3673addfdf --- /dev/null +++ b/tests/regression/76-memleak/02-simple-mem-leak.c @@ -0,0 +1,8 @@ +//PARAM: --set ana.malloc.unique_address_count 1 --set ana.activated[+] memLeak +#include + +int main(int argc, char const *argv[]) { + int *p = malloc(sizeof(int)); + // No free => memory is leaked + return 0; //WARN +} diff --git a/tests/regression/76-memleak/03-simple-exit-mem-leak.c b/tests/regression/76-memleak/03-simple-exit-mem-leak.c new file mode 100644 index 0000000000..451dafa471 --- /dev/null +++ b/tests/regression/76-memleak/03-simple-exit-mem-leak.c @@ -0,0 +1,7 @@ +//PARAM: --set ana.malloc.unique_address_count 1 --set ana.activated[+] memLeak +#include + +int main(int argc, char const *argv[]) { + int *p = malloc(sizeof(int)); + exit(0); //WARN +} diff --git a/tests/regression/76-memleak/04-simple-abort-mem-leak.c b/tests/regression/76-memleak/04-simple-abort-mem-leak.c new file mode 100644 index 0000000000..d4001410de --- /dev/null +++ b/tests/regression/76-memleak/04-simple-abort-mem-leak.c @@ -0,0 +1,7 @@ +//PARAM: --set ana.malloc.unique_address_count 1 --set ana.activated[+] memLeak +#include + +int main(int argc, char const *argv[]) { + int *p = malloc(sizeof(int)); + abort(); //WARN +} diff --git a/tests/regression/76-memleak/05-simple-assert-no-mem-leak.c b/tests/regression/76-memleak/05-simple-assert-no-mem-leak.c new file mode 100644 index 0000000000..8dbf20c433 --- /dev/null +++ b/tests/regression/76-memleak/05-simple-assert-no-mem-leak.c @@ -0,0 +1,10 @@ +//PARAM: --set ana.malloc.unique_address_count 1 --set ana.activated[+] memLeak +#include +#include + +int main(int argc, char const *argv[]) { + int *p = malloc(sizeof(int)); + assert(1); + free(p); + return 0; //NOWARN +} diff --git a/tests/regression/76-memleak/06-simple-assert-mem-leak.c b/tests/regression/76-memleak/06-simple-assert-mem-leak.c new file mode 100644 index 0000000000..b2f78388dc --- /dev/null +++ b/tests/regression/76-memleak/06-simple-assert-mem-leak.c @@ -0,0 +1,8 @@ +//PARAM: --set warn.assert false --set ana.malloc.unique_address_count 1 --set ana.activated[+] memLeak +#include +#include + +int main(int argc, char const *argv[]) { + int *p = malloc(sizeof(int)); + assert(0); //WARN +} diff --git a/tests/regression/76-memleak/07-simple-quick-exit-mem-leak.c b/tests/regression/76-memleak/07-simple-quick-exit-mem-leak.c new file mode 100644 index 0000000000..eba23385b8 --- /dev/null +++ b/tests/regression/76-memleak/07-simple-quick-exit-mem-leak.c @@ -0,0 +1,7 @@ +//PARAM: --set ana.malloc.unique_address_count 1 --set ana.activated[+] memLeak +#include + +int main(int argc, char const *argv[]) { + int *p = malloc(sizeof(int)); + quick_exit(0); //WARN +} diff --git a/tests/regression/76-memleak/08-unreachable-mem.c b/tests/regression/76-memleak/08-unreachable-mem.c new file mode 100644 index 0000000000..08e7b4e741 --- /dev/null +++ b/tests/regression/76-memleak/08-unreachable-mem.c @@ -0,0 +1,12 @@ +//PARAM: --set ana.malloc.unique_address_count 1 --set ana.activated[+] memLeak +#include + +int *g; + +int main(int argc, char const *argv[]) { + g = malloc(sizeof(int)); + // Reference to g's heap contents is lost here + g = NULL; + + return 0; //WARN +} diff --git a/tests/regression/76-memleak/09-unreachable-with-local-var.c b/tests/regression/76-memleak/09-unreachable-with-local-var.c new file mode 100644 index 0000000000..bc71bb560e --- /dev/null +++ b/tests/regression/76-memleak/09-unreachable-with-local-var.c @@ -0,0 +1,15 @@ +//PARAM: --set ana.malloc.unique_address_count 1 --set ana.activated[+] memLeak +#include + +int *g; + +int main(int argc, char const *argv[]) { + g = malloc(sizeof(int)); + // Reference to g's heap contents is lost here + g = NULL; + + // According to `valid-memtrack`, the memory of p is unreachable and we don't have a false positive + int *p = malloc(sizeof(int)); + + return 0; //WARN +} diff --git a/tests/regression/76-memleak/10-global-struct-no-ptr.c b/tests/regression/76-memleak/10-global-struct-no-ptr.c new file mode 100644 index 0000000000..490b2bb443 --- /dev/null +++ b/tests/regression/76-memleak/10-global-struct-no-ptr.c @@ -0,0 +1,16 @@ +//PARAM: --set ana.malloc.unique_address_count 1 --set ana.activated[+] memLeak +#include + +typedef struct st { + int *a; + int b; +} st; + +st st_nonptr; + +int main(int argc, char const *argv[]) { + st_nonptr.a = malloc(sizeof(int)); + st_nonptr.a = NULL; + + return 0; //WARN +} diff --git a/tests/regression/76-memleak/11-global-struct-ptr.c b/tests/regression/76-memleak/11-global-struct-ptr.c new file mode 100644 index 0000000000..4ebe1c16b8 --- /dev/null +++ b/tests/regression/76-memleak/11-global-struct-ptr.c @@ -0,0 +1,19 @@ +//PARAM: --set ana.malloc.unique_address_count 1 --set ana.activated[+] memLeak +#include + +typedef struct st { + int *a; + int b; +} st; + +st *st_ptr; + +int main(int argc, char const *argv[]) { + st_ptr = malloc(sizeof(st)); + st_ptr->a = malloc(sizeof(int)); + st_ptr->a = NULL; + free(st_ptr); + + // Only st_ptr->a is causing trouble here + return 0; //WARN +} diff --git a/tests/regression/76-memleak/12-global-nested-struct-ptr.c b/tests/regression/76-memleak/12-global-nested-struct-ptr.c new file mode 100644 index 0000000000..e0f5175064 --- /dev/null +++ b/tests/regression/76-memleak/12-global-nested-struct-ptr.c @@ -0,0 +1,25 @@ +//PARAM: --set ana.malloc.unique_address_count 1 --set ana.activated[+] memLeak +#include + +typedef struct st { + int *a; + int b; +} st; + +typedef struct st2 { + st *st_ptr; +} st2; + +st2 *st_var; + +int main(int argc, char const *argv[]) { + st_var = malloc(sizeof(st2)); + st_var->st_ptr = malloc(sizeof(st)); + st_var->st_ptr->a = malloc(sizeof(int)); + st_var->st_ptr->a = NULL; + free(st_var->st_ptr); + free(st_var); + + // Only st_var->st_ptr->a is causing trouble here + return 0; //WARN +} diff --git a/tests/regression/76-memleak/13-global-nested-struct-ptr-reachable.c b/tests/regression/76-memleak/13-global-nested-struct-ptr-reachable.c new file mode 100644 index 0000000000..1726625a59 --- /dev/null +++ b/tests/regression/76-memleak/13-global-nested-struct-ptr-reachable.c @@ -0,0 +1,29 @@ +//PARAM: --set ana.malloc.unique_address_count 1 --set ana.activated[+] memLeak +#include + +typedef struct st { + int *a; + int b; +} st; + +typedef struct st2 { + st *st_ptr; +} st2; + +st2 *st_var; + +int main(int argc, char const *argv[]) { + st_var = malloc(sizeof(st2)); + st_var->st_ptr = malloc(sizeof(st)); + int *local_ptr = malloc(sizeof(int)); + st_var->st_ptr->a = local_ptr; + local_ptr = NULL; + + free(st_var->st_ptr); + free(st_var); + + // local_ptr's memory is reachable through st_var->st_ptr->a + // It's leaked, because we don't call free() on it + // Hence, there should be a single warning for a memory leak, but not for unreachable memory + return 0; //WARN +} diff --git a/tests/regression/76-memleak/14-global-nested-struct-non-ptr-reachable.c b/tests/regression/76-memleak/14-global-nested-struct-non-ptr-reachable.c new file mode 100644 index 0000000000..1153bd81e0 --- /dev/null +++ b/tests/regression/76-memleak/14-global-nested-struct-non-ptr-reachable.c @@ -0,0 +1,25 @@ +//PARAM: --set ana.malloc.unique_address_count 1 --set ana.activated[+] memLeak +#include + +typedef struct st { + int *a; + int b; +} st; + +typedef struct st2 { + st *st_ptr; +} st2; + +st2 st_var; + +int main(int argc, char const *argv[]) { + st_var.st_ptr = malloc(sizeof(st)); + int *local_ptr = malloc(sizeof(int)); + st_var.st_ptr->a = local_ptr; + local_ptr = NULL; + free(st_var.st_ptr); + + // local_ptr's memory is reachable through st_var.st_ptr->a, but it's not freed + // Hence, there should be only a single warning for a memory leak, but not for unreachable memory + return 0; //WARN +} diff --git a/tests/regression/76-memleak/20-invalid-memcleanup-multi-threaded.c b/tests/regression/76-memleak/20-invalid-memcleanup-multi-threaded.c new file mode 100644 index 0000000000..038801f219 --- /dev/null +++ b/tests/regression/76-memleak/20-invalid-memcleanup-multi-threaded.c @@ -0,0 +1,36 @@ +//PARAM: --set ana.malloc.unique_address_count 1 --set ana.activated[+] memLeak --set ana.activated[+] thread +#include +#include + +int *g; +int *m1; + +void *f1(void *arg) { + m1 = malloc(sizeof(int)); + // Thread t1 leaks m1 here + pthread_exit(NULL); //WARN +} + +void *f2(void *arg) { + int *m2; + m2 = malloc(sizeof(int)); + free(m2); // No leak for thread t2, since it calls free before exiting + pthread_exit(NULL); //NOWARN +} + +int main(int argc, char const *argv[]) { + g = malloc(sizeof(int)); + pthread_t t1; + pthread_create(&t1, NULL, f1, NULL); + + pthread_t t2; + pthread_create(&t2, NULL, f2, NULL); + + free(g); + + pthread_join(t1, NULL); + pthread_join(t2, NULL); + + // main thread is not leaking anything + return 0; //NOWARN +} diff --git a/tests/regression/76-memleak/21-invalid-memcleanup-multi-threaded-abort.c b/tests/regression/76-memleak/21-invalid-memcleanup-multi-threaded-abort.c new file mode 100644 index 0000000000..eaba1e91b5 --- /dev/null +++ b/tests/regression/76-memleak/21-invalid-memcleanup-multi-threaded-abort.c @@ -0,0 +1,35 @@ +//PARAM: --set ana.malloc.unique_address_count 1 --set ana.activated[+] memLeak --set ana.activated[+] thread +#include + +int *g; +int *m1; + +void *f1(void *arg) { + m1 = malloc(sizeof(int)); + // Thread t1 leaks m1 here + exit(2); //WARN +} + +void *f2(void *arg) { + int *m2; + m2 = malloc(sizeof(int)); + free(m2); // No leak for thread t2, since it calls free before exiting + pthread_exit(NULL); //NOWARN +} + +int main(int argc, char const *argv[]) { + g = malloc(sizeof(int)); + pthread_t t1; + pthread_create(&t1, NULL, f1, NULL); + + pthread_t t2; + pthread_create(&t2, NULL, f2, NULL); + + free(g); + + pthread_join(t1, NULL); + pthread_join(t2, NULL); + + // main thread is not leaking anything + return 0; //NOWARN +} diff --git a/tests/regression/76-memleak/22-leak-later.c b/tests/regression/76-memleak/22-leak-later.c new file mode 100644 index 0000000000..6e6e51bbdc --- /dev/null +++ b/tests/regression/76-memleak/22-leak-later.c @@ -0,0 +1,25 @@ +//PARAM: --set ana.malloc.unique_address_count 1 --set ana.activated[+] memLeak +#include +#include + +int *g; +int *m1; +int *m2; + +void *f1(void *arg) { + int top; + + // Thread t1 leaks m0 here + exit(2); //WARN +} + +int main(int argc, char const *argv[]) { + pthread_t t1; + pthread_create(&t1, NULL, f1, NULL); + + int* m0 = malloc(sizeof(int)); + free(m0); + + // main thread is not leaking anything + return 0; +} diff --git a/tests/regression/76-memleak/23-leak-later-nested.c b/tests/regression/76-memleak/23-leak-later-nested.c new file mode 100644 index 0000000000..952dc66334 --- /dev/null +++ b/tests/regression/76-memleak/23-leak-later-nested.c @@ -0,0 +1,34 @@ +//PARAM: --set ana.malloc.unique_address_count 1 --set ana.activated[+] memLeak +#include +#include + +int *g; +int *m1; +int *m2; + +void *f2(void *arg) { + // Thread t2 leaks m0 and t1_ptr here + quick_exit(2); //WARN +} + +void *f1(void *arg) { + pthread_t t2; + pthread_create(&t2, NULL, f2, NULL); + + int *t1_ptr = malloc(sizeof(int)); + + pthread_join(t2, NULL); + // t1_ptr is leaked, since t2 calls quick_exit() potentially before this program point + free(t1_ptr); +} + +int main(int argc, char const *argv[]) { + pthread_t t1; + pthread_create(&t1, NULL, f1, NULL); + + int* m0 = malloc(sizeof(int)); + free(m0); + + // main thread is not leaking anything + return 0; +} diff --git a/tests/regression/76-memleak/24-multi-threaded-assert.c b/tests/regression/76-memleak/24-multi-threaded-assert.c new file mode 100644 index 0000000000..309a5dde75 --- /dev/null +++ b/tests/regression/76-memleak/24-multi-threaded-assert.c @@ -0,0 +1,34 @@ +//PARAM: --set ana.malloc.unique_address_count 1 --set ana.activated[+] memLeak --disable warn.assert +#include +#include +#include + +int *g; +int *m1; +int *m2; + +void *f2(void *arg) { + // Thread t2 leaks m0 and t1_ptr here + assert(0); //WARN +} + +void *f1(void *arg) { + pthread_t t2; + pthread_create(&t2, NULL, f2, NULL); + + int *t1_ptr = malloc(sizeof(int)); + assert(1); //NOWARN + pthread_join(t2, NULL); + free(t1_ptr); +} + +int main(int argc, char const *argv[]) { + pthread_t t1; + pthread_create(&t1, NULL, f1, NULL); + + int* m0 = malloc(sizeof(int)); + free(m0); + + // main thread is not leaking anything + return 0; +} diff --git a/tests/regression/76-memleak/25-assert-unknown-multi-threaded.c b/tests/regression/76-memleak/25-assert-unknown-multi-threaded.c new file mode 100644 index 0000000000..95eb291887 --- /dev/null +++ b/tests/regression/76-memleak/25-assert-unknown-multi-threaded.c @@ -0,0 +1,20 @@ +//PARAM: --set ana.malloc.unique_address_count 1 --set ana.activated[+] memLeak --disable warn.assert +#include +#include +#include + +void *f1(void *arg) { + int top; + assert(top); //WARN +} + +int main(int argc, char const *argv[]) { + pthread_t t1; + pthread_create(&t1, NULL, f1, NULL); + + int* m0 = malloc(sizeof(int)); + free(m0); + + // main thread is not leaking anything + return 0; +} diff --git a/tests/regression/76-memleak/26-invalid-memcleanup-multi-threaded-betterpiv.c b/tests/regression/76-memleak/26-invalid-memcleanup-multi-threaded-betterpiv.c new file mode 100644 index 0000000000..9f636ab587 --- /dev/null +++ b/tests/regression/76-memleak/26-invalid-memcleanup-multi-threaded-betterpiv.c @@ -0,0 +1,36 @@ +//PARAM: --set ana.malloc.unique_address_count 1 --set ana.activated[+] memLeak --set ana.base.privatization mutex-meet-tid --set ana.path_sens[+] threadflag --set ana.activated[+] thread +#include +#include + +int *g; +int *m1; +int *m2; + +void *f1(void *arg) { + m1 = malloc(sizeof(int)); + // Thread t1 leaks m1 here + pthread_exit(NULL); //WARN +} + +void *f2(void *arg) { + m2 = malloc(sizeof(int)); + free(m2); // No leak for thread t2, since it calls free before exiting + pthread_exit(NULL); //NOWARN +} + +int main(int argc, char const *argv[]) { + g = malloc(sizeof(int)); + pthread_t t1; + pthread_create(&t1, NULL, f1, NULL); + + pthread_t t2; + pthread_create(&t2, NULL, f2, NULL); + + free(g); + + pthread_join(t1, NULL); + pthread_join(t2, NULL); + + // main thread is not leaking anything + return 0; //NOWARN +} diff --git a/tests/regression/76-memleak/27-mem-leak-not-joined-thread.c b/tests/regression/76-memleak/27-mem-leak-not-joined-thread.c new file mode 100644 index 0000000000..15f249ffe1 --- /dev/null +++ b/tests/regression/76-memleak/27-mem-leak-not-joined-thread.c @@ -0,0 +1,19 @@ +//PARAM: --set ana.malloc.unique_address_count 1 --set ana.activated[+] memLeak --set ana.activated[+] thread +#include +#include + +int *m1; + +void *f1(void *arg) { + m1 = malloc(sizeof(int)); + while (1); + return NULL; +} + +int main(int argc, char const *argv[]) { + pthread_t t1; + pthread_create(&t1, NULL, f1, NULL); + + // memory from thread f1 which was not joined into main, is not freed + return 0; //WARN +} \ No newline at end of file diff --git a/tests/regression/76-memleak/28-no-mem-leak-thread-exit-main.c b/tests/regression/76-memleak/28-no-mem-leak-thread-exit-main.c new file mode 100644 index 0000000000..f7340d1d4f --- /dev/null +++ b/tests/regression/76-memleak/28-no-mem-leak-thread-exit-main.c @@ -0,0 +1,22 @@ +//PARAM: --set ana.malloc.unique_address_count 1 --set ana.activated[+] memLeak --set ana.activated[+] thread +#include +#include + +int *m1; + +void *f1(void *arg) { + m1 = malloc(sizeof(int)); + while (1); + return NULL; +} + +int main(int argc, char const *argv[]) { + pthread_t t1; + pthread_create(&t1, NULL, f1, NULL); + + // A pthread_exit called in main will wait for other threads to finish + // Therefore, no memory leak here + pthread_exit(NULL); // NOWARN + + return 0; // NOWARN (unreachable) +} \ No newline at end of file diff --git a/tests/regression/76-memleak/29-mem-leak-thread-return.c b/tests/regression/76-memleak/29-mem-leak-thread-return.c new file mode 100644 index 0000000000..bec64ca22f --- /dev/null +++ b/tests/regression/76-memleak/29-mem-leak-thread-return.c @@ -0,0 +1,26 @@ +//PARAM: --set ana.malloc.unique_address_count 1 --set ana.activated[+] memLeak --set ana.activated[+] thread +#include +#include + +int *m1; + +void *f2(void *arg) { + m1 = malloc(sizeof(int)); + while (1); + return NULL; +} + +void *f1(void *arg) { + pthread_t t2; + pthread_create(&t2, NULL, f2, NULL); + + return NULL; +} + +int main(int argc, char const *argv[]) { + pthread_t t1; + pthread_create(&t1, NULL, f1, NULL); + pthread_join(t1, NULL); + + return 0; // WARN +} \ No newline at end of file diff --git a/tests/regression/76-memleak/30-mem-leak-thread-exit.c b/tests/regression/76-memleak/30-mem-leak-thread-exit.c new file mode 100644 index 0000000000..e98ae3f346 --- /dev/null +++ b/tests/regression/76-memleak/30-mem-leak-thread-exit.c @@ -0,0 +1,27 @@ +//PARAM: --set ana.malloc.unique_address_count 1 --set ana.activated[+] memLeak --set ana.activated[+] thread +#include +#include + +int *m1; + +void *f2(void *arg) { + m1 = malloc(sizeof(int)); + while (1); + return NULL; +} + +void *f1(void *arg) { + pthread_t t2; + pthread_create(&t2, NULL, f2, NULL); + + pthread_exit(NULL); + return NULL; +} + +int main(int argc, char const *argv[]) { + pthread_t t1; + pthread_create(&t1, NULL, f1, NULL); + pthread_join(t1, NULL); + + return 0; // WARN +} \ No newline at end of file diff --git a/tests/regression/76-memleak/31-no-mem-leak-return.c b/tests/regression/76-memleak/31-no-mem-leak-return.c new file mode 100644 index 0000000000..70e0c66216 --- /dev/null +++ b/tests/regression/76-memleak/31-no-mem-leak-return.c @@ -0,0 +1,32 @@ +//PARAM: --set ana.malloc.unique_address_count 1 --set ana.activated[+] memLeak --set ana.activated[+] thread +#include +#include + + +void *f2(void *arg) { + int* m1 = malloc(sizeof(int)); + free(m1); + return NULL; +} + +// We check here that the analysis can distinguish between thread returns and normal returns + +void startf2(pthread_t* t){ + pthread_create(t, NULL, f2, NULL); + return; //NOWARN +} + +void *f1(void *arg) { + pthread_t t2; + startf2(&t2); + pthread_join(t2, NULL); + return NULL; // NOWARN +} + +int main(int argc, char const *argv[]) { + pthread_t t1; + pthread_create(&t1, NULL, f1, NULL); + pthread_join(t1, NULL); + + return 0; // NOWARN +} \ No newline at end of file diff --git a/tests/regression/78-termination/01-simple-loop-terminating.c b/tests/regression/78-termination/01-simple-loop-terminating.c new file mode 100644 index 0000000000..8ca4610057 --- /dev/null +++ b/tests/regression/78-termination/01-simple-loop-terminating.c @@ -0,0 +1,15 @@ +// SKIP TERM PARAM: --set "ana.activated[+]" termination --set ana.activated[+] apron --enable ana.int.interval --set ana.apron.domain polyhedra +#include + +int main() +{ + int i = 1; + + while (i <= 10) + { + printf("%d\n", i); + i++; + } + + return 0; +} diff --git a/tests/regression/78-termination/02-simple-loop-nonterminating.c b/tests/regression/78-termination/02-simple-loop-nonterminating.c new file mode 100644 index 0000000000..d8847e2b74 --- /dev/null +++ b/tests/regression/78-termination/02-simple-loop-nonterminating.c @@ -0,0 +1,12 @@ +// SKIP NONTERM PARAM: --set "ana.activated[+]" termination --set ana.activated[+] apron --enable ana.int.interval --set ana.apron.domain polyhedra +#include + +int main() +{ + while (1) // NONTERMLOOP termination analysis shall mark beginning of while as non-terminating loop + { + continue; + } + + return 0; +} diff --git a/tests/regression/78-termination/03-nested-loop-terminating.c b/tests/regression/78-termination/03-nested-loop-terminating.c new file mode 100644 index 0000000000..6b31204567 --- /dev/null +++ b/tests/regression/78-termination/03-nested-loop-terminating.c @@ -0,0 +1,27 @@ +// SKIP TODO TERM PARAM: --set "ana.activated[+]" termination --set ana.activated[+] apron --enable ana.int.interval --set ana.apron.domain polyhedra +#include + +int main() +{ + int rows = 3; + int columns = 4; + int i = 1; + + // Outer while loop for rows + while (i <= rows) + { + int j = 1; + + // Inner while loop for columns + while (j <= columns) + { + printf("(%d, %d) ", i, j); + j++; + } + + printf("\n"); + i++; + } + + return 0; +} diff --git a/tests/regression/78-termination/04-nested-loop-nonterminating.c b/tests/regression/78-termination/04-nested-loop-nonterminating.c new file mode 100644 index 0000000000..21b6014509 --- /dev/null +++ b/tests/regression/78-termination/04-nested-loop-nonterminating.c @@ -0,0 +1,23 @@ +// SKIP NONTERM PARAM: --set "ana.activated[+]" termination --set ana.activated[+] apron --enable ana.int.interval --set ana.apron.domain polyhedra +#include + +int main() +{ + int outerCount = 1; + + while (outerCount <= 3) + { + int innerCount = 1; + + while (1) // NONTERMLOOP termination analysis shall mark beginning of while as non-terminating loop + { + printf("(%d, %d) ", outerCount, innerCount); + innerCount++; + } + + printf("\n"); + outerCount++; + } + + return 0; +} diff --git a/tests/regression/78-termination/05-for-loop-terminating.c b/tests/regression/78-termination/05-for-loop-terminating.c new file mode 100644 index 0000000000..7a2b789496 --- /dev/null +++ b/tests/regression/78-termination/05-for-loop-terminating.c @@ -0,0 +1,14 @@ +// SKIP TERM PARAM: --set "ana.activated[+]" termination --set ana.activated[+] apron --enable ana.int.interval --set ana.apron.domain polyhedra +#include + +int main() +{ + int i; + + for (i = 1; i <= 10; i++) + { + printf("%d\n", i); + } + + return 0; +} diff --git a/tests/regression/78-termination/06-for-loop-nonterminating.c b/tests/regression/78-termination/06-for-loop-nonterminating.c new file mode 100644 index 0000000000..6c6123251c --- /dev/null +++ b/tests/regression/78-termination/06-for-loop-nonterminating.c @@ -0,0 +1,12 @@ +// SKIP NONTERM PARAM: --set "ana.activated[+]" termination --set ana.activated[+] apron --enable ana.int.interval --set ana.apron.domain polyhedra +#include + +int main() +{ + for (;;) // NONTERMLOOP termination analysis shall mark beginning of for as non-terminating loop + { + printf("This loop does not terminate.\n"); + } + + return 0; +} diff --git a/tests/regression/78-termination/07-nested-for-loop-terminating.c b/tests/regression/78-termination/07-nested-for-loop-terminating.c new file mode 100644 index 0000000000..3293a1fa2c --- /dev/null +++ b/tests/regression/78-termination/07-nested-for-loop-terminating.c @@ -0,0 +1,20 @@ +// SKIP TODO TERM PARAM: --set "ana.activated[+]" termination --set ana.activated[+] apron --enable ana.int.interval --set ana.apron.domain polyhedra +#include + +int main() +{ + int rows = 3; + int columns = 4; + + // Nested loop to iterate over rows and columns + for (int i = 1; i <= rows; i++) + { + for (int j = 1; j <= columns; j++) + { + printf("(%d, %d) ", i, j); + } + printf("\n"); + } + + return 0; +} diff --git a/tests/regression/78-termination/08-nested-for-loop-nonterminating.c b/tests/regression/78-termination/08-nested-for-loop-nonterminating.c new file mode 100644 index 0000000000..cb65a0d267 --- /dev/null +++ b/tests/regression/78-termination/08-nested-for-loop-nonterminating.c @@ -0,0 +1,19 @@ +// SKIP NONTERM PARAM: --set "ana.activated[+]" termination --set ana.activated[+] apron --enable ana.int.interval --set ana.apron.domain polyhedra +#include + +int main() +{ + int outerCount, innerCount; + + for (outerCount = 1; outerCount <= 3; outerCount++) + { + for (innerCount = 1;; innerCount++) // NONTERMLOOP termination analysis shall mark beginning of for as non-terminating loop + { + printf("(%d, %d) ", outerCount, innerCount); + } + + printf("\n"); + } + + return 0; +} diff --git a/tests/regression/78-termination/09-complex-for-loop-terminating.c b/tests/regression/78-termination/09-complex-for-loop-terminating.c new file mode 100644 index 0000000000..74ee41eae8 --- /dev/null +++ b/tests/regression/78-termination/09-complex-for-loop-terminating.c @@ -0,0 +1,98 @@ +// SKIP TODO TERM PARAM: --set "ana.activated[+]" termination --set ana.activated[+] apron --enable ana.int.interval --set ana.apron.domain polyhedra --set sem.int.signed_overflow assume_none +// Apron is not precise enough for some nested loops +#include + +int loops0(){ + int i, j, k; + + // Outer loop + for (i = 1; i <= 5; i++) + { + // Inner loop 1 + for (j = 1; j <= i; j++) + { + printf("%d ", j); + } + printf("\n"); + + // Inner loop 2 + for (k = i; k >= 1; k--) + { + printf("%d ", k); + } + printf("\n"); + } + + // Additional loop + for (i = 5; i >= 1; i--) + { + for (j = i; j >= 1; j--) + { + printf("%d ", j); + } + printf("\n"); + } + return 0; +} + +int loops1(){ + int i, j, k; + + // Loop with conditions + for (i = 1; i <= 10; i++) + { + if (i % 2 == 0) + { + printf("%d is even\n", i); + } + else + { + printf("%d is odd\n", i); + } + } + + // Loop with nested conditions + for (i = 1; i <= 10; i++) + { + printf("Number: %d - ", i); + if (i < 5) + { + printf("Less than 5\n"); + } + else if (i > 5) + { + printf("Greater than 5\n"); + } + else + { + printf("Equal to 5\n"); + } + } + + // Loop with a break statement + for (i = 1; i <= 10; i++) + { + printf("%d ", i); + if (i == 5) + { + break; + } + } + printf("\n"); + + // Loop with multiple variables + int a, b, c; + for (a = 1, b = 2, c = 3; a <= 10; a++, b += 2, c += 3) + { + printf("%d %d %d\n", a, b, c); + } + return 0; +} + +int main() +{ + loops0(); + loops1(); + + return 0; +} diff --git a/tests/regression/78-termination/10-complex-loop-terminating.c b/tests/regression/78-termination/10-complex-loop-terminating.c new file mode 100644 index 0000000000..96253c445f --- /dev/null +++ b/tests/regression/78-termination/10-complex-loop-terminating.c @@ -0,0 +1,218 @@ +// SKIP TODO TERM PARAM: --set "ana.activated[+]" termination --set ana.activated[+] apron --enable ana.int.interval --set ana.apron.domain polyhedra --set sem.int.signed_overflow assume_none +// Apron is not precise enough for some nested loops +#include + +int loops0(){ + int i = 1; + int j = 1; + int k = 5; + + // Outer while loop + while (i <= 5) + { + // Inner while loop 1 + while (j <= i) + { + printf("%d ", j); + j++; + } + printf("\n"); + j = 1; + + // Inner while loop 2 + while (k >= 1) + { + printf("%d ", k); + k--; + } + printf("\n"); + k = 5; + + i++; + } + + // Additional while loop + i = 5; + while (i >= 1) + { + j = i; + while (j >= 1) + { + printf("%d ", j); + j--; + } + printf("\n"); + i--; + } + + // Loop with conditions + i = 1; + while (i <= 10) + { + if (i % 2 == 0) + { + printf("%d is even\n", i); + } + else + { + printf("%d is odd\n", i); + } + i++; + } + + // Loop with nested conditions + i = 1; + while (i <= 10) + { + printf("Number: %d - ", i); + if (i < 5) + { + printf("Less than 5\n"); + } + else if (i > 5) + { + printf("Greater than 5\n"); + } + else + { + printf("Equal to 5\n"); + } + i++; + } + return 0; +} + +int loops1() +{ + int i = 1; + int j = 1; + int k = 5; + + // Outer while loop + while (i <= 5) + { + // Inner while loop 1 + while (j <= i) + { + printf("%d ", j); + j++; + } + printf("\n"); + j = 1; + + // Inner while loop 2 + while (k >= 1) + { + printf("%d ", k); + k--; + } + printf("\n"); + k = 5; + + i++; + } + + // Additional while loop + i = 5; + while (i >= 1) + { + j = i; + while (j >= 1) + { + printf("%d ", j); + j--; + } + printf("\n"); + i--; + } + + // Loop with conditions + i = 1; + while (i <= 10) + { + if (i % 2 == 0) + { + printf("%d is even\n", i); + } + else + { + printf("%d is odd\n", i); + } + i++; + } + + return 0; +} + +int loops2(){ + int i = 1; + int j = 1; + int k = 5; + + // Loop with nested conditions + i = 1; + while (i <= 10) + { + printf("Number: %d - ", i); + if (i < 5) + { + printf("Less than 5\n"); + } + else if (i > 5) + { + printf("Greater than 5\n"); + } + else + { + printf("Equal to 5\n"); + } + i++; + } + + // Loop with a break statement + i = 1; + while (i <= 10) + { + printf("%d ", i); + if (i == 5) + { + break; + } + i++; + } + printf("\n"); + + // Loop with a continue statement + i = 1; + while (i <= 10) + { + if (i % 2 == 0) + { + i++; + continue; + } + printf("%d ", i); + i++; + } + printf("\n"); + + // Loop with multiple variables + int a = 1; + int b = 2; + int c = 3; + while (a <= 10) + { + printf("%d %d %d\n", a, b, c); + a++; + b += 2; + c += 3; + } + return 0; +} + +int main(){ + loops0(); + loops1(); + loops2(); + return 0; +} \ No newline at end of file diff --git a/tests/regression/78-termination/11-loopless-termination.c b/tests/regression/78-termination/11-loopless-termination.c new file mode 100644 index 0000000000..9f1a7e0f13 --- /dev/null +++ b/tests/regression/78-termination/11-loopless-termination.c @@ -0,0 +1,8 @@ +// SKIP TERM PARAM: --set "ana.activated[+]" termination --set ana.activated[+] apron --enable ana.int.interval --set ana.apron.domain polyhedra +#include + +int main() +{ + printf("Terminating code without a loop\n"); + return 0; +} diff --git a/tests/regression/78-termination/12-do-while-instant-terminating.c b/tests/regression/78-termination/12-do-while-instant-terminating.c new file mode 100644 index 0000000000..5bc18902b3 --- /dev/null +++ b/tests/regression/78-termination/12-do-while-instant-terminating.c @@ -0,0 +1,15 @@ +// SKIP TERM PARAM: --set "ana.activated[+]" termination --set ana.activated[+] apron --enable ana.int.interval --set ana.apron.domain polyhedra +#include + +int main() +{ + int i = 0; + + do + { + printf("Inside the do-while loop\n"); + } while (i > 0); + + printf("Exited the loop\n"); + return 0; +} diff --git a/tests/regression/78-termination/13-do-while-terminating.c b/tests/regression/78-termination/13-do-while-terminating.c new file mode 100644 index 0000000000..6ac6946495 --- /dev/null +++ b/tests/regression/78-termination/13-do-while-terminating.c @@ -0,0 +1,16 @@ +// SKIP TERM PARAM: --set "ana.activated[+]" termination --set ana.activated[+] apron --enable ana.int.interval --set ana.apron.domain polyhedra +#include + +int main() +{ + int i = 1; + + do + { + printf("Inside the do-while loop\n"); + i++; + } while (i <= 5); + + printf("Exited the loop\n"); + return 0; +} diff --git a/tests/regression/78-termination/14-do-while-nonterminating.c b/tests/regression/78-termination/14-do-while-nonterminating.c new file mode 100644 index 0000000000..0a9df3421f --- /dev/null +++ b/tests/regression/78-termination/14-do-while-nonterminating.c @@ -0,0 +1,16 @@ +// SKIP NONTERM PARAM: --set "ana.activated[+]" termination --set ana.activated[+] apron --enable ana.int.interval --set ana.apron.domain polyhedra +#include + +int main() +{ + int i = 1; + + do + { + printf("Inside the do-while loop\n"); + i++; + } while (i >= 2); // NONTERMLOOP termination analysis shall mark while as non-terminating loop + + printf("Exited the loop\n"); + return 0; +} diff --git a/tests/regression/78-termination/15-complex-loop-combination-terminating.c b/tests/regression/78-termination/15-complex-loop-combination-terminating.c new file mode 100644 index 0000000000..4912bbb1f2 --- /dev/null +++ b/tests/regression/78-termination/15-complex-loop-combination-terminating.c @@ -0,0 +1,126 @@ +// SKIP TODO TERM PARAM: --set "ana.activated[+]" termination --set ana.activated[+] apron --enable ana.int.interval --set ana.apron.domain polyhedra --set sem.int.signed_overflow assume_none +// Apron is not precise enough for some nested loops +#include + +int non_nested_loops(){ + // Non-nested loops + int i; + + // for loop + for (i = 1; i <= 10; i++) + { + printf("For loop iteration: %d\n", i); + } + + // while loop + int j = 1; + while (j <= 10) + { + printf("While loop iteration: %d\n", j); + j++; + } + + // do-while loop + int k = 1; + do + { + printf("Do-While loop iteration: %d\n", k); + k++; + } while (k <= 10); + return 0; +} + +int nested_loops(){ + // Nested loops + int a, b; + + // Nested for and while loop + for (a = 1; a <= 5; a++) + { + int c = 1; + while (c <= a) + { + printf("Nested For-While loop: %d\n", c); + c++; + } + } + + // Nested while and do-while loop + int x = 1; + while (x <= 5) + { + int y = 1; + do + { + printf("Nested While-Do-While loop: %d\n", y); + y++; + } while (y <= x); + x++; + } + + // Nested do-while and for loop + int p = 1; + do + { + for (int q = 1; q <= p; q++) + { + printf("Nested Do-While-For loop: %d\n", q); + } + p++; + } while (p <= 5); + return 0; +} + +int nested_while_loop_with_break(){ + int m; + + // Nested while loop with a break statement + int n = 1; + while (n <= 5) + { + printf("Outer While loop iteration: %d\n", n); + m = 1; + while (1) + { + printf("Inner While loop iteration: %d\n", m); + m++; + if (m == 4) + { + break; + } + } + n++; + } + return 0; +} + +int nested_loop_with_conditions(){ + // Loop with nested conditions + for (int v = 1; v <= 10; v++) + { + printf("Loop with Nested Conditions: %d - ", v); + if (v < 5) + { + printf("Less than 5\n"); + } + else if (v > 5) + { + printf("Greater than 5\n"); + } + else + { + printf("Equal to 5\n"); + } + } +} + +int main() +{ + non_nested_loops(); + nested_loops(); + // Additional nested loops + nested_while_loop_with_break(); + nested_loop_with_conditions(); + + return 0; +} diff --git a/tests/regression/78-termination/16-nested-loop-nontrivial-nonterminating.c b/tests/regression/78-termination/16-nested-loop-nontrivial-nonterminating.c new file mode 100644 index 0000000000..267a2d2fd8 --- /dev/null +++ b/tests/regression/78-termination/16-nested-loop-nontrivial-nonterminating.c @@ -0,0 +1,23 @@ +// SKIP NONTERM PARAM: --set "ana.activated[+]" termination --set ana.activated[+] apron --enable ana.int.interval --set ana.apron.domain polyhedra +#include + +int main() +{ + int outerCount = 1; + + while (outerCount <= 3) + { + int innerCount = 1; + + while (outerCount < 3 || innerCount > 0) // NONTERMLOOP termination analysis shall mark beginning of while as non-terminating loop + { + printf("(%d, %d) ", outerCount, innerCount); + innerCount++; + } + + printf("\n"); + outerCount++; + } + + return 0; +} diff --git a/tests/regression/78-termination/17-goto-terminating.c b/tests/regression/78-termination/17-goto-terminating.c new file mode 100644 index 0000000000..2f678d294b --- /dev/null +++ b/tests/regression/78-termination/17-goto-terminating.c @@ -0,0 +1,21 @@ +// SKIP NONTERM PARAM: --set "ana.activated[+]" termination --set ana.activated[+] apron --enable ana.int.interval --set ana.apron.domain polyhedra +// The program terminates but the analysis is currently only meant to detect up-jumping gotos as potentially NonTerminating, therefore we expect an NonTerm +#include + +int main() +{ + int num = 1; + +loop: + printf("Current number: %d\n", num); + num++; + + if (num <= 10) + { + goto loop; // NONTERMGOTO termination analysis shall mark goto statement up-jumping goto + // We are not able to detect up-jumping gotos as terminating, we + // just warn about them might being nonterminating. + } + + return 0; +} diff --git a/tests/regression/78-termination/18-goto-nonterminating.c b/tests/regression/78-termination/18-goto-nonterminating.c new file mode 100644 index 0000000000..6de80effd7 --- /dev/null +++ b/tests/regression/78-termination/18-goto-nonterminating.c @@ -0,0 +1,15 @@ +// SKIP NONTERM PARAM: --set "ana.activated[+]" termination --set ana.activated[+] apron --enable ana.int.interval --set ana.apron.domain polyhedra +#include + +int main() +{ + int num = 1; + +loop: + printf("Current number: %d\n", num); + num++; + + goto loop; // NONTERMGOTO termination analysis shall mark goto statement up-jumping goto + + return 0; +} diff --git a/tests/regression/78-termination/19-rand-terminating.c b/tests/regression/78-termination/19-rand-terminating.c new file mode 100644 index 0000000000..a5b6c22941 --- /dev/null +++ b/tests/regression/78-termination/19-rand-terminating.c @@ -0,0 +1,31 @@ +// SKIP TERM PARAM: --set "ana.activated[+]" termination --set ana.activated[+] apron --enable ana.int.interval --set ana.apron.domain polyhedra +#include +#include +#include + +int main() +{ + // Seed the random number generator + srand(time(NULL)); + + if (rand()) + { + // Loop inside the if part + for (int i = 1; i <= 5; i++) + { + printf("Loop inside if part: %d\n", i); + } + } + else + { + // Loop inside the else part + int j = 1; + while (j <= 5) + { + printf("Loop inside else part: %d\n", j); + j++; + } + } + + return 0; +} diff --git a/tests/regression/78-termination/20-rand-nonterminating.c b/tests/regression/78-termination/20-rand-nonterminating.c new file mode 100644 index 0000000000..21b25ed9dd --- /dev/null +++ b/tests/regression/78-termination/20-rand-nonterminating.c @@ -0,0 +1,30 @@ +// SKIP NONTERM PARAM: --set "ana.activated[+]" termination --set ana.activated[+] apron --enable ana.int.interval --set ana.apron.domain polyhedra +#include +#include +#include + +int main() +{ + // Seed the random number generator + srand(time(NULL)); + + if (rand()) + { + // Loop inside the if part + for (int i = 1; i >= 0; i++) // NONTERMLOOP termination analysis shall mark beginning of while as non-terminating loop + { + printf("Loop inside if part: %d\n", i); + } + } + else + { + // Loop inside the else part + int j = 1; + while (j > 0) // NONTERMLOOP termination analysis shall mark beginning of while as non-terminating loop + { + printf("Loop inside else part: %d\n", j); + } + } + + return 0; +} diff --git a/tests/regression/78-termination/21-no-exit-on-rand-unproofable.c b/tests/regression/78-termination/21-no-exit-on-rand-unproofable.c new file mode 100644 index 0000000000..5f82d91079 --- /dev/null +++ b/tests/regression/78-termination/21-no-exit-on-rand-unproofable.c @@ -0,0 +1,20 @@ +// SKIP NONTERM PARAM: --set "ana.activated[+]" termination --set ana.activated[+] apron --enable ana.int.interval --set ana.apron.domain polyhedra +#include + +int main() +{ + int forever, i = 0; + + // This loop is not provable, therefore it should throw a warning + while (i < 4 || forever == 1) // NONTERMLOOP termination analysis shall mark beginning of while as non-terminating loop + { + i++; + if (i == 4) + { + if (rand()) + { + forever = 1; + } + } + } +} \ No newline at end of file diff --git a/tests/regression/78-termination/22-exit-on-rand-unproofable.c b/tests/regression/78-termination/22-exit-on-rand-unproofable.c new file mode 100644 index 0000000000..33838ca83d --- /dev/null +++ b/tests/regression/78-termination/22-exit-on-rand-unproofable.c @@ -0,0 +1,16 @@ +// SKIP NONTERM PARAM: --set "ana.activated[+]" termination --set ana.activated[+] apron --enable ana.int.interval --set ana.apron.domain polyhedra +#include + +int main() +{ + int forever = 1; + + // This loop is not provable, therefore it should throw a warning + while (forever == 1) // NONTERMLOOP termination analysis shall mark beginning of while as non-terminating loop + { + if (rand()) // May exit, may not + { + forever = 0; + } + } +} \ No newline at end of file diff --git a/tests/regression/78-termination/23-exit-on-rand-terminating.c b/tests/regression/78-termination/23-exit-on-rand-terminating.c new file mode 100644 index 0000000000..e65c064c40 --- /dev/null +++ b/tests/regression/78-termination/23-exit-on-rand-terminating.c @@ -0,0 +1,17 @@ +// SKIP TODO TERM PARAM: --set "ana.activated[+]" termination --set ana.activated[+] apron --enable ana.int.interval --set ana.apron.domain polyhedra +#include +#include + +int main() +{ + int short_run, i = 0; + // Currently not able to detect this as terminating due to multiple conditions + while (i < 90 && short_run != 1) + { + i++; + if (rand()) + { + short_run = 1; + } + } +} \ No newline at end of file diff --git a/tests/regression/78-termination/24-upjumping-goto-loopless-terminating.c b/tests/regression/78-termination/24-upjumping-goto-loopless-terminating.c new file mode 100644 index 0000000000..ce257d11ef --- /dev/null +++ b/tests/regression/78-termination/24-upjumping-goto-loopless-terminating.c @@ -0,0 +1,21 @@ +// SKIP NONTERM PARAM: --set "ana.activated[+]" termination --set ana.activated[+] apron --enable ana.int.interval --set ana.apron.domain polyhedra +// The program terminates but the analysis is currently only meant to detect up-jumping gotos as potentially NonTerminating, therefore we expect an NonTerm +#include + +int main() +{ // Currently not able to detect up-jumping loop free gotos + goto mark2; + +mark1: + printf("This is mark1\n"); + goto mark3; + +mark2: + printf("This is mark2\n"); + goto mark1; // NONTERMGOTO termination analysis shall mark goto statement up-jumping goto + +mark3: + printf("This is mark3\n"); + + return 0; +} diff --git a/tests/regression/78-termination/25-leave-loop-goto-terminating.c b/tests/regression/78-termination/25-leave-loop-goto-terminating.c new file mode 100644 index 0000000000..b882759bff --- /dev/null +++ b/tests/regression/78-termination/25-leave-loop-goto-terminating.c @@ -0,0 +1,28 @@ +// SKIP TODO TERM PARAM: --set "ana.activated[+]" termination --set ana.activated[+] apron --enable ana.int.interval --set ana.apron.domain polyhedra +#include + +int main() +{ + int counter = 0; + + while (1) + { + counter++; + + // Dummy code + printf("Iteration %d\n", counter); + int result = counter * 2; + printf("Result: %d\n", result); + + // Condition to terminate the loop + if (result >= 10) + { // Apron is not able to detect this + goto end; + } + } + +end: + printf("Loop exited. Result is greater than or equal to 10.\n"); + + return 0; +} diff --git a/tests/regression/78-termination/26-enter-loop-goto-terminating.c b/tests/regression/78-termination/26-enter-loop-goto-terminating.c new file mode 100644 index 0000000000..aa85f22b3e --- /dev/null +++ b/tests/regression/78-termination/26-enter-loop-goto-terminating.c @@ -0,0 +1,31 @@ +// SKIP TODO TERM PARAM: --set "ana.activated[+]" termination --set ana.activated[+] apron --enable ana.int.interval --set ana.apron.domain polyhedra +#include + +int main() +{ + int counter = 0; + + goto jump_point; + + while (1) + { + counter++; + + // Dummy code + printf("Iteration %d\n", counter); + int result = counter * 2; + jump_point: + printf("Result: %d\n", result); + + // Condition to terminate the loop + if (result >= 10) + { // Apron is not able to detect this + goto end; + } + } + +end: + printf("Loop exited. Result is greater than or equal to 10.\n"); + + return 0; +} diff --git a/tests/regression/78-termination/27-upjumping-goto-nonterminating.c b/tests/regression/78-termination/27-upjumping-goto-nonterminating.c new file mode 100644 index 0000000000..e0eb633b11 --- /dev/null +++ b/tests/regression/78-termination/27-upjumping-goto-nonterminating.c @@ -0,0 +1,21 @@ +// SKIP NONTERM PARAM: --set "ana.activated[+]" termination --set ana.activated[+] apron --enable ana.int.interval --set ana.apron.domain polyhedra +#include + +int main() +{ + goto mark2; + +mark1: + printf("This is mark1\n"); + goto mark3; + +mark2: + printf("This is mark2\n"); + goto mark1; // NONTERMGOTO termination analysis shall mark goto statement up-jumping goto + +mark3: + printf("This is mark3\n"); + goto mark1; // NONTERMGOTO termination analysis shall mark goto statement up-jumping goto + + return 0; +} diff --git a/tests/regression/78-termination/28-do-while-continue-terminating.c b/tests/regression/78-termination/28-do-while-continue-terminating.c new file mode 100644 index 0000000000..a61174d295 --- /dev/null +++ b/tests/regression/78-termination/28-do-while-continue-terminating.c @@ -0,0 +1,99 @@ +// SKIP TODO TERM PARAM: --set "ana.activated[+]" termination --set ana.activated[+] apron --enable ana.int.interval --set ana.apron.domain polyhedra +#include + +int main() +{ + int i = 1; + + do + { + i++; + printf("Inside the do-while loop\n"); + if (i % 2 == 0) + { + + printf("Skipping %i is even\n", i); + continue; // This is handled as an goto to line 8 and therefore an up-jumping goto + } + } while (i <= 5); + + printf("Exited the loop\n"); + return 0; +} + +/* +NOTE: +Test 28: does not terminate but should terminate (test case +"28-do-while-continue-terminating.c") Reason: upjumping goto + +If one has a look at the generated CIL output (attached at the bottom of this +file), one can see that the "continue" is translated in a "goto" with a +corresponding label "__Cont". This label points to the loop-exit condition. +Since the condition is part of the loop, its location is evaluated to 8-17. The +location of the goto "goto __Cont" is located in line 15. To provide soundness +for the analysis, the preprocessing detects upjumping gotos with the help of its +location. In case such a goto is detected, the program is classified as +non-terminating. Due to this inserted goto (which is a result of the +"continue"), an upjumping goto is located, which makes this program +non-terminating. + +It should be noted that this issue happens when "do while"-loops and "continues" +are combined. If one combines "while"-loops and "continues", the analysis can +still classify the loop as terminating. The reason for that can be seen in the +second CIL output, where the "do while"-loop is replaced by a "while"-loop. +Instead of creating a new label, the "while-continue" label of the loop is +reused. Also, this goto statement is not specified as a goto, but as a Continue +statement. Hence, it is not analyzed for the upjumping gotos, which does not +lead to the problem as with the "do while". + + +------- SHORTENED CIL output for Test 28 (DO WHILE): ------- +int main(void) +{{{{ + #line 8 + while (1) { + while_continue: ; + #line 12 + if (i % 2 == 0) { + #line 15 + goto __Cont; + } + __Cont: + #line 8 + if (! (i <= 5)) { + #line 8 + goto while_break; + } + } + + while_break: + }} + #line 20 + return (0); +}} + + +------- SHORTENED CIL output for Test 28 (WHILE): ------- +Test 28: replacing DO WHILE with WHILE: int main(void) +{{{{ + #line 8 + while (1) { + while_continue: ; + #line 8 + if (! (i <= 5)) { + #line 8 + goto while_break; + } + #line 12 + if (i % 2 == 0) { + #line 15 + goto while_continue; + } + } + while_break: ; + }} + #line 20 + return (0); +}} + +*/ diff --git a/tests/regression/78-termination/29-do-while-continue-nonterminating.c b/tests/regression/78-termination/29-do-while-continue-nonterminating.c new file mode 100644 index 0000000000..dd931c012f --- /dev/null +++ b/tests/regression/78-termination/29-do-while-continue-nonterminating.c @@ -0,0 +1,22 @@ +// SKIP NONTERM PARAM: --set "ana.activated[+]" termination --set ana.activated[+] apron --enable ana.int.interval --set ana.apron.domain polyhedra +#include + +int main() +{ + int i = 1; + + do + { + printf("Inside the do-while loop\n"); + i++; + + if (i % 2) + { + printf("Continue as %i is odd\n", i); + continue; + } + } while (i >= 2); // NONTERMLOOP termination analysis shall mark beginning of while as non-terminating loop + + printf("Exited the loop\n"); + return 0; +} diff --git a/tests/regression/78-termination/30-goto-out-of-inner-loop-terminating.c b/tests/regression/78-termination/30-goto-out-of-inner-loop-terminating.c new file mode 100644 index 0000000000..c07b558d07 --- /dev/null +++ b/tests/regression/78-termination/30-goto-out-of-inner-loop-terminating.c @@ -0,0 +1,36 @@ +// SKIP TODO TERM PARAM: --set "ana.activated[+]" termination --set ana.activated[+] apron --enable ana.int.interval --set ana.apron.domain polyhedra +#include + +int main() +{ + int rows = 5; + int columns = 5; + + // Outer loop for rows + for (int i = 1; i <= rows; i++) + { + // Inner loop for columns + for (int j = 1; j <= columns; j++) + { + if (j == 3) + { + goto outer_loop; // Jump to the label "outer_loop" + } + printf("(%d, %d) ", i, j); + } + printf("Not Skipped?\n"); + outer_loop:; // Label for the outer loop + printf("Skipped!\n"); + } + + return 0; +} + +/* +NOTE: In case we do NOT assume no-overflow: +Test 30: terminates (test case "30-goto-out-of-inner-loop-terminating.c") +Test 35: does not terminate (test case +"35-goto-out-of-inner-loop-with-print-terminating.c") + +The reason is explained in "35-goto-out-of-inner-loop-with-print-terminating.c" +*/ diff --git a/tests/regression/78-termination/31-goto-out-of-inner-loop-nonterminating.c b/tests/regression/78-termination/31-goto-out-of-inner-loop-nonterminating.c new file mode 100644 index 0000000000..f9b9275620 --- /dev/null +++ b/tests/regression/78-termination/31-goto-out-of-inner-loop-nonterminating.c @@ -0,0 +1,27 @@ +// SKIP NONTERM PARAM: --set "ana.activated[+]" termination --set ana.activated[+] apron --enable ana.int.interval --set ana.apron.domain polyhedra +#include + +int main() +{ + int rows = 5; + int columns = 5; + + // Outer loop for rows + for (int i = 1; 1; i++) // NONTERMLOOP termination analysis shall mark beginning of for as non-terminating loop + { + // Inner loop for columns + for (int j = 1; j <= columns; j++) + { + if (j == 3) + { + printf("Goto as continue for outer loop\n"); + goto outer_loop; + } + printf("(%d, %d) ", i, j); + } + printf("\n"); + outer_loop:; // Label for the outer loop + } + + return 0; +} diff --git a/tests/regression/78-termination/32-multithread-terminating.c b/tests/regression/78-termination/32-multithread-terminating.c new file mode 100644 index 0000000000..eb8b796a47 --- /dev/null +++ b/tests/regression/78-termination/32-multithread-terminating.c @@ -0,0 +1,30 @@ +// SKIP NONTERM PARAM: --set "ana.activated[+]" termination --set ana.activated[+] apron --enable ana.int.interval --set ana.apron.domain polyhedra +// The program terminates but as the termination analysis is meant to not handle multithreaded programs we expect NonTerm here +#include +#include +#include + +// Thread function +void *printPID(void *arg) +{ + pid_t pid = getpid(); + pthread_t tid = pthread_self(); + printf("Thread ID: %lu, Process ID: %d\n", (unsigned long)tid, pid); + return NULL; +} + +int main() +{ + // Create three threads + pthread_t thread1, thread2, thread3; + pthread_create(&thread1, NULL, printPID, NULL); + pthread_create(&thread2, NULL, printPID, NULL); + pthread_create(&thread3, NULL, printPID, NULL); + + // Wait for all threads to finish + pthread_join(thread1, NULL); + pthread_join(thread2, NULL); + pthread_join(thread3, NULL); + + return 0; +} diff --git a/tests/regression/78-termination/33-multithread-nonterminating.c b/tests/regression/78-termination/33-multithread-nonterminating.c new file mode 100644 index 0000000000..8a6274c7ab --- /dev/null +++ b/tests/regression/78-termination/33-multithread-nonterminating.c @@ -0,0 +1,40 @@ +// SKIP NONTERM PARAM: --set "ana.activated[+]" termination --set ana.activated[+] apron --enable ana.int.interval --set ana.apron.domain polyhedra +#include +#include +#include +#include +#include + +// Thread function +void *printPID(void *arg) +{ + pid_t pid = getpid(); + pthread_t tid = pthread_self(); + while (1) + { + printf("Thread ID: %lu, Process ID: %d\n", (unsigned long)tid, pid); + struct timespec sleepTime; + sleepTime.tv_sec = 1; // Seconds + sleepTime.tv_nsec = + 100000000 + (rand() % 200000000); // Nanoseconds (0.1 seconds + rand) + printf("Sleep for %ld nsec\n", sleepTime.tv_nsec); + nanosleep(&sleepTime, NULL); + } + return NULL; +} + +int main() +{ + // Create three threads + pthread_t thread1, thread2, thread3; + pthread_create(&thread1, NULL, printPID, NULL); + pthread_create(&thread2, NULL, printPID, NULL); + pthread_create(&thread3, NULL, printPID, NULL); + + // Wait for all threads to finish + pthread_join(thread1, NULL); + pthread_join(thread2, NULL); + pthread_join(thread3, NULL); + + return 0; +} diff --git a/tests/regression/78-termination/34-nested-for-loop-nonterminating.c b/tests/regression/78-termination/34-nested-for-loop-nonterminating.c new file mode 100644 index 0000000000..2f21f9e996 --- /dev/null +++ b/tests/regression/78-termination/34-nested-for-loop-nonterminating.c @@ -0,0 +1,19 @@ +// SKIP NONTERM PARAM: --set "ana.activated[+]" termination --set ana.activated[+] apron --enable ana.int.interval --set ana.apron.domain polyhedra +#include + +int main() +{ + int outerCount, innerCount; + + for (outerCount = 1; outerCount <= 3; outerCount++) + { + for (innerCount = 1; innerCount > 0; innerCount++) // NONTERMLOOP termination analysis shall mark beginning of for as non-terminating loop + { + printf("(%d, %d) ", outerCount, innerCount); + } + + printf("\n"); + } + + return 0; +} diff --git a/tests/regression/78-termination/35-goto-out-of-inner-loop-with-print-terminating.c b/tests/regression/78-termination/35-goto-out-of-inner-loop-with-print-terminating.c new file mode 100644 index 0000000000..4c738e1173 --- /dev/null +++ b/tests/regression/78-termination/35-goto-out-of-inner-loop-with-print-terminating.c @@ -0,0 +1,42 @@ +// SKIP TODO TERM PARAM: --set "ana.activated[+]" termination --set "ana.activated[+]" apron --enable ana.int.interval --set ana.apron.domain polyhedra --set sem.int.signed_overflow assume_none +#include + +int main() +{ + int rows = 5; + int columns = 5; + + // Outer loop for rows + for (int i = 1; i <= rows; i++) + { + // Inner loop for columns + for (int j = 1; j <= columns; j++) + { + if (j == 3) + { + goto outer_loop; // Jump to the label "outer_loop" + } + printf("(%d, %d) ", i, j); + } + outer_loop: // Label for the outer loop + printf("\n"); + } + + return 0; +} + +/* +NOTE: In case we do NOT assume no-overflow: +Test 30: terminates (test case "30-goto-out-of-inner-loop-terminating.c") +Test 35: does not terminate (test case +"35-goto-out-of-inner-loop-with-print-terminating.c") + +The only difference between Test 30 and Test 35 is line 17. Test 30 has an +additional statement, and Test 35 continues already with the label. This +difference in Test 35 leads to an overflow in line 11, and hence to the +non-termination. This overflow is created by a WPoint Issue. By enabling the +no-overflow option this issue can be fixed and, both test cases are correctly +detected as terminating. + +(The overflow also happens without the termination analysis enabled.) +*/ diff --git a/tests/regression/78-termination/36-recursion-terminating.c b/tests/regression/78-termination/36-recursion-terminating.c new file mode 100644 index 0000000000..179efabeea --- /dev/null +++ b/tests/regression/78-termination/36-recursion-terminating.c @@ -0,0 +1,25 @@ +// SKIP TERM PARAM: --set "ana.activated[+]" termination --set ana.activated[+] apron --enable ana.int.interval --set ana.apron.domain polyhedra +#include + +void recursiveFunction(int n) +{ + // Base case: When n reaches 0, stop recursion + if (n == 0) + { + printf("Terminating recursion\n"); + return; + } + + printf("Recursive call with n = %d\n", n); + + // Recursive call: Decrement n and call the function again + recursiveFunction(n - 1); +} + +int main() +{ + // Call the recursive function with an initial value + recursiveFunction(5); + + return 0; +} diff --git a/tests/regression/78-termination/37-recursion-nonterminating.c b/tests/regression/78-termination/37-recursion-nonterminating.c new file mode 100644 index 0000000000..c47fbcdd49 --- /dev/null +++ b/tests/regression/78-termination/37-recursion-nonterminating.c @@ -0,0 +1,25 @@ +// SKIP NONTERM PARAM: --set "ana.activated[+]" termination --set ana.activated[+] apron --enable ana.int.interval --set ana.apron.domain polyhedra --enable ana.context.widen +#include + +void recursiveFunction(int n) // NONTERMFUNDEC termination analysis shall mark fundec of non-terminating function +{ + // Base case: When n reaches 0, stop recursion + if (n == 30) + { + printf("Terminating recursion\n"); + return; + } + + printf("Recursive call with n = %d\n", n); + + // Recursive call: Decrement n and call the function again + recursiveFunction(n - 1); +} + +int main() +{ + // Call the recursive function with an initial value + recursiveFunction(5); + + return 0; +} diff --git a/tests/regression/78-termination/38-recursion-nested-terminating.c b/tests/regression/78-termination/38-recursion-nested-terminating.c new file mode 100644 index 0000000000..a471cfc386 --- /dev/null +++ b/tests/regression/78-termination/38-recursion-nested-terminating.c @@ -0,0 +1,41 @@ +// SKIP TERM PARAM: --set "ana.activated[+]" termination --set ana.activated[+] apron --enable ana.int.interval --set ana.apron.domain polyhedra +#include + +void innerRecursiveFunction(int n) +{ + if (n == 0) + { + printf("Terminating inner recursion\n"); + return; + } + + printf("Inner recursive call with n = %d\n", n); + + // Recursive call to the innerRecursiveFunction + innerRecursiveFunction(n - 1); +} + +void outerRecursiveFunction(int n) +{ + if (n == 0) + { + printf("Terminating outer recursion\n"); + return; + } + + printf("Outer recursive call with n = %d\n", n); + + // Recursive call to the outerRecursiveFunction + outerRecursiveFunction(n - 1); + + // Call to the innerRecursiveFunction + innerRecursiveFunction(n); +} + +int main() +{ + // Call the outerRecursiveFunction with an initial value + outerRecursiveFunction(3); + + return 0; +} diff --git a/tests/regression/78-termination/39-recursion-nested-nonterminating.c b/tests/regression/78-termination/39-recursion-nested-nonterminating.c new file mode 100644 index 0000000000..a8d7107442 --- /dev/null +++ b/tests/regression/78-termination/39-recursion-nested-nonterminating.c @@ -0,0 +1,29 @@ +// SKIP NONTERM PARAM: --set "ana.activated[+]" termination --set ana.activated[+] apron --enable ana.int.interval --set ana.apron.domain polyhedra +#include + +void innerRecursiveFunction() // TODO NONTERMFUNDEC termination analysis shall mark fundec of non-terminating function but can not as dead code is not analysed +{ + printf("Nested recursive call\n"); + + // Recursive call to the innerRecursiveFunction + innerRecursiveFunction(); +} + +void outerRecursiveFunction() // NONTERMFUNDEC termination analysis shall mark fundec of non-terminating function +{ + printf("Outer recursive call\n"); + + // Recursive call to the outerRecursiveFunction + outerRecursiveFunction(); + + // Call to the innerRecursiveFunction + innerRecursiveFunction(); +} + +int main() +{ + // Call the outerRecursiveFunction + outerRecursiveFunction(); + + return 0; +} diff --git a/tests/regression/78-termination/40-multi-expression-conditions-terminating.c b/tests/regression/78-termination/40-multi-expression-conditions-terminating.c new file mode 100644 index 0000000000..80f8c5a1e8 --- /dev/null +++ b/tests/regression/78-termination/40-multi-expression-conditions-terminating.c @@ -0,0 +1,44 @@ +// SKIP TODO TERM PARAM: --set "ana.activated[+]" termination --set ana.activated[+] apron --enable ana.int.interval --set ana.apron.domain polyhedra +#include + +int main() +{ + int i; + + // Loop with complex conditions + for (i = 1; i <= 10; i++) + { + if (i > 5 && i % 2 == 0) // CIL defines new jump labels to default location (-1) + { + printf("%d ", i); + } + } + printf("\n"); + + // Loop with complex conditions + i = 1; + while (i <= 10) + { + if (i > 5 && i % 2 == 0) // CIL defines new jump labels to default location (-1) + { + printf("%d ", i); + } + i++; + } + printf("\n"); + + // Loop with multiple conditions + int s = 1; + while (s <= 10 && s % 2 == 0) // CIL defines new jump labels to default location (-1) + { + printf("Loop with Multiple Conditions: %d\n", s); + s++; + } + + // Loop with multiple variables + int t, u; + for (t = 1, u = 10; t <= 5 && u >= 5; t++, u--) // CIL defines new jump labels to default location (-1) + { + printf("Loop with Multiple Variables: %d %d\n", t, u); + } +} \ No newline at end of file diff --git a/tests/regression/78-termination/41-for-continue-terminating.c b/tests/regression/78-termination/41-for-continue-terminating.c new file mode 100644 index 0000000000..d87a705868 --- /dev/null +++ b/tests/regression/78-termination/41-for-continue-terminating.c @@ -0,0 +1,27 @@ +// SKIP TODO TERM PARAM: --set "ana.activated[+]" termination --set ana.activated[+] apron --enable ana.int.interval --set ana.apron.domain polyhedra +#include + +int main() +{ + // Loop with a continue statement + for (int i = 1; i <= 10; i++) + { + if (i % 2 == 0) + { + continue; // Converted to an goto to "for" in line 7 + } + printf("%d ", i); + } + printf("\n"); + + + // Loop with a continue statement + for (int r = 1; r <= 10; r++) + { + if (r % 3 == 0) + { + continue; // Converted to an goto to "for" in line 19 + } + printf("Loop with Continue: %d\n", r); + } +} \ No newline at end of file diff --git a/tests/regression/78-termination/42-downjumping-goto-loopless-terminating.c b/tests/regression/78-termination/42-downjumping-goto-loopless-terminating.c new file mode 100644 index 0000000000..48864883f7 --- /dev/null +++ b/tests/regression/78-termination/42-downjumping-goto-loopless-terminating.c @@ -0,0 +1,19 @@ +// SKIP TERM PARAM: --set "ana.activated[+]" termination --set ana.activated[+] apron --enable ana.int.interval --set ana.apron.domain polyhedra +#include + +int main() { // Currently not able to detect up-jumping loop free gotos + goto mark2; + +mark1: + printf("This is mark1\n"); + goto mark3; + +mark2: + printf("This is mark2\n"); + goto mark3; + +mark3: + printf("This is mark3\n"); + + return 0; +} diff --git a/tests/regression/78-termination/43-return-from-endless-loop-terminating.c b/tests/regression/78-termination/43-return-from-endless-loop-terminating.c new file mode 100644 index 0000000000..fb48e1cdbe --- /dev/null +++ b/tests/regression/78-termination/43-return-from-endless-loop-terminating.c @@ -0,0 +1,14 @@ +// SKIP TERM PARAM: --set "ana.activated[+]" termination --set ana.activated[+] apron --enable ana.int.interval --set ana.apron.domain polyhedra +#include + +int main() { + int i = 1; + + while (i != 0) { + printf("%d\n", i); + i++; + if (i>10) { + return 0; + } + } +} diff --git a/tests/regression/78-termination/44-recursion-multiple-functions-terminating.c b/tests/regression/78-termination/44-recursion-multiple-functions-terminating.c new file mode 100644 index 0000000000..7f9b63527e --- /dev/null +++ b/tests/regression/78-termination/44-recursion-multiple-functions-terminating.c @@ -0,0 +1,40 @@ +// SKIP TERM PARAM: --set "ana.activated[+]" termination --set ana.activated[+] apron --enable ana.int.interval --set ana.apron.domain polyhedra +#include + +void functionB(int n); +void functionC(int n); +void functionD(int n); + +void functionA(int n) { + if (n > 0) { + printf("Function A: %d\n", n); + functionB(n - 1); + } +} + +void functionB(int n) { + if (n > 0) { + printf("Function B: %d\n", n); + functionC(n - 1); + } +} + +void functionC(int n) { + if (n > 0) { + printf("Function C: %d\n", n); + functionD(n - 1); + } +} + +void functionD(int n) { + if (n > 0) { + printf("Function D: %d\n", n); + functionA(n - 1); + } +} + +int main() { + int n = 15; + functionA(n); + return 0; +} diff --git a/tests/regression/78-termination/45-recursion-multiple-functions-nonterminating.c b/tests/regression/78-termination/45-recursion-multiple-functions-nonterminating.c new file mode 100644 index 0000000000..be47fde704 --- /dev/null +++ b/tests/regression/78-termination/45-recursion-multiple-functions-nonterminating.c @@ -0,0 +1,40 @@ +// SKIP NONTERM PARAM: --set "ana.activated[+]" termination --set ana.activated[+] apron --enable ana.int.interval --set ana.apron.domain polyhedra +#include + +void functionB(int n); +void functionC(int n); +void functionD(int n); + +void functionA(int n) { + if (n > 0) { + printf("Function A: %d\n", n); + functionB(n - 1); + } +} + +void functionB(int n) { + if (n > 0) { + printf("Function B: %d\n", n); + functionC(n - 1); + } +} + +void functionC(int n) { + if (n > 0) { + printf("Function C: %d\n", n); + functionD(n + 1); + } +} + +void functionD(int n) { + if (n > 0) { + printf("Function D: %d\n", n); + functionA(n + 1); + } +} + +int main() { + int n = 15; + functionA(n); + return 0; +} diff --git a/tests/regression/78-termination/46-recursion-different-context-terminating.c b/tests/regression/78-termination/46-recursion-different-context-terminating.c new file mode 100644 index 0000000000..2fa42f58fc --- /dev/null +++ b/tests/regression/78-termination/46-recursion-different-context-terminating.c @@ -0,0 +1,32 @@ +// SKIP TERM PARAM: --set "ana.activated[+]" termination --set ana.activated[+] apron --enable ana.int.interval --set ana.apron.domain polyhedra +#include + +void functionC(int n); + +void functionA(int n) { + if (n > 0) { + printf("Function A: %d\n", n); + functionC(n - 1); + } +} + +void functionB(int n) { + if (n > 0) { + printf("Function B: %d\n", n); + functionC(n - 1); + } +} + +void functionC(int n) { + if (n > 0) { + printf("Function C: %d\n", n); + functionC(n - 1); + } +} + +int main() { + int n = 5; + functionA(n + 1); + functionB(n + 7); + return 0; +} diff --git a/tests/regression/78-termination/47-recursion-different-context-nonterminating.c b/tests/regression/78-termination/47-recursion-different-context-nonterminating.c new file mode 100644 index 0000000000..b0e44bce92 --- /dev/null +++ b/tests/regression/78-termination/47-recursion-different-context-nonterminating.c @@ -0,0 +1,32 @@ +// SKIP NONTERM PARAM: --set "ana.activated[+]" termination --set ana.activated[+] apron --enable ana.int.interval --set ana.apron.domain polyhedra +#include + +void functionC(int n); + +void functionA(int n) { + if (n > 0) { + printf("Function A: %d\n", n); + functionC(n - 1); + } +} + +void functionB(int n) { + if (n > 0) { + printf("Function B: %d\n", n); + functionC(n - 1); + } +} + +void functionC(int n) { + if (n > 0) { + printf("Function C: %d\n", n); + functionC(n); + } +} + +int main() { + int n = 5; + functionA(n + 1); + functionB(n + 7); + return 0; +} diff --git a/tests/regression/78-termination/48-dynamic-recursion-nonterminating.c b/tests/regression/78-termination/48-dynamic-recursion-nonterminating.c new file mode 100644 index 0000000000..d54c49fb43 --- /dev/null +++ b/tests/regression/78-termination/48-dynamic-recursion-nonterminating.c @@ -0,0 +1,10 @@ +// SKIP NONTERM PARAM: --set "ana.activated[+]" termination --set ana.activated[+] apron --enable ana.int.interval --set ana.apron.domain polyhedra +void troll(void (*f) ()) +{ + f(f); +} + +int main() +{ + troll(troll); +} diff --git a/tests/regression/78-termination/49-longjmp.c b/tests/regression/78-termination/49-longjmp.c new file mode 100644 index 0000000000..be13cb286c --- /dev/null +++ b/tests/regression/78-termination/49-longjmp.c @@ -0,0 +1,11 @@ +// SKIP NONTERM PARAM: --set "ana.activated[+]" termination --set ana.activated[+] apron --enable ana.int.interval --set ana.apron.domain polyhedra +#include +jmp_buf buf; +int main() +{ + if(setjmp(buf)) { + + } + + longjmp(buf, 1); +} diff --git a/tests/regression/78-termination/50-decreasing-signed-int.c b/tests/regression/78-termination/50-decreasing-signed-int.c new file mode 100644 index 0000000000..01daa5ee21 --- /dev/null +++ b/tests/regression/78-termination/50-decreasing-signed-int.c @@ -0,0 +1,13 @@ +// SKIP TERM PARAM: --set "ana.activated[+]" termination --set ana.activated[+] apron --enable ana.int.interval --set ana.apron.domain octagon +int main() +{ + int x; + + if(x <= 0){ + return 0; + } + while (x > 0) { + x = x - 1; + } + return 0; +} diff --git a/tests/regression/78-termination/51-modulo.c b/tests/regression/78-termination/51-modulo.c new file mode 100644 index 0000000000..5f5b8f1924 --- /dev/null +++ b/tests/regression/78-termination/51-modulo.c @@ -0,0 +1,14 @@ +// 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) )" + +// This task previously crashed due to the autotuner +int main() { + int a; + int odd, count = 0; + while(a > 1) { + odd = a % 2; + if(!odd) a = a / 2; + else a = a - 1; + count++; + } + return count; +} diff --git a/tests/sv-comp/observer/path_nofun_true-unreach-call.c b/tests/sv-comp/observer/path_nofun_true-unreach-call.c index 0cb70d23e9..cf1191e9fd 100644 --- a/tests/sv-comp/observer/path_nofun_true-unreach-call.c +++ b/tests/sv-comp/observer/path_nofun_true-unreach-call.c @@ -21,4 +21,4 @@ int main() return 0; } -// ./goblint --enable ana.sv-comp --enable ana.wp --enable witness.uncil --disable ana.int.def_exc --enable ana.int.interval --set ana.activated '["base"]' --html tests/sv-comp/observer/path_nofun_true-unreach-call.c +// ./goblint --enable ana.sv-comp --enable ana.wp --enable witness.graphml.uncil --disable ana.int.def_exc --enable ana.int.interval --set ana.activated '["base"]' --html tests/sv-comp/observer/path_nofun_true-unreach-call.c diff --git a/tests/sv-comp/valid-memcleanup.prp b/tests/sv-comp/valid-memcleanup.prp new file mode 100644 index 0000000000..778c49e5dc --- /dev/null +++ b/tests/sv-comp/valid-memcleanup.prp @@ -0,0 +1,2 @@ +CHECK( init(main()), LTL(G valid-memcleanup) ) + diff --git a/tests/sv-comp/valid-memsafety.prp b/tests/sv-comp/valid-memsafety.prp new file mode 100644 index 0000000000..06a87f5a37 --- /dev/null +++ b/tests/sv-comp/valid-memsafety.prp @@ -0,0 +1,4 @@ +CHECK( init(main()), LTL(G valid-free) ) +CHECK( init(main()), LTL(G valid-deref) ) +CHECK( init(main()), LTL(G valid-memtrack) ) + diff --git a/unittest/dune b/unittest/dune index 7313aa964b..a08a4b2323 100644 --- a/unittest/dune +++ b/unittest/dune @@ -2,7 +2,7 @@ (test (name mainTest) - (libraries ounit2 qcheck-ounit goblint.lib goblint.sites.dune goblint.build-info.dune) + (libraries ounit2 qcheck-ounit goblint.std goblint.lib goblint.sites.dune goblint.build-info.dune) (preprocess (pps ppx_deriving.std ppx_deriving_hash ppx_deriving_yojson)) (flags :standard -linkall)) diff --git a/unittest/mainTest.ml b/unittest/mainTest.ml index df67340309..642e495d50 100644 --- a/unittest/mainTest.ml +++ b/unittest/mainTest.ml @@ -8,6 +8,7 @@ let all_tests = ("" >::: LvalTest.test (); CompilationDatabaseTest.tests; LibraryDslTest.tests; + CilfacadeTest.tests; (* etc *) "domaintest" >::: QCheck_ounit.to_ounit2_test_list Maindomaintest.all_testsuite; IntOpsTest.tests; diff --git a/unittest/util/cilfacadeTest.ml b/unittest/util/cilfacadeTest.ml new file mode 100644 index 0000000000..482a502824 --- /dev/null +++ b/unittest/util/cilfacadeTest.ml @@ -0,0 +1,14 @@ +open Goblint_lib +open OUnit2 +open Cilfacade + +let test_split_anoncomp_name _ = + let assert_equal = assert_equal ~printer:[%show: bool * string option * int] in + assert_equal (false, Some "pthread_mutexattr_t", 488594144) (split_anoncomp_name "__anonunion_pthread_mutexattr_t_488594144"); + assert_equal (true, Some "__once_flag", 1234) (split_anoncomp_name "__anonstruct___once_flag_1234"); + assert_equal (false, None, 50) (split_anoncomp_name "__anonunion_50") + +let tests = + "cilfacadeTest" >::: [ + "split_anoncomp_name" >:: test_split_anoncomp_name; + ] diff --git a/unittest/util/intOpsTest.ml b/unittest/util/intOpsTest.ml index 611f2f546f..307d9e84b0 100644 --- a/unittest/util/intOpsTest.ml +++ b/unittest/util/intOpsTest.ml @@ -1,4 +1,5 @@ open OUnit2 +open Goblint_std open Goblint_lib (* If the first operand of a div is negative, Zarith rounds the result away from zero. @@ -10,13 +11,13 @@ let old_div a b = if Z.lt a Z.zero then Z.neg (Z.ediv (Z.neg a) b) else Z.ediv a let old_rem a b = Z.sub a (Z.mul b (old_div a b)) let test_bigint_div = - QCheck.(Test.make ~name:"div" (pair MyCheck.Arbitrary.big_int MyCheck.Arbitrary.big_int) (fun (x, y) -> + QCheck.(Test.make ~name:"div" (pair GobQCheck.Arbitrary.big_int GobQCheck.Arbitrary.big_int) (fun (x, y) -> assume (Z.compare y Z.zero <> 0); Z.equal (Z.div x y) (old_div x y) )) let test_bigint_rem = - QCheck.(Test.make ~name:"rem" (pair MyCheck.Arbitrary.big_int MyCheck.Arbitrary.big_int) (fun (x, y) -> + QCheck.(Test.make ~name:"rem" (pair GobQCheck.Arbitrary.big_int GobQCheck.Arbitrary.big_int) (fun (x, y) -> assume (Z.compare y Z.zero <> 0); Z.equal (Z.rem x y) (old_rem x y) ))