diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index b77b865719..53dd9114cc 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -28,3 +28,6 @@ ec8611a3a72ae0d95ec82ffee16c5c4785111aa1 # Set up end-of-line normalization. 78fd79e7f4d15c4412221b155971fac2e0616b90 + +# fix indentation in baseInvariant +f3ffd5e45c034574020f56519ccdb021da2a1479 diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 0000000000..5635ebbeea --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,92 @@ +name: coverage + +on: + pull_request: + + workflow_dispatch: + + schedule: + # nightly + - cron: '31 1 * * *' # 01:31 UTC, 02:31/03:31 Munich, 03:31/04:31 Tartu + # GitHub Actions load is high at minute 0, so avoid that + +jobs: + coverage: + strategy: + fail-fast: false + matrix: + os: + - ubuntu-latest + ocaml-compiler: + - ocaml-variants.4.14.0+options,ocaml-option-flambda # matches opam lock file + # don't add any other because they won't be used + + runs-on: ${{ matrix.os }} + + env: + OCAMLRUNPARAM: b + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up OCaml ${{ matrix.ocaml-compiler }} + env: + # otherwise setup-ocaml pins non-locked dependencies + # https://github.com/ocaml/setup-ocaml/issues/166 + OPAMLOCKED: locked + uses: ocaml/setup-ocaml@v2 + with: + ocaml-compiler: ${{ matrix.ocaml-compiler }} + + - name: Install dependencies + run: opam install . --deps-only --locked --with-test + + - name: Install coverage dependencies + run: opam install bisect_ppx + + - name: Build + run: ./make.sh coverage + + - name: Test regression + run: ./make.sh headers testci + + - name: Test apron 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 apron -s + ruby scripts/update_suite.rb group apron2 -s + + - name: Test apron octagon regression # skipped by default but CI has apron, so explicitly test group (which ignores skipping -- it's now a feature!) + run: ruby scripts/update_suite.rb group octagon -s + + - name: Test apron affeq 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 affeq -s + + - name: Test apron regression (Mukherjee et. al SAS '17 paper') # skipped by default but CI has apron, so explicitly test group (which ignores skipping -- it's now a feature!) + run: ruby scripts/update_suite.rb group apron-mukherjee -s + + - name: Test regression cram + run: opam exec -- dune runtest tests/regression + + - name: Test incremental cram + run: opam exec -- dune runtest tests/incremental + + - name: Test unit + run: opam exec -- dune runtest unittest + + - name: Test incremental regression + run: ruby scripts/update_suite.rb -i + + - name: Test incremental regression with cfg comparison + run: ruby scripts/update_suite.rb -c + + - run: opam exec -- bisect-ppx-report send-to Coveralls --coverage-path=. + env: + COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} + PULL_REQUEST_NUMBER: ${{ github.event.number }} + + - uses: actions/upload-artifact@v3 + if: always() + with: + name: suite_result + path: tests/suite_result/ 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 new file mode 100644 index 0000000000..e1648904c3 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,71 @@ +name: docs + +on: + push: + branches: + - master + + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: "pages" + cancel-in-progress: true + +jobs: + api-build: + strategy: + matrix: + os: + - ubuntu-latest + ocaml-compiler: + - ocaml-variants.4.14.0+options,ocaml-option-flambda # matches opam lock file + # don't add any other because they won't be used + + runs-on: ${{ matrix.os }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Check for undocumented modules + run: python scripts/goblint-lib-modules.py + + - name: Set up OCaml ${{ matrix.ocaml-compiler }} + env: + # otherwise setup-ocaml pins non-locked dependencies + # https://github.com/ocaml/setup-ocaml/issues/166 + OPAMLOCKED: locked + uses: ocaml/setup-ocaml@v2 + with: + ocaml-compiler: ${{ matrix.ocaml-compiler }} + + - name: Setup Pages + id: pages + uses: actions/configure-pages@v3 + + - name: Install dependencies + run: opam install . --deps-only --locked --with-doc + + - name: Build API docs + run: opam exec -- dune build @doc + + - name: Upload artifact + uses: actions/upload-pages-artifact@v2 + with: + path: _build/default/_doc/_html/ + + api-deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: api-build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v2 diff --git a/.github/workflows/indentation.yml b/.github/workflows/indentation.yml index c6ddca971f..e22e674301 100644 --- a/.github/workflows/indentation.yml +++ b/.github/workflows/indentation.yml @@ -1,43 +1,33 @@ name: indentation -on: [ push, pull_request] +on: + push: + pull_request: + workflow_dispatch: jobs: indentation: - strategy: # remove? + strategy: matrix: os: - ubuntu-latest ocaml-compiler: - - 4.14.0 # setup-ocaml@v1 does not support 4.14.x for ocaml-version + - 4.14.x runs-on: ${{ matrix.os }} - if: ${{ github.event.before != '0000000000000000000000000000000000000000' }} + if: ${{ !github.event.forced && github.event.before != '0000000000000000000000000000000000000000' }} steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - # reuse tests.yml or depend on it to not have to setup OCaml? https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#example-using-an-action-in-the-same-repository-as-the-workflow - - # rely on cache for now - - name: Cache opam switch # https://github.com/marketplace/actions/cache - uses: actions/cache@v3 - with: - # A list of files, directories, and wildcard patterns to cache and restore - path: | - ~/.opam - _opam - # Key for restoring and saving the cache - key: opam-ocp-indent-${{ runner.os }}-${{ matrix.ocaml-compiler }} - - name: Set up OCaml ${{ matrix.ocaml-compiler }} - uses: ocaml/setup-ocaml@v1 # intentionally use v1 instead of v2 because it's faster with manual caching: https://github.com/goblint/analyzer/pull/308#issuecomment-887805857 + uses: ocaml/setup-ocaml@v2 with: - ocaml-version: ${{ matrix.ocaml-compiler }} + ocaml-compiler: ${{ matrix.ocaml-compiler }} - name: Install ocp-indent run: opam install -y ocp-indent diff --git a/.github/workflows/locked.yml b/.github/workflows/locked.yml index 685fdc0afd..65dfbe7bac 100644 --- a/.github/workflows/locked.yml +++ b/.github/workflows/locked.yml @@ -25,9 +25,12 @@ jobs: runs-on: ${{ matrix.os }} + env: + OCAMLRUNPARAM: b + steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up OCaml ${{ matrix.ocaml-compiler }} env: @@ -64,6 +67,9 @@ jobs: - name: Test regression cram run: opam exec -- dune runtest tests/regression + - name: Test incremental cram + run: opam exec -- dune runtest tests/incremental + - name: Test unit run: opam exec -- dune runtest unittest @@ -73,6 +79,12 @@ jobs: - name: Test incremental regression with cfg comparison run: ruby scripts/update_suite.rb -c + - uses: actions/upload-artifact@v3 + if: always() + with: + name: suite_result + path: tests/suite_result/ + extraction: if: ${{ github.event_name == 'workflow_dispatch' || github.event_name == 'schedule' }} @@ -89,7 +101,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up OCaml ${{ matrix.ocaml-compiler }} env: @@ -129,7 +141,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up OCaml ${{ matrix.ocaml-compiler }} env: @@ -141,7 +153,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 }} @@ -156,3 +168,10 @@ jobs: - name: Build Gobview run: opam exec -- dune build gobview + + - name: Install selenium + run: pip3 install selenium webdriver-manager + + - name: Test Gobview + run: | + python3 scripts/test-gobview.py diff --git a/.github/workflows/metadata.yml b/.github/workflows/metadata.yml index da20c6b675..6c7360f9e3 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 @@ -36,10 +36,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..94c49e4bf6 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/common/util/options.schema.json - name: Validate conf - run: ajv validate -s src/util/options.schema.json -d "conf/**/*.json" + run: ajv validate -s src/common/util/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/common/util/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 5455bb0cb7..6c23c7cdd4 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 @@ -94,6 +95,9 @@ jobs: - name: Test regression cram run: opam exec -- dune runtest tests/regression + - name: Test incremental cram + run: opam exec -- dune runtest tests/incremental + - name: Test unit run: opam exec -- dune runtest unittest @@ -128,7 +132,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 @@ -179,6 +183,9 @@ jobs: - name: Test regression cram run: opam exec -- dune runtest tests/regression + - name: Test incremental cram + run: opam exec -- dune runtest tests/incremental + - name: Test unit run: opam exec -- dune runtest unittest @@ -202,14 +209,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 @@ -240,7 +247,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 480839d17e..75bd23d36b 100644 --- a/.gitignore +++ b/.gitignore @@ -44,6 +44,7 @@ _opam/ cfgs/ cfg.dot cilcfg.*.dot +arg.dot *.graphml goblint.bc.js @@ -90,3 +91,13 @@ witness.certificate.yml # transformations transformed.c + +# docs +site/ + +# coverage + +# bisect_ppx +*.coverage +# bisect-ppx-report +_coverage/* diff --git a/.mailmap b/.mailmap index f84ff2fb60..9153d55765 100644 --- a/.mailmap +++ b/.mailmap @@ -21,11 +21,19 @@ Ralf Vogler Ivana Zuzic 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> Martin Wehking +Martin Wehking Edin Citaku + Alexander Eichler +Mireia Cano Pujol +Felix Krayer +Felix Krayer <91671586+FelixKrayer@users.noreply.github.com> +Manuel Pietsch diff --git a/.readthedocs.yaml b/.readthedocs.yaml index d41c44390d..08044d195c 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -8,6 +8,16 @@ mkdocs: configuration: mkdocs.yml python: - version: 3.8 install: - requirements: docs/requirements.txt + +build: + os: ubuntu-22.04 + tools: + python: "3.8" + jobs: + post_install: + - pip install json-schema-for-humans + post_build: + - mkdir _readthedocs/html/jsfh/ + - generate-schema-doc --config-file jsfh.yml src/common/util/options.schema.json _readthedocs/html/jsfh/ diff --git a/.semgrep/cilint.yml b/.semgrep/cilint.yml new file mode 100644 index 0000000000..2e62b58a42 --- /dev/null +++ b/.semgrep/cilint.yml @@ -0,0 +1,62 @@ +rules: + - id: cilint-to_string + pattern: Cilint.string_of_cilint + fix: Z.to_string + message: use Z instead + languages: [ocaml] + severity: WARNING + + - id: cilint-compare + pattern: Cilint.compare_cilint + fix: Z.compare + message: use Z instead + languages: [ocaml] + severity: WARNING + + - id: cilint-zero + pattern: Cilint.zero_cilint + fix: Z.zero + message: use Z instead + languages: [ocaml] + severity: WARNING + + - id: cilint-is-zero + pattern: Cilint.is_zero_cilint $X + fix: Z.equal $X Z.zero + message: use Z instead + languages: [ocaml] + severity: WARNING + + - id: cilint-to_int + pattern: Cilint.int_of_cilint + fix: Z.to_int + message: use Z instead + languages: [ocaml] + severity: WARNING + + - id: cilint-of_big_int + pattern: Cilint.cilint_of_big_int $X + fix: $X + message: Cilint is Z + languages: [ocaml] + severity: WARNING + + - id: cilint-to_big_int + pattern: Cilint.big_int_of_cilint $X + fix: $X + message: Cilint is Z + languages: [ocaml] + severity: WARNING + + - id: cilint-t + pattern: Cilint.cilint + fix: Z.t + message: use Z instead + languages: [ocaml] + severity: WARNING + + - id: cilint-open + pattern: open Cilint + message: use Z instead + languages: [ocaml] + severity: WARNING diff --git a/.semgrep/exit.yml b/.semgrep/exit.yml new file mode 100644 index 0000000000..4edeecedde --- /dev/null +++ b/.semgrep/exit.yml @@ -0,0 +1,17 @@ +rules: + - id: raise-exit + pattern: raise Exit + fix: raise Stdlib.Exit + message: explictly use Stdlib.Exit instead of accidentally different Pervasives.Exit + languages: [ocaml] + severity: ERROR + + - id: catch-exit + pattern-either: + - pattern: try ... with Exit -> ... + - pattern: try ... with | Exit -> ... + - pattern: try ... with ... | Exit -> ... + - pattern: exception Exit + message: explictly use Stdlib.Exit instead of accidentally different Pervasives.Exit + languages: [ocaml] + severity: ERROR 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/.semgrep/zarith.yml b/.semgrep/zarith.yml new file mode 100644 index 0000000000..5c88e41d1b --- /dev/null +++ b/.semgrep/zarith.yml @@ -0,0 +1,246 @@ +rules: + - id: big_int_z-zero + pattern: Big_int_Z.zero_big_int + fix: Z.zero + message: use Z instead + languages: [ocaml] + severity: WARNING + + - id: big_int_z-to_float + pattern: Big_int_Z.float_of_big_int + fix: Z.to_float + message: use Z instead + languages: [ocaml] + severity: WARNING + + - id: big_int_z-of_int + pattern: Big_int_Z.big_int_of_int + fix: Z.of_int + message: use Z instead + languages: [ocaml] + severity: WARNING + + - id: big_int_z-t + pattern: Big_int_Z.big_int + fix: Z.t + message: use Z instead + languages: [ocaml] + severity: WARNING + + - id: big_int_z-mul + pattern: Big_int_Z.mult_big_int + fix: Z.mul + message: use Z instead + languages: [ocaml] + severity: WARNING + + - id: big_int_z-of_int64 + pattern: Big_int_Z.big_int_of_int64 + fix: Z.of_int64 + message: use Z instead + languages: [ocaml] + severity: WARNING + + - id: big_int_z-to_int + pattern: Big_int_Z.int_of_big_int + fix: Z.to_int + message: use Z instead + languages: [ocaml] + severity: WARNING + + - id: big_int_z-to_int32 + pattern: Big_int_Z.int32_of_big_int + fix: Z.to_int32 + message: use Z instead + languages: [ocaml] + severity: WARNING + + - id: big_int_z-of_int32 + pattern: Big_int_Z.big_int_of_int32 + fix: Z.of_int32 + message: use Z instead + languages: [ocaml] + severity: WARNING + + - id: big_int_z-to_int64 + pattern: Big_int_Z.int64_of_big_int + fix: Z.to_int64 + message: use Z instead + languages: [ocaml] + severity: WARNING + + - id: big_int_z-one + pattern: Big_int_Z.unit_big_int + fix: Z.one + message: use Z instead + languages: [ocaml] + severity: WARNING + + - id: big_int_z-neg + pattern: Big_int_Z.minus_big_int + fix: Z.neg + message: use Z instead + languages: [ocaml] + severity: WARNING + + - id: big_int_z-abs + pattern: Big_int_Z.abs_big_int + fix: Z.abs + message: use Z instead + languages: [ocaml] + severity: WARNING + + - id: big_int_z-add + pattern: Big_int_Z.add_big_int + fix: Z.add + message: use Z instead + languages: [ocaml] + severity: WARNING + + - id: big_int_z-sub + pattern: Big_int_Z.sub_big_int + fix: Z.sub + message: use Z instead + languages: [ocaml] + severity: WARNING + + - id: big_int_z-ediv + pattern: Big_int_Z.div_big_int + fix: Z.ediv + message: use Z instead + languages: [ocaml] + severity: WARNING + + - id: big_int_z-gcd + pattern: Big_int_Z.gcd_big_int + fix: Z.gcd + message: use Z instead + languages: [ocaml] + severity: WARNING + + - id: big_int_z-compare + pattern: Big_int_Z.compare_big_int + fix: Z.compare + message: use Z instead + languages: [ocaml] + severity: WARNING + + - id: big_int_z-of_string + pattern: Big_int_Z.big_int_of_string + fix: Z.of_string + message: use Z instead + languages: [ocaml] + severity: WARNING + + - id: big_int_z-to_string + pattern: Big_int_Z.string_of_big_int + fix: Z.to_string + message: use Z instead + languages: [ocaml] + severity: WARNING + + - id: big_int_z-logand + pattern: Big_int_Z.and_big_int + fix: Z.logand + message: use Z instead + languages: [ocaml] + severity: WARNING + + - id: big_int_z-logor + pattern: Big_int_Z.or_big_int + fix: Z.logor + message: use Z instead + languages: [ocaml] + severity: WARNING + + - id: big_int_z-logxor + pattern: Big_int_Z.xor_big_int + fix: Z.logxor + message: use Z instead + languages: [ocaml] + severity: WARNING + + - id: big_int_z-shift_left + pattern: Big_int_Z.shift_left_big_int + fix: Z.shift_left + message: use Z instead + languages: [ocaml] + severity: WARNING + + - id: big_int_z-shift_right + pattern: Big_int_Z.shift_right_big_int + fix: Z.shift_right + message: use Z instead + languages: [ocaml] + severity: WARNING + + - id: big_int_z-lt + pattern: Big_int_Z.lt_big_int + fix: Z.lt + message: use Z instead + languages: [ocaml] + severity: WARNING + + - id: big_int_z-gt + pattern: Big_int_Z.gt_big_int + fix: Z.gt + message: use Z instead + languages: [ocaml] + severity: WARNING + + - id: big_int_z-equal + pattern: Big_int_Z.eq_big_int + fix: Z.equal + message: use Z instead + languages: [ocaml] + severity: WARNING + + - id: big_int_z-pow-int + pattern: Big_int_Z.power_int_positive_int $X $Y + fix: Z.pow (Z.of_int $X) $Y + message: use Z instead + languages: [ocaml] + severity: WARNING + + - id: big_int_z + pattern: Big_int_Z.$X + message: use Z instead + languages: [ocaml] + severity: WARNING + + - id: big_int_z-open + pattern: open Big_int_Z + message: use Z instead + languages: [ocaml] + severity: WARNING + + - id: z-add-one + pattern-either: + - pattern: Z.add $X Z.one + - pattern: Z.add Z.one $X + - pattern: Z.add Z.one @@ $X + fix: Z.succ $X + message: use Z.succ instead + languages: [ocaml] + severity: WARNING + + - id: z-sub-one + pattern: Z.sub $X Z.one + fix: Z.pred $X + message: use Z.pred instead + languages: [ocaml] + severity: WARNING + + - id: z-of_int-0 + pattern: Z.of_int 0 + fix: Z.zero + message: use Z.zero instead + languages: [ocaml] + severity: WARNING + + - id: z-of_int-1 + pattern: Z.of_int 1 + fix: Z.one + message: use Z.one instead + languages: [ocaml] + severity: WARNING \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index a9531a5766..97cc399133 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,27 @@ +## 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/README.md b/README.md index 3c94fc155b..4d97baa842 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,19 @@ # Goblint -[![locked workflow status](https://github.com/goblint/analyzer/actions/workflows/locked.yml/badge.svg)](https://github.com/goblint/analyzer/actions/workflows/locked.yml) -[![unlocked workflow status](https://github.com/goblint/analyzer/actions/workflows/unlocked.yml/badge.svg)](https://github.com/goblint/analyzer/actions/workflows/unlocked.yml) -[![docker workflow status](https://github.com/goblint/analyzer/actions/workflows/docker.yml/badge.svg)](https://github.com/goblint/analyzer/actions/workflows/docker.yml) -[![Documentation Status](https://readthedocs.org/projects/goblint/badge/?version=latest)](https://goblint.readthedocs.io/en/latest/?badge=latest) [![GitHub release status](https://img.shields.io/github/v/release/goblint/analyzer)](https://github.com/goblint/analyzer/releases) [![opam package status](https://badgen.net/opam/v/goblint)](https://opam.ocaml.org/packages/goblint) [![Zenodo DOI](https://zenodo.org/badge/2066905.svg)](https://zenodo.org/badge/latestdoi/2066905) +[![locked workflow status](https://github.com/goblint/analyzer/actions/workflows/locked.yml/badge.svg)](https://github.com/goblint/analyzer/actions/workflows/locked.yml) +[![unlocked workflow status](https://github.com/goblint/analyzer/actions/workflows/unlocked.yml/badge.svg)](https://github.com/goblint/analyzer/actions/workflows/unlocked.yml) +[![Coverage Status](https://coveralls.io/repos/github/goblint/analyzer/badge.svg?branch=master)](https://coveralls.io/github/goblint/analyzer?branch=master) +[![docker workflow status](https://github.com/goblint/analyzer/actions/workflows/docker.yml/badge.svg)](https://github.com/goblint/analyzer/actions/workflows/docker.yml) +[![Documentation Status](https://readthedocs.org/projects/goblint/badge/?version=latest)](https://goblint.readthedocs.io/en/latest/?badge=latest) + Documentation can be browsed on [Read the Docs](https://goblint.readthedocs.io/en/latest/) or [GitHub](./docs/). ## 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). @@ -18,6 +21,7 @@ Both for using an up-to-date version of Goblint or developing it, the best way i 3. Run `make setup` to install OCaml and dependencies via opam. 4. Run `make` to build Goblint itself. 5. Run `make install` to install Goblint into the opam switch for usage via switch's `PATH`. +6. _Optional:_ See [`scripts/bash-completion.sh`](./scripts/bash-completion.sh) for setting up bash completion for Goblint arguments. ### MacOS 1. Install GCC with `brew install gcc` (first run `xcode-select --install` if you don't want to build it from source). Goblint requires GCC while macOS's default `cpp` is Clang, which will not work. diff --git a/bench/basic/benchSet.ml b/bench/basic/benchSet.ml new file mode 100644 index 0000000000..14eb03be82 --- /dev/null +++ b/bench/basic/benchSet.ml @@ -0,0 +1,94 @@ +(* dune exec bench/basic/benchSet.exe -- -a *) + +open Benchmark +open Benchmark.Tree + +module IS = Set.Make (Int) + +let set1 = IS.of_seq (Seq.init 1024 Fun.id) +let set2 = IS.of_seq (Seq.init 1024 (fun i -> i + 1)) +let set3 = IS.of_seq (Seq.init 1024 (fun i -> i - 1)) + +let equal1 (x, y) = IS.equal x y +let equal2 (x, y) = IS.for_all (fun i -> IS.exists (Int.equal i) y) x + +let equal1' (x, y) = IS.cardinal x = IS.cardinal y && IS.equal x y +let equal2' (x, y) = IS.cardinal x = IS.cardinal y && IS.for_all (fun i -> IS.exists (Int.equal i) y) x + + +let () = + register ( + "equal" @>>> [ + "1-1" @> lazy ( + let args = (set1, set1) in + throughputN 1 [ + ("equal1", equal1, args); + ("equal2", equal2, args); + ("equal1'", equal1', args); + ("equal2'", equal2', args); + ] + ); + "1-2" @> lazy ( + let args = (set1, set2) in + throughputN 1 [ + ("equal1", equal1, args); + ("equal2", equal2, args); + ("equal1'", equal1', args); + ("equal2'", equal2', args); + ] + ); + "1-3" @> lazy ( + let args = (set1, set3) in + throughputN 1 [ + ("equal1", equal1, args); + ("equal2", equal2, args); + ("equal1'", equal1', args); + ("equal2'", equal2', args); + ] + ); + ] + ) + + +let map1 (f, x) = IS.map f x +let map2 (f, x) = + let add_to_it e x = IS.add (f e) x in + IS.fold add_to_it x IS.empty + +let () = + register ( + "map" @>>> [ + "inc" @> lazy ( + let args = ((fun x -> x + 1), set1) in + throughputN 1 [ + ("map1", map1, args); + ("map2", map2, args); + ] + ); + "flip" @> lazy ( + let args = ((fun x -> 2048 - x), set1) in + throughputN 1 [ + ("map1", map1, args); + ("map2", map2, args); + ] + ); + "const" @> lazy ( + let args = ((fun x -> 42), set1) in + throughputN 1 [ + ("map1", map1, args); + ("map2", map2, args); + ] + ); + "shuffle" @> lazy ( + let args = ((fun x -> (31 * x + 42) mod 37), set1) in + throughputN 1 [ + ("map1", map1, args); + ("map2", map2, args); + ] + ); + ] + ) + + +let () = + run_global () diff --git a/bench/basic/dune b/bench/basic/dune index ac80832014..38feeff8af 100644 --- a/bench/basic/dune +++ b/bench/basic/dune @@ -1,4 +1,4 @@ -(executable - (name benchTuple4) +(executables + (names benchTuple4 benchSet) (optional) ; TODO: for some reason this doesn't work: `dune build` still tries to compile if benchmark missing (https://github.com/ocaml/dune/issues/4065) (libraries benchmark batteries.unthreaded)) diff --git a/bench/zarith/benchZarith.ml b/bench/zarith/benchZarith.ml index 5830a226cf..0ce14b1434 100644 --- a/bench/zarith/benchZarith.ml +++ b/bench/zarith/benchZarith.ml @@ -8,8 +8,8 @@ open Benchmark.Tree let () = - let pow2_pow n = Big_int_Z.power_int_positive_int 2 n in - let pow2_lsl n = Big_int_Z.shift_left_big_int Big_int_Z.unit_big_int n in + let pow2_pow n = Z.pow (Z.of_int 2) n in + let pow2_lsl n = Z.shift_left Z.one n in register ( diff --git a/conf/bench-yaml-validate.json b/conf/bench-yaml-validate.json index 504be1a02d..7b18371bd1 100644 --- a/conf/bench-yaml-validate.json +++ b/conf/bench-yaml-validate.json @@ -1,23 +1,31 @@ { "ana": { + "sv-comp": { + "functions": true + }, "int": { "def_exc": true, "enums": false, "interval": true }, "activated": [ - "expRelation", "base", "threadid", "threadflag", "threadreturn", - "escape", + "mallocWrapper", "mutexEvents", "mutex", "access", - "mallocWrapper", + "race", + "escape", + "expRelation", "mhp", "assert", + "var_eq", + "symb_locks", + "thread", + "threadJoins", "unassume" ], "malloc": { @@ -44,14 +52,6 @@ "tokens": true } }, - "witness": { - "enabled": false, - "invariant": { - "loop-head": true, - "after-lock": true, - "other": false - } - }, "sem": { "unknown_function": { "invalidate": { @@ -64,6 +64,9 @@ }, "int": { "signed_overflow": "assume_none" + }, + "null-pointer": { + "dereference": "assume_none" } }, "pre": { diff --git a/conf/bench-yaml.json b/conf/bench-yaml.json index 02b6be226c..fd97b2c08c 100644 --- a/conf/bench-yaml.json +++ b/conf/bench-yaml.json @@ -1,23 +1,31 @@ { "ana": { + "sv-comp": { + "functions": true + }, "int": { "def_exc": true, "enums": false, "interval": true }, "activated": [ - "expRelation", "base", "threadid", "threadflag", "threadreturn", - "escape", + "mallocWrapper", "mutexEvents", "mutex", "access", - "mallocWrapper", + "race", + "escape", + "expRelation", "mhp", - "assert" + "assert", + "var_eq", + "symb_locks", + "thread", + "threadJoins" ], "malloc": { "wrappers": [ @@ -40,20 +48,6 @@ ] } }, - "witness": { - "enabled": false, - "yaml": { - "enabled": true - }, - "invariant": { - "exact": false, - "exclude-vars": [ - "tmp\\(___[0-9]+\\)?", - "cond", - "RETURN" - ] - } - }, "sem": { "unknown_function": { "invalidate": { @@ -66,6 +60,9 @@ }, "int": { "signed_overflow": "assume_none" + }, + "null-pointer": { + "dereference": "assume_none" } }, "pre": { diff --git a/conf/examples/large-program.json b/conf/examples/large-program.json index 79681e0a8d..679147309f 100644 --- a/conf/examples/large-program.json +++ b/conf/examples/large-program.json @@ -1,7 +1,7 @@ { "ana": { "activated": [ - "base", "mallocWrapper","escape","mutex","mutexEvents","access" + "base", "mallocWrapper","escape","mutex","mutexEvents","access", "race", "assert" ], "base": { "privatization": "none", diff --git a/conf/examples/medium-program.json b/conf/examples/medium-program.json index 250d19489b..2c1e7c7346 100644 --- a/conf/examples/medium-program.json +++ b/conf/examples/medium-program.json @@ -13,8 +13,11 @@ "mutexEvents", "mutex", "access", + "race", "escape", "expRelation", + "mhp", + "assert", "var_eq", "symb_locks", "region", diff --git a/conf/examples/very-precise.json b/conf/examples/very-precise.json index e86d001e10..84cbf53585 100644 --- a/conf/examples/very-precise.json +++ b/conf/examples/very-precise.json @@ -26,8 +26,11 @@ "mutexEvents", "mutex", "access", + "race", "escape", "expRelation", + "mhp", + "assert", "var_eq", "symb_locks", "region", diff --git a/conf/incremental.json b/conf/incremental.json index 3d8a20416c..a9c5fcd152 100644 --- a/conf/incremental.json +++ b/conf/incremental.json @@ -28,11 +28,13 @@ "trace": { "context": true }, - "debug": true, "timing": { "enabled": true } }, + "warn": { + "debug": true + }, "result": "none", "solver": "td3", "solvers": { diff --git a/conf/ldv-races.json b/conf/ldv-races.json index 2414413de4..8db800d74c 100644 --- a/conf/ldv-races.json +++ b/conf/ldv-races.json @@ -25,9 +25,11 @@ "mutexEvents", "mutex", "access", + "race", "escape", "expRelation", - "mhp" + "mhp", + "assert" ], "malloc": { "wrappers": [ @@ -51,8 +53,11 @@ } }, "witness": { - "id": "enumerate", - "unknown": false + "graphml": { + "enabled": true, + "id": "enumerate", + "unknown": false + } }, "solver": "td3", "sem": { diff --git a/conf/minimal_incremental.json b/conf/minimal_incremental.json index a92468c698..4eb9f8289a 100644 --- a/conf/minimal_incremental.json +++ b/conf/minimal_incremental.json @@ -27,11 +27,13 @@ "trace": { "context": true }, - "debug": true, "timing": { "enabled": true } }, + "warn": { + "debug": true + }, "result": "none", "solver": "td3", "solvers": { diff --git a/conf/svcomp-yaml-validate.json b/conf/svcomp-yaml-validate.json index ad635c787e..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": { @@ -68,17 +73,12 @@ "tokens": true } }, + "pre": { + "transform-paths": false + }, "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 28483e8059..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 @@ -64,15 +69,23 @@ "enabled": false } }, + "pre": { + "transform-paths": false + }, "exp": { "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 56474fbe2b..73f99500b9 100644 --- a/conf/svcomp.json +++ b/conf/svcomp.json @@ -70,7 +70,8 @@ "congruence", "octagon", "wideningThresholds", - "loopUnrollHeuristic" + "loopUnrollHeuristic", + "memsafetySpecification" ] } }, @@ -90,7 +91,13 @@ } }, "witness": { - "id": "enumerate", - "unknown": false + "graphml": { + "enabled": true, + "id": "enumerate", + "unknown": false + } + }, + "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 454ea97bfe..f7f7662b6a 100644 --- a/conf/svcomp22-intervals-novareq-affeq-apron.json +++ b/conf/svcomp22-intervals-novareq-affeq-apron.json @@ -17,12 +17,15 @@ "mutexEvents", "mutex", "access", + "race", "escape", "expRelation", "apron", "symb_locks", "region", - "thread" + "thread", + "mhp", + "assert" ], "context": { "widen": false @@ -65,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 dfd20dfb4d..00db00f30f 100644 --- a/conf/svcomp22-intervals-novareq-affeq-native.json +++ b/conf/svcomp22-intervals-novareq-affeq-native.json @@ -18,11 +18,14 @@ "mutex", "affeq", "access", + "race", "escape", "expRelation", "symb_locks", "region", - "thread" + "thread", + "mhp", + "assert" ], "context": { "widen": false @@ -62,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 e0754bd885..a0c09e8937 100644 --- a/conf/svcomp22-intervals-novareq-octagon-apron.json +++ b/conf/svcomp22-intervals-novareq-octagon-apron.json @@ -17,12 +17,15 @@ "mutexEvents", "mutex", "access", + "race", "escape", "expRelation", "apron", "symb_locks", "region", - "thread" + "thread", + "mhp", + "assert" ], "context": { "widen": false @@ -65,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 ebe63e62ab..3a478bf687 100644 --- a/conf/svcomp22-intervals-novareq-polyhedra-apron.json +++ b/conf/svcomp22-intervals-novareq-polyhedra-apron.json @@ -17,12 +17,15 @@ "mutexEvents", "mutex", "access", + "race", "escape", "expRelation", "apron", "symb_locks", "region", - "thread" + "thread", + "mhp", + "assert" ], "context": { "widen": false @@ -65,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/traces-rel-toy.json b/conf/traces-rel-toy.json index 5f05ca3dc8..449d346b81 100644 --- a/conf/traces-rel-toy.json +++ b/conf/traces-rel-toy.json @@ -10,6 +10,7 @@ "mutexEvents", "mutex", "access", + "race", "mallocWrapper", "mhp", "apron", diff --git a/conf/zstd-race.json b/conf/zstd-race.json index 9a8ae0a87f..d1a8848c06 100644 --- a/conf/zstd-race.json +++ b/conf/zstd-race.json @@ -2,7 +2,7 @@ "ana": { "activated": [ "expRelation", "base", "threadid", "threadflag", "threadreturn", - "escape", "mutexEvents", "mutex", "access", "mallocWrapper", "mhp", + "escape", "mutexEvents", "mutex", "access", "race", "mallocWrapper", "mhp", "assert", "symb_locks", "var_eq", "mallocFresh" ], "ctx_insens": [ @@ -31,6 +31,17 @@ "lines": true } }, + "lib": { + "activated": [ + "c", + "posix", + "pthread", + "gcc", + "glibc", + "linux-userspace", + "zstd" + ] + }, "sem": { "unknown_function": { "spawn": false, diff --git a/docs/artifact-descriptions/esop23.md b/docs/artifact-descriptions/esop23.md new file mode 100644 index 0000000000..a5a604d207 --- /dev/null +++ b/docs/artifact-descriptions/esop23.md @@ -0,0 +1,151 @@ +# ESOP '23 Artifact Description + +This is the artifact description for our ESOP '23 paper "Clustered Relational Thread-Modular Abstract Interpretation with Local Traces". +The artifact is available from Zenodo at [https://doi.org/10.5281/zenodo.7505428](https://doi.org/10.5281/zenodo.7505428). The `md5` of the `.ova` file is `e1ea120a70ed0ee4b7344527de68307c`. +The associated source code can conveniently be browsed at: https://github.com/goblint/analyzer/tree/esop23. + +The artifact is a VirtualBox Image based on Ubuntu 22.04.1. The login is `goblint:goblint`. + +For convenience this file is also included in the VM (at `~/analyzer/docs/artifact-descriptions/esop23.md`) in order to be able to copy commands. + +**The current version of Goblint no longer ships with all the convenience scripts described here. We recommend using the frozen version in the artifact.** +Alternatively, you may use the corresponding versions of Goblint (https://github.com/goblint/analyzer/releases/tag/esop23) and the benchmarks repository (https://github.com/goblint/bench/releases/tag/esop23). + +# Getting Started Guide + +- Install VirtualBox (e.g. from [virtualbox.org](https://www.virtualbox.org/)) +- Import the VM via `File -> Import Appliance` (requires ~25GB of free memory) +- Start the VM and (if prompted) login as `goblint` with password `goblint` +- If you use a Non-English keyboard layout, you can change the keyboard layout in the top right corner to match yours. +- Navigate to the folder `~/analyzer`. All paths are given relative to it. +- Run the following commands to verify the installation works as intended + - `./scripts/esop23-kick-tires.sh` (will take ~1min) + - Internally, this will run a few internal regression tests (you can open the script to see which). These exercise the major parts of the system. + - After the command has run, there should be some messages `No errors :)` as well as some messages `Excellent: ignored check on ... now passing!` +
+ Detailed expected message + ``` + Excellent: ignored check on tests/regression/01-cpa/33-asserts.c:35 is now passing! + Excellent: ignored check on tests/regression/28-race_reach/22-deref_read_racefree.c:20 is now passing! + No errors :) + No errors :) + Excellent: ignored check on 21-traces-cluster-based.c:48 is now passing! + Excellent: ignored check on 21-traces-cluster-based.c:66 is now passing! + Excellent: ignored check on 21-traces-cluster-based.c:69 is now passing! + Excellent: ignored check on 22-traces-write-centered-vs-meet-mutex.c:25 is now passing! + Excellent: ignored check on 42-threadenter-arg.c:6 is now passing! + No errors :) + No errors :) + No errors :) + ``` +
+ +# Step-by-Step Instructions + +The following are step-by-step instructions to reproduce the experimental results underlying the paper. +Depending on the host machine, the run times will be slightly different from what is reported in the paper, but they should behave the same way relative to each other. +The runtimes given here are based on runtimes we had in the VM on a host system with an 11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz with 32GB of RAM with some generous upwards rounding. +Apart from these small runtime deviations which are to be expected, all claims in the paper are reproducible and supported by the artifact. + +**Important note: We based our implementation on our previous work on Goblint, but also compare with Goblint in the non-relational setting from our previous SAS paper. This previous version is referred to as "Goblint w/ Interval"** + +**As a first step, navigate to the folder `~/analyzer`. All paths are given relative to it.** + +## Claims in Paragraph "Internal comparison" (p.23) + +All these claims derive from Fig. 13 (a) and 13 (b). The data underlying these tables is produced by running: + +1. Run the script `../bench/esop23_fig13.rb`. This takes ~25min (see note below). The runs for `ypbind` will fail (see below). +2. Open the results HTML `../bench/esop23_fig13/index.html`. + + - The configurations are named the same as in the paper (with the exception that the `Interval` configuration from the paper is named `box` in the table, and `Clusters` is named `cluster12`). + - As noted in appendix I.1, we had to exclude `ypbind` from the benchmarks, as it spawns a thread from an unknown pointer which the analysis can not handle + - As dumping the precision information incurs a significant performance penalty, each configuration is run twice: Once to measure runtime and once with + the post-processing that marshals the internal data structures to disk. + + + +### Notes +* The source code for benchmarks can be found in `../bench/pthread/` and `../bench/svcomp/`. +* Although it takes ~25 min to run all the benchmarks, the script continually updates the results HTML. Therefore it's possible to observe the first results in the partially-filled table without having to wait for the script to finish (if that shows you a blank, try waiting a while and refreshing). + + +## All other claims in Section 9 + +All these claims are based on the contents of Table 2. +Table 3 in the supplementary material gives details on the results for the Goblint set. + +### Reproducing the tables for our tool & Goblint w/ Interval + +To generate the tables for all sets, run `./scripts/esop23-table2.sh` (will take ~20 min). + +This will produce one HTML file with results per group: + +| Set | HTML-File | +| -------- | ------------------------------------------------| +| Our | `../bench/esop23_table2_set_our/index.html` | +| Goblint | `../bench/esop23_table2_set_goblint/index.html` | +| Watts | `../bench/esop23_table2_set_watts/index.html` | +| Ratcop | `../bench/esop23_table2_set_ratcop/index.html` | + + +How to interpret the results tables: + - The configurations are named the same as in the paper (with the exception that the `Interval` configuration from the paper is named `box` in the table, and `Clusters` is named `cluster12`). + - The results table shows for each test the total numbers of asserts and how many could be proven: + - If all are proven, the cell shows a checkmark + - If none are proven, the cell shows a cross + - If only some are proven, the cell shows both numbers + +**For the Set "Watts":** + - For a detailed discussion on these benchmarks, see Appendix I.2 of the paper. + - As opposed to the other scripts, this one also prints run-times as these are needed to also verify **Table 4** in the supplementary material. + +Note: To regenerate just some of the results: Invoke one of `../bench/esop23_table2_set_{our,goblint,watts,ratcop}.rb`. + +### Reproducing Duet numbers + +This artifact ships Duet (at commit `5ea68373bb8c8cff2a9b3a84785b12746e739cee`) with a bug fix (courtesy of its original author Zach Kincaid) allowing it to run successfully on some of the benchmarks. +For others, it still reported a number of reachable asserts that is too low. +We only give the instructions to reproduce the successful runs here. For a detailed discussion see Apppendix I.3. + +To generate the CSV file for all sets, invoke `./scripts/esop23-table2-duet.sh` (runs about 2mins). + +- The output is the complete output for Duet, which can be quite verbose. +- Outputs `Error: NN` indicate that `NN` asserts could not be proven by Duet and are not indicative of bigger issues. +Also, for some runs Duet will crash with an exception from solver.ml. + + +| Set | HTML-File | +| -------- | ------------------------------------------------| +| Our | `../bench/traces-relational-duet-ours.csv` | +| Goblint | - | +| Watts | `../bench/traces-relational-duet-watts.csv` | +| Ratcop | `../bench/traces-relational-duet-ratcop.csv` | + +These files contain for the tests on which Duet did not crash the number of verified assertions, +followed by the number of failed assertions. + +Note: To regenerate just some of the results: Invoke one of `python3 ../bench/duet-{ours,goblint,watts,ratcop}.py`. + + +## Additional Information +### Outline of how the code is structured +Lastly, we give a general outline of how code in the Goblint framework is organized: +The source code is in the directory `./src`, where the subdirectories are structured as follows: + +The most relevant directories are: + +- `./src/solvers`: Different fix-point solvers that can be used by Goblint (default is TD3) +- `./src/domains`: Various generic abstract domains: Sets, Maps, ... +- `./src/cdomains`: Abstract domains for C programs (e.g. Integers, Addresses, ...) +- `./src/analyses`: Different analyses supported by Goblint +- `./src/framework`: The code of the analysis framework + +Other, not directly relevant, directories: + +- `./src/extract`: Related to extracting Promela models from C code +- `./src/incremental`: Related to Incremental Analysis +- `./src/spec`: Related to parsing Specifications for an automata-based analysis of liveness properties +- `./src/transform`: Specify transformations to run based on the analysis results +- `./src/util`: Various utility modules +- `./src/witness`: Related to witness generation diff --git a/docs/developer-guide/firstanalysis.md b/docs/developer-guide/firstanalysis.md index 38668c28da..0923e792cd 100644 --- a/docs/developer-guide/firstanalysis.md +++ b/docs/developer-guide/firstanalysis.md @@ -35,7 +35,7 @@ This program is in the Goblint repository: `tests/regression/99-tutorials/01-fir But if you run Goblint out of the box on this example, it will not work: ```console -./goblint --enable dbg.debug tests/regression/99-tutorials/01-first.c +./goblint --enable warn.debug tests/regression/99-tutorials/01-first.c ``` This will claim that the assertion in unknown. @@ -69,6 +69,7 @@ There is no need to implement the transfer functions for branching for this exam 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. +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). ## Extending the domain diff --git a/docs/developer-guide/releasing.md b/docs/developer-guide/releasing.md index f6bfbb459e..69ffcb2461 100644 --- a/docs/developer-guide/releasing.md +++ b/docs/developer-guide/releasing.md @@ -45,13 +45,20 @@ 10. Check that analysis works: `goblint -v tests/regression/04-mutex/01-simple_rc.c`. 11. 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`. ## SV-COMP @@ -104,15 +111,9 @@ ### 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/developer-guide/testing.md b/docs/developer-guide/testing.md index 6c25492faa..0854e4f33d 100644 --- a/docs/developer-guide/testing.md +++ b/docs/developer-guide/testing.md @@ -11,7 +11,8 @@ Regression tests can be run with various granularity: * Run all tests with: `./scripts/update_suite.rb`. * Run a group of tests with: `./scripts/update_suite.rb group sanity`. - Unfortunately this also runs skipped tests... + Unfortunately this also runs skipped tests. + This is a bug that is used as a feature in the tests with Apron, as not all CI jobs have the Apron library installed. * Run a single test with: `./scripts/update_suite.rb assert`. * Run a single test with full output: `./regtest.sh 00 01`. @@ -24,8 +25,93 @@ gobopt='--set ana.base.privatization write+lock' ./scripts/update_suite.rb ``` ### Writing -* Add parameters to a regression test in the first line: `// PARAM: --set dbg.debug true` -* Annotate lines inside the regression test with comments: `arr[9] = 10; // WARN` +Regression tests use single-line comments (with `//`) as annotations. + +#### First line +A comment on the first line can contain the following: + +| Annotation | Comment | +| ---------- | ------- | +| `PARAM: `
(NB! space) | The following command line parameters are added to Goblint for this test. | +| `SKIP` | The test is skipped (except when run with `./scripts/update_suite.rb group`). | +| `NOMARSHAL` | Marshaling and unmarshaling of results is not tested on this program. | + +#### End of line +Comments at the end of other lines indicate the behavior on that line: + +| Annotation | Expected Goblint result | Concrete semantics | Checks | +| ---------- | ----- | ------------- | --- | +| `SUCCESS`
or nothing | Assertion succeeds | Assertion always succeeds | Precision | +| `FAIL` | Assertion fails | Assertion always fails | Precision | +| `UNKNOWN!` | Assertion is unknown | Assertion may both
succeed or fail | Soundness | +| `UNKNOWN` | Assertion is unknown | — | Intended imprecision | +| `TODO`
or `SKIP` | Assertion is unknown
or succeeds | Assertion always succeeds | Precision improvement | +| `NORACE` | No race warning | No data race | Precision | +| `RACE!` | Race warning | Data race is possible | Soundness | +| `RACE` | Race warning | — | Intended imprecision | +| `NODEADLOCK` | No deadlock warning | No deadlock | Precision | +| `DEADLOCK` | Deadlock warning | Deadlock is possible | Soundness | +| `NOWARN` | No warning | — | Precision | +| `WARN` | Some warning | — | Soundness | + +#### Other +Other useful constructs are the following: + +| Code with annotation | Comment | +| -------------------- | ------- | +| `__goblint_check(1); // reachable` | Checks that the line is reachable according
to Goblint results (soundness). | +| `__goblint_check(0); // NOWARN (unreachable)` | Checks that the line is unreachable (precision). | + +## Cram Tests +[Cram-style tests](https://dune.readthedocs.io/en/stable/tests.html#cram-tests) are also used to verify that existing functionality hasn't been broken. +They check the complete standard output of running the Goblint binary with specified command-line arguments. +Unlike regular regression tests, cram tests are not limited to testing annotations in C code. +They can be used to test arbitrary output from Goblint, such as program transformations. + +Cram tests are located next to regression tests in `./tests/regression/`. + +### Running +Cram tests are run as part of a complete test run: + +* `dune runtest` + +This might take a while though. Pass the test directory to `dune` to run only cram tests in a that directory: + +* `dune runtest tests/regression/` runs all cram tests. +* `dune runtest tests/regression/00-sanity` runs all cram tests in `00-sanity`. + +To run a single cram test, pass the file name without the `.t` extension and with a leading `@` to `dune build`: + +* `dune build @01-assert` runs only `tests/regression/00-sanity/01-assert.t`. + +### Writing +Create new cram tests in a subdirectory of `tests/regression` with the extension `.t`. The basic syntax of a cram test is as follows: + +```cram +Anything not indented by two spaces is a comment. + $ goblint file.c # This command gets run in a shell. + +``` + +A `dune` file in the subdirectory must declare dependencies on other files, e.g. C files for goblint. +For example, to declare a dependency on all C and JSON files in the directory, use the `deps` stanza with `glob_files`: + +```dune +(cram + (deps (glob_files *.c) (glob_files *.json))) +``` + +The [Dune documentation on file tests](https://dune.readthedocs.io/en/stable/tests.html#file-tests) contains more details. + +### Promoting Changes +When Goblint's output is intentionally changed by code changes, cram tests will fail. +After checking that the changes to Goblint's output shown in failing cram tests are as expected, you must update those tests. +Dune can automatically update cram test files, i.e. promote the changes. + +First, run the offending test as above. Once the new output is correct: + +* `dune promote` promotes the changes for all files. +* `dune promote ` promotes the changes for the specified files and directories. ## Incremental tests The incremental tests are regression tests that are first run with the option `incremental.save` and then again @@ -68,3 +154,17 @@ To test a domain, you need to do the following: 1. Implement `arbitrary` (reasonably). 2. Add the domain to `Maindomaintest`. + +## Coverage + +The [bisect_ppx](https://github.com/aantron/bisect_ppx) tool is used to produce code coverage reports for Goblint. +The code coverage reports are available on [Coveralls](https://coveralls.io/github/goblint/analyzer). + +To run `bisect_ppx` locally: + +1. Install bisect_ppx with `opam install bisect_ppx`. +2. Run `make coverage` to build Goblint with bisect_ppx instrumentation. +3. Run tests (this will now generate `.coverage` files in various directories). +4. Generate coverage report with `bisect-ppx-report html --coverage-path=.`. +5. After that the generated `.coverage` files can be removed with `find . -type f -name '*.coverage' -delete`. +6. The HTML report can be found in the `_coverage` folder. diff --git a/docs/img/wettlaufweltmeister.svg b/docs/img/wettlaufweltmeister.svg new file mode 100644 index 0000000000..23fb8cc327 --- /dev/null +++ b/docs/img/wettlaufweltmeister.svg @@ -0,0 +1,519 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + GOBLINT + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + GOBLINT + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/index.md b/docs/index.md index eee608e117..c2186fb37a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1 +1,18 @@ -# Welcome to Goblint documentation +# Goblint documentation + +Welcome to the Goblint project documentation! Here you may find *some* useful information about using and developing Goblint. + +Below is a picture of *Wettlaufweltmeister*, the Goblint organization mascot. The ranking of verifiers in the Race Detection category at SV-COMP 2023 explains the name. + + + +1. **Goblint (1304)** +2. Deagle (1211) +3. Dartagnan (768) +4. UAutomizer (756) +5. UGemCutter (732) +6. UTaipan (612) +7. CPAchecker (400) +8. Locksmith (226) +9. Theta (205) +10. ... diff --git a/docs/user-guide/annotating.md b/docs/user-guide/annotating.md index b732e3aaa9..cdfb892ffe 100644 --- a/docs/user-guide/annotating.md +++ b/docs/user-guide/annotating.md @@ -21,10 +21,11 @@ The attribute `goblint_context` can be used to fine-tune function contexts. The following string arguments are supported: 1. `base.interval`/`base.no-interval` to override the `ana.base.context.interval` option. -2. `base.int`/`base.no-int` to override the `ana.base.context.interval` option. -3. `base.non-ptr`/`base.no-non-ptr` to override the `ana.base.context.non-ptr` option. -4. `relation.context`/`relation.no-context` to override the `ana.relation.context` option. -5. `widen`/`no-widen` to override the `ana.context.widen` option. +2. `base.interval_set`/`base.no-interval_set` to override the `ana.base.context.interval_set` option. +3. `base.int`/`base.no-int` to override the `ana.base.context.interval` option. +4. `base.non-ptr`/`base.no-non-ptr` to override the `ana.base.context.non-ptr` option. +5. `relation.context`/`relation.no-context` to override the `ana.relation.context` option. +6. `widen`/`no-widen` to override the `ana.context.widen` option. ### Apron attributes The Apron library can be set to only track variables with the attribute `goblint_apron_track` @@ -40,7 +41,7 @@ struct array { int arr[5] __attribute__((goblint_array_domain("partitioned"))); }; ``` -It is also possible to annotate a type, so that all arrays of this type without an own attribute will use this one: +It is also possible to annotate a type, so that all arrays of this type without an own attribute will use this one: ```c typedef int unrollInt __attribute__((goblint_array_domain("trivial"))); 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 e71057e96b..9a32a14a4c 100644 --- a/docs/user-guide/configuring.md +++ b/docs/user-guide/configuring.md @@ -1,6 +1,6 @@ # Configuring -On top of passing options via the command line, Goblint can be configured with `json` files following the schema outlined in `/src/util/options.schema.json` +On top of passing options via the command line, Goblint can be configured with `json` files following the schema outlined in [the Options reference](../../jsfh/options.schema.html) (also on the sidebar) or using one of the default configurations we provide. # Example Configurations for Goblint @@ -24,7 +24,7 @@ In `.vscode/settings.json` add the following: "/conf/*.json", "/tests/incremental/*/*.json" ], - "url": "/src/util/options.schema.json" + "url": "/src/common/util/options.schema.json" } ] } diff --git a/docs/user-guide/inspecting.md b/docs/user-guide/inspecting.md index 9454f91207..f4f6036f1b 100644 --- a/docs/user-guide/inspecting.md +++ b/docs/user-guide/inspecting.md @@ -9,11 +9,17 @@ Modern browsers' security settings forbid some file access which is necessary for g2html to work, hence the need for serving the results via Python's `http.server` (or similar). -## Gobview +## GobView + +For the initial setup: 1. Install Node.js (preferably ≥ 12.0.0) and npm (≥ 5.2.0) -2. For the initial setup: `make setup_gobview` -3. Run `dune build gobview` to build the web UI -4. Run Goblint with these flags: `--enable gobview --set save_run DIR` (where `DIR` is the name of the result directory that Goblint will create and populate) -5. `cd` into `DIR` and run `python3 -m http.server` -6. Visit http://localhost:8000 +2. Run `make setup_gobview` in the analyzer directory + +To build GobView (also for development): + +1. Run `dune build gobview` in the analyzer directory to build the web UI +2. The executable for the http-server can then be found in the directory `./_build/default/gobview/goblint-http-server`. It takes the analyzer directory and additional Goblint configurations such as the files to be analyzed as parameters. Run it e.g. with the following command:\ +`./_build/default/gobview/goblint-http-server/goblint_http.exe -with-goblint ../analyzer/goblint -goblint --set files[+] "../analyzer/tests/regression/00-sanity/01-assert.c"` + +4. Visit diff --git a/dune-project b/dune-project index 108e8719ef..4a9cd8e3c1 100644 --- a/dune-project +++ b/dune-project @@ -24,11 +24,11 @@ (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.2)) ; 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.0)) (zarith (>= 1.8)) (yojson (>= 2.0.0)) - qcheck-core + (qcheck-core (>= 0.19)) ppx_deriving ppx_deriving_hash (ppx_deriving_yojson (>= 3.7.0)) @@ -41,9 +41,10 @@ json-data-encoding (jsonrpc (>= 1.12)) (sha (>= 1.12)) + (fileutils (>= 0.6.4)) cpu arg-complete - yaml + (yaml (>= 3.0.0)) uuidm catapult catapult-file diff --git a/goblint.opam b/goblint.opam index e2ca9a5ca8..bf51924626 100644 --- a/goblint.opam +++ b/goblint.opam @@ -21,11 +21,11 @@ bug-reports: "https://github.com/goblint/analyzer/issues" depends: [ "dune" {>= "3.6"} "ocaml" {>= "4.10"} - "goblint-cil" {>= "2.0.1"} - "batteries" {>= "3.4.0"} + "goblint-cil" {>= "2.0.2"} + "batteries" {>= "3.5.0"} "zarith" {>= "1.8"} "yojson" {>= "2.0.0"} - "qcheck-core" + "qcheck-core" {>= "0.19"} "ppx_deriving" "ppx_deriving_hash" "ppx_deriving_yojson" {>= "3.7.0"} @@ -38,9 +38,10 @@ depends: [ "json-data-encoding" "jsonrpc" {>= "1.12"} "sha" {>= "1.12"} + "fileutils" {>= "0.6.4"} "cpu" "arg-complete" - "yaml" + "yaml" {>= "3.0.0"} "uuidm" "catapult" "catapult-file" @@ -74,9 +75,10 @@ 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#c03ade107208546ef59cd14dcefa7b55f1506494" ] + [ "goblint-cil.2.0.2" "git+https://github.com/goblint/cil.git#c7ffc37ad83216a84d90fdbf427cc02a68ea5331" ] # 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#c852ebcc89e5cf4a5a3318e7c13c73e1756abb11"] +] +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 cb3e75b8a4..2744d2fe92 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"} @@ -56,21 +56,22 @@ depends: [ "dune-private-libs" {= "3.6.1"} "dune-site" {= "3.6.1"} "dyn" {= "3.6.1"} + "fileutils" {= "0.6.4"} "fmt" {= "0.9.0"} "fpath" {= "0.7.3"} - "goblint-cil" {= "2.0.1"} + "goblint-cil" {= "2.0.2"} "integers" {= "0.7.0"} "json-data-encoding" {= "0.12.1"} "jsonrpc" {= "1.15.0~5.0preview1"} "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} @@ -124,15 +125,14 @@ 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#c03ade107208546ef59cd14dcefa7b55f1506494" - ] - [ - "apron.v0.9.13" - "git+https://github.com/antoinemine/apron.git#c852ebcc89e5cf4a5a3318e7c13c73e1756abb11" + "goblint-cil.2.0.2" + "git+https://github.com/goblint/cil.git#c7ffc37ad83216a84d90fdbf427cc02a68ea5331" ] [ "ppx_deriving.5.2.1" diff --git a/goblint.opam.template b/goblint.opam.template index 7073aeeae7..d8e25cde38 100644 --- a/goblint.opam.template +++ b/goblint.opam.template @@ -2,9 +2,10 @@ # 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#c03ade107208546ef59cd14dcefa7b55f1506494" ] + [ "goblint-cil.2.0.2" "git+https://github.com/goblint/cil.git#c7ffc37ad83216a84d90fdbf427cc02a68ea5331" ] # 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#c852ebcc89e5cf4a5a3318e7c13c73e1756abb11"] +] +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 72dadf7172..42b07f8253 160000 --- a/gobview +++ b/gobview @@ -1 +1 @@ -Subproject commit 72dadf717245e68706ac0524e46ad6dc46b583a4 +Subproject commit 42b07f825316052ec030370daf0d00ebe28ec092 diff --git a/jsfh.yml b/jsfh.yml new file mode 100644 index 0000000000..430c1c8148 --- /dev/null +++ b/jsfh.yml @@ -0,0 +1,3 @@ +expand_buttons: true +collapse_long_descriptions: false +link_to_reused_ref: false diff --git a/lib/sv-comp/stub/src/sv-comp.c b/lib/sv-comp/stub/src/sv-comp.c index 230d66eae0..12c04125d6 100644 --- a/lib/sv-comp/stub/src/sv-comp.c +++ b/lib/sv-comp/stub/src/sv-comp.c @@ -13,7 +13,7 @@ void __VERIFIER_assume(int expression) { if (!expression) { LOOP: goto LOOP; }; // #define __VERIFIER_nondet(X) X __VERIFIER_nondet_##X() { X val; return val; } #define __VERIFIER_nondet2(X, Y) \ X __VERIFIER_nondet_##Y() __attribute__((goblint_stub)); \ - X __VERIFIER_nondet_##Y() { X val; return val; } + X __VERIFIER_nondet_##Y() { X val __attribute__((goblint_stub)); return val; } #define __VERIFIER_nondet(X) __VERIFIER_nondet2(X, X) __VERIFIER_nondet2(_Bool, bool) diff --git a/make.sh b/make.sh index 241cc35480..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() { @@ -23,6 +23,11 @@ rule() { dune build $TARGET.exe && rm -f goblint && cp _build/default/$TARGET.exe goblint + ;; coverage) + eval $(opam config env) + dune build --instrument-with bisect_ppx $TARGET.exe && + rm -f goblint && + cp _build/default/$TARGET.exe goblint ;; release) eval $(opam config env) dune build --profile=release $TARGET.exe && diff --git a/mkdocs.yml b/mkdocs.yml index 4263f892c1..428e28078d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -16,23 +16,26 @@ markdown_extensions: dev_addr: '127.0.0.1:8010' # different port from default python http.server for g2html nav: - - Home: index.md - - 'User guide': - - Installing: https://github.com/goblint/analyzer#installing - - user-guide/running.md - - user-guide/configuring.md - - user-guide/inspecting.md - - user-guide/annotating.md - - user-guide/benchmarking.md - - 'Developer guide': - - developer-guide/developing.md - - developer-guide/firstanalysis.md - - developer-guide/extending-library.md - - developer-guide/messaging.md - - developer-guide/testing.md - - developer-guide/debugging.md - - developer-guide/profiling.md - - developer-guide/documenting.md - - developer-guide/releasing.md - - 'Artifact descriptions': - - "SAS '21": artifact-descriptions/sas21.md + - 🏠 Home: index.md + - '👺 User guide': + - 🚚 Installing: https://github.com/goblint/analyzer#installing + - ▶️ Running: user-guide/running.md + - 🔧 Configuring: user-guide/configuring.md + - 🗂️ Options reference: jsfh/options.schema.html + - 🔍 Inspecting: user-guide/inspecting.md + - 📝 Annotating: user-guide/annotating.md + - 🐎 Benchmarking: user-guide/benchmarking.md + - '🚧 Developer guide': + - 🏗️ Developing: developer-guide/developing.md + - 👶 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/goblint/ + - 🚨 Testing: developer-guide/testing.md + - 🪲 Debugging: developer-guide/debugging.md + - 📉 Profiling: developer-guide/profiling.md + - 📚 Documenting: developer-guide/documenting.md + - 🚀 Releasing: developer-guide/releasing.md + - '📦 Artifact descriptions': + - "🇸 SAS '21": artifact-descriptions/sas21.md + - "🇪 ESOP '23": artifact-descriptions/esop23.md diff --git a/regtest.sh b/regtest.sh index e89278a551..488dd0bab4 100755 --- a/regtest.sh +++ b/regtest.sh @@ -14,7 +14,7 @@ if [[ $OSTYPE == 'darwin'* ]]; then grep="ggrep" fi params="`$grep -oP "PARAM: \K.*" $file`" -cmd="./goblint --enable dbg.debug --enable dbg.regression --html $params ${@:3} $file" # -v +cmd="./goblint --enable warn.debug --enable dbg.regression --html $params ${@:3} $file" # -v echo "$cmd" eval $cmd echo "See result/index.xml" diff --git a/scripts/bash-completion.sh b/scripts/bash-completion.sh index 28a86feb6e..cb518d4478 100644 --- a/scripts/bash-completion.sh +++ b/scripts/bash-completion.sh @@ -13,3 +13,48 @@ _goblint () } complete -o default -F _goblint goblint + + +_regtest () +{ + IFS=$'\n' + case $COMP_CWORD in + 1) + COMPREPLY=($(ls -1 tests/regression/ | sed -n -r 's/([0-9][0-9])-.*/\1/p' | grep "^${COMP_WORDS[1]}")) + ;; + 2) + COMPREPLY=($(ls -1 tests/regression/${COMP_WORDS[1]}-* | sed -n -r 's/([0-9][0-9])-.*/\1/p' | grep "^${COMP_WORDS[2]}")) + ;; + *) + COMPREPLY=($($(dirname ${COMP_WORDS[0]})/goblint --complete "${COMP_WORDS[@]:3:COMP_CWORD}")) + ;; + esac +} + +complete -o default -F _regtest regtest.sh + + +_update_suite () +{ + IFS=$'\n' + case $COMP_CWORD in + 1) + COMPREPLY=($(ls -1 tests/regression/*/*.c | sed -n -r 's|.*/([0-9][0-9])-(.*)\.c|\2|p' | grep "^${COMP_WORDS[1]}")) + if [[ "group" =~ ^${COMP_WORDS[1]} ]]; then + COMPREPLY+=("group") + fi + ;; + 2) + if [[ ${COMP_WORDS[1]} == "group" ]] ; then + COMPREPLY=($(ls -1 tests/regression/ | sed -n -r 's/([0-9][0-9])-(.*)/\2/p' | grep "^${COMP_WORDS[2]}")) + else + COMPREPLY=() + fi + ;; + *) + COMPREPLY=() + ;; + esac +} + +complete -F _update_suite update_suite.rb diff --git a/scripts/creduce/privPrecCompare.sh b/scripts/creduce/privPrecCompare.sh index 2608034d28..fdc5f9219d 100755 --- a/scripts/creduce/privPrecCompare.sh +++ b/scripts/creduce/privPrecCompare.sh @@ -22,7 +22,7 @@ for PRIV in "${PRIVS[@]}"; do PRIVDUMP="$OUTDIR/$PRIV" LOG="$OUTDIR/$PRIV.log" rm -f $PRIVDUMP - $GOBLINTDIR/goblint --sets exp.privatization $PRIV --sets exp.priv-prec-dump $PRIVDUMP $OPTS -v --enable dbg.debug &> $LOG + $GOBLINTDIR/goblint --sets exp.privatization $PRIV --sets exp.priv-prec-dump $PRIVDUMP $OPTS -v --enable warn.debug &> $LOG grep -F "Function definition missing" $LOG && exit 1 done diff --git a/scripts/goblint-lib-modules.py b/scripts/goblint-lib-modules.py new file mode 100755 index 0000000000..5f02271616 --- /dev/null +++ b/scripts/goblint-lib-modules.py @@ -0,0 +1,69 @@ +#!/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", + "Mainspec", + + # libraries + "Goblint_std", + "Goblint_timing", + "Goblint_backtrace", + "Goblint_sites", + "Goblint_build_info", + "Dune_build_info", + + "MessageCategory", # included in Messages + "PreValueDomain", # included in ValueDomain + "SpecCore", # spec stuff + "SpecUtil", # spec stuff + + "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/hooks/pre-commit b/scripts/hooks/pre-commit index 2d5efefa98..cb7cdb191f 100755 --- a/scripts/hooks/pre-commit +++ b/scripts/hooks/pre-commit @@ -71,7 +71,7 @@ for f in $(git diff --cached --name-only | grep -E ".*\.mli?$"); do lines="$a-$b" fi echo "ocp-indent file: $f, lines: $lines" - [[ $lines -eq "0" ]] || diff $f <(ocp-indent --lines=$lines $f) || fail="true" + [[ $lines -eq "0" ]] || diff $f <(ocp-indent --lines=$lines $f | sed 's/^[[:space:]]\+$//') || fail="true" done done if [ "$fail" == "true" ]; then diff --git a/scripts/spec/check.sh b/scripts/spec/check.sh index a69fac5007..57b63edfd2 100755 --- a/scripts/spec/check.sh +++ b/scripts/spec/check.sh @@ -12,7 +12,7 @@ else ana="spec" opt="--set ana.spec.file $spec" fi -cmd="./goblint --set ana.activated[0][+] $ana $opt --html --set dbg.debug $debug $file" +cmd="./goblint --set ana.activated[0][+] $ana $opt --html --set warn.debug $debug $file" echo -e "$(tput setaf 6)$cmd$(tput sgr 0)" $cmd diff --git a/scripts/test-gobview.py b/scripts/test-gobview.py new file mode 100644 index 0000000000..1ac8f6a76c --- /dev/null +++ b/scripts/test-gobview.py @@ -0,0 +1,68 @@ +# needs preinstalled libraries: +# pip3 install selenium webdriver-manager + +from selenium import webdriver +from selenium.webdriver.chrome.service import Service +from webdriver_manager.chrome import ChromeDriverManager +from selenium.webdriver.common.by import By +from selenium.webdriver.chrome.options import Options +from threading import Thread +import subprocess + +PORT = 8080 # has to match port defined in goblint_http.ml +DIRECTORY = "run" +IP = "localhost" +url = 'http://' + IP + ':' + str(PORT) + '/' + +# cleanup +def cleanup(browser, thread): + print("cleanup") + browser.close() + p.kill() + thread.join() + +# serve GobView in different thread so it does not block the testing +def serve(): + global p + goblint_http_path = '_build/default/gobview/goblint-http-server/goblint_http.exe' + p = subprocess.Popen(['./' + goblint_http_path, + '-with-goblint', '../analyzer/goblint', + '-goblint', '--set', 'files[+]', '"../analyzer/tests/regression/00-sanity/01-assert.c"']) + +print("serving at port", PORT) +thread = Thread(target=serve, args=()) +thread.start() + +# installation of browser +print("starting installation of browser\n") +options = Options() +options.add_argument('headless') +browser = webdriver.Chrome(service=Service(ChromeDriverManager().install()),options=options) +print("finished webdriver installation \n") +browser.maximize_window() +browser.implicitly_wait(10); + +try: + # retrieve and wait until page is fully loaded and rendered + browser.get(url) + print("open local GobView page\n") + + # check for the right page title: + title = browser.title + assert(title == "GobView") + print("found the site's title", title) + + # check the general structure of the page (whether main element, navbar, left and right sidebar, content view and panel exists) + # find_element throws an NoSuchElementException if this is not the case + main = browser.find_element(By.CLASS_NAME, "main") + leftS = browser.find_element(By.CLASS_NAME, "sidebar-left") + rightS = browser.find_element(By.CLASS_NAME, "sidebar-right") + content = browser.find_element(By.CLASS_NAME, "content") + panel = browser.find_element(By.CLASS_NAME, "panel") + print("found DOM elements main, sidebar-left, sidebar-right, content and panel") + + cleanup(browser, thread) + +except Exception as e: + cleanup(browser, thread) + raise e diff --git a/scripts/test-incremental-multiple.sh b/scripts/test-incremental-multiple.sh index 7afdadf6a0..8b56b2f6c5 100644 --- a/scripts/test-incremental-multiple.sh +++ b/scripts/test-incremental-multiple.sh @@ -7,7 +7,7 @@ conf=$base/$test.json patch1=$base/${test}_1.patch patch2=$base/${test}_2.patch -args="--enable dbg.debug --enable dbg.timing.enabled -v" +args="--enable warn.debug --enable dbg.timing.enabled -v" cat $source diff --git a/scripts/test-incremental.sh b/scripts/test-incremental.sh index 5047390718..ae5022d1bd 100755 --- a/scripts/test-incremental.sh +++ b/scripts/test-incremental.sh @@ -11,7 +11,7 @@ source=$base/$test.c conf=$base/$test.json patch=$base/$test.patch -args="--enable dbg.debug --enable dbg.timing.enabled -v --enable allglobs" +args="--enable warn.debug --enable dbg.timing.enabled -v --enable allglobs" ./goblint --conf $conf $args --enable incremental.save $source &> $base/$test.before.log diff --git a/scripts/update_suite.rb b/scripts/update_suite.rb index 5e65bb8c6c..ca408a513a 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 @@ -139,10 +139,8 @@ def report end def collect_warnings - warnings[-1] = "term" lines = IO.readlines(warnfile, :encoding => "UTF-8") lines.each do |l| - if l =~ /Function 'main' does not return/ then warnings[-1] = "noterm" end if l =~ /vars = (\d*).*evals = (\d+)/ then @vars = $1 @evals = $2 @@ -150,7 +148,7 @@ def collect_warnings next unless l =~ /(.*)\(.*?\:(\d+)(?:\:\d+)?(?:-(?:\d+)(?:\:\d+)?)?\)/ obj,i = $1,$2.to_i - ranking = ["other", "warn", "race", "norace", "deadlock", "nodeadlock", "success", "fail", "unknown", "term", "noterm"] + ranking = ["other", "warn", "race", "norace", "deadlock", "nodeadlock", "success", "fail", "unknown"] thiswarn = case obj when /\(conf\. \d+\)/ then "race" when /Deadlock/ then "deadlock" @@ -195,7 +193,7 @@ def compare_warnings end } case type - when "deadlock", "race", "fail", "noterm", "unknown", "term", "warn" + when "deadlock", "race", "fail", "unknown", "warn" check.call warnings[idx] == type when "nowarn" check.call warnings[idx].nil? @@ -308,12 +306,6 @@ def parse_tests (lines) end end end - case lines[0] - when /NON?TERM/ - tests[-1] = "noterm" - when /TERM/ - tests[-1] = "term" - end Tests.new(self, tests, tests_line, todo) end @@ -496,8 +488,8 @@ def create_test_set(lines) end def run () filename = File.basename(@path) - cmd1 = "#{$goblint} #{filename} #{@params} #{ENV['gobopt']} 1>#{@testset.warnfile}0 --enable dbg.debug --set dbg.timing.enabled true --enable witness.yaml.enabled --set goblint-dir .goblint-#{@id.sub('/','-')}-witness1 2>#{@testset.statsfile}0" - cmd2 = "#{$goblint} #{filename} #{@params} #{ENV['gobopt']} 1>#{@testset.warnfile} --set ana.activated[+] unassume --enable dbg.debug --set dbg.timing.enabled true --set witness.yaml.unassume witness.yml --set goblint-dir .goblint-#{@id.sub('/','-')}-witness2 2>#{@testset.statsfile}" + cmd1 = "#{$goblint} #{filename} #{@params} #{ENV['gobopt']} 1>#{@testset.warnfile}0 --enable warn.debug --set dbg.timing.enabled true --enable witness.yaml.enabled --set goblint-dir .goblint-#{@id.sub('/','-')}-witness1 2>#{@testset.statsfile}0" + cmd2 = "#{$goblint} #{filename} #{@params} #{ENV['gobopt']} 1>#{@testset.warnfile} --set ana.activated[+] unassume --enable warn.debug --set dbg.timing.enabled true --set witness.yaml.unassume witness.yml --set goblint-dir .goblint-#{@id.sub('/','-')}-witness2 2>#{@testset.statsfile}" starttime = Time.now run_testset(@testset, cmd1, starttime) starttime = Time.now @@ -544,7 +536,7 @@ def run () if $1 then params = $1 else params = "" end end # always enable debugging so that the warnings would work - params << " --set dbg.debug true" + params << " --set warn.debug true" p = if incremental then patch = f[0..-3] + ".patch" patch_path = File.expand_path(patch, grouppath) diff --git a/src/analyses/abortUnless.ml b/src/analyses/abortUnless.ml index bc135d516b..ee4db69820 100644 --- a/src/analyses/abortUnless.ml +++ b/src/analyses/abortUnless.ml @@ -1,6 +1,8 @@ -(** An analysis checking whether a function only returns if its only argument has a non-zero value. *) +(** Analysis of [assume_abort_if_not]-style functions ([abortUnless]). -open Prelude.Ana + Such a function only returns if its only argument has a non-zero value. *) + +open GoblintCil open Analyses module Spec = @@ -46,9 +48,9 @@ struct in [false, candidate] - let combine ctx (lval:lval option) fexp (f:fundec) (args:exp list) fc (au:D.t) : D.t = - if au && lval = None then ( - (* Assert happens after evaluation of call, so if variables in `arg` are assigned to, asserting might unsoundly yield bot *) + let combine_env ctx lval fexp f args fc au f_ask = + if au then ( + (* Assert before combine_assign, so if variables in `arg` are assigned to, asserting doesn't unsoundly yield bot *) (* See test 62/03 *) match args with | [arg] -> ctx.emit (Events.Assert arg) @@ -56,12 +58,15 @@ struct ); false + let combine_assign ctx (lval:lval option) fexp (f:fundec) (args:exp list) fc (au:D.t) (f_ask: Queries.ask) : D.t = + false + let special ctx (lval: lval option) (f:varinfo) (arglist:exp list) : D.t = 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 3f7448a83b..b181a1c70e 100644 --- a/src/analyses/accessAnalysis.ml +++ b/src/analyses/accessAnalysis.ml @@ -1,7 +1,7 @@ -(** Access analysis. *) +(** Analysis of memory accesses ([access]). *) module LF = LibraryFunctions -open Prelude.Ana +open GoblintCil open Analyses open GobConfig @@ -24,41 +24,50 @@ struct module G = AccessDomain.EventSet let collect_local = ref false + let emit_single_threaded = ref false let init _ = - collect_local := get_bool "witness.yaml.enabled" && get_bool "witness.invariant.accessed" + 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 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. + [deref=true], [reach=false] - Access [exp] by dereferencing once (may-point-to), used for lval writes and shallow special accesses. + [deref=true], [reach=true] - Access [exp] by dereferencing transitively (reachable), used for deep special accesses. *) 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 %b %a:\n" AccessKind.pretty kind reach d_exp exp; - if force || !collect_local || ThreadFlag.is_multi (Analyses.ask_of_ctx ctx) then ( - if deref then + 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 && 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; - Access.distribute_access_exp (do_access ctx Read false) exp + if M.tracing then M.tracei "access" "distribute_access_exp\n"; + Access.distribute_access_exp (do_access ctx Read false) exp; + if M.tracing then M.traceu "access" "distribute_access_exp\n"; ); - if M.tracing then M.traceu "access" "access_one_top %a %b %a\n" AccessKind.pretty kind reach d_exp exp + if M.tracing then M.traceu "access" "access_one_top\n" (** 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 = () (** Transfer functions: *) + let vdecl ctx v = + access_one_top ctx Read false (SizeOf v.vtype); + ctx.local + let assign ctx lval rval : D.t = (* ignore global inits *) - if !GU.global_initialization then ctx.local else begin + if !AnalysisState.global_initialization then ctx.local else begin access_one_top ~deref:true ctx Write false (AddrOf lval); access_one_top ctx Read false rval; ctx.local @@ -98,17 +107,21 @@ struct let enter ctx lv f args : (D.t * D.t) list = [(ctx.local,ctx.local)] - let combine ctx lv fexp f args fc al = + let combine_env ctx lval fexp f args fc au f_ask = + (* These should be in enter, but enter cannot emit events, nor has fexp argument *) access_one_top ctx Read false fexp; + List.iter (access_one_top ctx Read false) args; + au + + let combine_assign ctx lv fexp f args fc al f_ask = begin match lv with | None -> () | Some lval -> access_one_top ~deref:true ctx Write false (AddrOf lval) end; - List.iter (access_one_top ctx Read false) args; - al + 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 -> () @@ -124,25 +137,20 @@ struct let event ctx e octx = match e with - | Events.Access {lvals; kind; _} when !collect_local && !Goblintutil.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 = Lval.CilLval.to_ciloffs 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 new file mode 100644 index 0000000000..9baa601ddc --- /dev/null +++ b/src/analyses/activeLongjmp.ml @@ -0,0 +1,41 @@ +(** Analysis of active [longjmp] targets ([activeLongjmp]). *) + +open GoblintCil +open Analyses + +module Spec = +struct + include Analyses.IdentitySpec + + let name () = "activeLongjmp" + + (* The first component are the longjmp targets, the second are the longjmp callers *) + module D = JmpBufDomain.ActiveLongjmps + module C = Lattice.Unit + + let context _ _ = () + + let special ctx (lval: lval option) (f:varinfo) (arglist:exp list) : D.t = + let desc = LibraryFunctions.find f in + match desc.special arglist, f.vname with + | Longjmp {env; value}, _ -> + (* Set target to current value of env *) + let bufs = ctx.ask (EvalJumpBuf env) in + bufs, JmpBufDomain.NodeSet.singleton(ctx.prev_node) + | _ -> ctx.local + + (* Initial values don't really matter: overwritten at longjmp call. *) + let startstate v = 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 = + match q with + | ActiveJumpBuf -> + (* Does not compile without annotation: "This instance (...) is ambiguous: it would escape the scope of its equation" *) + (ctx.local:JmpBufDomain.ActiveLongjmps.t) + | _ -> Queries.Result.top q +end + +let _ = + MCP.register_analysis (module Spec : MCPSpec) diff --git a/src/analyses/activeSetjmp.ml b/src/analyses/activeSetjmp.ml new file mode 100644 index 0000000000..be13489993 --- /dev/null +++ b/src/analyses/activeSetjmp.ml @@ -0,0 +1,38 @@ +(** Analysis of active [setjmp] buffers ([activeSetjmp]). *) + +open GoblintCil +open Analyses + +module Spec = +struct + include Analyses.IdentitySpec + + let name () = "activeSetjmp" + + module D = JmpBufDomain.JmpBufSet + module C = JmpBufDomain.JmpBufSet + module P = IdentityP (D) + + let combine_env ctx (lval:lval option) fexp (f:fundec) (args:exp list) fc (au:D.t) (f_ask:Queries.ask): D.t = + ctx.local (* keep local as opposed to IdentitySpec *) + + let special ctx (lval: lval option) (f:varinfo) (arglist:exp list) : D.t = + let desc = LibraryFunctions.find f in + match desc.special arglist with + | Setjmp _ -> + let entry = (ctx.prev_node, ctx.control_context ()) in + D.add (Target entry) ctx.local + | _ -> ctx.local + + let startstate v = 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 = + match q with + | ValidLongJmp -> (ctx.local: D.t) + | _ -> Queries.Result.top q +end + +let _ = + MCP.register_analysis (module Spec : MCPSpec) diff --git a/src/analyses/apron/affineEqualityAnalysis.apron.ml b/src/analyses/apron/affineEqualityAnalysis.apron.ml index fe59209ca6..03a9ecdb57 100644 --- a/src/analyses/apron/affineEqualityAnalysis.apron.ml +++ b/src/analyses/apron/affineEqualityAnalysis.apron.ml @@ -1,5 +1,7 @@ -(* Ref: Affine Relationships Among Variables of a Program, Michael Karr 1976 - https://link.springer.com/content/pdf/10.1007/BF00268497.pdf *) +(** {{!RelationAnalysis} Relational integer value analysis} using an OCaml implementation of the affine equalities domain ([affeq]). + + @see Karr, M. Affine relationships among variables of a program. *) + open Analyses include RelationAnalysis diff --git a/src/analyses/apron/apronAnalysis.apron.ml b/src/analyses/apron/apronAnalysis.apron.ml index f3a2374bc1..29e295a662 100644 --- a/src/analyses/apron/apronAnalysis.apron.ml +++ b/src/analyses/apron/apronAnalysis.apron.ml @@ -1,4 +1,5 @@ -(** Analysis using Apron for integer variables. *) +(** {{!RelationAnalysis} Relational integer value analysis} using {!Apron} domains ([apron]). *) + open Analyses include RelationAnalysis diff --git a/src/analyses/apron/relationAnalysis.apron.ml b/src/analyses/apron/relationAnalysis.apron.ml index dc19be913b..13f549fc44 100644 --- a/src/analyses/apron/relationAnalysis.apron.ml +++ b/src/analyses/apron/relationAnalysis.apron.ml @@ -1,10 +1,17 @@ +(** Abstract relational {e integer} value analysis. + + See {!ApronAnalysis} and {!AffineEqualityAnalysis}. *) + (** Contains most of the implementation of the original apronDomain, but now solely operates with functions provided by relationDomain. *) -open Prelude.Ana +open Batteries +open GoblintCil +open Pretty 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 @@ -19,6 +26,12 @@ struct include Priv.V include StdV end + module P = + struct + include Priv.P + + let of_elt {priv; _} = of_elt priv + end module RV = RD.V @@ -27,8 +40,6 @@ struct (* Result map used for comparison of results for relational traces paper. *) let results = PCU.RH.create 103 - let should_join = Priv.should_join - let context fd x = if ContextUtil.should_keep ~isAttr:GobContext ~keepOption:"ana.relation.context" ~removeAttr:"relation.no-context" ~keepAttr:"relation.context" fd then x @@ -41,7 +52,7 @@ struct (* Functions for manipulating globals as temporary locals. *) let read_global ask getg st g x = - if ThreadFlag.is_multi ask then + if ThreadFlag.has_ever_been_multi ask then Priv.read_global ask getg st g x else ( let rel = st.rel in @@ -64,7 +75,7 @@ struct if VH.mem v_ins v then VH.find v_ins v else - let v_in = Goblintutil.create_var @@ makeVarinfo false (v.vname ^ "#in") v.vtype in (* temporary local g#in for global g *) + let v_in = Cilfacade.create_var @@ makeVarinfo false (v.vname ^ "#in") v.vtype in (* temporary local g#in for global g *) VH.replace v_ins v v_in; v_in in @@ -76,7 +87,7 @@ struct let e' = visitCilExpr visitor e in let rel = RD.add_vars st.rel (List.map RV.local (VH.values v_ins |> List.of_enum)) in (* add temporary g#in-s *) let rel' = VH.fold (fun v v_in rel -> - if M.tracing then M.trace "relation" "read_global %a %a\n" d_varinfo v d_varinfo v_in; + if M.tracing then M.trace "relation" "read_global %a %a\n" CilType.Varinfo.pretty v CilType.Varinfo.pretty v_in; read_global ask getg {st with rel} v v_in (* g#in = g; *) ) v_ins rel in @@ -85,7 +96,7 @@ struct let read_globals_to_locals_inv (ask: Queries.ask) getg st vs = let v_ins_inv = VH.create (List.length vs) in List.iter (fun v -> - let v_in = Goblintutil.create_var @@ makeVarinfo false (v.vname ^ "#in") v.vtype in (* temporary local g#in for global g *) + let v_in = Cilfacade.create_var @@ makeVarinfo false (v.vname ^ "#in") v.vtype in (* temporary local g#in for global g *) VH.replace v_ins_inv v_in v; ) vs; let rel = RD.add_vars st.rel (List.map RV.local (VH.keys v_ins_inv |> List.of_enum)) in (* add temporary g#in-s *) @@ -116,7 +127,7 @@ struct rel'' let write_global ask getg sideg st g x = - if ThreadFlag.is_multi ask then + if ThreadFlag.has_ever_been_multi ask then Priv.write_global ask getg sideg st g x else ( let rel = st.rel in @@ -137,25 +148,23 @@ struct {st with rel = f st v} ) else ( - let v_out = Goblintutil.create_var @@ makeVarinfo false (v.vname ^ "#out") v.vtype in (* temporary local g#out for global g *) + let v_out = Cilfacade.create_var @@ makeVarinfo false (v.vname ^ "#out") v.vtype in (* temporary local g#out for global g *) v_out.vattr <- v.vattr; (*copy the attributes because the tracking may depend on them. Otherwise an assertion fails *) let st = {st with rel = RD.add_vars st.rel [RV.local v_out]} in (* add temporary g#out *) let st' = {st with rel = f st v_out} in (* g#out = e; *) - if M.tracing then M.trace "relation" "write_global %a %a\n" d_varinfo v d_varinfo v_out; + if M.tracing then M.trace "relation" "write_global %a %a\n" CilType.Varinfo.pretty v CilType.Varinfo.pretty v_out; let st' = write_global ask getg sideg st' v v_out in (* g = g#out; *) let rel'' = RD.remove_vars st'.rel [RV.local v_out] in (* remove temporary g#out *) {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 (Lval.CilLval.to_lval 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 @@ -190,8 +199,8 @@ struct | ik when not (IntDomain.should_ignore_overflow ik) -> (* don't add type bounds for signed when assume_none *) 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 (Cilint.cilint_of_big_int type_max)), intType) in - let e2 = BinOp (Ge, Lval (Cil.var x), (Cil.kintegerCilint ik (Cilint.cilint_of_big_int type_min)), intType) in + let e1 = BinOp (Le, Lval (Cil.var x), (Cil.kintegerCilint ik type_max), intType) in + let e2 = BinOp (Ge, Lval (Cil.var x), (Cil.kintegerCilint ik type_min), intType) in 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 @@ -207,13 +216,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 -> - let lval = Lval.CilLval.to_lval (Queries.LS.choose a) in - Lval lval - (* 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 @@ -222,7 +234,7 @@ struct let assign ctx (lv:lval) e = let st = ctx.local in - if !GU.global_initialization && e = MyCFG.unknown_exp then + if !AnalysisState.global_initialization && e = MyCFG.unknown_exp then st (* ignore extern inits because there's no body before assign, so env is empty... *) else ( let simplified_e = replace_deref_exps ctx.ask e in @@ -230,7 +242,7 @@ struct let ask = Analyses.ask_of_ctx ctx in let r = assign_to_global_wrapper ask ctx.global ctx.sideg st lv (fun st v -> assign_from_globals_wrapper ask ctx.global st simplified_e (fun apr' e' -> - if M.tracing then M.traceli "relation" "assign inner %a = %a (%a)\n" d_varinfo v d_exp e' d_plainexp e'; + if M.tracing then M.traceli "relation" "assign inner %a = %a (%a)\n" CilType.Varinfo.pretty v d_exp e' d_plainexp e'; if M.tracing then M.trace "relation" "st: %a\n" RD.pretty apr'; let r = RD.assign_exp apr' (RV.local v) e' (no_overflow ask simplified_e) in if M.tracing then M.traceu "relation" "-> %a\n" RD.pretty r; @@ -259,7 +271,15 @@ 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 *) @@ -271,29 +291,30 @@ struct | None -> true | Some v -> any_local_reachable - let enter ctx r f args = + let make_callee_rel ~thread ctx f args = let fundec = Node.find_fundec ctx.node in let st = ctx.local in - if M.tracing then M.tracel "combine" "relation enter f: %a\n" d_varinfo f.svar; - if M.tracing then M.tracel "combine" "relation enter formals: %a\n" (d_list "," d_varinfo) f.sformals; - if M.tracing then M.tracel "combine" "relation enter local: %a\n" D.pretty ctx.local; 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) 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 *) *) (* TODO: parallel version of assign_from_globals_wrapper? *) - let ask = Analyses.ask_of_ctx ctx in - let new_rel = List.fold_left (fun new_rel (var, e) -> - assign_from_globals_wrapper ask ctx.global {st with rel = new_rel} e (fun rel' e' -> - RD.assign_exp rel' var e' (no_overflow ask e) - ) - ) new_rel arg_assigns + let new_rel = + if thread then + new_rel + else + let ask = Analyses.ask_of_ctx ctx in + List.fold_left (fun new_rel (var, e) -> + assign_from_globals_wrapper ask ctx.global {st with rel = new_rel} e (fun rel' e' -> + RD.assign_exp rel' var e' (no_overflow ask e) + ) + ) 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 @@ -302,7 +323,11 @@ struct | _ -> 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; - [st, {st with rel = new_rel}] + new_rel + + let enter ctx r f args = + let calle_rel = make_callee_rel ~thread:false ctx f args in + [ctx.local, {ctx.local with rel = calle_rel}] let body ctx f = let st = ctx.local in @@ -350,18 +375,22 @@ struct st' end - let combine ctx r fe f args fc fun_st = + 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" d_varinfo f.svar; - if M.tracing then M.tracel "combine" "relation formals: %a\n" (d_list "," d_varinfo) f.sformals; + 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 *) *) @@ -378,16 +407,23 @@ struct 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; 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 let new_rel = RD.keep_filter st.rel (fun var -> match RV.find_metadata var with | Some (Local _) when not (pass_to_callee fundec any_local_reachable var) -> true (* keep caller locals, provided they were not passed to the function *) | Some (Arg _) -> true (* keep caller args *) + | Some ((Local _ | Global _)) when not (RD.mem_var new_fun_rel var) -> false (* remove locals and globals, for which no record exists in the new_fun_apr *) + | Some ((Local v | Global v)) when not (TaintPartialContexts.VS.mem v tainted_vars) -> true (* keep locals and globals, which have not been touched by the call *) | _ -> false (* remove everything else (globals, global privs, reachable things from the caller) *) ) in let unify_rel = RD.unify new_rel new_fun_rel in (* TODO: unify_with *) if M.tracing then M.tracel "combine" "relation unifying %a %a = %a\n" RD.pretty new_rel RD.pretty new_fun_rel RD.pretty unify_rel; - let unify_st = {fun_st with rel = unify_rel} in + {fun_st with rel = unify_rel} + + let combine_assign ctx r fe f args fc fun_st (f_ask : Queries.ask) = + let unify_st = ctx.local in if RD.Tracked.type_tracked (Cilfacade.fundec_return_type f) then ( let unify_st' = match r with | Some lv -> @@ -417,13 +453,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 = @@ -435,9 +471,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 Lval.CilLval.to_lval + | 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 @@ -486,12 +526,19 @@ struct | Unknown, "__goblint_assume_join" -> let id = List.hd args in Priv.thread_join ~force:true ask ctx.global id st + | Rand, _ -> + (match r with + | Some lv -> + let st = invalidate_one ask ctx st lv in + assert_fn {ctx with local = st} (BinOp (Ge, Lval lv, zero, intType)) true + | None -> st) | _, _ -> let lvallist e = - let s = ask.f (Queries.MayPointTo e) in - match s with - | `Top -> [] - | `Lifted _ -> List.map (Lval.CilLval.to_lval) (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 @@ -527,7 +574,7 @@ struct let scope = Node.find_fundec ctx.node in let (apr, e_inv) = - if ThreadFlag.is_multi ask then ( + if ThreadFlag.has_ever_been_multi ask then ( let priv_vars = if keep_global then Priv.invariant_vars ask ctx.global ctx.local @@ -600,7 +647,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 -> @@ -608,30 +655,25 @@ struct Otherwise thread is analyzed with no global inits, reading globals gives bot, which turns into top, which might get published... sync `Thread doesn't help us here, it's not specific to entering multithreaded mode. EnterMultithreaded events only execute after threadenter and threadspawn. *) - if not (ThreadFlag.is_multi (Analyses.ask_of_ctx ctx)) then + if not (ThreadFlag.has_ever_been_multi (Analyses.ask_of_ctx ctx)) then ignore (Priv.enter_multithreaded (Analyses.ask_of_ctx ctx) ctx.global ctx.sideg st); let st' = Priv.threadenter (Analyses.ask_of_ctx ctx) ctx.global st in - let arg_vars = - fd.sformals - |> List.filter RD.Tracked.varinfo_tracked - |> List.map RV.arg - in - let new_rel = RD.add_vars st'.rel arg_vars in + let new_rel = make_callee_rel ~thread:true ctx fd args in [{st' with rel = new_rel}] | exception Not_found -> (* Unknown functions *) (* 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 = let st = ctx.local in match e with - | Events.Lock (addr, _) when ThreadFlag.is_multi (Analyses.ask_of_ctx ctx) -> (* TODO: is this condition sound? *) + | Events.Lock (addr, _) when ThreadFlag.has_ever_been_multi (Analyses.ask_of_ctx ctx) -> (* TODO: is this condition sound? *) Priv.lock (Analyses.ask_of_ctx ctx) ctx.global st addr - | Events.Unlock addr when ThreadFlag.is_multi (Analyses.ask_of_ctx ctx) -> (* TODO: is this condition sound? *) + | Events.Unlock addr when ThreadFlag.has_ever_been_multi (Analyses.ask_of_ctx ctx) -> (* TODO: is this condition sound? *) if addr = UnknownPtr then M.info ~category:Unsound "Unknown mutex unlocked, relation privatization unsound"; (* TODO: something more sound *) WideningTokens.with_local_side_tokens (fun () -> @@ -674,7 +716,7 @@ struct let sync ctx reason = (* After the solver is finished, store the results (for later comparison) *) - if !GU.postsolving then begin + if !AnalysisState.postsolving then begin let keep_local = GobConfig.get_bool "ana.relation.invariant.local" in let keep_global = GobConfig.get_bool "ana.relation.invariant.global" in diff --git a/src/analyses/apron/relationPriv.apron.ml b/src/analyses/apron/relationPriv.apron.ml index dc096cb4e0..b386af162b 100644 --- a/src/analyses/apron/relationPriv.apron.ml +++ b/src/analyses/apron/relationPriv.apron.ml @@ -1,6 +1,9 @@ -(** Has been modified to work with any domain that uses the functions provided relationDomain. *) +(** Relational thread-modular value analyses for {!RelationAnalysis}, i.e. {!ApronAnalysis} and {!AffineEqualityAnalysis}. -open Prelude.Ana + @see Schwarz, M., Saan, S., Seidl, H., Erhard, J., Vojdani, V. Clustered Relational Thread-Modular Abstract Interpretation with Local Traces. *) + +open Batteries +open GoblintCil open Analyses open RelationDomain open GobConfig @@ -18,10 +21,11 @@ module type S = module D: Lattice.S module G: Lattice.S module V: Printable.S + module P: DisjointDomain.Representative with type elt := D.t (** Path-representative. *) + type relation_components_t := RelationDomain.RelComponents (RD) (D).t val name: unit -> string val startstate: unit -> D.t - val should_join: relation_components_t -> relation_components_t -> bool val read_global: Q.ask -> (V.t -> G.t) -> relation_components_t -> varinfo -> varinfo -> RD.t @@ -57,12 +61,12 @@ struct module G = Lattice.Unit module V = EmptyV module AV = RD.V + module P = UnitP type relation_components_t = RelComponents (RD) (D).t let name () = "top" let startstate () = () - let should_join _ _ = true let read_global ask getg (st: relation_components_t) g x = let rel = st.rel in @@ -94,7 +98,7 @@ struct let sync (ask: Q.ask) getg sideg (st: relation_components_t) reason = match reason with | `Join -> - if (ask.f Q.MustBeSingleThreaded) then + if ask.f (Q.MustBeSingleThreaded {since_start = true}) then st else (* must be like enter_multithreaded *) @@ -145,10 +149,12 @@ struct open Protection (** Locally must-written protected globals that have been continuously protected since writing. *) - module P = - struct - include MustVars - let name () = "P" + open struct + module P = + struct + include MustVars + let name () = "P" + end end (** Locally may-written protected globals that have been continuously protected since writing. *) @@ -162,6 +168,16 @@ struct module D = Lattice.Prod (P) (W) module G = RD module V = Printable.UnitConf (struct let name = "global" end) + module PS = + struct + include Printable.Option (P) (struct let name = "None" end) + + let of_elt (p, _) = + if Param.path_sensitive then + Some p + else + None + end type relation_components_t = RelationComponents (RD) (D).t @@ -210,15 +226,6 @@ struct let startstate () = (P.empty (), W.empty ()) - let should_join (st1: relation_components_t) (st2: relation_components_t) = - if Param.path_sensitive then ( - let (p1, _) = st1.priv in - let (p2, _) = st2.priv in - P.equal p1 p2 - ) - else - true - let read_global ask getg (st: relation_components_t) g x = let rel = st.rel in let (p, w) = st.priv in @@ -341,7 +348,7 @@ struct st end | `Join -> - if (ask.f Q.MustBeSingleThreaded) then + if (ask.f (Q.MustBeSingleThreaded { since_start= true })) then st else (* must be like enter_multithreaded *) @@ -407,6 +414,8 @@ struct let invariant_vars ask getg st = protected_vars ask (* TODO: is this right? *) let finalize () = () + + module P = PS end module CommonPerMutex = functor(RD: RelationDomain.RD) -> @@ -451,6 +460,7 @@ struct module D = Lattice.Unit module G = RD + module P = UnitP type relation_components_t = RelationDomain.RelComponents (RD) (D).t @@ -460,8 +470,6 @@ struct let startstate () = () - let should_join _ _ = true - let get_m_with_mutex_inits ask getg m = let get_m = getg (V.mutex m) in let get_mutex_inits = getg V.mutex_inits in @@ -547,7 +555,7 @@ struct st end | `Join -> - if (ask.f Q.MustBeSingleThreaded) then + if (ask.f (Q.MustBeSingleThreaded {since_start = true})) then st else let rel = st.rel in @@ -705,11 +713,7 @@ module DownwardClosedCluster (ClusteringArg: ClusteringArg) = functor (RD: Rela struct open CommonPerMutex(RD) - module VS = - struct - include Printable.Std - include SetDomain.Make (CilType.Varinfo) - end + module VS = SetDomain.Make (CilType.Varinfo) module LRD = MapDomain.MapBot (VS) (RD) let keep_only_protected_globals ask m octs = @@ -856,6 +860,7 @@ struct end)(LRD) module AV = RD.V + module P = UnitP let name () = "PerMutexMeetPrivTID(" ^ (Cluster.name ()) ^ (if GobConfig.get_bool "ana.relation.priv.must-joined" then ",join" else "") ^ ")" @@ -871,8 +876,6 @@ struct type relation_components_t = RelationDomain.RelComponents (RD) (D).t - let should_join _ _ = true - let get_m_with_mutex_inits inits ask getg m = let get_m = get_relevant_writes ask m (G.mutex @@ getg (V.mutex m)) in if M.tracing then M.traceli "relationpriv" "get_m_with_mutex_inits %a\n get=%a\n" LockDomain.Addr.pretty m LRD.pretty get_m; @@ -1030,7 +1033,7 @@ struct match reason with | `Return -> st (* TODO: implement? *) | `Join -> - if (ask.f Q.MustBeSingleThreaded) then + if (ask.f (Q.MustBeSingleThreaded {since_start = true})) then st else let rel = st.rel in @@ -1096,7 +1099,7 @@ struct module RelComponents = RelationDomain.RelComponents (RD) (D) let read_global ask getg st g x = - if M.tracing then M.traceli "relationpriv" "read_global %a %a\n" d_varinfo g d_varinfo x; + if M.tracing then M.traceli "relationpriv" "read_global %a %a\n" CilType.Varinfo.pretty g CilType.Varinfo.pretty x; if M.tracing then M.trace "relationpriv" "st: %a\n" RelComponents.pretty st; let getg x = let r = getg x in @@ -1108,7 +1111,7 @@ struct r let write_global ?invariant ask getg sideg st g x = - if M.tracing then M.traceli "relationpriv" "write_global %a %a\n" d_varinfo g d_varinfo x; + if M.tracing then M.traceli "relationpriv" "write_global %a %a\n" CilType.Varinfo.pretty g CilType.Varinfo.pretty x; if M.tracing then M.trace "relationpriv" "st: %a\n" RelComponents.pretty st; let getg x = let r = getg x in diff --git a/src/analyses/assert.ml b/src/analyses/assert.ml index ccdf4abaef..8247a0d7e8 100644 --- a/src/analyses/assert.ml +++ b/src/analyses/assert.ml @@ -1,34 +1,17 @@ -open Prelude.Ana +(** Analysis of [assert] results ([assert]). *) + +open Batteries +open GoblintCil open Analyses open GobConfig module Spec : Analyses.MCPSpec = struct - include Analyses.DefaultSpec + include UnitAnalysis.Spec let name () = "assert" - module D = Lattice.Unit - module G = Lattice.Unit - module C = Lattice.Unit (* transfer functions *) - let assign ctx (lval:lval) (rval:exp) : D.t = - ctx.local - - let branch ctx (exp:exp) (tv:bool) : D.t = - ctx.local - - let body ctx (f:fundec) : D.t = - ctx.local - - let return ctx (exp:exp option) (f:fundec) : D.t = - ctx.local - - let enter ctx (lval: lval option) (fd:fundec) (args:exp list) : (D.t * D.t) list = - [ctx.local, ctx.local] - - let combine ctx (lval:lval option) fexp (fd:fundec) (args:exp list) fc (au:D.t) : D.t = - au let assert_fn ctx e check refine = @@ -40,7 +23,7 @@ struct | Some b -> `Lifted b | None -> `Top in - let expr = sprint d_exp e in + let expr = CilType.Exp.show e in let warn warn_fn ?annot msg = if check then if get_bool "dbg.regression" then ( (* This only prints unexpected results (with the difference) as indicated by the comment behind the assert (same as used by the regression test script). *) let loc = !M.current_loc in @@ -77,11 +60,6 @@ struct match desc.special args, f.vname with | Assert { exp; check; refine }, _ -> assert_fn ctx exp check refine | _, _ -> 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 exitstate v = D.top () end let _ = diff --git a/src/analyses/base.ml b/src/analyses/base.ml index 15b5770179..a8ad9af95b 100644 --- a/src/analyses/base.ml +++ b/src/analyses/base.ml @@ -1,6 +1,8 @@ -(** Value analysis. *) +(** Non-relational value analysis aka {e base analysis} ([base]). *) -open Prelude.Ana +open Batteries +open GoblintCil +open Pretty open Analyses open GobConfig open BaseUtil @@ -8,7 +10,6 @@ module A = Analyses module H = Hashtbl module Q = Queries -module GU = Goblintutil module ID = ValueDomain.ID module FD = ValueDomain.FD module IdxDom = ValueDomain.IndexDomain @@ -45,7 +46,7 @@ struct module V = struct - include Printable.Either (Priv.V) (ThreadIdDomain.Thread) + include Printable.Either (struct include Priv.V let name () = "priv" end) (struct include ThreadIdDomain.Thread let name () = "threadreturn" end) let priv x = `Left x let thread x = `Right x include StdV @@ -104,10 +105,10 @@ struct if Lazy.force array_domain_annotation_enabled then let rec pointedArrayMap = function | [] -> VarMap.empty - | (info,value)::xs -> + | (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 + | Address t when hasAttribute "goblint_array_domain" info.vattr -> + 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 @@ -144,11 +145,13 @@ struct let return_varstore = ref dummyFunDec.svar let return_varinfo () = !return_varstore - let return_var () = AD.from_var (return_varinfo ()) + let return_var () = AD.of_var (return_varinfo ()) let return_lval (): lval = (Var (return_varinfo ()), NoOffset) - let heap_var ctx = - let info = match (ctx.ask Q.HeapVar) with + let longjmp_return = ref dummyFunDec.svar + + 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 @@ -161,7 +164,8 @@ struct | Some marshal -> array_map := marshal | None -> () end; - return_varstore := Goblintutil.create_var @@ makeVarinfo false "RETURN" voidType; + return_varstore := Cilfacade.create_var @@ makeVarinfo false "RETURN" voidType; + longjmp_return := Cilfacade.create_var @@ makeVarinfo false "LONGJMP_RETURN" intType; Priv.init () let finalize () = @@ -185,17 +189,17 @@ struct | _ -> (fun c -> FD.top_of (FD.get_fkind c)) (* Evaluating Cil's unary operators. *) - let evalunop op typ = function - | `Int v1 -> `Int (ID.cast_to (Cilfacade.get_ikind typ) (unop_ID op v1)) - | `Float v -> `Float (unop_FD op v) - | `Address a when op = LNot -> + let evalunop op typ: value -> value = function + | Int v1 -> Int (ID.cast_to (Cilfacade.get_ikind typ) (unop_ID op v1)) + | Float v -> Float (unop_FD op v) + | Address a when op = LNot -> if AD.is_null a then - `Int (ID.of_bool (Cilfacade.get_ikind typ) true) + Int (ID.of_bool (Cilfacade.get_ikind typ) true) else if AD.is_not_null a then - `Int (ID.of_bool (Cilfacade.get_ikind typ) false) + Int (ID.of_bool (Cilfacade.get_ikind typ) false) else - `Int (ID.top_of (Cilfacade.get_ikind typ)) - | `Bot -> `Bot + Int (ID.top_of (Cilfacade.get_ikind typ)) + | Bot -> Bot | _ -> VD.top () let binop_ID (result_ik: Cil.ikind) = function @@ -291,65 +295,70 @@ struct | Addr.NullPtr when GobOption.exists (BI.equal BI.zero) (ID.to_int n) -> Addr.NullPtr | _ -> Addr.UnknownPtr in - match Addr.to_var_offset addr with - | Some (x, o) -> Addr.from_var_offset (x, addToOffset n (Some x.vtype) o) + match Addr.to_mval addr with + | Some (x, o) -> Addr.of_mval (x, addToOffset n (Some x.vtype) o) | None -> default addr in - let addToAddrOp p n = + let addToAddrOp p (n:ID.t):value = match op with (* For array indexing e[i] and pointer addition e + i we have: *) | IndexPI | PlusPI -> - `Address (AD.map (addToAddr n) p) + Address (AD.map (addToAddr n) p) (* Pointer subtracted by a value (e-i) is very similar *) (* Cast n to the (signed) ptrdiff_ikind, then add the its negated value. *) | MinusPI -> let n = ID.neg (ID.cast_to (Cilfacade.ptrdiff_ikind ()) n) in - `Address (AD.map (addToAddr n) p) - | Mod -> `Int (ID.top_of (Cilfacade.ptrdiff_ikind ())) (* we assume that address is actually casted to int first*) - | _ -> `Address AD.top_ptr + Address (AD.map (addToAddr n) p) + | Mod -> Int (ID.top_of (Cilfacade.ptrdiff_ikind ())) (* we assume that address is actually casted to int first*) + | _ -> Address AD.top_ptr in (* The main function! *) match a1,a2 with (* For the integer values, we apply the int domain operator *) - | `Int v1, `Int v2 -> + | Int v1, Int v2 -> let result_ik = Cilfacade.get_ikind t in - `Int (ID.cast_to result_ik (binop_ID result_ik op v1 v2)) + Int (ID.cast_to result_ik (binop_ID result_ik op v1 v2)) (* For the float values, we apply the float domain operators *) - | `Float v1, `Float v2 when is_int_returning_binop_FD op -> + | Float v1, Float v2 when is_int_returning_binop_FD op -> let result_ik = Cilfacade.get_ikind t in - `Int (ID.cast_to result_ik (int_returning_binop_FD op v1 v2)) - | `Float v1, `Float v2 -> `Float (binop_FD (Cilfacade.get_fkind t) op v1 v2) + Int (ID.cast_to result_ik (int_returning_binop_FD op v1 v2)) + | Float v1, Float v2 -> Float (binop_FD (Cilfacade.get_fkind t) op v1 v2) (* For address +/- value, we try to do some elementary ptr arithmetic *) - | `Address p, `Int n - | `Int n, `Address p when op=Eq || op=Ne -> + | Address p, Int n + | Int n, Address p when op=Eq || op=Ne -> let ik = Cilfacade.get_ikind t in - `Int (match ID.to_bool n, AD.to_bool p with + Int (match ID.to_bool n, AD.to_bool p with | Some a, Some b -> ID.of_bool ik (op=Eq && a=b || op=Ne && a<>b) | _ -> bool_top ik) - | `Address p, `Int n -> + | Address p, Int n -> addToAddrOp p n - | `Address p, `Top -> + | Address p, Top -> (* same as previous, but with Unknown instead of int *) (* TODO: why does this even happen in zstd-thread-pool-add? *) let n = ID.top_of (Cilfacade.ptrdiff_ikind ()) in (* pretend to have unknown ptrdiff int instead *) addToAddrOp p n (* If both are pointer values, we can subtract them and well, we don't * bother to find the result in most cases, but it's an integer. *) - | `Address p1, `Address p2 -> begin + | Address p1, Address p2 -> begin let ik = Cilfacade.get_ikind t in let eq x y = if AD.is_definite x && AD.is_definite y then let ax = AD.choose x in let ay = AD.choose y in - if AD.Addr.equal ax ay then - match AD.Addr.to_var ax 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 | _ -> Some true - else - (* If they are unequal, it does not matter if the underlying var represents multiple concrete vars or not *) - Some false + end + in + 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 + | Some false -> Some false + | None -> None else None in @@ -359,7 +368,7 @@ struct (* when subtracting pointers to arrays, per 6.5.6 of C-standard if we subtract two pointers to the same array, the difference *) (* between them is the difference in subscript *) begin - let rec calculateDiffFromOffset x y = + let rec calculateDiffFromOffset x y:value = match x, y with | `Field ((xf:Cil.fieldinfo), xo), `Field((yf:Cil.fieldinfo), yo) when CilType.Fieldinfo.equal xf yf -> @@ -368,47 +377,43 @@ struct begin let diff = ValueDomain.IndexDomain.sub i j in match ValueDomain.IndexDomain.to_int diff with - | Some z -> `Int(ID.of_int ik z) - | _ -> `Int (ID.top_of ik) + | Some z -> Int(ID.of_int ik z) + | _ -> Int (ID.top_of ik) end | `Index (xi, xo), `Index(yi, yo) when xi = yi -> (* TODO: ID.equal? *) calculateDiffFromOffset xo yo - | _ -> `Int (ID.top_of ik) + | _ -> Int (ID.top_of ik) in if AD.is_definite p1 && AD.is_definite p2 then - match Addr.to_var_offset (AD.choose p1), Addr.to_var_offset (AD.choose p2) with + match Addr.to_mval (AD.choose p1), Addr.to_mval (AD.choose p2) with | Some (x, xo), Some (y, yo) when CilType.Varinfo.equal x y -> calculateDiffFromOffset xo yo | _, _ -> - `Int (ID.top_of ik) + Int (ID.top_of ik) else - `Int (ID.top_of ik) + Int (ID.top_of ik) end | Eq -> - `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) + 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) + 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! *) - | `Bot, _ -> `Bot - | _, `Bot -> `Bot + | Bot, _ -> Bot + | _, Bot -> Bot | _ -> VD.top () - (* Auxiliary function to append an additional offset to a given offset. *) - let rec add_offset ofs add = - match ofs with - | `NoOffset -> add - | `Field (fld, `NoOffset) -> `Field (fld, add) - | `Field (fld, ofs) -> `Field (fld, add_offset ofs add) - | `Index (exp, `NoOffset) -> `Index (exp, add) - | `Index (exp, ofs) -> `Index (exp, add_offset ofs add) - + (* TODO: Use AddressDomain for queries *) (* We need the previous function with the varinfo carried along, so we can * map it on the address sets. *) let add_offset_varinfo add ad = - match Addr.to_var_offset ad with - | Some (x,ofs) -> Addr.from_var_offset (x, add_offset ofs add) + match Addr.to_mval ad with + | Some (x,ofs) -> Addr.of_mval (x, Addr.Offs.add_offset ofs add) | None -> ad @@ -423,10 +428,10 @@ struct | `Thread -> true | _ -> - ThreadFlag.is_multi (Analyses.ask_of_ctx ctx) + ThreadFlag.has_ever_been_multi (Analyses.ask_of_ctx ctx) in - if M.tracing then M.tracel "sync" "sync multi=%B earlyglobs=%B\n" multi !GU.earlyglobs; - if !GU.earlyglobs || multi then + if M.tracing then M.tracel "sync" "sync multi=%B earlyglobs=%B\n" multi !earlyglobs; + if !earlyglobs || multi then WideningTokens.with_local_side_tokens (fun () -> Priv.sync (Analyses.ask_of_ctx ctx) (priv_getg ctx.global) (priv_sideg ctx.sideg) ctx.local reason ) @@ -439,7 +444,7 @@ struct ignore (sync' reason ctx) let get_var (a: Q.ask) (gs: glob_fun) (st: store) (x: varinfo): value = - if (!GU.earlyglobs || ThreadFlag.is_multi a) && is_global a x then + if (!earlyglobs || ThreadFlag.has_ever_been_multi a) && is_global a x then Priv.read_global a (priv_getg gs) st x else begin if M.tracing then M.tracec "get" "Singlethreaded mode.\n"; @@ -451,7 +456,7 @@ struct * For the exp argument it is always ok to put None. This means not using precise information about * which part of an array is involved. *) let rec get ?(top=VD.top ()) ?(full=false) a (gs: glob_fun) (st: store) (addrs:address) (exp:exp option): value = - let at = AD.get_type addrs in + let at = AD.type_of addrs in let firstvar = if M.tracing then match AD.to_var_may addrs with [] -> "" | x :: _ -> x.vname else "" in if M.tracing then M.traceli "get" ~var:firstvar "Address: %a\nState: %a\n" AD.pretty addrs CPA.pretty st.cpa; (* Finding a single varinfo*offset pair *) @@ -459,10 +464,10 @@ struct let f_addr (x, offs) = (* get hold of the variable value, either from local or global state *) let var = get_var a gs st x in - let v = VD.eval_offset 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.from_var_offset (x, offs)) VD.pretty v; - if full then v else match v with - | `Blob (c,s,_) -> c + 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 var else match v with + | Blob (c,s,_) -> c | x -> x in let f = function @@ -474,11 +479,11 @@ struct | _ -> assert false end | Addr.UnknownPtr -> top (* top may be more precise than VD.top, e.g. for address sets, such that known addresses are kept for soundness *) - | Addr.StrPtr _ -> `Int (ID.top_of IChar) + | Addr.StrPtr _ -> Int (ID.top_of IChar) in (* We form the collecting function by joining *) - let c x = match x with (* If address type is arithmetic, and our value is an int, we cast to the correct ik *) - | `Int _ when Cil.isArithmeticType at -> VD.cast at x + let c (x:value) = match x with (* If address type is arithmetic, and our value is an int, we cast to the correct ik *) + | Int _ when Cil.isArithmeticType at -> VD.cast at x | _ -> x in let f x a = VD.join (c @@ f x) a in (* Finally we join over all the addresses in the set. *) @@ -495,14 +500,14 @@ struct (* From a list of values, presumably arguments to a function, simply extract * the pointer arguments. *) let get_ptrs (vals: value list): address list = - let f x acc = match x with - | `Address adrs when AD.is_top adrs -> + let f (x:value) acc = match x with + | Address adrs when AD.is_top adrs -> M.info ~category:Unsound "Unknown address given as function argument"; acc - | `Address adrs when AD.to_var_may adrs = [] -> acc - | `Address adrs -> - let typ = AD.get_type adrs in + | Address adrs when AD.to_var_may adrs = [] -> acc + | Address adrs -> + let typ = AD.type_of adrs in if isFunctionType typ then acc else adrs :: acc - | `Top -> M.info ~category:Unsound "Unknown value type given as function argument"; acc + | Top -> M.info ~category:Unsound "Unknown value type given as function argument"; acc | _ -> acc in List.fold_right f vals [] @@ -511,31 +516,33 @@ struct let empty = AD.empty () in if M.tracing then M.trace "reachability" "Checking value %a\n" VD.pretty value; match value with - | `Top -> + | Top -> if VD.is_immediate_type t then () else M.info ~category:Unsound "Unknown value in %s could be an escaped pointer address!" description; empty - | `Bot -> (*M.debug ~category:Analyzer "A bottom value when computing reachable addresses!";*) empty - | `Address adrs when AD.is_top adrs -> + | Bot -> (*M.debug ~category:Analyzer "A bottom value when computing reachable addresses!";*) empty + | Address adrs when AD.is_top adrs -> M.info ~category:Unsound "Unknown address in %s has escaped." description; AD.remove Addr.NullPtr adrs (* return known addresses still to be a bit more sane (but still unsound) *) (* The main thing is to track where pointers go: *) - | `Address adrs -> AD.remove Addr.NullPtr adrs + | Address adrs -> AD.remove Addr.NullPtr adrs (* Unions are easy, I just ingore the type info. *) - | `Union (f,e) -> reachable_from_value ask gs st e t description + | Union (f,e) -> reachable_from_value ask gs st e t description (* For arrays, we ask to read from an unknown index, this will cause it * join all its values. *) - | `Array a -> reachable_from_value ask gs st (ValueDomain.CArrays.get ask a (None, ValueDomain.ArrIdxDomain.top ())) t description - | `Blob (e,_,_) -> reachable_from_value ask gs st e t description - | `Struct s -> ValueDomain.Structs.fold (fun k v acc -> AD.join (reachable_from_value ask gs st v t description) acc) s empty - | `Int _ -> empty - | `Float _ -> empty - | `Thread _ -> empty (* thread IDs are abstract and nothing known can be reached from them *) - | `Mutex -> empty (* mutexes are abstract and nothing known can be reached from them *) + | Array a -> reachable_from_value ask gs st (ValueDomain.CArrays.get (Queries.to_value_domain_ask ask) a (None, ValueDomain.ArrIdxDomain.top ())) t description + | Blob (e,_,_) -> reachable_from_value ask gs st e t description + | Struct s -> ValueDomain.Structs.fold (fun k v acc -> AD.join (reachable_from_value ask gs st v t description) acc) s empty + | Int _ -> empty + | Float _ -> empty + | MutexAttr _ -> empty + | Thread _ -> empty (* thread IDs are abstract and nothing known can be reached from them *) + | JmpBuf _ -> empty (* Jump buffers are abstract and nothing known can be reached from them *) + | Mutex -> empty (* mutexes are abstract and nothing known can be reached from them *) (* Get the list of addresses accessable immediately from a given address, thus * all pointers within a structure should be considered, but we don't follow * pointers. We return a flattend representation, thus simply an address (set). *) let reachable_from_address (ask: Q.ask) (gs:glob_fun) st (adr: address): address = if M.tracing then M.tracei "reachability" "Checking for %a\n" AD.pretty adr; - let res = reachable_from_value ask gs st (get ask gs st adr None) (AD.get_type adr) (AD.show adr) in + let res = reachable_from_value ask gs st (get ask gs st adr None) (AD.type_of adr) (AD.show adr) in if M.tracing then M.traceu "reachability" "Reachable addresses: %a\n" AD.pretty res; res @@ -565,71 +572,51 @@ 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 - | `Address _ as v -> v - | `Blob (v,s,o) -> + | VD.Address _ as v -> v + | Blob (v,s,o) -> begin match replace_val v with - | `Blob (`Top,_,_) - | `Top -> `Top - | t -> `Blob (t,s,o) + | Blob (Top,_,_) + | Top -> Top + | t -> Blob (t,s,o) end - | `Struct s -> `Struct (ValueDomain.Structs.map replace_val s) - | _ -> `Top + | Struct s -> Struct (ValueDomain.Structs.map replace_val s) + | _ -> Top in CPA.map replace_val st let drop_ints (st:CPA.t) : CPA.t = if CPA.is_top st then st else - let rec replace_val = function - | `Int _ -> `Top - | `Array n -> `Array (ValueDomain.CArrays.map replace_val n) - | `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.drop_ints x) + let rec replace_val: value -> value = function + | Int _ -> Top + | Array n -> Array (ValueDomain.CArrays.map replace_val n) + | 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 (AD.map ValueDomain.Addr.top_indices x) | x -> x in CPA.map replace_val st - let drop_interval = CPA.map (function `Int x -> `Int (ID.no_interval x) | x -> x) + let drop_interval = CPA.map (function Int x -> Int (ID.no_interval x) | x -> x) + + let drop_intervalSet = CPA.map (function Int x -> Int (ID.no_intervalSet x) | x -> x ) let context (fd: fundec) (st: store): store = let f keep drop_fn (st: store) = if keep then st else { st with cpa = drop_fn st.cpa} in st |> (* Here earlyglobs only drops syntactic globals from the context and does not consider e.g. escaped globals. *) (* This is equivalent to having escaped globals excluded from earlyglobs for contexts *) - f (not !GU.earlyglobs) (CPA.filter (fun k v -> (not k.vglob) || is_excluded_from_earlyglobs k)) + f (not !earlyglobs) (CPA.filter (fun k v -> (not k.vglob) || is_excluded_from_earlyglobs k)) %> f (ContextUtil.should_keep ~isAttr:GobContext ~keepOption:"ana.base.context.non-ptr" ~removeAttr:"base.no-non-ptr" ~keepAttr:"base.non-ptr" fd) drop_non_ptrs %> f (ContextUtil.should_keep ~isAttr:GobContext ~keepOption:"ana.base.context.int" ~removeAttr:"base.no-int" ~keepAttr:"base.int" fd) drop_ints %> 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 - let context_cpa fd (st: store) = (context fd st).cpa - - let convertToQueryLval x = - let rec offsNormal o = - let ik = Cilfacade.ptrdiff_ikind () in - let toInt i = - match IdxDom.to_int @@ ID.cast_to ik i with - | Some x -> Const (CInt (x,ik, None)) - | _ -> Cilfacade.mkCast ~e:(Const (CStr ("unknown",No_encoding))) ~newt:intType - - in - match o with - | `NoOffset -> `NoOffset - | `Field (f,o) -> `Field (f,offsNormal o) - | `Index (i,o) -> `Index (toInt i,offsNormal o) - in - match x with - | ValueDomain.AD.Addr.Addr (v,o) ->[v,offsNormal 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 @@ -652,23 +639,25 @@ struct in let rec reachable_from_value (value: value) = match value with - | `Top -> (empty, TS.top (), true) - | `Bot -> (empty, TS.bot (), false) - | `Address adrs when AD.is_top adrs -> (empty,TS.bot (), true) - | `Address adrs -> (adrs,TS.bot (), AD.has_unknown adrs) - | `Union (t,e) -> with_field (reachable_from_value e) t - | `Array a -> reachable_from_value (ValueDomain.CArrays.get (Analyses.ask_of_ctx ctx) a (None, ValueDomain.ArrIdxDomain.top ())) - | `Blob (e,_,_) -> reachable_from_value e - | `Struct s -> + | Top -> (empty, TS.top (), true) + | Bot -> (empty, TS.bot (), false) + | Address adrs when AD.is_top adrs -> (empty,TS.bot (), true) + | Address adrs -> (adrs,TS.bot (), AD.may_be_unknown adrs) + | Union (t,e) -> with_field (reachable_from_value e) t + | Array a -> reachable_from_value (ValueDomain.CArrays.get (Queries.to_value_domain_ask (Analyses.ask_of_ctx ctx)) a (None, ValueDomain.ArrIdxDomain.top ())) + | Blob (e,_,_) -> reachable_from_value e + | Struct s -> let join_tr (a1,t1,_) (a2,t2,_) = AD.join a1 a2, TS.join t1 t2, false in let f k v = join_tr (with_type k.ftype (reachable_from_value v)) in ValueDomain.Structs.fold f s (empty, TS.bot (), false) - | `Int _ -> (empty, TS.bot (), false) - | `Float _ -> (empty, TS.bot (), false) - | `Thread _ -> (empty, TS.bot (), false) (* TODO: is this right? *) - | `Mutex -> (empty, TS.bot (), false) (* TODO: is this right? *) + | Int _ -> (empty, TS.bot (), false) + | Float _ -> (empty, TS.bot (), false) + | MutexAttr _ -> (empty, TS.bot (), false) + | Thread _ -> (empty, TS.bot (), false) (* TODO: is this right? *) + | JmpBuf _ -> (empty, TS.bot (), false) (* TODO: is this right? *) + | Mutex -> (empty, TS.bot (), false) (* TODO: is this right? *) in reachable_from_value (get (Analyses.ask_of_ctx ctx) ctx.global ctx.local adr None) in @@ -709,7 +698,7 @@ struct and eval_rv_ask_evalint a gs st exp = let eval_next () = eval_rv_no_ask_evalint a gs st exp in if M.tracing then M.traceli "evalint" "base eval_rv_ask_evalint %a\n" d_exp exp; - let r = + let r:value = match Cilfacade.typeOf exp with | typ when Cil.isIntegralType typ && not (Cil.isConstant exp) -> (* don't EvalInt integer constants, base can do them precisely itself *) if M.tracing then M.traceli "evalint" "base ask EvalInt %a\n" d_exp exp; @@ -717,9 +706,9 @@ struct if M.tracing then M.traceu "evalint" "base ask EvalInt %a -> %a\n" d_exp exp Queries.ID.pretty a; begin match a with | `Bot -> eval_next () (* Base EvalInt returns bot on incorrect type (e.g. pthread_t); ignore and continue. *) - (* | x -> Some (`Int x) *) - | `Lifted x -> `Int x (* cast should be unnecessary, EvalInt should guarantee right ikind already *) - | `Top -> `Int (ID.top_of (Cilfacade.get_ikind typ)) (* query cycle *) + (* | x -> Some (Int x) *) + | `Lifted x -> Int x (* cast should be unnecessary, EvalInt should guarantee right ikind already *) + | `Top -> Int (ID.top_of (Cilfacade.get_ikind typ)) (* query cycle *) end | exception Cilfacade.TypeOfError _ (* Bug: typeOffset: Field on a non-compound *) | _ -> eval_next () @@ -750,15 +739,6 @@ struct and eval_rv_base (a: Q.ask) (gs:glob_fun) (st: store) (exp:exp): value = let eval_rv = eval_rv_back_up in if M.tracing then M.traceli "evalint" "base eval_rv_base %a\n" d_exp exp; - let rec do_offs def = function (* for types that only have one value *) - | Field (fd, offs) -> begin - match Goblintutil.is_blessed (TComp (fd.fcomp, [])) with - | Some v -> do_offs (`Address (AD.singleton (Addr.from_var_offset (v,convert_offset a gs st (Field (fd, offs)))))) offs - | None -> do_offs def offs - end - | Index (_, offs) -> do_offs def offs - | NoOffset -> def - in let binop_remove_same_casts ~extra_is_safe ~e1 ~e2 ~t1 ~t2 ~c1 ~c2 = let te1 = Cilfacade.typeOf e1 in let te2 = Cilfacade.typeOf e2 in @@ -780,26 +760,33 @@ struct (* seems like constFold already converts CChr to CInt *) | Const (CChr x) -> eval_rv a gs st (Const (charConstToInt x)) (* char becomes int, see Cil doc/ISO C 6.4.4.4.10 *) | Const (CInt (num,ikind,str)) -> - (match str with Some x -> M.tracel "casto" "CInt (%s, %a, %s)\n" (Cilint.string_of_cilint num) d_ikind ikind x | None -> ()); - `Int (ID.cast_to ikind (IntDomain.of_const (num,ikind,str))) - | Const (CReal (_, (FFloat | FDouble | FLongDouble as fkind), Some str)) -> `Float (FD.of_string fkind str) (* prefer parsing from string due to higher precision *) - | Const (CReal (num, (FFloat | FDouble | FLongDouble as fkind), None)) -> `Float (FD.of_const fkind num) + (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) && 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.from_string x) (* normal 8-bit strings, type: char* *) + | 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* *) - let x = Pretty.sprint ~width:80 (d_const () c) in (* escapes, see impl. of d_const in cil.ml *) + let x = CilType.Constant.show c in (* escapes, see impl. of d_const in cil.ml *) let x = String.sub x 2 (String.length x - 3) in (* remove surrounding quotes: L"foo" -> foo *) - `Address (AD.from_string x) (* `Address (AD.str_ptr ()) *) + Address (AD.of_string x) (* Address (AD.str_ptr ()) *) | Const _ -> VD.top () (* Variables and address expressions *) | Lval lv -> - eval_rv_base_lval ~eval_lv ~do_offs a gs st exp lv + eval_rv_base_lval ~eval_lv a gs st exp lv (* Binary operators *) (* Eq/Ne when both values are equal and casted to the same type *) | BinOp ((Eq | Ne) as op, (CastE (t1, e1) as c1), (CastE (t2, e2) as c2), typ) when typeSig t1 = typeSig t2 -> let a1 = eval_rv a gs st e1 in let a2 = eval_rv a gs st e2 in - let (e1, e2) = binop_remove_same_casts ~extra_is_safe:(VD.equal a1 a2) ~e1 ~e2 ~t1 ~t2 ~c1 ~c2 in + let extra_is_safe = + match evalbinop_base a st op t1 a1 t2 a2 typ with + | Int i -> ID.to_bool i = Some true + | _ + | exception IntDomain.IncompatibleIKinds _ -> false + in + let (e1, e2) = binop_remove_same_casts ~extra_is_safe ~e1 ~e2 ~t1 ~t2 ~c1 ~c2 in (* re-evaluate e1 and e2 in evalbinop because might be with cast *) evalbinop a gs st op ~e1 ~t1 ~e2 ~t2 typ | BinOp (LOr, e1, e2, typ) as exp -> @@ -831,33 +818,33 @@ struct else None in - let eqs_value = + let eqs_value: value option = let* eqs = split exp in let* (e, es) = find_common eqs in let v = eval_rv a gs st e in (* value of common exp *) let vs = List.map (eval_rv a gs st) es in (* values of other sides *) let ik = Cilfacade.get_ikind typ in match v with - | `Address a -> + | Address a -> (* get definite addrs from vs *) - let rec to_definite_ad = function + let rec to_definite_ad: value list -> AD.t = function | [] -> AD.empty () - | `Address a :: vs when AD.is_definite a -> + | Address a :: vs when AD.is_definite a -> AD.union a (to_definite_ad vs) | _ :: vs -> to_definite_ad vs in let definite_ad = to_definite_ad vs in if AD.leq a definite_ad then (* other sides cover common address *) - Some (`Int (ID.of_bool ik true)) + Some (VD.Int (ID.of_bool ik true)) else (* TODO: detect disjoint cases using may: https://github.com/goblint/analyzer/pull/757#discussion_r898105918 *) None - | `Int i -> + | Int i -> let module BISet = IntDomain.BISet in (* get definite ints from vs *) - let rec to_int_set = function + let rec to_int_set: value list -> BISet.t = function | [] -> BISet.empty () - | `Int i :: vs -> + | Int i :: vs -> begin match ID.to_int i with | Some i' -> BISet.add i' (to_int_set vs) | None -> to_int_set vs @@ -869,7 +856,7 @@ struct let incl_set = BISet.of_list incl_list in let int_set = to_int_set vs in if BISet.leq incl_set int_set then (* other sides cover common int *) - Some (`Int (ID.of_bool ik true)) + Some (VD.Int (ID.of_bool ik true)) else (* TODO: detect disjoint cases using may: https://github.com/goblint/analyzer/pull/757#discussion_r898105918 *) None | _ -> @@ -886,17 +873,13 @@ struct let a1 = eval_rv a gs st arg1 in evalunop op typ a1 (* The &-operator: we create the address abstract element *) - | AddrOf lval -> `Address (eval_lv a gs st lval) + | AddrOf lval -> Address (eval_lv a gs st lval) (* CIL's very nice implicit conversion of an array name [a] to a pointer * to its first element [&a[0]]. *) | StartOf lval -> let array_ofs = `Index (IdxDom.of_int (Cilfacade.ptrdiff_ikind ()) BI.zero, `NoOffset) in - let array_start ad = - match Addr.to_var_offset ad with - | Some (x, offs) -> Addr.from_var_offset (x, add_offset offs array_ofs) - | None -> ad - in - `Address (AD.map array_start (eval_lv a gs st lval)) + let array_start = add_offset_varinfo array_ofs in + Address (AD.map array_start (eval_lv a gs st lval)) | CastE (t, Const (CStr (x,e))) -> (* VD.top () *) eval_rv a gs st (Const (CStr (x,e))) (* TODO safe? *) | CastE (t, exp) -> let v = eval_rv a gs st exp in @@ -915,10 +898,10 @@ struct if M.tracing then M.traceu "evalint" "base eval_rv_base %a -> %a\n" d_exp exp VD.pretty r; r - and eval_rv_base_lval ~eval_lv ~do_offs (a: Q.ask) (gs:glob_fun) (st: store) (exp: exp) (lv: lval): value = + and eval_rv_base_lval ~eval_lv (a: Q.ask) (gs:glob_fun) (st: store) (exp: exp) (lv: lval): value = match lv with - | (Var v, ofs) -> do_offs (get a gs st (eval_lv a gs st (Var v, ofs)) (Some exp)) ofs - (*| Lval (Mem e, ofs) -> do_offs (get a gs st (eval_lv a gs st (Mem e, ofs))) ofs*) + | (Var v, ofs) -> get a gs st (eval_lv a gs st (Var v, ofs)) (Some exp) + (*| Lval (Mem e, ofs) -> get a gs st (eval_lv a gs st (Mem e, ofs)) *) | (Mem e, ofs) -> (*M.tracel "cast" "Deref: lval: %a\n" d_plainlval lv;*) let rec contains_vla (t:typ) = match t with @@ -938,15 +921,15 @@ struct match a with | Addr (x, o) -> begin - let at = get_type_addr (x, o) in + let at = Addr.Mval.type_of (x, o) in if M.tracing then M.tracel "evalint" "cast_ok %a %a %a\n" Addr.pretty (Addr (x, o)) CilType.Typ.pretty (Cil.unrollType x.vtype) CilType.Typ.pretty at; if at = TVoid [] then (* HACK: cast from alloc variable is always fine *) true else match Cil.getInteger (sizeOf t), Cil.getInteger (sizeOf at) with - | Some i1, Some i2 -> Cilint.compare_cilint i1 i2 <= 0 + | Some i1, Some i2 -> Z.compare i1 i2 <= 0 | _ -> - if contains_vla t || contains_vla (get_type_addr (x, o)) then + if contains_vla t || contains_vla (Addr.Mval.type_of (x, o)) then begin (* TODO: Is this ok? *) M.info ~category:Unsound "Casting involving a VLA is assumed to work"; @@ -968,8 +951,7 @@ struct in let v' = VD.cast t v in (* cast to the expected type (the abstract type might be something other than t since we don't change addresses upon casts!) *) if M.tracing then M.tracel "cast" "Ptr-Deref: cast %a to %a = %a!\n" VD.pretty v d_type t VD.pretty v'; - let v' = VD.eval_offset a (fun x -> get a gs st x (Some exp)) v' (convert_offset a gs st ofs) (Some exp) None t in (* handle offset *) - let v' = do_offs v' ofs in (* handle blessed fields? *) + let v' = VD.eval_offset (Queries.to_value_domain_ask a) (fun x -> get a gs st x (Some exp)) v' (convert_offset a gs st ofs) (Some exp) None t in (* handle offset *) v' in AD.fold (fun a acc -> VD.join acc (lookup_with_offs a)) p (VD.bot ()) @@ -987,7 +969,7 @@ struct let r = evalbinop_base a st op t1 a1 t2 a2 t in if Cil.isIntegralType t then ( match r with - | `Int i when ID.to_int i <> None -> r (* Avoid fallback, cannot become any more precise. *) + | Int i when ID.to_int i <> None -> r (* Avoid fallback, cannot become any more precise. *) | _ -> (* Fallback to MustBeEqual query, could get extra precision from exprelation/var_eq. *) let must_be_equal () = @@ -998,21 +980,21 @@ struct match op with | MinusA when must_be_equal () -> let ik = Cilfacade.get_ikind t in - `Int (ID.of_int ik BI.zero) + Int (ID.of_int ik BI.zero) | MinusPI (* TODO: untested *) | MinusPP when must_be_equal () -> let ik = Cilfacade.ptrdiff_ikind () in - `Int (ID.of_int ik BI.zero) + Int (ID.of_int ik BI.zero) (* Eq case is unnecessary: Q.must_be_equal reconstructs BinOp (Eq, _, _, _) and repeats EvalInt query for that, yielding a top from query cycle and never being must equal *) | Le | Ge when must_be_equal () -> let ik = Cilfacade.get_ikind t in - `Int (ID.of_bool ik true) + Int (ID.of_bool ik true) | Ne | Lt | Gt when must_be_equal () -> let ik = Cilfacade.get_ikind t in - `Int (ID.of_bool ik false) + Int (ID.of_bool ik false) | _ -> r (* Fallback didn't help. *) ) else @@ -1027,11 +1009,11 @@ struct (* Used also for thread creation: *) and eval_tv a (gs:glob_fun) st (exp:exp): AD.t = match (eval_rv a gs st exp) with - | `Address x -> x + | Address x -> x | _ -> failwith "Problems evaluating expression to function calls!" and eval_int a gs st exp = match eval_rv a gs st exp with - | `Int x -> x + | Int x -> x | _ -> ID.top_of (Cilfacade.get_ikind_exp exp) (* A function to convert the offset to our abstract representation of * offsets, i.e. evaluate the index expression to the integer domain. *) @@ -1040,53 +1022,51 @@ struct match ofs with | NoOffset -> `NoOffset | Field (fld, ofs) -> `Field (fld, convert_offset a gs st ofs) - | Index (CastE (TInt(IInt,[]), Const (CStr ("unknown",No_encoding))), ofs) -> (* special offset added by convertToQueryLval *) + | Index (exp, ofs) when CilType.Exp.equal exp Offset.Index.Exp.any -> (* special offset added by convertToQueryLval *) `Index (IdxDom.top (), convert_offset a gs st ofs) | Index (exp, ofs) -> match eval_rv a gs st exp with - | `Int i -> `Index (iDtoIdx i, convert_offset a gs st ofs) - | `Address add -> `Index (AD.to_int (module IdxDom) add, convert_offset a gs st ofs) - | `Top -> `Index (IdxDom.top (), convert_offset a gs st ofs) - | `Bot -> `Index (IdxDom.bot (), convert_offset a gs st ofs) + | Int i -> `Index (iDtoIdx i, convert_offset a gs st ofs) + | Address add -> `Index (AD.to_int add, convert_offset a gs st ofs) + | Top -> `Index (IdxDom.top (), convert_offset a gs st ofs) + | Bot -> `Index (IdxDom.bot (), convert_offset a gs st ofs) | _ -> failwith "Index not an integer value" (* Evaluation of lvalues to our abstract address domain. *) and eval_lv (a: Q.ask) (gs:glob_fun) st (lval:lval): AD.t = let eval_rv = eval_rv_back_up in - let rec do_offs def = function - | Field (fd, offs) -> begin - match Goblintutil.is_blessed (TComp (fd.fcomp, [])) with - | Some v -> do_offs (AD.singleton (Addr.from_var_offset (v,convert_offset a gs st (Field (fd, offs))))) offs - | None -> do_offs def offs - end - | Index (_, offs) -> do_offs def offs - | NoOffset -> def - in match lval with - | Var x, NoOffset when (not x.vglob) && Goblintutil.is_blessed x.vtype<> None -> - begin match Goblintutil.is_blessed x.vtype with - | Some v -> AD.singleton (Addr.from_var v) - | _ -> AD.singleton (Addr.from_var_offset (x, convert_offset a gs st NoOffset)) - end (* The simpler case with an explicit variable, e.g. for [x.field] we just * create the address { (x,field) } *) | Var x, ofs -> - if x.vglob - then AD.singleton (Addr.from_var_offset (x, convert_offset a gs st ofs)) - else do_offs (AD.singleton (Addr.from_var_offset (x, convert_offset a gs st ofs))) ofs + AD.singleton (Addr.of_mval (x, convert_offset a gs st ofs)) (* The more complicated case when [exp = & x.field] and we are asked to * evaluate [(\*exp).subfield]. We first evaluate [exp] to { (x,field) } * and then add the subfield to it: { (x,field.subfield) }. *) | 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"); - do_offs (AD.map (add_offset_varinfo (convert_offset a gs st ofs)) adr) ofs - | `Bot -> AD.bot () + | Address adr -> + ( + 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 | _ -> - M.debug ~category:Analyzer "Failed evaluating %a to lvalue" d_lval lval; do_offs AD.unknown_ptr ofs + M.debug ~category:Analyzer "Failed evaluating %a to lvalue" d_lval lval; + AD.unknown_ptr end (* run eval_rv from above and keep a result that is bottom *) @@ -1106,10 +1086,11 @@ struct let query_evalint ask gs st e = if M.tracing then M.traceli "evalint" "base query_evalint %a\n" d_exp e; let r = match eval_rv_no_ask_evalint ask gs st e with - | `Int i -> `Lifted i (* cast should be unnecessary, eval_rv should guarantee right ikind already *) - | `Bot -> Queries.ID.top () (* out-of-scope variables cause bot, but query result should then be unknown *) - | `Top -> Queries.ID.top () (* some float computations cause top (57-float/01-base), but query result should then be unknown *) + | Int i -> `Lifted i (* cast should be unnecessary, eval_rv should guarantee right ikind already *) + | Bot -> Queries.ID.top () (* out-of-scope variables cause bot, but query result should then be unknown *) + | Top -> Queries.ID.top () (* some float computations cause top (57-float/01-base), but query result should then be unknown *) | v -> M.debug ~category:Analyzer "Base EvalInt %a query answering bot instead of %a" d_exp e VD.pretty v; Queries.ID.bot () + | exception (IntDomain.ArithmeticOnIntegerBot _) when not !AnalysisState.should_warn -> Queries.ID.top () (* for some privatizations, values can intermediately be bot because side-effects have not happened yet *) in if M.tracing then M.traceu "evalint" "base query_evalint %a -> %a\n" d_exp e Queries.ID.pretty r; r @@ -1132,26 +1113,21 @@ struct and ask asked = { Queries.f = fun (type a) (q: a Queries.t) -> query asked q } (* our version of ask *) and gs = function `Left _ -> `Lifted1 (Priv.G.top ()) | `Right _ -> `Lifted2 (VD.top ()) in (* the expression is guaranteed to not contain globals *) match (eval_rv (ask Queries.Set.empty) gs st exp) with - | `Int x -> ValueDomain.ID.to_int x + | 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. *) + Avoids expensive Apron EvalInt if the Int result would be useless to us anyway. *) let eval_rv_address ask gs st e = (* no way to do eval_rv with expected type, so filter expression beforehand *) match Cilfacade.typeOf e with @@ -1163,6 +1139,10 @@ struct (* interpreter end *) + 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 @@ -1183,8 +1163,8 @@ struct Invariant.none in - if CilLval.Set.is_top context.Invariant.lvals then ( - if !GU.earlyglobs || ThreadFlag.is_multi ask then ( + if Lval.Set.is_top context.Invariant.lvals then ( + if !earlyglobs || ThreadFlag.has_ever_been_multi ask then ( let cpa_invariant = CPA.fold (fun k v a -> if not (is_global ask k) then @@ -1208,7 +1188,7 @@ struct ) ) else ( - CilLval.Set.fold (fun k a -> + Lval.Set.fold (fun k a -> let i = match k with | (Var v, offset) when not (InvariantCil.var_is_heap v) -> @@ -1242,15 +1222,39 @@ 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 + 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 -> + if AD.mem Addr.UnknownPtr jmp_buf then + M.warn ~category:Imprecise "Jump buffer %a may contain unknown pointers." d_exp e; + begin match get ~top:(VD.bot ()) (Analyses.ask_of_ctx ctx) ctx.global ctx.local jmp_buf None with + | JmpBuf (x, copied) -> + 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 + | 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 + | _ -> + 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 + | Q.EvalMutexAttr e -> begin + let e:exp = Lval (Cil.mkMem ~addr:e ~off:NoOffset) in + match eval_rv (Analyses.ask_of_ctx ctx) ctx.global ctx.local e with + | MutexAttr a -> a + | v -> MutexAttrDomain.top () + end | Q.EvalLength e -> begin match eval_rv_address (Analyses.ask_of_ctx ctx) ctx.global ctx.local e with - | `Address a -> + | Address a -> let slen = Seq.map String.length (List.to_seq (AD.to_string a)) in let lenOf = function | TArray (_, l, _) -> (try Some (lenOfArray l) with LenOfArray -> None) @@ -1260,81 +1264,101 @@ struct let d = Seq.fold_left ID.join (ID.bot_of (Cilfacade.ptrdiff_ikind ())) (Seq.map (ID.of_int (Cilfacade.ptrdiff_ikind ()) %BI.of_int) (Seq.append slen alen)) in (* ignore @@ printf "EvalLength %a = %a\n" d_exp e ID.pretty d; *) `Lifted d - | `Bot -> Queries.Result.bot q (* TODO: remove *) + | Bot -> Queries.Result.bot q (* TODO: remove *) | _ -> Queries.Result.top q end - | Q.BlobSize e -> begin + | Q.EvalValue e -> + eval_rv (Analyses.ask_of_ctx ctx) ctx.global ctx.local e + | 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) + | Address a -> + (* 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_heap_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 + | 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 - | `Bot -> Queries.Result.bot q (* TODO: remove *) + | 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 let v = eval_rv (Analyses.ask_of_ctx ctx) ctx.global ctx.local e in (* ignore (Pretty.eprintf "evalthread %a (%a): %a" d_exp e d_plainexp e VD.pretty v); *) match v with - | `Thread a -> a - | `Bot -> Queries.Result.bot q (* TODO: remove *) + | Thread a -> a + | Bot -> Queries.Result.bot q (* TODO: remove *) | _ -> Queries.Result.top q end | Q.ReachableFrom e -> begin match eval_rv_address (Analyses.ask_of_ctx ctx) ctx.global ctx.local e with - | `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 *) + | 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: 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 - | `Top -> Queries.Result.top q - | `Bot -> Queries.Result.bot q (* TODO: remove *) - | `Address a when AD.is_top a || AD.mem Addr.UnknownPtr a -> + | Top -> Queries.Result.top q + | Bot -> Queries.Result.bot q (* TODO: remove *) + | Address a when AD.is_top a || AD.mem Addr.UnknownPtr a -> Q.TS.top () - | `Address a -> + | Address a -> reachable_top_pointers_types ctx a | _ -> Q.TS.empty () end | Q.EvalStr e -> begin match eval_rv_address (Analyses.ask_of_ctx ctx) ctx.global ctx.local e with (* exactly one string in the set (works for assignments of string constants) *) - | `Address a when List.compare_length_with (AD.to_string a) 1 = 0 -> (* exactly one string *) + | Address a when List.compare_length_with (AD.to_string a) 1 = 0 -> (* exactly one string *) `Lifted (List.hd (AD.to_string a)) (* check if we have an array of chars that form a string *) (* TODO return may-points-to-set of strings *) - | `Address a when List.compare_length_with (AD.to_string a) 1 > 0 -> (* oh oh *) + | Address a when List.compare_length_with (AD.to_string a) 1 > 0 -> (* oh oh *) M.debug "EvalStr (%a) returned %a" d_exp e AD.pretty a; Queries.Result.top q - | `Address a when List.compare_length_with (AD.to_var_may a) 1 = 0 -> (* some other address *) + | Address a when List.compare_length_with (AD.to_var_may a) 1 = 0 -> (* some other address *) (* Cil.varinfo * (AD.Addr.field, AD.Addr.idx) Lval.offs *) - (* 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); *) + (* 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 v, offs = Q.LS.choose @@ addrToLvalSet a in - let ciloffs = Lval.CilLval.to_ciloffs offs in - let lval = Var v, ciloffs 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? *) @@ -1345,7 +1369,8 @@ struct (* ignore @@ printf "EvalStr Unknown: %a -> %s\n" d_plainexp e (VD.short 80 x); *) Queries.Result.top q end - | Q.IsMultiple v -> WeakUpdates.mem v ctx.local.weak + | Q.IsMultiple v -> WeakUpdates.mem v ctx.local.weak || + (hasAttribute "thread" v.vattr && v.vaddrof) (* thread-local variables if they have their address taken, as one could then compare several such variables *) | Q.IterSysVars (vq, vf) -> let vf' x = vf (Obj.repr (V.priv x)) in Priv.iter_sys_vars (priv_getg ctx.global) vq vf' @@ -1369,15 +1394,15 @@ struct Dep.add var vMapNew dep in match value with - | `Array _ - | `Struct _ - | `Union _ -> + | Array _ + | Struct _ + | Union _ -> begin let vars_in_partitioning = VD.affecting_vars value in let dep_new = List.fold_left (fun dep var -> add_one_dep x var dep) st.deps vars_in_partitioning in { st with deps = dep_new } end - (* `Blob cannot contain arrays *) + (* Blob cannot contain arrays *) | _ -> st (** [set st addr val] returns a state where [addr] is set to [val] @@ -1400,7 +1425,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 @@ -1415,39 +1440,44 @@ struct in let update_offset old_value = (* Projection globals to highest Precision *) - let projected_value = project_val a None None value (is_global a x) in - let new_value = VD.update_offset a old_value offs projected_value lval_raw ((Var x), cil_offset) t in + let projected_value = project_val (Queries.to_value_domain_ask a) None None value (is_global a x) in + let new_value = VD.update_offset (Queries.to_value_domain_ask a) old_value offs projected_value lval_raw ((Var x), cil_offset) t in if WeakUpdates.mem x st.weak then VD.join old_value new_value - else if invariant then + else if invariant then ( (* without this, invariant for ambiguous pointer might worsen precision for each individual address to their join *) - VD.meet old_value new_value + try + VD.meet old_value new_value + with Lattice.Uncomparable -> + new_value + ) else new_value in - if M.tracing then M.tracel "set" ~var:firstvar "update_one_addr: start with '%a' (type '%a') \nstate:%a\n\n" AD.pretty (AD.from_var_offset (x,offs)) d_type x.vtype D.pretty st; + if M.tracing then M.tracel "set" ~var:firstvar "update_one_addr: start with '%a' (type '%a') \nstate:%a\n\n" AD.pretty (AD.of_mval (x,offs)) d_type x.vtype D.pretty st; if isFunctionType x.vtype then begin if M.tracing then M.tracel "set" ~var:firstvar "update_one_addr: returning: '%a' is a function type \n" d_type x.vtype; st end else if get_bool "exp.globs_are_top" then begin if M.tracing then M.tracel "set" ~var:firstvar "update_one_addr: BAD? exp.globs_are_top is set \n"; - { st with cpa = CPA.add x `Top st.cpa } + { st with cpa = CPA.add x Top st.cpa } end else (* Check if we need to side-effect this one. We no longer generate * side-effects here, but the code still distinguishes these cases. *) - if (!GU.earlyglobs || ThreadFlag.is_multi a) && is_global a x then begin + if (!earlyglobs || ThreadFlag.has_ever_been_multi a) && is_global a x then begin if M.tracing then M.tracel "set" ~var:x.vname "update_one_addr: update a global var '%s' ...\n" x.vname; let priv_getg = priv_getg gs in (* 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; 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 @@ -1486,10 +1516,10 @@ struct | Some (Lval(Var l',NoOffset)), Some r' -> begin let moved_by = movement_for_expr l' r' in - VD.affect_move a v x moved_by + VD.affect_move (Queries.to_value_domain_ask a) v x moved_by end | _ -> - VD.affect_move a v x (fun x -> None) + VD.affect_move (Queries.to_value_domain_ask a) v x (fun x -> None) else let patched_ask = (* The usual recursion trick for ctx. *) @@ -1513,20 +1543,23 @@ struct in let moved_by = fun x -> Some 0 in (* this is ok, the information is not provided if it *) (* TODO: why does affect_move need general ask (of any query) instead of eval_exp? *) - VD.affect_move patched_ask v x moved_by (* was a set call caused e.g. by a guard *) + VD.affect_move (Queries.to_value_domain_ask patched_ask) v x moved_by (* was a set call caused e.g. by a guard *) in { st with cpa = update_variable arr arr.vtype nval st.cpa } in (* within invariant, a change to the way arrays are partitioned is not necessary *) List.fold_left (fun x y -> effect_on_array (not invariant) y x) st affected_arrays in - let x_updated = update_variable x t new_value st.cpa in - let with_dep = add_partitioning_dependencies x new_value {st with cpa = x_updated } in - effect_on_arrays a with_dep + if VD.is_bot new_value && invariant && not (CPA.mem x st.cpa) then + st + else + let x_updated = update_variable x t new_value st.cpa in + let with_dep = add_partitioning_dependencies x new_value {st with cpa = x_updated } in + effect_on_arrays a with_dep end in let update_one x store = - match Addr.to_var_offset x with + match Addr.to_mval x with | Some x -> update_one_addr x store | None -> store in try @@ -1580,9 +1613,9 @@ struct * Auxillary functions **************************************************************************) - let is_some_bot x = + let is_some_bot (x:value) = match x with - | `Bot -> false (* HACK: bot is here due to typing conflict (we do not cast appropriately) *) + | Bot -> false (* HACK: bot is here due to typing conflict (we do not cast appropriately) *) | _ -> VD.is_bot_value x module InvariantEval = @@ -1598,7 +1631,7 @@ struct let get_var = get_var let get a gs st addrs exp = get a gs st addrs exp - let set a ~ctx gs st lval lval_type value = set a ~ctx ~invariant:true gs st lval lval_type value + let set a ~ctx gs st lval lval_type ?lval_raw value = set a ~ctx ~invariant:true gs st lval lval_type ?lval_raw value let refine_entire_var = true let map_oldval oldval _ = oldval @@ -1606,6 +1639,8 @@ struct let id_meet_down ~old ~c = ID.meet old c let fd_meet_down ~old ~c = FD.meet old c + + let contra _ = raise Deadcode end module Invariant = BaseInvariant.Make (InvariantEval) @@ -1616,7 +1651,7 @@ struct let set_savetop ~ctx ?lval_raw ?rval_raw ask (gs:glob_fun) st adr lval_t v : store = if M.tracing then M.tracel "set" "savetop %a %a %a\n" AD.pretty adr d_type lval_t VD.pretty v; match v with - | `Top -> set ~ctx ask gs st adr lval_t (VD.top_value (AD.get_type adr)) ?lval_raw ?rval_raw + | Top -> set ~ctx ask gs st adr lval_t (VD.top_value (AD.type_of adr)) ?lval_raw ?rval_raw | v -> set ~ctx ask gs st adr lval_t v ?lval_raw ?rval_raw @@ -1644,7 +1679,7 @@ struct in match last_index lval, stripCasts rval with | Some (lv, i), Const(CChr c) when c<>'\000' -> (* "abc" <> "abc\000" in OCaml! *) - let i = Cilint.int_of_cilint i in + let i = Z.to_int i in (* ignore @@ printf "%a[%i] = %c\n" d_lval lv i c; *) let s = try Hashtbl.find char_array lv with Not_found -> Bytes.empty in (* current string for lv or empty string *) if i >= Bytes.length s then ((* optimized b/c Out_of_memory *) @@ -1661,9 +1696,10 @@ struct in char_array_hack (); let rval_val = eval_rv (Analyses.ask_of_ctx ctx) ctx.global ctx.local rval in + let rval_val = VD.mark_jmpbufs_as_copied rval_val in let lval_val = eval_lv (Analyses.ask_of_ctx ctx) ctx.global ctx.local lval in (* let sofa = AD.short 80 lval_val^" = "^VD.short 80 rval_val in *) - (* M.debug ~category:Analyzer @@ sprint ~width:80 @@ dprintf "%a = %a\n%s" d_plainlval lval d_plainexp rval sofa; *) + (* M.debug ~category:Analyzer @@ sprint ~width:max_int @@ dprintf "%a = %a\n%s" d_plainlval lval d_plainexp rval sofa; *) let not_local xs = let not_local x = match Addr.to_var_may x with @@ -1673,8 +1709,8 @@ struct AD.is_top xs || AD.exists not_local xs in (match rval_val, lval_val with - | `Address adrs, lval - when (not !GU.global_initialization) && get_bool "kernel" && not_local lval && not (AD.is_top adrs) -> + | Address adrs, lval + when (not !AnalysisState.global_initialization) && get_bool "kernel" && not_local lval && not (AD.is_top adrs) -> let find_fps e xs = match Addr.to_var_must e with | Some x -> x :: xs | None -> xs @@ -1685,25 +1721,25 @@ struct | _ -> () ); match lval with (* this section ensure global variables contain bottom values of the proper type before setting them *) - | (Var v, offs) when AD.is_definite lval_val && v.vglob -> + | (Var v, offs) when v.vglob -> (* Optimization: In case of simple integral types, we not need to evaluate the old value. v is not an allocated block, as v directly appears as a variable in the program; so no explicit check is required here (unlike in set) *) let current_val = if Cil.isIntegralType v.vtype then begin assert (offs = NoOffset); - `Bot + VD.Bot end else eval_rv_keep_bot (Analyses.ask_of_ctx ctx) ctx.global ctx.local (Lval (Var v, NoOffset)) in begin match current_val with - | `Bot -> (* current value is VD `Bot *) - begin match Addr.to_var_offset (AD.choose lval_val) with + | Bot -> (* current value is VD Bot *) + begin match Addr.to_mval (AD.choose lval_val) with | Some (x,offs) -> let t = v.vtype in let iv = VD.bot_value ~varAttr:v.vattr t in (* correct bottom value for top level variable *) if M.tracing then M.tracel "set" "init bot value: %a\n" VD.pretty iv; - let nv = VD.update_offset (Analyses.ask_of_ctx ctx) iv offs rval_val (Some (Lval lval)) lval t in (* do desired update to value *) - set_savetop ~ctx (Analyses.ask_of_ctx ctx) ctx.global ctx.local (AD.from_var v) lval_t nv ~lval_raw:lval ~rval_raw:rval (* set top-level variable to updated value *) + let nv = VD.update_offset (Queries.to_value_domain_ask (Analyses.ask_of_ctx ctx)) iv offs rval_val (Some (Lval lval)) lval t in (* do desired update to value *) + set_savetop ~ctx (Analyses.ask_of_ctx ctx) ctx.global ctx.local (AD.of_var v) lval_t nv ~lval_raw:lval ~rval_raw:rval (* set top-level variable to updated value *) | None -> set_savetop ~ctx (Analyses.ask_of_ctx ctx) ctx.global ctx.local lval_val lval_t rval_val ~lval_raw:lval ~rval_raw:rval end @@ -1731,7 +1767,7 @@ struct (* First we want to see, if we can determine a dead branch: *) match valu with (* For a boolean value: *) - | `Int value -> + | Int value -> if M.tracing then M.traceu "branch" "Expression %a evaluated to %a\n" d_exp exp ID.pretty value; begin match ID.to_bool value with | Some v -> @@ -1746,11 +1782,11 @@ struct refine () (* like fallback below *) end (* for some reason refine () can refine these, but not raise Deadcode in struct *) - | `Address ad when tv && AD.is_null ad -> + | Address ad when tv && AD.is_null ad -> raise Deadcode - | `Address ad when not tv && AD.is_not_null ad -> + | Address ad when not tv && AD.is_not_null ad -> raise Deadcode - | `Bot -> + | Bot -> if M.tracing then M.traceu "branch" "The branch %B is dead!\n" tv; raise Deadcode (* Otherwise we try to impose an invariant: *) @@ -1761,7 +1797,7 @@ struct let body ctx f = (* First we create a variable-initvalue pair for each variable *) - let init_var v = (AD.from_var v, v.vtype, VD.init_value ~varAttr:v.vattr v.vtype) in + let init_var v = (AD.of_var v, v.vtype, VD.init_value ~varAttr:v.vattr v.vtype) in (* Apply it to all the locals and then assign them all *) let inits = List.map init_var f.slocals in set_many ~ctx (Analyses.ask_of_ctx ctx) ctx.global ctx.local inits @@ -1779,7 +1815,7 @@ struct Priv.enter_multithreaded (Analyses.ask_of_ctx ctx) (priv_getg ctx.global) (priv_sideg ctx.sideg) st | _ -> let locals = List.filter (fun v -> not (WeakUpdates.mem v st.weak)) (fundec.sformals @ fundec.slocals) in - let nst_part = rem_many_partitioning (Analyses.ask_of_ctx ctx) ctx.local locals in + let nst_part = rem_many_partitioning (Queries.to_value_domain_ask (Analyses.ask_of_ctx ctx)) ctx.local locals in let nst: store = rem_many (Analyses.ask_of_ctx ctx) nst_part locals in match exp with | None -> nst @@ -1825,7 +1861,7 @@ struct collect_funargs ask ~warn gs st exps else ( let mpt e = match eval_rv_address ask gs st e with - | `Address a -> AD.remove NullPtr a + | Address a -> AD.remove NullPtr a | _ -> AD.empty () in List.map mpt exps @@ -1837,9 +1873,9 @@ struct (* To invalidate a single address, we create a pair with its corresponding * top value. *) let invalidate_address st a = - let t = AD.get_type a in + let t = AD.type_of a in let v = get ask gs st a None in (* None here is ok, just causes us to be a bit less precise *) - let nv = VD.invalidate_value ask t v in + let nv = VD.invalidate_value (Queries.to_value_domain_ask ask) t v in (a, t, nv) in (* We define the function that invalidates all the values that an address @@ -1874,13 +1910,13 @@ struct Otherwise thread is analyzed with no global inits, reading globals gives bot, which turns into top, which might get published... sync `Thread doesn't help us here, it's not specific to entering multithreaded mode. EnterMultithreaded events only execute after threadenter and threadspawn. *) - if not (ThreadFlag.is_multi (Analyses.ask_of_ctx ctx)) then + if not (ThreadFlag.has_ever_been_multi (Analyses.ask_of_ctx ctx)) then ignore (Priv.enter_multithreaded (Analyses.ask_of_ctx ctx) (priv_getg ctx.global) (priv_sideg ctx.sideg) st); Priv.threadenter (Analyses.ask_of_ctx ctx) st ) else (* use is_global to account for values that became globals because they were saved into global variables *) let globals = CPA.filter (fun k v -> is_global (Analyses.ask_of_ctx ctx) k) st.cpa in - (* let new_cpa = if !GU.earlyglobs || ThreadFlag.is_multi ctx.ask then CPA.filter (fun k v -> is_private ctx.ask ctx.local k) globals else globals in *) + (* let new_cpa = if !earlyglobs || ThreadFlag.is_multi ctx.ask then CPA.filter (fun k v -> is_private ctx.ask ctx.local k) globals else globals in *) let new_cpa = globals in {st with cpa = new_cpa} in @@ -1895,7 +1931,7 @@ struct (* Projection to Precision of the Callee *) let p = PU.int_precision_from_fundec fundec in - let new_cpa = project (Analyses.ask_of_ctx ctx) (Some p) new_cpa fundec in + let new_cpa = project (Queries.to_value_domain_ask (Analyses.ask_of_ctx ctx)) (Some p) new_cpa fundec in (* Identify locals of this fundec for which an outer copy (from a call down the callstack) is reachable *) let reachable_other_copies = List.filter (fun v -> match Cilfacade.find_scope_fundec v with Some scope -> CilType.Fundec.equal scope fundec | None -> false) reachable in @@ -1908,7 +1944,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 *) @@ -1949,7 +1985,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. @@ -1962,9 +1998,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 ", " d_varinfo) 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 *) @@ -1976,7 +2012,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 @@ -1999,6 +2035,90 @@ 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_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 -> + 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 + ) 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 -> @@ -2009,14 +2129,76 @@ struct let addr_type_of_exp exp = let lval = mkMem ~addr:(Cil.stripCasts exp) ~off:NoOffset in let addr = eval_lv (Analyses.ask_of_ctx ctx) ctx.global ctx.local lval in - (addr, AD.get_type addr) + (addr, AD.type_of addr) in - let forks = forkfun ctx lv f args in - if M.tracing then if not (List.is_empty forks) then M.tracel "spawn" "Base.special %s: spawning functions %a\n" f.vname (d_list "," d_varinfo) (List.map BatTuple.Tuple3.second forks); - List.iter (BatTuple.Tuple3.uncurry ctx.spawn) forks; + 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 ~multiple)) forks; let st: store = ctx.local in let gs = ctx.global in let desc = LF.find f in + 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) && 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 + VD.top_value (unrollType dest_typ) + in + set ~ctx (Analyses.ask_of_ctx ctx) gs st dest_a dest_typ value in + (* for string functions *) + let eval_n = function + (* if only n characters of a given string are needed, evaluate expression n to an integer option *) + | Some n -> + begin match eval_rv (Analyses.ask_of_ctx ctx) gs st n with + | Int i -> + begin match ID.to_int i with + | Some x -> Some (Z.to_int x) + | _ -> Some (-1) + end + | _ -> Some (-1) + end + (* do nothing if all characters are needed *) + | _ -> None + in + let string_manipulation s1 s2 lv all op = + let s1_a, s1_typ = addr_type_of_exp s1 in + let s2_a, s2_typ = addr_type_of_exp s2 in + match lv, op with + | Some lv_val, Some f -> + (* when whished types coincide, compute result of operation op, otherwise use top *) + let lv_a = eval_lv (Analyses.ask_of_ctx ctx) gs st lv_val in + let lv_typ = Cilfacade.typeOfLval lv_val in + if all && typeSig s1_typ = typeSig s2_typ && typeSig s2_typ = typeSig lv_typ then (* all types need to coincide *) + lv_a, lv_typ, (f s1_a s2_a) + else if not all && typeSig s1_typ = typeSig s2_typ then (* only the types of s1 and s2 need to coincide *) + lv_a, lv_typ, (f s1_a s2_a) + else + lv_a, lv_typ, (VD.top_value (unrollType lv_typ)) + | _ -> + (* check if s1 is potentially a string literal as writing to it would be undefined behavior; then return top *) + let _ = AD.string_writing_defined s1_a in + s1_a, s1_typ, VD.top_value (unrollType s1_typ) + in let st = match desc.special args, f.vname with | Memset { dest; ch; count; }, _ -> (* TODO: check count *) @@ -2024,7 +2206,7 @@ struct let dest_a, dest_typ = addr_type_of_exp dest in let value = match eval_ch with - | `Int i when ID.to_int i = Some Z.zero -> + | Int i when ID.to_int i = Some Z.zero -> VD.zero_init_value dest_typ | _ -> VD.top_value dest_typ @@ -2036,33 +2218,57 @@ 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 }, _ - | Strcpy { dest = dst; src }, _ -> - (* invalidating from interactive *) - (* let dest_a, dest_typ = addr_type_of_exp dst in - let value = VD.top_value dest_typ in - set ~ctx (Analyses.ask_of_ctx ctx) gs st dest_a dest_typ value *) - (* TODO: reuse addr_type_of_exp for master *) - (* assigning from master *) - let get_type lval = - let address = eval_lv (Analyses.ask_of_ctx ctx) gs st lval in - AD.get_type address - in - let dst_lval = mkMem ~addr:(Cil.stripCasts dst) ~off:NoOffset in - let src_lval = mkMem ~addr:(Cil.stripCasts src) ~off:NoOffset in - - let dest_typ = get_type dst_lval in - let src_typ = get_type src_lval 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 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 - VD.top_value (unrollType dest_typ) - in - let dest_a = eval_lv (Analyses.ask_of_ctx ctx) gs st dst_lval in + | 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 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)) + (* strncpy(dest, src, n); *) + | Strcpy { dest = dst; src; n }, _ -> + begin match eval_n n with + | Some num -> + let dest_a, dest_typ, value = string_manipulation dst src None false None in + set ~ctx (Analyses.ask_of_ctx ctx) gs st dest_a dest_typ value + | None -> failwith "already handled in case above" + end + | Strcat { dest = dst; src; n }, _ -> + let dest_a, dest_typ, value = string_manipulation dst src None false None in set ~ctx (Analyses.ask_of_ctx ctx) gs st dest_a dest_typ value + | Strlen s, _ -> + begin match lv with + | Some lv_val -> + let dest_a = eval_lv (Analyses.ask_of_ctx ctx) gs st lv_val in + let dest_typ = Cilfacade.typeOfLval lv_val in + let lval = mkMem ~addr:(Cil.stripCasts s) ~off:NoOffset in + let address = eval_lv (Analyses.ask_of_ctx ctx) gs st lval in + let (value:value) = Int(AD.to_string_length address) in + set ~ctx (Analyses.ask_of_ctx ctx) gs st dest_a dest_typ value + | None -> st + end + | Strstr { haystack; needle }, _ -> + begin match lv with + | Some _ -> + (* when haystack, needle and dest type coincide, check if needle is a substring of haystack: + if that is the case, assign the substring of haystack starting at the first occurrence of needle to dest, + else use top *) + let dest_a, dest_typ, value = string_manipulation haystack needle lv true (Some (fun h_a n_a -> Address(AD.substring_extraction h_a n_a))) in + set ~ctx (Analyses.ask_of_ctx ctx) gs st dest_a dest_typ value + | None -> st + end + | Strcmp { s1; s2; n }, _ -> + begin match lv with + | Some _ -> + (* when s1 and s2 type coincide, compare both both strings completely or their first n characters, otherwise use top *) + let dest_a, dest_typ, value = string_manipulation s1 s2 lv false (Some (fun s1_a s2_a -> Int(AD.string_comparison s1_a s2_a (eval_n n)))) in + set ~ctx (Analyses.ask_of_ctx ctx) gs st dest_a dest_typ value + | None -> st + end | Abort, _ -> raise Deadcode | ThreadExit { ret_val = exp }, _ -> begin match ThreadId.get_current (Analyses.ask_of_ctx ctx) with @@ -2080,6 +2286,26 @@ struct | _ -> () end; raise Deadcode + | MutexAttrSetType {attr = attr; typ = mtyp}, _ -> + begin + let get_type lval = + let address = eval_lv (Analyses.ask_of_ctx ctx) gs st lval in + AD.type_of address + in + let dst_lval = mkMem ~addr:(Cil.stripCasts attr) ~off:NoOffset in + let dest_typ = get_type dst_lval in + let dest_a = eval_lv (Analyses.ask_of_ctx ctx) gs st dst_lval in + match eval_rv (Analyses.ask_of_ctx ctx) gs st mtyp with + | Int x -> + begin + match ID.to_int x with + | Some z -> + if M.tracing then M.tracel "attr" "setting\n"; + set ~ctx (Analyses.ask_of_ctx ctx) gs st dest_a dest_typ (MutexAttr (ValueDomain.MutexAttr.of_int z)) + | None -> set ~ctx (Analyses.ask_of_ctx ctx) gs st dest_a dest_typ (MutexAttr (ValueDomain.MutexAttr.top ())) + end + | _ -> set ~ctx (Analyses.ask_of_ctx ctx) gs st dest_a dest_typ (MutexAttr (ValueDomain.MutexAttr.top ())) + end | Identity e, _ -> begin match lv with | Some x -> assign ctx x e @@ -2090,7 +2316,7 @@ struct let apply_unary fk float_fun x = let eval_x = eval_rv (Analyses.ask_of_ctx ctx) gs st x in begin match eval_x with - | `Float float_x -> float_fun (FD.cast_to fk float_x) + | Float float_x -> float_fun (FD.cast_to fk float_x) | _ -> failwith ("non-floating-point argument in call to function "^f.vname) end in @@ -2098,38 +2324,38 @@ struct let eval_x = eval_rv (Analyses.ask_of_ctx ctx) gs st x in let eval_y = eval_rv (Analyses.ask_of_ctx ctx) gs st y in begin match eval_x, eval_y with - | `Float float_x, `Float float_y -> float_fun (FD.cast_to fk float_x) (FD.cast_to fk float_y) + | Float float_x, Float float_y -> float_fun (FD.cast_to fk float_x) (FD.cast_to fk float_y) | _ -> failwith ("non-floating-point argument in call to function "^f.vname) end in - let result = + let result:value = begin match fun_args with - | Nan (fk, str) when Cil.isPointerType (Cilfacade.typeOf str) -> `Float (FD.nan_of fk) + | Nan (fk, str) when Cil.isPointerType (Cilfacade.typeOf str) -> Float (FD.nan_of fk) | Nan _ -> failwith ("non-pointer argument in call to function "^f.vname) - | Inf fk -> `Float (FD.inf_of fk) - | Isfinite x -> `Int (ID.cast_to IInt (apply_unary FDouble FD.isfinite x)) - | Isinf x -> `Int (ID.cast_to IInt (apply_unary FDouble FD.isinf x)) - | Isnan x -> `Int (ID.cast_to IInt (apply_unary FDouble FD.isnan x)) - | Isnormal x -> `Int (ID.cast_to IInt (apply_unary FDouble FD.isnormal x)) - | Signbit x -> `Int (ID.cast_to IInt (apply_unary FDouble FD.signbit x)) - | Ceil (fk,x) -> `Float (apply_unary fk FD.ceil x) - | Floor (fk,x) -> `Float (apply_unary fk FD.floor x) - | Fabs (fk, x) -> `Float (apply_unary fk FD.fabs x) - | Acos (fk, x) -> `Float (apply_unary fk FD.acos x) - | Asin (fk, x) -> `Float (apply_unary fk FD.asin x) - | Atan (fk, x) -> `Float (apply_unary fk FD.atan x) - | Atan2 (fk, y, x) -> `Float (apply_binary fk (fun y' x' -> FD.atan (FD.div y' x')) y x) - | Cos (fk, x) -> `Float (apply_unary fk FD.cos x) - | Sin (fk, x) -> `Float (apply_unary fk FD.sin x) - | Tan (fk, x) -> `Float (apply_unary fk FD.tan x) - | Isgreater (x,y) -> `Int(ID.cast_to IInt (apply_binary FDouble FD.gt x y)) - | Isgreaterequal (x,y) -> `Int(ID.cast_to IInt (apply_binary FDouble FD.ge x y)) - | Isless (x,y) -> `Int(ID.cast_to IInt (apply_binary FDouble FD.lt x y)) - | Islessequal (x,y) -> `Int(ID.cast_to IInt (apply_binary FDouble FD.le x y)) - | Islessgreater (x,y) -> `Int(ID.logor (ID.cast_to IInt (apply_binary FDouble FD.lt x y)) (ID.cast_to IInt (apply_binary FDouble FD.gt x y))) - | 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) + | Inf fk -> Float (FD.inf_of fk) + | Isfinite x -> Int (ID.cast_to IInt (apply_unary FDouble FD.isfinite x)) + | Isinf x -> Int (ID.cast_to IInt (apply_unary FDouble FD.isinf x)) + | Isnan x -> Int (ID.cast_to IInt (apply_unary FDouble FD.isnan x)) + | Isnormal x -> Int (ID.cast_to IInt (apply_unary FDouble FD.isnormal x)) + | Signbit x -> Int (ID.cast_to IInt (apply_unary FDouble FD.signbit x)) + | Ceil (fk,x) -> Float (apply_unary fk FD.ceil x) + | Floor (fk,x) -> Float (apply_unary fk FD.floor x) + | Fabs (fk, x) -> Float (apply_unary fk FD.fabs x) + | Acos (fk, x) -> Float (apply_unary fk FD.acos x) + | Asin (fk, x) -> Float (apply_unary fk FD.asin x) + | Atan (fk, x) -> Float (apply_unary fk FD.atan x) + | Atan2 (fk, y, x) -> Float (apply_binary fk (fun y' x' -> FD.atan (FD.div y' x')) y x) + | Cos (fk, x) -> Float (apply_unary fk FD.cos x) + | Sin (fk, x) -> Float (apply_unary fk FD.sin x) + | Tan (fk, x) -> Float (apply_unary fk FD.tan x) + | Isgreater (x,y) -> Int(ID.cast_to IInt (apply_binary FDouble FD.gt x y)) + | Isgreaterequal (x,y) -> Int(ID.cast_to IInt (apply_binary FDouble FD.ge x y)) + | Isless (x,y) -> Int(ID.cast_to IInt (apply_binary FDouble FD.lt x y)) + | Islessequal (x,y) -> Int(ID.cast_to IInt (apply_binary FDouble FD.le x y)) + | Islessgreater (x,y) -> Int(ID.logor (ID.cast_to IInt (apply_binary FDouble FD.lt x y)) (ID.cast_to IInt (apply_binary FDouble FD.gt x y))) + | 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) end in begin match lv with @@ -2143,10 +2369,10 @@ struct | ThreadJoin { thread = id; ret_var }, _ -> let st' = match (eval_rv (Analyses.ask_of_ctx ctx) gs st ret_var) with - | `Int n when GobOption.exists (BI.equal BI.zero) (ID.to_int n) -> st - | `Address ret_a -> + | Int n when GobOption.exists (BI.equal BI.zero) (ID.to_int n) -> st + | Address ret_a -> begin match eval_rv (Analyses.ask_of_ctx ctx) gs st id with - | `Thread a -> + | Thread a -> let v = List.fold VD.join (VD.bot ()) (List.map (fun x -> G.thread (ctx.global (V.thread x))) (ValueDomain.Threads.elements a)) in (* TODO: is this type right? *) set ~ctx (Analyses.ask_of_ctx ctx) gs st ret_a (Cilfacade.typeOf ret_var) v @@ -2159,52 +2385,75 @@ 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.from_var (heap_var ctx)) AD.null_ptr - else AD.from_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)); - (eval_lv (Analyses.ask_of_ctx ctx) gs st lv, (Cilfacade.typeOfLval lv), `Address heap_var)] + 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 | 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.from_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.from_var_offset (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_invalid_mem_dealloc ctx f p; begin match lv with | Some lv -> let ask = Analyses.ask_of_ctx ctx in let p_rv = eval_rv ask gs st p in let p_addr = match p_rv with - | `Address a -> a + | Address a -> a (* TODO: don't we already have logic for this? *) - | `Int i when ID.to_int i = Some BI.zero -> AD.null_ptr - | `Int i -> AD.top_ptr + | Int i when ID.to_int i = Some BI.zero -> AD.null_ptr + | Int i -> AD.top_ptr | _ -> AD.top_ptr (* TODO: why does this ever happen? *) in let p_addr' = AD.remove NullPtr p_addr in (* realloc with NULL is same as malloc, remove to avoid unknown value from NullPtr access *) let p_addr_get = get 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 = `Blob (p_addr_get, size_int, true) in (* copy old contents with new size *) - let heap_addr = AD.from_var (heap_var ctx) 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 false ctx) in let heap_addr' = if get_bool "sem.malloc.fail" then AD.join heap_addr AD.null_ptr @@ -2214,40 +2463,108 @@ struct let lv_addr = eval_lv ask gs st lv in set_many ~ctx ask gs st [ (heap_addr, TVoid [], heap_val); - (lv_addr, Cilfacade.typeOfLval lv, `Address heap_addr'); + (lv_addr, Cilfacade.typeOfLval lv, Address heap_addr'); ] (* TODO: free (i.e. invalidate) old blob if successful? *) | None -> st end + | Free ptr, _ -> + (* Free shouldn't be passed non-dynamically allocated memory *) + check_invalid_mem_dealloc ctx f ptr; + st | Assert { exp; refine; _ }, _ -> assert_fn ctx exp refine - | _, _ -> begin - let st = - special_unknown_invalidate ctx (Analyses.ask_of_ctx ctx) gs st f args - (* - * TODO: invalidate vars reachable via args - * publish globals - * if single-threaded: *call f*, privatize globals - * else: spawn f - *) - in - (* invalidate lhs in case of assign *) - let st = invalidate_ret_lv st in - (* apply all registered abstract effects from other analysis on the base value domain *) - LibraryFunctionEffects.effects_for f.vname args - |> List.to_seq - |> Seq.map (fun sets -> - BatList.fold_left (fun acc (lv, x) -> - set ~ctx (Analyses.ask_of_ctx ctx) ctx.global acc (eval_lv (Analyses.ask_of_ctx ctx) ctx.global acc lv) (Cilfacade.typeOfLval lv) x - ) st sets - ) - |> Seq.fold_left D.meet st - - (* List.map (fun f -> f (fun lv -> (fun x -> set ~ctx:(Some ctx) ctx.ask ctx.global st (eval_lv ctx.ask ctx.global st lv) (Cilfacade.typeOfLval lv) x))) (LF.effects_for f.vname args) |> BatList.fold_left D.meet st *) + | Setjmp { env }, _ -> + let ask = Analyses.ask_of_ctx ctx in + let st' = match eval_rv ask gs st env with + | Address jmp_buf -> + let value = VD.JmpBuf (ValueDomain.JmpBufs.Bufs.singleton (Target (ctx.prev_node, ctx.control_context ())), false) in + let r = set ~ctx ask gs st jmp_buf (Cilfacade.typeOf env) value in + if M.tracing then M.tracel "setjmp" "setting setjmp %a on %a -> %a\n" d_exp env D.pretty st D.pretty r; + r + | _ -> failwith "problem?!" + in + begin match lv with + | Some lv -> + set ~ctx ask gs st' (eval_lv ask ctx.global st lv) (Cilfacade.typeOfLval lv) (Int (ID.of_int IInt BI.zero)) + | None -> st' end + | Longjmp {env; value}, _ -> + let ask = Analyses.ask_of_ctx ctx in + let ensure_not_zero (rv:value) = match rv with + | Int i -> + begin match ID.to_bool i with + | Some true -> rv + | Some false -> + M.error "Must: Longjmp with a value of 0 is silently changed to 1"; + Int (ID.of_int (ID.ikind i) Z.one) + | None -> + M.warn "May: Longjmp with a value of 0 is silently changed to 1"; + let ik = ID.ikind i in + Int (ID.join (ID.meet i (ID.of_excl_list ik [Z.zero])) (ID.of_int ik Z.one)) + end + | _ -> + M.warn ~category:Program "Arguments to longjmp are strange!"; + rv + in + let rv = ensure_not_zero @@ eval_rv ask ctx.global ctx.local value in + let t = Cilfacade.typeOf value in + set ~ctx ~t_override:t ask ctx.global ctx.local (AD.of_var !longjmp_return) t rv (* Not raising Deadcode here, deadcode is raised at a higher level! *) + | Rand, _ -> + begin match lv with + | Some x -> + let result:value = (Int (ID.starting IInt Z.zero)) in + set ~ctx (Analyses.ask_of_ctx ctx) gs st (eval_lv (Analyses.ask_of_ctx ctx) ctx.global st x) (Cilfacade.typeOfLval x) result + | None -> st + end + | _, _ -> + let st = + special_unknown_invalidate ctx (Analyses.ask_of_ctx ctx) gs st f args + (* + * TODO: invalidate vars reachable via args + * publish globals + * if single-threaded: *call f*, privatize globals + * else: spawn f + *) + in + (* invalidate lhs in case of assign *) + invalidate_ret_lv st in if get_bool "sem.noreturn.dead_code" && Cil.hasAttribute "noreturn" f.vattr then raise Deadcode else st - let combine ctx (lval: lval option) fexp (f: fundec) (args: exp list) fc (after: D.t) : D.t = + let combine_st ctx (local_st : store) (fun_st : store) (tainted_lvs : AD.t) : store = + let ask = (Analyses.ask_of_ctx ctx) in + 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) = if M.tracing then M.tracel "combine" "%a\n%a\n" CPA.pretty st.cpa CPA.pretty fun_st.cpa; (* This function does miscellaneous things, but the main task was to give the @@ -2258,37 +2575,73 @@ struct let add_globals (st: store) (fun_st: store) = (* Remove the return value as this is dealt with separately. *) let cpa_noreturn = CPA.remove (return_varinfo ()) fun_st.cpa in - let cpa_local = CPA.filter (fun x _ -> not (is_global (Analyses.ask_of_ctx ctx) x)) st.cpa in - let cpa' = CPA.fold CPA.add cpa_noreturn cpa_local in (* add cpa_noreturn to cpa_local *) - { fun_st with cpa = cpa' } + 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 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 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'; + { fun_st with cpa = cpa' } + else + (* remove variables from caller cpa, that are global and not in the callee cpa *) + let cpa_caller = CPA.filter (fun x _ -> (not (is_global ask x)) || CPA.mem x fun_st.cpa) st.cpa in + if M.tracing then M.trace "taintPC" "cpa_caller: %a\n" CPA.pretty cpa_caller; + (* add variables from callee that are not in caller yet *) + let cpa_new = CPA.filter (fun x _ -> not (CPA.mem x cpa_caller)) cpa_noreturn in + if M.tracing then M.trace "taintPC" "cpa_new: %a\n" CPA.pretty cpa_new; + 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 = 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 } in + let nst = add_globals st fun_st in + + (* Projection to Precision of the Caller *) + let p = PrecisionUtil.int_precision_from_node () in (* Since f is the fundec of the Callee we have to get the fundec of the current Node instead *) + let callerFundec = match !MyCFG.current_node with + | Some n -> Node.find_fundec n + | None -> failwith "callerfundec not found" + in + let cpa' = project (Queries.to_value_domain_ask (Analyses.ask_of_ctx ctx)) (Some p) nst.cpa callerFundec in + + if get_bool "sem.noreturn.dead_code" && Cil.hasAttribute "noreturn" f.svar.vattr then raise Deadcode; + + { nst with cpa = cpa'; weak = st.weak } (* keep weak from caller *) + in + combine_one ctx.local au + + let combine_assign ctx (lval: lval option) fexp (f: fundec) (args: exp list) fc (after: D.t) (f_ask: Q.ask) : D.t = + let combine_one (st: D.t) (fun_st: D.t) = let return_var = return_var () in let return_val = if CPA.mem (return_varinfo ()) fun_st.cpa then get (Analyses.ask_of_ctx ctx) ctx.global fun_st return_var None else VD.top () in - let nst = add_globals st fun_st in (* Projection to Precision of the Caller *) - let p = PrecisionUtil.int_precision_from_node ()in (* Since f is the fundec of the Callee we have to get the fundec of the current Node instead *) + let p = PrecisionUtil.int_precision_from_node () in (* Since f is the fundec of the Callee we have to get the fundec of the current Node instead *) let callerFundec = match !MyCFG.current_node with | Some n -> Node.find_fundec n | None -> failwith "callerfundec not found" in - let return_val = project_val (Analyses.ask_of_ctx ctx) (attributes_varinfo (return_varinfo ()) callerFundec) (Some p) return_val (is_privglob (return_varinfo ())) in - let cpa' = project (Analyses.ask_of_ctx ctx) (Some p) nst.cpa callerFundec in - - if get_bool "sem.noreturn.dead_code" && Cil.hasAttribute "noreturn" f.svar.vattr then raise Deadcode; + let return_val = project_val (Queries.to_value_domain_ask (Analyses.ask_of_ctx ctx)) (attributes_varinfo (return_varinfo ()) callerFundec) (Some p) return_val (is_privglob (return_varinfo ())) in - let st = { nst with cpa = cpa'; weak = st.weak } in (* keep weak from caller *) match lval with | None -> st | Some lval -> set_savetop ~ctx (Analyses.ask_of_ctx ctx) ctx.global st (eval_lv (Analyses.ask_of_ctx ctx) ctx.global st lval) (Cilfacade.typeOfLval lval) return_val 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] @@ -2298,7 +2651,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 @@ -2310,7 +2663,7 @@ struct | None -> () end; (* D.join ctx.local @@ *) - ctx.local + Priv.threadspawn (Analyses.ask_of_ctx ctx) (priv_getg ctx.global) (priv_sideg ctx.sideg) ctx.local let unassume (ctx: (D.t, _, _, _) ctx) e uuids = (* TODO: structural unassume instead of invariant hack *) @@ -2335,19 +2688,20 @@ struct let asked' = Queries.Set.add anyq asked in let r: a Queries.result = match q with - | MustBeSingleThreaded when single -> true + | MustBeSingleThreaded _ when single -> true | MayEscape _ | MayBePublic _ | MayBePublicWithout _ | MustBeProtectedBy _ | MustLockset | MustBeAtomic - | MustBeSingleThreaded + | MustBeSingleThreaded _ | MustBeUniqueThread | CurrentThreadId | MayBeThreadReturn | PartAccess _ | IsHeapVar _ + | IsAllocVar _ | IsMultiple _ | CreatedThreads | MustJoinedThreads -> @@ -2394,19 +2748,20 @@ struct (* all updates happen in ctx with top values *) let get_var = get_var let get a gs st addrs exp = get a gs st addrs exp - let set a ~ctx gs st lval lval_type value = set a ~ctx ~invariant:false gs st lval lval_type value (* TODO: should have invariant false? doesn't work with empty cpa then, because meets *) + let set a ~ctx gs st lval lval_type ?lval_raw value = set a ~ctx ~invariant:false gs st lval lval_type ?lval_raw value (* TODO: should have invariant false? doesn't work with empty cpa then, because meets *) let refine_entire_var = false let map_oldval oldval t_lval = if VD.is_bot oldval then VD.top_value t_lval else oldval let eval_rv_lval_refine a gs st exp lv = (* new, use different ctx for eval_lv (for Mem): *) - let do_offs def o = def in (* HACK: no do_offs blessed here *) - eval_rv_base_lval ~eval_lv ~do_offs a gs st exp lv + eval_rv_base_lval ~eval_lv a gs st exp lv (* don't meet with current octx values when propagating inverse operands down *) let id_meet_down ~old ~c = c let fd_meet_down ~old ~c = c + + let contra st = st end in let module Unassume = BaseInvariant.Make (UnassumeEval) in @@ -2436,7 +2791,7 @@ struct let e_d' = WideningTokens.with_side_tokens (WideningTokens.TS.of_list uuids) (fun () -> CPA.fold (fun x v acc -> - let addr: AD.t = AD.from_var_offset (x, `NoOffset) in + let addr: AD.t = AD.of_mval (x, `NoOffset) in set (Analyses.ask_of_ctx ctx) ~ctx ~invariant:false ctx.global acc addr x.vtype v ) e_d.cpa ctx.local ) @@ -2446,10 +2801,10 @@ struct let event ctx e octx = let st: store = ctx.local in match e with - | Events.Lock (addr, _) when ThreadFlag.is_multi (Analyses.ask_of_ctx ctx) -> (* TODO: is this condition sound? *) + | Events.Lock (addr, _) when ThreadFlag.has_ever_been_multi (Analyses.ask_of_ctx ctx) -> (* TODO: is this condition sound? *) if M.tracing then M.tracel "priv" "LOCK EVENT %a\n" LockDomain.Addr.pretty addr; Priv.lock (Analyses.ask_of_ctx ctx) (priv_getg ctx.global) st addr - | Events.Unlock addr when ThreadFlag.is_multi (Analyses.ask_of_ctx ctx) -> (* TODO: is this condition sound? *) + | Events.Unlock addr when ThreadFlag.has_ever_been_multi (Analyses.ask_of_ctx ctx) -> (* TODO: is this condition sound? *) if addr = UnknownPtr then M.info ~category:Unsound "Unknown mutex unlocked, base privatization unsound"; (* TODO: something more sound *) WideningTokens.with_local_side_tokens (fun () -> @@ -2461,11 +2816,18 @@ struct Priv.enter_multithreaded (Analyses.ask_of_ctx ctx) (priv_getg ctx.global) (priv_sideg ctx.sideg) st | Events.AssignSpawnedThread (lval, tid) -> (* TODO: is this type right? *) - set ~ctx (Analyses.ask_of_ctx ctx) ctx.global ctx.local (eval_lv (Analyses.ask_of_ctx ctx) ctx.global ctx.local lval) (Cilfacade.typeOfLval lval) (`Thread (ValueDomain.Threads.singleton tid)) + set ~ctx (Analyses.ask_of_ctx ctx) ctx.global ctx.local (eval_lv (Analyses.ask_of_ctx ctx) ctx.global ctx.local lval) (Cilfacade.typeOfLval lval) (Thread (ValueDomain.Threads.singleton tid)) | Events.Assert exp -> assert_fn ctx exp true | Events.Unassume {exp; uuids} -> Timing.wrap "base unassume" (unassume ctx exp) uuids + | Events.Longjmped {lval} -> + begin match lval with + | Some lval -> + let st' = assign ctx lval (Lval (Cil.var !longjmp_return)) in + {st' with cpa = CPA.remove !longjmp_return st'.cpa} + | None -> ctx.local + end | _ -> ctx.local end @@ -2475,8 +2837,6 @@ module type MainSpec = sig include BaseDomain.ExpEvaluator val return_lval: unit -> Cil.lval val return_varinfo: unit -> Cil.varinfo - type extra = (varinfo * Offs.t * bool) list - val context_cpa: fundec -> D.t -> BaseDomain.CPA.t end let main_module: (module MainSpec) Lazy.t = diff --git a/src/analyses/baseInvariant.ml b/src/analyses/baseInvariant.ml index c1026a7a65..72e00efbb1 100644 --- a/src/analyses/baseInvariant.ml +++ b/src/analyses/baseInvariant.ml @@ -1,4 +1,5 @@ -open Prelude.Ana +(** {!Analyses.Spec.branch} refinement for {!Base} analysis. *) + open GoblintCil module M = Messages @@ -17,11 +18,11 @@ sig val eval_rv: Queries.ask -> (V.t -> G.t) -> D.t -> exp -> VD.t val eval_rv_address: Queries.ask -> (V.t -> G.t) -> D.t -> exp -> VD.t val eval_lv: Queries.ask -> (V.t -> G.t) -> D.t -> lval -> AD.t - val convert_offset: Queries.ask -> (V.t -> G.t) -> D.t -> offset -> (fieldinfo, ID.t) Lval.offs + val convert_offset: Queries.ask -> (V.t -> G.t) -> D.t -> offset -> ID.t Offset.t val get_var: Queries.ask -> (V.t -> G.t) -> D.t -> varinfo -> VD.t val get: Queries.ask -> (V.t -> G.t) -> D.t -> AD.t -> exp option -> VD.t - val set: Queries.ask -> ctx:(D.t, G.t, _, V.t) Analyses.ctx -> (V.t -> G.t) -> D.t -> AD.t -> typ -> VD.t -> D.t + val set: Queries.ask -> ctx:(D.t, G.t, _, V.t) Analyses.ctx -> (V.t -> G.t) -> D.t -> AD.t -> typ -> ?lval_raw:lval -> VD.t -> D.t val refine_entire_var: bool val map_oldval: VD.t -> typ -> VD.t @@ -29,6 +30,12 @@ sig val id_meet_down: old:ID.t -> c:ID.t -> ID.t val fd_meet_down: old:FD.t -> c:FD.t -> FD.t + + (** Handle contradiction. + + Normal branch refinement just raises {!Analyses.Deadcode}. + Unassume leaves unchanged. *) + val contra: D.t -> D.t end module Make (Eval: Eval) = @@ -45,61 +52,58 @@ struct (* other unary operators are not implemented on float values *) | _ -> (fun c -> FD.top_of (FD.get_fkind c)) - let is_some_bot x = + let is_some_bot (x:VD.t) = match x with - | `Bot -> false (* HACK: bot is here due to typing conflict (we do not cast appropriately) *) + | Bot -> false (* HACK: bot is here due to typing conflict (we do not cast appropriately) *) | _ -> VD.is_bot_value x - let apply_invariant oldv newv = - match oldv, newv with - (* | `Address o, `Address n when AD.mem (Addr.unknown_ptr ()) o && AD.mem (Addr.unknown_ptr ()) n -> *) - (* `Address (AD.join o n) *) - (* | `Address o, `Address n when AD.mem (Addr.unknown_ptr ()) o -> `Address n *) - (* | `Address o, `Address n when AD.mem (Addr.unknown_ptr ()) n -> `Address o *) - | _ -> VD.meet oldv newv + let apply_invariant ~old_val ~new_val = + try + VD.meet old_val new_val + with Lattice.Uncomparable -> old_val let refine_lv_fallback ctx a gs st lval value tv = if M.tracing then M.tracec "invariant" "Restricting %a with %a\n" d_lval lval VD.pretty value; let addr = eval_lv a gs st lval in if (AD.is_top addr) then st else - let oldval = get a gs st addr None in (* None is ok here, we could try to get more precise, but this is ok (reading at unknown position in array) *) + let old_val = get a gs st addr None in (* None is ok here, we could try to get more precise, but this is ok (reading at unknown position in array) *) let t_lval = Cilfacade.typeOfLval lval in - let oldval = map_oldval oldval t_lval in - let oldval = - if is_some_bot oldval then ( + let old_val = map_oldval old_val t_lval in + let old_val = + if is_some_bot old_val then ( if M.tracing then M.tracec "invariant" "%a is bot! This should not happen. Will continue with top!" d_lval lval; VD.top () ) else - oldval + old_val in let state_with_excluded = set a gs st addr t_lval value ~ctx in let value = get a gs state_with_excluded addr None in - let new_val = apply_invariant oldval value in + let new_val = apply_invariant ~old_val ~new_val:value in if M.tracing then M.traceu "invariant" "New value is %a\n" VD.pretty new_val; (* make that address meet the invariant, i.e exclusion sets will be joined *) if is_some_bot new_val then ( if M.tracing then M.tracel "branch" "C The branch %B is dead!\n" tv; - raise Analyses.Deadcode + contra st ) else if VD.is_bot new_val then set a gs st addr t_lval value ~ctx (* no *_raw because this is not a real assignment *) else set a gs st addr t_lval new_val ~ctx (* no *_raw because this is not a real assignment *) let refine_lv ctx a gs st c x c' pretty exp = - let set' lval v st = set a gs st (eval_lv a gs st lval) (Cilfacade.typeOfLval lval) v ~ctx in + let set' lval v st = set a gs st (eval_lv a gs st lval) (Cilfacade.typeOfLval lval) ~lval_raw:lval v ~ctx in match x with | Var var, o when refine_entire_var -> (* For variables, this is done at to the level of entire variables to benefit e.g. from disjunctive struct domains *) - let oldv = get_var a gs st var in - let oldv = map_oldval oldv var.vtype in + let old_val = get_var a gs st var in + let old_val = map_oldval old_val var.vtype in let offs = convert_offset a gs st o in - let newv = VD.update_offset a oldv offs c' (Some exp) x (var.vtype) in - let v = VD.meet oldv newv in - if is_some_bot v then raise Analyses.Deadcode + let new_val = VD.update_offset (Queries.to_value_domain_ask a) old_val offs c' (Some exp) x (var.vtype) in + let v = apply_invariant ~old_val ~new_val in + if is_some_bot v then contra st else ( - if M.tracing then M.tracel "inv" "improve variable %a from %a to %a (c = %a, c' = %a)\n" d_varinfo var VD.pretty oldv VD.pretty v pretty c VD.pretty c'; + if M.tracing then M.tracel "inv" "improve variable %a from %a to %a (c = %a, c' = %a)\n" CilType.Varinfo.pretty var VD.pretty old_val VD.pretty v pretty c VD.pretty c'; let r = set' (Var var,NoOffset) v st in if M.tracing then M.tracel "inv" "st from %a to %a\n" D.pretty st D.pretty r; r @@ -107,52 +111,52 @@ struct | Var _, _ | Mem _, _ -> (* For accesses via pointers, not yet *) - let oldv = eval_rv_lval_refine a gs st exp x in - let oldv = map_oldval oldv (Cilfacade.typeOfLval x) in - let v = VD.meet oldv c' in - if is_some_bot v then raise Analyses.Deadcode + let old_val = eval_rv_lval_refine a gs st exp x in + let old_val = map_oldval old_val (Cilfacade.typeOfLval x) in + let v = apply_invariant ~old_val ~new_val:c' in + if is_some_bot v then contra st else ( - if M.tracing then M.tracel "inv" "improve lval %a from %a to %a (c = %a, c' = %a)\n" d_lval x VD.pretty oldv VD.pretty v pretty c VD.pretty c'; + if M.tracing then M.tracel "inv" "improve lval %a from %a to %a (c = %a, c' = %a)\n" d_lval x VD.pretty old_val VD.pretty v pretty c VD.pretty c'; set' x v st ) let invariant_fallback ctx a (gs:V.t -> G.t) st exp tv = (* We use a recursive helper function so that x != 0 is false can be handled * as x == 0 is true etc *) - let rec helper (op: binop) (lval: lval) (value: VD.t) (tv: bool) = + let rec helper (op: binop) (lval: lval) (value: VD.t) (tv: bool): (lval * VD.t) option = match (op, lval, value, tv) with (* The true-branch where x == value: *) | Eq, x, value, true -> if M.tracing then M.tracec "invariant" "Yes, %a equals %a\n" d_lval x VD.pretty value; (match value with - | `Int n -> + | Int n -> let ikind = Cilfacade.get_ikind_exp (Lval lval) in - Some (x, `Int (ID.cast_to ikind n)) + Some (x, Int (ID.cast_to ikind n)) | _ -> Some(x, value)) (* The false-branch for x == value: *) | Eq, x, value, false -> begin match value with - | `Int n -> begin + | Int n -> begin match ID.to_int n with | Some n -> (* When x != n, we can return a singleton exclusion set *) if M.tracing then M.tracec "invariant" "Yes, %a is not %s\n" d_lval x (BI.to_string n); let ikind = Cilfacade.get_ikind_exp (Lval lval) in - Some (x, `Int (ID.of_excl_list ikind [n])) + Some (x, Int (ID.of_excl_list ikind [n])) | None -> None end - | `Address n -> begin + | Address n -> begin if M.tracing then M.tracec "invariant" "Yes, %a is not %a\n" d_lval x AD.pretty n; match eval_rv_address a gs st (Lval x) with - | `Address a when AD.is_definite n -> - Some (x, `Address (AD.diff a n)) - | `Top when AD.is_null n -> - Some (x, `Address AD.not_null) + | Address a when AD.is_definite n -> + Some (x, Address (AD.diff a n)) + | Top when AD.is_null n -> + Some (x, Address AD.not_null) | v -> if M.tracing then M.tracec "invariant" "No address invariant for: %a != %a\n" VD.pretty v AD.pretty n; None end - (* | `Address a -> Some (x, value) *) + (* | Address a -> Some (x, value) *) | _ -> (* We can't say anything else, exclusion sets are finite, so not * being in one means an infinite number of values *) @@ -162,7 +166,7 @@ struct | Ne, x, value, _ -> helper Eq x value (not tv) | Lt, x, value, _ -> begin match value with - | `Int n -> begin + | Int n -> begin let ikind = Cilfacade.get_ikind_exp (Lval lval) in let n = ID.cast_to ikind n in let range_from x = if tv then ID.ending ikind (BI.sub x BI.one) else ID.starting ikind x in @@ -170,14 +174,14 @@ struct match limit_from n with | Some n -> if M.tracing then M.tracec "invariant" "Yes, success! %a is not %s\n\n" d_lval x (BI.to_string n); - Some (x, `Int (range_from n)) + Some (x, Int (range_from n)) | None -> None end | _ -> None end | Le, x, value, _ -> begin match value with - | `Int n -> begin + | Int n -> begin let ikind = Cilfacade.get_ikind_exp (Lval lval) in let n = ID.cast_to ikind n in let range_from x = if tv then ID.ending ikind x else ID.starting ikind (BI.add x BI.one) in @@ -185,7 +189,7 @@ struct match limit_from n with | Some n -> if M.tracing then M.tracec "invariant" "Yes, success! %a is not %s\n\n" d_lval x (BI.to_string n); - Some (x, `Int (range_from n)) + Some (x, Int (range_from n)) | None -> None end | _ -> None @@ -197,11 +201,11 @@ struct None in if M.tracing then M.traceli "invariant" "assume expression %a is %B\n" d_exp exp tv; - let null_val typ = + let null_val (typ:typ):VD.t = match Cil.unrollType typ with - | TPtr _ -> `Address AD.null_ptr + | TPtr _ -> Address AD.null_ptr | TEnum({ekind=_;_},_) - | _ -> `Int (ID.of_int (Cilfacade.get_ikind typ) BI.zero) + | _ -> Int (ID.of_int (Cilfacade.get_ikind typ) BI.zero) in let rec derived_invariant exp tv = let switchedOp = function Lt -> Gt | Gt -> Lt | Le -> Ge | Ge -> Le | x -> x in (* a op b <=> b (switchedOp op) b *) @@ -213,7 +217,7 @@ struct -> derived_invariant (BinOp (op, c1, c2, t)) tv | BinOp(op, CastE (TInt (ik, _) as t1, Lval x), rval, typ) -> (match eval_rv a gs st (Lval x) with - | `Int v -> + | Int v -> (* This is tricky: It it is not sufficient to check that ID.cast_to_ik v = v * If there is one domain that knows this to be true and the other does not, we * should still impose the invariant. E.g. i -> ([1,5]; Not {0}[byte]) *) @@ -339,11 +343,10 @@ struct in a, b | Eq | Ne as op -> - let both x = x, x in - let m = ID.meet a b in begin match op, ID.to_bool c with | Eq, Some true - | Ne, Some false -> both m (* def. equal: if they compare equal, both values must be from the meet *) + | Ne, Some false -> (* def. equal: if they compare equal, both values must be from the meet *) + (id_meet_down ~old:a ~c:b, id_meet_down ~old:b ~c:a) | Eq, Some false | Ne, Some true -> (* def. unequal *) (* Both values can not be in the meet together, but it's not sound to exclude the meet from both. @@ -362,8 +365,8 @@ struct let top_ik = ID.top_of ikind in match ID.minimal b, ID.maximal b with | Some lb, Some ub -> - let starting = if Z.equal lb x then ID.starting ikind (Z.add lb Z.one) else top_ik in - let ending = if Z.equal ub x then ID.ending ikind (Z.sub ub Z.one) else top_ik in + let starting = if Z.equal lb x then ID.starting ikind (Z.succ lb) else top_ik in + let ending = if Z.equal ub x then ID.ending ikind (Z.pred ub) else top_ik in ID.meet starting ending | _ -> top_ik @@ -407,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 @@ -541,186 +556,248 @@ struct with FloatDomain.ArithmeticOnFloatBot _ -> raise Analyses.Deadcode 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 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 raise Analyses.Deadcode; - match exp, c_typed with - | UnOp (LNot, e, _), `Int c -> - let ikind = Cilfacade.get_ikind_exp e in - let c' = - match ID.to_bool (unop_ID LNot c) with - | Some true -> - (* i.e. e should evaluate to [1,1] *) - (* LNot x is 0 for any x != 0 *) - ID.of_excl_list ikind [BI.zero] - | Some false -> ID.of_bool ikind false - | _ -> ID.top_of ikind - in - inv_exp (`Int c') e st - | UnOp (Neg, e, _), `Float c -> inv_exp (`Float (unop_FD Neg c)) e st - | UnOp ((BNot|Neg) as op, e, _), `Int c -> inv_exp (`Int (unop_ID op c)) e st - (* no equivalent for `Float, as VD.is_safe_cast fails for all float types anyways *) - | BinOp((Eq | Ne) as op, CastE (t1, e1), CastE (t2, e2), t), `Int c when typeSig (Cilfacade.typeOf e1) = typeSig (Cilfacade.typeOf e2) && VD.is_safe_cast t1 (Cilfacade.typeOf e1) && VD.is_safe_cast t2 (Cilfacade.typeOf e2) -> - inv_exp (`Int c) (BinOp (op, e1, e2, t)) st - | BinOp (LOr, arg1, arg2, typ) as exp, `Int c -> - (* copied & modified from eval_rv_base... *) - let (let*) = Option.bind in - (* split nested LOr Eqs to equality pairs, if possible *) - let rec split = function - (* copied from above to support pointer equalities with implicit casts inserted *) - | BinOp (Eq, CastE (t1, e1), CastE (t2, e2), typ) when typeSig (Cilfacade.typeOf e1) = typeSig (Cilfacade.typeOf e2) && VD.is_safe_cast t1 (Cilfacade.typeOf e1) && VD.is_safe_cast t2 (Cilfacade.typeOf e2) -> (* slightly different from eval_rv_base... *) - Some [(e1, e2)] - | BinOp (Eq, arg1, arg2, _) -> - Some [(arg1, arg2)] - | BinOp (LOr, arg1, arg2, _) -> - let* s1 = split arg1 in - let* s2 = split arg2 in - Some (s1 @ s2) - | _ -> - None - in - (* find common exp from all equality pairs and list of other sides, if possible *) - let find_common = function - | [] -> assert false - | (e1, e2) :: eqs -> - let eqs_for_all_mem e = List.for_all (fun (e1, e2) -> CilType.Exp.(equal e1 e || equal e2 e)) eqs in - let eqs_map_remove e = List.map (fun (e1, e2) -> if CilType.Exp.equal e1 e then e2 else e1) eqs in - if eqs_for_all_mem e1 then - Some (e1, e2 :: eqs_map_remove e1) - else if eqs_for_all_mem e2 then - Some (e2, e1 :: eqs_map_remove e2) - else + if VD.is_bot_value c_typed then contra st + else + match exp, c_typed with + | UnOp (LNot, e, _), Int c -> + let ikind = Cilfacade.get_ikind_exp e in + let c' = + match ID.to_bool (unop_ID LNot c) with + | Some true -> + (* i.e. e should evaluate to [1,1] *) + (* LNot x is 0 for any x != 0 *) + ID.of_excl_list ikind [BI.zero] + | Some false -> ID.of_bool ikind false + | _ -> ID.top_of ikind + in + inv_exp (Int c') e st + | UnOp (Neg, e, _), Float c -> inv_exp (Float (unop_FD Neg c)) e st + | UnOp ((BNot|Neg) as op, e, _), Int c -> inv_exp (Int (unop_ID op c)) e st + (* no equivalent for Float, as VD.is_safe_cast fails for all float types anyways *) + | BinOp((Eq | Ne) as op, CastE (t1, e1), CastE (t2, e2), t), Int c when typeSig (Cilfacade.typeOf e1) = typeSig (Cilfacade.typeOf e2) && VD.is_safe_cast t1 (Cilfacade.typeOf e1) && VD.is_safe_cast t2 (Cilfacade.typeOf e2) -> + inv_exp (Int c) (BinOp (op, e1, e2, t)) st + | BinOp (LOr, arg1, arg2, typ) as exp, Int c -> + (* copied & modified from eval_rv_base... *) + let (let*) = Option.bind in + (* split nested LOr Eqs to equality pairs, if possible *) + let rec split = function + (* copied from above to support pointer equalities with implicit casts inserted *) + | BinOp (Eq, CastE (t1, e1), CastE (t2, e2), typ) when typeSig (Cilfacade.typeOf e1) = typeSig (Cilfacade.typeOf e2) && VD.is_safe_cast t1 (Cilfacade.typeOf e1) && VD.is_safe_cast t2 (Cilfacade.typeOf e2) -> (* slightly different from eval_rv_base... *) + Some [(e1, e2)] + | BinOp (Eq, arg1, arg2, _) -> + Some [(arg1, arg2)] + | BinOp (LOr, arg1, arg2, _) -> + let* s1 = split arg1 in + let* s2 = split arg2 in + Some (s1 @ s2) + | _ -> None - in - let eqs_st = - let* eqs = split exp in - let* (e, es) = find_common eqs in - let v = eval e st in (* value of common exp *) - let vs = List.map (fun e -> eval e st) es in (* values of other sides *) - match v with - | `Address _ -> - (* get definite addrs from vs *) - let rec to_definite_ad = function - | [] -> AD.empty () - | `Address a :: vs when AD.is_definite a -> - AD.union a (to_definite_ad vs) - | _ :: vs -> - AD.top () - in - let definite_ad = to_definite_ad vs in - let c' = `Address definite_ad in - Some (inv_exp c' e st) - | `Int i -> - let ik = ID.ikind i in - let module BISet = IntDomain.BISet in - (* get definite ints from vs *) - let rec to_int_id = function - | [] -> ID.bot_of ik - | `Int i :: vs -> - begin match ID.to_int i with - | Some i' -> ID.join i (to_int_id vs) - | None -> ID.top_of ik - end - | _ :: vs -> - ID.top_of ik - in - let int_id = to_int_id vs in - let c' = `Int int_id in - Some (inv_exp c' e st) - | _ -> - None - in - begin match eqs_st with - | Some st -> st - | None -> st (* TODO: not bothering to fall back, no other case can refine LOr anyway *) - end - | (BinOp (op, e1, e2, _) as e, `Float _) - | (BinOp (op, e1, e2, _) as e, `Int _) -> - let invert_binary_op c pretty c_int c_float = - if M.tracing then M.tracel "inv" "binop %a with %a %a %a == %a\n" d_exp e VD.pretty (eval e1 st) d_binop op VD.pretty (eval e2 st) pretty c; - (match eval e1 st, eval e2 st with - | `Int a, `Int b -> - let ikind = Cilfacade.get_ikind_exp e1 in (* both operands have the same type (except for Shiftlt, Shiftrt)! *) - let ikres = Cilfacade.get_ikind_exp e in (* might be different from argument types, e.g. for LT, GT, EQ, ... *) - let a', b' = inv_bin_int (a, b) ikind (c_int ikres) op in - if M.tracing then M.tracel "inv" "binop: %a, c: %a, a': %a, b': %a\n" d_exp e ID.pretty (c_int ikind) ID.pretty a' ID.pretty b'; - let st' = inv_exp (`Int a') e1 st in - let st'' = inv_exp (`Int b') e2 st' in - st'' - | `Float a, `Float b -> - let fkind = Cilfacade.get_fkind_exp e1 in (* both operands have the same type *) - let a', b' = inv_bin_float (a, b) (c_float fkind) op in - if M.tracing then M.tracel "inv" "binop: %a, c: %a, a': %a, b': %a\n" d_exp e FD.pretty (c_float fkind) FD.pretty a' FD.pretty b'; - let st' = inv_exp (`Float a') e1 st in - let st'' = inv_exp (`Float b') e2 st' in - st'' - (* Mixed `Float and `Int cases should never happen, as there are no binary operators with one float and one int parameter ?!*) - | `Int _, `Float _ | `Float _, `Int _ -> failwith "ill-typed program"; - (* | `Address a, `Address b -> ... *) - | a1, a2 -> fallback ("binop: got abstract values that are not `Int: " ^ sprint VD.pretty a1 ^ " and " ^ sprint VD.pretty a2) st) - (* use closures to avoid unused casts *) - in (match c_typed with - | `Int c -> invert_binary_op c ID.pretty (fun ik -> ID.cast_to ik c) (fun fk -> FD.of_int fk c) - | `Float c -> invert_binary_op c FD.pretty (fun ik -> FD.to_int ik c) (fun fk -> FD.cast_to fk c) - | _ -> failwith "unreachable") - | 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 *) - begin match c_typed with - | `Int c -> - let c' = match t with - | TPtr _ -> `Address (AD.of_int (module ID) c) - | TInt (ik, _) - | TEnum ({ekind = ik; _}, _) -> `Int (ID.cast_to ik c) - | TFloat (fk, _) -> `Float (FD.of_int fk c) - | _ -> `Int c - in - update_lval c x c' ID.pretty - | `Float c -> - let c' = match t with - (* | TPtr _ -> ..., pointer conversion from/to float is not supported *) - | TInt (ik, _) -> `Int (FD.to_int ik c) - (* this is theoretically possible and should be handled correctly, however i can't imagine an actual piece of c code producing this?! *) - | TEnum ({ekind = ik; _}, _) -> `Int (FD.to_int ik c) - | TFloat (fk, _) -> `Float (FD.cast_to fk c) - | _ -> `Float c - in - update_lval c x c' FD.pretty - | `Address c -> - let c' = c_typed in (* TODO: need any of the type-matching nonsense? *) - update_lval c x c' AD.pretty - | _ -> assert false - end - | Const _ , _ -> st (* nothing to do *) - | CastE ((TFloat (_, _)), e), `Float c -> - (match unrollType (Cilfacade.typeOf e), FD.get_fkind c with - | TFloat (FLongDouble as fk, _), FFloat - | TFloat (FDouble as fk, _), FFloat - | TFloat (FLongDouble as fk, _), FDouble - | TFloat (fk, _), FLongDouble - | TFloat (FDouble as fk, _), FDouble - | TFloat (FFloat as fk, _), FFloat -> inv_exp (`Float (FD.cast_to fk c)) e st - | _ -> fallback ("CastE: incompatible types") st) - | CastE ((TInt (ik, _)) as t, e), `Int c - | CastE ((TEnum ({ekind = ik; _ }, _)) as t, e), `Int c -> (* Can only meet the t part of an Lval in e with c (unless we meet with all overflow possibilities)! Since there is no good way to do this, we only continue if e has no values outside of t. *) - (match eval e st with - | `Int i -> - if ID.leq i (ID.cast_to ik i) then - match unrollType (Cilfacade.typeOf e) with - | TInt(ik_e, _) - | TEnum ({ekind = ik_e; _ }, _) -> - (* let c' = ID.cast_to ik_e c in *) - let c' = ID.cast_to ik_e (ID.meet c (ID.cast_to ik (ID.top_of ik_e))) in (* TODO: cast without overflow, is this right for normal invariant? *) - if M.tracing then M.tracel "inv" "cast: %a from %a to %a: i = %a; cast c = %a to %a = %a\n" d_exp e d_ikind ik_e d_ikind ik ID.pretty i ID.pretty c d_ikind ik_e ID.pretty c'; - inv_exp (`Int c') e st - | x -> fallback ("CastE: e did evaluate to `Int, but the type did not match" ^ sprint d_type t) st - else - fallback ("CastE: " ^ sprint d_plainexp e ^ " evaluates to " ^ sprint ID.pretty i ^ " which is bigger than the type it is cast to which is " ^ sprint d_type t) st - | v -> fallback ("CastE: e did not evaluate to `Int, but " ^ sprint VD.pretty v) st) - | e, _ -> fallback (sprint d_plainexp e ^ " not implemented") st + in + (* find common exp from all equality pairs and list of other sides, if possible *) + let find_common = function + | [] -> assert false + | (e1, e2) :: eqs -> + let eqs_for_all_mem e = List.for_all (fun (e1, e2) -> CilType.Exp.(equal e1 e || equal e2 e)) eqs in + let eqs_map_remove e = List.map (fun (e1, e2) -> if CilType.Exp.equal e1 e then e2 else e1) eqs in + if eqs_for_all_mem e1 then + Some (e1, e2 :: eqs_map_remove e1) + else if eqs_for_all_mem e2 then + Some (e2, e1 :: eqs_map_remove e2) + else + None + in + let eqs_st = + let* eqs = split exp in + let* (e, es) = find_common eqs in + let v = eval e st in (* value of common exp *) + let vs = List.map (fun e -> eval e st) es in (* values of other sides *) + match v with + | Address _ -> + (* get definite addrs from vs *) + let rec to_definite_ad = function + | [] -> AD.empty () + | VD.Address a :: vs when AD.is_definite a -> + AD.union a (to_definite_ad vs) + | _ :: vs -> + AD.top () + in + let definite_ad = to_definite_ad vs in + let c' = VD.Address definite_ad in + Some (inv_exp c' e st) + | Int i -> + let ik = ID.ikind i in + let module BISet = IntDomain.BISet in + (* get definite ints from vs *) + let rec to_int_id = function + | [] -> ID.bot_of ik + | VD.Int i :: vs -> + begin match ID.to_int i with + | Some i' -> ID.join i (to_int_id vs) + | None -> ID.top_of ik + end + | _ :: vs -> + ID.top_of ik + in + let int_id = to_int_id vs in + let c' = VD.Int int_id in + Some (inv_exp c' e st) + | _ -> + None + in + begin match eqs_st with + | Some st -> st + | None when ID.to_bool c = Some true -> + begin match inv_exp (Int c) arg1 st with + | st1 -> + begin match inv_exp (Int c) arg2 st with + | st2 -> D.join st1 st2 + | exception Analyses.Deadcode -> st1 + end + | exception Analyses.Deadcode -> inv_exp (Int c) arg2 st (* Deadcode falls through *) + end + | None -> + st (* TODO: not bothering to fall back, no other case can refine LOr anyway *) + end + | (BinOp (op, e1, e2, _) as e, Float _) + | (BinOp (op, e1, e2, _) as e, Int _) -> + let invert_binary_op c pretty c_int c_float = + if M.tracing then M.tracel "inv" "binop %a with %a %a %a == %a\n" d_exp e VD.pretty (eval e1 st) d_binop op VD.pretty (eval e2 st) pretty c; + (match eval e1 st, eval e2 st with + | Int a, Int b -> + let ikind = Cilfacade.get_ikind_exp e1 in (* both operands have the same type (except for Shiftlt, Shiftrt)! *) + let ikres = Cilfacade.get_ikind_exp e in (* might be different from argument types, e.g. for LT, GT, EQ, ... *) + let a', b' = inv_bin_int (a, b) ikind (c_int ikres) op in + if M.tracing then M.tracel "inv" "binop: %a, c: %a, a': %a, b': %a\n" d_exp e ID.pretty (c_int ikind) ID.pretty a' ID.pretty b'; + let st' = inv_exp (Int a') e1 st in + let st'' = inv_exp (Int b') e2 st' in + st'' + | Float a, Float b -> + let fkind = Cilfacade.get_fkind_exp e1 in (* both operands have the same type *) + let a', b' = inv_bin_float (a, b) (c_float fkind) op in + if M.tracing then M.tracel "inv" "binop: %a, c: %a, a': %a, b': %a\n" d_exp e FD.pretty (c_float fkind) FD.pretty a' FD.pretty b'; + let st' = inv_exp (Float a') e1 st in + let st'' = inv_exp (Float b') e2 st' in + st'' + (* Mixed Float and Int cases should never happen, as there are no binary operators with one float and one int parameter ?!*) + | Int _, Float _ | Float _, Int _ -> failwith "ill-typed program"; + (* | Address a, Address b -> ... *) + | a1, a2 -> fallback (GobPretty.sprintf "binop: got abstract values that are not Int: %a and %a" VD.pretty a1 VD.pretty a2) st) + (* use closures to avoid unused casts *) + in (match c_typed with + | Int c -> invert_binary_op c ID.pretty (fun ik -> ID.cast_to ik c) (fun fk -> FD.of_int fk c) + | Float c -> invert_binary_op c FD.pretty (fun ik -> FD.to_int ik c) (fun fk -> FD.cast_to fk c) + | _ -> failwith "unreachable") + | 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 + | TPtr _ -> VD.Address (AD.of_int c) + | TInt (ik, _) + | TEnum ({ekind = ik; _}, _) -> Int (ID.cast_to ik c) + | TFloat (fk, _) -> Float (FD.of_int fk c) + | _ -> Int c + in + (* handle special calls *) + begin match t with + | TInt (ik, _) -> + begin match x with + | ((Var v), offs) -> + if M.tracing then M.trace "invSpecial" "qry Result: %a\n" Queries.ML.pretty (ctx.ask (Queries.TmpSpecial (v, Offset.Exp.of_cil offs))); + let tv_opt = ID.to_bool c in + begin match tv_opt with + | Some tv -> + begin match ctx.ask (Queries.TmpSpecial (v, Offset.Exp.of_cil offs)) 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*) + | `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 + | _ -> update_lval c x c' ID.pretty + 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 *) + | TInt (ik, _) -> VD.Int (FD.to_int ik c) + (* this is theoretically possible and should be handled correctly, however i can't imagine an actual piece of c code producing this?! *) + | TEnum ({ekind = ik; _}, _) -> Int (FD.to_int ik c) + | TFloat (fk, _) -> Float (FD.cast_to fk c) + | _ -> Float c + in + (* handle special calls *) + begin match t with + | TFloat (fk, _) -> + begin match x with + | ((Var v), offs) -> + if M.tracing then M.trace "invSpecial" "qry Result: %a\n" Queries.ML.pretty (ctx.ask (Queries.TmpSpecial (v, Offset.Exp.of_cil offs))); + begin match ctx.ask (Queries.TmpSpecial (v, Offset.Exp.of_cil offs)) 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 + | _ -> 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 + | _ -> assert false + end + | Const _ , _ -> st (* nothing to do *) + | CastE ((TFloat (_, _)), e), Float c -> + (match unrollType (Cilfacade.typeOf e), FD.get_fkind c with + | TFloat (FLongDouble as fk, _), FFloat + | TFloat (FDouble as fk, _), FFloat + | TFloat (FLongDouble as fk, _), FDouble + | TFloat (fk, _), FLongDouble + | TFloat (FDouble as fk, _), FDouble + | TFloat (FFloat as fk, _), FFloat -> inv_exp (Float (FD.cast_to fk c)) e st + | _ -> fallback ("CastE: incompatible types") st) + | CastE ((TInt (ik, _)) as t, e), Int c + | CastE ((TEnum ({ekind = ik; _ }, _)) as t, e), Int c -> (* Can only meet the t part of an Lval in e with c (unless we meet with all overflow possibilities)! Since there is no good way to do this, we only continue if e has no values outside of t. *) + (match eval e st with + | Int i -> + if ID.leq i (ID.cast_to ik i) then + match unrollType (Cilfacade.typeOf e) with + | TInt(ik_e, _) + | TEnum ({ekind = ik_e; _ }, _) -> + (* let c' = ID.cast_to ik_e c in *) + let c' = ID.cast_to ik_e (ID.meet c (ID.cast_to ik (ID.top_of ik_e))) in (* TODO: cast without overflow, is this right for normal invariant? *) + if M.tracing then M.tracel "inv" "cast: %a from %a to %a: i = %a; cast c = %a to %a = %a\n" d_exp e d_ikind ik_e d_ikind ik ID.pretty i ID.pretty c d_ikind ik_e ID.pretty c'; + inv_exp (Int c') e st + | x -> fallback (GobPretty.sprintf "CastE: e did evaluate to Int, but the type did not match %a" CilType.Typ.pretty t) st + else + fallback (GobPretty.sprintf "CastE: %a evaluates to %a which is bigger than the type it is cast to which is %a" d_plainexp e ID.pretty i CilType.Typ.pretty t) st + | v -> fallback (GobPretty.sprintf "CastE: e did not evaluate to Int, but %a" VD.pretty v) st) + | e, _ -> fallback (GobPretty.sprintf "%a not implemented" d_plainexp e) st in - if eval_bool exp st = Some (not tv) then raise Analyses.Deadcode (* we already know that the branch is dead *) + if eval_bool exp st = Some (not tv) then contra st (* we already know that the branch is dead *) else (* C11 6.5.13, 6.5.14, 6.5.3.3: LAnd, LOr and LNot also return 0 or 1 *) let is_cmp = function @@ -728,20 +805,20 @@ 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 _ -> + inv_exp (Int itv) exp st + | 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. else FD.top_of fk in - inv_exp (`Float ftv) exp st + inv_exp (Float ftv) exp st end diff --git a/src/analyses/basePriv.ml b/src/analyses/basePriv.ml index bc20df011d..3843dda300 100644 --- a/src/analyses/basePriv.ml +++ b/src/analyses/basePriv.ml @@ -1,4 +1,5 @@ -open Prelude.Ana +open Batteries +open GoblintCil open Analyses open GobConfig open BaseUtil @@ -35,6 +36,7 @@ sig val escape: Q.ask -> (V.t -> G.t) -> (V.t -> G.t -> unit) -> BaseComponents (D).t -> EscapeDomain.EscapedVars.t -> BaseComponents (D).t val enter_multithreaded: Q.ask -> (V.t -> G.t) -> (V.t -> G.t -> unit) -> BaseComponents (D).t -> BaseComponents (D).t val threadenter: Q.ask -> BaseComponents (D).t -> BaseComponents (D).t + val threadspawn: Q.ask -> (V.t -> G.t) -> (V.t -> G.t -> unit) -> BaseComponents (D).t -> BaseComponents (D).t val iter_sys_vars: (V.t -> G.t) -> VarQuery.t -> V.t VarQuery.f -> unit val thread_join: ?force:bool -> Q.ask -> (V.t -> G.t) -> Cil.exp -> BaseComponents (D).t -> BaseComponents (D).t @@ -55,7 +57,7 @@ end let old_threadenter (type d) ask (st: d BaseDomain.basecomponents_t) = (* Copy-paste from Base make_entry *) let globals = CPA.filter (fun k v -> is_global ask k) st.cpa in - (* let new_cpa = if !GU.earlyglobs || ThreadFlag.is_multi ctx.ask then CPA.filter (fun k v -> is_private ctx.ask ctx.local k) globals else globals in *) + (* let new_cpa = if !earlyglobs || ThreadFlag.is_multi ctx.ask then CPA.filter (fun k v -> is_private ctx.ask ctx.local k) globals else globals in *) let new_cpa = globals in {st with cpa = new_cpa} @@ -81,6 +83,7 @@ struct let escape ask getg sideg st escaped = st let enter_multithreaded ask getg sideg st = st let threadenter = old_threadenter + let threadspawn ask getg sideg st = st let iter_sys_vars getg vq vf = match vq with @@ -175,7 +178,7 @@ struct let cpa' = CPA.fold (fun x v acc -> if EscapeDomain.EscapedVars.mem x escaped (* && is_unprotected ask x *) then ( - if M.tracing then M.tracel "priv" "ESCAPE SIDE %a = %a\n" d_varinfo x VD.pretty v; + if M.tracing then M.tracel "priv" "ESCAPE SIDE %a = %a\n" CilType.Varinfo.pretty x VD.pretty v; sideg (V.global x) (CPA.singleton x v); CPA.remove x acc ) @@ -191,8 +194,8 @@ struct let cpa' = CPA.fold (fun x v acc -> if is_global ask x (* && is_unprotected ask x *) then ( - if M.tracing then M.tracel "priv" "enter_multithreaded remove %a\n" d_varinfo x; - if M.tracing then M.tracel "priv" "ENTER MULTITHREADED SIDE %a = %a\n" d_varinfo x VD.pretty v; + if M.tracing then M.tracel "priv" "enter_multithreaded remove %a\n" CilType.Varinfo.pretty x; + if M.tracing then M.tracel "priv" "ENTER MULTITHREADED SIDE %a = %a\n" CilType.Varinfo.pretty x VD.pretty v; sideg (V.global x) (CPA.singleton x v); CPA.remove x acc ) @@ -203,6 +206,7 @@ struct {st with cpa = cpa'} let threadenter = old_threadenter + let threadspawn ask get set st = st let thread_join ?(force=false) ask get e st = st let thread_return ask get set tid st = st @@ -225,9 +229,9 @@ struct else 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" d_varinfo x CilType.Location.pretty !Tracing.current_loc (is_unprotected ask x) VD.pretty v); - r *) + 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); + r *) let write_global ?(invariant=false) ask getg sideg (st: BaseComponents (D).t) x v = let cpa' = CPA.add x v st.cpa in if not invariant then @@ -235,9 +239,9 @@ struct (* Unlock after invariant will still side effect refined value from CPA, because cannot distinguish from non-invariant write. *) {st with cpa = cpa'} (* let write_global ask getg sideg cpa x v = - let cpa' = write_global ask getg sideg cpa x v in - ignore (Pretty.printf "WRITE GLOBAL %a %a = %a\n" d_varinfo x VD.pretty v CPA.pretty cpa'); - cpa' *) + let cpa' = write_global ask getg sideg cpa x v in + ignore (Pretty.printf "WRITE GLOBAL %a %a = %a\n" CilType.Varinfo.pretty x VD.pretty v CPA.pretty cpa'); + cpa' *) let lock ask getg (st: BaseComponents (D).t) m = if Locksets.(not (Lockset.mem m (current_lockset ask))) then ( @@ -314,7 +318,7 @@ struct CPA.find x st.cpa let read_global ask getg st x = let v = read_global ask getg st x in - if M.tracing then M.tracel "priv" "READ GLOBAL %a %B %a = %a\n" d_varinfo x (is_unprotected ask x) CPA.pretty st.cpa VD.pretty v; + if M.tracing then M.tracel "priv" "READ GLOBAL %a %B %a = %a\n" CilType.Varinfo.pretty x (is_unprotected ask x) CPA.pretty st.cpa VD.pretty v; v let write_global ?(invariant=false) ask getg sideg (st: BaseComponents (D).t) x v = let cpa' = @@ -324,15 +328,15 @@ struct CPA.add x v st.cpa in if not invariant then ( - if M.tracing then M.tracel "priv" "WRITE GLOBAL SIDE %a = %a\n" d_varinfo x VD.pretty v; + if M.tracing then M.tracel "priv" "WRITE GLOBAL SIDE %a = %a\n" CilType.Varinfo.pretty x VD.pretty v; sideg (V.global x) (CPA.singleton x v) (* Unlock after invariant will still side effect refined value (if protected) from CPA, because cannot distinguish from non-invariant write. *) ); {st with cpa = cpa'} (* let write_global ask getg sideg cpa x v = - let cpa' = write_global ask getg sideg cpa x v in - ignore (Pretty.printf "WRITE GLOBAL %a %a = %a\n" d_varinfo x VD.pretty v CPA.pretty cpa'); - cpa' *) + let cpa' = write_global ask getg sideg cpa x v in + ignore (Pretty.printf "WRITE GLOBAL %a %a = %a\n" CilType.Varinfo.pretty x VD.pretty v CPA.pretty cpa'); + cpa' *) let lock (ask: Queries.ask) getg (st: BaseComponents (D).t) m = if Locksets.(not (Lockset.mem m (current_lockset ask))) then ( @@ -369,12 +373,12 @@ struct let cpa' = CPA.fold (fun x v cpa -> if is_global ask x && is_unprotected ask x (* && not (VD.is_top v) *) then ( - if M.tracing then M.tracel "priv" "SYNC SIDE %a = %a\n" d_varinfo x VD.pretty v; + if M.tracing then M.tracel "priv" "SYNC SIDE %a = %a\n" CilType.Varinfo.pretty x VD.pretty v; sideg (V.global x) (CPA.singleton x v); CPA.remove x cpa ) else ( - if M.tracing then M.tracel "priv" "SYNC NOSIDE %a = %a\n" d_varinfo x VD.pretty v; + if M.tracing then M.tracel "priv" "SYNC NOSIDE %a = %a\n" CilType.Varinfo.pretty x VD.pretty v; cpa ) ) st.cpa st.cpa @@ -389,6 +393,7 @@ end module PerMutexMeetTIDPriv: S = struct + open Queries.Protection include PerMutexMeetPrivBase include PerMutexTidCommon(struct let exclude_not_started () = GobConfig.get_bool "ana.base.priv.not-started" @@ -402,6 +407,12 @@ struct let long_meet m1 m2 = CPA.long_map2 VD.meet m1 m2 + let update_if_mem var value m = + if CPA.mem var m then + CPA.add var value m + else + m + let get_mutex_global_g_with_mutex_inits inits ask getg g = let get_mutex_global_g = get_relevant_writes_nofilter ask @@ G.mutex @@ getg (V.global g) in let r = if not inits then @@ -416,7 +427,7 @@ struct let get_relevant_writes (ask:Q.ask) m v = let current = ThreadId.get_current ask in let must_joined = ask.f Queries.MustJoinedThreads in - let is_in_Gm x _ = is_protected_by ask m x in + let is_in_Gm x _ = is_protected_by ~protection:Weak ask m x in GMutex.fold (fun k v acc -> if compatible ask current must_joined k then CPA.join acc (CPA.filter is_in_Gm v) @@ -431,7 +442,7 @@ struct get_m else let get_mutex_inits = merge_all @@ G.mutex @@ getg V.mutex_inits in - let is_in_Gm x _ = is_protected_by ask m x in + let is_in_Gm x _ = is_protected_by ~protection:Weak ask m x in let get_mutex_inits' = CPA.filter is_in_Gm get_mutex_inits in CPA.join get_m get_mutex_inits' in @@ -442,7 +453,7 @@ struct let lm = LLock.global x in let tmp = get_mutex_global_g_with_mutex_inits (not (LMust.mem lm lmust)) ask getg x in let local_m = BatOption.default (CPA.bot ()) (L.find_opt lm l) in - if is_unprotected ask x then + if is_unprotected ask ~protection:Weak x then (* We can not rely upon the old value here, it may be too small due to reuse at widening points (and or nice bot/top confusion) in Base *) CPA.find x (CPA.join tmp local_m) else @@ -450,22 +461,29 @@ struct let read_global ask getg st x = let v = read_global ask getg st x in - if M.tracing then M.tracel "priv" "READ GLOBAL %a %B %a = %a\n" d_varinfo x (is_unprotected ask x) CPA.pretty st.cpa VD.pretty v; + if M.tracing then M.tracel "priv" "READ GLOBAL %a %B %a = %a\n" CilType.Varinfo.pretty x (is_unprotected ~protection:Weak ask x) CPA.pretty st.cpa VD.pretty v; v let write_global ?(invariant=false) ask getg sideg (st: BaseComponents (D).t) x v = let w,lmust,l = st.priv in let lm = LLock.global x in let cpa' = - if is_unprotected ask x then + if is_unprotected ask ~protection:Weak x then st.cpa else CPA.add x v st.cpa in - if M.tracing then M.tracel "priv" "WRITE GLOBAL SIDE %a = %a\n" d_varinfo x VD.pretty v; + if M.tracing then M.tracel "priv" "WRITE GLOBAL SIDE %a = %a\n" CilType.Varinfo.pretty x VD.pretty v; let tid = ThreadId.get_current ask in let sidev = GMutex.singleton tid (CPA.singleton x v) in let l' = L.add lm (CPA.singleton x v) l in + let is_recovered_st = ask.f (Queries.MustBeSingleThreaded {since_start = false}) && not @@ ask.f (Queries.MustBeSingleThreaded {since_start = true}) in + let l' = if is_recovered_st then + (* update value of local record for all where it appears *) + L.map (update_if_mem x v) l' + else + l' + in sideg (V.global x) (G.create_global sidev); {st with cpa = cpa'; priv = (W.add x w,LMust.add lm lmust,l')} @@ -475,9 +493,10 @@ struct let lm = LLock.mutex m in let get_m = get_m_with_mutex_inits (not (LMust.mem lm lmust)) ask getg m in let local_m = BatOption.default (CPA.bot ()) (L.find_opt lm l) in - let is_in_Gm x _ = is_protected_by ask m x in + let is_in_Gm x _ = is_protected_by ~protection:Weak ask m x in let local_m = CPA.filter is_in_Gm local_m in - let meet = long_meet st.cpa (CPA.join get_m local_m) in + let r = CPA.join get_m local_m in + let meet = long_meet st.cpa r in {st with cpa = meet} ) else @@ -486,18 +505,18 @@ struct let unlock ask getg sideg (st: BaseComponents (D).t) m = let w,lmust,l = st.priv in let cpa' = CPA.fold (fun x v cpa -> - if is_protected_by ask m x && is_unprotected_without ask x m then + if is_protected_by ~protection:Weak ask m x && is_unprotected_without ~protection:Weak ask x m then CPA.remove x cpa else cpa ) st.cpa st.cpa in - let w' = W.filter (fun v -> not (is_unprotected_without ask v m)) w in - let side_needed = W.exists (fun v -> is_protected_by ask m v) w in + let w' = W.filter (fun v -> not (is_unprotected_without ~protection:Weak ask v m)) w in + let side_needed = W.exists (fun v -> is_protected_by ~protection:Weak ask m v) w in if not side_needed then {st with cpa = cpa'; priv = (w',lmust,l)} else - let is_in_Gm x _ = is_protected_by ask m x in + let is_in_Gm x _ = is_protected_by ~protection:Weak ask m x in let tid = ThreadId.get_current ask in let sidev = GMutex.singleton tid (CPA.filter is_in_Gm st.cpa) in sideg (V.mutex m) (G.create_mutex sidev); @@ -554,7 +573,7 @@ struct sideg V.mutex_inits (G.create_mutex sidev); let cpa' = CPA.fold (fun x v acc -> if EscapeDomain.EscapedVars.mem x escaped (* && is_unprotected ask x *) then ( - if M.tracing then M.tracel "priv" "ESCAPE SIDE %a = %a\n" d_varinfo x VD.pretty v; + if M.tracing then M.tracel "priv" "ESCAPE SIDE %a = %a\n" CilType.Varinfo.pretty x VD.pretty v; let sidev = GMutex.singleton tid (CPA.singleton x v) in sideg (V.global x) (G.create_global sidev); CPA.remove x acc @@ -578,12 +597,26 @@ struct {st with cpa= cpa_local } let threadenter ask (st: BaseComponents (D).t): BaseComponents (D).t = - (* Copy-paste from Base make_entry *) - let globals = CPA.filter (fun k v -> is_global ask k) st.cpa in - (* let new_cpa = if !GU.earlyglobs || ThreadFlag.is_multi ctx.ask then CPA.filter (fun k v -> is_private ctx.ask ctx.local k) globals else globals in *) - let new_cpa = globals in let _,lmust,l = st.priv in - {st with cpa = new_cpa; priv = (W.bot (),lmust,l)} + (* Thread starts without any mutexes, so the local state cannot contain any privatized things. The locals of the created thread are added later, *) + (* so the cpa component of st is bot. *) + {st with cpa = CPA.bot (); priv = (W.bot (),lmust,l)} + + let threadspawn (ask:Queries.ask) get set (st: BaseComponents (D).t) = + let is_recovered_st = ask.f (Queries.MustBeSingleThreaded {since_start = false}) && not @@ ask.f (Queries.MustBeSingleThreaded {since_start = true}) in + let unprotected_after x = ask.f (Q.MayBePublic {global=x; write=true; protection=Weak}) in + if is_recovered_st then + (* Remove all things that are now unprotected *) + let cpa' = CPA.fold (fun x v cpa -> + (* recoverable is false as after this, we will be multi-threaded *) + if unprotected_after x then + CPA.remove x cpa + else + cpa + ) st.cpa st.cpa + in + {st with cpa = cpa'} + else st let read_unprotected_global getg x = let get_mutex_global_x = merge_all @@ G.mutex @@ getg (V.global x) in @@ -665,7 +698,7 @@ struct let write_global ?(invariant=false) ask getg sideg (st: BaseComponents (D).t) x v = if not invariant then ( sideg (V.unprotected x) v; - if !GU.earlyglobs then (* earlyglobs workaround for 13/60 *) + if !earlyglobs then (* earlyglobs workaround for 13/60 *) sideg (V.protected x) v (* Unlock after invariant will still side effect refined value (if protected) from CPA, because cannot distinguish from non-invariant write since W is implicit. *) ); @@ -738,6 +771,7 @@ struct ) st.cpa st let threadenter = startstate_threadenter startstate + let threadspawn ask get set st = st let thread_join ?(force=false) ask get e st = st let thread_return ask get set tid st = st @@ -859,11 +893,12 @@ 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 let thread_return ask get set tid st = st + let threadspawn ask get set st = st end module MineNaivePrivBase = @@ -897,7 +932,7 @@ struct let global_init_thread = RichVarinfo.single ~name:"global_init" let current_thread (ask: Q.ask): Thread.t = - if !GU.global_initialization then + if !AnalysisState.global_initialization then ThreadIdDomain.Thread.threadinit (global_init_thread ()) ~multiple:false else ThreadId.get_current_unlift ask @@ -917,7 +952,7 @@ struct let s = current_lockset ask in let t = current_thread ask in let cpa' = CPA.add x v st.cpa in - if not invariant && not (!GU.earlyglobs && is_excluded_from_earlyglobs x) then + if not invariant && not (!earlyglobs && is_excluded_from_earlyglobs x) then sideg (V.global x) (G.create_weak (GWeak.singleton s (ThreadMap.singleton t v))); (* Unlock after invariant will not side effect refined value from weak, because it's not side effected there. *) {st with cpa = cpa'} @@ -975,7 +1010,7 @@ struct let write_global ?(invariant=false) ask getg sideg (st: BaseComponents (D).t) x v = let s = current_lockset ask in let cpa' = CPA.add x v st.cpa in - if not invariant && not (!GU.earlyglobs && is_excluded_from_earlyglobs x) then + if not invariant && not (!earlyglobs && is_excluded_from_earlyglobs x) then sideg (V.global x) (G.create_weak (GWeak.singleton s v)); (* Unlock after invariant will not side effect refined value from weak, because it's not side effected there. *) {st with cpa = cpa'} @@ -1046,7 +1081,7 @@ struct let write_global ?(invariant=false) ask getg sideg (st: BaseComponents (D).t) x v = let s = current_lockset ask in let cpa' = CPA.add x v st.cpa in - if not invariant && not (!GU.earlyglobs && is_excluded_from_earlyglobs x) then + if not invariant && not (!earlyglobs && is_excluded_from_earlyglobs x) then sideg (V.global x) (G.create_weak (GWeak.singleton s v)); let w' = if not invariant then W.add x st.priv @@ -1192,7 +1227,7 @@ struct ) l vv in let cpa' = CPA.add x v st.cpa in - if not invariant && not (!GU.earlyglobs && is_excluded_from_earlyglobs x) then ( + if not invariant && not (!earlyglobs && is_excluded_from_earlyglobs x) then ( let v = distr_init getg x v in sideg (V.global x) (G.create_weak (GWeak.singleton s v)) (* Unlock after invariant will still side effect refined value from CPA, because cannot distinguish from non-invariant write. *) @@ -1353,7 +1388,7 @@ struct let p' = P.add x (MinLocksets.singleton s) p in let p' = P.map (fun s' -> MinLocksets.add s s') p' in let cpa' = CPA.add x v st.cpa in - if not invariant && not (!GU.earlyglobs && is_excluded_from_earlyglobs x) then ( + if not invariant && not (!earlyglobs && is_excluded_from_earlyglobs x) then ( let v = distr_init getg x v in sideg (V.global x) (G.create_weak (GWeak.singleton s (GWeakW.singleton s v))) ); @@ -1370,7 +1405,7 @@ struct let side_gsyncw = CPA.fold (fun x v acc -> if is_global ask x then ( let w_x = W.find x w in - if M.tracing then M.trace "priv" "gsyncw %a %a %a\n" d_varinfo x VD.pretty v MinLocksets.pretty w_x; + if M.tracing then M.trace "priv" "gsyncw %a %a %a\n" CilType.Varinfo.pretty x VD.pretty v MinLocksets.pretty w_x; MinLocksets.fold (fun w acc -> let v = distr_init getg x v in GSyncW.add w (CPA.add x v (GSyncW.find w acc)) acc @@ -1519,7 +1554,7 @@ struct ) l vv in let cpa' = CPA.add x v st.cpa in - if not invariant && not (!GU.earlyglobs && is_excluded_from_earlyglobs x) then ( + if not invariant && not (!earlyglobs && is_excluded_from_earlyglobs x) then ( let v = distr_init getg x v in sideg (V.global x) (G.create_weak (GWeak.singleton s (GWeakW.singleton s v))) ); @@ -1603,6 +1638,7 @@ struct let escape ask getg sideg st escaped = time "escape" (Priv.escape ask getg sideg st) escaped let enter_multithreaded ask getg sideg st = time "enter_multithreaded" (Priv.enter_multithreaded ask getg sideg) st let threadenter ask st = time "threadenter" (Priv.threadenter ask) st + let threadspawn ask get set st = time "threadspawn" (Priv.threadspawn ask get set) st let iter_sys_vars getg vq vf = time "iter_sys_vars" (Priv.iter_sys_vars getg vq) vf let invariant_global getg v = time "invariant_global" (Priv.invariant_global getg) v let invariant_vars ask getg st = time "invariant_vars" (Priv.invariant_vars ask getg) st @@ -1631,14 +1667,14 @@ struct let read_global ask getg st x = let v = Priv.read_global ask getg st x in - if !GU.postsolving && !is_dumping then + if !AnalysisState.postsolving && !is_dumping then LVH.modify_def (VD.bot ()) (!Tracing.current_loc, x) (VD.join v) lvh; v let dump () = let f = open_out_bin (get_string "exp.priv-prec-dump") in (* LVH.iter (fun (l, x) v -> - ignore (Pretty.printf "%a %a = %a\n" CilType.Location.pretty l d_varinfo x VD.pretty v) + ignore (Pretty.printf "%a %a = %a\n" CilType.Location.pretty l CilType.Varinfo.pretty x VD.pretty v) ) lvh; *) Marshal.output f ({name = get_string "ana.base.privatization"; results = lvh}: result); close_out_noerr f @@ -1656,7 +1692,7 @@ struct module BaseComponents = BaseComponents (D) let read_global ask getg st x = - if M.tracing then M.traceli "priv" "read_global %a\n" d_varinfo x; + if M.tracing then M.traceli "priv" "read_global %a\n" CilType.Varinfo.pretty x; if M.tracing then M.trace "priv" "st: %a\n" BaseComponents.pretty st; let getg x = let r = getg x in @@ -1668,7 +1704,7 @@ struct v let write_global ?invariant ask getg sideg st x v = - if M.tracing then M.traceli "priv" "write_global %a %a\n" d_varinfo x VD.pretty v; + if M.tracing then M.traceli "priv" "write_global %a %a\n" CilType.Varinfo.pretty x VD.pretty v; if M.tracing then M.trace "priv" "st: %a\n" BaseComponents.pretty st; let getg x = let r = getg x in diff --git a/src/analyses/basePriv.mli b/src/analyses/basePriv.mli index 781771c221..6906e6e4e1 100644 --- a/src/analyses/basePriv.mli +++ b/src/analyses/basePriv.mli @@ -1,3 +1,7 @@ +(** Non-relational thread-modular value analyses for {!Base}. + + @see Schwarz, M., Saan, S., Seidl, H., Apinis, K., Erhard, J., Vojdani, V. Improving Thread-Modular Abstract Interpretation. *) + open GoblintCil (* Cannot use local module substitutions because ppx_import is still stuck at 4.07 AST: https://github.com/ocaml-ppx/ppx_import/issues/50#issuecomment-775817579. *) (* TODO: try again, because ppx_import is now removed *) @@ -21,6 +25,7 @@ sig val escape: Queries.ask -> (V.t -> G.t) -> (V.t -> G.t -> unit) -> BaseDomain.BaseComponents (D).t -> EscapeDomain.EscapedVars.t -> BaseDomain.BaseComponents (D).t val enter_multithreaded: Queries.ask -> (V.t -> G.t) -> (V.t -> G.t -> unit) -> BaseDomain.BaseComponents (D).t -> BaseDomain.BaseComponents (D).t val threadenter: Queries.ask -> BaseDomain.BaseComponents (D).t -> BaseDomain.BaseComponents (D).t + val threadspawn: Queries.ask -> (V.t -> G.t) -> (V.t -> G.t -> unit) -> BaseDomain.BaseComponents (D).t -> BaseDomain.BaseComponents (D).t val iter_sys_vars: (V.t -> G.t) -> VarQuery.t -> V.t VarQuery.f -> unit (** [Queries.IterSysVars] for base. *) val thread_join: ?force:bool -> Queries.ask -> (V.t -> G.t) -> Cil.exp -> BaseDomain.BaseComponents (D).t -> BaseDomain.BaseComponents (D).t diff --git a/src/analyses/baseUtil.ml b/src/analyses/baseUtil.ml index 202aa54410..dd77392404 100644 --- a/src/analyses/baseUtil.ml +++ b/src/analyses/baseUtil.ml @@ -1,4 +1,4 @@ -open Prelude.Ana +open GoblintCil open GobConfig module Q = Queries @@ -6,6 +6,7 @@ let is_global (a: Q.ask) (v: varinfo): bool = v.vglob || ThreadEscape.has_escaped a v let is_static (v:varinfo): bool = v.vstorage = Static +let is_volatile variable = Ciltools.is_volatile_tp variable.vtype let is_always_unknown variable = variable.vstorage = Extern || Ciltools.is_volatile_tp variable.vtype diff --git a/src/analyses/baseUtil.mli b/src/analyses/baseUtil.mli index 60e41ffb80..7054cd57fc 100644 --- a/src/analyses/baseUtil.mli +++ b/src/analyses/baseUtil.mli @@ -1,7 +1,10 @@ +(** Basic analysis utilities. *) + open GoblintCil val is_global: Queries.ask -> varinfo -> bool val is_static: varinfo -> bool +val is_volatile: varinfo -> bool val is_always_unknown: varinfo -> bool val is_excluded_from_earlyglobs: varinfo -> bool val is_excluded_from_invalidation: varinfo -> bool diff --git a/src/analyses/commonPriv.ml b/src/analyses/commonPriv.ml index 670e89f16a..88181000b9 100644 --- a/src/analyses/commonPriv.ml +++ b/src/analyses/commonPriv.ml @@ -1,4 +1,7 @@ -open Prelude.Ana +(** Thread-modular value analysis utilities for {!BasePriv} and {!RelationPriv}. *) + +open Batteries +open GoblintCil open Analyses open BaseUtil module Q = Queries @@ -16,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 @@ -38,32 +43,29 @@ end module Protection = struct - let is_unprotected ask x: bool = - let multi = ThreadFlag.is_multi ask in - (!GU.earlyglobs && not multi && not (is_excluded_from_earlyglobs x)) || + open Q.Protection + let is_unprotected ask ?(protection=Strong) x: bool = + let multi = if protection = Weak then ThreadFlag.is_currently_multi ask else ThreadFlag.has_ever_been_multi ask in + (!GobConfig.earlyglobs && not multi && not (is_excluded_from_earlyglobs x)) || ( multi && - ask.f (Q.MayBePublic {global=x; write=true}) + ask.f (Q.MayBePublic {global=x; write=true; protection}) ) - let is_unprotected_without ask ?(write=true) x m: bool = - ThreadFlag.is_multi ask && - ask.f (Q.MayBePublicWithout {global=x; write; without_mutex=m}) + let is_unprotected_without ask ?(write=true) ?(protection=Strong) x m: bool = + (if protection = Weak then ThreadFlag.is_currently_multi ask else ThreadFlag.has_ever_been_multi ask) && + ask.f (Q.MayBePublicWithout {global=x; write; without_mutex=m; protection}) - let is_protected_by ask m x: bool = + let is_protected_by ask ?(protection=Strong) m x: bool = is_global ask x && not (VD.is_immediate_type x.vtype) && - ask.f (Q.MustBeProtectedBy {mutex=m; global=x; write=true}) + 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.from_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 = @@ -84,7 +86,8 @@ struct module V = struct (* TODO: Either3? *) - include Printable.Either (Printable.Either (VMutex) (VMutexInits)) (VGlobal) + include Printable.Either (struct include Printable.Either (VMutex) (VMutexInits) let name () = "mutex" end) (VGlobal) + let name () = "MutexGlobals" let mutex x: t = `Left (`Left x) let mutex_inits: t = `Left (`Right ()) let global x: t = `Right x @@ -112,29 +115,17 @@ module Locksets = struct module Lock = LockDomain.Addr - module Lockset = - struct - include Printable.Std (* To make it Groupable *) - include SetDomain.ToppedSet (Lock) (struct let topname = "All locks" end) - end + module Lockset = SetDomain.ToppedSet (Lock) (struct let topname = "All locks" end) module MustLockset = SetDomain.Reverse (Lockset) - let rec conv_offset = function - | `NoOffset -> `NoOffset - | `Field (f, o) -> `Field (f, conv_offset o) - (* TODO: better indices handling *) - | `Index (_, o) -> `Index (IdxDom.top (), conv_offset o) - let current_lockset (ask: Q.ask): Lockset.t = (* TODO: remove this global_init workaround *) - if !GU.global_initialization then + 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.from_var_offset (var, conv_offset 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 12e9cf8b00..3b23dc03fc 100644 --- a/src/analyses/condVars.ml +++ b/src/analyses/condVars.ml @@ -1,12 +1,13 @@ -(** Must equality between variables and logical expressions. *) +(** Symbolic variable - logical expression equalities analysis ([condvars]). *) (* TODO: unused, what is this analysis? *) -open Prelude.Ana +open Batteries +open GoblintCil open Analyses module Domain = struct module V = Queries.ES - include MapDomain.MapBot (Lval.CilLval) (V) + include MapDomain.MapBot (Mval.Exp) (V) let rec var_in_lval p (lh,offs) = var_in_offs p offs && match lh with | Var v -> p v | Mem e -> var_in_expr p e @@ -28,7 +29,7 @@ module Domain = struct |> filter_exprs_with_var p let remove_var v = filter_vars ((<>) v) let remove_fun_locals f d = - let p v = not @@ List.mem v (f.sformals @ f.slocals) in + let p v = not @@ List.mem_cmp CilType.Varinfo.compare v (f.sformals @ f.slocals) in filter_vars p d let only_globals d = let p v = v.vglob in @@ -36,12 +37,15 @@ module Domain = struct let only_locals d = let p v = not v.vglob in filter_vars p d + let only_untainted d tainted = + let p v = (not v.vglob) || not (TaintPartialContexts.VS.mem v tainted) in + filter_vars p d let only_global_exprs s = V.for_all (var_in_expr (fun v -> v.vglob)) s let rec get k d = if mem k d && V.cardinal (find k d) = 1 then let s = find k d in match V.choose s with - | Lval (Var v, offs) -> get (v, Lval.CilLval.of_ciloffs offs) d (* transitive lookup *) + | Lval (Var v, offs) -> get (v, Offset.Exp.of_cil offs) d (* transitive lookup *) | _ -> Some s else None let get_elt k d = Option.map V.choose @@ get k d @@ -60,18 +64,18 @@ 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 CilLval *) + let mustPointTo ctx exp = (* this is just to get Mval.Exp *) match mayPointTo ctx exp with | [clval] -> Some clval | _ -> None @@ -104,7 +108,7 @@ struct let save_expr lval expr = match mustPointTo ctx (AddrOf lval) with | Some clval -> - if M.tracing then M.tracel "condvars" "CondVars: saving %a = %a" Lval.CilLval.pretty clval d_exp expr; + if M.tracing then M.tracel "condvars" "CondVars: saving %a = %a\n" Mval.Exp.pretty clval d_exp expr; D.add clval (D.V.singleton expr) d (* if lval must point to clval, add expr *) | None -> d in @@ -136,18 +140,23 @@ struct let enter ctx (lval: lval option) (f:fundec) (args:exp list) : (D.t * D.t) list = [ctx.local, D.bot ()] - let combine ctx (lval:lval option) fexp (f:fundec) (args:exp list) fc (au:D.t) : D.t = + let combine_env ctx lval fexp f args fc au (f_ask: Queries.ask) = (* combine caller's state with globals from callee *) (* TODO (precision): globals with only global vars are kept, the rest is lost -> collect which globals are assigned to *) (* D.merge (fun k s1 s2 -> match s2 with Some ss2 when (fst k).vglob && D.only_global_exprs ss2 -> s2 | _ when (fst k).vglob -> None | _ -> s1) ctx.local au *) - D.only_locals ctx.local (* globals might have changed... *) + let tainted = TaintPartialContexts.conv_varset (f_ask.f Queries.MayBeTainted) in + D.only_untainted ctx.local tainted (* tainted globals might have changed... *) + + let combine_assign ctx (lval:lval option) fexp (f:fundec) (args:exp list) fc (au:D.t) (f_ask: Queries.ask) : D.t = + ctx.local let special ctx (lval: lval option) (f:varinfo) (arglist:exp list) : D.t = + (* TODO: shouldn't there be some kind of invalidadte, depending on the effect of the special function? *) 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/deadlock.ml b/src/analyses/deadlock.ml index 8ff6f372c4..c23d6f4294 100644 --- a/src/analyses/deadlock.ml +++ b/src/analyses/deadlock.ml @@ -1,6 +1,7 @@ -(** Deadlock analysis. *) +(** Deadlock analysis ([deadlock]). *) -open Prelude.Ana +open Batteries +open GoblintCil open Analyses open DeadlockDomain @@ -23,7 +24,7 @@ struct module G = MapDomain.MapBot (Lock) (MayLockEventPairs) let side_lock_event_pair ctx ((before_node, _, _) as before) ((after_node, _, _) as after) = - if !GU.should_warn then + if !AnalysisState.should_warn then ctx.sideg before_node (G.singleton after_node (MayLockEventPairs.singleton (before, after))) let part_access ctx: MCPAccess.A.t = diff --git a/src/analyses/expRelation.ml b/src/analyses/expRelation.ml index 529df6bc10..39df650bc0 100644 --- a/src/analyses/expRelation.ml +++ b/src/analyses/expRelation.ml @@ -1,16 +1,15 @@ +(** Stateless symbolic comparison expression analysis ([expRelation]). *) + (** An analysis specification to answer questions about how two expressions relate to each other. *) -(** Currently this works purely syntactically on the expressions, and only for =_{must}. *) -(** Does not keep state, this is only formulated as an analysis to integrate well into framework *) +(** Currently this works purely syntactically on the expressions, and only for {m =_{must}}. *) +(** Does not keep state, this is only formulated as an analysis to integrate well into the framework. *) -open Prelude.Ana +open GoblintCil open Analyses -open Cilint module Spec : Analyses.MCPSpec = struct - include Analyses.DefaultSpec - module D = Lattice.Unit - module C = Lattice.Unit + include UnitAnalysis.Spec let name () = "expRelation" @@ -57,13 +56,13 @@ struct begin (* Compare the cilint first in the hope that it is cheaper than the LVal comparison *) match e1, e2 with - | BinOp(PlusA, Lval l1, Const(CInt(i,_,_)), _), Lval l2 when (compare_cilint i zero_cilint > 0 && lvalsEq l1 l2) -> + | BinOp(PlusA, Lval l1, Const(CInt(i,_,_)), _), Lval l2 when (Z.compare i Z.zero > 0 && lvalsEq l1 l2) -> Queries.ID.of_bool (Cilfacade.get_ikind t) false (* c > 0 => (! x+c < x) *) - | Lval l1, BinOp(PlusA, Lval l2, Const(CInt(i,_,_)), _) when (compare_cilint i zero_cilint < 0 && lvalsEq l1 l2) -> + | Lval l1, BinOp(PlusA, Lval l2, Const(CInt(i,_,_)), _) when (Z.compare i Z.zero < 0 && lvalsEq l1 l2) -> Queries.ID.of_bool (Cilfacade.get_ikind t) false (* c < 0 => (! x < x+c )*) - | BinOp(MinusA, Lval l1, Const(CInt(i,_,_)), _), Lval l2 when (compare_cilint i zero_cilint < 0 && lvalsEq l1 l2) -> + | BinOp(MinusA, Lval l1, Const(CInt(i,_,_)), _), Lval l2 when (Z.compare i Z.zero < 0 && lvalsEq l1 l2) -> Queries.ID.of_bool (Cilfacade.get_ikind t) false (* c < 0 => (! x-c < x) *) - | Lval l1, BinOp(MinusA, Lval l2, Const(CInt(i,_,_)), _) when (compare_cilint i zero_cilint > 0 && lvalsEq l1 l2) -> + | Lval l1, BinOp(MinusA, Lval l2, Const(CInt(i,_,_)), _) when (Z.compare i Z.zero > 0 && lvalsEq l1 l2) -> Queries.ID.of_bool (Cilfacade.get_ikind t) false (* c > 0 => (! x < x-c) *) | _ -> Queries.ID.top () @@ -74,41 +73,12 @@ struct | BinOp(PlusA, Lval l1, Const(CInt(i,_,_)), _), Lval l2 | Lval l2, BinOp(PlusA, Lval l1, Const(CInt(i,_,_)), _) | BinOp(MinusA, Lval l1, Const(CInt(i,_,_)), _), Lval l2 - | Lval l2, BinOp(MinusA, Lval l1, Const(CInt(i,_,_)), _) when compare_cilint i zero_cilint <> 0 && (lvalsEq l1 l2) -> + | Lval l2, BinOp(MinusA, Lval l1, Const(CInt(i,_,_)), _) when Z.compare i Z.zero <> 0 && (lvalsEq l1 l2) -> Queries.ID.of_bool (Cilfacade.get_ikind t) false | _ -> Queries.ID.top () end | _ -> Queries.Result.top q - - - (* below here is all the usual stuff an analysis requires, we don't do anything here *) - (* transfer functions *) - let assign ctx (lval:lval) (rval:exp) : D.t = - ctx.local - - let branch ctx (exp:exp) (tv:bool) : D.t = - ctx.local - - let body ctx (f:fundec) : D.t = - ctx.local - - let return ctx (exp:exp option) (f:fundec) : D.t = - ctx.local - - let enter ctx (lval: lval option) (f:fundec) (args:exp list) : (D.t * D.t) list = - [ctx.local, ctx.local] - - let combine ctx (lval:lval option) fexp (f:fundec) (args:exp list) fc (au:D.t) : D.t = - au - - let special ctx (lval: lval option) (f:varinfo) (arglist:exp list) : D.t = - 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 exitstate v = D.top () end let _ = diff --git a/src/analyses/expsplit.ml b/src/analyses/expsplit.ml index a9789fb618..fef3d9ff9f 100644 --- a/src/analyses/expsplit.ml +++ b/src/analyses/expsplit.ml @@ -1,4 +1,7 @@ -open Prelude.Ana +(** Path-sensitive analysis according to values of arbitrary given expressions ([expsplit]). *) + +open Batteries +open GoblintCil open Analyses module M = Messages @@ -16,8 +19,7 @@ struct let exitstate = startstate include Analyses.DefaultSpec - - let should_join = D.equal + module P = IdentityP (D) let emit_splits ctx d = D.iter (fun e _ -> @@ -46,28 +48,45 @@ struct let return ctx (exp:exp option) (f:fundec) = emit_splits_ctx ctx - let combine ctx (lval:lval option) fexp (f:fundec) (args:exp list) fc au = + let combine_env ctx lval fexp f args fc au f_ask = let d = D.join ctx.local au in - emit_splits ctx d + emit_splits ctx d (* Update/preserve splits for globals in combined environment. *) + + let combine_assign ctx (lval:lval option) fexp (f:fundec) (args:exp list) fc au (f_ask: Queries.ask) = + emit_splits_ctx ctx (* Update/preserve splits over assigned variable. *) let special ctx (lval: lval option) (f:varinfo) (arglist:exp list) = - let d = match f.vname with - | "__goblint_split_begin" -> + let d = match (LibraryFunctions.find f).special arglist, f.vname with + | _, "__goblint_split_begin" -> let exp = List.hd arglist in let ik = Cilfacade.get_ikind_exp exp in (* TODO: something different for pointers, currently casts pointers to ints and loses precision (other than NULL) *) D.add exp (ID.top_of ik) ctx.local (* split immediately follows *) - | "__goblint_split_end" -> + | _, "__goblint_split_end" -> let exp = List.hd arglist in D.remove exp ctx.local + | Setjmp { env }, _ -> + Option.map_default (fun lval -> + match GobConfig.get_string "ana.setjmp.split" with + | "none" -> ctx.local + | "precise" -> + let e = Lval lval in + let ik = Cilfacade.get_ikind_exp e in + D.add e (ID.top_of ik) ctx.local + | "coarse" -> + let e = Lval lval in + let e = BinOp (Eq, e, integer 0, intType) in + D.add e (ID.top_of IInt) ctx.local + | _ -> failwith "Invalid value for ana.setjmp.split" + ) ctx.local lval | _ -> ctx.local 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 = @@ -75,6 +94,8 @@ struct | UpdateExpSplit exp -> let value = ctx.ask (EvalInt exp) in D.add exp value ctx.local + | Longjmped _ -> + emit_splits_ctx ctx | _ -> ctx.local end diff --git a/src/analyses/extractPthread.ml b/src/analyses/extractPthread.ml index 6f8ab04928..f084a21edb 100644 --- a/src/analyses/extractPthread.ml +++ b/src/analyses/extractPthread.ml @@ -1,6 +1,8 @@ -(** Tracking of pthread lib code. Output to promela. *) +(** Promela extraction analysis for Pthread programs ([extract-pthread]). *) -open Prelude.Ana +open GoblintCil +open Pretty +open GobPretty open Analyses open Cil open BatteriesExceptionless @@ -242,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 @@ -387,8 +389,7 @@ module Variables = struct let get_globals () = Hashtbl.values !table |> List.of_enum - |> List.map Set.elements - |> List.flatten + |> List.concat_map Set.elements |> List.filter_map (function | Var v when Variable.is_global v -> Some v @@ -572,7 +573,7 @@ module Codegen = struct module Writer = struct let write desc ext content = - let dir = Goblintutil.create_dir (Fpath.v "pml-result") in + let dir = GobSys.mkdir_or_exists_absolute (Fpath.v "pml-result") in let path = Fpath.to_string @@ Fpath.append dir (Fpath.v ("pthread." ^ ext)) in output_file ~filename:path ~text:content ; print_endline @@ "saved " ^ desc ^ " as " ^ path @@ -748,7 +749,7 @@ module Codegen = struct |> List.of_enum |> List.filter (fun res -> Resource.res_type res = Resource.Thread) |> List.unique - |> List.sort (compareBy PmlResTbl.get) + |> List.sort (BatOrd.map_comp PmlResTbl.get compare) |> List.concat_map process_def in let fun_ret_defs = @@ -768,7 +769,7 @@ module Codegen = struct escape body in Tbls.FunCallTbl.to_list () - |> List.group (compareBy (fst % fst)) + |> List.group (BatOrd.map_comp (fst % fst) compare) |> List.concat_map fun_map in let globals = List.map Variable.show_def @@ Variables.get_globals () in @@ -842,8 +843,7 @@ module Codegen = struct Hashtbl.keys Edges.table |> List.of_enum |> List.unique - |> List.map dot_thread - |> List.concat + |> List.concat_map dot_thread in String.concat "\n " ("digraph file {" :: lines) ^ "}" in @@ -869,32 +869,17 @@ 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 = - Goblintutil.create_var (makeGlobalVar "__GOBLINT_PTHREAD_TASKS" voidPtrType) + Cilfacade.create_var (makeGlobalVar "__GOBLINT_PTHREAD_TASKS" voidPtrType) 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 @@ -967,7 +952,7 @@ module Spec : Analyses.MCPSpec = struct in let var_str = Variable.show % Option.get % Variable.make_from_lhost in let pred_str op lhs rhs = - let cond_str = lhs ^ " " ^ sprint d_binop op ^ " " ^ rhs in + let cond_str = lhs ^ " " ^ CilType.Binop.show op ^ " " ^ rhs in if tv then cond_str else "!(" ^ cond_str ^ ")" in @@ -1034,7 +1019,7 @@ module Spec : Analyses.MCPSpec = struct let body ctx (f : fundec) : D.t = (* enter is not called for spawned threads -> initialize them here *) - let context_hash = Int64.of_int (if not !Goblintutil.global_initialization then ControlSpecC.hash (ctx.control_context ()) else 37) in + let context_hash = Int64.of_int (if not !AnalysisState.global_initialization then ControlSpecC.hash (ctx.control_context ()) else 37) in { ctx.local with ctx = Ctx.of_int context_hash } @@ -1056,15 +1041,10 @@ module Spec : Analyses.MCPSpec = struct (* set predecessor set to start node of function *) [ (d_caller, d_callee) ] + let combine_env ctx lval fexp f args fc au f_ask = + ctx.local - let combine - ctx - (lval : lval option) - fexp - (f : fundec) - (args : exp list) - fc - (au : D.t) : D.t = + let combine_assign ctx (lval : lval option) fexp (f : fundec) (args : exp list) fc (au : D.t) (f_ask: Queries.ask) : D.t = if D.any_is_bot ctx.local || D.any_is_bot au then ctx.local else @@ -1129,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 (v, o) -> - let lval = (Var v, Lval.CilLval.to_ciloffs o) 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 @@ -1153,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 @@ -1259,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 index 9fc51b0ee6..58257b7843 100644 --- a/src/analyses/fileUse.ml +++ b/src/analyses/fileUse.ml @@ -1,6 +1,9 @@ -(** An analysis for checking correct use of file handles. *) +(** Analysis of correct file handle usage ([file]). -open Prelude.Ana + @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 = @@ -12,8 +15,8 @@ struct module C = FileDomain.Dom (* special variables *) - let return_var = Goblintutil.create_var @@ Cil.makeVarinfo false "@return" Cil.voidType, `NoOffset - let unclosed_var = Goblintutil.create_var @@ Cil.makeVarinfo false "@unclosed" Cil.voidType, `NoOffset + 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 @@ -24,19 +27,20 @@ struct | 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 = + let query_ad (ask: Queries.ask) exp = match ask.f (Queries.MayPointTo exp) with - | l when not (Queries.LS.is_top l) -> - Queries.LS.elements l + | ad when not (Queries.AD.is_top ad) -> Queries.AD.elements ad | _ -> [] 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 addrs = query_ad ask exp in (* MayPointTo -> LValSet *) + let pretty_key = function + | Queries.AD.Addr.Addr (v,o) -> Pretty.text (D.string_of_key (v, ValueDomain.Addr.Offs.to_exp o)) + | _ -> Pretty.text "" in + if M.tracing then M.tracel "file" "%s MayPointTo %a = [%a]" msg d_exp exp (Pretty.docList ~sep:(Pretty.text ", ") pretty_key) addrs let eval_fv ask exp: varinfo option = - match query_lv ask exp with - | [(v,_)] -> Some v + match query_ad ask exp with + | [addr] -> Queries.AD.Addr.to_var_may addr | _ -> None @@ -84,7 +88,7 @@ struct | 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 Cilint.compare_cilint i Cilint.zero_cilint = 0 && tv then ( + if Z.compare i Z.zero = 0 && tv then ( (* ignore(printf "error-branch\n"); *) D.error k m )else @@ -163,12 +167,15 @@ struct D.extend_value unclosed_var (mustOpen, mayOpen) m ) else m - let combine ctx (lval:lval option) fexp (f:fundec) (args:exp list) fc (au:D.t) : D.t = + 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?) *) - let m = D.without_special_vars au |> D.add_all m in + 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 -> @@ -203,7 +210,7 @@ struct (* 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 @@ "could not resolve "^sprint d_exp (Lval lval); m) + 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 @@ -217,7 +224,7 @@ struct (* 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 CilLval from lval *) + (* 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 @@ -243,7 +250,7 @@ struct | _ -> D.warn "[Unsound]unknown filename"; D.fopen k loc "???" mode m ) | xs -> - let args = (String.concat ", " (List.map (sprint d_exp) xs)) in + 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 @@ -280,8 +287,8 @@ struct | _ -> m 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/libraryDesc.ml b/src/analyses/libraryDesc.ml index 3e848c45db..72a4261cb5 100644 --- a/src/analyses/libraryDesc.ml +++ b/src/analyses/libraryDesc.ml @@ -1,6 +1,7 @@ (** Library function descriptor (specification). *) -module Cil = GoblintCil +module Cil = GoblintCil +open Cil (** Pointer argument access specification. *) module Access = struct @@ -13,38 +14,40 @@ struct 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) + | 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) + | 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) [@@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 @@ -53,15 +56,29 @@ type special = | 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 } - | Strcpy of { dest: Cil.exp; src: Cil.exp } (* TODO: add count for strncpy when actually used *) + | 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; } + | Rand | Unknown (** Anything not belonging to other types. *) (* TODO: rename to Other? *) @@ -115,29 +132,54 @@ type t = { 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 = { +let of_old ?(attrs: attr list=[]) (old_accesses: Accesses.old): t = { attrs; accs = Accesses.of_old old_accesses; - special = special_of_old classify_name; + 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 + | 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 + + 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.mli b/src/analyses/libraryDsl.mli index e134ad68d7..fd0bc45c26 100644 --- a/src/analyses/libraryDsl.mli +++ b/src/analyses/libraryDsl.mli @@ -18,11 +18,11 @@ type ('k, 'r) args_desc = | (::): ('k, _, 'm) arg_desc * ('m, 'r) args_desc -> ('k, 'r) args_desc (** Cons one argument descriptor. Argument must occur. *) -(** Create library function descriptor from arguments descriptor and continuation function, which takes as many arguments as are captured using {!__} and returns the corresponding {!LibraryDesc.special}. *) +(** Create library function descriptor from arguments descriptor and continuation function, which takes as many arguments as are captured using {!__} and returns the corresponding {!LibraryDesc.type-special}. *) val special: ?attrs:LibraryDesc.attr list -> ('k, LibraryDesc.special) args_desc -> 'k -> LibraryDesc.t -(** Create library function descriptor from arguments descriptor, which must {!drop} all arguments, and continuation function, which takes as an {!unit} argument and returns the corresponding {!LibraryDesc.special}. - Unlike {!special}, this allows the {!LibraryDesc.special} of an argumentless function to depend on options, such that they aren't evaluated at initialization time in {!LibraryFunctions}. *) +(** Create library function descriptor from arguments descriptor, which must {!drop} all arguments, and continuation function, which takes as an {!unit} argument and returns the corresponding {!LibraryDesc.type-special}. + Unlike {!special}, this allows the {!LibraryDesc.type-special} of an argumentless function to depend on options, such that they aren't evaluated at initialization time in {!LibraryFunctions}. *) val special': ?attrs:LibraryDesc.attr list -> (LibraryDesc.special, LibraryDesc.special) args_desc -> (unit -> LibraryDesc.special) -> LibraryDesc.t (** Create unknown library function descriptor from arguments descriptor, which must {!drop} all arguments. *) diff --git a/src/analyses/libraryFunctionEffects.ml b/src/analyses/libraryFunctionEffects.ml deleted file mode 100644 index 7e03ba1ba9..0000000000 --- a/src/analyses/libraryFunctionEffects.ml +++ /dev/null @@ -1,5 +0,0 @@ -open GoblintCil - -let effects: (string -> Cil.exp list -> (Cil.lval * _) list option) list ref = ref [] -let add_effects f = effects := f :: !effects -let effects_for fname args = List.filter_map (fun f -> f fname args) !effects diff --git a/src/analyses/libraryFunctionEffects.mli b/src/analyses/libraryFunctionEffects.mli deleted file mode 100644 index 7226b5d9ea..0000000000 --- a/src/analyses/libraryFunctionEffects.mli +++ /dev/null @@ -1,5 +0,0 @@ -(* can't use Base.Main.store b/c of circular build - this is painful... *) -open GoblintCil - -val add_effects : (string -> Cil.exp list -> (Cil.lval * ValueDomain.Compound.t) list option) -> unit -val effects_for : string -> Cil.exp list -> (Cil.lval * ValueDomain.Compound.t) list list diff --git a/src/analyses/libraryFunctions.ml b/src/analyses/libraryFunctions.ml index f63895c6c6..117dcbd236 100644 --- a/src/analyses/libraryFunctions.ml +++ b/src/analyses/libraryFunctions.ml @@ -1,6 +1,7 @@ (** Tools for dealing with library functions. *) -open Prelude.Ana +open Batteries +open GoblintCil open GobConfig module M = Messages @@ -11,17 +12,77 @@ 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 }); - ("strncpy", special [__ "dest" [w]; __ "src" [r]; drop "n" []] @@ fun dest src -> Strcpy { dest; src }); - ("strcpy", special [__ "dest" [w]; __ "src" [r]] @@ fun dest src -> Strcpy { dest; src }); + ("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]]); - ("fscanf", unknown ((drop "stream" [r; w]) :: (drop "format" [r]) :: (VarArgs (drop' [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]]); @@ -29,21 +90,65 @@ let c_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ ("iswalnum", unknown [drop "wc" []]); ("iswprint", unknown [drop "wc" []]); ("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 [drop "rm" [r]]); + ("ctime", unknown ~attrs:[ThreadUnsafe] [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' []))); + ("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", 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); ] (** C POSIX library functions. @@ -53,26 +158,91 @@ let posix_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ ("__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" []]); + ("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 [drop "stream" [w]]); - ("getchar_unlocked", unknown []); - ("putc_unlocked", unknown [drop "c" []; drop "stream" [w]]); - ("putchar_unlocked", unknown [drop "c" []]); + ("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" []]); - ("fseeko", unknown [drop "stream" [w]; 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" []]); @@ -80,42 +250,267 @@ let posix_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ ("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" []]); + ("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 [drop "in" []]); + ("inet_ntoa", unknown ~attrs:[ThreadUnsafe] [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" []]); + ("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" []]); + ("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 *) ] (** 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); (* gcc's built-in assert *) + ("__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' []))); + ("__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]]); @@ -144,17 +539,51 @@ let gcc_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ ("__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]]); - ("futimesat", unknown [drop "dirfd" [w]; drop "pathname" [r]; drop "times" [r]]); - ("error", unknown ((drop "status" []):: (drop "errnum" []) :: (drop "format" [r]) :: (VarArgs (drop' [r])))); + ("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_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" []]); @@ -173,17 +602,104 @@ let glibc_desc_list: (string * LibraryDesc.t) list = LibraryDsl.[ ("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 big_kernel_lock = AddrOf (Cil.var (Goblintutil.create_var (makeGlobalVar "[big kernel lock]" intType))) -let console_sem = AddrOf (Cil.var (Goblintutil.create_var (makeGlobalVar "[console semaphore]" intType))) +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" []]); + ] + +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. *) -(* TODO: conditional on kernel option *) -let linux_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ +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); @@ -196,6 +712,10 @@ let linux_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ ("__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. *) @@ -213,7 +733,7 @@ let goblint_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ 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", unknown [drop "ptr" [f]; drop "customMem" [r]]); + ("ZSTD_customFree", special [__ "ptr" [f]; drop "customMem" [r]] @@ fun ptr -> Free ptr); ] (** math functions. @@ -301,6 +821,144 @@ let math_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ ("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", unknown [drop "x" []]); + ("sqrtf", unknown [drop "x" []]); + ("sqrtl", unknown [drop "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 *) @@ -312,94 +970,152 @@ let math_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[ ("__fpclassifyl", unknown [drop "x" []]); ] -let verifier_atomic_var = Goblintutil.create_var (makeGlobalVar "[__VERIFIER_atomic]" intType) -let verifier_atomic = AddrOf (Cil.var (Goblintutil.create_var verifier_atomic_var)) +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. *) -(* TODO: conditional on ana.sv-comp.functions option *) 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 *) ] -(* TODO: allow selecting which lists to use *) -let library_descs = Hashtbl.of_list (List.concat [ - c_descs_list; - posix_descs_list; - pthread_descs_list; - gcc_descs_list; - glibc_desc_list; - linux_descs_list; - goblint_descs_list; - zstd_descs_list; - math_descs_list; - svcomp_descs_list; - ]) +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 []); + ] -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 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 classify fn exps: categories = - let strange_arguments () = - M.warn ~category:Program "%s arguments are strange!" fn; - `Unknown fn +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 - 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 + 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 @@ -423,70 +1139,70 @@ struct let writesAllButFirst n f a x = match a with - | Write | Spawn -> f a x @ drop n x + | Write | Call | 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 + | 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 | Spawn -> o + | Write | Call | Spawn -> o | Read -> i | Free -> [] let writes ns a x = let i, o = partition ns x in match a with - | Write | Spawn -> i + | Write | Call | Spawn -> i | Read -> o | Free -> [] let frees ns a x = let i, o = partition ns x in match a with - | Write | Spawn -> [] + | Write | Call | Spawn -> [] | Read -> o | Free -> i let readsFrees rs fs a x = match a with - | Write | Spawn -> [] + | Write | Call | Spawn -> [] | Read -> keep rs x | Free -> keep fs x let onlyReads ns a x = match a with - | Write | Spawn -> [] + | Write | Call | Spawn -> [] | Read -> keep ns x | Free -> [] let onlyWrites ns a x = match a with - | Write | Spawn -> keep ns x + | Write | Call | Spawn -> keep ns x | Read -> [] | Free -> [] let readsWrites rs ws a x = match a with - | Write | Spawn -> keep ws x + | Write | Call | Spawn -> keep ws x | Read -> keep rs x | Free -> [] let readsAll a x = match a with - | Write | Spawn -> [] + | Write | Call | Spawn -> [] | Read -> x | Free -> [] let writesAll a x = match a with - | Write | Spawn -> x + | Write | Call | Spawn -> x | Read -> [] | Free -> [] end @@ -497,249 +1213,65 @@ open Invalidate * 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*) - "strcmp", readsAll;(*safe*) - "strftime", writes [1];(*keep [1]*) - "strlen", readsAll;(*safe*) - "strncmp", readsAll;(*safe*) - "strncat", writes [1];(*keep [1]*) - "strstr", readsAll;(*safe*) - "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*) - "__builtin_strcmp", readsAll;(*safe*) - "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_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*) - "__builtin___strcpy", writes [1];(*keep [1]*) - "__builtin___strcpy_chk", writes [1];(*keep [1]*) - "strcat", writes [1];(*keep [1]*) - "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; @@ -747,261 +1279,33 @@ let invalidate_actions = [ "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*) - "rand", 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 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"]) @@ -1015,13 +1319,15 @@ let is_safe_uncalled fn_name = List.exists (fun r -> Str.string_match r fn_name 0) kernel_safe_uncalled_regex -let unknown_desc ~f name = (* TODO: remove name argument, unknown function shouldn't have classify *) +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 @@ -1031,27 +1337,23 @@ let unknown_desc ~f name = (* TODO: remove name argument, unknown function shoul 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 + (* 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 library_descs name with + match Hashtbl.find_option (ResettableLazy.force activated_library_descs) name with | Some desc -> desc | None -> - match get_invalidate_action name with + match Hashtbl.find_option invalidate_actions name with | Some old_accesses -> - LibraryDesc.of_old old_accesses (classify name) + LibraryDesc.of_old old_accesses | None -> - unknown_desc ~f name + unknown_desc f let is_special fv = diff --git a/src/analyses/libraryFunctions.mli b/src/analyses/libraryFunctions.mli index 6a641652bc..9a8e55a48a 100644 --- a/src/analyses/libraryFunctions.mli +++ b/src/analyses/libraryFunctions.mli @@ -1,4 +1,5 @@ -(** This allows us to query information about library functions. *) +(** Hard-coded database of library function specifications. *) + open GoblintCil val add_lib_funs : string list -> unit @@ -17,3 +18,5 @@ val is_special: Cil.varinfo -> bool val verifier_atomic_var: Cil.varinfo + +val reset_lazy: unit -> unit 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/mCP.ml b/src/analyses/mCP.ml index dbe69bd898..50f6d5409b 100644 --- a/src/analyses/mCP.ml +++ b/src/analyses/mCP.ml @@ -1,6 +1,7 @@ -(** Master Control Program *) +(** MCP analysis specification. *) -open Prelude.Ana +open Batteries +open GoblintCil open GobConfig open Analyses @@ -10,12 +11,14 @@ module MCP2 : Analyses.Spec with module D = DomListLattice (LocalDomainListSpec) and module G = DomVariantLattice (GlobalDomainListSpec) and module C = DomListPrintable (ContextListSpec) - and module V = DomVariantSysVar (VarListSpec) = + and module V = DomVariantSysVar (VarListSpec) + and module P = DomListRepresentative (PathListSpec) = struct module D = DomListLattice (LocalDomainListSpec) module G = DomVariantLattice (GlobalDomainListSpec) module C = DomListPrintable (ContextListSpec) module V = DomVariantSysVar (VarListSpec) + module P = DomListRepresentative (PathListSpec) open List open Obj let v_of n v = (n, repr v) @@ -56,7 +59,7 @@ struct if not (exists (fun (y',_) -> y=y') xs) then begin let xn = find_spec_name x in Legacy.Printf.eprintf "Activated analysis '%s' depends on '%s' and '%s' is not activated.\n" xn yn yn; - raise Exit + raise Stdlib.Exit end in let deps (x,_) = iter (check_dep x) @@ (find_spec x).dep in @@ -81,6 +84,7 @@ struct check_deps !activated; activated := topo_sort_an !activated; activated_ctx_sens := List.filter (fun (n, _) -> not (List.mem n !cont_inse)) !activated; + activated_path_sens := List.filter (fun (n, _) -> List.mem n !path_sens) !activated; match marshal with | Some marshal -> iter2 (fun (_,{spec=(module S:MCPSpec); _}) marshal -> S.init (Some (Obj.obj marshal))) !activated marshal @@ -110,27 +114,10 @@ struct Some (n, repr @@ S.context fd (obj d)) ) x - let should_join x y = - (* TODO: GobList.for_all3 *) - let rec zip3 lst1 lst2 lst3 = match lst1,lst2,lst3 with - | [],_, _ -> [] - | _,[], _ -> [] - | _,_ , []-> [] - | (x::xs),(y::ys), (z::zs) -> (x,y,z)::(zip3 xs ys zs) - in - let should_join ((_,(module S:Analyses.MCPSpec),_),(_,x),(_,y)) = S.should_join (obj x) (obj y) in - (* obtain all analyses specs that are path sensitive and their values both in x and y *) - let specs = filter (fun (x,_,_) -> mem x !path_sens) (spec_list x) in - let xs = filter (fun (x,_) -> mem x !path_sens) x in - let ys = filter (fun (x,_) -> mem x !path_sens) y in - let zipped = zip3 specs xs ys in - List.for_all should_join zipped - let exitstate v = map (fun (n,{spec=(module S:MCPSpec); _}) -> n, repr @@ S.exitstate v) !activated let startstate v = map (fun (n,{spec=(module S:MCPSpec); _}) -> n, repr @@ S.startstate v) !activated let morphstate v x = map (fun (n,(module S:MCPSpec),d) -> n, repr @@ S.morphstate v (obj d)) (spec_list x) - let rec assoc_replace (n,c) = function | [] -> failwith "assoc_replace" | (n',c')::xs -> if n=n' then (n,c)::xs else (n',c') :: assoc_replace (n,c) xs @@ -153,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) = @@ -307,6 +296,8 @@ struct (* TODO: only query others that actually respond to EvalInt *) (* 2x speed difference on SV-COMP nla-digbench-scaling/ps6-ll_valuebound5.c *) f (Result.top ()) (!base_id, spec !base_id, assoc !base_id ctx.local) *) + | Queries.DYojson -> + `Lifted (D.to_yojson ctx.local) | _ -> let r = fold_left (f ~q) (Result.top ()) @@ spec_list ctx.local in do_sideg ctx !sides; @@ -333,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) @@ -518,11 +509,40 @@ struct do_spawns ctx !spawns; map (fun xs -> (topo_sort_an @@ map fst xs, topo_sort_an @@ map snd xs)) @@ n_cartesian_product css - let combine (ctx:(D.t, G.t, C.t, V.t) ctx) r fe f a fc fd = + let combine_env (ctx:(D.t, G.t, C.t, V.t) ctx) r fe f a fc fd f_ask = + let spawns = ref [] in + let sides = ref [] in + let emits = ref [] in + let ctx'' = outer_ctx "combine_env" ~spawns ~sides ~emits ctx in + (* Like spec_list2 but for three lists. Tail recursion like map3_rev would have. + Due to context-insensitivity, second list is optional and may only contain a subset of analyses + in the same order, so some skipping needs to happen to align the three lists. + See https://github.com/goblint/analyzer/pull/578/files#r794376508. *) + let rec spec_list3_rev_acc acc l1 l2_opt l3 = match l1, l2_opt, l3 with + | [], _, [] -> acc + | (n, x) :: l1, Some ((n', y) :: l2), (n'', z) :: l3 when n = n' -> (* context-sensitive *) + assert (n = n''); + spec_list3_rev_acc ((n, spec n, (x, Some y, z)) :: acc) l1 (Some l2) l3 + | (n, x) :: l1, l2_opt, (n'', z) :: l3 -> (* context-insensitive *) + assert (n = n''); + spec_list3_rev_acc ((n, spec n, (x, None, z)) :: acc) l1 l2_opt l3 + | _, _, _ -> invalid_arg "MCP.spec_list3_rev_acc" + in + let f post_all (n,(module S:MCPSpec),(d,fc,fd)) = + let ctx' : (S.D.t, S.G.t, S.C.t, S.V.t) ctx = inner_ctx "combine_env" ~post_all ctx'' n d in + n, repr @@ S.combine_env ctx' r fe f a (Option.map obj fc) (obj fd) f_ask + in + let d, q = map_deadcode f @@ List.rev @@ spec_list3_rev_acc [] ctx.local fc fd in + do_sideg ctx !sides; + do_spawns ctx !spawns; + let d = do_emits ctx !emits d q in + if q then raise Deadcode else d + + let combine_assign (ctx:(D.t, G.t, C.t, V.t) ctx) r fe f a fc fd f_ask = let spawns = ref [] in let sides = ref [] in let emits = ref [] in - let ctx'' = outer_ctx "combine" ~spawns ~sides ~emits ctx in + let ctx'' = outer_ctx "combine_assign" ~spawns ~sides ~emits ctx in (* Like spec_list2 but for three lists. Tail recursion like map3_rev would have. Due to context-insensitivity, second list is optional and may only contain a subset of analyses in the same order, so some skipping needs to happen to align the three lists. @@ -538,8 +558,8 @@ struct | _, _, _ -> invalid_arg "MCP.spec_list3_rev_acc" in let f post_all (n,(module S:MCPSpec),(d,fc,fd)) = - let ctx' : (S.D.t, S.G.t, S.C.t, S.V.t) ctx = inner_ctx "combine" ~post_all ctx'' n d in - n, repr @@ S.combine ctx' r fe f a (Option.map obj fc) (obj fd) + let ctx' : (S.D.t, S.G.t, S.C.t, S.V.t) ctx = inner_ctx "combine_assign" ~post_all ctx'' n d in + n, repr @@ S.combine_assign ctx' r fe f a (Option.map obj fc) (obj fd) f_ask in let d, q = map_deadcode f @@ List.rev @@ spec_list3_rev_acc [] ctx.local fc fd in do_sideg ctx !sides; @@ -547,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 @@ -568,10 +588,15 @@ 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; let d = do_emits ctx !emits d q in if q then raise Deadcode else d + + let event (ctx:(D.t, G.t, C.t, V.t) ctx) e _ = do_emits ctx [e] ctx.local false + + (* Just to satisfy signature *) + let paths_as_set ctx = [ctx.local] end diff --git a/src/analyses/mCPAccess.ml b/src/analyses/mCPAccess.ml index 44b5931496..92db6fbc5d 100644 --- a/src/analyses/mCPAccess.ml +++ b/src/analyses/mCPAccess.ml @@ -1,3 +1,4 @@ +(** {{!Analyses.MCPA} Memory access metadata module} for MCP. *) open MCPRegistry module Pretty = GoblintCil.Pretty diff --git a/src/analyses/mCPRegistry.ml b/src/analyses/mCPRegistry.ml index 620fbc8c79..810da827ff 100644 --- a/src/analyses/mCPRegistry.ml +++ b/src/analyses/mCPRegistry.ml @@ -1,4 +1,9 @@ -open Prelude.Ana +(** Registry of dynamically activatable analyses. + Analysis specification modules for the dynamic product. *) + +open Batteries +open GoblintCil +open Pretty open Analyses type spec_modules = { name : string @@ -8,10 +13,12 @@ type spec_modules = { name : string ; glob : (module Lattice.S) ; cont : (module Printable.S) ; var : (module SpecSysVar) - ; acc : (module MCPA) } + ; acc : (module MCPA) + ; path : (module DisjointDomain.Representative) } let activated : (int * spec_modules) list ref = ref [] let activated_ctx_sens: (int * spec_modules) list ref = ref [] +let activated_path_sens: (int * spec_modules) list ref = ref [] let registered: (int, spec_modules) Hashtbl.t = Hashtbl.create 100 let registered_name: (string, int) Hashtbl.t = Hashtbl.create 100 @@ -19,6 +26,12 @@ let register_analysis = let count = ref 0 in fun ?(dep=[]) (module S:MCPSpec) -> let n = S.name () in + let module P = + struct + include S.P + type elt = S.D.t + end + in let s = { name = n ; dep ; spec = (module S : MCPSpec) @@ -27,6 +40,7 @@ let register_analysis = ; cont = (module S.C : Printable.S) ; var = (module S.V : SpecSysVar) ; acc = (module S.A : MCPA) + ; path = (module P : DisjointDomain.Representative) } in Hashtbl.replace registered !count s; @@ -45,6 +59,12 @@ sig val domain_list : unit -> (int * (module Printable.S)) list end +module type DomainListRepresentativeSpec = +sig + val assoc_dom : int -> (module DisjointDomain.Representative) + val domain_list : unit -> (int * (module DisjointDomain.Representative)) list +end + module type DomainListSysVarSpec = sig val assoc_dom : int -> (module SpecSysVar) @@ -75,6 +95,18 @@ struct List.map (fun (x,y) -> (x,f y)) (D.domain_list ()) end +module PrintableOfRepresentativeSpec (D:DomainListRepresentativeSpec) : DomainListPrintableSpec = +struct + let assoc_dom n = + let f (module L:DisjointDomain.Representative) = (module L : Printable.S) + in + f (D.assoc_dom n) + + let domain_list () = + let f (module L:DisjointDomain.Representative) = (module L : Printable.S) in + List.map (fun (x,y) -> (x,f y)) (D.domain_list ()) +end + module PrintableOfMCPASpec (D:DomainListMCPASpec) : DomainListPrintableSpec = struct let assoc_dom n = @@ -114,20 +146,24 @@ struct let unop_fold f a (x:t) = fold_left2 (fun a (n,d) (n',s) -> assert (n = n'); f a n s d) a x (domain_list ()) - 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 unop_map f x = + List.rev @@ unop_fold (fun a n s d -> (n, f s d) :: a) [] x + + 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) @@ -161,12 +197,13 @@ struct let hash = unop_fold (fun a n (module S : Printable.S) x -> hashmul a @@ S.hash (obj x)) 0 - let name () = - let domain_name (n, (module D: Printable.S)) = - let analysis_name = find_spec_name n in - analysis_name ^ ":(" ^ D.name () ^ ")" - in - IO.to_string (List.print ~first:"[" ~last:"]" ~sep:", " String.print) (map domain_name @@ domain_list ()) + (* let name () = + let domain_name (n, (module D: Printable.S)) = + let analysis_name = find_spec_name n in + analysis_name ^ ":(" ^ D.name () ^ ")" + in + IO.to_string (List.print ~first:"[" ~last:"]" ~sep:", " String.print) (map domain_name @@ domain_list ()) *) + let name () = "MCP.C" let printXml f xs = let print_one a n (module S : Printable.S) x : unit = @@ -179,6 +216,8 @@ 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 + + let relift = unop_map (fun (module S: Printable.S) x -> Obj.repr (S.relift (Obj.obj x))) end module DomVariantPrintable (DLSpec : DomainListPrintableSpec) @@ -197,7 +236,8 @@ struct f n (assoc_dom n) d let pretty () = unop_map (fun n (module S: Printable.S) x -> - Pretty.dprintf "%s:%a" (S.name ()) S.pretty (obj x) + let analysis_name = find_spec_name n in + Pretty.dprintf "%s:%a" analysis_name S.pretty (obj x) ) let show = unop_map (fun n (module S: Printable.S) x -> @@ -247,6 +287,8 @@ struct let arbitrary () = let arbs = map (fun (n, (module S: Printable.S)) -> QCheck.map ~rev:(fun (_, o) -> obj o) (fun x -> (n, repr x)) @@ S.arbitrary ()) @@ domain_list () in QCheck.oneof arbs + + let relift = unop_map (fun n (module S: Printable.S) x -> (n, Obj.repr (S.relift (Obj.obj x)))) end module DomVariantSysVar (DLSpec : DomainListSysVarSpec) @@ -257,6 +299,7 @@ struct open Obj include DomVariantPrintable (PrintableOfSysVarSpec (DLSpec)) + let name () = "MCP.V" let unop_map f ((n, d):t) = f n (assoc_dom n) d @@ -266,6 +309,33 @@ struct ) end +module DomListRepresentative (DLSpec : DomainListRepresentativeSpec) + : DisjointDomain.Representative with type t = (int * unknown) list and type elt = (int * unknown) list += +struct + open DLSpec + open List + open Obj + + include DomListPrintable (PrintableOfRepresentativeSpec (DLSpec)) + let name () = "MCP.P" + + type elt = (int * unknown) list + + let of_elt (xs: elt): t = + let rec aux xs ss acc = + match xs, ss with + | [], [] -> acc + | _ :: _, [] -> acc + | (n, d) :: xs', (n', (module P: DisjointDomain.Representative)) :: ss' when n = n' -> + aux xs' ss' ((n, repr (P.of_elt (obj d))) :: acc) + | _ :: xs', _ :: _ -> + aux xs' ss acc + | [], _ :: _ -> invalid_arg "DomListRepresentative.of_elt" + in + List.rev (aux xs (domain_list ()) []) +end + module DomListLattice (DLSpec : DomainListLatticeSpec) : Lattice.S with type t = (int * unknown) list = @@ -275,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 ()) @@ -301,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) @@ -317,6 +395,7 @@ struct open Obj include DomVariantPrintable (PrintableOfLatticeSpec (DLSpec)) + let name () = "MCP.G" let binop_map' (f: int -> (module Lattice.S) -> Obj.t -> Obj.t -> 'a) (n1, d1) (n2, d2) = assert (n1 = n2); @@ -346,7 +425,10 @@ struct end module DomVariantLattice (DLSpec : DomainListLatticeSpec) = - Lattice.Lift (DomVariantLattice0 (DLSpec)) (Printable.DefaultNames) +struct + include Lattice.Lift (DomVariantLattice0 (DLSpec)) (Printable.DefaultNames) + let name () = "MCP.G" +end module LocalDomainListSpec : DomainListLatticeSpec = struct @@ -377,3 +459,9 @@ struct let assoc_dom n = (find_spec n).acc let domain_list () = List.map (fun (n,p) -> n, p.acc) !activated end + +module PathListSpec : DomainListRepresentativeSpec = +struct + let assoc_dom n = (find_spec n).path + let domain_list () = List.map (fun (n,p) -> n, p.path) !activated_path_sens +end diff --git a/src/analyses/mHPAnalysis.ml b/src/analyses/mHPAnalysis.ml index 975f059bf2..a24dbc3cd6 100644 --- a/src/analyses/mHPAnalysis.ml +++ b/src/analyses/mHPAnalysis.ml @@ -1,4 +1,5 @@ -(** MHP access analysis. *) +(** May-happen-in-parallel (MHP) analysis for memory accesses ([mhp]). *) + open Analyses module Spec = diff --git a/src/analyses/mallocFresh.ml b/src/analyses/mallocFresh.ml index fa76c5cbdc..d1314d5009 100644 --- a/src/analyses/mallocFresh.ml +++ b/src/analyses/mallocFresh.ml @@ -1,4 +1,6 @@ -open Prelude.Ana +(** Analysis of unescaped (i.e. thread-local) heap locations ([mallocFresh]). *) + +open GoblintCil open Analyses @@ -17,17 +19,20 @@ 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 - let combine ctx lval f fd args context f_local = + let combine_env ctx lval fexp f args fc au f_ask = + ctx.local (* keep local as opposed to IdentitySpec *) + + let combine_assign ctx lval f fd args context f_local (f_ask: Queries.ask) = match lval with | None -> f_local | Some lval -> assign_lval (Analyses.ask_of_ctx ctx) lval f_local @@ -38,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 @@ -47,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/mallocWrapperAnalysis.ml b/src/analyses/mallocWrapperAnalysis.ml deleted file mode 100644 index 90c167bdad..0000000000 --- a/src/analyses/mallocWrapperAnalysis.ml +++ /dev/null @@ -1,147 +0,0 @@ -(** An analysis that handles the case when malloc is called from a wrapper function all over the code. *) - -open Prelude.Ana -open Analyses -open GobConfig -open ThreadIdDomain -module Q = Queries - -module Spec: Analyses.MCPSpec = -struct - include Analyses.DefaultSpec - - module PL = Lattice.Flat (Node) (struct - let top_name = "Unknown node" - let bot_name = "Unreachable node" - end) - - module Chain = Lattice.Chain (struct - let n () = - let p = get_int "ana.malloc.unique_address_count" in - if p < 0 then - failwith "Option ana.malloc.unique_address_count has to be non-negative" - else p + 1 (* Unique addresses + top address *) - - let names x = if x = (n () - 1) then "top" else Format.asprintf "%d" x - - end) - - (* Map for counting malloc node visits up to n (of the current thread). *) - module MallocCounter = struct - include MapDomain.MapBot_LiftTop(PL)(Chain) - - (* Increase counter for given node. If it does not exists yet, create it. *) - let add_malloc counter node = - let malloc = `Lifted node in - let count = find malloc counter in - if Chain.is_top count then - counter - else - remove malloc counter |> add malloc (count + 1) - end - - module ThreadNode = struct - include Printable.Prod3 (ThreadIdDomain.ThreadLifted) (Node) (Chain) - - (* Description that gets appended to the varinfo-name in user output. *) - let describe_varinfo (v: varinfo) (t, node, c) = - let loc = UpdateCil.getLoc node in - CilType.Location.show loc - - let name_varinfo (t, node, c) = - Format.asprintf "(alloc@sid:%s@tid:%s(#%s))" (Node.show_id node) (ThreadLifted.show t) (Chain.show c) - - end - - module NodeVarinfoMap = RichVarinfo.BiVarinfoMap.Make(ThreadNode) - let name () = "mallocWrapper" - - module D = Lattice.Prod (MallocCounter) (PL) - module C = D - - let wrappers = Hashtbl.create 13 - - (* transfer functions *) - let assign ctx (lval:lval) (rval:exp) : D.t = - ctx.local - - let branch ctx (exp:exp) (tv:bool) : D.t = - ctx.local - - let body ctx (f:fundec) : D.t = - ctx.local - - let return ctx (exp:exp option) (f:fundec) : D.t = - ctx.local - - let enter ctx (lval: lval option) (f:fundec) (args:exp list) : (D.t * D.t) list = - let counter, wrapper_node = ctx.local in - let new_wrapper_node = - if Hashtbl.mem wrappers f.svar.vname then - match wrapper_node with - | `Lifted _ -> wrapper_node (* if an interesting callee is called by an interesting caller, then we remember the caller context *) - | _ -> (`Lifted ctx.node) (* if an interesting callee is called by an uninteresting caller, then we remember the callee context *) - else - PL.top () (* if an uninteresting callee is called, then we forget what was called before *) - in - let callee = (counter, new_wrapper_node) in - [(ctx.local, callee)] - - let combine ctx (lval:lval option) fexp (f:fundec) (args:exp list) fc ((counter, _):D.t) : D.t = - (* Keep (potentially higher) counter from callee and keep wrapper node from caller *) - let _, lnode = ctx.local in - (counter, lnode) - - let special (ctx: (D.t, G.t, C.t, V.t) ctx) (lval: lval option) (f:varinfo) (arglist:exp list) : D.t = - let desc = LibraryFunctions.find f in - match desc.special arglist with - | Malloc _ | Calloc _ | Realloc _ -> - let counter, wrapper_node = ctx.local in - (MallocCounter.add_malloc counter ctx.node, wrapper_node) - | _ -> ctx.local - - let startstate v = D.bot () - - let threadenter ctx lval f args = - (* The new thread receives a fresh counter *) - [D.bot ()] - - let threadspawn ctx lval f args fctx = ctx.local - let exitstate v = D.top () - - type marshal = NodeVarinfoMap.marshal - - let get_heap_var = NodeVarinfoMap.to_varinfo - - - let query (ctx: (D.t, G.t, C.t, V.t) ctx) (type a) (q: a Q.t): a Q.result = - let counter, wrapper_node = ctx.local in - match q with - | Q.HeapVar -> - let node = match wrapper_node with - | `Lifted wrapper_node -> wrapper_node - | _ -> ctx.node - in - let count = MallocCounter.find (`Lifted node) counter in - let var = get_heap_var (ctx.ask Q.CurrentThreadId, node, count) in - var.vdecl <- UpdateCil.getLoc node; (* TODO: does this do anything bad for incremental? *) - `Lifted var - | Q.IsHeapVar v -> - NodeVarinfoMap.mem_varinfo v - | Q.IsMultiple v -> - begin match NodeVarinfoMap.from_varinfo v with - | Some (_, _, c) -> Chain.is_top c || not (ctx.ask Q.MustBeUniqueThread) - | None -> false - end - | _ -> Queries.Result.top q - - let init marshal = - List.iter (fun wrapper -> Hashtbl.replace wrappers wrapper ()) (get_string_list "ana.malloc.wrappers"); - NodeVarinfoMap.unmarshal marshal - - let finalize () = - NodeVarinfoMap.marshal () -end - -let _ = - MCP.register_analysis (module Spec) diff --git a/src/analyses/malloc_null.ml b/src/analyses/malloc_null.ml index 034d571a1b..f993db0c6e 100644 --- a/src/analyses/malloc_null.ml +++ b/src/analyses/malloc_null.ml @@ -1,10 +1,10 @@ -(** Path-sensitive analysis that verifies checking the result of the malloc function. *) +(** Path-sensitive analysis of failed dynamic memory allocations ([malloc_null]). *) module AD = ValueDomain.AD module IdxDom = ValueDomain.IndexDomain module Offs = ValueDomain.Offs -open Prelude.Ana +open GoblintCil open Analyses module Spec = @@ -14,51 +14,35 @@ struct module Addr = ValueDomain.Addr module D = ValueDomain.AddrSetDomain module C = ValueDomain.AddrSetDomain - - let should_join x y = D.equal x y - - (* NB! Currently we care only about concrete indexes. Base (seeing only a int domain - element) answers with the string "unknown" on all non-concrete cases. *) - let rec conv_offset x = - match x with - | `NoOffset -> `NoOffset - | `Index (Const (CInt (i,ik,s)),o) -> `Index (IntDomain.of_const (i,ik,s), conv_offset o) - | `Index (_,o) -> `Index (IdxDom.top (), conv_offset o) - | `Field (f,o) -> `Field (f, conv_offset o) + module P = IdentityP (D) (* Addr set functions: *) - let is_prefix_of (v1,ofs1: varinfo * (Addr.field,Addr.idx) Lval.offs) (v2,ofs2: varinfo * (Addr.field,Addr.idx) Lval.offs) : bool = - let rec is_offs_prefix_of pr os = - match (pr, os) with - | (`NoOffset, `NoOffset) -> true - | (`NoOffset, _) -> false - | (`Field (f1, o1), `Field (f2,o2)) -> f1 == f2 && is_offs_prefix_of o1 o2 - | (_, _) -> false - in - CilType.Varinfo.equal v1 v2 && is_offs_prefix_of ofs1 ofs2 + let is_prefix_of m1 m2 = Option.is_some (Addr.Mval.prefix m1 m2) (* We just had to dereference an lval --- warn if it was null *) - let warn_lval (st:D.t) (v :varinfo * (Addr.field,Addr.idx) Lval.offs) : unit = + let warn_lval (st:D.t) (v :Addr.Mval.t) : unit = try - if D.exists (fun x -> GobOption.exists (fun x -> is_prefix_of x v) (Addr.to_var_offset x)) st + if D.exists (fun x -> GobOption.exists (fun x -> is_prefix_of x v) (Addr.to_mval x)) st then - let var = Addr.from_var_offset v in + let var = Addr.of_mval v in Messages.warn ~category:Messages.Category.Behavior.Undefined.nullpointer_dereference "Possible dereferencing of null on variable '%a'." Addr.pretty var with SetDomain.Unsupported _ -> () (* Warn null-lval dereferences, but not normal (null-) lvals*) let rec warn_deref_exp (a: Queries.ask) (st:D.t) (e:exp): unit = let warn_lval_mem e offs = - (* begin try List.iter (warn_lval st) (AD.to_var_offset (BS.eval_lv gl s (Mem e, offs))) + (* begin try List.iter (warn_lval st) (AD.to_mval (BS.eval_lv gl s (Mem e, offs))) with SetDomain.Unsupported _ -> () end;*) 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, conv_offset 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,54 +76,51 @@ struct warn_deref_exp a st t; warn_deref_exp a st f - let may (f: 'a -> 'b) (x: 'a option) : unit = - match x with - | Some x -> f x - | None -> () - (* Generate addresses to all points in an given varinfo. (Depends on type) *) let to_addrs (v:varinfo) : Addr.t list = let make_offs = List.fold_left (fun o f -> `Field (f, o)) `NoOffset in - let rec add_fields (base: Addr.field list) fs acc = + let rec add_fields (base: fieldinfo list) fs acc = match fs with | [] -> acc | f :: fs -> match unrollType f.ftype with | TComp ({cfields=ffs; _},_) -> add_fields base fs (List.rev_append (add_fields (f::base) ffs []) acc) - | _ -> add_fields base fs ((Addr.from_var_offset (v,make_offs (f::base))) :: acc) + | _ -> add_fields base fs ((Addr.of_mval (v,make_offs (f::base))) :: acc) in match unrollType v.vtype with | TComp ({cfields=fs; _},_) -> add_fields [] fs [] - | _ -> [Addr.from_var v] + | _ -> [Addr.of_var v] (* 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.from_var_offset (v,(conv_offset 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, conv_offset 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) = @@ -150,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, conv_offset o) x) (Addr.to_var_offset 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 (* @@ -166,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.from_var_offset (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 = @@ -196,34 +179,34 @@ struct let enter ctx (lval: lval option) (f:fundec) (args:exp list) : (D.t * D.t) list = let nst = remove_unreachable (Analyses.ask_of_ctx ctx) args ctx.local in - may (fun x -> warn_deref_exp (Analyses.ask_of_ctx ctx) ctx.local (Lval x)) lval; + Option.iter (fun x -> warn_deref_exp (Analyses.ask_of_ctx ctx) ctx.local (Lval x)) lval; List.iter (warn_deref_exp (Analyses.ask_of_ctx ctx) ctx.local) args; [ctx.local,nst] - let combine ctx (lval:lval option) fexp (f:fundec) (args:exp list) fc (au:D.t) : D.t = + let combine_env ctx lval fexp f args fc au f_ask = let cal_st = remove_unreachable (Analyses.ask_of_ctx ctx) args ctx.local in - let ret_st = D.union au (D.diff ctx.local cal_st) in - let new_u = - match lval, D.mem (return_addr ()) ret_st with - | Some lv, true -> - begin match get_concrete_lval (Analyses.ask_of_ctx ctx) lv with - | Some (Var v,ofs) -> D.remove (return_addr ()) (D.add (Addr.from_var_offset (v,ofs)) ret_st) - | _ -> ret_st end - | _ -> ret_st - in - new_u + D.union (D.remove (return_addr ()) au) (D.diff ctx.local cal_st) + + let combine_assign ctx (lval:lval option) fexp (f:fundec) (args:exp list) fc (au:D.t) (f_ask: Queries.ask) : D.t = + match lval, D.mem (return_addr ()) au with + | Some lv, true -> + begin match get_concrete_lval (Analyses.ask_of_ctx ctx) lv with + | Some mval -> D.add (Addr.of_mval mval) ctx.local + | _ -> ctx.local + end + | _ -> ctx.local let special ctx (lval: lval option) (f:varinfo) (arglist:exp list) : D.t = - may (fun x -> warn_deref_exp (Analyses.ask_of_ctx ctx) ctx.local (Lval x)) lval; + Option.iter (fun x -> warn_deref_exp (Analyses.ask_of_ctx ctx) ctx.local (Lval x)) lval; List.iter (warn_deref_exp (Analyses.ask_of_ctx ctx) ctx.local) arglist; let desc = LibraryFunctions.find f in match desc.special arglist, lval with | 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.from_var_offset (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 @@ -232,12 +215,12 @@ 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 = - return_addr_ := Addr.from_var (Goblintutil.create_var @@ makeVarinfo false "RETURN" voidType) + return_addr_ := Addr.of_var (Cilfacade.create_var @@ makeVarinfo false "RETURN" voidType) end let _ = diff --git a/src/analyses/mayLocks.ml b/src/analyses/mayLocks.ml index 182b93ff3e..853005de87 100644 --- a/src/analyses/mayLocks.ml +++ b/src/analyses/mayLocks.ml @@ -1,18 +1,43 @@ -(** May lockset analysis (unused). *) +(** May lockset analysis and analysis of double locking ([maylocks]). *) open Analyses +open GoblintCil +module LF = LibraryFunctions -module Arg = +module Arg:LocksetAnalysis.MayArg = struct - module D = LockDomain.MayLockset + module D = LockDomain.MayLocksetNoRW module G = DefaultSpec.G module V = DefaultSpec.V - let add ctx l = - D.add l ctx.local + let add ctx (l,r) = + if D.mem l ctx.local then + let default () = + M.warn ~category:M.Category.Behavior.Undefined.double_locking "Acquiring a (possibly non-recursive) mutex that may be already held"; + ctx.local + in + match D.Addr.to_mval l with + | Some (v,o) -> + (let mtype = ctx.ask (Queries.MutexType (v, Offset.Unit.of_offs o)) in + match mtype with + | `Lifted MutexAttrDomain.MutexKind.Recursive -> ctx.local + | `Lifted MutexAttrDomain.MutexKind.NonRec -> + M.warn ~category:M.Category.Behavior.Undefined.double_locking "Acquiring a non-recursive mutex that may be already held"; + ctx.local + | _ -> default ()) + | _ -> default () + else + D.add l ctx.local let remove ctx l = - D.remove (l, true) (D.remove (l, false) ctx.local) + if not (D.mem l ctx.local) then M.warn "Releasing a mutex that is definitely not held"; + match D.Addr.to_mval l with + | Some (v,o) -> + (let mtype = ctx.ask (Queries.MutexType (v, Offset.Unit.of_offs o)) in + match mtype with + | `Lifted MutexAttrDomain.MutexKind.NonRec -> D.remove l ctx.local + | _ -> ctx.local (* we cannot remove them here *)) + | None -> ctx.local (* we cannot remove them here *) end module Spec = @@ -21,6 +46,17 @@ struct let name () = "maylocks" let exitstate v = D.top () (* TODO: why? *) + + let return ctx exp fundec = + if not (D.is_bot ctx.local) && ThreadReturn.is_current (Analyses.ask_of_ctx ctx) then M.warn "Exiting thread while still holding a mutex!"; + ctx.local + + let special ctx (lv:lval option) (f: varinfo) (args: exp list) = + (match(LF.find f).special args with + | ThreadExit _ -> if not @@ D.is_bot ctx.local then M.warn "Exiting thread while still holding a mutex!" + | _ -> ()) + ; + ctx.local end let _ = diff --git a/src/analyses/memLeak.ml b/src/analyses/memLeak.ml new file mode 100644 index 0000000000..62b6bbe3a7 --- /dev/null +++ b/src/analyses/memLeak.ml @@ -0,0 +1,99 @@ +(** 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 Spec : Analyses.MCPSpec = +struct + include Analyses.IdentitySpec + + let name () = "memLeak" + + module D = ToppedVarInfoSet + module C = D + module P = IdentityP (D) + + let context _ d = d + + (* HELPER FUNCTIONS *) + let warn_for_multi_threaded ctx = + if not (ctx.ask (Queries.MustBeSingleThreaded { since_start = true })) then ( + set_mem_safety_flag InvalidMemTrack; + set_mem_safety_flag InvalidMemcleanup; + M.warn ~category:(Behavior (Undefined MemoryLeak)) ~tags:[CWE 401] "Program isn't running in single-threaded mode. A memory leak might occur due to multi-threading" + ) + + let check_for_mem_leak ?(assert_exp_imprecise = false) ?(exp = None) ctx = + let state = ctx.local in + if not @@ D.is_empty state then + match assert_exp_imprecise, exp with + | true, Some exp -> + set_mem_safety_flag InvalidMemTrack; + 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 state + | _ -> + set_mem_safety_flag InvalidMemTrack; + set_mem_safety_flag InvalidMemcleanup; + M.warn ~category:(Behavior (Undefined MemoryLeak)) ~tags:[CWE 401] "Memory leak detected for heap variables: %a" D.pretty state + + (* TRANSFER FUNCTIONS *) + let return ctx (exp:exp option) (f:fundec) : D.t = + (* 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; + 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 _ -> + (* Warn about multi-threaded programs as soon as we encounter a dynamic memory allocation function *) + warn_for_multi_threaded ctx; + begin match ctx.ask (Queries.AllocVar {on_stack = false}) with + | `Lifted var -> D.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) -> D.remove v state (* Unique pointed to heap vars *) + | _ -> state + end + | _ -> state + end + | Abort -> + (* An "Abort" special function indicates program exit => need to check for memory leaks *) + check_for_mem_leak ctx; + state + | Assert { exp; _ } -> + let warn_for_assert_exp = + 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 b -> + (* If we know for sure that the expression in "assert" is false => need to check for memory leaks *) + if b = false then + check_for_mem_leak ctx + else () + | None -> check_for_mem_leak ctx ~assert_exp_imprecise:true ~exp:(Some exp) + end + in + warn_for_assert_exp; + state + | _ -> state + + let startstate v = D.bot () + let exitstate v = D.top () +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..fc60352298 --- /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_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, o) -> ctx.ask (Queries.IsHeapVar v) + | _ -> false + ) a + | _ -> false + + let get_size_of_ptr_target ctx ptr = + 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, _) -> + 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/modifiedSinceLongjmp.ml new file mode 100644 index 0000000000..a129c9f92c --- /dev/null +++ b/src/analyses/modifiedSinceLongjmp.ml @@ -0,0 +1,77 @@ +(** Analysis of variables modified since [setjmp] ([modifiedSinceLongjmp]). *) + +(* TODO: this name is wrong *) + +open GoblintCil +open Analyses + +module Spec = +struct + include Analyses.IdentitySpec + + let name () = "modifiedSinceLongjmp" + module D = JmpBufDomain.LocallyModifiedMap + module VS = D.VarSet + module C = Lattice.Unit + + let context _ _ = () + + let add_to_all_defined vs d = + D.map (fun vs' -> VS.union vs vs') d + + let is_relevant v = + (* 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_ad ls = + (* TODO: what about AD with both known and unknown pointers? *) + if Queries.AD.is_top ls then + VS.top () + else + 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_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 = + ctx.local + + let special ctx (lval: lval option) (f:varinfo) (arglist:exp list) : D.t = + let desc = LibraryFunctions.find f in + match desc.special arglist with + | Setjmp _ -> + let entry = (ctx.prev_node, ctx.control_context ()) in + let v = D.find entry ctx.local in (* Will make bot binding explicit here *) + (* LHS of setjmp not marked as tainted on purpose *) + D.add entry v ctx.local + | _ -> + ctx.local + + let startstate v = 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 = + match q with + | Queries.MayBeModifiedSinceSetjmp entry -> D.find entry ctx.local + | _ -> Queries.Result.top q + + let event ctx (e: Events.t) octx = + match e with + | Access {ad; kind = Write; _} -> + add_to_all_defined (relevants_from_ad ad) ctx.local + | _ -> + ctx.local +end + +let _ = + MCP.register_analysis (module Spec : MCPSpec) diff --git a/src/analyses/mutexAnalysis.ml b/src/analyses/mutexAnalysis.ml index 83590ac16a..ee050f55ca 100644 --- a/src/analyses/mutexAnalysis.ml +++ b/src/analyses/mutexAnalysis.ml @@ -1,13 +1,13 @@ -(** Protecting mutex analysis. Must locksets locally and for globals. *) +(** Must lockset and protecting lockset analysis ([mutex]). *) module M = Messages module Addr = ValueDomain.Addr module Lockset = LockDomain.Lockset module Mutexes = LockDomain.Mutexes module LF = LibraryFunctions -open Prelude.Ana +open GoblintCil open Analyses - +open Batteries module VarSet = SetDomain.Make (Basetype.Variables) @@ -15,17 +15,52 @@ module Spec = struct module Arg = struct - module D = Lockset + module Multiplicity = struct + (* the maximum multiplicity which we keep track of precisely *) + let max_count () = 4 + + module Count = Lattice.Reverse ( + Lattice.Chain ( + struct + let n () = max_count () + 1 + let names x = if x = max_count () then Format.asprintf ">= %d" x else Format.asprintf "%d" x + end + ) + ) + + include MapDomain.MapTop_LiftBot (ValueDomain.Addr) (Count) + + let name () = "multiplicity" + + let increment v x = + let current = find v x in + if current = max_count () then + x + else + add v (current + 1) x + + let decrement v x = + let current = find v x in + if current = 0 then + (x, true) + else + (add v (current - 1) x, current - 1 = 0) + end + + module D = struct include Lattice.Prod(Lockset)(Multiplicity) + let empty () = (Lockset.empty (), Multiplicity.empty ()) + end + (** Global data is collected using dirty side-effecting. *) (* Two global invariants: 1. varinfo -> set of mutexes -- used for protecting locksets (M[g]) - 2. mutex -> set of varinfos -- used for protected variables (G_m), only collected during postsolving *) + 2. mutex -> set of varinfos -- used for protected variables (G_m), only collected during postsolving (!) *) module V = struct - include Printable.Either (CilType.Varinfo) (ValueDomain.Addr) + include Printable.Either (struct include CilType.Varinfo let name () = "protecting" end) (struct include ValueDomain.Addr let name () = "protected" end) let name () = "mutex" let protecting x = `Left x let protected x = `Right x @@ -34,23 +69,67 @@ struct | `Right _ -> true end - module MakeG (G0: Lattice.S) = - struct + module MakeP (G0: Lattice.S) = struct module ReadWrite = struct include G0 let name () = "readwrite" end + module Write = struct include G0 let name () = "write" end - include Lattice.Prod (ReadWrite) (Write) + + module P = Lattice.Prod (ReadWrite) (Write) + include Lattice.Prod (P) (P) + + let name () = "strong protection * weak protection" + + let get ~write protection (s,w) = + let (rw, w) = match protection with + | Queries.Protection.Strong -> s + | Weak -> w + in + if write then w else rw + end + + (** Collects information about which variables are protected by which mutexes *) + module GProtecting: sig + include Lattice.S + val make: write:bool -> recovered:bool -> Mutexes.t -> t + val get: write:bool -> Queries.Protection.t -> t -> Mutexes.t + end = struct + include MakeP (LockDomain.Simple) + + let make ~write ~recovered locks = + (* If the access is not a write, set to T so intersection with current write-protecting is identity *) + let wlocks = if write then locks else Mutexes.top () in + if recovered then + (* If we are in single-threaded mode again, this does not need to be added to set of mutexes protecting in mt-mode only *) + ((locks, wlocks), (Mutexes.top (), Mutexes.top ())) + else + ((locks, wlocks), (locks, wlocks)) + end + + + (** Collects information about which mutex protects which variable *) + module GProtected: sig + include Lattice.S + val make: write:bool -> VarSet.t -> t + val get: write:bool -> Queries.Protection.t -> t -> VarSet.t + end = struct + include MakeP (VarSet) + + let make ~write vs = + let vs_empty = VarSet.empty () in + if write then + ((vs_empty, vs), (vs_empty, vs)) + else + ((vs, vs_empty), (vs, vs_empty)) end - module GProtecting = MakeG (LockDomain.Simple) - module GProtected = MakeG (VarSet) module G = struct include Lattice.Lift2 (GProtecting) (GProtected) (Printable.DefaultNames) @@ -67,24 +146,44 @@ struct let create_protected protected = `Lifted2 protected end - let add ctx l = - D.add l ctx.local + let add ctx (l:Mutexes.elt*bool) = + let s,m = ctx.local in + let s' = Lockset.add l s in + match Addr.to_mval (fst l) with + | Some mval when MutexTypeAnalysis.must_be_recursive ctx mval -> + (s', Multiplicity.increment (fst l) m) + | _ -> (s', m) - let remove ctx l = - D.remove (l, true) (D.remove (l, false) ctx.local) + let remove' ctx ~warn l = + let s, m = ctx.local in + let rm s = Lockset.remove (l, true) (Lockset.remove (l, false) s) in + if warn && (not (Lockset.mem (l,true) s || Lockset.mem (l,false) s)) then M.warn "unlocking mutex (%a) which may not be held" Addr.pretty l; + match Addr.to_mval l with + | Some mval when MutexTypeAnalysis.must_be_recursive ctx mval -> + let m',rmed = Multiplicity.decrement l m in + if rmed then + (rm s, m') + else + (s, m') + | _ -> (rm s, m) + + let remove = remove' ~warn:true let remove_all ctx = (* Mutexes.iter (fun m -> ctx.emit (MustUnlock m) ) (D.export_locks ctx.local); *) (* TODO: used to have remove_nonspecial, which kept v.vname.[0] = '{' variables *) - D.empty () + M.warn "unlocking unknown mutex which may not be held"; + (Lockset.empty (), Multiplicity.empty ()) + + let empty () = (Lockset.empty (), Multiplicity.empty ()) end include LocksetAnalysis.MakeMust (Arg) let name () = "mutex" module D = Arg.D (* help type checker using explicit constraint *) - let should_join x y = D.equal x y + module P = IdentityP (D) module V = Arg.V module GProtecting = Arg.GProtecting @@ -102,68 +201,49 @@ struct num_mutexes := 0; sum_protected := 0 - let rec conv_offset_inv = function - | `NoOffset -> `NoOffset - | `Field (f, o) -> `Field (f, conv_offset_inv o) - | `Index (i, o) -> - let i_exp = - match ValueDomain.IndexDomain.to_int i with - | Some i -> Const (CInt (i, Cilfacade.ptrdiff_ikind (), Some (Z.to_string i))) - | None -> MyCFG.unknown_exp - in - `Index (i_exp, conv_offset_inv o) - let query ctx (type a) (q: a Queries.t): a Queries.result = - let check_fun ~write ls = - let locks = Lockset.export_locks ls in - if write then (Mutexes.bot (), locks) else (locks, Mutexes.bot ()) - in - let non_overlapping locks1 locks2 = - let intersect = GProtecting.join locks1 locks2 in - GProtecting.is_top intersect - in + let ls, m = ctx.local in + (* get the set of mutexes protecting the variable v in the given mode *) + let protecting ~write mode v = GProtecting.get ~write mode (G.protecting (ctx.global (V.protecting v))) in + let non_overlapping locks1 locks2 = Mutexes.is_empty @@ Mutexes.inter locks1 locks2 in match q with - | Queries.MayBePublic _ when Lockset.is_bot ctx.local -> false - | Queries.MayBePublic {global=v; write} -> - let held_locks: GProtecting.t = check_fun ~write (Lockset.filter snd ctx.local) in + | Queries.MayBePublic _ when Lockset.is_bot ls -> false + | Queries.MayBePublic {global=v; write; protection} -> + let held_locks = Lockset.export_locks (Lockset.filter snd ls) in + let protecting = protecting ~write protection v in (* TODO: unsound in 29/24, why did we do this before? *) (* if Mutexes.mem verifier_atomic (Lockset.export_locks ctx.local) then false else *) - non_overlapping held_locks (G.protecting (ctx.global (V.protecting v))) - | Queries.MayBePublicWithout _ when Lockset.is_bot ctx.local -> false - | Queries.MayBePublicWithout {global=v; write; without_mutex} -> - let held_locks: GProtecting.t = check_fun ~write (Lockset.remove (without_mutex, true) (Lockset.filter snd ctx.local)) in + non_overlapping held_locks protecting + | Queries.MayBePublicWithout _ when Lockset.is_bot ls -> false + | Queries.MayBePublicWithout {global=v; write; without_mutex; protection} -> + let held_locks = Lockset.export_locks @@ fst @@ Arg.remove' ctx ~warn:false without_mutex in + let protecting = protecting ~write protection v in (* TODO: unsound in 29/24, why did we do this before? *) (* if Mutexes.mem verifier_atomic (Lockset.export_locks (Lockset.remove (without_mutex, true) ctx.local)) then false else *) - non_overlapping held_locks (G.protecting (ctx.global (V.protecting v))) - | Queries.MustBeProtectedBy {mutex; global; write} -> - let mutex_lockset = Lockset.singleton (mutex, true) in - let held_locks: GProtecting.t = check_fun ~write mutex_lockset in + non_overlapping held_locks protecting + | Queries.MustBeProtectedBy {mutex; global=v; write; protection} -> + let mutex_lockset = Lockset.export_locks @@ Lockset.singleton (mutex, true) in + let protecting = protecting ~write protection v in (* TODO: unsound in 29/24, why did we do this before? *) (* if LockDomain.Addr.equal mutex verifier_atomic then true else *) - GProtecting.leq (G.protecting (ctx.global (V.protecting global))) held_locks + Mutexes.leq mutex_lockset protecting | Queries.MustLockset -> - let held_locks = Lockset.export_locks (Lockset.filter snd ctx.local) in - let ls = Mutexes.fold (fun addr ls -> - match Addr.to_var_offset addr with - | Some (var, offs) -> Queries.LS.add (var, conv_offset_inv offs) ls - | None -> ls - ) held_locks (Queries.LS.empty ()) - in - ls + let held_locks = Lockset.export_locks (Lockset.filter snd ls) in + Mutexes.fold (fun addr ls -> Queries.AD.add addr ls) held_locks (Queries.AD.empty ()) | Queries.MustBeAtomic -> - let held_locks = Lockset.export_locks (Lockset.filter snd ctx.local) in - Mutexes.mem (LockDomain.Addr.from_var LF.verifier_atomic_var) held_locks + 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 = (if write then snd else fst) (G.protected (ctx.global (V.protected m))) in + 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 -> @@ -171,13 +251,13 @@ struct begin match g with | `Left g' -> (* protecting *) if GobConfig.get_bool "dbg.print_protection" then ( - let (protecting, _) = G.protecting (ctx.global g) in (* readwrite protecting *) + let protecting = GProtecting.get ~write:false Strong (G.protecting (ctx.global g)) in (* readwrite protecting *) let s = Mutexes.cardinal protecting in M.info_noloc ~category:Race "Variable %a read-write protected by %d mutex(es): %a" CilType.Varinfo.pretty g' s Mutexes.pretty protecting ) | `Right m -> (* protected *) if GobConfig.get_bool "dbg.print_protection" then ( - let (protected, _) = G.protected (ctx.global g) in (* readwrite protected *) + let protected = GProtected.get ~write:false Strong (G.protected (ctx.global g)) in (* readwrite protected *) let s = VarSet.cardinal protected in max_protected := max !max_protected s; sum_protected := !sum_protected + s; @@ -189,77 +269,73 @@ struct module A = struct - include D + include Lockset let name () = "lock" let may_race ls1 ls2 = (* not mutually exclusive *) - not @@ D.exists (fun ((m1, w1) as l1) -> + not @@ exists (fun ((m1, w1) as l1) -> if w1 then (* write lock is exclusive with write lock or read lock *) - D.mem l1 ls2 || D.mem (m1, false) ls2 + mem l1 ls2 || mem (m1, false) ls2 else (* read lock is exclusive with just write lock *) - D.mem (m1, true) ls2 + mem (m1, true) ls2 ) ls1 let should_print ls = not (is_empty ls) end let access ctx (a: Queries.access) = - ctx.local + fst ctx.local let event ctx e octx = match e with - | Events.Access {exp; lvals; kind; _} when ThreadFlag.is_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 | Some v -> - if not (Lockset.is_bot octx.local) then - let locks = Lockset.export_locks (Lockset.filter snd octx.local) in + if not (Lockset.is_bot (fst octx.local)) then + let locks = Lockset.export_locks (Lockset.filter snd (fst octx.local)) in let write = match kind with | Write | Free -> true | Read -> false + | Call | Spawn -> false (* TODO: nonsense? *) in - let el = (locks, if write then locks else Mutexes.top ()) in - ctx.sideg (V.protecting v) (G.create_protecting el); - - if !GU.postsolving then ( - let held_locks = (if write then snd else fst) (G.protecting (ctx.global (V.protecting v))) in - let vs_empty = VarSet.empty () in - Mutexes.iter (fun addr -> - let vs = VarSet.singleton v in - let protected = - if write then - (vs_empty, vs) - else - (vs, vs_empty) - in - ctx.sideg (V.protected addr) (G.create_protected protected) - ) held_locks + let s = GProtecting.make ~write ~recovered:is_recovered_to_st locks in + ctx.sideg (V.protecting v) (G.create_protecting s); + + if !AnalysisState.postsolving then ( + let protecting mode = GProtecting.get ~write mode (G.protecting (ctx.global (V.protecting v))) in + let held_strong = protecting Strong in + let held_weak = protecting Weak in + let vs = VarSet.singleton v in + let protected = G.create_protected @@ GProtected.make ~write vs in + Mutexes.iter (fun addr -> ctx.sideg (V.protected addr) protected) held_strong; + (* If the mutex set here is top, it is actually not accessed *) + if is_recovered_to_st && not @@ Mutexes.is_top held_weak then + Mutexes.iter (fun addr -> ctx.sideg (V.protected addr) protected) held_weak; ) | 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 = Lval.CilLval.to_ciloffs 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 @@ -271,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 8f4cd67268..162527b32b 100644 --- a/src/analyses/mutexEventsAnalysis.ml +++ b/src/analyses/mutexEventsAnalysis.ml @@ -1,11 +1,14 @@ -(** Mutex events analysis (Lock and Unlock). *) +(** Mutex locking and unlocking analysis ([mutexEvents]). + + Emits {!Events.Lock} and {!Events.Unlock} to other analyses. *) module M = Messages module Addr = ValueDomain.Addr module Lockset = LockDomain.Lockset module Mutexes = LockDomain.Mutexes module LF = LibraryFunctions -open Prelude.Ana +open Batteries +open GoblintCil open Analyses open GobConfig @@ -15,32 +18,12 @@ struct include UnitAnalysis.Spec let name () = "mutexEvents" + let eval_exp_addr (a: Queries.ask) exp = a.f (Queries.MayPointTo exp) - (* Currently we care only about concrete indexes. *) - let rec conv_offset x = - match x with - | `NoOffset -> `NoOffset - | `Index (Const (CInt (i,_,s)),o) -> `Index (IntDomain.of_const (i,Cilfacade.ptrdiff_ikind (),s), conv_offset o) - | `Index (_,o) -> `Index (ValueDomain.IndexDomain.top (), conv_offset o) - | `Field (f,o) -> `Field (f, conv_offset o) - - let eval_exp_addr (a: Queries.ask) exp = - let gather_addr (v,o) b = ValueDomain.Addr.from_var_offset (v,conv_offset 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 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 @@ -48,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 ( @@ -62,17 +45,17 @@ struct let return ctx exp fundec : D.t = (* deprecated but still valid SV-COMP convention for atomic block *) if get_bool "ana.sv-comp.functions" && String.starts_with fundec.svar.vname "__VERIFIER_atomic_" then - ctx.emit (Events.Unlock (LockDomain.Addr.from_var LF.verifier_atomic_var)) + ctx.emit (Events.Unlock (LockDomain.Addr.of_var LF.verifier_atomic_var)) let body ctx f : D.t = (* deprecated but still valid SV-COMP convention for atomic block *) if get_bool "ana.sv-comp.functions" && String.starts_with f.svar.vname "__VERIFIER_atomic_" then - ctx.emit (Events.Lock (LockDomain.Addr.from_var LF.verifier_atomic_var, true)) + ctx.emit (Events.Lock (LockDomain.Addr.of_var LF.verifier_atomic_var, true)) 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 @@ -88,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 new file mode 100644 index 0000000000..e640a261cd --- /dev/null +++ b/src/analyses/mutexTypeAnalysis.ml @@ -0,0 +1,82 @@ +(** An analysis tracking the type of a mutex ([pthreadMutexType]). *) + +open GoblintCil +open Analyses + +module MAttr = ValueDomain.MutexAttr +module LF = LibraryFunctions + +module Spec : Analyses.MCPSpec with module D = Lattice.Unit and module C = Lattice.Unit = +struct + include Analyses.IdentitySpec + + let name () = "pthreadMutexType" + module D = Lattice.Unit + module C = Lattice.Unit + + (* Removing indexes here avoids complicated lookups and allows to have the LVals as vars here, at the price that different types of mutexes in arrays are not dinstinguished *) + module O = Offset.Unit + + module V = struct + include Printable.Prod(CilType.Varinfo)(O) (* TODO: use Mval.Unit *) + let is_write_only _ = false + end + + module G = MAttr + + (* transfer functions *) + let assign ctx (lval:lval) (rval:exp) : D.t = + match lval with + | (Var v, o) -> + (* There's no way to use the PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP etc for accesses via pointers *) + let rec helper o t = function + | Field ({fname = "__data"; _}, Field ({fname = "__kind"; _}, NoOffset)) when ValueDomain.Compound.is_mutex_type t -> + let kind = + (match Cil.constFold true rval with + | Const (CInt (c, _, _)) -> MAttr.of_int c + | _ -> `Top) + in + ctx.sideg (v,o) kind; + ctx.local + | Index (i,o') -> + let o'' = O.of_offs (`Index (i, `NoOffset)) in + helper (O.add_offset o o'') (Cilfacade.typeOffset t (Index (i,NoOffset))) o' + | Field (f,o') -> + let o'' = O.of_offs (`Field (f, `NoOffset)) in + helper (O.add_offset o o'') (Cilfacade.typeOffset t (Field (f,NoOffset))) o' + | NoOffset -> ctx.local + in + helper `NoOffset v.vtype o + | _ -> ctx.local + + let special ctx (lval: lval option) (f:varinfo) (arglist:exp list) : D.t = + let desc = LF.find f in + match desc.special arglist with + | MutexInit {mutex = mutex; attr = attr} -> + 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.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 ~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 = + match q with + | Queries.MutexType (v,o) -> (ctx.global (v,o):MutexAttrDomain.t) + | _ -> Queries.Result.top q +end + +let must_be_recursive ctx (v,o) = + ctx.ask (Queries.MutexType (v, Offset.Unit.of_offs o)) = `Lifted MutexAttrDomain.MutexKind.Recursive + +let _ = + MCP.register_analysis (module Spec : MCPSpec) diff --git a/src/analyses/poisonVariables.ml b/src/analyses/poisonVariables.ml new file mode 100644 index 0000000000..865cb928aa --- /dev/null +++ b/src/analyses/poisonVariables.ml @@ -0,0 +1,114 @@ +(** Taint analysis of variables that were modified between [setjmp] and [longjmp] and not yet overwritten. ([poisonVariables]). *) + +open Batteries +open GoblintCil +open Analyses + +module Spec = +struct + include Analyses.IdentitySpec + module VS = SetDomain.ToppedSet(CilType.Varinfo) (struct let topname = "All vars" end) + + let name () = "poisonVariables" + module D = VS + module C = Lattice.Unit + + let context _ _ = () + + 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_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 *) + + + (* transfer functions *) + let return ctx (exp:exp option) (f:fundec) : D.t = + (* remove locals, except ones which need to be weakly updated*) + if D.is_top ctx.local then + ctx.local + 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) + ) ctx.local + ) + + 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 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 = + 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] + ) + + let combine_env ctx lval fexp f args fc au f_ask = + VS.join au ctx.local + + let startstate v = D.bot () + let threadenter ctx ~multiple lval f args = [D.bot ()] + let exitstate v = D.top () + + let event ctx e octx = + match e with + | Events.Longjmped {lval} -> + let modified_locals = ctx.ask (MayBeModifiedSinceSetjmp (ctx.prev_node, ctx.control_context ())) in + let modified_locals = match lval with + | Some (Var v, NoOffset) -> Queries.VS.remove v modified_locals + | _ -> modified_locals (* Does usually not really occur, if it does, this is sound *) + in + let (_, longjmp_nodes) = ctx.ask ActiveJumpBuf in + JmpBufDomain.NodeSet.iter (fun longjmp_node -> + if Queries.VS.is_top modified_locals then + M.info ~category:(Behavior (Undefined Other)) ~loc:(Node longjmp_node) "Since setjmp at %a, potentially all locals were modified! Reading them will yield Undefined Behavior." Node.pretty ctx.prev_node + else if not (Queries.VS.is_empty modified_locals) then + M.info ~category:(Behavior (Undefined Other)) ~loc:(Node longjmp_node) "Since setjmp at %a, locals %a were modified! Reading them will yield Undefined Behavior." Node.pretty ctx.prev_node Queries.VS.pretty modified_locals + else + () + ) longjmp_nodes; + D.join modified_locals ctx.local + | 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!" + | ad -> + (* Use original access state instead of current with removed written vars. *) + Queries.AD.iter (check_mval octx.local) ad + end; + 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 + +let _ = + MCP.register_analysis (module Spec : MCPSpec) diff --git a/src/analyses/pthreadSignals.ml b/src/analyses/pthreadSignals.ml index 602a8917fd..70f1624922 100644 --- a/src/analyses/pthreadSignals.ml +++ b/src/analyses/pthreadSignals.ml @@ -1,6 +1,6 @@ -(** Analysis of must-received pthread_signals. *) +(** Must received signals analysis for Pthread condition variables ([pthreadSignals]). *) -open Prelude.Ana +open GoblintCil open Analyses module LF = LibraryFunctions @@ -9,7 +9,7 @@ struct module Signals = SetDomain.ToppedSet (ValueDomain.Addr) (struct let topname = "All signals" end) module MustSignals = Lattice.Reverse (Signals) - include Analyses.DefaultSpec + include Analyses.IdentitySpec module V = VarinfoV let name () = "pthreadSignals" @@ -17,41 +17,10 @@ struct module C = MustSignals module G = SetDomain.ToppedSet (MHP) (struct let topname = "All Threads" end) - let rec conv_offset x = - match x with - | `NoOffset -> `NoOffset - | `Index (Const (CInt (i,_,s)),o) -> `Index (IntDomain.of_const (i,Cilfacade.ptrdiff_ikind (),s), conv_offset o) - | `Index (_,o) -> `Index (ValueDomain.IndexDomain.top (), conv_offset o) - | `Field (f,o) -> `Field (f, conv_offset o) - - let eval_exp_addr (a: Queries.ask) exp = - let gather_addr (v,o) b = ValueDomain.Addr.from_var_offset (v,conv_offset 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 *) - let assign ctx (lval:lval) (rval:exp) : D.t = - ctx.local - - let branch ctx (exp:exp) (tv:bool) : D.t = - ctx.local - - let body ctx (f:fundec) : D.t = - ctx.local - - let return ctx (exp:exp option) (f:fundec) : D.t = - ctx.local - - let enter ctx (lval: lval option) (f:fundec) (args:exp list) : (D.t * D.t) list = - [ctx.local, ctx.local] - - let combine ctx (lval:lval option) fexp (f:fundec) (args:exp list) fc (au:D.t) : D.t = - au let special ctx (lval: lval option) (f:varinfo) (arglist:exp list) : D.t = let desc = LF.find f in @@ -88,7 +57,7 @@ struct end in let open Signalled in - let add_if_singleton conds = match conds with | [a] -> Signals.add (ValueDomain.Addr.from_var a) ctx.local | _ -> ctx.local in + let add_if_singleton conds = match conds with | [a] -> Signals.add (ValueDomain.Addr.of_var a) ctx.local | _ -> ctx.local in let conds = possible_vinfos (Analyses.ask_of_ctx ctx) cond in (match List.fold_left (fun acc cond -> can_be_signalled cond ||| acc) Never conds with | PossiblySignalled -> add_if_singleton conds @@ -104,8 +73,7 @@ struct | _ -> ctx.local let startstate v = Signals.empty () - 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 exitstate v = Signals.empty () end diff --git a/src/analyses/raceAnalysis.ml b/src/analyses/raceAnalysis.ml index bfff9ce4e3..9c2272fabb 100644 --- a/src/analyses/raceAnalysis.ml +++ b/src/analyses/raceAnalysis.ml @@ -1,8 +1,130 @@ -(** Data race analysis. *) +(** Data race analysis ([race]). *) -open Prelude.Ana +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,30 +134,74 @@ struct let name () = "race" (* Two global invariants: - 1. (lval, type) -> accesses -- used for warnings - 2. varinfo -> set of (lval, type) -- used for IterSysVars Global *) + 1. memoroot -> (offset --trie--> accesses) -- used for warnings + 2. varinfo -> set of memo -- used for IterSysVars Global *) - module V0 = Printable.Prod (Access.LVOpt) (Access.T) module V = struct - include Printable.Either (V0) (CilType.Varinfo) + include Printable.Either (Access.MemoRoot) (CilType.Varinfo) let name () = "race" let access x = `Left x let vars x = `Right x let is_write_only _ = true end - module V0Set = SetDomain.Make (V0) + module MemoSet = SetDomain.Make (Access.Memo) + + module OneOffset = + struct + include Printable.StdLeaf + type t = + | Field of CilType.Fieldinfo.t + | Index + [@@deriving eq, ord, hash, to_yojson] + + let name () = "oneoffset" + + let show = function + | Field f -> CilType.Fieldinfo.show f + | Index -> "?" + + include Printable.SimpleShow (struct + type nonrec t = t + let show = show + end) + + let to_offset : t -> Offset.Unit.t = function + | Field f -> `Field (f, `NoOffset) + | Index -> `Index ((), `NoOffset) + end + + module OffsetTrie = + struct + (* 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') -> (`Bot, ChildMap.singleton (Field f) (singleton offset' value)) + | `Index ((), offset') -> (`Bot, ChildMap.singleton Index (singleton offset' value)) + end + module G = struct - include Lattice.Lift2 (Access.AS) (V0Set) (Printable.DefaultNames) + include Lattice.Lift2 (OffsetTrie) (MemoSet) (Printable.DefaultNames) let access = function - | `Bot -> Access.AS.bot () + | `Bot -> OffsetTrie.bot () | `Lifted1 x -> x | _ -> failwith "Race.access" let vars = function - | `Bot -> V0Set.bot () + | `Bot -> MemoSet.bot () | `Lifted2 x -> x | _ -> failwith "Race.vars" let create_access access = `Lifted1 access @@ -51,24 +217,51 @@ struct vulnerable := 0; unsafe := 0 - let side_vars ctx lv_opt ty = - match lv_opt with - | Some (v, _) -> - if !GU.should_warn then - ctx.sideg (V.vars v) (G.create_vars (V0Set.singleton (lv_opt, ty))) - | None -> + let side_vars ctx memo = + match memo with + | (`Var v, _) -> + if !AnalysisState.should_warn then + ctx.sideg (V.vars v) (G.create_vars (MemoSet.singleton memo)) + | _ -> () - let side_access ctx ty lv_opt (conf, w, loc, e, a) = - let ty = - if Option.is_some lv_opt then - `Type Cil.voidType (* avoid unsound type split for alloc variables *) - else - ty + 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 (`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 - if !GU.should_warn then - ctx.sideg (V.access (lv_opt, ty)) (G.create_access (Access.AS.singleton (conf, w, loc, e, a))); - side_vars ctx lv_opt ty + let 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 @@ -76,63 +269,83 @@ struct 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); *) - let accs = G.access (ctx.global g) in - let (lv, ty) = g' in - let mem_loc_str = Pretty.sprint ~width:max_int (Access.d_memo () (ty, lv)) in - Timing.wrap ~args:[("memory location", `String mem_loc_str)] "race" (Access.warn_global safe vulnerable unsafe g') accs + (* 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) ~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 {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 ~prefix:(Access.AS.empty ()) ~type_suffix_prefix:(Access.AS.empty ()) | `Right _ -> (* vars *) () end | IterSysVars (Global g, vf) -> - V0Set.iter (fun v -> + MemoSet.iter (fun v -> vf (Obj.repr (V.access v)) ) (G.vars (ctx.global (V.vars g))) | _ -> Queries.Result.top q let event ctx e octx = match e with - | Events.Access {exp=e; lvals; kind; reach} when ThreadFlag.is_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 part_access (vo:varinfo option) (oo: offset option): MCPAccess.A.t = + 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 add_access conf vo oo = - let a = part_access vo oo in - Access.add (side_access octx) e kind conf vo oo a; + let node = Option.get !Node.current_node in + let add_access conf 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 None in - Access.add_struct (side_access octx) e kind conf (`Struct (ci,`NoOffset)) None a + 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 = Lval.CilLval.to_ciloffs offs in - if CilType.Varinfo.equal var dummyFunDec.svar then - add_access conf None (Some coffs) - else - add_access conf (Some var) (Some 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 -> @@ -145,14 +358,28 @@ struct in Queries.TS.iter f ts end; - on_lvals ls !includes_uk - | _ -> - add_access (conf - 60) None 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 3102d4c35a..f8591db4c9 100644 --- a/src/analyses/region.ml +++ b/src/analyses/region.ml @@ -1,6 +1,9 @@ -(** Assigning static regions to dynamic memory. *) +(** Analysis of disjoint heap regions for dynamically allocated memory ([region]). -open Prelude.Ana + @see Seidl, H., Vojdani, V. Region Analysis for Race Detection. *) + +open Batteries +open GoblintCil open Analyses module RegMap = RegionDomain.RegMap @@ -20,13 +23,12 @@ struct include StdV end - let regions ctx exp part : Lval.CilLval.t list = + let regions ctx exp part : Mval.Exp.t list = match ctx.local with | `Lifted reg -> let ask = Analyses.ask_of_ctx ctx in let ev = Reg.eval_exp ask exp in - let to_exp (v,f) = (v,Lval.Fields.to_offs' f) in - List.map to_exp (Reg.related_globals ask ev (part,reg)) + Reg.related_globals ask ev (part,reg) | `Top -> Messages.info ~category:Unsound "Region state is broken :("; [] | `Bot -> [] @@ -57,7 +59,7 @@ struct ls | _ -> Queries.Result.top q - module Lvals = SetDomain.Make (Lval.CilLval) + module Lvals = SetDomain.Make (Mval.Exp) module A = struct include Printable.Option (Lvals) (struct let name = "no region" end) @@ -67,7 +69,7 @@ struct | None, _ | _, None -> false (* The following cases are needed if RegMap has empty values, due to bugs. - When not handling escape, 09-regions/34-escape_rc would fail without this: + When not handling escape, 09-regions/34-escape_rc would fail without this: | Some r1, _ when Lvals.is_empty r1 -> true | _, Some r2 when Lvals.is_empty r2 -> true *) | Some r1, Some r2 when Lvals.disjoint r1 r2 -> false @@ -82,12 +84,9 @@ struct Some (Lvals.empty ()) | Memory {exp = e; _} -> (* TODO: remove regions that cannot be reached from the var*) - let rec unknown_index = function - | `NoOffset -> `NoOffset - | `Field (f, os) -> `Field (f, unknown_index os) - | `Index (i, os) -> `Index (MyCFG.unknown_exp, unknown_index os) (* forget specific indices *) - in - Option.map (Lvals.of_list % List.map (Tuple2.map2 unknown_index)) (get_region ctx e) + (* forget specific indices *) + (* TODO: If indices are topped, could they not be collected in the first place? *) + Option.map (Lvals.of_list % List.map (Tuple2.map2 Offset.Exp.top_indices)) (get_region ctx e) (* transfer functions *) let assign ctx (lval:lval) (rval:exp) : D.t = @@ -140,9 +139,12 @@ struct [ctx.local, `Lifted reg] | x -> [x,x] - let combine ctx (lval:lval option) fexp (f:fundec) (args:exp list) fc (au:D.t) : D.t = + let combine_env ctx lval fexp f args fc au f_ask = + 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 = match au with - | `Lifted reg -> + | `Lifted reg -> let old_regpart = ctx.global () in let module BS = (val Base.get_main ()) in let regpart, reg = match lval with @@ -170,27 +172,20 @@ struct | _ -> ctx.local end | _ -> - let t, _, _, _ = splitFunctionTypeVI f in - match unrollType t with - | TPtr (t,_) -> - begin match Goblintutil.is_blessed t, lval with - | Some rv, Some lv -> assign ctx lv (AddrOf (Var rv, NoOffset)) - | _ -> ctx.local - end - | _ -> ctx.local + ctx.local let startstate v = `Lifted (RegMap.bot ()) - let threadenter ctx lval f args: D.t list = + let threadenter ctx ~multiple lval f args: D.t list = let fd = Cilfacade.find_varinfo_fundec f in match args, fd.sformals with - | [exp], [param] -> + | [exp], [param] -> (* The parameter may not have escaped here (for the first thread). *) let reg = Reg.assign ~thread_arg:true (Analyses.ask_of_ctx ctx) (var param) exp (ctx.global (), RegMap.bot ()) in - [`Lifted (snd reg)] + [`Lifted (snd reg)] | _ -> [`Lifted (RegMap.bot ())] - let threadspawn ctx lval f args fctx = ctx.local + let threadspawn ctx ~multiple lval f args fctx = ctx.local let exitstate v = `Lifted (RegMap.bot ()) diff --git a/src/analyses/spec.ml b/src/analyses/spec.ml index 0fced7891c..2f754f6160 100644 --- a/src/analyses/spec.ml +++ b/src/analyses/spec.ml @@ -1,6 +1,11 @@ -(** Analysis by specification file. *) +(** Analysis using finite automaton specification file ([spec]). -open Prelude.Ana + @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 @@ -14,8 +19,8 @@ struct module C = SpecDomain.Dom (* special variables *) - let return_var = Goblintutil.create_var @@ Cil.makeVarinfo false "@return" Cil.voidType, `NoOffset - let global_var = Goblintutil.create_var @@ Cil.makeVarinfo false "@global" Cil.voidType, `NoOffset + 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 [] @@ -177,7 +182,7 @@ struct 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_exp var))] in (* use Fl for Lval instead? *) + 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)]; @@ -198,15 +203,14 @@ struct match q with | _ -> Queries.Result.top q - let query_lv ask exp = + let query_addrs ask exp = match ask (Queries.MayPointTo exp) with - | l when not (Queries.LS.is_top l) -> - Queries.LS.elements l + | ad when not (Queries.AD.is_top ad) -> Queries.AD.elements ad | _ -> [] let eval_fv ask exp: varinfo option = - match query_lv ask exp with - | [(v,_)] -> Some v + match query_addrs ask exp with + | [addr] -> Queries.AD.Addr.to_var_may addr | _ -> None @@ -234,7 +238,7 @@ struct in let m = SpecCheck.check ctx get_key matches in let key_from_exp = function - | Lval (Var v,o) -> Some (v, Lval.CilLval.of_ciloffs o) + | 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 *) @@ -248,7 +252,7 @@ struct | 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 Lval.CilLval.class_tag k2 = `Temp (* check if k2 is a temporary Lval introduced by CIL *) + 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 *) @@ -291,7 +295,7 @@ struct 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 Cilint.is_zero_cilint i && tv then ( + if Z.equal i Z.zero && tv then ( M.debug ~category:Analyzer "error-branch"; (* D.remove key m *) )else( @@ -311,8 +315,8 @@ struct (* c_exp=exp *) (* leads to Out_of_memory *) match SC.branch_exp c with | Some (c_exp,c_tv) -> - (* let exp_str = sprint d_exp exp in *) (* contains too many casts, so that matching fails *) - let exp_str = sprint d_exp binop in + (* 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)); *) @@ -414,32 +418,35 @@ struct D.edit_callstack (BatList.cons (Option.get !Node.current_node)) ctx.local else ctx.local in [m, m] - let combine ctx (lval:lval option) fexp (f:fundec) (args:exp list) fc (au:D.t) : D.t = + 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 - (* remove special return var and handle potential overwrites *) - let au = D.remove' return_var au (* |> check_overwrite_open k *) 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 au + 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 au + 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 au - | _ -> au + D.add' k v ctx.local + | _ -> ctx.local let special ctx (lval: lval option) (f:varinfo) (arglist:exp list) : D.t = - (* let _ = GobConfig.set_bool "dbg.debug" false in *) 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 -> @@ -480,8 +487,8 @@ struct 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/stackTrace.ml b/src/analyses/stackTrace.ml index 1c488e8b67..3c3bd56640 100644 --- a/src/analyses/stackTrace.ml +++ b/src/analyses/stackTrace.ml @@ -1,83 +1,52 @@ -(** Stack-trace "analyses". *) +(** Call stack analyses ([stack_trace], [stack_trace_set], [stack_loc]). *) -open Prelude.Ana +open GoblintCil open Analyses module LF = LibraryFunctions -module Spec (D: StackDomain.S) (P: sig val name : string end)= +module Spec (D: StackDomain.S) (N: sig val name : string end)= struct - include Analyses.DefaultSpec + include Analyses.IdentitySpec - let name () = P.name + let name () = N.name module D = D module C = D (* transfer functions *) - let assign ctx (lval:lval) (rval:exp) : D.t = - ctx.local - - let branch ctx (exp:exp) (tv:bool) : D.t = - ctx.local let body ctx (f:fundec) : D.t = if f.svar.vname = "__goblint_dummy_init" then ctx.local else D.push f.svar ctx.local - let return ctx (exp:exp option) (f:fundec) : D.t = - ctx.local - - let enter ctx (lval: lval option) (f:fundec) (args:exp list) : (D.t * D.t) list = - [ctx.local,ctx.local] - - let combine ctx (lval:lval option) fexp (f:fundec) (args:exp list) fc (au:D.t) : D.t = - ctx.local - - let special ctx (lval: lval option) (f:varinfo) (arglist:exp list) : D.t = - ctx.local + let combine_env ctx lval fexp f args fc au f_ask = + ctx.local (* keep local as opposed to IdentitySpec *) let startstate v = D.bot () - let 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 exitstate v = D.top () end module SpecLoc = struct - include Analyses.DefaultSpec + include Analyses.IdentitySpec let name () = "stack_loc" module D = StackDomain.Dom3 module C = StackDomain.Dom3 (* transfer functions *) - let assign ctx (lval:lval) (rval:exp) : D.t = - ctx.local - - let branch ctx (exp:exp) (tv:bool) : D.t = - ctx.local - - let body ctx (f:fundec) : D.t = - ctx.local - - let return ctx (exp:exp option) (f:fundec) : D.t = - ctx.local 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] - let combine ctx (lval:lval option) fexp (f:fundec) (args:exp list) fc (au:D.t) : D.t = - ctx.local - - let special ctx (lval: lval option) (f:varinfo) (arglist:exp list) : D.t = - ctx.local + let combine_env ctx lval fexp f args fc au f_ask = + ctx.local (* keep local as opposed to IdentitySpec *) let startstate v = D.bot () let exitstate v = D.top () - let threadenter ctx lval f args = + let threadenter ctx ~multiple lval f args = [D.push !Tracing.current_loc ctx.local] - - let threadspawn ctx lval f args fctx = ctx.local end diff --git a/src/analyses/symbLocks.ml b/src/analyses/symbLocks.ml index af495ba972..f6fdd96c2e 100644 --- a/src/analyses/symbLocks.ml +++ b/src/analyses/symbLocks.ml @@ -1,6 +1,6 @@ -(** Symbolic lock-sets for use in per-element patterns. +(** Symbolic lockset analysis for per-element (field or index) locking patterns ([symb_locks]). - See Section 5 and 6 in https://dl.acm.org/doi/10.1145/2970276.2970337 for more details. *) + @see Static race detection for device drivers: the Goblint approach. Section 5 and 6. *) module LF = LibraryFunctions module LP = SymbLocksDomain.LockingPattern @@ -10,7 +10,9 @@ module VarEq = VarEq.Spec module PS = SetDomain.ToppedSet (LP) (struct let topname = "All" end) -open Prelude.Ana +open Batteries +open GoblintCil +open Pretty open Analyses (* Note: This is currently more conservative than varEq --- but @@ -27,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 @@ -46,7 +48,8 @@ struct List.fold_right D.remove_var (fundec.sformals@fundec.slocals) ctx.local let enter ctx lval f args = [(ctx.local,ctx.local)] - let combine ctx lval fexp f args fc st2 = st2 + let combine_env ctx lval fexp f args fc au f_ask = au + let combine_assign ctx lval fexp f args fc st2 f_ask = ctx.local let get_locks e st = let add_perel x xs = @@ -152,7 +155,7 @@ struct let one_lockstep (_,a,m) xs = match m with | AddrOf (Var v,o) -> - let lock = ILock.from_var_offset (v, o) in + let lock = ILock.of_mval (v, o) in A.add (`Right lock) xs | _ -> Messages.info ~category:Unsound "Internal error: found a strange lockstep pattern."; @@ -181,7 +184,7 @@ struct (match ctx.ask (Queries.Regions e) with | ls when not (Queries.LS.is_top ls || Queries.LS.is_empty ls) -> let add_exp x xs = - try Queries.ES.add (Lval.CilLval.to_exp x) xs + try Queries.ES.add (Mval.Exp.to_cil_exp x) xs with Lattice.BotValue -> xs in begin try Queries.LS.fold add_exp ls (Queries.ES.singleton e) diff --git a/src/analyses/taintPartialContexts.ml b/src/analyses/taintPartialContexts.ml new file mode 100644 index 0000000000..85dabd1c9d --- /dev/null +++ b/src/analyses/taintPartialContexts.ml @@ -0,0 +1,116 @@ +(** Taint analysis of variables modified in a function ([taintPartialContexts]). *) + +(* TaintPartialContexts: Set of Lvalues, which are tainted at a specific Node. *) +(* An Lvalue is tainted, if its Rvalue might have been altered in the context of the current function, + implying that the Rvalue of any Lvalue not in the set has definitely not been changed within the current context. *) +open GoblintCil +open Analyses + +module AD = ValueDomain.AD + +module Spec = +struct + include Analyses.IdentitySpec + + let name () = "taintPartialContexts" + 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 = + D.union (ctx.ask (Queries.MayPointTo (AddrOf lval))) ctx.local + + (* this analysis is context insensitive*) + let context _ _ = () + + (* transfer functions *) + let assign ctx (lval:lval) (rval:exp) : D.t = + taint_lval ctx lval + + let return ctx (exp:exp option) (f:fundec) : D.t = + (* remove locals, except ones which need to be weakly updated*) + let d = ctx.local in + let d_return = + if D.is_top d then + d + else + let locals = f.sformals @ f.slocals in + 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 + + + let enter ctx (lval: lval option) (f:fundec) (args:exp list) : (D.t * D.t) list = + (* Entering a function, all globals count as untainted *) + [ctx.local, (D.bot ())] + + let combine_env ctx lval fexp f args fc au f_ask = + if M.tracing then M.trace "taintPC" "combine for %s in TaintPC: tainted: in function: %a before call: %a\n" f.svar.vname D.pretty au D.pretty ctx.local; + D.union ctx.local au + + let combine_assign ctx (lvalOpt:lval option) fexp (f:fundec) (args:exp list) fc (au:D.t) (f_ask: Queries.ask) : D.t = + match lvalOpt with + | Some lv -> taint_lval ctx lv + | None -> 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 d = + match lvalOpt with + | Some lv -> taint_lval ctx lv + | None -> ctx.local + in + let desc = LibraryFunctions.find f in + let shallow_addrs = LibraryDesc.Accesses.find desc.accs { kind = Write; deep = false } arglist in + let deep_addrs = LibraryDesc.Accesses.find desc.accs { kind = Write; deep = true } arglist in + let deep_addrs = + if List.mem LibraryDesc.InvalidateGlobals desc.attrs then ( + foldGlobals !Cilfacade.current_file (fun acc global -> + match global with + | GVar (vi, _, _) when not (BaseUtil.is_static vi) -> + mkAddrOf (Var vi, NoOffset) :: acc + (* TODO: what about GVarDecl? (see "base.ml -> special_unknown_invalidate")*) + | _ -> acc + ) deep_addrs + ) + 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 + in + d + + let startstate v = D.bot () + let threadenter ctx ~multiple lval f args = + [D.bot ()] + let threadspawn ctx ~multiple lval f args fctx = + match lval with + | Some lv -> taint_lval ctx lv + | None -> ctx.local + let exitstate v = D.top () + + let query ctx (type a) (q: a Queries.t) : a Queries.result = + match q with + | MayBeTainted -> (ctx.local : Queries.AD.t) + | _ -> Queries.Result.top q + +end + +let _ = + MCP.register_analysis (module Spec : MCPSpec) + +module VS = SetDomain.ToppedSet(Basetype.Variables) (struct let topname = "All" end) + +(* Convert Lval set to (less precise) Varinfo 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 index d2a9079c68..0563730fb2 100644 --- a/src/analyses/termination.ml +++ b/src/analyses/termination.ml @@ -1,6 +1,7 @@ -(** Termination of loops. *) +(** Termination analysis of loops using counter variables ([term]). *) -open Prelude.Ana +open Batteries +open GoblintCil open Analyses module M = Messages @@ -22,7 +23,7 @@ class loopCounterVisitor (fd : fundec) = object(self) (* 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 = Goblintutil.create_var (makeLocalVar fd name ~init:(SingleInit zero) typ) in + 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 *) @@ -90,7 +91,7 @@ let makeVar fd loc name = 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 *) - Goblintutil.create_var (makeLocalVar fd id ~init:(SingleInit zero) typ) + 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) @@ -184,7 +185,7 @@ end module Spec = struct - include Analyses.DefaultSpec + include Analyses.IdentitySpec let name () = "term" module D = TermDomain @@ -197,8 +198,6 @@ struct (*| _ -> Queries.Result.top ()*) (* transfer functions *) - let assign ctx (lval:lval) (rval:exp) : D.t = - ctx.local let branch ctx (exp:exp) (tv:bool) : D.t = ctx.local @@ -217,24 +216,8 @@ struct (* ctx.local *) (* | _ -> ctx.local *) - let body ctx (f:fundec) : D.t = - ctx.local - - let return ctx (exp:exp option) (f:fundec) : D.t = - ctx.local - - let enter ctx (lval: lval option) (f:fundec) (args:exp list) : (D.t * D.t) list = - [ctx.local,ctx.local] - - let combine ctx (lval:lval option) fexp (f:fundec) (args:exp list) fc (au:D.t) : D.t = - au - - let special ctx (lval: lval option) (f:varinfo) (arglist:exp list) : D.t = - 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 exitstate v = D.bot () end diff --git a/src/analyses/threadAnalysis.ml b/src/analyses/threadAnalysis.ml index 7722218a05..0264f4b700 100644 --- a/src/analyses/threadAnalysis.ml +++ b/src/analyses/threadAnalysis.ml @@ -1,6 +1,6 @@ -(** Thread creation and uniqueness analyses. *) +(** Created threads and their uniqueness analysis ([thread]). *) -open Prelude.Ana +open GoblintCil open Analyses module T = ThreadIdDomain.Thread @@ -8,7 +8,7 @@ module TS = ConcDomain.ThreadSet module Spec = struct - include Analyses.DefaultSpec + include Analyses.IdentitySpec let name () = "thread" module D = ConcDomain.CreatedThreadSet @@ -19,13 +19,9 @@ struct include T include StdV end - - let should_join = D.equal + module P = IdentityP (D) (* transfer functions *) - let assign ctx (lval:lval) (rval:exp) : D.t = ctx.local - let branch ctx (exp:exp) (tv:bool) : D.t = ctx.local - let body ctx (f:fundec) : D.t = ctx.local let return ctx (exp:exp option) (f:fundec) : D.t = let tid = ThreadId.get_current (Analyses.ask_of_ctx ctx) in begin match tid with @@ -33,18 +29,24 @@ struct | _ -> () end; ctx.local - let enter ctx (lval: lval option) (f:fundec) (args:exp list) : (D.t * D.t) list = [ctx.local,ctx.local] - let combine ctx (lval:lval option) fexp (f:fundec) (args:exp list) fc (au:D.t) : D.t = au 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 @@ -59,7 +61,8 @@ 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) | _ -> ctx.local @@ -71,17 +74,25 @@ struct | `Lifted tid -> not (is_not_unique ctx tid) | _ -> false end - | Queries.MustBeSingleThreaded -> begin + | Queries.MustBeSingleThreaded {since_start = false} -> begin let tid = ThreadId.get_current (Analyses.ask_of_ctx ctx) in match tid with - | `Lifted tid when T.is_main tid -> D.is_empty ctx.local + | `Lifted tid when T.is_main tid -> + (* This analysis cannot tell if we are back in single-threaded mode or never left it. *) + D.is_empty ctx.local | _ -> false end | _ -> 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 3ac975314c..21a8b69c93 100644 --- a/src/analyses/threadEscape.ml +++ b/src/analyses/threadEscape.ml @@ -1,9 +1,10 @@ -(** Variables that escape threads using the last argument from pthread_create. *) +(** Escape analysis for thread-local variables ([escape]). *) -open Prelude.Ana +open GoblintCil open Analyses module M = Messages +module AD = Queries.AD let has_escaped (ask: Queries.ask) (v: varinfo): bool = assert (not v.vglob); @@ -14,127 +15,165 @@ let has_escaped (ask: Queries.ask) (v: varinfo): bool = module Spec = struct - include Analyses.DefaultSpec + include Analyses.IdentitySpec + + module ThreadIdSet = SetDomain.Make (ThreadIdDomain.ThreadLifted) let name () = "escape" module D = EscapeDomain.EscapedVars module C = EscapeDomain.EscapedVars module V = VarinfoV - module G = EscapeDomain.EscapedVars - - let rec cut_offset x = - match x with - | `NoOffset -> `NoOffset - | `Index (_,o) -> `NoOffset - | `Field (f,o) -> `Field (f, cut_offset o) + module G = ThreadIdSet 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 (Addr.from_var_offset (v, cut_offset o)) set in *) - 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 (Addr.from_var_offset (v, cut_offset o)) set in *) - 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 = + ctx.ask Queries.CurrentThreadId + + (** Emit an escape event: + Only necessary when code has ever been multithreaded, + or when about to go multithreaded. *) + let emit_escape_event ctx escaped = + (* avoid emitting unnecessary event *) + if not (D.is_empty escaped) then + ctx.emit (Events.Escape escaped) + + (** Side effect escapes: In contrast to the emitting the event, side-effecting is + necessary in single threaded mode, since we rely on escape status in Base + for passing locals reachable via globals *) + let side_effect_escape ctx escaped threadid = + let threadid = G.singleton threadid in + D.iter (fun v -> + ctx.sideg v threadid) escaped + (* queries *) let query ctx (type a) (q: a Queries.t): a Queries.result = match q with - | Queries.MayEscape v -> D.mem v ctx.local + | Queries.MayEscape v -> + let threads = ctx.global v in + if ThreadIdSet.is_empty threads then + false + else begin + let possibly_started current = function + | `Lifted tid -> + let threads = ctx.ask Queries.CreatedThreads in + let not_started = MHP.definitely_not_started (current, threads) tid in + let possibly_started = not not_started in + possibly_started + | `Top -> true + | `Bot -> false + in + let equal_current current = function + | `Lifted tid -> + ThreadId.Thread.equal current tid + | `Top -> true + | `Bot -> false + in + match ctx.ask Queries.CurrentThreadId with + | `Lifted current -> + let possibly_started = ThreadIdSet.exists (possibly_started current) threads in + if possibly_started then + true + else + 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 threads then + (* Another instance of the non-unqiue current thread may have escaped the variable *) + true + else + (* Check whether current unique thread has escaped the variable *) + D.mem v ctx.local + | `Top -> + true + | `Bot -> + M.warn ~category:MessageCategory.Analyzer "CurrentThreadId is bottom."; + false + end | _ -> Queries.Result.top q + let escape_rval ctx (rval:exp) = + let ask = Analyses.ask_of_ctx ctx in + let escaped = reachable ask rval in + let escaped = D.filter (fun v -> not v.vglob) escaped in + + let thread_id = thread_id ctx in + side_effect_escape ctx escaped thread_id; + if ThreadFlag.has_ever_been_multi ask then (* avoid emitting unnecessary event *) + emit_escape_event ctx escaped; + escaped + (* transfer functions *) let assign ctx (lval:lval) (rval:exp) : D.t = let ask = Analyses.ask_of_ctx ctx in let vs = mpt ask (AddrOf lval) in - if M.tracing then M.tracel "escape" "assign vs: %a\n" D.pretty vs; if D.exists (fun v -> v.vglob || has_escaped ask v) vs then ( - let escaped = reachable ask rval in - let escaped = D.filter (fun v -> not v.vglob) escaped in - if M.tracing then M.tracel "escape" "assign vs: %a | %a\n" D.pretty vs D.pretty escaped; - if not (D.is_empty escaped) && ThreadFlag.is_multi ask then (* avoid emitting unnecessary event *) - ctx.emit (Events.Escape escaped); - D.iter (fun v -> - ctx.sideg v escaped; - ) vs; + let escaped = escape_rval ctx rval in D.join ctx.local escaped - ) - else + ) else begin ctx.local - - let branch ctx (exp:exp) (tv:bool) : D.t = - ctx.local - - let body ctx (f:fundec) : D.t = - ctx.local - - let return ctx (exp:exp option) (f:fundec) : D.t = - ctx.local - - let enter ctx (lval: lval option) (f:fundec) (args:exp list) : (D.t * D.t) list = - [ctx.local,ctx.local] - - let combine ctx (lval:lval option) fexp (f:fundec) (args:exp list) fc (au:D.t) : D.t = - au + end let special ctx (lval: lval option) (f:varinfo) (args:exp list) : D.t = let desc = LibraryFunctions.find f in match desc.special args, f.vname, args with | _, "pthread_setspecific" , [key; pt_value] -> - let escaped = reachable (Analyses.ask_of_ctx ctx) pt_value in - let escaped = D.filter (fun v -> not v.vglob) escaped in - if not (D.is_empty escaped) then (* avoid emitting unnecessary event *) - ctx.emit (Events.Escape escaped); - let extra = D.fold (fun v acc -> D.join acc (ctx.global v)) escaped (D.empty ()) in (* TODO: must transitively join escapes of every ctx.global v as well? *) - D.join ctx.local (D.join escaped extra) + let escaped = escape_rval ctx pt_value in + D.join ctx.local escaped | _ -> ctx.local 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 ~multiple lval f args fctx = + D.join ctx.local @@ match args with | [ptc_arg] -> + (* not reusing fctx.local to avoid unnecessarily early join of extra *) let escaped = reachable (Analyses.ask_of_ctx ctx) ptc_arg in let escaped = D.filter (fun v -> not v.vglob) escaped in - if not (D.is_empty escaped) then (* avoid emitting unnecessary event *) - ctx.emit (Events.Escape escaped); - let extra = D.fold (fun v acc -> D.join acc (ctx.global v)) escaped (D.empty ()) in (* TODO: must transitively join escapes of every ctx.global v as well? *) - [D.join ctx.local (D.join escaped extra)] - | _ -> [ctx.local] - - let threadspawn ctx lval f args fctx = - D.join ctx.local @@ - match args with - | [ptc_arg] -> - (* not reusing fctx.local to avoid unnecessarily early join of extra *) - let escaped = reachable (Analyses.ask_of_ctx ctx) ptc_arg in - let escaped = D.filter (fun v -> not v.vglob) escaped in - if M.tracing then M.tracel "escape" "%a: %a\n" d_exp ptc_arg D.pretty escaped; - if not (D.is_empty escaped) then (* avoid emitting unnecessary event *) - ctx.emit (Events.Escape escaped); - escaped - | _ -> D.bot () + if M.tracing then M.tracel "escape" "%a: %a\n" d_exp ptc_arg D.pretty escaped; + let thread_id = thread_id ctx in + emit_escape_event ctx escaped; + side_effect_escape ctx escaped thread_id; + escaped + | _ -> D.bot () let event ctx e octx = match e with | Events.EnterMultiThreaded -> let escaped = ctx.local in - if not (D.is_empty escaped) then (* avoid emitting unnecessary event *) - ctx.emit (Events.Escape escaped); + let thread_id = thread_id ctx in + emit_escape_event ctx escaped; + side_effect_escape ctx escaped thread_id; ctx.local | _ -> ctx.local end diff --git a/src/analyses/threadFlag.ml b/src/analyses/threadFlag.ml index 3dd1c68586..6bd466caef 100644 --- a/src/analyses/threadFlag.ml +++ b/src/analyses/threadFlag.ml @@ -1,23 +1,26 @@ -(** Multi-threadedness analysis. *) +(** Multi-threadedness analysis ([threadflag]). *) -module GU = Goblintutil module LF = LibraryFunctions -open Prelude.Ana +open GoblintCil open Analyses -let is_multi (ask: Queries.ask): bool = - if !GU.global_initialization then false else - not (ask.f Queries.MustBeSingleThreaded) +let is_currently_multi (ask: Queries.ask): bool = + if !AnalysisState.global_initialization then false else + not (ask.f (Queries.MustBeSingleThreaded {since_start = false})) +let has_ever_been_multi (ask: Queries.ask): bool = + if !AnalysisState.global_initialization then false else + not (ask.f (Queries.MustBeSingleThreaded {since_start = true})) module Spec = struct - include Analyses.DefaultSpec + include Analyses.IdentitySpec module Flag = ThreadFlagDomain.Simple module D = Flag module C = Flag + module P = IdentityP (D) let name () = "threadflag" @@ -29,12 +32,6 @@ struct let create_tid v = Flag.get_multi () - let should_join = D.equal - - let body ctx f = ctx.local - - let branch ctx exp tv = ctx.local - let return ctx exp fundec = match fundec.svar.vname with | "__goblint_dummy_init" -> @@ -43,20 +40,9 @@ struct | _ -> ctx.local - let assign ctx (lval:lval) (rval:exp) : D.t = - ctx.local - - let enter ctx lval f args = - [ctx.local,ctx.local] - - let combine ctx lval fexp f args fc st2 = st2 - - let special ctx lval f args = - ctx.local - let query ctx (type a) (x: a Queries.t): a Queries.result = match x with - | Queries.MustBeSingleThreaded -> not (Flag.is_multi ctx.local) + | 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) (* This used to be in base but also commented out. *) (* | Queries.MayBePublic _ -> Flag.is_multi ctx.local *) @@ -70,15 +56,15 @@ struct let should_print m = not m end let access ctx _ = - is_multi (Analyses.ask_of_ctx ctx) + is_currently_multi (Analyses.ask_of_ctx ctx) - let threadenter ctx lval f args = - if not (is_multi (Analyses.ask_of_ctx ctx)) then + 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 = - if not (is_multi (Analyses.ask_of_ctx ctx)) then + let threadspawn ctx ~multiple lval f args fctx = + if not (has_ever_been_multi (Analyses.ask_of_ctx ctx)) then ctx.emit Events.EnterMultiThreaded; D.join ctx.local (Flag.get_main ()) end diff --git a/src/analyses/threadId.ml b/src/analyses/threadId.ml index 14967ffc5a..da2c688ad1 100644 --- a/src/analyses/threadId.ml +++ b/src/analyses/threadId.ml @@ -1,9 +1,8 @@ -(** Current thread ID analysis. *) +(** Current thread ID analysis ([threadid]). *) -module GU = Goblintutil module LF = LibraryFunctions -open Prelude.Ana +open Batteries open Analyses open GobList.Syntax @@ -18,73 +17,115 @@ let get_current_unlift ask: Thread.t = | `Lifted thread -> thread | _ -> failwith "ThreadId.get_current_unlift" +module VNI = + Printable.Prod3 + (CilType.Varinfo) + (Node) ( + Printable.Option + (WrapperFunctionAnalysis0.ThreadCreateUniqueCount) + (struct let name = "no index" end)) module Spec = struct - include Analyses.DefaultSpec + include Analyses.IdentitySpec + 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 - module D = Lattice.Prod (ThreadLifted) (TD) + (** Uniqueness Counter * TID * (All thread creates of current thread * All thread creates of the current function and its callees) *) + module D = Lattice.Prod3 (N) (ThreadLifted) (Created) module C = D + module P = IdentityP (D) let tids = ref (Hashtbl.create 20) let name () = "threadid" - let startstate v = (ThreadLifted.bot (), TD.bot ()) - let exitstate v = (`Lifted (Thread.threadinit v ~multiple:false), TD.bot ()) + let context fd ((n,current,td) as d) = + if GobConfig.get_bool "ana.thread.context.create-edges" then + d + else + (n, current, (TD.bot (), TD.bot ())) + + let startstate v = (N.bot (), ThreadLifted.bot (), (TD.bot (),TD.bot ())) + let exitstate v = (N.bot (), `Lifted (Thread.threadinit v ~multiple:false), (TD.bot (), TD.bot ())) let morphstate v _ = let tid = Thread.threadinit v ~multiple:false in if GobConfig.get_bool "dbg.print_tids" then Hashtbl.replace !tids tid (); - (`Lifted (tid), TD.bot ()) + (N.bot (), `Lifted (tid), (TD.bot (), TD.bot ())) - let create_tid (current, td) (node: Node.t) 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 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 | _ -> [`Lifted (Thread.threadinit v ~multiple:true)] - let body ctx f = ctx.local - - let branch ctx exp tv = ctx.local - - let return ctx exp fundec = ctx.local - - let assign ctx (lval:lval) (rval:exp) : D.t = - ctx.local + let is_unique ctx = + ctx.ask Queries.MustBeUniqueThread let enter ctx lval f args = - [ctx.local,ctx.local] - - let combine ctx lval fexp f args fc st2 = st2 + let (n, current, (td, _)) = ctx.local in + [ctx.local, (n, current, (td,TD.bot ()))] - let special ctx lval f args = - ctx.local - - let is_unique ctx = - ctx.ask Queries.MustBeUniqueThread + let combine_env ctx lval fexp f args fc ((n,current,(_, au_ftd)) as au) f_ask = + let (_, _, (td, ftd)) = ctx.local in + if not (GobConfig.get_bool "ana.thread.context.create-edges") then + (n,current,(TD.join td au_ftd, TD.join ftd au_ftd)) + else + au - let created (current, td) = + let created (_, current, (td, _)) = match current with | `Lifted current -> BatOption.map_default (ConcDomain.ThreadSet.of_list) (ConcDomain.ThreadSet.top ()) (Thread.created current td) | _ -> ConcDomain.ThreadSet.top () let query (ctx: (D.t, _, _, _) ctx) (type a) (x: a Queries.t): a Queries.result = match x with - | Queries.CurrentThreadId -> fst ctx.local + | Queries.CurrentThreadId -> Tuple3.second ctx.local | Queries.CreatedThreads -> created ctx.local | Queries.MustBeUniqueThread -> - begin match fst ctx.local with + begin match Tuple3.second ctx.local with | `Lifted tid -> Thread.is_unique tid | _ -> Queries.MustBool.top () end + | Queries.MustBeSingleThreaded {since_start} -> + begin match Tuple3.second ctx.local with + | `Lifted tid when Thread.is_main tid -> + let created = created ctx.local in + if since_start then + ConcDomain.ThreadSet.is_empty created + else if ctx.ask Queries.ThreadsJoinedCleanly then + let joined = ctx.ask Queries.MustJoinedThreads in + ConcDomain.ThreadSet.is_empty (ConcDomain.ThreadSet.diff created joined) + else + false + | _ -> false + end | _ -> Queries.Result.top x module A = @@ -99,18 +140,27 @@ struct let access ctx _ = if is_unique ctx then - let tid = fst ctx.local in + let tid = Tuple3.second ctx.local in Some tid else None - let threadenter ctx lval f args = - let+ tid = create_tid ctx.local ctx.prev_node f in - (tid, TD.bot ()) - - let threadspawn ctx lval f args fctx = - let (current, td) = ctx.local in - (current, Thread.threadspawn td ctx.prev_node f) + (** get the node that identifies the current context, possibly that of a wrapper function *) + let indexed_node_for_ctx ctx = + match ctx.ask Queries.ThreadCreateIndexedNode with + | `Lifted node, count when WrapperFunctionAnalysis.ThreadCreateUniqueCount.is_top count -> node, None + | `Lifted node, count -> node, Some count + | (`Bot | `Top), _ -> ctx.prev_node, None + + let threadenter ctx ~multiple lval f args:D.t list = + let n, i = indexed_node_for_ctx ctx in + let+ tid = create_tid ~multiple ctx.local (n, i) f in + (`Lifted (f, n, i), tid, (TD.bot (), TD.bot ())) + + 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 ~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 da6333aa91..862711073c 100644 --- a/src/analyses/threadJoins.ml +++ b/src/analyses/threadJoins.ml @@ -1,19 +1,26 @@ -(** Thread join analysis. *) -open Prelude.Ana +(** Joined threads analysis ([threadJoins]). + + @see Schwarz, M., Saan, S., Seidl, H., Erhard, J., Vojdani, V. Clustered Relational Thread-Modular Abstract Interpretation with Local Traces. Appendix F. *) + +open GoblintCil open Analyses module TID = ThreadIdDomain.Thread module TIDs = ConcDomain.ThreadSet module MustTIDs = ConcDomain.MustThreadSet +module CleanExit = Queries.MustBool module Spec = struct include Analyses.IdentitySpec let name () = "threadJoins" - module D = MustTIDs + + (* The first component is the set of must-joined TIDs, the second component tracks whether all TIDs recorded in MustTIDs have been exited cleanly, *) + (* i.e., all created subthreads have also been joined. This is helpful as there is no set of all transitively created threads available. *) + module D = Lattice.Prod(MustTIDs)(CleanExit) module C = D - module G = MustTIDs + module G = D module V = struct include TID @@ -21,22 +28,25 @@ struct end (* transfer functions *) + let threadreturn ctx = + match ctx.ask CurrentThreadId with + | `Lifted tid -> + let (j,joined_clean) = ctx.local in + (* the current thread has been exited cleanly if all joined threads where exited cleanly, and all created threads are joined *) + let created = ctx.ask Queries.CreatedThreads in + let clean = TIDs.subset created j in + ctx.sideg tid (j, joined_clean && clean) + | _ -> () (* correct? *) + + let return ctx (exp:exp option) (f:fundec) : D.t = - ( - match ctx.ask CurrentThreadId with - | `Lifted tid when ThreadReturn.is_current (Analyses.ask_of_ctx ctx) -> ctx.sideg tid ctx.local - | _ -> () (* correct? *) - ); + if ThreadReturn.is_current (Analyses.ask_of_ctx ctx) then threadreturn ctx; ctx.local let special ctx (lval: lval option) (f:varinfo) (arglist:exp list) : D.t = let desc = LibraryFunctions.find f in match desc.special arglist, f.vname with - | ThreadExit _, _ -> (match ctx.ask CurrentThreadId with - | `Lifted tid -> ctx.sideg tid ctx.local - | _ -> () (* correct? *) - ); - ctx.local + | ThreadExit _, _ -> threadreturn ctx; ctx.local | ThreadJoin { thread = id; ret_var }, _ -> let threads = ctx.ask (Queries.EvalThread id) in if TIDs.is_top threads then @@ -46,9 +56,10 @@ struct let threads = TIDs.elements threads in match threads with | [tid] when TID.is_unique tid-> - let joined = ctx.global tid in - D.union (D.add tid ctx.local) joined - | _ -> ctx.local (* if multiple possible thread ids are joined, none of them is must joined*) + let (local_joined, local_clean) = ctx.local in + let (other_joined, other_clean) = ctx.global tid in + (MustTIDs.union (MustTIDs.add tid local_joined) other_joined, local_clean && other_clean) + | _ -> ctx.local (* if multiple possible thread ids are joined, none of them is must joined *) (* Possible improvement: Do the intersection first, things that are must joined in all possibly joined threads are must-joined *) ) | Unknown, "__goblint_assume_join" -> @@ -56,21 +67,21 @@ struct let threads = ctx.ask (Queries.EvalThread id) in if TIDs.is_top threads then ( M.info ~category:Unsound "Unknown thread ID assume-joined, assuming ALL threads must-joined."; - D.bot () (* consider everything joined, D is reversed so bot is All threads *) + (MustTIDs.bot(), true) (* consider everything joined, MustTIDs is reversed so bot is All threads *) ) else ( (* elements throws if the thread set is top *) let threads = TIDs.elements threads in if List.compare_length_with threads 1 > 0 then M.info ~category:Unsound "Ambiguous thread ID assume-joined, assuming all of those threads must-joined."; - List.fold_left (fun acc tid -> - let joined = ctx.global tid in - D.union (D.add tid acc) joined - ) ctx.local threads + List.fold_left (fun (joined, clean) tid -> + let (other_joined, other_clean) = ctx.global tid in + (MustTIDs.union (MustTIDs.add tid joined) other_joined, clean && other_clean) + ) (ctx.local) threads ) | _, _ -> 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 *) @@ -78,20 +89,24 @@ struct else match ThreadId.get_current (Analyses.ask_of_ctx fctx) with | `Lifted tid -> - D.remove tid ctx.local + let (j, clean) = ctx.local in + (MustTIDs.remove tid j, clean) | _ -> ctx.local let query ctx (type a) (q: a Queries.t): a Queries.result = match q with - | Queries.MustJoinedThreads -> (ctx.local:ConcDomain.MustThreadSet.t) (* type annotation needed to avoid "would escape the scope of its equation" *) + | Queries.MustJoinedThreads -> (fst ctx.local:ConcDomain.MustThreadSet.t) (* type annotation needed to avoid "would escape the scope of its equation" *) + | Queries.ThreadsJoinedCleanly -> (snd ctx.local:bool) | _ -> Queries.Result.top q - let combine ctx (lval:lval option) fexp (f:fundec) (args:exp list) fc au = - D.union ctx.local au + let combine_env ctx lval fexp f args fc au f_ask = + let (caller_joined, local_clean) = ctx.local in + let (callee_joined, callee_clean) = au in + (MustTIDs.union caller_joined callee_joined, local_clean && callee_clean) - let startstate v = D.top () - let exitstate v = D.top () + let startstate v = (MustTIDs.empty (), true) + let exitstate v = (MustTIDs.empty (), true) end let _ = MCP.register_analysis ~dep:["threadid"] (module Spec : MCPSpec) diff --git a/src/analyses/threadReturn.ml b/src/analyses/threadReturn.ml index e43cae4818..0aed06851a 100644 --- a/src/analyses/threadReturn.ml +++ b/src/analyses/threadReturn.ml @@ -1,6 +1,6 @@ -(** Thread returning analysis. *) +(** Thread returning analysis which abstracts a thread's call stack by a boolean, indicating whether it is at the topmost call stack frame or not ([threadreturn]). *) -open Prelude.Ana +open GoblintCil open Analyses let is_current (ask: Queries.ask): bool = @@ -9,37 +9,26 @@ let is_current (ask: Queries.ask): bool = module Spec : Analyses.MCPSpec = struct - include Analyses.DefaultSpec + include Analyses.IdentitySpec let name () = "threadreturn" module D = IntDomain.Booleans module C = D (* transfer functions *) - let assign ctx (lval:lval) (rval:exp) : D.t = - ctx.local - - let branch ctx (exp:exp) (tv:bool) : D.t = - ctx.local - - let body ctx (f:fundec) : D.t = - ctx.local - - let return ctx (exp:exp option) (f:fundec) : D.t = - ctx.local let enter ctx (lval: lval option) (f:fundec) (args:exp list) : (D.t * D.t) list = - [ctx.local, false] - - let combine ctx (lval:lval option) fexp (f:fundec) (args:exp list) fc (au:D.t) : D.t = - ctx.local + if !AnalysisState.global_initialization then + (* We are inside enter_with inside a startfun, and thus the current function retruning is the main function *) + [ctx.local, true] + else + [ctx.local, false] - let special ctx (lval: lval option) (f:varinfo) (arglist:exp list) : D.t = - ctx.local + let combine_env ctx lval fexp f args fc au f_ask = + ctx.local (* keep local as opposed to IdentitySpec *) let startstate v = true - let threadenter ctx lval f args = [true] - let threadspawn ctx lval f args fctx = ctx.local + 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/constants.ml b/src/analyses/tutorials/constants.ml index 7e0bc9ea0b..e1d341e993 100644 --- a/src/analyses/tutorials/constants.ml +++ b/src/analyses/tutorials/constants.ml @@ -1,5 +1,6 @@ +(** Simple intraprocedural integer constants analysis example ([constants]). *) -open Prelude.Ana +open GoblintCil open Analyses (** An analysis specification for didactic purposes. @@ -63,7 +64,7 @@ struct let body ctx (f:fundec) : D.t = (* Initialize locals to top *) - List.fold (fun m l -> D.add l (I.top ()) m) ctx.local f.slocals + List.fold_left (fun m l -> D.add l (I.top ()) m) ctx.local f.slocals let return ctx (exp:exp option) (f:fundec) : D.t = (* Do nothing, as we are not interested in return values for now. *) @@ -71,7 +72,7 @@ struct let enter ctx (lval: lval option) (f:fundec) (args:exp list) : (D.t * D.t) list = (* Set the formal int arguments to top *) - let callee_state = List.fold (fun m l -> D.add l (I.top ()) m) (D.bot ()) f.sformals in + let callee_state = List.fold_left (fun m l -> D.add l (I.top ()) m) (D.bot ()) f.sformals in [(ctx.local, callee_state)] let set_local_int_lval_top (state: D.t) (lval: lval option) = @@ -83,7 +84,10 @@ struct ) |_ -> state - let combine ctx (lval:lval option) fexp (f:fundec) (args:exp list) fc (au:D.t) : D.t = + let combine_env ctx lval fexp f args fc au f_ask = + ctx.local (* keep local as opposed to IdentitySpec *) + + let combine_assign ctx (lval:lval option) fexp (f:fundec) (args:exp list) fc (au:D.t) (f_ask: Queries.ask): D.t = (* If we have a function call with assignment x = f (e1, ... , ek) with a local int variable x on the left, we set it to top *) diff --git a/src/analyses/tutorials/signs.ml b/src/analyses/tutorials/signs.ml index f28db6663b..31168df86a 100644 --- a/src/analyses/tutorials/signs.ml +++ b/src/analyses/tutorials/signs.ml @@ -1,12 +1,13 @@ -(** An analysis specification for didactic purposes. *) +(** Simple intraprocedural integer signs analysis template ([signs]). -open Prelude.Ana + @see *) + +open GoblintCil open Analyses -open Cilint module Signs = struct - include Printable.Std + include Printable.StdLeaf type t = Neg | Zero | Pos [@@deriving eq, ord, hash, to_yojson] let name () = "signs" @@ -22,14 +23,13 @@ struct (* TODO: An attempt to abstract integers, but it's just a little wrong... *) let of_int i = - if compare_cilint i zero_cilint < 0 then Zero - else if compare_cilint i zero_cilint > 0 then Zero + if Z.compare i Z.zero < 0 then Zero + else if Z.compare i Z.zero > 0 then Zero else Zero let lt x y = match x, y with | Neg, Pos | Neg, Zero -> true (* TODO: Maybe something missing? *) | _ -> false - end (* Now we turn this into a lattice by adding Top and Bottom elements. diff --git a/src/analyses/tutorials/taint.ml b/src/analyses/tutorials/taint.ml index d5c7360fa0..a978d0faf4 100644 --- a/src/analyses/tutorials/taint.ml +++ b/src/analyses/tutorials/taint.ml @@ -1,10 +1,12 @@ +(** Simple interprocedural taint analysis template ([taint]). *) + (** An analysis specification for didactic purposes. *) -(* Helpful link on CIL: https://goblint.in.tum.de/assets/goblint-cil/ *) (* Goblint documentation: https://goblint.readthedocs.io/en/latest/ *) +(* Helpful link on CIL: https://goblint.github.io/cil/ *) (* You may test your analysis on our toy examples by running `ruby scripts/update_suite.rb group tutorials` *) (* after removing the `SKIP` from the beginning of the tests in tests/regression/99-tutorials/{03-taint_simple.c,04-taint_inter.c} *) -open Prelude.Ana +open GoblintCil open Analyses module VarinfoSet = SetDomain.Make(CilType.Varinfo) @@ -85,7 +87,7 @@ struct (** For a function call "lval = f(args)" or "f(args)", [enter] returns a caller state, and the initial state of the callee. - In [enter], the caller state can usually be returned unchanged, as [combine] (below) + In [enter], the caller state can usually be returned unchanged, as [combine_env] and [combine_assign] (below) will compute the caller state after the function call, given the return state of the callee. *) let enter ctx (lval: lval option) (f:fundec) (args:exp list) : (D.t * D.t) list = let caller_state = ctx.local in @@ -102,9 +104,16 @@ struct [caller_state, callee_state] (** For a function call "lval = f(args)" or "f(args)", - computes the state of the caller after the call. + computes the global environment state of the caller after the call. + Argument [callee_local] is the state of [f] at its return node. *) + let combine_env ctx (lval:lval option) fexp (f:fundec) (args:exp list) fc (callee_local:D.t) (f_ask: Queries.ask): D.t = + (* Nothing needs to be done *) + ctx.local + + (** For a function call "lval = f(args)" or "f(args)", + computes the state of the caller after assigning the return value from the call. Argument [callee_local] is the state of [f] at its return node. *) - let combine ctx (lval:lval option) fexp (f:fundec) (args:exp list) fc (callee_local:D.t) : D.t = + let combine_assign 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 (* TODO: Record whether lval was tainted. *) caller_state @@ -120,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 86eaeadf7e..dc377cdd97 100644 --- a/src/analyses/tutorials/unitAnalysis.ml +++ b/src/analyses/tutorials/unitAnalysis.ml @@ -1,6 +1,6 @@ -(** An analysis specification for didactic purposes. *) +(** Simplest possible analysis with unit domain ([unit]). *) -open Prelude.Ana +open GoblintCil open Analyses (* module Spec : Analyses.MCPSpec with module D = Lattice.Unit and module C = Lattice.Unit and type marshal = unit = *) @@ -29,15 +29,18 @@ struct let enter ctx (lval: lval option) (f:fundec) (args:exp list) : (D.t * D.t) list = [ctx.local, ctx.local] - let combine ctx (lval:lval option) fexp (f:fundec) (args:exp list) fc (au:D.t) : D.t = + let combine_env ctx lval fexp f args fc au f_ask = au + let combine_assign ctx (lval:lval option) fexp (f:fundec) (args:exp list) fc (au:D.t) (f_ask: Queries.ask) : D.t = + ctx.local + let special ctx (lval: lval option) (f:varinfo) (arglist:exp list) : D.t = 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 a64cd24a8c..43707acd1e 100644 --- a/src/analyses/unassumeAnalysis.ml +++ b/src/analyses/unassumeAnalysis.ml @@ -1,6 +1,7 @@ -(** Unassume analysis. +(** Unassume analysis ([unassume]). Emits unassume events for other analyses based on YAML witness invariants. *) + open Analyses module Cil = GoblintCil.Cil @@ -23,7 +24,6 @@ struct let exitstate _ = D.empty () let context _ _ = () - let should_join _ _ = false module Locator = WitnessUtil.Locator (Node) @@ -55,7 +55,8 @@ struct let rec iter_node node = if not (NH.mem reachable node) then begin NH.replace reachable node (); - (* TODO: filter synthetic like in Validator *) + (* TODO: filter synthetic? + See YamlWitness. *) if WitnessInvariant.is_invariant_node node then Locator.add locator (Node.location node) node; if WitnessUtil.NH.mem WitnessInvariant.loop_heads node then @@ -110,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} @@ -156,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 @@ -229,8 +230,8 @@ struct match es with | x :: xs -> let e = List.fold_left (fun a {exp = b; _} -> Cil.(BinOp (LAnd, a, b, intType))) x.exp xs in - (* M.info ~category:Witness "unassume invariant: %a" CilType.Exp.pretty e; *) - if not !Goblintutil.postsolving then ( + M.info ~category:Witness "unassume invariant: %a" CilType.Exp.pretty e; + if not !AnalysisState.postsolving then ( if not (GobConfig.get_bool "ana.unassume.precheck" && Queries.ID.to_bool (ctx.ask (EvalInt e)) = Some false) then ( let uuids = x.uuid :: List.map (fun {uuid; _} -> uuid) xs in ctx.emit (Unassume {exp = e; uuids}); @@ -273,7 +274,10 @@ struct let enter ctx lv f args = [(ctx.local, D.empty ())] - let combine ctx lv fe f args fc fd = + let combine_env ctx lval fexp f args fc au f_ask = + ctx.local (* not here because isn't final transfer function on edge *) + + let combine_assign ctx lv fe f args fc fd f_ask = emit_unassume ctx (* not in sync, query, entry, threadenter because they aren't final transfer function on edge *) diff --git a/src/analyses/uninit.ml b/src/analyses/uninit.ml index e376ef7e34..8693599a4d 100644 --- a/src/analyses/uninit.ml +++ b/src/analyses/uninit.ml @@ -1,11 +1,11 @@ -(** Local variable initialization analysis. *) +(** Analysis of initialized local variables ([uninit]). *) module M = Messages module AD = ValueDomain.AD module IdxDom = ValueDomain.IndexDomain module Offs = ValueDomain.Offs -open Prelude.Ana +open GoblintCil open Analyses module Spec = @@ -16,6 +16,7 @@ struct module D = ValueDomain.AddrSetDomain module C = ValueDomain.AddrSetDomain + module P = IdentityP (D) type trans_in = D.t type trans_out = D.t @@ -23,27 +24,20 @@ struct let name () = "uninit" - let should_join x y = D.equal x y - 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 () - (* NB! Currently we care only about concrete indexes. Base (seeing only a int domain - element) answers with the string "unknown" on all non-concrete cases. *) - let rec conv_offset x = - match x with - | `NoOffset -> `NoOffset - | `Index (Const (CInt (i,ik,s)),o) -> `Index (IntDomain.of_const (i,ik,s), conv_offset o) - | `Index (_,o) -> `Index (IdxDom.top (), conv_offset o) - | `Field (f,o) -> `Field (f, conv_offset o) - 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, Base.Offs.from_offset (conv_offset 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"; [] @@ -91,57 +85,41 @@ struct let f vs (v,o,_) = (v,o) :: vs in List.fold_left f [] (access_one_byval a false rval) - let vars a (rval:exp) : Addr.t list = - List.map Addr.from_var_offset (varoffs a rval) - - let is_prefix_of (v1,ofs1: varinfo * (Addr.field,Addr.idx) Lval.offs) (v2,ofs2: varinfo * (Addr.field,Addr.idx) Lval.offs) : bool = - let rec is_offs_prefix_of pr os = - match (pr, os) with - | (`NoOffset, _) -> true - | (`Field (f1, o1), `Field (f2,o2)) -> CilType.Fieldinfo.equal f1 f2 && is_offs_prefix_of o1 o2 - | (_, _) -> false - in - CilType.Varinfo.equal v1 v2 && is_offs_prefix_of ofs1 ofs2 - + let is_prefix_of m1 m2 = Option.is_some (Addr.Mval.prefix m1 m2) (* Does it contain non-initialized variables? *) let is_expr_initd a (expr:exp) (st:D.t) : bool = - let variables = vars a expr in - let raw_vars = List.filter_map Addr.to_var_offset variables in - let will_addr_init (t:bool) a = + let mvals = varoffs a expr in + let will_mval_init (t:bool) mval = let f addr = - GobOption.exists (is_prefix_of a) (Addr.to_var_offset addr) + GobOption.exists (is_prefix_of mval) (Addr.to_mval addr) in - if D.exists f st then begin - M.error ~category:M.Category.Behavior.Undefined.uninitialized ~tags:[CWE 457] "Uninitialized variable %a accessed." Addr.pretty (Addr.from_var_offset a); + if D.exists f st then ( + M.error ~category:M.Category.Behavior.Undefined.uninitialized ~tags:[CWE 457] "Uninitialized variable %a accessed." Addr.Mval.pretty mval; false - end else - t in - List.fold_left will_addr_init true raw_vars + ) + else + t + in + List.fold_left will_mval_init true mvals - let remove_if_prefix (pr: varinfo * (Addr.field,Addr.idx) Lval.offs) (uis: D.t) : D.t = + let remove_if_prefix (pr: Addr.Mval.t) (uis: D.t) : D.t = let f ad = - let vals = Addr.to_var_offset ad in + let vals = Addr.to_mval ad in GobOption.for_all (fun a -> not (is_prefix_of pr a)) vals in D.filter f uis - type lval_offs = (Addr.field,Addr.idx) Lval.offs - type var_offs = varinfo * lval_offs + type lval_offs = Addr.Offs.t + type var_offs = Addr.Mval.t (* Call to [get_pfx v cx] returns initialized prefixes ... *) let rec get_pfx (v:varinfo) (cx:lval_offs) (ofs:lval_offs) (target:typ) (other:typ) : var_offs list = - let rec cat o i = - match o with - | `NoOffset -> i - | `Field (f, o) -> `Field (f, cat o i) - | `Index (v, o) -> `Index (v, cat o i) - in let rec rev lo = match lo with | `NoOffset -> `NoOffset - | `Field (f, o) -> cat (rev o) (`Field (f, `NoOffset)) - | `Index (v, o) -> cat (rev o) (`Index (v, `NoOffset)) + | `Field (f, o) -> Addr.Offs.add_offset (rev o) (`Field (f, `NoOffset)) + | `Index (v, o) -> Addr.Offs.add_offset (rev o) (`Index (v, `NoOffset)) in let rec bothstruct (t:fieldinfo list) (tf:fieldinfo) (o:fieldinfo list) (no:lval_offs) : var_offs list = match t, o with @@ -152,7 +130,7 @@ struct | x::xs, y::ys -> [] (* found a mismatch *) | _ -> - M.info ~category:Unsound "Failed to analyze union at point %a -- did not find %s" Addr.pretty (Addr.from_var_offset (v,rev cx)) tf.fname; + M.info ~category:Unsound "Failed to analyze union at point %a -- did not find %s" Addr.pretty (Addr.of_mval (v,rev cx)) tf.fname; [] in let utar, uoth = unrollType target, unrollType other in @@ -180,7 +158,7 @@ struct (* step into all other fields *) List.concat (List.rev_map (fun oth_f -> get_pfx v (`Field (oth_f, cx)) ofs utar oth_f.ftype) c2.cfields) | _ -> - M.info ~category:Unsound "Failed to analyze union at point %a" Addr.pretty (Addr.from_var_offset (v,rev cx)); + M.info ~category:Unsound "Failed to analyze union at point %a" Addr.pretty (Addr.of_mval (v,rev cx)); [] @@ -190,45 +168,50 @@ 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 (conv_offset 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 let to_addrs (v:varinfo) : Addr.t list = let make_offs = List.fold_left (fun o f -> `Field (f, o)) `NoOffset in - let rec add_fields (base: Addr.field list) fs acc = + let rec add_fields (base: fieldinfo list) fs acc = match fs with | [] -> acc | f :: fs -> match unrollType f.ftype with | TComp ({cfields=ffs; _},_) -> add_fields base fs (List.rev_append (add_fields (f::base) ffs []) acc) - | _ -> add_fields base fs ((Addr.from_var_offset (v,make_offs (f::base))) :: acc) + | _ -> add_fields base fs ((Addr.of_mval (v,make_offs (f::base))) :: acc) in match unrollType v.vtype with | TComp ({cfields=fs; _},_) -> add_fields [] fs [] - | _ -> [Addr.from_var v] + | _ -> [Addr.of_var v] 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.from_var_offset (v,(conv_offset 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 @@ -261,13 +244,15 @@ struct let nst = remove_unreachable (Analyses.ask_of_ctx ctx) args ctx.local in [ctx.local, nst] - let combine ctx (lval:lval option) fexp (f:fundec) (args:exp list) fc (au:D.t) : trans_out = + let combine_env ctx lval fexp f args fc au f_ask = ignore (List.map (fun x -> is_expr_initd (Analyses.ask_of_ctx ctx) x ctx.local) args); let cal_st = remove_unreachable (Analyses.ask_of_ctx ctx) args ctx.local in - let ret_st = D.union au (D.diff ctx.local cal_st) in + D.union au (D.diff ctx.local cal_st) + + let combine_assign ctx (lval:lval option) fexp (f:fundec) (args:exp list) fc (au:D.t) (f_ask: Queries.ask) : trans_out = match lval with - | None -> ret_st - | Some lv -> init_lval (Analyses.ask_of_ctx ctx) lv ret_st + | None -> ctx.local + | Some lv -> init_lval (Analyses.ask_of_ctx ctx) lv ctx.local let special ctx (lval: lval option) (f:varinfo) (arglist:exp list) : D.t = diff --git a/src/analyses/useAfterFree.ml b/src/analyses/useAfterFree.ml new file mode 100644 index 0000000000..a901a8d2e5 --- /dev/null +++ b/src/analyses/useAfterFree.ml @@ -0,0 +1,246 @@ +(** An analysis for the detection of use-after-free vulnerabilities ([useAfterFree]). *) + +open GoblintCil +open Analyses +open MessageCategory +open AnalysisStateUtil + +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.IdentitySpec + + let name () = "useAfterFree" + + module D = StackAndHeapVars + module C = Lattice.Unit + module G = ThreadIdToJoinedThreadsMap + module V = VarinfoV + + let context _ _ = () + + + (* HELPER FUNCTIONS *) + + let get_current_threadid ctx = + ctx.ask Queries.CurrentThreadId + + 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 }) || G.is_empty freeing_threads then () + else begin + let possibly_started current tid joined_threads = + match tid with + | `Lifted tid -> + 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 + (* 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 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 = 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 = 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 -> + 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_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 (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 + 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_implicitly_derefed = false) ?(is_double_free = false) (transfer_fn_name:string) ctx (exp:exp) = + match exp with + (* Base recursion cases *) + | Const _ + | SizeOf _ + | SizeOfStr _ + | AlignOf _ + | AddrOfLabel _ -> () + (* Non-base cases *) + | Real e + | Imag e + | SizeOfE e + | AlignOfE e + | UnOp (_, 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_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_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_implicitly_derefed ~is_double_free transfer_fn_name ctx lval + + 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 *) + + let assign ctx (lval:lval) (rval:exp) : D.t = + warn_lval_might_contain_freed "assign" ctx lval; + warn_exp_might_contain_freed "assign" ctx rval; + ctx.local + + let branch ctx (exp:exp) (tv:bool) : D.t = + warn_exp_might_contain_freed "branch" ctx exp; + 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 + + 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; + (* 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_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; + 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 + 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 + | ad when not (Queries.AD.is_top ad) -> + let pointed_to_heap_vars = + 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) (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 startstate v = D.bot () + let exitstate v = D.top () + +end + +let _ = + MCP.register_analysis (module Spec : MCPSpec) diff --git a/src/analyses/varEq.ml b/src/analyses/varEq.ml index 82008bf564..30b36404af 100644 --- a/src/analyses/varEq.ml +++ b/src/analyses/varEq.ml @@ -1,11 +1,11 @@ -(** Variable equalities necessary for per-element patterns. *) +(** Symbolic expression equalities analysis ([var_eq]). *) module Addr = ValueDomain.Addr module Offs = ValueDomain.Offs module AD = ValueDomain.AD module Exp = CilType.Exp module LF = LibraryFunctions -open Prelude.Ana +open GoblintCil open Analyses @@ -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? *) @@ -55,13 +55,13 @@ struct method! vexpr e = if Cilfacade.isFloatType (Cilfacade.typeOf e) then - raise Exit; + raise Stdlib.Exit; DoChildren end in match Cil.visitCilExpr visitor e with | _ -> false - | exception Exit -> true + | exception Stdlib.Exit -> true let exp_equal e1 e2 = CilType.Exp.equal e1 e2 && not (contains_float_subexp e1) @@ -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 @@ -369,7 +378,7 @@ struct | Lval rlval -> begin match ask (Queries.MayPointTo (mkAddrOf rlval)) with | rv when not (Queries.LS.is_top rv) && Queries.LS.cardinal rv = 1 -> - let rv = Lval.CilLval.to_exp (Queries.LS.choose rv) in + let rv = Mval.Exp.to_cil_exp (Queries.LS.choose rv) in if is_local lv && Exp.is_global_var rv = Some false then D.add_eq (rv,Lval lv) st else st @@ -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. *) @@ -429,24 +432,43 @@ struct | true -> raise Analyses.Deadcode | false -> [ctx.local,nst] - let combine ctx lval fexp f args fc st2 = + let combine_env ctx lval fexp f args fc au (f_ask: Queries.ask) = + let tainted = f_ask.f Queries.MayBeTainted in + 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.AD.is_top tainted || not (ctx.ask (Queries.MustBeSingleThreaded {since_start = true})) then + D.top () + else + 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 match D.is_bot ctx.local with | true -> raise Analyses.Deadcode - | false -> - match lval with - | Some lval -> remove (Analyses.ask_of_ctx ctx) lval st2 - | None -> st2 + | false -> d + + let combine_assign ctx lval fexp f args fc st2 (f_ask : Queries.ask) = + match lval with + | Some lval -> remove (Analyses.ask_of_ctx ctx) lval ctx.local + | 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 (Lval.CilLval.to_lval 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 @@ -476,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 new file mode 100644 index 0000000000..665612aa99 --- /dev/null +++ b/src/analyses/vla.ml @@ -0,0 +1,41 @@ +(** Analysis of variable-length arrays (VLAs) in scope ([vla]). *) + +open GoblintCil +open Analyses + +module Spec = +struct + include Analyses.IdentitySpec + + let name () = "vla" + module D = BoolDomain.MayBool + module C = Lattice.Unit + + let context _ _ = () + + let enter ctx (lval: lval option) (f:fundec) (args:exp list) : (D.t * D.t) list = + [ctx.local, false] + + let combine_env ctx lval fexp f args fc au f_ask = + ctx.local (* keep local as opposed to IdentitySpec *) + + let special ctx (lval: lval option) (f:varinfo) (arglist:exp list) : D.t = + match (LibraryFunctions.find f).special arglist with + | Setjmp _ -> + (* Checking if this within the scope of an identifier of variably modified type *) + if ctx.local then + M.warn ~category:(Behavior (Undefined Other)) "setjmp called within the scope of a variably modified type. If a call to longjmp is made after this scope is left, the behavior is undefined."; + ctx.local + | _ -> + ctx.local + + let vdecl ctx (v:varinfo) : D.t = + ctx.local || Cilfacade.isVLAType v.vtype + + let startstate v = D.bot () + let threadenter ctx ~multiple lval f args = [D.top ()] + let exitstate v = D.top () +end + +let _ = + MCP.register_analysis (module Spec : MCPSpec) diff --git a/src/analyses/wrapperFunctionAnalysis.ml b/src/analyses/wrapperFunctionAnalysis.ml new file mode 100644 index 0000000000..2e068329ea --- /dev/null +++ b/src/analyses/wrapperFunctionAnalysis.ml @@ -0,0 +1,207 @@ +(** Family of analyses which provide symbolic locations for special library functions. + Provides symbolic heap locations for dynamic memory allocations and symbolic thread + identifiers for thread creation ([mallocWrapper], [threadCreateWrapper]). + + Provided heap locations are based on the node and thread ID. + Provided thread identifiers are based solely the node. + Considers wrapper functions and a number of unique heap locations + or thread identifiers for additional precision. *) + +open GoblintCil +open Analyses +open GobConfig +open ThreadIdDomain +module Q = Queries + +include WrapperFunctionAnalysis0 + +(* Functor argument for determining wrapper and wrapped functions *) +module type WrapperArgs = sig + val wrappers : unit -> string list + val is_wrapped : LibraryDesc.special -> bool +end + +(* The main analysis, generic to which functions are being wrapped. *) +module SpecBase (UniqueCount : Lattice.S with type t = int) (WrapperArgs : WrapperArgs) = +struct + include IdentitySpec + + (* Use the previous CFG node (ctx.prev_node) for identifying calls to (wrapper) functions. + For one, this is the node that typically contains the call as its statement. + Additionally, it distinguishes two calls that share the next CFG node (ctx.node), e.g.: + if (cond) { x = malloc(1); } else { x = malloc(2); } + 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 = + 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 = + let open UniqueCallCounter in + let unique_call = `Lifted node in + let count = find unique_call counter in + if UniqueCount.is_top count then counter + else remove unique_call counter |> add unique_call (count + 1) + + module D = Lattice.Prod (NodeFlatLattice) (UniqueCallCounter) + module C = D + + let wrappers = Hashtbl.create 13 + + (* transfer functions *) + + let enter ctx (lval: lval option) (f:fundec) (args:exp list) : (D.t * D.t) list = + let wrapper_node, counter = ctx.local in + let new_wrapper_node = + if Hashtbl.mem wrappers f.svar.vname then + match wrapper_node with + (* if an interesting callee is called by an interesting caller, then we remember the caller context *) + | `Lifted _ -> wrapper_node + (* if an interesting callee is called by an uninteresting caller, then we remember the callee context *) + | _ -> `Lifted (node_for_ctx ctx) + else + NodeFlatLattice.top () (* if an uninteresting callee is called, then we forget what was called before *) + in + let callee = (new_wrapper_node, counter) in + [(ctx.local, callee)] + + let combine_env ctx lval fexp f args fc (_, counter) f_ask = + (* Keep (potentially higher) counter from callee and keep wrapper node from caller *) + let lnode, _ = ctx.local in + (lnode, counter) + + let add_unique_call_ctx ctx = + let wrapper_node, counter = ctx.local in + wrapper_node, + (* track the unique ID per call to the wrapper function, not to the wrapped function *) + add_unique_call counter + (match wrapper_node with `Lifted node -> node | _ -> node_for_ctx ctx) + + let special (ctx: (D.t, G.t, C.t, V.t) ctx) (lval: lval option) (f: varinfo) (arglist:exp list) : D.t = + let desc = LibraryFunctions.find f in + if WrapperArgs.is_wrapped @@ desc.special arglist then add_unique_call_ctx ctx else ctx.local + + let startstate v = D.bot () + + let threadenter ctx ~multiple lval f args = + (* The new thread receives a fresh counter *) + [D.bot ()] + + let exitstate v = D.top () + + type marshal = unit + + let init (_ : marshal option) = + List.iter (fun wrapper -> Hashtbl.replace wrappers wrapper ()) (WrapperArgs.wrappers ()) + +end + + +module MallocWrapper : MCPSpec = struct + + include SpecBase + (MallocUniqueCount) + (struct + let wrappers () = get_string_list "ana.malloc.wrappers" + + let is_wrapped = function + | LibraryDesc.(Malloc _ | Calloc _ | Realloc _) -> true + | _ -> false + end) + + module ThreadNode = struct + include Printable.Prod3 (ThreadIdDomain.ThreadLifted) (Node) (UniqueCount) + + (* Description that gets appended to the varinfo-name in user output. *) + let describe_varinfo (v: varinfo) (t, node, c) = + let loc = UpdateCil.getLoc node in + CilType.Location.show loc + + let name_varinfo (t, node, c) = + Format.asprintf "(alloc@sid:%s@tid:%s(#%s))" (Node.show_id node) (ThreadLifted.show t) (UniqueCount.show c) + + end + + module NodeVarinfoMap = RichVarinfo.BiVarinfoMap.Make(ThreadNode) + + let name () = "mallocWrapper" + + 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.AllocVar {on_stack = on_stack} -> + let node = match wrapper_node with + | `Lifted wrapper_node -> wrapper_node + | _ -> node_for_ctx ctx + in + 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 + | Some (_, _, c) -> UniqueCount.is_top c || not (ctx.ask Q.MustBeUniqueThread) + | None -> false + end + | _ -> Queries.Result.top q + + type marshal = NodeVarinfoMap.marshal + + let init marshal = + (* call init from SpecBase *) + init None; + NodeVarinfoMap.unmarshal marshal + + let finalize () = + NodeVarinfoMap.marshal () +end + + +module ThreadCreateWrapper : MCPSpec = struct + + include SpecBase + (ThreadCreateUniqueCount) + (struct + let wrappers () = get_string_list "ana.thread.wrappers" + + let is_wrapped = function + | LibraryDesc.ThreadCreate _ -> true + | _ -> false + + end) + + let name () = "threadCreateWrapper" + + let query (ctx: (D.t, G.t, C.t, V.t) ctx) (type a) (q: a Q.t): a Q.result = + match q with + | Q.ThreadCreateIndexedNode -> + let wrapper_node, counter = ctx.local in + let node = match wrapper_node with + | `Lifted wrapper_node -> wrapper_node + | _ -> node_for_ctx ctx + in + let count = UniqueCallCounter.find (`Lifted node) counter in + `Lifted node, count + | _ -> Queries.Result.top q + +end + +let _ = List.iter MCP.register_analysis [(module MallocWrapper); (module ThreadCreateWrapper)] diff --git a/src/analyses/wrapperFunctionAnalysis0.ml b/src/analyses/wrapperFunctionAnalysis0.ml new file mode 100644 index 0000000000..9ea9c0c9aa --- /dev/null +++ b/src/analyses/wrapperFunctionAnalysis0.ml @@ -0,0 +1,42 @@ +(** Part of the wrapper function analysis. Separate out the modules for counting + unique calls: Chain alone is a functor, yet we need the resulting module to + define queries over it. Since the wrapper function analysis also references + those queries, we would have a circular dependency otherwise. *) + +open GobConfig + +(* Functor argument for creating the chain lattice of unique calls *) +module type UniqueCountArgs = sig + val unique_count : unit -> int + val label : string +end + +module MakeUniqueCount (UniqueCountArgs : UniqueCountArgs) : Lattice.S with type t = int = + Lattice.Chain (struct + let n () = + let p = UniqueCountArgs.unique_count () in + if p < 0 then + failwith @@ UniqueCountArgs.label ^ " has to be non-negative" + else p + 1 (* Unique addresses + top address *) + + let names x = if x = (n () - 1) then "top" else Format.asprintf "%d" x + + end) + +(* Create the chain argument-module, given the config key to loop up *) +let unique_count_args_from_config key = (module struct + let unique_count () = get_int key + let label = "Option " ^ key +end : UniqueCountArgs) + +module MallocUniqueCount = + MakeUniqueCount (val unique_count_args_from_config "ana.malloc.unique_address_count") + +module ThreadCreateUniqueCount = + MakeUniqueCount (val unique_count_args_from_config "ana.thread.unique_thread_id_count") + +(* since the query also references NodeFlatLattice, it also needs to reside here *) +module NodeFlatLattice = Lattice.Flat (Node) (struct + let top_name = "Unknown node" + let bot_name = "Unreachable node" + end) diff --git a/src/autoTune.ml b/src/autoTune.ml index 67b4c80f36..7ddc1aee43 100644 --- a/src/autoTune.ml +++ b/src/autoTune.ml @@ -1,3 +1,5 @@ +(** Autotuning of the configuration based on syntactic heuristics. *) + open GobConfig open GoblintCil open AutoTune0 @@ -25,30 +27,44 @@ class collectFunctionCallsVisitor(callSet, calledBy, argLists, fd) = object | _ -> DoChildren end -class functionVisitor(calling, calledBy, argLists) = object +class functionVisitor(calling, calledBy, argLists, dynamicallyCalled) = object inherit nopCilVisitor + method! vglob = function + | GVarDecl (vinfo,_) -> + if vinfo.vaddrof && isFunctionType vinfo.vtype then dynamicallyCalled := FunctionSet.add vinfo !dynamicallyCalled; + DoChildren + | _ -> DoChildren + method! vfunc fd = let callSet = ref FunctionSet.empty in let callVisitor = new collectFunctionCallsVisitor (callSet, calledBy, argLists, fd.svar) in ignore @@ Cil.visitCilFunction callVisitor fd; calling := FunctionCallMap.add fd.svar !callSet !calling; - SkipChildren + DoChildren end +type functionCallMaps = { + calling: FunctionSet.t FunctionCallMap.t; + calledBy: (FunctionSet.t * int) FunctionCallMap.t; + argLists: Cil.exp list FunctionCallMap.t; + dynamicallyCalled: FunctionSet.t; +} + let functionCallMaps = ResettableLazy.from_fun (fun () -> let calling = ref FunctionCallMap.empty in let calledBy = ref FunctionCallMap.empty in let argLists = ref FunctionCallMap.empty in - let thisVisitor = new functionVisitor(calling,calledBy, argLists) in + let dynamicallyCalled = ref FunctionSet.empty in + let thisVisitor = new functionVisitor(calling,calledBy, argLists, dynamicallyCalled) in visitCilFileSameGlobals thisVisitor (!Cilfacade.current_file); - !calling, !calledBy, !argLists) + {calling = !calling; calledBy = !calledBy; argLists = !argLists; dynamicallyCalled= !dynamicallyCalled}) (* Only considers static calls!*) -let calledFunctions fd = ResettableLazy.force functionCallMaps |> fun (x,_,_) -> x |> FunctionCallMap.find_opt fd |> Option.value ~default:FunctionSet.empty -let callingFunctions fd = ResettableLazy.force functionCallMaps |> fun (_,x,_) -> x |> FunctionCallMap.find_opt fd |> Option.value ~default:(FunctionSet.empty, 0) |> fst -let timesCalled fd = ResettableLazy.force functionCallMaps |> fun (_,x,_) -> x |> FunctionCallMap.find_opt fd |> Option.value ~default:(FunctionSet.empty, 0) |> snd -let functionArgs fd = ResettableLazy.force functionCallMaps |> fun (_,_,x) -> x |> FunctionCallMap.find_opt fd +let calledFunctions fd = (ResettableLazy.force functionCallMaps).calling |> FunctionCallMap.find_opt fd |> Option.value ~default:FunctionSet.empty +let callingFunctions fd = (ResettableLazy.force functionCallMaps).calledBy |> FunctionCallMap.find_opt fd |> Option.value ~default:(FunctionSet.empty, 0) |> fst +let timesCalled fd = (ResettableLazy.force functionCallMaps).calledBy |> FunctionCallMap.find_opt fd |> Option.value ~default:(FunctionSet.empty, 0) |> snd +let functionArgs fd = (ResettableLazy.force functionCallMaps).argLists |> FunctionCallMap.find_opt fd let findMallocWrappers () = let isMalloc f = @@ -64,8 +80,7 @@ let findMallocWrappers () = else false in - ResettableLazy.force functionCallMaps - |> (fun (x,_,_) -> x) + (ResettableLazy.force functionCallMaps).calling |> FunctionCallMap.filter (fun _ allCalled -> FunctionSet.exists isMalloc allCalled) |> FunctionCallMap.filter (fun f _ -> timesCalled f > 10) |> FunctionCallMap.bindings @@ -126,14 +141,44 @@ let addModAttributes file = let disableIntervalContextsInRecursiveFunctions () = - ResettableLazy.force functionCallMaps |> fun (x,_,_) -> x |> FunctionCallMap.iter (fun f set -> + (ResettableLazy.force functionCallMaps).calling |> FunctionCallMap.iter (fun f set -> (*detect direct recursion and recursion with one indirection*) if FunctionSet.mem f set || (not @@ FunctionSet.disjoint (calledFunctions f) (callingFunctions f)) then ( - print_endline ("function " ^ (f.vname) ^" is recursive, disable interval context"); - f.vattr <- addAttributes (f.vattr) [Attr ("goblint_context",[AStr "base.no-interval"; AStr "relation.no-context"])]; + print_endline ("function " ^ (f.vname) ^" is recursive, disable interval and interval_set contexts"); + f.vattr <- addAttributes (f.vattr) [Attr ("goblint_context",[AStr "base.no-interval"; AStr "base.no-interval_set"; AStr "relation.no-context"])]; ) ) +let hasFunction pred = + let relevant_static var = + if LibraryFunctions.is_special var then + let desc = LibraryFunctions.find var in + GobOption.exists (fun args -> pred (desc.special args)) (functionArgs var) + else + false + in + let relevant_dynamic var = + if LibraryFunctions.is_special var then + let desc = LibraryFunctions.find var in + (* We don't really have arguments at hand, so we cheat and just feed it a list of MyCFG.unknown_exp of appropriate length *) + match unrollType var.vtype with + | TFun (_, args, _, _) -> + let args = BatOption.map_default (List.map (fun (x,_,_) -> MyCFG.unknown_exp)) [] args in + pred (desc.special args) + | _ -> false + else + false + in + let calls = ResettableLazy.force functionCallMaps in + calls.calledBy |> FunctionCallMap.exists (fun var _ -> relevant_static var) || + calls.dynamicallyCalled |> FunctionSet.exists relevant_dynamic + +let disableAnalyses anas = + List.iter (GobConfig.set_auto "ana.activated[-]") anas + +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*) (*escape is also still enabled, because otherwise we get a warning*) @@ -141,43 +186,70 @@ let disableIntervalContextsInRecursiveFunctions () = let notNeccessaryThreadAnalyses = ["race"; "deadlock"; "maylocks"; "symb_locks"; "thread"; "threadid"; "threadJoins"; "threadreturn"] let reduceThreadAnalyses () = - let hasThreadCreate () = - ResettableLazy.force functionCallMaps - |> (fun (_,x,_) -> x) (*every function that is called*) - |> FunctionCallMap.exists (fun var (callers,_) -> - if LibraryFunctions.is_special var then ( - let desc = LibraryFunctions.find var in - match functionArgs var with - | None -> false; - | Some args -> - match desc.special args with - | ThreadCreate _ -> - print_endline @@ "thread created by " ^ var.vname ^ ", called by:"; - FunctionSet.iter ( fun c -> print_endline @@ " " ^ c.vname) callers; - true - | _ -> false - ) - else - false - ) + let isThreadCreate = function + | LibraryDesc.ThreadCreate _ -> true + | _ -> false in - if not @@ hasThreadCreate () then ( - print_endline @@ "no thread creation -> disabeling thread analyses \"" ^ (String.concat ", " notNeccessaryThreadAnalyses) ^ "\""; - let disableAnalysis = GobConfig.set_auto "ana.activated[-]" in - List.iter disableAnalysis notNeccessaryThreadAnalyses; + let hasThreadCreate = hasFunction isThreadCreate in + if not @@ hasThreadCreate then ( + print_endline @@ "no thread creation -> disabling thread analyses \"" ^ (String.concat ", " notNeccessaryThreadAnalyses) ^ "\""; + disableAnalyses notNeccessaryThreadAnalyses; + ) +(* This is run independent of the autotuner being enabled or not to be sound in the presence of setjmp/longjmp *) +(* It is done this way around to allow enabling some of these analyses also for programs without longjmp *) +let longjmpAnalyses = ["activeLongjmp"; "activeSetjmp"; "taintPartialContexts"; "modifiedSinceLongjmp"; "poisonVariables"; "expsplit"; "vla"] + +let activateLongjmpAnalysesWhenRequired () = + let isLongjmp = function + | LibraryDesc.Longjmp _ -> true + | _ -> false + in + if hasFunction isLongjmp then ( + print_endline @@ "longjmp -> enabling longjmp analyses \"" ^ (String.concat ", " longjmpAnalyses) ^ "\""; + 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 focusOnSpecification (spec: Svcomp.Specification.t) = + match spec with | UnreachCall s -> () | NoDataRace -> (*enable all thread analyses*) - print_endline @@ "Specification: NoDataRace -> enabeling thread analyses \"" ^ (String.concat ", " notNeccessaryThreadAnalyses) ^ "\""; - let enableAnalysis = GobConfig.set_auto "ana.activated[+]" in - List.iter enableAnalysis notNeccessaryThreadAnalyses; + print_endline @@ "Specification: NoDataRace -> enabling thread analyses \"" ^ (String.concat ", " notNeccessaryThreadAnalyses) ^ "\""; + enableAnalyses notNeccessaryThreadAnalyses; | 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 @@ -243,9 +315,11 @@ let isComparison = function | Lt | Gt | Le | Ge | Ne | Eq -> true | _ -> false +let isGoblintStub v = List.exists (fun (Attr(s,_)) -> s = "goblint_stub") v.vattr + let rec extractVar = function | UnOp (Neg, e, _) -> extractVar e - | Lval ((Var info),_) -> Some info + | Lval ((Var info),_) when not (isGoblintStub info) -> Some info | _ -> None let extractOctagonVars = function @@ -283,7 +357,7 @@ class octagonVariableVisitor(varMap, globals) = object handle varMap 5 globals (extractOctagonVars e2) ; DoChildren ) - | Lval ((Var info),_) -> handle varMap 1 globals (Some (`Right info)) ; SkipChildren + | Lval ((Var info),_) when not (isGoblintStub info) -> handle varMap 1 globals (Some (`Right info)) ; SkipChildren (*Traverse down only operations fitting for linear equations*) | UnOp (Neg, _,_) | BinOp (PlusA,_,_,_) @@ -333,9 +407,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 = 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..c1de250263 100644 --- a/src/build-info/dune +++ b/src/build-info/dune @@ -8,4 +8,22 @@ (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}\""))) + 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 7cfffd3dcd..5981caf9ea 100644 --- a/src/cdomains/addressDomain.ml +++ b/src/cdomains/addressDomain.ml @@ -1,31 +1,240 @@ +include AddressDomain_intf + open GoblintCil open IntOps -module GU = Goblintutil module M = Messages +module Mval_outer = Mval + + +module AddressBase (Mval: Printable.S) = +struct + include Printable.StdLeaf + type t = + | Addr of Mval.t + | NullPtr + | UnknownPtr + | StrPtr of string option + [@@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)" + | UnknownPtr -> "?" + | NullPtr -> "NULL" + + include Printable.SimpleShow ( + struct + type nonrec t = t + let show = show + end + ) + + (* strings *) + let of_string x = StrPtr (Some x) + let to_string = function + | StrPtr (Some x) -> Some x + | _ -> 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 + | _ -> 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 + let to_string_length x = + match to_c_string x with + | Some x -> Some (String.length x) + | _ -> None + + let arbitrary () = QCheck.always UnknownPtr (* S TODO: non-unknown *) +end + +module AddressPrintable (Mval: Mval.Printable) = +struct + include AddressBase (Mval) + + let of_var x = Addr (x, `NoOffset) + let of_mval (x, o) = Addr (x, o) + + let to_var = function + | Addr (x,_) -> Some x + | _ -> None + let to_var_may = function + | Addr (x,_) -> Some x + | _ -> None + let to_var_must = function + | Addr (x,`NoOffset) -> Some x + | _ -> None + let to_mval = function + | Addr (x, o) -> Some (x, o) + | _ -> None + + let type_of = function + | Addr (x, o) -> Mval.type_of (x, o) + | StrPtr _ -> charPtrType (* TODO Cil.charConstPtrType? *) + | NullPtr -> voidType + | UnknownPtr -> voidPtrType + + (* 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.") + | NullPtr -> integer 0 + | UnknownPtr -> raise Lattice.TopValue + (* TODO: unused *) + let add_offset x o = match x with + | Addr m -> Addr (Mval.add_offset m o) + | x -> x + + + let is_definite = function + | NullPtr -> true + | Addr m -> Mval.is_definite m + | _ -> false +end + +module AddressLattice (Mval: Mval.Lattice) = +struct + include AddressPrintable (Mval) + + 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 + | NullPtr, NullPtr -> Some true + | UnknownPtr, UnknownPtr + | UnknownPtr, Addr _ + | Addr _, UnknownPtr + | UnknownPtr, StrPtr _ + | StrPtr _, UnknownPtr -> None + | _, _ -> Some false + + let leq x y = match x, y with + | StrPtr _, StrPtr None -> true + | StrPtr a, StrPtr b -> a = b + | Addr x, Addr y -> Mval.leq x y + | _ -> x = y + + let top_indices = function + | 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 + | NullPtr , NullPtr -> NullPtr + | StrPtr a, StrPtr b -> StrPtr (sop a b) + | 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 -module type S = -sig - include Lattice.S - type idx - type field - - val from_var: varinfo -> t - val from_var_offset: (varinfo * (idx,field) Lval.offs) -> t - val to_var_offset: t -> (varinfo * (idx,field) Lval.offs) list - val to_var_may: t -> varinfo list - val to_var_must: t -> varinfo list - val get_type: t -> typ + include Lattice.NoBotTop + + let pretty_diff () (x,y) = Pretty.dprintf "%s: %a not leq %a" (name ()) pretty x pretty y +end + +module AddressLatticeRepr (Mval: Mval.Lattice) = +struct + include AddressLattice (Mval) + + module VariableRepr: DisjointDomain.Representative with type elt = t = + struct + type elt = t + + include AddressBase (Basetype.Variables) + + 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 *) + | NullPtr -> NullPtr + | UnknownPtr -> UnknownPtr + end + + module UnitOffsetRepr: DisjointDomain.Representative with type elt = t = + struct + type elt = t + + (* Offset module for representative without abstract values for index offsets, i.e. with unit index offsets. + Reason: The offset in the representative (used for buckets) should not depend on the integer domains, + since different integer domains may be active at different program points. *) + include AddressPrintable (Mval_outer.Unit) + + 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 *) + | NullPtr -> NullPtr + | UnknownPtr -> UnknownPtr + end end -module AddressSet (Idx: IntDomain.Z) = +module AddressSet (Mval: Mval.Lattice) (ID: IntDomain.Z) = struct - module Addr = Lval.NormalLatRepr (Idx) - module J = SetDomain.Joined (Addr) + module Addr = AddressLatticeRepr (Mval) + module J = + struct + include SetDomain.Joined (Addr) + let may_be_equal a b = Option.value (Addr.semantic_equal a b) ~default:true + end + module OffsetSplit = DisjointDomain.ProjectiveSetPairwiseMeet (Addr) (J) (Addr.UnitOffsetRepr) + (* module H = HoareDomain.SetEM (Addr) *) (* Hoare set for bucket doesn't play well with StrPtr limiting: https://github.com/goblint/analyzer/pull/808 *) - include DisjointDomain.ProjectiveSet (Addr) (J) (Addr.R) + module AddressSet: SetDomain.S with type elt = Addr.t = DisjointDomain.ProjectiveSet (Addr) (OffsetSplit) (Addr.VariableRepr) + include AddressSet + + let name () = Format.sprintf "address set (%s)" (Mval.name ()) (* short-circuit with physical equality, makes a difference at long-scale: https://github.com/goblint/analyzer/pull/809#issuecomment-1206174751 *) @@ -47,23 +256,19 @@ struct if M.tracing then M.traceu "ad" "-> %B\n" r; r - type field = Addr.field - type idx = Idx.t - type offs = [`NoOffset | `Field of (field * offs) | `Index of (idx * offs)] - let null_ptr = singleton Addr.NullPtr let unknown_ptr = singleton Addr.UnknownPtr let not_null = unknown_ptr - let top_ptr = of_list Addr.([UnknownPtr; NullPtr]) - let may_be_unknown x = exists (fun e -> e = Addr.UnknownPtr) x + let top_ptr = of_list Addr.[UnknownPtr; NullPtr] + let is_element a x = cardinal x = 1 && Addr.equal (choose x) a - let is_null x = is_element Addr.NullPtr x - let is_not_null x = for_all (fun e -> e <> Addr.NullPtr) x - let may_be_null x = exists (fun e -> e = Addr.NullPtr) x + let is_null x = is_element Addr.NullPtr x + let may_be_null x = mem Addr.NullPtr x + let is_not_null x = not (may_be_null x) + let may_be_unknown x = mem Addr.UnknownPtr x let to_bool x = if is_null x then Some false else if is_not_null x then Some true else None - let has_unknown x = mem Addr.UnknownPtr x - let of_int (type a) (module ID : IntDomain.Z with type t = a) i = + let of_int i = match ID.to_int i with | x when GobOption.exists BigIntOps.(equal (zero)) x -> null_ptr | x when GobOption.exists BigIntOps.(equal (one)) x -> not_null @@ -71,7 +276,7 @@ struct | Some (xs, _) when List.exists BigIntOps.(equal (zero)) xs -> not_null | _ -> top_ptr - let to_int (type a) (module ID : IntDomain.Z with type t = a) x = + let to_int x = let ik = Cilfacade.ptr_ikind () in if equal x null_ptr then ID.of_int ik Z.zero @@ -80,24 +285,95 @@ struct else ID.top_of ik - let get_type xs = - try Addr.get_type (choose xs) + let type_of xs = + try Addr.type_of (choose xs) with (* WTF? Returns TVoid when it is unknown and stuff??? *) | _ -> voidType - let from_var x = singleton (Addr.from_var x) - let from_var_offset x = singleton (Addr.from_var_offset x) + let of_var x = singleton (Addr.of_var x) + let of_mval x = singleton (Addr.of_mval x) let to_var_may x = List.filter_map Addr.to_var_may (elements x) let to_var_must x = List.filter_map Addr.to_var_must (elements x) - let to_var_offset x = List.filter_map Addr.to_var_offset (elements x) - let is_definite x = match elements x with - | [x] when Addr.is_definite x -> true - | _ -> false + let to_mval x = List.filter_map Addr.to_mval (elements x) + let is_definite x = cardinal x = 1 && Addr.is_definite (choose x) (* strings *) - let from_string x = singleton (Addr.from_string x) + let of_string x = singleton (Addr.of_string x) + let to_string x = List.filter_map Addr.to_string (elements x) + let to_string_length x = + let transform elem = + match Addr.to_string_length elem with + | Some x -> ID.of_int !Cil.kindOfSizeOf (Z.of_int x) + | None -> ID.top_of !Cil.kindOfSizeOf in + (* maps any StrPtr to the length of its content, otherwise maps to top *) + List.map transform (elements x) + (* and returns the least upper bound of computed IntDomain values *) + |> List.fold_left ID.join (ID.bot_of !Cil.kindOfSizeOf) + + let substring_extraction haystack needle = + (* map all StrPtr elements in input address sets to contained strings *) + let haystack' = List.map Addr.to_c_string (elements haystack) in + let needle' = List.map Addr.to_c_string (elements needle) in + + (* helper functions *) + let extract_lval_string = function + | Some s -> of_string s + | None -> null_ptr in + let compute_substring s1 s2 = + try + let i = Str.search_forward (Str.regexp_string s2) s1 0 in + Some (String.sub s1 i (String.length s1 - i)) + with Not_found -> None in + + (* if any of the input address sets contains an element that isn't a StrPtr, return top *) + if List.mem None haystack' || List.mem None needle' then + top_ptr + else + (* else try to find the first occurrence of all strings in needle' in all strings s of haystack', + collect s starting from that occurrence or if there is none, collect a NULL pointer, + and return the least upper bound *) + BatList.cartesian_product haystack' needle' + |> List.map (fun (s1, s2) -> extract_lval_string (compute_substring (Option.get s1) (Option.get s2))) + |> List.fold_left join (bot ()) + + let string_comparison x y n = + let f = match n with + | Some num -> Addr.to_n_c_string num + | None -> Addr.to_c_string in + + (* map all StrPtr elements in input address sets to contained strings / n-substrings *) + let x' = List.map f (elements x) in + let y' = List.map f (elements y) in + + (* helper functions *) + let compare s1 s2 = + let res = String.compare s1 s2 in + if res = 0 then + ID.of_int IInt Z.zero + else if res > 0 then + ID.starting IInt Z.one + else + ID.ending IInt Z.minus_one in + + (* if any of the input address sets contains an element that isn't a StrPtr, return top *) + if List.mem None x' || List.mem None y' then + ID.top_of IInt + else + (* else compare every string of x' with every string of y' and return the least upper bound *) + BatList.cartesian_product x' y' + |> List.map (fun (s1, s2) -> compare (Option.get s1) (Option.get s2)) + |> List.fold_left ID.join (ID.bot_of IInt) + + let string_writing_defined dest = + (* if the destination address set contains a StrPtr, writing to such a string literal is undefined behavior *) + if List.exists Option.is_some (List.map Addr.to_c_string (elements dest)) then + (M.warn ~category:M.Category.Behavior.Undefined.other "May write to a string literal, which leads to a segmentation fault in most cases"; + false) + else + true + (* add an & in front of real addresses *) module ShortAddr = struct @@ -136,8 +412,8 @@ struct | false, false -> join x y *) - (* TODO: overrides is_top, but not top? *) - let is_top a = mem Addr.UnknownPtr a + let is_top = may_be_unknown + let top () = top_ptr let merge uop cop x y = let no_null x y = @@ -164,4 +440,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.mli b/src/cdomains/addressDomain.mli new file mode 100644 index 0000000000..6f6a9a1001 --- /dev/null +++ b/src/cdomains/addressDomain.mli @@ -0,0 +1,3 @@ +(** Domains for addresses/pointers. *) + +include AddressDomain_intf.AddressDomain (** @inline *) diff --git a/src/cdomains/addressDomain_intf.ml b/src/cdomains/addressDomain_intf.ml new file mode 100644 index 0000000000..0ef3d6dd8d --- /dev/null +++ b/src/cdomains/addressDomain_intf.ml @@ -0,0 +1,180 @@ +module type AddressDomain = +sig + + module AddressBase (Mval: Printable.S): + sig + type t = + | 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 *) + include Printable.S with type t := t (** @closed *) + + val of_string: string -> t + (** Convert string to {!StrPtr}. *) + + 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. *) + + val to_n_c_string: int -> t -> string option + (** Convert {!StrPtr} to C string of given maximum length if possible. *) + + val to_string_length: t -> int option + (** Find length of C string if possible. *) + end + + module AddressPrintable (Mval: Mval.Printable): + sig + include module type of AddressBase (Mval) + + val is_definite: t -> bool + (** Whether address is a [NULL] pointer or an mvalue that has only definite integer indexing (and fields). *) + + val add_offset: t -> Mval.idx Offset.t -> t + (** [add_offset a o] appends [o] to an mvalue address [a]. *) + + val of_var: GoblintCil.varinfo -> t + (** Convert from variable (without offset). *) + + val of_mval: Mval.t -> t + (** Convert from mvalue. *) + + val to_var: t -> GoblintCil.varinfo option + (** Convert to variable if possible. *) + + val to_var_may: t -> GoblintCil.varinfo option + (** Convert to variable with any offset if possible. *) + + val to_var_must: t -> GoblintCil.varinfo option + (** Convert to variable without offset if possible. *) + + val to_mval: t -> Mval.t option + (** Convert to mvalue if possible. *) + + val to_exp: t -> GoblintCil.exp + (** Convert to CIL expression. *) + + val type_of: t -> GoblintCil.typ + (** Type of address. *) + end + + (** Address lattice. + + Actually a disjoint union of lattices without top or bottom. + Addresses are grouped as follows: + + - 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. *) + module AddressLattice (Mval: Mval.Lattice): + sig + include module type of AddressPrintable (Mval) + include Lattice.S with type t := t (** @closed *) + + val top_indices: t -> t + (** Change all indices to top indices. *) + + val semantic_equal: t -> t -> bool option + (** Check semantic equality of two addresses. + + @return [Some true] if definitely equal, [Some false] if definitely not equal, [None] if unknown. *) + end + + (** Address lattice with sublattice representatives for {!DisjointDomain}. *) + module AddressLatticeRepr (Mval: Mval.Lattice): + sig + include module type of AddressLattice (Mval) (** @closed *) + + module VariableRepr: DisjointDomain.Representative with type elt = t + (** Representative without mvalue offsets. *) + + module UnitOffsetRepr: DisjointDomain.Representative with type elt = t + (** Representative without mvalue offset indices. *) + end + + (** Address set lattice. + + @param Mval mvalue used in addresses. + @param ID integers used for conversions. *) + module AddressSet (Mval: Mval.Lattice) (ID: IntDomain.Z): + sig + module Addr: module type of AddressLattice (Mval) + include SetDomain.S with type elt = Addr.t (** @closed *) + + val null_ptr: t + (** Address set containing only the [NULL] pointer. *) + + val unknown_ptr: t + (** Address set containing the unknown pointer, which is non-[NULL]. *) + + val not_null: t + (** Address set containing the unknown pointer, which is non-[NULL]. *) + + val top_ptr: t + (** Address set containing any pointer, [NULL] or not. *) + + val is_null: t -> bool + (** Whether address set contains only the [NULL] pointer. *) + + val is_not_null: t -> bool + (** Whether address set does not contain the [NULL] pointer. *) + + val may_be_null: t -> bool + (** Whether address set contains the [NULL] pointer. *) + + val may_be_unknown: t -> bool + (** Whether address set contains the unknown pointer. *) + + val is_definite: t -> bool + (** Whether address set is a single [NULL] pointer or mvalue that has only definite integer indexing (and fields). *) + + val is_element: Addr.t -> t -> bool + (** Whether address set contains only the given address. *) + + val of_var: GoblintCil.varinfo -> t + (** Convert from variable (without offset). *) + + val of_mval: Mval.t -> t + (** Convert from mvalue. *) + + val of_int: ID.t -> t + (** Convert from integer. *) + + val to_var_may: t -> GoblintCil.varinfo list + (** Convert to variables with any offset. *) + + val to_var_must: t -> GoblintCil.varinfo list + (** Convert to variables without offset. *) + + val to_mval: t -> Mval.t list + (** Convert to mvalues. *) + + val to_int: t -> ID.t + (** Convert to integer. *) + + val to_bool: t -> bool option + (** Convert to boolean if possible. *) + + val type_of: t -> GoblintCil.typ + (** Type of address set. *) + + val of_string: string -> t + (** Convert from string literal. *) + + val to_string: t -> string list + (** Convert to string literals. *) + + val to_string_length: t -> ID.t + (** Find length of C string. *) + + val substring_extraction: t -> t -> t + val string_comparison: t -> t -> int option -> ID.t + val string_writing_defined: t -> bool + end +end diff --git a/src/cdomains/apron/affineEqualityDomain.apron.ml b/src/cdomains/apron/affineEqualityDomain.apron.ml index 7b5305c899..a6f00fdba0 100644 --- a/src/cdomains/apron/affineEqualityDomain.apron.ml +++ b/src/cdomains/apron/affineEqualityDomain.apron.ml @@ -1,8 +1,14 @@ +(** OCaml implementation of the affine equalities domain. + + @see Karr, M. Affine relationships among variables of a program. *) + (** Abstract states in the newly added domain are represented by structs containing a matrix and an apron environment. Matrices are modeled as proposed by Karr: Each variable is assigned to a column and each row represents a linear affine relationship that must hold at the corresponding program point. The apron environment is hereby used to organize the order of columns and variables. *) -open Prelude.Ana +open Batteries +open GoblintCil +open Pretty module M = Messages open Apron open VectorMatrix @@ -73,12 +79,14 @@ struct let change_d t new_env add del = timing_wrap "dimension change" (change_d t new_env add) del let add_vars t vars = + let t = copy t in let env' = 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 change_d t env' false del @@ -105,12 +113,14 @@ struct t.env <- t'.env let keep_filter t f = + let t = copy t in let env' = 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 change_d t env' false false @@ -209,6 +219,7 @@ end module D(Vc: AbstractVector) (Mx: AbstractMatrix) = struct + include Printable.Std include ConvenienceOps (Mpqf) include VarManagement (Vc) (Mx) @@ -218,8 +229,6 @@ struct type var = V.t - let tag t = failwith "No tag" - let show t = let conv_to_ints row = let module BI = IntOps.BigIntOps in @@ -268,8 +277,6 @@ struct let to_yojson _ = failwith "ToDo Implement in future" - let arbitrary () = failwith "no arbitrary" - let is_bot t = equal t (bot ()) diff --git a/src/cdomains/apron/apronDomain.apron.ml b/src/cdomains/apron/apronDomain.apron.ml index 64ee0e3237..7dffafe967 100644 --- a/src/cdomains/apron/apronDomain.apron.ml +++ b/src/cdomains/apron/apronDomain.apron.ml @@ -1,4 +1,6 @@ -open Prelude +(** {!Apron} domains. *) + +open Batteries open GoblintCil open Pretty (* A binding to a selection of Apron-Domains *) @@ -463,6 +465,8 @@ end module DBase (Man: Manager): SPrintable with type t = Man.mt A.t = struct + include Printable.StdLeaf + type t = Man.mt A.t let name () = "Apron " ^ Man.name () @@ -473,11 +477,7 @@ struct let is_top_env = A.is_top Man.mgr let is_bot_env = A.is_bottom Man.mgr - let to_yojson x = failwith "TODO implement to_yojson" let invariant _ = [] - let tag _ = failwith "Std: no tag" - let arbitrary () = failwith "no arbitrary" - let relift x = x let show (x:t) = Format.asprintf "%a (env: %a)" A.print x (Environment.print: Format.formatter -> Environment.t -> unit) (A.env x) @@ -494,6 +494,20 @@ struct Stdlib.compare x y let printXml f x = BatPrintf.fprintf f "\n\n\nconstraints\n\n\n%s\n\nenv\n\n\n%s\n\n\n" (XmlUtil.escape (Format.asprintf "%a" A.print x)) (XmlUtil.escape (Format.asprintf "%a" (Environment.print: Format.formatter -> Environment.t -> unit) (A.env x))) + let 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)) + in + let env = `String (Format.asprintf "%a" (Environment.print: Format.formatter -> Environment.t -> unit) (A.env x)) + in + `Assoc [ + ("constraints", `List constraints); + ("env", env); + ] + let unify x y = A.unify Man.mgr x y @@ -679,16 +693,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 diff --git a/src/cdomains/apron/relationDomain.apron.ml b/src/cdomains/apron/relationDomain.apron.ml index d0abeeffb9..c5b6a0a89b 100644 --- a/src/cdomains/apron/relationDomain.apron.ml +++ b/src/cdomains/apron/relationDomain.apron.ml @@ -1,6 +1,8 @@ -(** Interfaces/implementations that generalize the apronDomain and affineEqualityDomain. *) +(** Signatures for relational value domains. -open Prelude + See {!ApronDomain} and {!AffineEqualityDomain}. *) + +open Batteries open GoblintCil (** Abstracts the extended apron Var. *) @@ -163,6 +165,8 @@ struct include Printable.Std open Pretty + let relift {rel; priv} = {rel = RD.relift rel; priv = PrivD.relift priv} + let show r = let first = RD.show r.rel in let third = PrivD.show r.priv in @@ -176,7 +180,7 @@ struct ++ text ")" let printXml f r = - BatPrintf.fprintf f "\n\n\n%s\n\n%a\n%s\n\n%a\n\n" (Goblintutil.escape (RD.name ())) RD.printXml r.rel (Goblintutil.escape (PrivD.name ())) PrivD.printXml r.priv + BatPrintf.fprintf f "\n\n\n%s\n\n%a\n%s\n\n%a\n\n" (XmlUtil.escape (RD.name ())) RD.printXml r.rel (XmlUtil.escape (PrivD.name ())) PrivD.printXml r.priv let name () = RD.name () ^ " * " ^ PrivD.name () diff --git a/src/cdomains/apron/sharedFunctions.apron.ml b/src/cdomains/apron/sharedFunctions.apron.ml index cdfe11838c..059a7f8264 100644 --- a/src/cdomains/apron/sharedFunctions.apron.ml +++ b/src/cdomains/apron/sharedFunctions.apron.ml @@ -1,7 +1,7 @@ -(** Functions and modules that are shared among the original apronDomain and the new affineEqualityDomain. *) +(** Relational value domain utilities. *) open GoblintCil -open Prelude +open Batteries open Apron module M = Messages diff --git a/src/cdomains/arrayDomain.ml b/src/cdomains/arrayDomain.ml index 381ae772e2..2f91e47663 100644 --- a/src/cdomains/arrayDomain.ml +++ b/src/cdomains/arrayDomain.ml @@ -5,8 +5,8 @@ open FlagHelper module M = Messages module A = Array -module Q = Queries module BI = IntOps.BigIntOps +module VDQ = ValueDomainQueries type domain = TrivialDomain | PartitionedDomain | UnrolledDomain @@ -45,12 +45,14 @@ sig type idx type value - val get: ?checkBounds:bool -> Q.ask -> t -> Basetype.CilExp.t option * idx -> value - val set: Q.ask -> t -> Basetype.CilExp.t option * idx -> value -> t + val domain_of_t: t -> domain + + val get: ?checkBounds:bool -> VDQ.t -> t -> Basetype.CilExp.t option * idx -> value + val set: VDQ.t -> t -> Basetype.CilExp.t option * idx -> value -> t val make: ?varAttr:attributes -> ?typAttr:attributes -> idx -> value -> t val length: t -> idx option - val move_if_affected: ?replace_with_const:bool -> Q.ask -> t -> Cil.varinfo -> (Cil.exp -> int option) -> t + val move_if_affected: ?replace_with_const:bool -> VDQ.t -> t -> Cil.varinfo -> (Cil.exp -> int option) -> t val get_vars_in_e: t -> Cil.varinfo list val map: (value -> value) -> t -> t val fold_left: ('a -> value -> 'a) -> 'a -> t -> 'a @@ -58,7 +60,8 @@ sig val smart_widen: (exp -> BI.t option) -> (exp -> BI.t option) -> t -> t -> t val smart_leq: (exp -> BI.t option) -> (exp -> BI.t option) -> t -> t -> bool val update_length: idx -> t -> t - val project: ?varAttr:attributes -> ?typAttr:attributes -> Q.ask -> t -> t + val project: ?varAttr:attributes -> ?typAttr:attributes -> VDQ.t -> t -> t + val invariant: value_invariant:(offset:Cil.offset -> lval:Cil.lval -> value -> Invariant.t) -> offset:Cil.offset -> lval:Cil.lval -> t -> Invariant.t end module type LatticeWithSmartOps = @@ -77,11 +80,18 @@ struct type idx = Idx.t type value = Val.t + let domain_of_t _ = TrivialDomain + let show x = "Array: " ^ Val.show x let pretty () x = text "Array: " ++ pretty () x let pretty_diff () (x,y) = dprintf "%s: %a not leq %a" (name ()) pretty x pretty y - let get ?(checkBounds=true) (ask: Q.ask) a i = a - let set (ask: Q.ask) a i v = join a v + let get ?(checkBounds=true) (ask: VDQ.t) a i = a + let set (ask: VDQ.t) a (ie, i) v = + match ie with + | Some ie when CilType.Exp.equal ie Offset.Index.Exp.all -> + v + | _ -> + join a v let make ?(varAttr=[]) ?(typAttr=[]) i v = v let length _ = None @@ -96,6 +106,21 @@ struct let smart_leq _ _ = leq let update_length _ x = x let project ?(varAttr=[]) ?(typAttr=[]) _ t = t + + let invariant ~value_invariant ~offset ~lval x = + match offset with + (* invariants for all indices *) + | NoOffset when get_bool "witness.invariant.goblint" -> + let i_lval = Cil.addOffsetLval (Index (Offset.Index.Exp.all, NoOffset)) lval in + value_invariant ~offset ~lval:i_lval x + | NoOffset -> + Invariant.none + (* invariant for one index *) + | Index (i, offset) -> + value_invariant ~offset ~lval x + (* invariant for one field *) + | Field (f, offset) -> + Invariant.none end let factor () = @@ -112,6 +137,9 @@ struct let name () = "unrolled arrays" type idx = Idx.t type value = Val.t + + let domain_of_t _ = UnrolledDomain + let join_of_all_parts (xl, xr) = List.fold_left Val.join xr xl let show (xl, xr) = let rec show_list xlist = match xlist with @@ -124,15 +152,15 @@ struct let extract x default = match x with | Some c -> c | None -> default - let get ?(checkBounds=true) (ask: Q.ask) (xl, xr) (_,i) = + let get ?(checkBounds=true) (ask: VDQ.t) (xl, xr) (_,i) = let search_unrolled_values min_i max_i = let rec subjoin l i = match l with | [] -> Val.bot () | hd::tl -> begin match Z.gt i max_i, Z.lt i min_i with - | false,true -> subjoin tl (Z.add i Z.one) - | false,false -> Val.join hd (subjoin tl (Z.add i Z.one)) + | false,true -> subjoin tl (Z.succ i) + | false,false -> Val.join hd (subjoin tl (Z.succ i)) | _,_ -> Val.bot () end in subjoin xl Z.zero in @@ -142,18 +170,18 @@ struct if Z.geq min_i f then xr else if Z.lt max_i f then search_unrolled_values min_i max_i else Val.join xr (search_unrolled_values min_i (Z.of_int ((factor ())-1))) - let set (ask: Q.ask) (xl,xr) (_,i) v = + let set (ask: VDQ.t) (xl,xr) (_,i) v = let update_unrolled_values min_i max_i = let rec weak_update l i = match l with | [] -> [] | hd::tl -> - if Z.lt i min_i then hd::(weak_update tl (Z.add i Z.one)) + if Z.lt i min_i then hd::(weak_update tl (Z.succ i)) else if Z.gt i max_i then (hd::tl) - else (Val.join hd v)::(weak_update tl (Z.add i Z.one)) in + else (Val.join hd v)::(weak_update tl (Z.succ i)) in let rec full_update l i = match l with | [] -> [] | hd::tl -> - if Z.lt i min_i then hd::(full_update tl (Z.add i Z.one)) + if Z.lt i min_i then hd::(full_update tl (Z.succ i)) else v::tl in if Z.equal min_i max_i then full_update xl Z.zero else weak_update xl Z.zero in @@ -163,6 +191,14 @@ struct if Z.geq min_i f then (xl, (Val.join xr v)) else if Z.lt max_i f then ((update_unrolled_values min_i max_i), xr) else ((update_unrolled_values min_i (Z.of_int ((factor ())-1))), (Val.join xr v)) + let set ask (xl, xr) (ie, i) v = + match ie with + | Some ie when CilType.Exp.equal ie Offset.Index.Exp.all -> + (* TODO: Doesn't seem to work for unassume because unrolled elements are top-initialized, not bot-initialized. *) + (BatList.make (factor ()) v, v) + | _ -> + set ask (xl, xr) (ie, i) v + let make ?(varAttr=[]) ?(typAttr=[]) _ v = let xl = BatList.make (factor ()) v in (xl,Val.bot ()) @@ -181,6 +217,32 @@ struct let smart_leq _ _ = leq let update_length _ x = x let project ?(varAttr=[]) ?(typAttr=[]) _ t = t + + let invariant ~value_invariant ~offset ~lval ((xl, xr) as x) = + match offset with + (* invariants for all indices *) + | NoOffset -> + let i_all = + if Val.is_bot xr then + Invariant.top () + else if get_bool "witness.invariant.goblint" then ( + let i_lval = Cil.addOffsetLval (Index (Offset.Index.Exp.all, NoOffset)) lval in + value_invariant ~offset ~lval:i_lval (join_of_all_parts x) + ) + else + Invariant.top () + in + BatList.fold_lefti (fun acc i x -> + let i_lval = Cil.addOffsetLval (Index (Cil.integer i, NoOffset)) lval in + let i = value_invariant ~offset ~lval:i_lval x in + Invariant.(acc && i) + ) i_all xl + (* invariant for one index *) + | Index (i, offset) -> + Invariant.none (* TODO: look up *) + (* invariant for one field *) + | Field (f, offset) -> + Invariant.none end (** Special signature so that we can use the _with_length functions from PartitionedWithLength but still match the interface * @@ -188,23 +250,25 @@ end module type SPartitioned = sig include S - val set_with_length: idx option -> Q.ask -> t -> Basetype.CilExp.t option * idx -> value -> t + val set_with_length: idx option -> VDQ.t -> t -> Basetype.CilExp.t option * idx -> value -> t val smart_join_with_length: idx option -> (exp -> BI.t option) -> (exp -> BI.t option) -> t -> t -> t val smart_widen_with_length: idx option -> (exp -> BI.t option) -> (exp -> BI.t option) -> t -> t-> t val smart_leq_with_length: idx option -> (exp -> BI.t option) -> (exp -> BI.t option) -> t -> t -> bool - val move_if_affected_with_length: ?replace_with_const:bool -> idx option -> Q.ask -> t -> Cil.varinfo -> (Cil.exp -> int option) -> t + val move_if_affected_with_length: ?replace_with_const:bool -> idx option -> VDQ.t -> t -> Cil.varinfo -> (Cil.exp -> int option) -> t end module Partitioned (Val: LatticeWithSmartOps) (Idx:IntDomain.Z): SPartitioned with type value = Val.t and type idx = Idx.t = struct + include Printable.Std + type t = Joint of Val.t | Partitioned of (CilType.Exp.t * (Val.t * Val.t * Val.t)) [@@deriving eq, ord, hash] type idx = Idx.t type value = Val.t - let name () = "partitioned array" + let domain_of_t _ = PartitionedDomain - let tag _ = failwith "Std: no tag" + let name () = "partitioned array" let relift = function | Joint v -> Joint (Val.relift v) @@ -253,8 +317,6 @@ struct if CilType.Exp.equal e e' then Partitioned (e,(Val.widen xl yl, Val.widen xm ym, Val.widen xr yr)) else Joint (Val.widen (join_of_all_parts x) (join_of_all_parts y)) - let arbitrary () = failwith "no arbitray" - let show = function | Joint x -> "Array (no part.): " ^ Val.show x | Partitioned (e,(xl, xm, xr)) -> @@ -284,21 +346,21 @@ struct ("m", Val.to_yojson xm); ("r", Val.to_yojson xr) ] - let get ?(checkBounds=true) (ask:Q.ask) (x:t) (i,_) = + let get ?(checkBounds=true) (ask:VDQ.t) (x:t) (i,_) = match x, i with | Joint v, _ -> v | Partitioned (e, (xl, xm, xr)), Some i' -> begin - if Q.must_be_equal ask e i' then xm + if VDQ.must_be_equal ask.eval_int e i' then xm else begin - let contributionLess = match Q.may_be_less ask i' e with (* (may i < e) ? xl : bot *) + let contributionLess = match VDQ.may_be_less ask.eval_int i' e with (* (may i < e) ? xl : bot *) | false -> Val.bot () | _ -> xl in - let contributionEqual = match Q.may_be_equal ask i' e with (* (may i = e) ? xm : bot *) + let contributionEqual = match VDQ.may_be_equal ask.eval_int i' e with (* (may i = e) ? xm : bot *) | false -> Val.bot () | _ -> xm in - let contributionGreater = match Q.may_be_less ask e i' with (* (may i > e) ? xr : bot *) + let contributionGreater = match VDQ.may_be_less ask.eval_int e i' with (* (may i > e) ? xr : bot *) | false -> Val.bot () | _ -> xr in Val.join (Val.join contributionLess contributionEqual) contributionGreater @@ -351,7 +413,7 @@ struct | Joint x -> f a x | Partitioned (_, (xl,xm,xr)) -> f (f (f a xl) xm) xr - let move_if_affected_with_length ?(replace_with_const=false) length (ask:Q.ask) x (v:varinfo) (movement_for_exp: exp -> int option) = + let move_if_affected_with_length ?(replace_with_const=false) length (ask:VDQ.t) x (v:varinfo) (movement_for_exp: exp -> int option) = normalize @@ let move (i:int option) (e, (xl,xm, xr)) = match i with @@ -370,8 +432,8 @@ struct let nval = join_of_all_parts x in let default = Joint nval in if replace_with_const then - let n = ask.f (Q.EvalInt e) in - match Q.ID.to_int n with + let n = ask.eval_int e in + match VDQ.ID.to_int n with | Some i -> Partitioned ((Cil.kintegerCilint (Cilfacade.ptrdiff_ikind ()) i), (xl, xm, xr)) | _ -> default @@ -393,14 +455,14 @@ struct begin match Idx.to_int l with | Some i -> - let b = Q.may_be_less ask e (Cil.kintegerCilint (Cilfacade.ptrdiff_ikind ()) i) in + let b = VDQ.may_be_less ask.eval_int e (Cil.kintegerCilint (Cilfacade.ptrdiff_ikind ()) i) in not b (* !(e <_{may} length) => e >=_{must} length *) | None -> false end | _ -> false in let e_must_less_zero = - Q.eval_int_binop (module Q.MustBool) Lt ask e Cil.zero (* TODO: untested *) + VDQ.eval_int_binop (module BoolDomain.MustBool) Lt ask.eval_int e Cil.zero (* TODO: untested *) in if e_must_bigger_max_index then (* Entire array is covered by left part, dropping partitioning. *) @@ -416,20 +478,24 @@ struct let move_if_affected ?replace_with_const = move_if_affected_with_length ?replace_with_const None - let set_with_length length (ask:Q.ask) x (i,_) a = + let set_with_length length (ask:VDQ.t) x (i,_) a = if M.tracing then M.trace "update_offset" "part array set_with_length %a %s %a\n" pretty x (BatOption.map_default Basetype.CilExp.show "None" i) Val.pretty a; - if i = Some MyCFG.all_array_index_exp then - (assert !Goblintutil.global_initialization; (* just joining with xm here assumes that all values will be set, which is guaranteed during inits *) + match i with + | Some ie when CilType.Exp.equal ie Offset.Index.Exp.all -> + (* TODO: Doesn't seem to work for unassume. *) + Joint a + | Some i when CilType.Exp.equal i Offset.Index.Exp.any -> + (assert !AnalysisState.global_initialization; (* just joining with xm here assumes that all values will be set, which is guaranteed during inits *) (* the join is needed here! see e.g 30/04 *) let o = match x with Partitioned (_, (_, xm, _)) -> xm | Joint v -> v in let r = Val.join o a in Joint r) - else + | _ -> normalize @@ let use_last = get_string "ana.base.partition-arrays.keep-expr" = "last" in let exp_value e = - let n = ask.f (Q.EvalInt e) in - Option.map BI.of_bigint (Q.ID.to_int n) + let n = ask.eval_int e in + Option.map BI.of_bigint (VDQ.ID.to_int n) in let equals_zero e = BatOption.map_default (BI.equal BI.zero) false (exp_value e) in let equals_maxIndex e = @@ -453,20 +519,20 @@ struct | _ -> Joint (Val.join v a) ) | Partitioned (e, (xl, xm, xr)) -> - let isEqual = Q.must_be_equal ask in + let isEqual = VDQ.must_be_equal ask.eval_int in match i with | Some i' when not use_last || not_allowed_for_part i' -> begin let default = let left = - match Q.may_be_less ask i' e with (* (may i < e) ? xl : bot *) (* TODO: untested *) + match VDQ.may_be_less ask.eval_int i' e with (* (may i < e) ? xl : bot *) (* TODO: untested *) | false -> xl | _ -> lubIfNotBot xl in let middle = - match Q.may_be_equal ask i' e with (* (may i = e) ? xm : bot *) + match VDQ.may_be_equal ask.eval_int i' e with (* (may i = e) ? xm : bot *) | false -> xm | _ -> Val.join xm a in let right = - match Q.may_be_less ask e i' with (* (may i > e) ? xr : bot *) (* TODO: untested *) + match VDQ.may_be_less ask.eval_int e i' with (* (may i > e) ? xr : bot *) (* TODO: untested *) | false -> xr | _ -> lubIfNotBot xr in Partitioned (e, (left, middle, right)) @@ -477,9 +543,6 @@ struct else if Cil.isConstant e && Cil.isConstant i' then match Cil.getInteger e, Cil.getInteger i' with | Some (e'': Cilint.cilint), Some i'' -> - let (i'': BI.t) = Cilint.big_int_of_cilint i'' in - let (e'': BI.t) = Cilint.big_int_of_cilint e'' in - if BI.equal i'' (BI.add e'' BI.one) then (* If both are integer constants and they are directly adjacent, we change partitioning to maintain information *) Partitioned (i', (Val.join xl xm, a, xr)) @@ -497,35 +560,35 @@ struct Partitioned (e, (xl, a, xr)) else let left = if equals_zero i' then Val.bot () else Val.join xl @@ Val.join - (match Q.may_be_equal ask e i' with (* TODO: untested *) + (match VDQ.may_be_equal ask.eval_int e i' with (* TODO: untested *) | false -> Val.bot() | _ -> xm) (* if e' may be equal to i', but e' may not be smaller than i' then we only need xm *) ( let t = Cilfacade.typeOf e in let ik = Cilfacade.get_ikind t in - match Q.must_be_equal ask (BinOp(PlusA, e, Cil.kinteger ik 1, t)) i' with + match VDQ.must_be_equal ask.eval_int (BinOp(PlusA, e, Cil.kinteger ik 1, t)) i' with | true -> xm | _ -> begin - match Q.may_be_less ask e i' with (* TODO: untested *) + match VDQ.may_be_less ask.eval_int e i' with (* TODO: untested *) | false-> Val.bot() | _ -> Val.join xm xr (* if e' may be less than i' then we also need xm for sure *) end ) in let right = if equals_maxIndex i' then Val.bot () else Val.join xr @@ Val.join - (match Q.may_be_equal ask e i' with (* TODO: untested *) + (match VDQ.may_be_equal ask.eval_int e i' with (* TODO: untested *) | false -> Val.bot() | _ -> xm) ( let t = Cilfacade.typeOf e in let ik = Cilfacade.get_ikind t in - match Q.must_be_equal ask (BinOp(PlusA, e, Cil.kinteger ik (-1), t)) i' with (* TODO: untested *) + match VDQ.must_be_equal ask.eval_int (BinOp(PlusA, e, Cil.kinteger ik (-1), t)) i' with (* TODO: untested *) | true -> xm | _ -> begin - match Q.may_be_less ask i' e with (* TODO: untested *) + match VDQ.may_be_less ask.eval_int i' e with (* TODO: untested *) | false -> Val.bot() | _ -> Val.join xl xm (* if e' may be less than i' then we also need xm for sure *) end @@ -697,6 +760,21 @@ struct let update_length _ x = x let project ?(varAttr=[]) ?(typAttr=[]) _ t = t + + let invariant ~value_invariant ~offset ~lval x = + match offset with + (* invariants for all indices *) + | NoOffset when get_bool "witness.invariant.goblint" -> + let i_lval = Cil.addOffsetLval (Index (Offset.Index.Exp.all, NoOffset)) lval in + value_invariant ~offset ~lval:i_lval (join_of_all_parts x) + | NoOffset -> + Invariant.none + (* invariant for one index *) + | Index (i, offset) -> + Invariant.none (* TODO: look up *) + (* invariant for one field *) + | Field (f, offset) -> + Invariant.none end (* This is the main array out of bounds check *) @@ -709,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 () @@ -728,10 +811,12 @@ struct type idx = Idx.t type value = Val.t - let get ?(checkBounds=true) (ask : Q.ask) (x, (l : idx)) (e, v) = + let domain_of_t _ = TrivialDomain + + let get ?(checkBounds=true) (ask : VDQ.t) (x, (l : idx)) (e, v) = if checkBounds then (array_oob_check (module Idx) (x, l) (e, v)); Base.get ask x (e, v) - let set (ask: Q.ask) (x,l) i v = Base.set ask x i v, l + let set (ask: VDQ.t) (x,l) i v = Base.set ask x i v, l let make ?(varAttr=[]) ?(typAttr=[]) l x = Base.make l x, l let length (_,l) = Some l let move_if_affected ?(replace_with_const=false) _ x _ _ = x @@ -753,6 +838,9 @@ struct let project ?(varAttr=[]) ?(typAttr=[]) _ t = t + let invariant ~value_invariant ~offset ~lval (x, _) = + Base.invariant ~value_invariant ~offset ~lval x + let printXml f (x,y) = BatPrintf.fprintf f "\n\n\n%s\n\n%a\n%s\n\n%a\n\n" (XmlUtil.escape (Base.name ())) Base.printXml x "length" Idx.printXml y @@ -767,7 +855,9 @@ struct type idx = Idx.t type value = Val.t - let get ?(checkBounds=true) (ask : Q.ask) (x, (l : idx)) (e, v) = + let domain_of_t _ = PartitionedDomain + + let get ?(checkBounds=true) (ask : VDQ.t) (x, (l : idx)) (e, v) = if checkBounds then (array_oob_check (module Idx) (x, l) (e, v)); Base.get ask x (e, v) let set ask (x,l) i v = Base.set_with_length (Some l) ask x i v, l @@ -803,6 +893,9 @@ struct let project ?(varAttr=[]) ?(typAttr=[]) _ t = t + let invariant ~value_invariant ~offset ~lval (x, _) = + Base.invariant ~value_invariant ~offset ~lval x + let printXml f (x,y) = BatPrintf.fprintf f "\n\n\n%s\n\n%a\n%s\n\n%a\n\n" (XmlUtil.escape (Base.name ())) Base.printXml x "length" Idx.printXml y @@ -816,10 +909,12 @@ struct type idx = Idx.t type value = Val.t - let get ?(checkBounds=true) (ask : Q.ask) (x, (l : idx)) (e, v) = + let domain_of_t _ = UnrolledDomain + + let get ?(checkBounds=true) (ask : VDQ.t) (x, (l : idx)) (e, v) = if checkBounds then (array_oob_check (module Idx) (x, l) (e, v)); Base.get ask x (e, v) - let set (ask: Q.ask) (x,l) i v = Base.set ask x i v, l + let set (ask: VDQ.t) (x,l) i v = Base.set ask x i v, l let make ?(varAttr=[]) ?(typAttr=[]) l x = Base.make l x, l let length (_,l) = Some l @@ -842,6 +937,9 @@ struct let project ?(varAttr=[]) ?(typAttr=[]) _ t = t + let invariant ~value_invariant ~offset ~lval (x, _) = + Base.invariant ~value_invariant ~offset ~lval x + let printXml f (x,y) = BatPrintf.fprintf f "\n\n\n%s\n\n%a\n%s\n\n%a\n\n" (XmlUtil.escape (Base.name ())) Base.printXml x "length" Idx.printXml y @@ -871,6 +969,12 @@ struct module I = struct include LatticeFlagHelper (T) (U) (K) let name () = "" end include LatticeFlagHelper (P) (I) (K) + let domain_of_t = function + | (Some p, None) -> PartitionedDomain + | (None, Some (Some t, None)) -> TrivialDomain + | (None, Some (None, Some u)) -> UnrolledDomain + | _ -> failwith "Array of invalid domain" + let binop' opp opt opu = binop opp (I.binop opt opu) let unop' opp opt opu = unop opp (I.unop opt opu) let binop_to_t' opp opt opu = binop_to_t opp (I.binop_to_t opt opu) @@ -884,12 +988,12 @@ struct else P.get ~checkBounds a x (e, i) ) (fun x -> T.get ~checkBounds a x (e,i)) (fun x -> U.get ~checkBounds a x (e,i)) x - let set (ask:Q.ask) x i a = unop_to_t' (fun x -> P.set ask x i a) (fun x -> T.set ask x i a) (fun x -> U.set ask x i a) x + let set (ask:VDQ.t) x i a = unop_to_t' (fun x -> P.set ask x i a) (fun x -> T.set ask x i a) (fun x -> U.set ask x i a) x let length = unop' P.length T.length U.length let map f = unop_to_t' (P.map f) (T.map f) (U.map f) let fold_left f s = unop' (P.fold_left f s) (T.fold_left f s) (U.fold_left f s) - let move_if_affected ?(replace_with_const=false) (ask:Q.ask) x v f = unop_to_t' (fun x -> P.move_if_affected ~replace_with_const:replace_with_const ask x v f) (fun x -> T.move_if_affected ~replace_with_const:replace_with_const ask x v f) (fun x -> U.move_if_affected ~replace_with_const:replace_with_const ask x v f) x + let move_if_affected ?(replace_with_const=false) (ask:VDQ.t) x v f = unop_to_t' (fun x -> P.move_if_affected ~replace_with_const:replace_with_const ask x v f) (fun x -> T.move_if_affected ~replace_with_const:replace_with_const ask x v f) (fun x -> U.move_if_affected ~replace_with_const:replace_with_const ask x v f) x let get_vars_in_e = unop' P.get_vars_in_e T.get_vars_in_e U.get_vars_in_e let smart_join f g = binop_to_t' (P.smart_join f g) (T.smart_join f g) (U.smart_join f g) let smart_widen f g = binop_to_t' (P.smart_widen f g) (T.smart_widen f g) (U.smart_widen f g) @@ -956,4 +1060,10 @@ struct | UnrolledDomain, (None, Some (Some x, None)) -> to_t @@ (None, None, Some (unroll_of_trivial ask x) ) | UnrolledDomain, (None, Some (None, Some x)) -> to_t @@ (None, None, Some x) | _ -> failwith "AttributeConfiguredArrayDomain received a value where not exactly one component is set" + + let invariant ~value_invariant ~offset ~lval = + unop' + (P.invariant ~value_invariant ~offset ~lval) + (T.invariant ~value_invariant ~offset ~lval) + (U.invariant ~value_invariant ~offset ~lval) end diff --git a/src/cdomains/arrayDomain.mli b/src/cdomains/arrayDomain.mli index a2fa6adcd9..ebf265ac0b 100644 --- a/src/cdomains/arrayDomain.mli +++ b/src/cdomains/arrayDomain.mli @@ -1,5 +1,8 @@ +(** Abstract domains for C arrays. *) + open IntOps open GoblintCil +module VDQ = ValueDomainQueries type domain = TrivialDomain | PartitionedDomain | UnrolledDomain @@ -19,10 +22,13 @@ sig type value (** The abstract domain of values stored in the array. *) - val get: ?checkBounds:bool -> Queries.ask -> t -> Basetype.CilExp.t option * idx -> value + val domain_of_t: t -> domain + (* Returns the domain used for the array*) + + val get: ?checkBounds:bool -> VDQ.t -> t -> Basetype.CilExp.t option * idx -> value (** Returns the element residing at the given index. *) - val set: Queries.ask -> t -> Basetype.CilExp.t option * idx -> value -> t + val set: VDQ.t -> t -> Basetype.CilExp.t option * idx -> value -> t (** Returns a new abstract value, where the given index is replaced with the * given element. *) @@ -33,7 +39,7 @@ sig val length: t -> idx option (** returns length of array if known *) - val move_if_affected: ?replace_with_const:bool -> Queries.ask -> t -> Cil.varinfo -> (Cil.exp -> int option) -> t + val move_if_affected: ?replace_with_const:bool -> VDQ.t -> t -> Cil.varinfo -> (Cil.exp -> int option) -> t (** changes the way in which the array is partitioned if this is necessitated by a change * to the variable **) @@ -52,7 +58,8 @@ sig val smart_leq: (Cil.exp -> BigIntOps.t option) -> (Cil.exp -> BigIntOps.t option) -> t -> t -> bool val update_length: idx -> t -> t - val project: ?varAttr:Cil.attributes -> ?typAttr:Cil.attributes -> Queries.ask -> t -> t + val project: ?varAttr:Cil.attributes -> ?typAttr:Cil.attributes -> VDQ.t -> t -> t + val invariant: value_invariant:(offset:Cil.offset -> lval:Cil.lval -> value -> Invariant.t) -> offset:Cil.offset -> lval:Cil.lval -> t -> Invariant.t end module type LatticeWithSmartOps = diff --git a/src/cdomains/baseDomain.ml b/src/cdomains/baseDomain.ml index 637f7cb829..ce6cc171fa 100644 --- a/src/cdomains/baseDomain.ml +++ b/src/cdomains/baseDomain.ml @@ -1,4 +1,4 @@ -(** domain of the base analysis *) +(** Full domain of {!Base} analysis. *) open GoblintCil module VD = ValueDomain.Compound @@ -6,20 +6,14 @@ module BI = IntOps.BigIntOps module CPA = struct + module M0 = MapDomain.MapBot (Basetype.Variables) (VD) module M = struct - include MapDomain.LiftTop (VD) (MapDomain.HashCached (MapDomain.MapBot (Basetype.Variables) (VD))) - let name () = "value domain" + include M0 + include MapDomain.PrintGroupable (Basetype.Variables) (VD) (M0) end - - include M -end - - -module Glob = -struct - module Var = Basetype.Variables - module Val = VD + include MapDomain.LiftTop (VD) (MapDomain.HashCached (M)) + let name () = "value domain" end (* Keeps track of which arrays are potentially partitioned according to an expression containing a specific variable *) @@ -31,7 +25,7 @@ struct let name () = "array partitioning deps" end -(** Maintains a set of local variables that need to be weakly updated, because multiple reachbale copies of them may *) +(** Maintains a set of local variables that need to be weakly updated, because multiple reachable copies of them may *) (* exist on the call stack *) module WeakUpdates = struct @@ -121,6 +115,9 @@ struct let meet = op_scheme CPA.meet PartDeps.meet WeakUpdates.meet PrivD.meet let widen = op_scheme CPA.widen PartDeps.widen WeakUpdates.widen PrivD.widen let narrow = op_scheme CPA.narrow PartDeps.narrow WeakUpdates.narrow PrivD.narrow + + let relift {cpa; deps; weak; priv} = + {cpa = CPA.relift cpa; deps = PartDeps.relift deps; weak = WeakUpdates.relift weak; priv = PrivD.relift priv} end module type ExpEvaluator = @@ -157,7 +154,7 @@ module DomWithTrivialExpEval (PrivD: Lattice.S) = DomFunctor (PrivD) (struct | Lval (Var v, NoOffset) -> begin match CPA.find v r.cpa with - | `Int i -> ValueDomain.ID.to_int i + | Int i -> ValueDomain.ID.to_int i | _ -> None end | _ -> None diff --git a/src/cdomains/cilLval.ml b/src/cdomains/cilLval.ml deleted file mode 100644 index 74118785ef..0000000000 --- a/src/cdomains/cilLval.ml +++ /dev/null @@ -1 +0,0 @@ -module Set = SetDomain.ToppedSet (CilType.Lval) (struct let topname = "All" end) diff --git a/src/cdomains/concDomain.ml b/src/cdomains/concDomain.ml index 94df15cb7a..b16cdf1d9f 100644 --- a/src/cdomains/concDomain.ml +++ b/src/cdomains/concDomain.ml @@ -1,3 +1,5 @@ +(** Domains for thread sets and their uniqueness. *) + module ThreadSet = SetDomain.ToppedSet (ThreadIdDomain.Thread) (struct let topname = "All Threads" end) module MustThreadSet = SetDomain.Reverse(ThreadSet) diff --git a/src/cdomains/deadlockDomain.ml b/src/cdomains/deadlockDomain.ml index 06fc16600c..1e62a48933 100644 --- a/src/cdomains/deadlockDomain.ml +++ b/src/cdomains/deadlockDomain.ml @@ -1,3 +1,5 @@ +(** Deadlock domain. *) + module Lock = LockDomain.Addr module LockEvent = Printable.Prod3 (Lock) (Node) (MCPAccess.A) diff --git a/src/cdomains/escapeDomain.ml b/src/cdomains/escapeDomain.ml index 4c2331d9ab..1e2769dcd7 100644 --- a/src/cdomains/escapeDomain.ml +++ b/src/cdomains/escapeDomain.ml @@ -1,3 +1,5 @@ +(** Domain for escaped thread-local variables. *) + module EscapedVars = struct include SetDomain.ToppedSet (Basetype.Variables) (struct let topname = "All Variables" end) diff --git a/src/cdomains/fileDomain.ml b/src/cdomains/fileDomain.ml index d9d21f9822..ca585b8bce 100644 --- a/src/cdomains/fileDomain.ml +++ b/src/cdomains/fileDomain.ml @@ -1,6 +1,8 @@ -open Prelude +(** Domains for file handles. *) -module D = LvalMapDomain +open Batteries + +module D = MvalMapDomain module Val = diff --git a/src/cdomains/flagModeDomain.ml b/src/cdomains/flagModeDomain.ml deleted file mode 100644 index 6d290fbf29..0000000000 --- a/src/cdomains/flagModeDomain.ml +++ /dev/null @@ -1,50 +0,0 @@ -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 ff861be1e3..f52c849111 100644 --- a/src/cdomains/floatDomain.ml +++ b/src/cdomains/floatDomain.ml @@ -41,6 +41,13 @@ module type FloatArith = sig val tan : t -> t (** tan(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 +95,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 @@ -96,7 +104,7 @@ module type FloatDomainBase = sig end module FloatIntervalImpl(Float_t : CFloatType) = struct - include Printable.Std (* for default invariant, tag and relift *) + include Printable.StdLeaf (* for default invariant, tag and relift *) type t = Top | Bot | NaN | PlusInfinity | MinusInfinity | Interval of (Float_t.t * Float_t.t) [@@deriving eq, ord, to_yojson, hash] let show = function @@ -135,8 +143,8 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct let of_int x = match IntDomain.IntDomTuple.minimal x, IntDomain.IntDomTuple.maximal x with | Some l, Some h when l >= Float_t.to_big_int Float_t.lower_bound && h <= Float_t.to_big_int Float_t.upper_bound -> - let l' = Float_t.of_float Down (Big_int_Z.float_of_big_int l) in - let h' = Float_t.of_float Up (Big_int_Z.float_of_big_int h) in + let l' = Float_t.of_float Down (Z.to_float l) in + let h' = Float_t.of_float Up (Z.to_float h) in if not (Float_t.is_finite l' && Float_t.is_finite h') then Top else @@ -210,6 +218,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 +321,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 @@ -353,7 +362,7 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct | _ -> (0, 1) in IntDomain.IntDomTuple.of_interval IBool - (Big_int_Z.big_int_of_int a, Big_int_Z.big_int_of_int b) + (Z.of_int a, Z.of_int b) let eval_neg = function @@ -555,7 +564,7 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct | _ -> (0, 0) in IntDomain.IntDomTuple.of_interval IBool - (Big_int_Z.big_int_of_int l, Big_int_Z.big_int_of_int u) + (Z.of_int l, Z.of_int u) let ne a b = Messages.warn @@ -575,7 +584,7 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct | _ -> (1, 1) in IntDomain.IntDomTuple.of_interval IBool - (Big_int_Z.big_int_of_int l, Big_int_Z.big_int_of_int u) + (Z.of_int l, Z.of_int u) let unordered op1 op2 = let a, b = @@ -585,27 +594,27 @@ module FloatIntervalImpl(Float_t : CFloatType) = struct | Top, _ | _, Top -> (0,1) (*neither of the arguments is Top/Bot/NaN*) | _ -> (0, 0) in - IntDomain.IntDomTuple.of_interval IBool (Big_int_Z.big_int_of_int a, Big_int_Z.big_int_of_int b) + IntDomain.IntDomTuple.of_interval IBool (Z.of_int a, Z.of_int b) - let true_nonZero_IInt = IntDomain.IntDomTuple.of_excl_list IInt [(Big_int_Z.big_int_of_int 0)] - let false_zero_IInt = IntDomain.IntDomTuple.of_int IInt (Big_int_Z.big_int_of_int 0) - let unknown_IInt = IntDomain.IntDomTuple.top_of IInt + let true_nonZero_IInt () = IntDomain.IntDomTuple.of_excl_list IInt [Z.zero] + let false_zero_IInt () = IntDomain.IntDomTuple.of_int IInt Z.zero + let unknown_IInt () = IntDomain.IntDomTuple.top_of IInt let eval_isnormal = function | (l, h) -> if l >= Float_t.smallest || h <= (Float_t.neg (Float_t.smallest)) then - true_nonZero_IInt + true_nonZero_IInt () else if l > (Float_t.neg (Float_t.smallest)) && h < Float_t.smallest then - false_zero_IInt + false_zero_IInt () else - unknown_IInt + unknown_IInt () (**it seems strange not to return an explicit 1 for negative numbers, but in c99 signbit is defined as: *) (**<> *) let eval_signbit = function - | (_, h) when h < Float_t.zero -> true_nonZero_IInt - | (l, _) when l > Float_t.zero -> false_zero_IInt - | _ -> unknown_IInt (**any interval containing zero has to fall in this case, because we do not distinguish between 0. and -0. *) + | (_, h) when h < Float_t.zero -> true_nonZero_IInt () + | (l, _) when l > Float_t.zero -> false_zero_IInt () + | _ -> unknown_IInt () (**any interval containing zero has to fall in this case, because we do not distinguish between 0. and -0. *) (**This Constant overapproximates pi to use as bounds for the return values of trigonometric functions *) let overapprox_pi = 3.1416 @@ -661,41 +670,83 @@ 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_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))) - | Top -> unknown_IInt - | Interval _ -> true_nonZero_IInt - | NaN | PlusInfinity | MinusInfinity -> false_zero_IInt + | Top -> unknown_IInt () + | Interval _ -> true_nonZero_IInt () + | NaN | PlusInfinity | MinusInfinity -> false_zero_IInt () let isinf op = match op with | Bot -> raise (ArithmeticOnFloatBot (Printf.sprintf "unop %s" (show op))) - | Top -> unknown_IInt - | PlusInfinity | MinusInfinity -> true_nonZero_IInt - | Interval _ | NaN -> false_zero_IInt + | Top -> unknown_IInt () + | PlusInfinity | MinusInfinity -> true_nonZero_IInt () + | Interval _ | NaN -> false_zero_IInt () let isnan op = match op with | Bot -> raise (ArithmeticOnFloatBot (Printf.sprintf "unop %s" (show op))) - | Top -> unknown_IInt - | Interval _ | PlusInfinity | MinusInfinity -> false_zero_IInt - | NaN -> true_nonZero_IInt + | Top -> unknown_IInt () + | Interval _ | PlusInfinity | MinusInfinity -> false_zero_IInt () + | NaN -> true_nonZero_IInt () let isnormal op = match op with | Bot -> raise (ArithmeticOnFloatBot (Printf.sprintf "unop %s" (show op))) - | Top -> unknown_IInt + | Top -> unknown_IInt () | Interval i -> eval_isnormal i - | PlusInfinity | MinusInfinity | NaN -> false_zero_IInt + | PlusInfinity | MinusInfinity | NaN -> false_zero_IInt () let signbit op = match op with | Bot -> raise (ArithmeticOnFloatBot (Printf.sprintf "unop %s" (show op))) - | Top | NaN -> unknown_IInt + | Top | NaN -> unknown_IInt () | Interval i -> eval_signbit i - | PlusInfinity -> false_zero_IInt - | MinusInfinity -> true_nonZero_IInt + | PlusInfinity -> false_zero_IInt () + | MinusInfinity -> true_nonZero_IInt () let fabs op = warn_on_specials_unop op; @@ -727,13 +778,23 @@ 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 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) @@ -762,6 +823,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 @@ -772,7 +834,7 @@ module type FloatDomain = sig end module FloatIntervalImplLifted = struct - include Printable.Std (* for default invariant, tag and relift *) + include Printable.StdLeaf (* for default invariant, tag and relift *) module F1 = F32Interval module F2 = F64Interval @@ -784,38 +846,44 @@ module FloatIntervalImplLifted = struct type t = | F32 of F1.t | F64 of F2.t - | FLong of F2.t [@@deriving to_yojson, eq, ord, hash] + | FLong of F2.t + | FFloat128 of F2.t [@@deriving to_yojson, eq, ord, hash] let show = function | F32 a -> "float: " ^ F1.show a | F64 a -> "double: " ^ F2.show a | FLong a -> "long double: " ^ F2.show a + | FFloat128 a -> "float128: " ^ F2.show a let lift2 (op32, op64) x y = match x, y with | F32 a, F32 b -> F32 (op32 a b) | F64 a, F64 b -> F64 (op64 a b) | FLong a, FLong b -> FLong (op64 a b) + | FFloat128 a, FFloat128 b -> FFloat128 (op64 a b) | _ -> failwith ("fkinds do not match. Values: " ^ show x ^ " and " ^ show y) let lift2_cmp (op32, op64) x y = match x, y with | F32 a, F32 b -> op32 a b | F64 a, F64 b -> op64 a b | FLong a, FLong b -> op64 a b + | FFloat128 a, FFloat128 b -> op64 a b | _ -> failwith ("fkinds do not match. Values: " ^ show x ^ " and " ^ show y) let lift (op32, op64) = function | F32 a -> F32 (op32 a) | F64 a -> F64 (op64 a) | FLong a -> FLong (op64 a) + | FFloat128 a -> FFloat128 (op64 a) let dispatch (op32, op64) = function | F32 a -> op32 a - | F64 a | FLong a -> op64 a + | F64 a | FLong a | FFloat128 a-> op64 a let dispatch_fkind fkind (op32, op64) = match fkind with | FFloat -> F32 (op32 ()) | FDouble -> F64 (op64 ()) | FLongDouble -> FLong (op64 ()) + | FFloat128 -> FFloat128 (op64 ()) | _ -> (* this should never be reached, as we have to check for invalid fkind elsewhere, however we could instead of crashing also return top_of some fkind to avoid this and nonetheless have no actual information about anything*) @@ -831,6 +899,20 @@ 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 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) @@ -855,7 +937,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) @@ -869,6 +951,7 @@ module FloatIntervalImplLifted = struct | F32 _ -> FFloat | F64 _ -> FDouble | FLong _ -> FLongDouble + | FFloat128 _ -> FFloat128 let leq = lift2_cmp (F1.leq, F2.leq) let join = lift2 (F1.join, F2.join) @@ -894,6 +977,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) @@ -997,6 +1081,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); } @@ -1074,6 +1160,15 @@ module FloatDomTupleImpl = struct let tan = map { f1= (fun (type a) (module F : FloatDomain with type t = a) -> F.tan); } + (*"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 = map2 { f2= (fun (type a) (module F : FloatDomain with type t = a) -> F.join); } @@ -1132,4 +1227,6 @@ module FloatDomTupleImpl = struct let show = show end ) + + let relift a = Option.map F1.relift a end diff --git a/src/cdomains/floatDomain.mli b/src/cdomains/floatDomain.mli index 8be4304c5e..06bca69aca 100644 --- a/src/cdomains/floatDomain.mli +++ b/src/cdomains/floatDomain.mli @@ -1,5 +1,5 @@ -(** Abstract Domains for floats. These are domains that support the C - * operations on double/float values. *) +(** Abstract domains for C floating-point numbers. *) + open GoblintCil exception ArithmeticOnFloatBot of string @@ -9,67 +9,102 @@ module type FloatArith = sig val neg : t -> t (** Negating a float value: [-x] *) + val add : t -> t -> t (** Addition: [x + y] *) + val sub : t -> t -> t (** Subtraction: [x - y] *) + val mul : t -> t -> t (** Multiplication: [x * y] *) + val div : t -> t -> t (** Division: [x / y] *) + val fmax : t -> t -> t (** Maximum *) + val fmin : t -> t -> t (** Minimum *) - (** {unary functions} *) + (** {b Unary functions} *) + val ceil: t -> t - (* ceil(x) *) + (** ceil(x) *) + val floor: t -> t - (* floor(x) *) + (** floor(x) *) + val fabs : t -> t (** fabs(x) *) + val acos : t -> t (** acos(x) *) + val asin : t -> t (** asin(x) *) + val atan : t -> t (** atan(x) *) + val cos : t -> t (** cos(x) *) + val sin : t -> t (** sin(x) *) + val tan : t -> t (** tan(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 (** Less than: [x < y] *) + val gt : t -> t -> IntDomain.IntDomTuple.t (** Greater than: [x > y] *) + val le : t -> t -> IntDomain.IntDomTuple.t (** Less than or equal: [x <= y] *) + val ge : t -> t -> IntDomain.IntDomTuple.t (** Greater than or equal: [x >= y] *) + val eq : t -> t -> IntDomain.IntDomTuple.t (** Equal to: [x == y] *) + val ne : t -> t -> IntDomain.IntDomTuple.t (** Not equal to: [x != y] *) + val unordered: t -> t -> IntDomain.IntDomTuple.t (** Unordered *) - (** {unary functions returning int} *) + (** {b Unary functions returning [int]} *) + val isfinite : t -> IntDomain.IntDomTuple.t - (** __builtin_isfinite(x) *) + (** [__builtin_isfinite(x)] *) + val isinf : t -> IntDomain.IntDomTuple.t - (** __builtin_isinf(x) *) + (** [__builtin_isinf(x)] *) + val isnan : t -> IntDomain.IntDomTuple.t - (** __builtin_isnan(x) *) + (** [__builtin_isnan(x)] *) + val isnormal : t -> IntDomain.IntDomTuple.t - (** __builtin_isnormal(x) *) + (** [__builtin_isnormal(x)] *) + val signbit : t -> IntDomain.IntDomTuple.t - (** __builtin_signbit(x) *) + (** [__builtin_signbit(x)] *) end module type FloatDomainBase = sig @@ -89,6 +124,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 @@ -123,6 +159,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 3ef7c89d4d..a951ec08fe 100644 --- a/src/cdomains/floatOps/floatOps.ml +++ b/src/cdomains/floatOps/floatOps.ml @@ -15,7 +15,7 @@ module type CFloatType = sig val of_float: round_mode -> float -> t val to_float: t -> float option - val to_big_int: t -> Big_int_Z.big_int + val to_big_int: t -> Z.t val is_finite: t -> bool val pred: t -> t @@ -42,9 +42,9 @@ let big_int_of_float f = let x, n = Float.frexp f in let shift = min 52 n in let x' = x *. Float.pow 2. (Float.of_int shift) in - Big_int_Z.mult_big_int - (Big_int_Z.big_int_of_int64 (Int64.of_float x')) - (Big_int_Z.power_int_positive_int 2 (n - shift)) + Z.mul + (Z.of_int64 (Int64.of_float x')) + (Z.pow (Z.of_int 2) (n - shift)) module CDouble = struct type t = float [@@deriving eq, ord, to_yojson] diff --git a/src/cdomains/floatOps/floatOps.mli b/src/cdomains/floatOps/floatOps.mli index bae360e19a..05bf363872 100644 --- a/src/cdomains/floatOps/floatOps.mli +++ b/src/cdomains/floatOps/floatOps.mli @@ -1,3 +1,5 @@ +(** Unified interface for floating-point types. *) + type round_mode = | Nearest | ToZero @@ -15,7 +17,7 @@ module type CFloatType = sig val of_float: round_mode -> float -> t val to_float: t -> float option - val to_big_int: t -> Big_int_Z.big_int + val to_big_int: t -> Z.t val is_finite: t -> bool val pred: t -> t diff --git a/src/cdomains/intDomain.ml b/src/cdomains/intDomain.ml index 2565ee8303..3bc84ae676 100644 --- a/src/cdomains/intDomain.ml +++ b/src/cdomains/intDomain.ml @@ -3,7 +3,6 @@ open GoblintCil open Pretty open PrecisionUtil -module GU = Goblintutil module M = Messages module BI = IntOps.BigIntOps @@ -15,6 +14,54 @@ exception Unknown exception Error exception ArithmeticOnIntegerBot of string + + + +(** Define records that hold mutable variables representing different Configuration values. + * These values are used to keep track of whether or not the corresponding Config values are en-/disabled *) +type ana_int_config_values = { + mutable interval_threshold_widening : bool option; + mutable interval_narrow_by_meet : bool option; + mutable def_exc_widen_by_join : bool option; + mutable interval_threshold_widening_constants : string option; + mutable refinement : string option; +} + +let ana_int_config: ana_int_config_values = { + interval_threshold_widening = None; + interval_narrow_by_meet = None; + def_exc_widen_by_join = None; + interval_threshold_widening_constants = None; + refinement = None; +} + +let get_interval_threshold_widening () = + if ana_int_config.interval_threshold_widening = None then + ana_int_config.interval_threshold_widening <- Some (get_bool "ana.int.interval_threshold_widening"); + Option.get ana_int_config.interval_threshold_widening + +let get_interval_narrow_by_meet () = + if ana_int_config.interval_narrow_by_meet = None then + ana_int_config.interval_narrow_by_meet <- Some (get_bool "ana.int.interval_narrow_by_meet"); + Option.get ana_int_config.interval_narrow_by_meet + +let get_def_exc_widen_by_join () = + if ana_int_config.def_exc_widen_by_join = None then + ana_int_config.def_exc_widen_by_join <- Some (get_bool "ana.int.def_exc_widen_by_join"); + Option.get ana_int_config.def_exc_widen_by_join + +let get_interval_threshold_widening_constants () = + if ana_int_config.interval_threshold_widening_constants = None then + ana_int_config.interval_threshold_widening_constants <- Some (get_string "ana.int.interval_threshold_widening_constants"); + Option.get ana_int_config.interval_threshold_widening_constants + +let get_refinement () = + if ana_int_config.refinement = None then + ana_int_config.refinement <- Some (get_string "ana.int.refinement"); + Option.get ana_int_config.refinement + + + (** Whether for a given ikind, we should compute with wrap-around arithmetic. * Always for unsigned types, for signed types if 'sem.int.signed_overflow' is 'assume_wraparound' *) let should_wrap ik = not (Cil.isSigned ik) || get_string "sem.int.signed_overflow" = "assume_wraparound" @@ -26,9 +73,30 @@ let should_ignore_overflow ik = Cil.isSigned ik && get_string "sem.int.signed_ov let widening_thresholds = ResettableLazy.from_fun WideningThresholds.thresholds let widening_thresholds_desc = ResettableLazy.from_fun (List.rev % WideningThresholds.thresholds) +type overflow_info = { overflow: bool; underflow: bool;} + +let set_overflow_flag ~cast ~underflow ~overflow ik = + let signed = Cil.isSigned ik in + if !AnalysisState.postsolving && signed && not cast then + AnalysisState.svcomp_may_overflow := true; + let sign = if signed then "Signed" else "Unsigned" in + match underflow, overflow with + | true, true -> + M.warn ~category:M.Category.Integer.overflow ~tags:[CWE 190; CWE 191] "%s integer overflow and underflow" sign + | true, false -> + M.warn ~category:M.Category.Integer.overflow ~tags:[CWE 191] "%s integer underflow" sign + | false, true -> + M.warn ~category:M.Category.Integer.overflow ~tags:[CWE 190] "%s integer overflow" sign + | false, false -> assert false + let reset_lazy () = ResettableLazy.reset widening_thresholds; - ResettableLazy.reset widening_thresholds_desc + ResettableLazy.reset widening_thresholds_desc; + ana_int_config.interval_threshold_widening <- None; + ana_int_config.interval_narrow_by_meet <- None; + ana_int_config.def_exc_widen_by_join <- None; + ana_int_config.interval_threshold_widening_constants <- None; + ana_int_config.refinement <- None module type Arith = sig @@ -115,7 +183,7 @@ sig val cast_to: ?torg:Cil.typ -> Cil.ikind -> t -> t end - +(** Interface of IntDomain implementations that do not take ikinds for arithmetic operations yet. TODO: Should be ported to S in the future. *) module type IkindUnawareS = sig include B @@ -129,9 +197,8 @@ sig val arbitrary: unit -> t QCheck.arbitrary val invariant: Cil.exp -> t -> Invariant.t end -(** Interface of IntDomain implementations that do not take ikinds for arithmetic operations yet. - TODO: Should be ported to S in the future. *) +(** Interface of IntDomain implementations taking an ikind for arithmetic operations *) module type S = sig include B @@ -165,7 +232,35 @@ sig val project: Cil.ikind -> int_precision -> t -> t val arbitrary: Cil.ikind -> t QCheck.arbitrary end -(** Interface of IntDomain implementations taking an ikind for arithmetic operations *) + +module type SOverflow = +sig + + include S + + val add : ?no_ov:bool -> Cil.ikind -> t -> t -> t * overflow_info + + val sub : ?no_ov:bool -> Cil.ikind -> t -> t -> t * overflow_info + + val mul : ?no_ov:bool -> Cil.ikind -> t -> t -> t * overflow_info + + val div : ?no_ov:bool -> Cil.ikind -> t -> t -> t * overflow_info + + val neg : ?no_ov:bool -> Cil.ikind -> t -> t * overflow_info + + val cast_to : ?torg:Cil.typ -> ?no_ov:bool -> Cil.ikind -> t -> t * overflow_info + + val of_int : Cil.ikind -> int_t -> t * overflow_info + + val of_interval: ?suppress_ovwarn:bool -> Cil.ikind -> int_t * int_t -> t * overflow_info + + val starting : ?suppress_ovwarn:bool -> Cil.ikind -> int_t -> t * overflow_info + val ending : ?suppress_ovwarn:bool -> Cil.ikind -> int_t -> t * overflow_info + + val shift_left : Cil.ikind -> t -> t -> t * overflow_info + + val shift_right : Cil.ikind -> t -> t -> t * overflow_info +end module type Y = sig @@ -290,7 +385,7 @@ struct let ikind {ikind; _} = ikind (* Helper functions *) - let check_ikinds x y = if x.ikind <> y.ikind then raise (IncompatibleIKinds ("ikinds " ^ Prelude.Ana.sprint Cil.d_ikind x.ikind ^ " and " ^ Prelude.Ana.sprint Cil.d_ikind y.ikind ^ " are incompatible. Values: " ^ Prelude.Ana.sprint I.pretty x.v ^ " and " ^ Prelude.Ana.sprint I.pretty y.v)) else () + let check_ikinds x y = if x.ikind <> y.ikind then raise (IncompatibleIKinds (GobPretty.sprintf "ikinds %a and %a are incompatible. Values: %a and %a" CilType.Ikind.pretty x.ikind CilType.Ikind.pretty y.ikind I.pretty x.v I.pretty y.v)) let lift op x = {x with v = op x.ikind x.v } (* For logical operations the result is of type int *) let lift_logical op x = {v = op x.ikind x.v; ikind = Cil.IInt} @@ -389,7 +484,7 @@ struct end module Size = struct (* size in bits as int, range as int64 *) - open Cil open Big_int_Z + open Cil let sign x = if BI.compare x BI.zero < 0 then `Signed else `Unsigned let top_typ = TInt (ILongLong, []) @@ -400,15 +495,15 @@ module Size = struct (* size in bits as int, range as int64 *) let is_int64_big_int x = Z.fits_int64 x let card ik = (* cardinality *) let b = bit ik in - shift_left_big_int unit_big_int b + Z.shift_left Z.one b let bits ik = (* highest bits for neg/pos values *) let s = bit ik in if isSigned ik then s-1, s-1 else 0, s let bits_i64 ik = BatTuple.Tuple2.mapn Int64.of_int (bits ik) let range ik = let a,b = bits ik in - let x = if isSigned ik then minus_big_int (shift_left_big_int unit_big_int a) (* -2^a *) else zero_big_int in - let y = sub_big_int (shift_left_big_int unit_big_int b) unit_big_int in (* 2^b - 1 *) + let x = if isSigned ik then Z.neg (Z.shift_left Z.one a) (* -2^a *) else Z.zero in + let y = Z.pred (Z.shift_left Z.one b) in (* 2^b - 1 *) x,y let is_cast_injective ~from_type ~to_type = @@ -418,16 +513,19 @@ module Size = struct (* size in bits as int, range as int64 *) BI.compare to_min from_min <= 0 && BI.compare from_max to_max <= 0 let cast t x = (* TODO: overflow is implementation-dependent! *) - let a,b = range t in - let c = card t in - (* let z = add (rem (sub x a) c) a in (* might lead to overflows itself... *)*) - let y = mod_big_int x c in - let y = if gt_big_int y b then sub_big_int y c - else if lt_big_int y a then add_big_int y c - else y - in - if M.tracing then M.tracel "cast_int" "Cast %s to range [%s, %s] (%s) = %s (%s in int64)\n" (string_of_big_int x) (string_of_big_int a) (string_of_big_int b) (string_of_big_int c) (string_of_big_int y) (if is_int64_big_int y then "fits" else "does not fit"); - y + if t = IBool then + (* C11 6.3.1.2 Boolean type *) + if Z.equal x Z.zero then Z.zero else Z.one + else + let a,b = range t in + let c = card t in + let y = Z.erem x c in + let y = if Z.gt y b then Z.sub y c + else if Z.lt y a then Z.add y c + else y + in + if M.tracing then M.tracel "cast" "Cast %s to range [%s, %s] (%s) = %s (%s in int64)\n" (Z.to_string x) (Z.to_string a) (Z.to_string b) (Z.to_string c) (Z.to_string y) (if is_int64_big_int y then "fits" else "does not fit"); + y let min_range_sign_agnostic x = let size ik = @@ -480,7 +578,7 @@ module Std (B: sig val show: t -> string val equal: t -> t -> bool end) = struct - include Printable.Std + include Printable.StdLeaf let name = B.name (* overwrite the one from Printable.Std *) open B let is_top x = failwith "is_top not implemented for IntDomain.Std" @@ -500,11 +598,48 @@ module Std (B: sig include StdTop (B) end -module IntervalFunctor(Ints_t : IntOps.IntOps): S with type int_t = Ints_t.t and type t = (Ints_t.t * Ints_t.t) option = +(* Textbook interval arithmetic, without any overflow handling etc. *) +module IntervalArith(Ints_t : IntOps.IntOps) = struct + let min4 a b c d = Ints_t.min (Ints_t.min a b) (Ints_t.min c d) + let max4 a b c d = Ints_t.max (Ints_t.max a b) (Ints_t.max c d) + + let mul (x1, x2) (y1, y2) = + let x1y1 = (Ints_t.mul x1 y1) in + let x1y2 = (Ints_t.mul x1 y2) in + let x2y1 = (Ints_t.mul x2 y1) in + let x2y2 = (Ints_t.mul x2 y2) in + (min4 x1y1 x1y2 x2y1 x2y2, max4 x1y1 x1y2 x2y1 x2y2) + + let div (x1, x2) (y1, y2) = + let x1y1n = (Ints_t.div x1 y1) in + let x1y2n = (Ints_t.div x1 y2) in + let x2y1n = (Ints_t.div x2 y1) in + let x2y2n = (Ints_t.div x2 y2) in + let x1y1p = (Ints_t.div x1 y1) in + let x1y2p = (Ints_t.div x1 y2) in + let x2y1p = (Ints_t.div x2 y1) in + let x2y2p = (Ints_t.div x2 y2) in + (min4 x1y1n x1y2n x2y1n x2y2n, max4 x1y1p x1y2p x2y1p x2y2p) + + let add (x1, x2) (y1, y2) = (Ints_t.add x1 y1, Ints_t.add x2 y2) + let sub (x1, x2) (y1, y2) = (Ints_t.sub x1 y2, Ints_t.sub x2 y1) + + let neg (x1, x2) = (Ints_t.neg x2, Ints_t.neg x1) + + let one = (Ints_t.one, Ints_t.one) + let zero = (Ints_t.zero, Ints_t.zero) + let top_bool = (Ints_t.zero, Ints_t.one) + + let to_int (x1, x2) = + if Ints_t.equal x1 x2 then Some x1 else None +end + +module IntervalFunctor(Ints_t : IntOps.IntOps): SOverflow with type int_t = Ints_t.t and type t = (Ints_t.t * Ints_t.t) option = struct let name () = "intervals" type int_t = Ints_t.t type t = (Ints_t.t * Ints_t.t) option [@@deriving eq, ord, hash] + module IArith = IntervalArith(Ints_t) let range ik = BatTuple.Tuple2.mapn Ints_t.of_bigint (Size.range ik) @@ -522,51 +657,40 @@ struct | Some (a, b) -> if a = b && b = i then `Eq else if Ints_t.compare a i <= 0 && Ints_t.compare i b <=0 then `Top else `Neq - let set_overflow_flag ~cast ~underflow ~overflow ik = - let signed = Cil.isSigned ik in - if !GU.postsolving && signed && not cast then ( - Goblintutil.svcomp_may_overflow := true); - - let sign = if signed then "Signed" else "Unsigned" in - match underflow, overflow with - | true, true -> - M.warn ~category:M.Category.Integer.overflow ~tags:[CWE 190; CWE 191] "%s integer overflow and underflow" sign - | true, false -> - M.warn ~category:M.Category.Integer.overflow ~tags:[CWE 191] "%s integer underflow" sign - | false, true -> - M.warn ~category:M.Category.Integer.overflow ~tags:[CWE 190] "%s integer overflow" sign - | false, false -> assert false - - let norm ?(suppress_ovwarn=false) ?(cast=false) ik = function None -> None | Some (x,y) -> - if Ints_t.compare x y > 0 then None + let norm ?(suppress_ovwarn=false) ?(cast=false) ik : (t -> t * overflow_info) = function None -> (None, {underflow=false; overflow=false}) | Some (x,y) -> + if Ints_t.compare x y > 0 then + (None,{underflow=false; overflow=false}) else ( let (min_ik, max_ik) = range ik in let underflow = Ints_t.compare min_ik x > 0 in let overflow = Ints_t.compare max_ik y < 0 in - if underflow || overflow then ( - if not suppress_ovwarn then set_overflow_flag ~cast ~underflow ~overflow ik; - if should_wrap ik then (* could add [|| cast], but that's GCC implementation-defined behavior: https://gcc.gnu.org/onlinedocs/gcc/Integers-implementation.html#Integers-implementation *) - (* We can only soundly wrap if at most one overflow occurred, otherwise the minimal and maximal values of the interval *) - (* on Z will not safely contain the minimal and maximal elements after the cast *) - let diff = Ints_t.abs (Ints_t.sub max_ik min_ik) in - let resdiff = Ints_t.abs (Ints_t.sub y x) in - if Ints_t.compare resdiff diff > 0 then - top_of ik - else - let l = Ints_t.of_bigint @@ Size.cast ik (Ints_t.to_bigint x) in - let u = Ints_t.of_bigint @@ Size.cast ik (Ints_t.to_bigint y) in - if Ints_t.compare l u <= 0 then - Some (l, u) - else - (* Interval that wraps around (begins to the right of its end). We can not represent such intervals *) + let ov_info = { underflow = underflow && not suppress_ovwarn; overflow = overflow && not suppress_ovwarn } in + let v = + if underflow || overflow then + if should_wrap ik then (* could add [|| cast], but that's GCC implementation-defined behavior: https://gcc.gnu.org/onlinedocs/gcc/Integers-implementation.html#Integers-implementation *) + (* We can only soundly wrap if at most one overflow occurred, otherwise the minimal and maximal values of the interval *) + (* on Z will not safely contain the minimal and maximal elements after the cast *) + let diff = Ints_t.abs (Ints_t.sub max_ik min_ik) in + let resdiff = Ints_t.abs (Ints_t.sub y x) in + if Ints_t.compare resdiff diff > 0 then top_of ik - else if not cast && should_ignore_overflow ik then - let tl, tu = BatOption.get @@ top_of ik in - Some (Ints_t.max tl x, Ints_t.min tu y) + else + let l = Ints_t.of_bigint @@ Size.cast ik (Ints_t.to_bigint x) in + let u = Ints_t.of_bigint @@ Size.cast ik (Ints_t.to_bigint y) in + if Ints_t.compare l u <= 0 then + Some (l, u) + else + (* Interval that wraps around (begins to the right of its end). We can not represent such intervals *) + top_of ik + else if not cast && should_ignore_overflow ik then + let tl, tu = BatOption.get @@ top_of ik in + Some (Ints_t.max tl x, Ints_t.min tu y) + else + top_of ik else - top_of ik - ) - else Some (x,y) + Some (x,y) + in + (v, ov_info) ) let leq (x:t) (y:t) = @@ -578,20 +702,20 @@ struct let join ik (x:t) y = match x, y with | None, z | z, None -> z - | Some (x1,x2), Some (y1,y2) -> norm ik @@ Some (Ints_t.min x1 y1, Ints_t.max x2 y2) + | Some (x1,x2), Some (y1,y2) -> norm ik @@ Some (Ints_t.min x1 y1, Ints_t.max x2 y2) |> fst let meet ik (x:t) y = match x, y with | None, z | z, None -> None - | Some (x1,x2), Some (y1,y2) -> norm ik @@ Some (Ints_t.max x1 y1, Ints_t.min x2 y2) + | Some (x1,x2), Some (y1,y2) -> norm ik @@ Some (Ints_t.max x1 y1, Ints_t.min x2 y2) |> fst (* TODO: change to_int signature so it returns a big_int *) - let to_int = function Some (x,y) when Ints_t.compare x y = 0 -> Some x | _ -> None + let to_int x = Option.bind x (IArith.to_int) let of_interval ?(suppress_ovwarn=false) ik (x,y) = norm ~suppress_ovwarn ik @@ Some (x,y) let of_int ik (x: int_t) = of_interval ik (x,x) - let zero = Some (Ints_t.zero, Ints_t.zero) - let one = Some (Ints_t.one, Ints_t.one) - let top_bool = Some (Ints_t.zero, Ints_t.one) + let zero = Some IArith.zero + let one = Some IArith.one + let top_bool = Some IArith.top_bool let of_bool _ik = function true -> one | false -> zero let to_bool (a: t) = match a with @@ -616,15 +740,15 @@ struct | None, z | z, None -> z | Some (l0,u0), Some (l1,u1) -> let (min_ik, max_ik) = range ik in - let threshold = get_bool "ana.int.interval_threshold_widening" in + let threshold = get_interval_threshold_widening () in let upper_threshold u = - let ts = if GobConfig.get_string "ana.int.interval_threshold_widening_constants" = "comparisons" then WideningThresholds.upper_thresholds () else ResettableLazy.force widening_thresholds in + let ts = if get_interval_threshold_widening_constants () = "comparisons" then WideningThresholds.upper_thresholds () else ResettableLazy.force widening_thresholds in let u = Ints_t.to_bigint u in let t = List.find_opt (fun x -> Z.compare u x <= 0) ts in BatOption.map_default Ints_t.of_bigint max_ik t in let lower_threshold l = - let ts = if GobConfig.get_string "ana.int.interval_threshold_widening_constants" = "comparisons" then WideningThresholds.lower_thresholds () else ResettableLazy.force widening_thresholds_desc in + let ts = if get_interval_threshold_widening_constants () = "comparisons" then WideningThresholds.lower_thresholds () else ResettableLazy.force widening_thresholds_desc in let l = Ints_t.to_bigint l in let t = List.find_opt (fun x -> Z.compare l x >= 0) ts in BatOption.map_default Ints_t.of_bigint min_ik t @@ -633,10 +757,10 @@ struct let l2 = if Ints_t.compare l0 l1 = 0 then l0 else Ints_t.min l1 (Ints_t.max lt min_ik) in let ut = if threshold then upper_threshold u1 else max_ik in let u2 = if Ints_t.compare u0 u1 = 0 then u0 else Ints_t.max u1 (Ints_t.min ut max_ik) in - norm ik @@ Some (l2,u2) + 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 @@ -647,10 +771,11 @@ struct let (min_ik, max_ik) = range ik in let lr = if Ints_t.compare min_ik x1 = 0 then y1 else x1 in let ur = if Ints_t.compare max_ik x2 = 0 then y2 else x2 in - norm ik @@ Some (lr,ur) + norm ik @@ Some (lr,ur) |> fst + let narrow ik x y = - if get_bool "ana.int.interval_narrow_by_meet" then + if get_interval_narrow_by_meet () then meet ik x y else narrow ik x y @@ -687,21 +812,33 @@ struct | _ , 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 norm ik (of_int ik (f ik x y)) with Division_by_zero -> top_of ik) + | Some x, Some y -> (try of_int ik (f ik x y) |> fst with Division_by_zero -> top_of ik) | _ -> top_of ik let bitcomp f ik i1 i2 = match is_bot i1, is_bot i2 with - | true, true -> bot_of ik + | true, true -> (bot_of ik,{underflow=false; overflow=false}) | true, _ | _ , true -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show i1) (show i2))) | _ -> match to_int i1, to_int i2 with - | Some x, Some y -> (try norm ik (of_int ik (f ik x y)) with Division_by_zero | Invalid_argument _ -> top_of ik) - | _ -> (set_overflow_flag ~cast:false ~underflow:true ~overflow:true ik; top_of ik) + | Some x, Some y -> (try of_int ik (f ik x y) with Division_by_zero | Invalid_argument _ -> (top_of ik,{underflow=false; overflow=false})) + | _ -> (top_of ik,{underflow=true; overflow=true}) let 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 = @@ -709,24 +846,23 @@ struct bot_of ik else match to_int i1 with - | Some x -> of_int ik (f ik x) + | Some x -> of_int ik (f ik x) |> fst | _ -> top_of ik 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 | Some (x,y) -> norm ik @@ Some (Ints_t.neg y, Ints_t.neg x) + let neg ?no_ov ik = function None -> (None,{underflow=false; overflow=false}) | Some x -> norm ik @@ Some (IArith.neg x) - let add ?no_ov ik x y = match x, y with - | None, None -> None - | None, _ | _, None -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) - | Some (x1,x2), Some (y1,y2) -> norm ik @@ Some (Ints_t.add x1 y1, Ints_t.add x2 y2) + let binary_op_with_norm ?no_ov op ik x y = match x, y with + | None, None -> (None, {overflow=false; underflow= false}) + | None, _ | _, None -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) + | Some x, Some y -> norm ik @@ Some (op x y) - let sub ?no_ov ik x y = match x, y with - | None, None -> None - | None, _ | _, None -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) - | Some (x1,x2), Some (y1,y2) -> norm ik @@ Some (Ints_t.sub x1 y2, Ints_t.sub x2 y1) (* y1, y2 are in different order here than in add *) + let add ?no_ov = binary_op_with_norm IArith.add + let mul ?no_ov = binary_op_with_norm IArith.mul + let sub ?no_ov = binary_op_with_norm IArith.sub let rem ik x y = match x, y with | None, None -> None @@ -742,43 +878,27 @@ struct top_of ik else (* If we have definite values, Ints_t.rem will give a definite result. - * Otherwise we meet with a [range] the result can be in. - * This range is [0, min xu b] if x is positive, and [max xl -b, min xu b] if x can be negative. - * The precise bound b is one smaller than the maximum bound. Negative y give the same result as positive. *) + * Otherwise we meet with a [range] the result can be in. + * This range is [0, min xu b] if x is positive, and [max xl -b, min xu b] if x can be negative. + * The precise bound b is one smaller than the maximum bound. Negative y give the same result as positive. *) let pos x = if Ints_t.compare x Ints_t.zero < 0 then Ints_t.neg x else x in let b = Ints_t.sub (Ints_t.max (pos yl) (pos yu)) Ints_t.one in let range = if Ints_t.compare xl Ints_t.zero>= 0 then Some (Ints_t.zero, Ints_t.min xu b) else Some (Ints_t.max xl (Ints_t.neg b), Ints_t.min (Ints_t.max (pos xl) (pos xu)) b) in meet ik (bit (fun _ik -> Ints_t.rem) ik x y) range - let mul ?no_ov ik x y = - match x, y with - | None, None -> bot () - | None, _ | _, None -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) - | Some (x1,x2), Some (y1,y2) -> - let x1y1 = (Ints_t.mul x1 y1) in let x1y2 = (Ints_t.mul x1 y2) in - let x2y1 = (Ints_t.mul x2 y1) in let x2y2 = (Ints_t.mul x2 y2) in - norm ik @@ Some ((Ints_t.min (Ints_t.min x1y1 x1y2) (Ints_t.min x2y1 x2y2)), - (Ints_t.max (Ints_t.max x1y1 x1y2) (Ints_t.max x2y1 x2y2))) - let rec div ?no_ov ik x y = match x, y with - | None, None -> bot () + | None, None -> (bot (),{underflow=false; overflow=false}) | None, _ | _, None -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) - | Some (x1,x2), Some (y1,y2) -> + | (Some (x1,x2) as x), (Some (y1,y2) as y) -> begin let is_zero v = Ints_t.compare v Ints_t.zero = 0 in match y1, y2 with - | l, u when is_zero l && is_zero u -> top_of ik (* TODO warn about undefined behavior *) + | l, u when is_zero l && is_zero u -> (top_of ik,{underflow=false; overflow=false}) (* TODO warn about undefined behavior *) | l, _ when is_zero l -> div ik (Some (x1,x2)) (Some (Ints_t.one,y2)) | _, u when is_zero u -> div ik (Some (x1,x2)) (Some (y1, Ints_t.(neg one))) - | _ when leq (of_int ik (Ints_t.zero)) (Some (y1,y2)) -> top_of ik - | _ -> - let x1y1n = (Ints_t.div x1 y1) in let x1y2n = (Ints_t.div x1 y2) in - let x2y1n = (Ints_t.div x2 y1) in let x2y2n = (Ints_t.div x2 y2) in - let x1y1p = (Ints_t.div x1 y1) in let x1y2p = (Ints_t.div x1 y2) in - let x2y1p = (Ints_t.div x2 y1) in let x2y2p = (Ints_t.div x2 y2) in - norm ik @@ Some ((Ints_t.min (Ints_t.min x1y1n x1y2n) (Ints_t.min x2y1n x2y2n)), - (Ints_t.max (Ints_t.max x1y1p x1y2p) (Ints_t.max x2y1p x2y2p))) + | _ when leq (of_int ik (Ints_t.zero) |> fst) (Some (y1,y2)) -> (top_of ik,{underflow=false; overflow=false}) + | _ -> binary_op_with_norm IArith.div ik x y end let ne ik x y = @@ -863,11 +983,10 @@ struct let int_arb = QCheck.map ~rev:Ints_t.to_int64 Ints_t.of_int64 MyCheck.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) + | Some (l, u) -> (return None) <+> (MyCheck.shrink pair_arb (l, u) >|= of_interval ik >|= fst) | None -> empty in - QCheck.(set_shrink shrink @@ set_print show @@ map (*~rev:BatOption.get*) (of_interval ik) pair_arb) - let relift x = x + QCheck.(set_shrink shrink @@ set_print show @@ map (*~rev:BatOption.get*) (fun x -> of_interval ik x |> fst ) pair_arb) let modulo n k = let result = Ints_t.rem n k in @@ -889,8 +1008,8 @@ struct if Ints_t.equal y max_ik then y else Ints_t.sub y (modulo (Ints_t.sub y c) (Ints_t.abs m)) in if Ints_t.compare rcx lcy > 0 then None - else if Ints_t.equal rcx lcy then norm ik @@ Some (rcx, rcx) - else norm ik @@ Some (rcx, lcy) + else if Ints_t.equal rcx lcy then norm ik @@ Some (rcx, rcx) |> fst + else norm ik @@ Some (rcx, lcy) |> fst | _ -> None let refine_with_congruence ik x y = @@ -905,36 +1024,607 @@ struct | None, _ | _, None -> intv | Some(l, u), Some(ls, (rl, rh)) -> let rec shrink op b = - let new_b = (op b (Ints_t.of_int(Bool.to_int(List.mem b ls)))) in + let new_b = (op b (Ints_t.of_int(Bool.to_int(BatList.mem_cmp Ints_t.compare b ls)))) in if not (Ints_t.equal b new_b) then shrink op new_b else new_b in let (min_ik, max_ik) = range ik in let l' = if Ints_t.equal l min_ik then l else shrink Ints_t.add l in let u' = if Ints_t.equal u max_ik then u else shrink Ints_t.sub u in - let intv' = norm ik @@ Some (l', u') in - let range = norm ~suppress_ovwarn:true ik (Some (Ints_t.of_bigint (Size.min_from_bit_range rl), Ints_t.of_bigint (Size.max_from_bit_range rh))) in + let intv' = norm ik @@ Some (l', u') |> fst in + let range = norm ~suppress_ovwarn:true ik (Some (Ints_t.of_bigint (Size.min_from_bit_range rl), Ints_t.of_bigint (Size.max_from_bit_range rh))) |> fst in meet ik intv' range let refine_with_incl_list ik (intv: t) (incl : (int_t list) option) : t = match intv, incl with | None, _ | _, None -> intv | Some(l, u), Some(ls) -> - let rec min m1 ms = match ms with | [] -> m1 | x::xs -> match m1 with - | None -> min (Some x) xs | Some m -> if Ints_t.compare m x < 0 then min (Some m) xs else min (Some x) xs in - let rec max m1 ms = match ms with | [] -> m1 | x::xs -> match m1 with - | None -> max (Some x) xs | Some m -> if Ints_t.compare m x > 0 then max (Some m) xs else max (Some x) xs in - match min None ls, max None ls with - | Some m1, Some m2 -> refine_with_interval ik (Some(l, u)) (Some (m1, m2)) - | _, _-> intv + let rec min m1 ms = match ms with | [] -> m1 | x::xs -> match m1 with + | None -> min (Some x) xs | Some m -> if Ints_t.compare m x < 0 then min (Some m) xs else min (Some x) xs in + let rec max m1 ms = match ms with | [] -> m1 | x::xs -> match m1 with + | None -> max (Some x) xs | Some m -> if Ints_t.compare m x > 0 then max (Some m) xs else max (Some x) xs in + match min None ls, max None ls with + | Some m1, Some m2 -> refine_with_interval ik (Some(l, u)) (Some (m1, m2)) + | _, _-> intv + + let project ik p t = t +end + +(** IntervalSetFunctor that is not just disjunctive completion of intervals, but attempts to be precise for wraparound arithmetic for unsigned types *) +module IntervalSetFunctor(Ints_t : IntOps.IntOps): SOverflow with type int_t = Ints_t.t and type t = (Ints_t.t * Ints_t.t) list = +struct + + module Interval = IntervalFunctor(Ints_t) + module IArith = IntervalArith(Ints_t) + + + let name () = "interval_sets" + + type int_t = Ints_t.t + + let (>.) a b = Ints_t.compare a b > 0 + let (=.) a b = Ints_t.compare a b = 0 + let (<.) a b = Ints_t.compare a b < 0 + let (>=.) a b = Ints_t.compare a b >= 0 + let (<=.) a b = Ints_t.compare a b <= 0 + let (+.) a b = Ints_t.add a b + let (-.) a b = Ints_t.sub a b + + (* + Each domain's element is guaranteed to be in canonical form. That is, each interval contained + inside the set does not overlap with each other and they are not adjacent. + *) + type t = (Ints_t.t * Ints_t.t) list [@@deriving eq, hash, ord] + + let range ik = BatTuple.Tuple2.mapn Ints_t.of_bigint (Size.range ik) + + let top () = failwith @@ "top () not implemented for " ^ (name ()) + + let top_of ik = [range ik] + + let bot () = [] + + let bot_of ik = bot () + + let show (x: t) = + let show_interval i = Printf.sprintf "[%s, %s]" (Ints_t.to_string (fst i)) (Ints_t.to_string (snd i)) in + List.fold_left (fun acc i -> (show_interval i) :: acc) [] x |> List.rev |> String.concat ", " |> Printf.sprintf "[%s]" + + (* New type definition for the sweeping line algorithm used for implementing join/meet functions. *) + type event = Enter of Ints_t.t | Exit of Ints_t.t + + let unbox_event = function Enter x -> x | Exit x -> x + + let cmp_events x y = + (* Deliberately comparing ints first => Cannot be derived *) + let res = Ints_t.compare (unbox_event x) (unbox_event y) in + if res <> 0 then res + else + begin + match (x, y) with + | (Enter _, Exit _) -> -1 + | (Exit _, Enter _) -> 1 + | (_, _) -> 0 + end + + let interval_set_to_events (xs: t) = + List.concat_map (fun (a, b) -> [Enter a; Exit b]) xs + + let two_interval_sets_to_events (xs: t) (ys: t) = + let xs = interval_set_to_events xs in + let ys = interval_set_to_events ys in + List.merge cmp_events xs ys + + (* Using the sweeping line algorithm, combined_event_list returns a new event list representing the intervals in which at least n intervals in xs overlap + This function is used for both join and meet operations with different parameter n: 1 for join, 2 for meet *) + let combined_event_list lattice_op (xs:event list) = + let l = match lattice_op with `Join -> 1 | `Meet -> 2 in + let aux (interval_count, acc) = function + | Enter x -> (interval_count + 1, if (interval_count + 1) >= l && interval_count < l then (Enter x)::acc else acc) + | Exit x -> (interval_count - 1, if interval_count >= l && (interval_count - 1) < l then (Exit x)::acc else acc) + in + List.fold_left aux (0, []) xs |> snd |> List.rev + + let rec events_to_intervals = function + | [] -> [] + | (Enter x)::(Exit y)::xs -> (x, y)::(events_to_intervals xs) + | _ -> failwith "Invalid events list" + + let remove_empty_gaps (xs: t) = + let aux acc (l, r) = match acc with + | ((a, b)::acc') when (b +. Ints_t.one) >=. l -> (a, r)::acc' + | _ -> (l, r)::acc + in + List.fold_left aux [] xs |> List.rev + + let canonize (xs: t) = + interval_set_to_events xs |> + List.sort cmp_events |> + combined_event_list `Join |> + events_to_intervals |> + remove_empty_gaps + + let unop (x: t) op = match x with + | [] -> [] + | _ -> canonize @@ List.concat_map op x + + let binop (x: t) (y: t) op : t = match x, y with + | [], _ -> [] + | _, [] -> [] + | _, _ -> canonize @@ List.concat_map op (BatList.cartesian_product x y) + + + include Std (struct type nonrec t = t let name = name let top_of = top_of let bot_of = bot_of let show = show let equal = equal end) + + let minimal = function + | [] -> None + | (x, _)::_ -> Some x + + let maximal = function + | [] -> None + | xs -> Some (BatList.last xs |> snd) + + let equal_to_interval i (a, b) = + if a =. b && b =. i then + `Eq + else if a <=. i && i <=. b then + `Top + else + `Neq + + let equal_to i xs = match List.map (equal_to_interval i) xs with + | [] -> failwith "unsupported: equal_to with bottom" + | [`Eq] -> `Eq + | ys when List.for_all ((=) `Neq) ys -> `Neq + | _ -> `Top + + let norm_interval ?(suppress_ovwarn=false) ?(cast=false) ik (x,y) : t*overflow_info = + if x >. y then + ([],{underflow=false; overflow=false}) + else + let (min_ik, max_ik) = range ik in + let underflow = min_ik >. x in + let overflow = max_ik <. y in + let v = if underflow || overflow then + begin + if should_wrap ik then (* could add [|| cast], but that's GCC implementation-defined behavior: https://gcc.gnu.org/onlinedocs/gcc/Integers-implementation.html#Integers-implementation *) + (* We can only soundly wrap if at most one overflow occurred, otherwise the minimal and maximal values of the interval *) + (* on Z will not safely contain the minimal and maximal elements after the cast *) + let diff = Ints_t.abs (max_ik -. min_ik) in + let resdiff = Ints_t.abs (y -. x) in + if resdiff >. diff then + [range ik] + else + let l = Ints_t.of_bigint @@ Size.cast ik (Ints_t.to_bigint x) in + let u = Ints_t.of_bigint @@ Size.cast ik (Ints_t.to_bigint y) in + if l <=. u then + [(l, u)] + else + (* Interval that wraps around (begins to the right of its end). We CAN represent such intervals *) + [(min_ik, u); (l, max_ik)] + else if not cast && should_ignore_overflow ik then + [Ints_t.max min_ik x, Ints_t.min max_ik y] + else + [range ik] + end + else + [(x,y)] + in + if suppress_ovwarn then (v, {underflow=false; overflow=false}) else (v, {underflow; overflow}) + + let norm_intvs ?(suppress_ovwarn=false) ?(cast=false) (ik:ikind) (xs: t) : t*overflow_info = + let res = List.map (norm_interval ~suppress_ovwarn ~cast ik) xs in + let intvs = List.concat_map fst res in + let underflow = List.exists (fun (_,{underflow; _}) -> underflow) res in + let overflow = List.exists (fun (_,{overflow; _}) -> underflow) res in + (canonize intvs,{underflow; overflow}) + + let binary_op_with_norm op (ik:ikind) (x: t) (y: t) : t*overflow_info = match x, y with + | [], _ -> ([],{overflow=false; underflow=false}) + | _, [] -> ([],{overflow=false; underflow=false}) + | _, _ -> norm_intvs ik @@ List.concat_map (fun (x,y) -> [op x y]) (BatList.cartesian_product x y) + + let binary_op_with_ovc (x: t) (y: t) op : t*overflow_info = match x, y with + | [], _ -> ([],{overflow=false; underflow=false}) + | _, [] -> ([],{overflow=false; underflow=false}) + | _, _ -> + let res = List.map op (BatList.cartesian_product x y) in + let intvs = List.concat_map fst res in + let underflow = List.exists (fun (_,{underflow; _}) -> underflow) res in + let overflow = List.exists (fun (_,{overflow; _}) -> underflow) res in + (canonize intvs,{underflow; overflow}) + + let unary_op_with_norm op (ik:ikind) (x: t) = match x with + | [] -> ([],{overflow=false; underflow=false}) + | _ -> norm_intvs ik @@ List.concat_map (fun x -> [op x]) x + + let rec leq (xs: t) (ys: t) = + let leq_interval (al, au) (bl, bu) = al >=. bl && au <=. bu in + match xs, ys with + | [], _ -> true + | _, [] -> false + | (xl,xr)::xs', (yl,yr)::ys' -> + if leq_interval (xl,xr) (yl,yr) then + leq xs' ys + else if xr <. yl then + false + else + leq xs ys' + + let join ik (x: t) (y: t): t = + two_interval_sets_to_events x y |> + combined_event_list `Join |> + events_to_intervals |> + remove_empty_gaps + + let meet ik (x: t) (y: t): t = + two_interval_sets_to_events x y |> + combined_event_list `Meet |> + events_to_intervals + + let to_int = function + | [x] -> IArith.to_int x + | _ -> None + + let zero = [IArith.zero] + let one = [IArith.one] + let top_bool = [IArith.top_bool] + + let not_bool (x:t) = + let is_false x = equal x zero in + let is_true x = equal x one in + if is_true x then zero else if is_false x then one else top_bool + + let to_bool = function + | [(l,u)] when l =. Ints_t.zero && u =. Ints_t.zero -> Some false + | x -> if leq zero x then None else Some true + + let of_bool _ = function true -> one | false -> zero + + let of_interval ?(suppress_ovwarn=false) ik (x,y) = norm_interval ~suppress_ovwarn ~cast:false ik (x,y) + + let of_int ik (x: int_t) = of_interval ik (x, x) + + let lt ik x y = + match x, y with + | [], [] -> bot_of ik + | [], _ | _, [] -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) + | _, _ -> + let (max_x, min_y) = (maximal x |> Option.get, minimal y |> Option.get) in + let (min_x, max_y) = (minimal x |> Option.get, maximal y |> Option.get) in + if max_x <. min_y then + of_bool ik true + else if min_x >=. max_y then + of_bool ik false + else + top_bool + + let le ik x y = + match x, y with + | [], [] -> bot_of ik + | [], _ | _, [] -> raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) + | _, _ -> + let (max_x, min_y) = (maximal x |> Option.get, minimal y |> Option.get) in + let (min_x, max_y) = (minimal x |> Option.get, maximal y |> Option.get) in + if max_x <=. min_y then + of_bool ik true + else if min_x >. max_y then + of_bool ik false + else + top_bool + + let gt ik x y = not_bool @@ le ik x y + + let ge ik x y = not_bool @@ lt ik x y + + let eq ik x y = match x, y with + | (a, b)::[], (c, d)::[] when a =. b && c =. d && a =. c -> + one + | _ -> + if is_bot (meet ik x y) then + zero + else + top_bool + + let ne ik x y = not_bool @@ eq ik x y + let interval_to_int i = Interval.to_int (Some i) + let interval_to_bool i = Interval.to_bool (Some i) + + let log f ik (i1, i2) = + match (interval_to_bool i1, interval_to_bool i2) with + | Some x, Some y -> of_bool ik (f x y) + | _ -> top_of ik + + + let bit f ik (i1, i2) = + match (interval_to_int i1), (interval_to_int i2) with + | Some x, Some y -> (try of_int ik (f x y) |> fst with Division_by_zero -> top_of ik) + | _ -> top_of ik + + + let bitcomp f ik (i1, i2) = + match (interval_to_int i1, interval_to_int i2) with + | Some x, Some y -> (try of_int ik (f x y) with Division_by_zero | Invalid_argument _ -> (top_of ik,{overflow=false; underflow=false})) + | _, _ -> (top_of ik,{overflow=false; underflow=false}) + + let bitand ik x y = + let interval_bitand = bit Ints_t.bitand ik in + binop x y interval_bitand + + let bitor ik x y = + let interval_bitor = bit Ints_t.bitor ik in + binop x y interval_bitor + + let bitxor ik x y = + let interval_bitxor = bit Ints_t.bitxor ik in + binop x y interval_bitxor + + let bitnot ik x = + let interval_bitnot i = + match interval_to_int i with + | Some x -> of_int ik (Ints_t.bitnot x) |> fst + | _ -> top_of ik + in + unop x interval_bitnot + + let shift_left ik x y = + let interval_shiftleft = bitcomp (fun x y -> Ints_t.shift_left x (Ints_t.to_int y)) ik in + binary_op_with_ovc x y interval_shiftleft + + let shift_right ik x y = + let interval_shiftright = bitcomp (fun x y -> Ints_t.shift_right x (Ints_t.to_int y)) ik in + binary_op_with_ovc x y interval_shiftright + + let lognot ik x = + let log1 f ik i1 = + match interval_to_bool i1 with + | Some x -> of_bool ik (f x) + | _ -> top_of ik + in + let interval_lognot = log1 not ik in + unop x interval_lognot + + let logand ik x y = + let interval_logand = log (&&) ik in + binop x y interval_logand + + let logor ik x y = + let interval_logor = log (||) ik in + binop x y interval_logor + + let add ?no_ov = binary_op_with_norm IArith.add + let sub ?no_ov = binary_op_with_norm IArith.sub + let mul ?no_ov = binary_op_with_norm IArith.mul + let neg ?no_ov = unary_op_with_norm IArith.neg + + let div ?no_ov ik x y = + let rec interval_div x (y1, y2) = begin + let top_of ik = top_of ik |> List.hd in + let is_zero v = v =. Ints_t.zero in + match y1, y2 with + | l, u when is_zero l && is_zero u -> top_of ik (* TODO warn about undefined behavior *) + | l, _ when is_zero l -> interval_div x (Ints_t.one,y2) + | _, u when is_zero u -> interval_div x (y1, Ints_t.(neg one)) + | _ when leq (of_int ik (Ints_t.zero) |> fst) ([(y1,y2)]) -> top_of ik + | _ -> IArith.div x (y1, y2) + end + in binary_op_with_norm interval_div ik x y + + let rem ik x y = + let interval_rem (x, y) = + if Interval.is_top_of ik (Some x) && Interval.is_top_of ik (Some y) then + top_of ik + else + let (xl, xu) = x in let (yl, yu) = y in + let pos x = if x <. Ints_t.zero then Ints_t.neg x else x in + let b = (Ints_t.max (pos yl) (pos yu)) -. Ints_t.one in + let range = if xl >=. Ints_t.zero then (Ints_t.zero, Ints_t.min xu b) else (Ints_t.max xl (Ints_t.neg b), Ints_t.min (Ints_t.max (pos xl) (pos xu)) b) in + meet ik (bit Ints_t.rem ik (x, y)) [range] + in + binop x y interval_rem + + let cast_to ?torg ?no_ov ik x = norm_intvs ~cast:true ik x + + (* + narrows down the extremeties of xs if they are equal to boundary values of the ikind with (possibly) narrower values from ys + *) + let narrow ik xs ys = match xs ,ys with + | [], _ -> [] | _ ,[] -> xs + | _, _ -> + let min_xs = minimal xs |> Option.get in + let max_xs = maximal xs |> Option.get in + let min_ys = minimal ys |> Option.get in + let max_ys = maximal ys |> Option.get in + let min_range,max_range = range ik in + let min = if min_xs =. min_range then min_ys else min_xs in + let max = if max_xs =. max_range then max_ys else max_xs in + xs + |> (function (_, y)::z -> (min, y)::z | _ -> []) + |> List.rev + |> (function (x, _)::z -> (x, max)::z | _ -> []) + |> List.rev + + (* + 1. partitions the intervals of xs by assigning each of them to the an interval in ys that includes it. + and joins all intervals in xs assigned to the same interval in ys as one interval. + 2. checks for every pair of adjacent pairs whether the pairs did approach (if you compare the intervals from xs and ys) and merges them if it is the case. + 3. checks whether partitions at the extremeties are approaching infinity (and expands them to infinity. in that case) + + The expansion (between a pair of adjacent partitions or at extremeties ) stops at a threshold. + *) + let widen ik xs ys = + let (min_ik,max_ik) = range ik in + let threshold = get_bool "ana.int.interval_threshold_widening" in + let upper_threshold (_,u) = + let ts = if GobConfig.get_string "ana.int.interval_threshold_widening_constants" = "comparisons" then WideningThresholds.upper_thresholds () else ResettableLazy.force widening_thresholds in + let u = Ints_t.to_bigint u in + let t = List.find_opt (fun x -> Z.compare u x <= 0) ts in + BatOption.map_default Ints_t.of_bigint max_ik t + in + let lower_threshold (l,_) = + let ts = if GobConfig.get_string "ana.int.interval_threshold_widening_constants" = "comparisons" then WideningThresholds.lower_thresholds () else ResettableLazy.force widening_thresholds_desc in + let l = Ints_t.to_bigint l in + let t = List.find_opt (fun x -> Z.compare l x >= 0) ts in + BatOption.map_default Ints_t.of_bigint min_ik t + in + (*obtain partitioning of xs intervals according to the ys interval that includes them*) + let rec interval_sets_to_partitions (ik: ikind) (acc : (int_t * int_t) option) (xs: t) (ys: t)= + match xs,ys with + | _, [] -> [] + | [], (y::ys) -> (acc,y):: interval_sets_to_partitions ik None [] ys + | (x::xs), (y::ys) when Interval.leq (Some x) (Some y) -> interval_sets_to_partitions ik (Interval.join ik acc (Some x)) xs (y::ys) + | (x::xs), (y::ys) -> (acc,y) :: interval_sets_to_partitions ik None (x::xs) ys + in + let interval_sets_to_partitions ik xs ys = interval_sets_to_partitions ik None xs ys in + (*merge a pair of adjacent partitions*) + let merge_pair ik (a,b) (c,d) = + let new_a = function + | None -> Some (upper_threshold b, upper_threshold b) + | Some (ax,ay) -> Some (ax, upper_threshold b) + in + let new_c = function + | None -> Some (lower_threshold d, lower_threshold d) + | Some (cx,cy) -> Some (lower_threshold d, cy) + in + if threshold && (lower_threshold d +. Ints_t.one) >. (upper_threshold b) then + [(new_a a,(fst b, upper_threshold b)); (new_c c, (lower_threshold d, snd d))] + else + [(Interval.join ik a c, (Interval.join ik (Some b) (Some d) |> Option.get))] + in + let partitions_are_approaching part_left part_right = match part_left, part_right with + | (Some (_, left_x), (_, left_y)), (Some (right_x, _), (right_y, _)) -> (right_x -. left_x) >. (right_y -. left_y) + | _,_ -> false + in + (*merge all approaching pairs of adjacent partitions*) + let rec merge_list ik = function + | [] -> [] + | x::y::xs when partitions_are_approaching x y -> merge_list ik ((merge_pair ik x y) @ xs) + | x::xs -> x :: merge_list ik xs + in + (*expands left extremity*) + let widen_left = function + | [] -> [] + | (None,(lb,rb))::ts -> let lt = if threshold then lower_threshold (lb,lb) else min_ik in (None, (lt,rb))::ts + | (Some (la,ra), (lb,rb))::ts when lb <. la -> let lt = if threshold then lower_threshold (lb,lb) else min_ik in (Some (la,ra),(lt,rb))::ts + | x -> x + in + (*expands right extremity*) + let widen_right x = + let map_rightmost = function + | [] -> [] + | (None,(lb,rb))::ts -> let ut = if threshold then upper_threshold (rb,rb) else max_ik in (None, (lb,ut))::ts + | (Some (la,ra), (lb,rb))::ts when ra <. rb -> let ut = if threshold then upper_threshold (rb,rb) else max_ik in (Some (la,ra),(lb,ut))::ts + | x -> x + in + List.rev x |> map_rightmost |> List.rev + in + interval_sets_to_partitions ik xs ys |> merge_list ik |> widen_left |> widen_right |> List.map snd + + let starting ?(suppress_ovwarn=false) ik n = norm_interval ik ~suppress_ovwarn (n, snd (range ik)) + + let ending ?(suppress_ovwarn=false) ik n = norm_interval ik ~suppress_ovwarn (fst (range ik), n) + + let invariant_ikind e ik xs = + List.map (fun x -> Interval.invariant_ikind e ik (Some x)) xs |> + let open Invariant in List.fold_left (||) (bot ()) + + let modulo n k = + let result = Ints_t.rem n k in + if result >=. Ints_t.zero then result + else result +. k + + let refine_with_congruence ik (intvs: t) (cong: (int_t * int_t ) option): t = + let refine_with_congruence_interval ik (cong : (int_t * int_t ) option) (intv : (int_t * int_t ) option): t = + match intv, cong with + | Some (x, y), Some (c, m) -> + if m =. Ints_t.zero && (c <. x || c >. y) then [] + else if m =. Ints_t.zero then + [(c, c)] + else + let (min_ik, max_ik) = range ik in + let rcx = + if x =. min_ik then x else + x +. (modulo (c -. x) (Ints_t.abs m)) in + let lcy = + if y =. max_ik then y else + y -. (modulo (y -. c) (Ints_t.abs m)) in + if rcx >. lcy then [] + else if rcx =. lcy then norm_interval ik (rcx, rcx) |> fst + else norm_interval ik (rcx, lcy) |> fst + | _ -> [] + in + List.concat_map (fun x -> refine_with_congruence_interval ik cong (Some x)) intvs + + let refine_with_interval ik xs = function None -> [] | Some (a,b) -> meet ik xs [(a,b)] + + let refine_with_incl_list ik intvs = function + | None -> intvs + | Some xs -> meet ik intvs (List.map (fun x -> (x,x)) xs) + + let excl_range_to_intervalset (ik: ikind) ((min, max): int_t * int_t) (excl: int_t): t = + let intv1 = (min, excl -. Ints_t.one) in + let intv2 = (excl +. Ints_t.one, max) in + norm_intvs ik ~suppress_ovwarn:true [intv1 ; intv2] |> fst + + let of_excl_list ik (excls: int_t list) = + let excl_list = List.map (excl_range_to_intervalset ik (range ik)) excls in + let res = List.fold_left (meet ik) (top_of ik) excl_list in + res + + let refine_with_excl_list ik (intv : t) = function + | None -> intv + | Some (xs, range) -> + let excl_to_intervalset (ik: ikind) ((rl, rh): (int64 * int64)) (excl: int_t): t = + excl_range_to_intervalset ik (Ints_t.of_bigint (Size.min_from_bit_range rl),Ints_t.of_bigint (Size.max_from_bit_range rh)) excl + in + let excl_list = List.map (excl_to_intervalset ik range) xs in + List.fold_left (meet ik) intv excl_list let project ik p t = t + + let arbitrary ik = + let open QCheck.Iter in + (* let int_arb = QCheck.map ~rev:Ints_t.to_bigint Ints_t.of_bigint MyCheck.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 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 + in QCheck.(set_shrink shrink @@ set_print show @@ map (*~rev:BatOption.get*) canonize_randomly_generated_list list_pair_arb) end +module SOverflowUnlifter (D : SOverflow) : S with type int_t = D.int_t and type t = D.t = struct + include D + + let add ?no_ov ik x y = fst @@ D.add ?no_ov ik x y + + let sub ?no_ov ik x y = fst @@ D.sub ?no_ov ik x y + + let mul ?no_ov ik x y = fst @@ D.mul ?no_ov ik x y + + let div ?no_ov ik x y = fst @@ D.div ?no_ov ik x y + + let neg ?no_ov ik x = fst @@ D.neg ?no_ov ik x + + let cast_to ?torg ?no_ov ik x = fst @@ D.cast_to ?torg ?no_ov ik x + + let of_int ik x = fst @@ D.of_int ik x + + let of_interval ?suppress_ovwarn ik x = fst @@ D.of_interval ?suppress_ovwarn ik x + + let starting ?suppress_ovwarn ik x = fst @@ D.starting ?suppress_ovwarn ik x + + let ending ?suppress_ovwarn ik x = fst @@ D.ending ?suppress_ovwarn ik x + + let shift_left ik x y = fst @@ D.shift_left ik x y + + let shift_right ik x y = fst @@ D.shift_right ik x y +end module IntIkind = struct let ikind () = Cil.IInt end module Interval = IntervalFunctor (BI) -module Interval32 = IntDomWithDefaultIkind (IntDomLifter (IntervalFunctor (IntOps.Int64Ops))) (IntIkind) - +module Interval32 = IntDomWithDefaultIkind (IntDomLifter ( SOverflowUnlifter (IntervalFunctor (IntOps.Int64Ops)) ) ) (IntIkind) +module IntervalSet = IntervalSetFunctor(BI) module Integers(Ints_t : IntOps.IntOps): IkindUnawareS with type t = Ints_t.t and type int_t = Ints_t.t = (* no top/bot, order is <= *) struct include Printable.Std @@ -1202,7 +1892,7 @@ struct else (* The cardinality did fit, so we check for all elements that are represented by range r, whether they are in (xs union ys) *) let min_a = min_of_range r in let max_a = max_of_range r in - GU.for_all_in_range (min_a, max_a) (fun el -> BISet.mem el xs || BISet.mem el ys) + GobZ.for_all_range (fun el -> BISet.mem el xs || BISet.mem el ys) (min_a, max_a) let leq (Exc (xs, r)) (Exc (ys, s)) = let min_a, max_a = min_of_range r, max_of_range r in @@ -1216,13 +1906,13 @@ struct let min_b, max_b = min_of_range s, max_of_range s in let leq1 = (* check whether the elements in [r_l; s_l-1] are all in xs, i.e. excluded *) if I.compare min_a min_b < 0 then - GU.for_all_in_range (min_a, BI.sub min_b BI.one) (fun x -> BISet.mem x xs) + GobZ.for_all_range (fun x -> BISet.mem x xs) (min_a, BI.sub min_b BI.one) else true in let leq2 () = (* check whether the elements in [s_u+1; r_u] are all in xs, i.e. excluded *) if I.compare max_b max_a < 0 then - GU.for_all_in_range (BI.add max_b BI.one, max_a) (fun x -> BISet.mem x xs) + GobZ.for_all_range (fun x -> BISet.mem x xs) (BI.add max_b BI.one, max_a) else true in @@ -1293,83 +1983,87 @@ struct let is_top x = x = top () let equal_to i = function - | `Bot -> failwith "unsupported: equal_to with bottom" - | `Definite x -> if i = x then `Eq else `Neq - | `Excluded (s,r) -> if S.mem i s then `Neq else `Top + | `Bot -> failwith "unsupported: equal_to with bottom" + | `Definite x -> if i = x then `Eq else `Neq + | `Excluded (s,r) -> if S.mem i s then `Neq else `Top let top_of ik = `Excluded (S.empty (), size ik) let cast_to ?torg ?no_ov ik = function | `Excluded (s,r) -> let r' = size ik in - `Excluded ( if R.leq r r' then (* upcast -> no change *) - s, r - else (* downcast: may overflow *) + `Excluded (s, r) + else if ik = IBool then (* downcast to bool *) + if S.mem BI.zero s then + `Definite (BI.one) + else + `Excluded (S.empty(), r') + 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. *) (* Since this is hard to compute, we just keep all i in s' which overflowed, since those are safe - all i which did not overflow may now be possible due to overflow of r. *) (* S.diff s' s, r' *) (* The above is needed for test 21/03, but not sound! See example https://github.com/goblint/analyzer/pull/95#discussion_r483023140 *) - S.empty (), r' - ) + `Excluded (S.empty (), r') | `Definite x -> `Definite (BigInt.cast_to ik x) | `Bot -> `Bot - (* Wraps definite values and excluded values according to the ikind. - * For an `Excluded s,r , assumes that r is already an overapproximation of the range of possible values. - * r might be larger than the possible range of this type; the range of the returned `Excluded set will be within the bounds of the ikind. - *) - let norm ik v = - match v with - | `Excluded (s, r) -> - let possibly_overflowed = not (R.leq r (size ik)) || not (S.for_all (in_range (size ik)) s) in - (* If no overflow occurred, just return x *) - if not possibly_overflowed then ( - v - ) - (* Else, if an overflow might have occurred but we should just ignore it *) - else if should_ignore_overflow ik then ( - let r = size ik in - (* filter out excluded elements that are not in the range *) - let mapped_excl = S.filter (in_range r) s in - `Excluded (mapped_excl, r) - ) - (* Else, if an overflow occurred that we should not treat with wrap-around, go to top *) - else if not (should_wrap ik) then ( - top_of ik - ) else ( - (* Else an overflow occurred that we should treat with wrap-around *) - let r = size ik in - (* Perform a wrap-around for unsigned values and for signed values (if configured). *) - let mapped_excl = S.map (fun excl -> BigInt.cast_to ik excl) s in - match ik with - | IBool -> - begin match S.mem BigInt.zero mapped_excl, S.mem BigInt.one mapped_excl with - | false, false -> `Excluded (mapped_excl, r) (* Not {} -> Not {} *) - | true, false -> `Definite BigInt.one (* Not {0} -> 1 *) - | false, true -> `Definite BigInt.zero (* Not {1} -> 0 *) - | true, true -> `Bot (* Not {0, 1} -> bot *) - end - | ik -> - `Excluded (mapped_excl, r) - ) - | `Definite x -> - let min, max = Size.range ik in + (* Wraps definite values and excluded values according to the ikind. + * For an `Excluded s,r , assumes that r is already an overapproximation of the range of possible values. + * r might be larger than the possible range of this type; the range of the returned `Excluded set will be within the bounds of the ikind. + *) + let norm ik v = + match v with + | `Excluded (s, r) -> + let possibly_overflowed = not (R.leq r (size ik)) || not (S.for_all (in_range (size ik)) s) in + (* If no overflow occurred, just return x *) + if not possibly_overflowed then ( + v + ) + (* Else, if an overflow might have occurred but we should just ignore it *) + else if should_ignore_overflow ik then ( + let r = size ik in + (* filter out excluded elements that are not in the range *) + let mapped_excl = S.filter (in_range r) s in + `Excluded (mapped_excl, r) + ) + (* Else, if an overflow occurred that we should not treat with wrap-around, go to top *) + else if not (should_wrap ik) then ( + top_of ik + ) else ( + (* Else an overflow occurred that we should treat with wrap-around *) + let r = size ik in (* Perform a wrap-around for unsigned values and for signed values (if configured). *) - if should_wrap ik then ( - cast_to ik v - ) - else if BigInt.compare min x <= 0 && BigInt.compare x max <= 0 then ( - v - ) - else if should_ignore_overflow ik then ( - M.warn ~category:M.Category.Integer.overflow "DefExc: Value was outside of range, indicating overflow, but 'sem.int.signed_overflow' is 'assume_none' -> Returned Bot"; - `Bot - ) - else ( - top_of ik - ) - | `Bot -> `Bot + let mapped_excl = S.map (fun excl -> BigInt.cast_to ik excl) s in + match ik with + | IBool -> + begin match S.mem BigInt.zero mapped_excl, S.mem BigInt.one mapped_excl with + | false, false -> `Excluded (mapped_excl, r) (* Not {} -> Not {} *) + | true, false -> `Definite BigInt.one (* Not {0} -> 1 *) + | false, true -> `Definite BigInt.zero (* Not {1} -> 0 *) + | true, true -> `Bot (* Not {0, 1} -> bot *) + end + | ik -> + `Excluded (mapped_excl, r) + ) + | `Definite x -> + let min, max = Size.range ik in + (* Perform a wrap-around for unsigned values and for signed values (if configured). *) + if should_wrap ik then ( + cast_to ik v + ) + else if BigInt.compare min x <= 0 && BigInt.compare x max <= 0 then ( + v + ) + else if should_ignore_overflow ik then ( + M.warn ~category:M.Category.Integer.overflow "DefExc: Value was outside of range, indicating overflow, but 'sem.int.signed_overflow' is 'assume_none' -> Returned Bot"; + `Bot + ) + else ( + top_of ik + ) + | `Bot -> `Bot let leq x y = match (x,y) with (* `Bot <= x is always true *) @@ -1414,8 +2108,9 @@ struct let join ik = join' ik + let widen ik x y = - if get_bool "ana.int.def_exc_widen_by_join" then + if get_def_exc_widen_by_join () then join' ik x y else if equal x y then x @@ -1497,36 +2192,36 @@ struct | `Bot -> `Bot let lift2 f ik x y = norm ik (match x,y with - (* We don't bother with exclusion sets: *) - | `Excluded _, `Definite _ - | `Definite _, `Excluded _ - | `Excluded _, `Excluded _ -> top () - (* The good case: *) - | `Definite x, `Definite y -> - (try `Definite (f x y) with | Division_by_zero -> top ()) - | `Bot, `Bot -> `Bot - | _ -> - (* If only one of them is bottom, we raise an exception that eval_rv will catch *) - raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y)))) + (* We don't bother with exclusion sets: *) + | `Excluded _, `Definite _ + | `Definite _, `Excluded _ + | `Excluded _, `Excluded _ -> top () + (* The good case: *) + | `Definite x, `Definite y -> + (try `Definite (f x y) with | Division_by_zero -> top ()) + | `Bot, `Bot -> `Bot + | _ -> + (* If only one of them is bottom, we raise an exception that eval_rv will catch *) + raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y)))) (* Default behaviour for binary operators that are injective in either * argument, so that Exclusion Sets can be used: *) let lift2_inj f ik x y = let def_exc f x s r = `Excluded (S.map (f x) s, apply_range (f x) r) in norm ik @@ - match x,y with - (* If both are exclusion sets, there isn't anything we can do: *) - | `Excluded _, `Excluded _ -> top () - (* A definite value should be applied to all members of the exclusion set *) - | `Definite x, `Excluded (s,r) -> def_exc f x s r - (* Same thing here, but we should flip the operator to map it properly *) - | `Excluded (s,r), `Definite x -> def_exc (Batteries.flip f) x s r - (* The good case: *) - | `Definite x, `Definite y -> `Definite (f x y) - | `Bot, `Bot -> `Bot - | _ -> - (* If only one of them is bottom, we raise an exception that eval_rv will catch *) - raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) + match x,y with + (* If both are exclusion sets, there isn't anything we can do: *) + | `Excluded _, `Excluded _ -> top () + (* A definite value should be applied to all members of the exclusion set *) + | `Definite x, `Excluded (s,r) -> def_exc f x s r + (* Same thing here, but we should flip the operator to map it properly *) + | `Excluded (s,r), `Definite x -> def_exc (Batteries.flip f) x s r + (* The good case: *) + | `Definite x, `Definite y -> `Definite (f x y) + | `Bot, `Bot -> `Bot + | _ -> + (* If only one of them is bottom, we raise an exception that eval_rv will catch *) + raise (ArithmeticOnIntegerBot (Printf.sprintf "%s op %s" (show x) (show y))) (* The equality check: *) let eq ik x y = match x,y with @@ -1599,7 +2294,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 @@ -1842,13 +2558,18 @@ module Enums : S with type int_t = BigInt.t = struct let r' = size ik in if R.leq r r' then (* upcast -> no change *) Exc (s, r) + else if ik = IBool then (* downcast to bool *) + if BISet.mem BI.zero s then + Inc (BISet.singleton BI.one) + else + Exc (BISet.empty(), r') else (* downcast: may overflow *) Exc ((BISet.empty ()), r') | Inc xs -> let casted_xs = BISet.map (BigInt.cast_to ik) xs in if Cil.isSigned ik && not (BISet.equal xs casted_xs) - then top_of ik (* When casting into a signed type and the result does not fit, the behavior is implementation-defined *) - else Inc casted_xs + then top_of ik (* When casting into a signed type and the result does not fit, the behavior is implementation-defined *) + else Inc casted_xs let of_int ikind x = cast_to ikind (Inc (BISet.singleton x)) @@ -1914,9 +2635,9 @@ module Enums : S with type int_t = BigInt.t = struct let lift2 f (ikind: Cil.ikind) u v = handle_bot u v (fun () -> - norm ikind @@ match u, v with - | Inc x,Inc y when BISet.is_singleton x && BISet.is_singleton y -> Inc (BISet.singleton (f (BISet.choose x) (BISet.choose y))) - | _,_ -> top_of ikind) + norm ikind @@ match u, v with + | Inc x,Inc y when BISet.is_singleton x && BISet.is_singleton y -> Inc (BISet.singleton (f (BISet.choose x) (BISet.choose y))) + | _,_ -> top_of ikind) let lift2 f ikind a b = try lift2 f ikind a b with Division_by_zero -> top_of ikind @@ -1950,18 +2671,18 @@ module Enums : S with type int_t = BigInt.t = struct let shift (shift_op: int_t -> int -> int_t) (ik: Cil.ikind) (x: t) (y: t) = handle_bot x y (fun () -> - (* BigInt only accepts int as second argument for shifts; perform conversion here *) - let shift_op_big_int a (b: int_t) = - let (b : int) = BI.to_int b in - shift_op a b - in - (* If one of the parameters of the shift is negative, the result is undefined *) - let x_min = minimal x in - let y_min = minimal y in - if x_min = None || y_min = None || BI.compare (Option.get x_min) BI.zero < 0 || BI.compare (Option.get y_min) BI.zero < 0 then - top_of ik - else - lift2 shift_op_big_int ik x y) + (* BigInt only accepts int as second argument for shifts; perform conversion here *) + let shift_op_big_int a (b: int_t) = + let (b : int) = BI.to_int b in + shift_op a b + in + (* If one of the parameters of the shift is negative, the result is undefined *) + let x_min = minimal x in + let y_min = minimal y in + if x_min = None || y_min = None || BI.compare (Option.get x_min) BI.zero < 0 || BI.compare (Option.get y_min) BI.zero < 0 then + top_of ik + else + lift2 shift_op_big_int ik x y) let shift_left = shift BigInt.shift_left @@ -1995,8 +2716,8 @@ module Enums : S with type int_t = BigInt.t = struct then x else match to_bool x with - | Some b -> of_bool ik (not b) - | None -> top_bool + | Some b -> of_bool ik (not b) + | None -> top_bool let logand = lift2 I.logand let logor = lift2 I.logor @@ -2026,32 +2747,32 @@ module Enums : S with type int_t = BigInt.t = struct let lt ik x y = handle_bot x y (fun () -> - match minimal x, maximal x, minimal y, maximal y with - | _, Some x2, Some y1, _ when I.compare x2 y1 < 0 -> of_bool ik true - | Some x1, _, _, Some y2 when I.compare x1 y2 >= 0 -> of_bool ik false - | _, _, _, _ -> top_bool) + match minimal x, maximal x, minimal y, maximal y with + | _, Some x2, Some y1, _ when I.compare x2 y1 < 0 -> of_bool ik true + | Some x1, _, _, Some y2 when I.compare x1 y2 >= 0 -> of_bool ik false + | _, _, _, _ -> top_bool) let gt ik x y = lt ik y x let le ik x y = handle_bot x y (fun () -> - match minimal x, maximal x, minimal y, maximal y with - | _, Some x2, Some y1, _ when I.compare x2 y1 <= 0 -> of_bool ik true - | Some x1, _, _, Some y2 when I.compare x1 y2 > 0 -> of_bool ik false - | _, _, _, _ -> top_bool) + match minimal x, maximal x, minimal y, maximal y with + | _, Some x2, Some y1, _ when I.compare x2 y1 <= 0 -> of_bool ik true + | Some x1, _, _, Some y2 when I.compare x1 y2 > 0 -> of_bool ik false + | _, _, _, _ -> top_bool) let ge ik x y = le ik y x let eq ik x y = handle_bot x y (fun () -> - match x, y with + match x, y with | Inc xs, Inc ys when BISet.is_singleton xs && BISet.is_singleton ys -> of_bool ik (I.equal (BISet.choose xs) (BISet.choose ys)) - | _, _ -> - if is_bot (meet ik x y) then - (* If the meet is empty, there is no chance that concrete values are equal *) - of_bool ik false - else - top_bool) + | _, _ -> + if is_bot (meet ik x y) then + (* If the meet is empty, there is no chance that concrete values are equal *) + of_bool ik false + else + top_bool) let ne ik x y = lognot ik (eq ik x y) @@ -2061,7 +2782,7 @@ module Enums : S with type int_t = BigInt.t = struct if BISet.cardinal ps > 1 || get_bool "witness.invariant.exact" then List.fold_left (fun a x -> let i = Invariant.of_exp Cil.(BinOp (Eq, e, kintegerCilint ik x, intType)) in - Invariant.(a || i) + Invariant.(a || i) [@coverage off] (* bisect_ppx cannot handle redefined (||) *) ) (Invariant.bot ()) (BISet.elements ps) else Invariant.top () @@ -2170,7 +2891,7 @@ struct let equal_to i = function | None -> failwith "unsupported: equal_to with bottom" | Some (a, b) when b =: Ints_t.zero -> if a =: i then `Eq else `Neq - | Some (a, b) -> if i %: b =: a then `Top else `Neq + | Some (a, b) -> if i %: b =: a then `Top else `Neq let leq (x:t) (y:t) = match x, y with @@ -2179,7 +2900,7 @@ struct | Some (c1,m1), Some (c2,m2) when m2 =: Ints_t.zero && m1 =: Ints_t.zero -> c1 =: c2 | Some (c1,m1), Some (c2,m2) when m2 =: Ints_t.zero -> c1 =: c2 && m1 =: Ints_t.zero | Some (c1,m1), Some (c2,m2) -> m2 |: (Ints_t.gcd (c1 -: c2) m1) - (* Typo in original equation of P. Granger (m2 instead of m1): gcd (c1 -: c2) m2 + (* Typo in original equation of P. Granger (m2 instead of m1): gcd (c1 -: c2) m2 Reference: https://doi.org/10.1080/00207168908803778 Page 171 corollary 3.3*) let leq x y = @@ -2320,8 +3041,8 @@ struct let shift_right ik x y = let res = shift_right ik x y in - if M.tracing then M.trace "congruence" "shift_right : %a %a becomes %a \n" pretty x pretty y pretty res; - res + if M.tracing then M.trace "congruence" "shift_right : %a %a becomes %a \n" pretty x pretty y pretty res; + res let shift_left ik x y = (* Naive primality test *) @@ -2446,7 +3167,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))) @@ -2456,7 +3177,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 @@ -2466,8 +3199,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 @@ -2541,6 +3274,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 @@ -2561,8 +3295,6 @@ struct let to_pair = Option.get in set_print show (map ~rev:to_pair (of_pair ik) cong_arb) - let relift x = x - let refine_with_interval ik (cong : t) (intv : (int_t * int_t ) option) : t = match intv, cong with | Some (x, y), Some (c, m) -> @@ -2578,8 +3310,8 @@ struct let refine_with_interval ik (cong : t) (intv : (int_t * int_t) option) : t = let pretty_intv _ i = (match i with - | Some(l, u) -> let s = "["^Ints_t.to_string l^","^Ints_t.to_string u^"]" in Pretty.text s - | _ -> Pretty.text ("Display Error")) in + | Some(l, u) -> let s = "["^Ints_t.to_string l^","^Ints_t.to_string u^"]" in Pretty.text s + | _ -> Pretty.text ("Display Error")) in let refn = refine_with_interval ik cong intv in if M.tracing then M.trace "refine" "cong_refine_with_interval %a %a -> %a\n" pretty cong pretty_intv intv pretty refn; refn @@ -2591,6 +3323,40 @@ struct let project ik p t = t end +module SOverflowLifter (D : S) : SOverflow with type int_t = D.int_t and type t = D.t = struct + + include D + + let lift v = (v, {overflow=false; underflow=false}) + + let add ?no_ov ik x y = lift @@ D.add ?no_ov ik x y + + let sub ?no_ov ik x y = lift @@ D.sub ?no_ov ik x y + + let mul ?no_ov ik x y = lift @@ D.mul ?no_ov ik x y + + let div ?no_ov ik x y = lift @@ D.div ?no_ov ik x y + + let neg ?no_ov ik x = lift @@ D.neg ?no_ov ik x + + let cast_to ?torg ?no_ov ik x = lift @@ D.cast_to ?torg ?no_ov ik x + + let of_int ik x = lift @@ D.of_int ik x + + let of_interval ?suppress_ovwarn ik x = lift @@ D.of_interval ?suppress_ovwarn ik x + + let starting ?suppress_ovwarn ik x = lift @@ D.starting ?suppress_ovwarn ik x + + let ending ?suppress_ovwarn ik x = lift @@ D.ending ?suppress_ovwarn ik x + + let shift_left ik x y = lift @@ D.shift_left ik x y + + let shift_right ik x y = lift @@ D.shift_right ik x y + +end + + + (* The old IntDomList had too much boilerplate since we had to edit every function in S when adding a new domain. With the following, we only have to edit the places where fn are applied, i.e., create, mapp, map, map2. You can search for I3 below to see where you need to extend. *) (* discussion: https://github.com/goblint/analyzer/pull/188#issuecomment-818928540 *) module IntDomTupleImpl = struct @@ -2598,150 +3364,192 @@ module IntDomTupleImpl = struct open Batteries type int_t = BI.t - module I1 = DefExc + module I1 = SOverflowLifter(DefExc) module I2 = Interval - module I3 = Enums - module I4 = Congruence + module I3 = SOverflowLifter(Enums) + module I4 = SOverflowLifter(Congruence) + module I5 = IntervalSetFunctor (BI) - type t = I1.t option * I2.t option * I3.t option * I4.t option + type t = I1.t option * I2.t option * I3.t option * I4.t option * I5.t option [@@deriving to_yojson, eq, ord] let name () = "intdomtuple" (* The Interval domain can lead to too many contexts for recursive functions (top is [min,max]), but we don't want to drop all ints as with `ana.base.context.int`. TODO better solution? *) - let no_interval = Tuple4.map2 (const None) + let no_interval = Tuple5.map2 (const None) + let no_intervalSet = Tuple5.map5 (const None) - type 'a m = (module S with type t = 'a) - type 'a m2 = (module S with type t = 'a and type int_t = int_t ) + type 'a m = (module SOverflow with type t = 'a) + type 'a m2 = (module SOverflow with type t = 'a and type int_t = int_t ) (* only first-order polymorphism on functions -> use records to get around monomorphism restriction on arguments *) type 'b poly_in = { fi : 'a. 'a m -> 'b -> 'a } (* inject *) type 'b poly2_in = { fi2 : 'a. 'a m2 -> 'b -> 'a } (* inject for functions that depend on int_t *) + type 'b poly2_in_ovc = { fi2_ovc : 'a. 'a m2 -> 'b -> 'a * overflow_info} (* inject for functions that depend on int_t *) + type 'b poly_pr = { fp : 'a. 'a m -> 'a -> 'b } (* project *) type 'b poly_pr2 = { fp2 : 'a. 'a m2 -> 'a -> 'b } (* project for functions that depend on int_t *) type 'b poly2_pr = {f2p: 'a. 'a m -> ?no_ov:bool -> 'a -> 'a -> 'b} type poly1 = {f1: 'a. 'a m -> ?no_ov:bool -> 'a -> 'a} (* needed b/c above 'b must be different from 'a *) + type poly1_ovc = {f1_ovc: 'a. 'a m -> ?no_ov:bool -> 'a -> 'a * overflow_info } (* needed b/c above 'b must be different from 'a *) type poly2 = {f2: 'a. 'a m -> ?no_ov:bool -> 'a -> 'a -> 'a} + type poly2_ovc = {f2_ovc: 'a. 'a m -> ?no_ov:bool -> 'a -> 'a -> 'a * overflow_info } type 'b poly3 = { f3: 'a. 'a m -> 'a option } (* used for projection to given precision *) - let create r x ((p1, p2, p3, p4): int_precision) = + let create r x ((p1, p2, p3, p4, p5): int_precision) = let f b g = if b then Some (g x) else None in - f p1 @@ r.fi (module I1), f p2 @@ r.fi (module I2), f p3 @@ r.fi (module I3), f p4 @@ r.fi (module I4) + f p1 @@ r.fi (module I1), f p2 @@ r.fi (module I2), f p3 @@ r.fi (module I3), f p4 @@ r.fi (module I4), f p5 @@ r.fi (module I5) let create r x = (* use where values are introduced *) create r x (int_precision_from_node_or_config ()) - let create2 r x ((p1, p2, p3, p4): int_precision) = + let create2 r x ((p1, p2, p3, p4, p5): int_precision) = let f b g = if b then Some (g x) else None in - f p1 @@ r.fi2 (module I1), f p2 @@ r.fi2 (module I2), f p3 @@ r.fi2 (module I3), f p4 @@ r.fi2 (module I4) + f p1 @@ r.fi2 (module I1), f p2 @@ r.fi2 (module I2), f p3 @@ r.fi2 (module I3), f p4 @@ r.fi2 (module I4), f p5 @@ r.fi2 (module I5) let create2 r x = (* use where values are introduced *) create2 r x (int_precision_from_node_or_config ()) + let no_overflow ik = function + | Some(_, {underflow; overflow}) -> not (underflow || overflow) + | _ -> false + + let check_ov 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; + ); + no_ov + + let create2_ovc ik r x ((p1, p2, p3, p4, p5): int_precision) = + let f b g = if b then Some (g x) else None in + let map x = Option.map fst x in + let intv = f p2 @@ r.fi2_ovc (module I2) in + let intv_set = f p5 @@ r.fi2_ovc (module I5) in + ignore (check_ov ik intv intv_set); + map @@ f p1 @@ r.fi2_ovc (module I1), map @@ f p2 @@ r.fi2_ovc (module I2), map @@ f p3 @@ r.fi2_ovc (module I3), map @@ f p4 @@ r.fi2_ovc (module I4), map @@ f p5 @@ r.fi2_ovc (module I5) + + let create2_ovc ik r x = (* use where values are introduced *) + create2_ovc ik r x (int_precision_from_node_or_config ()) + + let opt_map2 f ?no_ov = curry @@ function Some x, Some y -> Some (f ?no_ov x y) | _ -> None - let to_list x = Tuple4.enum x |> List.of_enum |> List.filter_map identity (* contains only the values of activated domains *) + let to_list x = Tuple5.enum x |> List.of_enum |> List.filter_map identity (* contains only the values of activated domains *) let to_list_some x = List.filter_map identity @@ to_list x (* contains only the Some-values of activated domains *) let exists = function - | (Some true, _, _, _) - | (_, Some true, _, _) - | (_, _, Some true, _) - | (_, _, _, Some true) -> + | (Some true, _, _, _, _) + | (_, Some true, _, _, _) + | (_, _, Some true, _, _) + | (_, _, _, Some true, _) + | (_, _, _, _, Some true) -> true | _ -> false let for_all = function - | (Some false, _, _, _) - | (_, Some false, _, _) - | (_, _, Some false, _) - | (_, _, _, Some false) -> + | (Some false, _, _, _, _) + | (_, Some false, _, _, _) + | (_, _, Some false, _, _) + | (_, _, _, Some false, _) + | (_, _, _, _, Some false) -> false | _ -> true (* f0: constructors *) - let top () = create { fi = fun (type a) (module I:S with type t = a) -> I.top } () - let bot () = create { fi = fun (type a) (module I:S with type t = a) -> I.bot } () - let top_of = create { fi = fun (type a) (module I:S with type t = a) -> I.top_of } - let bot_of = create { fi = fun (type a) (module I:S with type t = a) -> I.bot_of } - let of_bool ik = create { fi = fun (type a) (module I:S with type t = a) -> I.of_bool ik } - let of_excl_list ik = create2 { fi2 = fun (type a) (module I:S with type t = a and type int_t = int_t) -> I.of_excl_list ik} - let of_int ik = create2 { fi2 = fun (type a) (module I:S with type t = a and type int_t = int_t) -> I.of_int ik } - let starting ?(suppress_ovwarn=false) ik = create2 { fi2 = fun (type a) (module I:S with type t = a and type int_t = int_t) -> I.starting ~suppress_ovwarn ik } - let ending ?(suppress_ovwarn=false) ik = create2 { fi2 = fun (type a) (module I:S with type t = a and type int_t = int_t) -> I.ending ~suppress_ovwarn ik } - let of_interval ?(suppress_ovwarn=false) ik = create2 { fi2 = fun (type a) (module I:S with type t = a and type int_t = int_t) -> I.of_interval ~suppress_ovwarn ik } - let of_congruence ik = create2 { fi2 = fun (type a) (module I:S with type t = a and type int_t = int_t) -> I.of_congruence ik } - - let refine_with_congruence ik ((a, b, c, d) : t) (cong : (int_t * int_t) option) : t= + let top () = create { fi = fun (type a) (module I:SOverflow with type t = a) -> I.top } () + let bot () = create { fi = fun (type a) (module I:SOverflow with type t = a) -> I.bot } () + let top_of = create { fi = fun (type a) (module I:SOverflow with type t = a) -> I.top_of } + let bot_of = create { fi = fun (type a) (module I:SOverflow with type t = a) -> I.bot_of } + let of_bool ik = create { fi = fun (type a) (module I:SOverflow with type t = a) -> I.of_bool ik } + let of_excl_list ik = create2 { fi2 = fun (type a) (module I:SOverflow with type t = a and type int_t = int_t) -> I.of_excl_list ik} + let of_int ik = create2_ovc ik { fi2_ovc = fun (type a) (module I:SOverflow with type t = a and type int_t = int_t) -> I.of_int ik } + let starting ?(suppress_ovwarn=false) ik = create2_ovc ik { fi2_ovc = fun (type a) (module I:SOverflow with type t = a and type int_t = int_t) -> I.starting ~suppress_ovwarn ik } + let ending ?(suppress_ovwarn=false) ik = create2_ovc ik { fi2_ovc = fun (type a) (module I:SOverflow with type t = a and type int_t = int_t) -> I.ending ~suppress_ovwarn ik } + let of_interval ?(suppress_ovwarn=false) ik = create2_ovc ik { fi2_ovc = fun (type a) (module I:SOverflow with type t = a and type int_t = int_t) -> I.of_interval ~suppress_ovwarn ik } + let of_congruence ik = create2 { fi2 = fun (type a) (module I:SOverflow with type t = a and type int_t = int_t) -> I.of_congruence ik } + + let refine_with_congruence ik ((a, b, c, d, e) : t) (cong : (int_t * int_t) option) : t= let opt f a = curry @@ function Some x, y -> Some (f a x y) | _ -> None in ( opt I1.refine_with_congruence ik a cong , opt I2.refine_with_congruence ik b cong , opt I3.refine_with_congruence ik c cong - , opt I4.refine_with_congruence ik d cong ) + , opt I4.refine_with_congruence ik d cong + , opt I5.refine_with_congruence ik e cong) - let refine_with_interval ik (a, b, c, d) intv = + let refine_with_interval ik (a, b, c, d, e) intv = let opt f a = curry @@ function Some x, y -> Some (f a x y) | _ -> None in ( opt I1.refine_with_interval ik a intv , opt I2.refine_with_interval ik b intv , opt I3.refine_with_interval ik c intv - , opt I4.refine_with_interval ik d intv ) + , opt I4.refine_with_interval ik d intv + , opt I5.refine_with_interval ik e intv ) - let refine_with_excl_list ik (a, b, c, d) excl = + let refine_with_excl_list ik (a, b, c, d, e) excl = let opt f a = curry @@ function Some x, y -> Some (f a x y) | _ -> None in ( opt I1.refine_with_excl_list ik a excl , opt I2.refine_with_excl_list ik b excl , opt I3.refine_with_excl_list ik c excl - , opt I4.refine_with_excl_list ik d excl ) + , opt I4.refine_with_excl_list ik d excl + , opt I5.refine_with_excl_list ik e excl ) - let refine_with_incl_list ik (a, b, c, d) incl = + let refine_with_incl_list ik (a, b, c, d, e) incl = let opt f a = curry @@ function Some x, y -> Some (f a x y) | _ -> None in ( opt I1.refine_with_incl_list ik a incl , opt I2.refine_with_incl_list ik b incl , opt I3.refine_with_incl_list ik c incl - , opt I4.refine_with_incl_list ik d incl ) + , opt I4.refine_with_incl_list ik d incl + , opt I5.refine_with_incl_list ik e incl ) - let mapp r (a, b, c, d) = + let mapp r (a, b, c, d, e) = let map = BatOption.map in ( map (r.fp (module I1)) a , map (r.fp (module I2)) b , map (r.fp (module I3)) c - , map (r.fp (module I4)) d) + , map (r.fp (module I4)) d + , map (r.fp (module I5)) e) - let mapp2 r (a, b, c, d) = + let mapp2 r (a, b, c, d, e) = BatOption. - ( map (r.fp2 (module I1)) a - , map (r.fp2 (module I2)) b - , map (r.fp2 (module I3)) c - , map (r.fp2 (module I4)) d ) + ( map (r.fp2 (module I1)) a + , map (r.fp2 (module I2)) b + , map (r.fp2 (module I3)) c + , map (r.fp2 (module I4)) d + , map (r.fp2 (module I5)) e) (* exists/for_all *) - let is_bot = exists % mapp { fp = fun (type a) (module I:S with type t = a) -> I.is_bot } - let is_top = for_all % mapp { fp = fun (type a) (module I:S with type t = a) -> I.is_top } - let is_top_of ik = for_all % mapp { fp = fun (type a) (module I:S with type t = a) -> I.is_top_of ik } - let is_excl_list = exists % mapp { fp = fun (type a) (module I:S with type t = a) -> I.is_excl_list } - - let map2p r (xa, xb, xc, xd) (ya, yb, yc, yd) = - ( opt_map2 (r.f2p (module I1)) xa ya - , opt_map2 (r.f2p (module I2)) xb yb - , opt_map2 (r.f2p (module I3)) xc yc - , opt_map2 (r.f2p (module I4)) xd yd ) + let is_bot = exists % mapp { fp = fun (type a) (module I:SOverflow with type t = a) -> I.is_bot } + let is_top = for_all % mapp { fp = fun (type a) (module I:SOverflow with type t = a) -> I.is_top } + let is_top_of ik = for_all % mapp { fp = fun (type a) (module I:SOverflow with type t = a) -> I.is_top_of ik } + let is_excl_list = exists % mapp { fp = fun (type a) (module I:SOverflow with type t = a) -> I.is_excl_list } + + let map2p r (xa, xb, xc, xd, xe) (ya, yb, yc, yd, ye) = + ( opt_map2 (r.f2p (module I1)) xa ya + , opt_map2 (r.f2p (module I2)) xb yb + , opt_map2 (r.f2p (module I3)) xc yc + , opt_map2 (r.f2p (module I4)) xd yd + , opt_map2 (r.f2p (module I5)) xe ye) (* f2p: binary projections *) let (%%) f g x = f % (g x) (* composition for binary function g *) let leq = for_all - %% map2p {f2p= (fun (type a) (module I : S with type t = a) ?no_ov -> I.leq)} + %% map2p {f2p= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.leq)} let flat f x = match to_list_some x with [] -> None | xs -> Some (f xs) @@ -2751,7 +3559,7 @@ module IntDomTupleImpl = struct let (mins, maxs) = List.split rs in (List.concat vs, (List.min mins, List.max maxs)) in - mapp2 { fp2 = fun (type a) (module I:S with type t = a and type int_t = int_t) -> I.to_excl_list } x |> flat merge + mapp2 { fp2 = fun (type a) (module I:SOverflow with type t = a and type int_t = int_t) -> I.to_excl_list } x |> flat merge let to_incl_list x = let hd l = match l with h::t -> h | _ -> [] in @@ -2760,104 +3568,103 @@ module IntDomTupleImpl = struct let b y = BatList.map BatSet.of_list (tl y) in let merge y = BatSet.elements @@ BatList.fold BatSet.intersect (a y) (b y) in - mapp2 { fp2 = fun (type a) (module I:S with type t = a and type int_t = int_t) -> I.to_incl_list } x |> flat merge + mapp2 { fp2 = fun (type a) (module I:SOverflow with type t = a and type int_t = int_t) -> I.to_incl_list } x |> flat merge - let pretty () = (fun xs -> text "(" ++ (try List.reduce (fun a b -> a ++ text "," ++ b) xs with Invalid_argument _ -> nil) ++ text ")") % to_list % mapp { fp = fun (type a) (module I:S with type t = a) -> (* assert sf==I.short; *) I.pretty () } (* NOTE: the version above does something else. also, we ignore the sf-argument here. *) + let pretty () = (fun xs -> text "(" ++ (try List.reduce (fun a b -> a ++ text "," ++ b) xs with Invalid_argument _ -> nil) ++ text ")") % to_list % mapp { fp = fun (type a) (module I:SOverflow with type t = a) -> (* assert sf==I.short; *) I.pretty () } (* NOTE: the version above does something else. also, we ignore the sf-argument here. *) let refine_functions ik : (t -> t) list = let maybe reffun ik domtup dom = match dom with Some y -> reffun ik domtup y | _ -> domtup in - [(fun (a, b, c, d) -> refine_with_excl_list ik (a, b, c, d) (to_excl_list (a, b, c, d))); - (fun (a, b, c, d) -> refine_with_incl_list ik (a, b, c, d) (to_incl_list (a, b, c, d))); - (fun (a, b, c, d) -> maybe refine_with_interval ik (a, b, c, d) b); - (fun (a, b, c, d) -> maybe refine_with_congruence ik (a, b, c, d) d)] - - let refine ik ((a, b, c, d ) : t ) : t = - let dt = ref (a, b, c, d) in - (match GobConfig.get_string "ana.int.refinement" with - | "never" -> () - | "once" -> + [(fun (a, b, c, d, e) -> refine_with_excl_list ik (a, b, c, d, e) (to_excl_list (a, b, c, d, e))); + (fun (a, b, c, d, e) -> refine_with_incl_list ik (a, b, c, d, e) (to_incl_list (a, b, c, d, e))); + (fun (a, b, c, d, e) -> maybe refine_with_interval ik (a, b, c, d, e) b); + (fun (a, b, c, d, e) -> maybe refine_with_congruence ik (a, b, c, d, e) d)] + + let refine ik ((a, b, c, d, e) : t ) : t = + let dt = ref (a, b, c, d, e) in + (match get_refinement () with + | "never" -> () + | "once" -> + List.iter (fun f -> dt := f !dt) (refine_functions ik); + | "fixpoint" -> + let quit_loop = ref false in + while not !quit_loop do + let old_dt = !dt in List.iter (fun f -> dt := f !dt) (refine_functions ik); - | "fixpoint" -> - let quit_loop = ref false in - while not !quit_loop do - let old_dt = !dt in - List.iter (fun f -> dt := f !dt) (refine_functions ik); - quit_loop := equal old_dt !dt; - if is_bot !dt then dt := bot_of ik; quit_loop := true; - if M.tracing then M.trace "cong-refine-loop" "old: %a, new: %a\n" pretty old_dt pretty !dt; - done; - | _ -> () + quit_loop := equal old_dt !dt; + if is_bot !dt then dt := bot_of ik; quit_loop := true; + if M.tracing then M.trace "cong-refine-loop" "old: %a, new: %a\n" pretty old_dt pretty !dt; + done; + | _ -> () ); !dt - let no_overflow ik r = - if should_ignore_overflow ik then true - else let ika, ikb = Size.range ik in - match I2.minimal r, I2.maximal r with - | Some ra, Some rb -> BI.compare ika ra < 0 || BI.compare rb ikb < 0 - | _ -> false (* map with overflow check *) - let mapovc ik r (a, b, c, d) = + let mapovc ?(cast=false) ik r (a, b, c, d, e) = let map f ?no_ov = function Some x -> Some (f ?no_ov x) | _ -> None in - let intv = map (r.f1 (module I2)) b in - let no_ov = - match intv with Some i -> no_overflow ik i | _ -> should_ignore_overflow ik - in refine ik - ( map (r.f1 (module I1)) a - , intv - , map (r.f1 (module I3)) c - , map (r.f1 (module I4)) ~no_ov d ) + 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 = no_ov || should_ignore_overflow ik in + refine ik + ( map (fun ?no_ov x -> r.f1_ovc ?no_ov (module I1) x |> fst) a + , BatOption.map fst intv + , map (fun ?no_ov x -> r.f1_ovc ?no_ov (module I3) x |> fst) c + , map (fun ?no_ov x -> r.f1_ovc ?no_ov (module I4) x |> fst) ~no_ov d + , BatOption.map fst intv_set ) (* map2 with overflow check *) - let map2ovc ik r (xa, xb, xc, xd) (ya, yb, yc, yd) = - let intv = opt_map2 (r.f2 (module I2)) xb yb in - let no_ov = - match intv with Some i -> no_overflow ik i | _ -> should_ignore_overflow ik - in + let map2ovc 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 = no_ov || should_ignore_overflow ik in refine ik - ( opt_map2 (r.f2 (module I1)) xa ya - , intv - , opt_map2 (r.f2 (module I3)) xc yc - , opt_map2 (r.f2 (module I4)) ~no_ov xd yd ) + ( opt_map2 (fun ?no_ov x y -> r.f2_ovc ?no_ov (module I1) x y |> fst) xa ya + , BatOption.map fst intv + , opt_map2 (fun ?no_ov x y -> r.f2_ovc ?no_ov (module I3) x y |> fst) xc yc + , opt_map2 (fun ?no_ov x y -> r.f2_ovc ?no_ov (module I4) x y |> fst) ~no_ov:no_ov xd yd + , BatOption.map fst intv_set ) - let map ik r (a, b, c, d) = + let map ik r (a, b, c, d, e) = refine ik BatOption. ( map (r.f1 (module I1)) a , map (r.f1 (module I2)) b , map (r.f1 (module I3)) c - , map (r.f1 (module I4)) d ) + , map (r.f1 (module I4)) d + , map (r.f1 (module I5)) e) - let map2 ?(norefine=false) ik r (xa, xb, xc, xd) (ya, yb, yc, yd) = + let map2 ?(norefine=false) ik r (xa, xb, xc, xd, xe) (ya, yb, yc, yd, ye) = let r = ( opt_map2 (r.f2 (module I1)) xa ya , opt_map2 (r.f2 (module I2)) xb yb , opt_map2 (r.f2 (module I3)) xc yc - , opt_map2 (r.f2 (module I4)) xd yd ) + , opt_map2 (r.f2 (module I4)) xd yd + , opt_map2 (r.f2 (module I5)) xe ye) in if norefine then r else refine ik r (* f1: unary ops *) let neg ?no_ov ik = - mapovc ik {f1= (fun (type a) (module I : S with type t = a) ?no_ov -> I.neg ?no_ov ik)} + mapovc ik {f1_ovc = (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.neg ?no_ov ik)} let bitnot ik = - map ik {f1= (fun (type a) (module I : S with type t = a) ?no_ov -> I.bitnot ik)} + map ik {f1 = (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.bitnot ik)} let lognot ik = - map ik {f1= (fun (type a) (module I : S with type t = a) ?no_ov -> I.lognot ik)} + map ik {f1 = (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.lognot ik)} let cast_to ?torg ?no_ov t = - mapovc t {f1= (fun (type a) (module I : S with type t = a) ?no_ov -> I.cast_to ?torg ?no_ov t)} + mapovc ~cast:true t {f1_ovc = (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.cast_to ?torg ?no_ov t)} (* fp: projections *) let equal_to i x = - let xs = mapp2 { fp2 = fun (type a) (module I:S with type t = a and type int_t = int_t) -> I.equal_to i } x |> Tuple4.enum |> List.of_enum |> List.filter_map identity in + let xs = mapp2 { fp2 = fun (type a) (module I:SOverflow with type t = a and type int_t = int_t) -> I.equal_to i } x |> Tuple5.enum |> List.of_enum |> List.filter_map identity in if List.mem `Eq xs then `Eq else if List.mem `Neq xs then `Neq else `Top @@ -2867,23 +3674,24 @@ module IntDomTupleImpl = struct if n>1 then Messages.info ~category:Unsound "Inconsistent state! %a" (Pretty.docList ~sep:(Pretty.text ",") (Pretty.text % show)) us; (* do not want to abort *) None ) - let to_int = same BI.to_string % mapp2 { fp2 = fun (type a) (module I:S with type t = a and type int_t = int_t) -> I.to_int } - let to_bool = same string_of_bool % mapp { fp = fun (type a) (module I:S with type t = a) -> I.to_bool } - let minimal = flat (List.max ~cmp:BI.compare) % mapp2 { fp2 = fun (type a) (module I:S with type t = a and type int_t = int_t) -> I.minimal } - let maximal = flat (List.min ~cmp:BI.compare) % mapp2 { fp2 = fun (type a) (module I:S with type t = a and type int_t = int_t) -> I.maximal } + let to_int = same BI.to_string % mapp2 { fp2 = fun (type a) (module I:SOverflow with type t = a and type int_t = int_t) -> I.to_int } + let to_bool = same string_of_bool % mapp { fp = fun (type a) (module I:SOverflow with type t = a) -> I.to_bool } + let minimal = flat (List.max ~cmp:BI.compare) % mapp2 { fp2 = fun (type a) (module I:SOverflow with type t = a and type int_t = int_t) -> I.minimal } + let maximal = flat (List.min ~cmp:BI.compare) % mapp2 { fp2 = fun (type a) (module I:SOverflow with type t = a and type int_t = int_t) -> I.maximal } (* others *) - let show = String.concat "; " % to_list % mapp { fp = fun (type a) (module I:S with type t = a) x -> I.name () ^ ":" ^ (I.show x) } - let to_yojson = [%to_yojson: Yojson.Safe.t list] % to_list % mapp { fp = fun (type a) (module I:S with type t = a) x -> I.to_yojson x } - let hash = List.fold_left (lxor) 0 % to_list % mapp { fp = fun (type a) (module I:S with type t = a) -> I.hash } + let show = String.concat "; " % to_list % mapp { fp = fun (type a) (module I:SOverflow with type t = a) x -> I.name () ^ ":" ^ (I.show x) } + let to_yojson = [%to_yojson: Yojson.Safe.t list] % to_list % mapp { fp = fun (type a) (module I:SOverflow with type t = a) x -> I.to_yojson x } + let hash = List.fold_left (lxor) 0 % to_list % mapp { fp = fun (type a) (module I:SOverflow with type t = a) -> I.hash } (* `map/opt_map` are used by `project` *) let opt_map b f = curry @@ function None, true -> f | x, y when y || b -> x | _ -> None - let map ~keep r (i1, i2, i3, i4) (b1, b2, b3, b4) = + let map ~keep r (i1, i2, i3, i4, i5) (b1, b2, b3, b4, b5) = ( opt_map keep (r.f3 (module I1)) i1 b1 , opt_map keep (r.f3 (module I2)) i2 b2 , opt_map keep (r.f3 (module I3)) i3 b3 - , opt_map keep (r.f3 (module I4)) i4 b4 ) + , opt_map keep (r.f3 (module I4)) i4 b4 + , opt_map keep (r.f3 (module I5)) i5 b5 ) (** Project tuple t to precision p * We have to deactivate IntDomains after the refinement, since we might @@ -2902,81 +3710,81 @@ module IntDomTupleImpl = struct * This way we won't loose any information for the refinement. * ~keep:false will set the elements to `None` as defined by p *) let project ik (p: int_precision) t = - let t_padded = map ~keep:true { f3 = fun (type a) (module I:S with type t = a) -> Some (I.top_of ik) } t p in + let t_padded = map ~keep:true { f3 = fun (type a) (module I:SOverflow with type t = a) -> Some (I.top_of ik) } t p in let t_refined = refine ik t_padded in - map ~keep:false { f3 = fun (type a) (module I:S with type t = a) -> None } t_refined p + map ~keep:false { f3 = fun (type a) (module I:SOverflow with type t = a) -> None } t_refined p (* f2: binary ops *) let join ik = - map2 ~norefine:true ik {f2= (fun (type a) (module I : S with type t = a) ?no_ov -> I.join ik)} + map2 ~norefine:true ik {f2= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.join ik)} let meet ik = - map2 ik {f2= (fun (type a) (module I : S with type t = a) ?no_ov -> I.meet ik)} + map2 ik {f2= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.meet ik)} let widen ik = - map2 ~norefine:true ik {f2= (fun (type a) (module I : S with type t = a) ?no_ov -> I.widen ik)} + map2 ~norefine:true ik {f2= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.widen ik)} let narrow ik = - map2 ik {f2= (fun (type a) (module I : S with type t = a) ?no_ov -> I.narrow ik)} + map2 ik {f2= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.narrow ik)} let add ?no_ov ik = map2ovc ik - {f2= (fun (type a) (module I : S with type t = a) ?no_ov -> I.add ?no_ov ik)} + {f2_ovc = (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.add ?no_ov ik)} let sub ?no_ov ik = map2ovc ik - {f2= (fun (type a) (module I : S with type t = a) ?no_ov -> I.sub ?no_ov ik)} + {f2_ovc = (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.sub ?no_ov ik)} let mul ?no_ov ik = map2ovc ik - {f2= (fun (type a) (module I : S with type t = a) ?no_ov -> I.mul ?no_ov ik)} + {f2_ovc = (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.mul ?no_ov ik)} let div ?no_ov ik = map2ovc ik - {f2= (fun (type a) (module I : S with type t = a) ?no_ov -> I.div ?no_ov ik)} + {f2_ovc = (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.div ?no_ov ik)} let rem ik = - map2 ik {f2= (fun (type a) (module I : S with type t = a) ?no_ov -> I.rem ik)} + map2 ik {f2= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.rem ik)} let lt ik = - map2 ik {f2= (fun (type a) (module I : S with type t = a) ?no_ov -> I.lt ik)} + map2 ik {f2= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.lt ik)} let gt ik = - map2 ik {f2= (fun (type a) (module I : S with type t = a) ?no_ov -> I.gt ik)} + map2 ik {f2= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.gt ik)} let le ik = - map2 ik {f2= (fun (type a) (module I : S with type t = a) ?no_ov -> I.le ik)} + map2 ik {f2= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.le ik)} let ge ik = - map2 ik {f2= (fun (type a) (module I : S with type t = a) ?no_ov -> I.ge ik)} + map2 ik {f2= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.ge ik)} let eq ik = - map2 ik {f2= (fun (type a) (module I : S with type t = a) ?no_ov -> I.eq ik)} + map2 ik {f2= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.eq ik)} let ne ik = - map2 ik {f2= (fun (type a) (module I : S with type t = a) ?no_ov -> I.ne ik)} + map2 ik {f2= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.ne ik)} let bitand ik = - map2 ik {f2= (fun (type a) (module I : S with type t = a) ?no_ov -> I.bitand ik)} + map2 ik {f2= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.bitand ik)} let bitor ik = - map2 ik {f2= (fun (type a) (module I : S with type t = a) ?no_ov -> I.bitor ik)} + map2 ik {f2= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.bitor ik)} let bitxor ik = - map2 ik {f2= (fun (type a) (module I : S with type t = a) ?no_ov -> I.bitxor ik)} + map2 ik {f2= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.bitxor ik)} let shift_left ik = - map2 ik {f2= (fun (type a) (module I : S with type t = a) ?no_ov -> I.shift_left ik)} + map2ovc ik {f2_ovc= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.shift_left ik)} let shift_right ik = - map2 ik {f2= (fun (type a) (module I : S with type t = a) ?no_ov -> I.shift_right ik)} + map2ovc ik {f2_ovc= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.shift_right ik)} let logand ik = - map2 ik {f2= (fun (type a) (module I : S with type t = a) ?no_ov -> I.logand ik)} + map2 ik {f2= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.logand ik)} let logor ik = - map2 ik {f2= (fun (type a) (module I : S with type t = a) ?no_ov -> I.logor ik)} + map2 ik {f2= (fun (type a) (module I : SOverflow with type t = a) ?no_ov -> I.logor ik)} (* printing boilerplate *) @@ -2992,22 +3800,26 @@ module IntDomTupleImpl = struct else Invariant.top () | None -> - let is = to_list (mapp { fp = fun (type a) (module I:S with type t = a) -> I.invariant_ikind e ik } x) + let is = to_list (mapp { fp = fun (type a) (module I:SOverflow with type t = a) -> I.invariant_ikind e ik } x) in List.fold_left (fun a i -> Invariant.(a && i) ) (Invariant.top ()) is - let arbitrary ik = QCheck.(set_print show @@ quad (option (I1.arbitrary ik)) (option (I2.arbitrary ik)) (option (I3.arbitrary ik)) (option (I4.arbitrary ik))) + let arbitrary ik = QCheck.(set_print show @@ tup5 (option (I1.arbitrary ik)) (option (I2.arbitrary ik)) (option (I3.arbitrary ik)) (option (I4.arbitrary ik)) (option (I5.arbitrary ik))) + + let relift (a, b, c, d, e) = + (Option.map I1.relift a, Option.map I2.relift b, Option.map I3.relift c, Option.map I4.relift d, Option.map I5.relift e) end module IntDomTuple = struct - module I = IntDomLifter (IntDomTupleImpl) - include I + module I = IntDomLifter (IntDomTupleImpl) + include I - let top () = failwith "top in IntDomTuple not supported. Use top_of instead." - let no_interval (x: I.t) = {x with v = IntDomTupleImpl.no_interval x.v} + let top () = failwith "top in IntDomTuple not supported. Use top_of instead." + let no_interval (x: I.t) = {x with v = IntDomTupleImpl.no_interval x.v} + let no_intervalSet (x: I.t) = {x with v = IntDomTupleImpl.no_intervalSet x.v} end let of_const (i, ik, str) = IntDomTuple.of_int ik i diff --git a/src/cdomains/intDomain.mli b/src/cdomains/intDomain.mli index 259337e388..a853c8acca 100644 --- a/src/cdomains/intDomain.mli +++ b/src/cdomains/intDomain.mli @@ -1,5 +1,5 @@ -(** Abstract Domains for integers. These are domains that support the C - * operations on integer values. *) +(** Abstract domains for C integers. *) + open GoblintCil val should_wrap: Cil.ikind -> bool @@ -7,6 +7,8 @@ val should_ignore_overflow: Cil.ikind -> bool val reset_lazy: unit -> unit +type overflow_info = { overflow: bool; underflow: bool;} + module type Arith = sig type t @@ -273,6 +275,39 @@ sig end (** Interface of IntDomain implementations taking an ikind for arithmetic operations *) +module type SOverflow = +sig + + include S + + val add : ?no_ov:bool -> Cil.ikind -> t -> t -> t * overflow_info + + val sub : ?no_ov:bool -> Cil.ikind -> t -> t -> t * overflow_info + + val mul : ?no_ov:bool -> Cil.ikind -> t -> t -> t * overflow_info + + val div : ?no_ov:bool -> Cil.ikind -> t -> t -> t * overflow_info + + val neg : ?no_ov:bool -> Cil.ikind -> t -> t * overflow_info + + val cast_to : ?torg:Cil.typ -> ?no_ov:bool -> Cil.ikind -> t -> t * overflow_info + + val of_int : Cil.ikind -> int_t -> t * overflow_info + + val of_interval: ?suppress_ovwarn:bool -> Cil.ikind -> int_t * int_t -> t * overflow_info + + val starting : ?suppress_ovwarn:bool -> Cil.ikind -> int_t -> t * overflow_info + val ending : ?suppress_ovwarn:bool -> Cil.ikind -> int_t -> t * overflow_info + + val shift_left : Cil.ikind -> t -> t -> t * overflow_info + + val shift_right: Cil.ikind -> t -> t -> t * overflow_info + + +end + +module SOverflowUnlifter (D : SOverflow) : S with type int_t = D.int_t and type t = D.t + module OldDomainFacade (Old : IkindUnawareS with type int_t = int64) : S with type int_t = IntOps.BigIntOps.t and type t = Old.t (** Facade for IntDomain implementations that do not implement the interface where arithmetic functions take an ikind parameter. *) @@ -320,10 +355,11 @@ module IntDomWithDefaultIkind (I: Y) (Ik: Ikind) : Y with type t = I.t and type module IntDomTuple : sig include Z val no_interval: t -> t + val no_intervalSet: t -> t val ikind: t -> ikind end -val of_const: Cilint.cilint * Cil.ikind * string option -> IntDomTuple.t +val of_const: Z.t * Cil.ikind * string option -> IntDomTuple.t module Size : sig @@ -369,7 +405,9 @@ module FlattenedBI : IkindUnawareS with type t = [`Top | `Lifted of IntOps.BigIn module Lifted : IkindUnawareS with type t = [`Top | `Lifted of int64 | `Bot] and type int_t = int64 (** Artificially bounded integers in their natural ordering. *) -module IntervalFunctor(Ints_t : IntOps.IntOps): S with type int_t = Ints_t.t and type t = (Ints_t.t * Ints_t.t) option +module IntervalFunctor(Ints_t : IntOps.IntOps): SOverflow with type int_t = Ints_t.t and type t = (Ints_t.t * Ints_t.t) option + +module IntervalSetFunctor(Ints_t : IntOps.IntOps): SOverflow with type int_t = Ints_t.t and type t = (Ints_t.t * Ints_t.t) list module Interval32 :Y with (* type t = (IntOps.Int64Ops.t * IntOps.Int64Ops.t) option and *) type int_t = IntOps.Int64Ops.t @@ -379,7 +417,9 @@ module BigInt: val cast_to: Cil.ikind -> Z.t -> Z.t end -module Interval : S with type int_t = IntOps.BigIntOps.t +module Interval : SOverflow with type int_t = IntOps.BigIntOps.t + +module IntervalSet : SOverflow with type int_t = IntOps.BigIntOps.t module Congruence : S with type int_t = IntOps.BigIntOps.t diff --git a/src/cdomains/jmpBufDomain.ml b/src/cdomains/jmpBufDomain.ml new file mode 100644 index 0000000000..3c94fa8f47 --- /dev/null +++ b/src/cdomains/jmpBufDomain.ml @@ -0,0 +1,73 @@ +(** Domains for [setjmp] and [longjmp] analyses, and [setjmp] buffers. *) + +module BufferEntry = Printable.ProdSimple(Node)(ControlSpecC) + +module BufferEntryOrTop = struct + include Printable.Std + type t = AllTargets | Target of BufferEntry.t [@@deriving eq, ord, hash, to_yojson] + + let name () = "jmpbuf entry" + + let relift = function + | AllTargets -> AllTargets + | Target x -> Target (BufferEntry.relift x) + + let show = function AllTargets -> "All" | Target x -> BufferEntry.show x + + include Printable.SimpleShow (struct + type nonrec t = t + let show = show + end) +end + +module JmpBufSet = +struct + include SetDomain.Make (BufferEntryOrTop) + let top () = singleton BufferEntryOrTop.AllTargets + let name () = "Jumpbuffers" + + let inter x y = + if mem BufferEntryOrTop.AllTargets x || mem BufferEntryOrTop.AllTargets y then + let fromx = if mem BufferEntryOrTop.AllTargets y then x else bot () in + let fromy = if mem BufferEntryOrTop.AllTargets x then y else bot () in + union fromx fromy + else + inter x y + + let meet = inter +end + +module JmpBufSetTaint = +struct + module Bufs = JmpBufSet + include Lattice.Prod(JmpBufSet)(BoolDomain.MayBool) + let buffers (a,_) = a + let copied (_,b) = b + let name () = "JumpbufferCopyTaint" +end + + +(* module JmpBufSet = + struct + include SetDomain.ToppedSet (BufferEntry) (struct let topname = "All jumpbufs" end) + let name () = "Jumpbuffers" + end *) + +module NodeSet = +struct + include SetDomain.ToppedSet (Node) (struct let topname = "All longjmp callers" end) + let name () = "Longjumps" +end + +module ActiveLongjmps = +struct + include Lattice.ProdSimple(JmpBufSet)(NodeSet) +end + +module LocallyModifiedMap = +struct + module VarSet = SetDomain.ToppedSet(CilType.Varinfo) (struct let topname = "All vars" end) + include MapDomain.MapBot_LiftTop (BufferEntry)(VarSet) + + let name () = "Locally modified variables since the corresponding setjmp" +end diff --git a/src/cdomains/lockDomain.ml b/src/cdomains/lockDomain.ml index 0ebcf4a8a5..107c1c0692 100644 --- a/src/cdomains/lockDomain.ml +++ b/src/cdomains/lockDomain.ml @@ -1,21 +1,16 @@ +(** Lockset domains. *) + module Addr = ValueDomain.Addr module Offs = ValueDomain.Offs -module Equ = MusteqDomain.Equ module Exp = CilType.Exp 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 -module Glob = -struct - module Var = Basetype.Variables - module Val = Simple -end - module Lockset = struct @@ -41,48 +36,31 @@ struct ) end - (* TODO: use SetDomain.Reverse *) - module ReverseAddrSet = SetDomain.ToppedSet (Lock) - (struct let topname = "All mutexes" end) - - module AddrSet = Lattice.Reverse (ReverseAddrSet) + include SetDomain.Reverse(SetDomain.ToppedSet (Lock) (struct let topname = "All mutexes" end)) + let name () = "lockset" - include AddrSet - - let rec may_be_same_offset of1 of2 = - match of1, of2 with - | `NoOffset , `NoOffset -> true - | `Field (x1,y1) , `Field (x2,y2) -> CilType.Compinfo.equal x1.fcomp x2.fcomp && may_be_same_offset y1 y2 (* TODO: why not fieldinfo equal? *) - | `Index (x1,y1) , `Index (x2,y2) - -> ((IdxDom.to_int x1 = None) || (IdxDom.to_int x2 = None)) - || IdxDom.equal x1 x2 && may_be_same_offset y1 y2 - | _ -> false + let may_be_same_offset of1 of2 = + (* Only reached with definite of2 and indefinite of1. *) + (* TODO: Currently useless, because MayPointTo query doesn't return index offset ranges, so not enough information to ever return false. *) + (* TODO: Use Addr.Offs.semantic_equal. *) + true let add (addr,rw) set = - match (Addr.to_var_offset addr) with - | Some (_,x) when Offs.is_definite x -> ReverseAddrSet.add (addr,rw) set + match (Addr.to_mval addr) with + | Some (_,x) when Offs.is_definite x -> add (addr,rw) set | _ -> set let remove (addr,rw) set = let collect_diff_varinfo_with (vi,os) (addr,rw) = - match (Addr.to_var_offset addr) with + match (Addr.to_mval addr) with | Some (v,o) when CilType.Varinfo.equal vi v -> not (may_be_same_offset o os) | Some (v,o) -> true | None -> false in - match (Addr.to_var_offset addr) with - | Some (_,x) when Offs.is_definite x -> ReverseAddrSet.remove (addr,rw) set - | Some x -> ReverseAddrSet.filter (collect_diff_varinfo_with x) set - | _ -> AddrSet.top () - - let empty = ReverseAddrSet.empty - let is_empty = ReverseAddrSet.is_empty - - let filter = ReverseAddrSet.filter - let fold = ReverseAddrSet.fold - let singleton = ReverseAddrSet.singleton - let mem = ReverseAddrSet.mem - let exists = ReverseAddrSet.exists + match (Addr.to_mval addr) with + | Some (_,x) when Offs.is_definite x -> remove (addr,rw) set + | Some x -> filter (collect_diff_varinfo_with x) set + | _ -> top () let export_locks ls = let f (x,_) set = Mutexes.add x set in @@ -99,6 +77,11 @@ struct let bot = Lockset.top end +module MayLocksetNoRW = +struct + include PreValueDomain.AD +end + module Symbolic = struct (* TODO: use SetDomain.Reverse *) diff --git a/src/cdomains/lval.ml b/src/cdomains/lval.ml index eb284f549d..9a86a3b083 100644 --- a/src/cdomains/lval.ml +++ b/src/cdomains/lval.ml @@ -1,574 +1,3 @@ -open GoblintCil -open Pretty +(** Domains for {!GoblintCil.lval}. *) -module GU = Goblintutil - -type ('a, 'b) offs = [ - | `NoOffset - | `Field of 'a * ('a,'b) offs - | `Index of 'b * ('a,'b) offs -] [@@deriving eq, ord, hash] - - -(** Subinterface of IntDomain.Z which is sufficient for Printable (but not Lattice) Offset. *) -module type IdxPrintable = -sig - include Printable.S - val equal_to: IntOps.BigIntOps.t -> t -> [`Eq | `Neq | `Top] - val to_int: t -> IntOps.BigIntOps.t option -end - -module type IdxDomain = -sig - include IdxPrintable - include Lattice.S with type t := t -end - -module OffsetPrintable (Idx: IdxPrintable) = -struct - type t = (fieldinfo, Idx.t) offs - include Printable.Std - - let is_first_field x = match x.fcomp.cfields with - | [] -> false - | f :: _ -> CilType.Fieldinfo.equal f x - - let rec cmp_zero_offset : t -> [`MustZero | `MustNonzero | `MayZero] = function - | `NoOffset -> `MustZero - | `Index (x, o) -> (match cmp_zero_offset o, Idx.equal_to (IntOps.BigIntOps.zero) x with - | `MustNonzero, _ - | _, `Neq -> `MustNonzero - | `MustZero, `Eq -> `MustZero - | _, _ -> `MayZero) - | `Field (x, o) -> - if is_first_field x then cmp_zero_offset o else `MustNonzero - - let rec equal x y = - match x, y with - | `NoOffset , `NoOffset -> true - | `NoOffset, x - | x, `NoOffset -> cmp_zero_offset x = `MustZero (* cannot derive due to this special case, special cases not used for AddressDomain any more due to splitting *) - | `Field (f1,o1), `Field (f2,o2) when CilType.Fieldinfo.equal f1 f2 -> equal o1 o2 - | `Index (i1,o1), `Index (i2,o2) when Idx.equal i1 i2 -> equal o1 o2 - | _ -> false - - let rec show = function - | `NoOffset -> "" - | `Index (x,o) -> "[" ^ (Idx.show x) ^ "]" ^ (show o) - | `Field (x,o) -> "." ^ (x.fname) ^ (show o) - - include Printable.SimpleShow ( - struct - type nonrec t = t - let show = show - end - ) - - let pretty_diff () (x,y) = - dprintf "%s: %a not leq %a" (name ()) pretty x pretty y - - let rec hash = function (* special cases not used for AddressDomain any more due to splitting *) - | `NoOffset -> 1 - | `Field (f,o) when not (is_first_field f) -> Hashtbl.hash f.fname * hash o + 13 - | `Field (_,o) (* zero offsets need to yield the same hash as `NoOffset! *) - | `Index (_,o) -> hash o (* index might become top during fp -> might be zero offset *) - let name () = "Offset" - - let from_offset x = x - - let rec is_definite = function - | `NoOffset -> true - | `Field (f,o) -> is_definite o - | `Index (i,o) -> Idx.to_int i <> None && is_definite o - - (* append offset o2 to o1 *) - (* TODO: unused *) - let rec add_offset o1 o2 = - match o1 with - | `NoOffset -> o2 - | `Field (f1,o1) -> `Field (f1,add_offset o1 o2) - | `Index (i1,o1) -> `Index (i1,add_offset o1 o2) - - let rec compare o1 o2 = match o1, o2 with - | `NoOffset, `NoOffset -> 0 - | `NoOffset, x - | x, `NoOffset when cmp_zero_offset x = `MustZero -> 0 (* cannot derive due to this special case, special cases not used for AddressDomain any more due to splitting *) - | `Field (f1,o1), `Field (f2,o2) -> - let c = CilType.Fieldinfo.compare f1 f2 in - if c=0 then compare o1 o2 else c - | `Index (i1,o1), `Index (i2,o2) -> - let c = Idx.compare i1 i2 in - if c=0 then compare o1 o2 else c - | `NoOffset, _ -> -1 - | _, `NoOffset -> 1 - | `Field _, `Index _ -> -1 - | `Index _, `Field _ -> 1 - - let rec to_cil_offset (x:t) = - match x with - | `NoOffset -> NoOffset - | `Field(f,o) -> Field(f, to_cil_offset o) - | `Index(i,o) -> NoOffset (* array domain can not deal with this -> leads to being handeled as access to unknown part *) -end - -module Offset (Idx: IdxDomain) = -struct - include OffsetPrintable (Idx) - - let rec leq x y = - match x, y with - | `NoOffset, `NoOffset -> true - | `NoOffset, x -> cmp_zero_offset x <> `MustNonzero (* special case not used for AddressDomain any more due to splitting *) - | x, `NoOffset -> cmp_zero_offset x = `MustZero (* special case not used for AddressDomain any more due to splitting *) - | `Index (i1,o1), `Index (i2,o2) when Idx.leq i1 i2 -> leq o1 o2 - | `Field (f1,o1), `Field (f2,o2) when CilType.Fieldinfo.equal f1 f2 -> leq o1 o2 - | _ -> false - - let rec merge cop x y = - let op = match cop with `Join -> Idx.join | `Meet -> Idx.meet | `Widen -> Idx.widen | `Narrow -> Idx.narrow in - match x, y with - | `NoOffset, `NoOffset -> `NoOffset - | `NoOffset, x - | x, `NoOffset -> (match cop, cmp_zero_offset x with (* special cases not used for AddressDomain any more due to splitting *) - | (`Join | `Widen), (`MustZero | `MayZero) -> x - | (`Meet | `Narrow), (`MustZero | `MayZero) -> `NoOffset - | _ -> raise Lattice.Uncomparable) - | `Field (x1,y1), `Field (x2,y2) when CilType.Fieldinfo.equal x1 x2 -> `Field (x1, merge cop y1 y2) - | `Index (x1,y1), `Index (x2,y2) -> `Index (op x1 x2, merge cop y1 y2) - | _ -> raise Lattice.Uncomparable (* special case not used for AddressDomain any more due to splitting *) - - let join x y = merge `Join x y - let meet x y = merge `Meet x y - let widen x y = merge `Widen x y - let narrow x y = merge `Narrow x y - - let rec drop_ints = function - | `Index (x, o) -> `Index (Idx.top (), drop_ints o) - | `Field (x, o) -> `Field (x, drop_ints o) - | `NoOffset -> `NoOffset -end - -module type S = -sig - type field - type idx - include Printable.S - - val null_ptr: unit -> t - val str_ptr: unit -> t - val is_null: t -> bool - val get_location: t -> location - - val from_var: varinfo -> t - (** Creates an address from variable. *) - - val from_var_offset: (varinfo * (field,idx) offs) -> t - (** Creates an address from a variable and offset. *) - - val to_var_offset: t -> (varinfo * (field,idx) offs) list - (** Get the offset *) - - val to_var: t -> varinfo list - (** Strips the varinfo out of the address representation. *) - - val to_var_may: t -> varinfo list - val to_var_must: t -> varinfo list - (** Strips the varinfo out of the address representation. *) - - val get_type: t -> typ - (** Finds the type of the address location. *) -end - -module Normal (Idx: IdxPrintable) = -struct - type field = fieldinfo - type idx = Idx.t - module Offs = OffsetPrintable (Idx) - - type t = - | Addr of CilType.Varinfo.t * Offs.t (** Pointer to offset of a variable. *) - | 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 *) - [@@deriving eq, ord, hash] (* TODO: StrPtr equal problematic if the same literal appears more than once *) - - let hash x = match x with - | StrPtr _ -> - if GobConfig.get_bool "ana.base.limit-string-addresses" then - 13859 - else - hash x - | _ -> hash x - - include Printable.Std - let name () = "Normal Lvals" - - type group = Basetype.Variables.group - let show_group = Basetype.Variables.show_group - let to_group = function - | Addr (x,_) -> Basetype.Variables.to_group x - | _ -> Some Basetype.Variables.Local - - let from_var x = Addr (x, `NoOffset) - let from_var_offset (x, o) = Addr (x, o) - - let to_var = function - | Addr (x,_) -> Some x - | _ -> None - let to_var_may = function - | Addr (x,_) -> Some x - | _ -> None - let to_var_must = function - | Addr (x,`NoOffset) -> Some x - | _ -> None - let to_var_offset = function - | Addr (x, o) -> Some (x, o) - | _ -> None - - (* strings *) - let from_string x = StrPtr (Some x) - let to_string = function - | StrPtr (Some x) -> Some x - | _ -> None - - let rec short_offs = function - | `NoOffset -> "" - | `Field (fld, o) -> "." ^ fld.fname ^ short_offs o - | `Index (v, o) -> "[" ^ Idx.show v ^ "]" ^ short_offs o - - let short_addr (x, o) = - if RichVarinfo.BiVarinfoMap.Collection.mem_varinfo x then - let description = RichVarinfo.BiVarinfoMap.Collection.describe_varinfo x in - "(" ^ x.vname ^ ", " ^ description ^ ")" ^ short_offs o - else x.vname ^ short_offs o - - let show = function - | Addr (x, o)-> short_addr (x, o) - | StrPtr (Some x) -> "\"" ^ x ^ "\"" - | StrPtr None -> "(unknown string)" - | UnknownPtr -> "?" - | NullPtr -> "NULL" - - include Printable.SimpleShow ( - struct - type nonrec t = t - let show = show - end - ) - - (* exception if the offset can't be followed completely *) - exception Type_offset of typ * string - (* tries to follow o in t *) - let rec type_offset t o = match unrollType t, o with (* resolves TNamed *) - | t, `NoOffset -> t - | TArray (t,_,_), `Index (i,o) - | TPtr (t,_), `Index (i,o) -> type_offset t o - | TComp (ci,_), `Field (f,o) -> - let fi = try getCompField ci f.fname - with Not_found -> - let s = sprint ~width:0 @@ dprintf "Addr.type_offset: field %s not found in type %a" f.fname d_plaintype t in - raise (Type_offset (t, s)) - in type_offset fi.ftype o - | TComp _, `Index (_,o) -> type_offset t o (* this happens (hmmer, perlbench). safe? *) - | t,o -> - let s = sprint ~width:0 @@ dprintf "Addr.type_offset: could not follow offset in type. type: %a, offset: %s" d_plaintype t (short_offs o) in - raise (Type_offset (t, s)) - - let get_type_addr (v,o) = try type_offset v.vtype o with Type_offset (t,_) -> t - - let get_type = function - | Addr (x, o) -> get_type_addr (x, o) - | StrPtr _ -> charPtrType (* TODO Cil.charConstPtrType? *) - | NullPtr -> voidType - | UnknownPtr -> voidPtrType - - let is_zero_offset x = Offs.cmp_zero_offset x = `MustZero - - (* TODO: seems to be unused *) - let to_exp (f:idx -> exp) x = - let rec to_cil c = - match c with - | `NoOffset -> NoOffset - | `Field (fld, ofs) -> Field (fld , to_cil ofs) - | `Index (idx, ofs) -> Index (f idx, to_cil ofs) - in - match x with - | Addr (v,o) -> AddrOf (Var v, to_cil o) - | StrPtr (Some x) -> mkString x - | StrPtr None -> raise (Lattice.Unsupported "Cannot express unknown string pointer as expression.") - | NullPtr -> integer 0 - | UnknownPtr -> raise Lattice.TopValue - let rec add_offsets x y = match x with - | `NoOffset -> y - | `Index (i,x) -> `Index (i, add_offsets x y) - | `Field (f,x) -> `Field (f, add_offsets x y) - (* TODO: unused *) - let add_offset x o = match x with - | Addr (v, u) -> Addr (v, add_offsets u o) - | x -> x - let rec remove_offset = function - | `NoOffset -> `NoOffset - | `Index (_,`NoOffset) | `Field (_,`NoOffset) -> `NoOffset - | `Index (i,o) -> `Index (i, remove_offset o) - | `Field (f,o) -> `Field (f, remove_offset o) - - let arbitrary () = QCheck.always UnknownPtr (* S TODO: non-unknown *) -end - -(** Lvalue lattice. - - Actually a disjoint union of lattices without top or bottom. - Lvalues are grouped as follows: - - - Each {!Addr}, modulo precise index expressions in offset, is a sublattice with ordering induced by {!Offset}. - - {!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. *) -module NormalLat (Idx: IdxDomain) = -struct - include Normal (Idx) - module Offs = Offset (Idx) - - let is_definite = function - | NullPtr -> true - | Addr (v,o) when Offs.is_definite o -> true - | _ -> false - - let leq x y = match x, y with - | StrPtr _, StrPtr None -> true - | StrPtr a, StrPtr b -> a = b - | Addr (x,o), Addr (y,u) -> CilType.Varinfo.equal x y && Offs.leq o u - | _ -> x = y - - let drop_ints = function - | Addr (x, o) -> Addr (x, Offs.drop_ints o) - | 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 cop x y = - match x, y with - | UnknownPtr, UnknownPtr -> UnknownPtr - | NullPtr , NullPtr -> NullPtr - | StrPtr a, StrPtr b -> - StrPtr - begin match cop with - |`Join | `Widen -> join_string_ptr a b - |`Meet | `Narrow -> meet_string_ptr a b - end - | Addr (x,o), Addr (y,u) when CilType.Varinfo.equal x y -> Addr (x, Offs.merge cop o u) - | _ -> raise Lattice.Uncomparable - - let join = merge `Join - let widen = merge `Widen - let meet = merge `Meet - let narrow = merge `Narrow - - include Lattice.NoBotTop - - let pretty_diff () (x,y) = dprintf "%s: %a not leq %a" (name ()) pretty x pretty y -end - -(** Lvalue lattice with sublattice representatives for {!DisjointDomain}. *) -module NormalLatRepr (Idx: IdxDomain) = -struct - include NormalLat (Idx) - - module UnitIdxDomain = - struct - include Lattice.Unit - let equal_to _ _ = `Top - let to_int _ = None - end - (** Representatives for lvalue sublattices as defined by {!NormalLat}. *) - module R: DisjointDomain.Representative with type elt = t = - struct - type elt = t - - (* Offset module for representative without abstract values for index offsets, i.e. with unit index offsets. - Reason: The offset in the representative (used for buckets) should not depend on the integer domains, - since different integer domains may be active at different program points. *) - include Normal (UnitIdxDomain) - - let rec of_elt_offset: (fieldinfo, Idx.t) offs -> (fieldinfo, UnitIdxDomain.t) offs = - function - | `NoOffset -> `NoOffset - | `Field (f,o) -> `Field (f, of_elt_offset o) - | `Index (_,o) -> `Index (UnitIdxDomain.top (), of_elt_offset o) (* all indices to same bucket *) - - let of_elt (x: elt): t = match x with - | Addr (v, o) -> Addr (v, of_elt_offset 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 *) - | NullPtr -> NullPtr - | UnknownPtr -> UnknownPtr - end -end - -module Fields = -struct - module F = CilType.Fieldinfo - module I = Basetype.CilExp - module FI = Printable.Either (F) (I) - include Printable.Liszt (FI) - - let rec show x = match x with - | [] -> "" - | (`Left x :: xs) -> "." ^ F.show x ^ show xs - | (`Right x :: xs) -> "[" ^ I.show x ^ "]" ^ show xs - - include Printable.SimpleShow ( - struct - type nonrec t = t - let show = show - end - ) - - let rec printInnerXml f = function - | [] -> () - | (`Left x :: xs) -> - BatPrintf.fprintf f ".%s%a" (F.show x) printInnerXml xs - | (`Right x :: xs) -> - BatPrintf.fprintf f "[%s]%a" (I.show x) printInnerXml xs - - let printXml f x = BatPrintf.fprintf f "\n\n%a\n\n\n" printInnerXml x - - let rec listify ofs: t = - match ofs with - | NoOffset -> [] - | Field (x,ofs) -> `Left x :: listify ofs - | Index (i,ofs) -> `Right i :: listify ofs - - let rec to_offs' (ofs:t) = match ofs with - | (`Left x::xs) -> `Field (x, to_offs' xs) - | (`Right x::xs) -> `Index (x, to_offs' xs) - | [] -> `NoOffset - - let rec kill v (fds: t): t = match fds with - | (`Right x::xs) when I.occurs v x -> [] - | (x::xs) -> x :: kill v xs - | [] -> [] - - let replace x exp ofs = - let f o = match o with - | `Right e -> `Right (I.replace x exp e) - | x -> x - in - List.map f ofs - - let top () = [] - let is_top x = x = [] - let bot () = failwith "Bottom offset list!" - let is_bot x = false - - let rec leq x y = - match x,y with - | _, [] -> true - | x::xs, y::ys when FI.equal x y -> leq xs ys - | _ -> false - - let rec meet x y = - match x,y with - | [], x | x, [] -> x - | x::xs, y::ys when FI.equal x y -> x :: meet xs ys - | _ -> failwith "Arguments do not meet" - - let narrow = meet - - let rec join x y = - match x,y with - | x::xs, y::ys when FI.equal x y -> x :: join xs ys - | _ -> [] - - let widen = join - - let rec collapse x y = - match x,y with - | [], x | x, [] -> true - | x :: xs, y :: ys when FI.equal x y -> collapse xs ys - | `Left x::xs, `Left y::ys -> false - | `Right x::xs, `Right y::ys -> true - | _ -> failwith "Type mismatch!" - - (* TODO: use the type information to do this properly. Currently, this assumes - * there are no nested arrays, so all indexing is eliminated. *) - let rec real_region (fd:t) typ: bool = - match fd with - | [] -> true - | `Left _ :: xs -> real_region xs typ - | `Right i :: _ -> false - - let pretty_diff () ((x:t),(y:t)): Pretty.doc = - Pretty.dprintf "%a not leq %a" pretty x pretty y -end - - -module CilLval = -struct - include Printable.Std - type t = CilType.Varinfo.t * (CilType.Fieldinfo.t, Basetype.CilExp.t) offs [@@deriving eq, ord, hash] - - let name () = "simplified lval" - - let class_tag (v,o) = - match v with - | _ when v.vglob -> `Global - | _ when v.vdecl.line = -1 -> `Temp - | _ when Cilfacade.is_varinfo_formal v -> `Parameter - | _ -> `Local - - let rec short_offs (o: (fieldinfo, exp) offs) a = - match o with - | `NoOffset -> a - | `Field (f,o) -> short_offs o (a^"."^f.fname) - | `Index (e,o) when CilType.Exp.equal e MyCFG.unknown_exp -> short_offs o (a^"[?]") - | `Index (e,o) -> short_offs o (a^"["^CilType.Exp.show e^"]") - - let rec of_ciloffs x = - match x with - | NoOffset -> `NoOffset - | Index (i,o) -> `Index (i, of_ciloffs o) - | Field (f,o) -> `Field (f, of_ciloffs o) - - let rec to_ciloffs x = - match x with - | `NoOffset -> NoOffset - | `Index (i,o) -> Index (i, to_ciloffs o) - | `Field (f,o) -> Field (f, to_ciloffs o) - - let to_lval (v,o) = Var v, to_ciloffs o - let to_exp (v,o) = Lval (Var v, to_ciloffs o) - - let rec has_index_offs = - function - | `NoOffset -> false - | `Index _ -> true - | `Field (_,o) -> has_index_offs o - let has_index (v,o) = has_index_offs o - - let show (v,o) = short_offs o v.vname - include Printable.SimpleShow ( - struct - type nonrec t = t - let show = show - end - ) -end +module Set = SetDomain.ToppedSet (CilType.Lval) (struct let topname = "All" end) diff --git a/src/cdomains/mHP.ml b/src/cdomains/mHP.ml index 5b367aaae4..8037cfa21d 100644 --- a/src/cdomains/mHP.ml +++ b/src/cdomains/mHP.ml @@ -1,5 +1,9 @@ +(** May-happen-in-parallel (MHP) domain. *) + include Printable.Std +let name () = "mhp" + module TID = ThreadIdDomain.FlagConfiguredTID module Pretty = GoblintCil.Pretty @@ -9,6 +13,9 @@ type t = { must_joined: ConcDomain.ThreadSet.t; } [@@deriving eq, ord, hash] +let relift {tid; created; must_joined} = + {tid = ThreadIdDomain.ThreadLifted.relift tid; created = ConcDomain.ThreadSet.relift created; must_joined = ConcDomain.ThreadSet.relift must_joined} + let current (ask:Queries.ask) = { tid = ask.f Queries.CurrentThreadId; @@ -64,7 +71,7 @@ let must_be_joined other joined = if ConcDomain.ThreadSet.is_top joined then true (* top means all threads are joined, so [other] must be as well *) else - List.mem other (ConcDomain.ThreadSet.elements joined) + ConcDomain.ThreadSet.mem other joined (** May two program points with respective MHP information happen in parallel *) let may_happen_in_parallel one two = diff --git a/src/cdomains/musteqDomain.ml b/src/cdomains/musteqDomain.ml index bf3d694c23..c7a1cbc176 100644 --- a/src/cdomains/musteqDomain.ml +++ b/src/cdomains/musteqDomain.ml @@ -1,35 +1,87 @@ +(** Symbolic lvalue equalities domain. *) + open GoblintCil open Pretty module V = Basetype.Variables module F = struct - include Lval.Fields - - let rec prefix x y = match x,y with - | (x::xs), (y::ys) when FI.equal x y -> prefix xs ys - | [], ys -> Some ys - | _ -> None - - let append x y: t = x @ y + module F = CilType.Fieldinfo + module I = Basetype.CilExp + + include Offset.Exp + + let rec kill v (fds: t): t = match fds with + | `Index (x, xs) when I.occurs v x -> `NoOffset + | `Index (x, xs) -> `Index (x, kill v xs) + | `Field (x, xs) -> `Field (x, kill v xs) + | `NoOffset -> `NoOffset + + let replace x exp = map_indices (I.replace x exp) + + let top () = `NoOffset + let is_top x = x = `NoOffset + let bot () = failwith "Bottom offset list!" + let is_bot x = false + + let rec leq x y = + match x,y with + | _, `NoOffset -> true + | `Index (x, xs), `Index (y, ys) when I.equal x y -> leq xs ys + | `Field (x, xs), `Field (y, ys) when F.equal x y -> leq xs ys + | _ -> false + + let rec meet x y = + match x,y with + | `NoOffset, x | x, `NoOffset -> x + | `Index (x, xs), `Index (y, ys) when I.equal x y -> `Index (x, meet xs ys) + | `Field (x, xs), `Field (y, ys) when F.equal x y -> `Field (x, meet xs ys) + | _ -> failwith "Arguments do not meet" + + let narrow = meet + + let rec join x y = + match x,y with + | `Index (x, xs), `Index (y, ys) when I.equal x y -> `Index (x, join xs ys) + | `Field (x, xs), `Field (y, ys) when F.equal x y -> `Field (x, join xs ys) + | _ -> `NoOffset + + let widen = join + + let rec collapse x y = + match x,y with + | `NoOffset, x | x, `NoOffset -> true + | `Index (x, xs), `Index (y, ys) when I.equal x y -> collapse xs ys + | `Field (x, xs), `Field (y, ys) when F.equal x y -> collapse xs ys + | `Field (x, xs), `Field (y, ys) -> false + | `Index (x, xs), `Index (y, ys) -> true + | _ -> failwith "Type mismatch!" + + (* TODO: use the type information to do this properly. Currently, this assumes + there are no nested arrays, so all indexing is eliminated. *) + let real_region (fd:t) typ: bool = not (contains_index fd) + + let pretty_diff () ((x:t),(y:t)): Pretty.doc = + Pretty.dprintf "%a not leq %a" pretty x pretty y let rec occurs v fds = match fds with - | (`Left x::xs) -> occurs v xs - | (`Right x::xs) -> I.occurs v x || occurs v xs - | [] -> false + | `Field (x, xs) -> occurs v xs + | `Index (x, xs) -> I.occurs v x || occurs v xs + | `NoOffset -> false end -module EquAddr = +(* TODO: Use Mval.MakeLattice, but weakened with smaller offset signature. *) +module VF = struct - include Printable.ProdSimple (V) (F) - let show (v,fd) = - let v_str = V.show v in - let fd_str = F.show fd in - v_str ^ fd_str - let pretty () x = text (show x) - - let prefix (v1,fd1: t) (v2,fd2: t): F.t option = - if V.equal v1 v2 then F.prefix fd1 fd2 else None + include Mval.MakePrintable (F) + + (* Indicates if the two var * offset pairs should collapse or not. *) + let collapse (v1,f1) (v2,f2) = V.equal v1 v2 && F.collapse f1 f2 + let leq (v1,f1) (v2,f2) = V.equal v1 v2 && F.leq f1 f2 + (* Joins the fields, assuming the vars are equal. *) + let join (v1,f1) (v2,f2) = (v1,F.join f1 f2) + let kill x (v,f) = v, F.kill x f + let replace x exp (v,fd) = v, F.replace x exp fd end module P = Printable.ProdSimple (V) (V) @@ -63,7 +115,7 @@ struct in fold f d (add_old (x,y) fd d) in - if fd = [] then add_closure (y,x) [] (add_closure (x,y) [] d) + if fd = `NoOffset then add_closure (y,x) `NoOffset (add_closure (x,y) `NoOffset d) else add_closure (x,y) fd d let kill x d = @@ -78,10 +130,10 @@ struct (* Function to find all addresses equal to { vfd } in { eq }. *) let other_addrs vfd eq = let rec helper (v,fd) addrs = - if List.exists (EquAddr.equal (v,fd)) addrs then addrs else + if List.exists (VF.equal (v,fd)) addrs then addrs else let f (x,y) fd' acc = if V.equal v x then - helper (y, F.append fd' fd) acc + helper (y, F.add_offset fd' fd) acc else if V.equal v y then (match F.prefix fd' fd with | Some rest -> helper (x,rest) acc @@ -92,11 +144,11 @@ struct in helper vfd [] - let eval_rv rv: EquAddr.t option = + let eval_rv rv: VF.t option = match rv with - | Lval (Var x, NoOffset) -> Some (x, []) + | Lval (Var x, NoOffset) -> Some (x, `NoOffset) | AddrOf (Var x, ofs) - | AddrOf (Mem (Lval (Var x, NoOffset)), ofs) -> Some (x, F.listify ofs) + | AddrOf (Mem (Lval (Var x, NoOffset)), ofs) -> Some (x, F.of_cil ofs) | _ -> None let eval_lv lv = @@ -104,7 +156,7 @@ struct | Var x, NoOffset -> Some x | _ -> None - let add_eq (x,y) d = add (x,y) [] d + let add_eq (x,y) d = add (x,y) `NoOffset d let assign lval rval st = match lval with @@ -115,9 +167,9 @@ struct | Lval (Var y, NoOffset) when y.vname.[0] = '{' -> st | AddrOf (Var y, NoOffset) when y.vname.[0] = '{' -> st | Lval (Var y, NoOffset) -> add_eq (x,y) st - | AddrOf (Var y, ofs) -> add (x,y) (F.listify ofs) st + | AddrOf (Var y, ofs) -> add (x,y) (F.of_cil ofs) st | AddrOf (Mem (Lval (Var y, NoOffset)), ofs) -> - add (x,y) (F.listify ofs) st + add (x,y) (F.of_cil ofs) st | _ -> st end | _ -> st diff --git a/src/cdomains/mutexAttrDomain.ml b/src/cdomains/mutexAttrDomain.ml new file mode 100644 index 0000000000..748ede0ff5 --- /dev/null +++ b/src/cdomains/mutexAttrDomain.ml @@ -0,0 +1,46 @@ +(** Mutex attribute type domain. *) + +module MutexKind = +struct + include Printable.StdLeaf + + (* NonRec represents any of PTHREAD_MUTEX_ERRORCHECK / PTHREAD_MUTEX_NORMAL / PTHREAD_MUTEX_DEFAULT *) + (* Once Goblint supports the notion of failing lock operations, this should be replaced with more precise definitions *) + type t = NonRec | Recursive [@@deriving eq, ord, hash, to_yojson] + let name () = "mutexKind" + let show x = match x with + | NonRec -> "fast/error_checking" + | Recursive -> "recursive" + + include Printable.SimpleShow (struct + type nonrec t = t + let show = show + end) +end + +include Lattice.Flat(MutexKind) (struct let bot_name = "Uninitialized" let top_name = "Top" end) + +(* Needed because OS X is weird and assigns different constants than normal systems... :( *) +let recursive_int = lazy ( + let res = ref (Z.of_int 2) in (* Use OS X as the default, it doesn't have the enum *) + GoblintCil.iterGlobals !Cilfacade.current_file (function + | GEnumTag (einfo, _) -> + List.iter (fun (name, exp, _) -> + if name = "PTHREAD_MUTEX_RECURSIVE" then + res := Option.get @@ GoblintCil.getInteger exp + ) einfo.eitems + | _ -> () + ); + !res +) + + +let of_int z = + if Z.equal z Z.zero then + `Lifted MutexKind.NonRec + else + let recursive_int = Lazy.force recursive_int in + if Z.equal z recursive_int then + `Lifted MutexKind.Recursive + else + `Top diff --git a/src/cdomains/mval.ml b/src/cdomains/mval.ml new file mode 100644 index 0000000000..2bc5658460 --- /dev/null +++ b/src/cdomains/mval.ml @@ -0,0 +1,74 @@ +include Mval_intf + +open GoblintCil + +module M = Messages + + +module MakePrintable (Offs: Offset.Printable): Printable with type idx = Offs.idx = +struct + type idx = Offs.idx + include Printable.StdLeaf + (* Use Basetype.Variables to print with RichVarinfo. *) + type t = Basetype.Variables.t * Offs.t [@@deriving eq, ord, hash] + + let name () = Format.sprintf "lval (%s)" (Offs.name ()) + + let show ((v, o): t): string = CilType.Varinfo.show v ^ Offs.show o + include Printable.SimpleShow ( + struct + type nonrec t = t + let show = show + end + ) + + let add_offset (v, o) o' = (v, Offs.add_offset o o') + + let type_of (v,o) = try Offs.type_of ~base:v.vtype o with Offset.Type_of_error (t,_) -> t + + let prefix (v1,ofs1) (v2,ofs2) = + if CilType.Varinfo.equal v1 v2 then + Offs.prefix ofs1 ofs2 + else + None + + let to_cil ((v, o): t): lval = (Var v, Offs.to_cil o) + let to_cil_exp lv = Lval (to_cil lv) + + let is_definite (_, o) = Offs.is_definite o + let top_indices (x, o) = (x, Offs.top_indices o) +end + +module MakeLattice (Offs: Offset.Lattice): Lattice with type idx = Offs.idx = +struct + include MakePrintable (Offs) + + let semantic_equal (x, xoffs) (y, yoffs) = + if CilType.Varinfo.equal x y then + let typ1 = x.vtype in + let typ2 = y.vtype in + Offs.semantic_equal ~typ1 xoffs ~typ2 yoffs + else + Some false + + + let leq (x,o) (y,u) = CilType.Varinfo.equal x y && Offs.leq o u + let merge op (x,o) (y,u) = + if CilType.Varinfo.equal x y then + (x, op o u) + else + raise Lattice.Uncomparable + + let join = merge Offs.join + let meet = merge Offs.meet + let widen = merge Offs.widen + let narrow = merge Offs.narrow + + include Lattice.NoBotTop + + let pretty_diff () (x,y) = + Pretty.dprintf "%s: %a not equal %a" (name ()) pretty x pretty y +end + +module Unit = MakePrintable (Offset.Unit) +module Exp = MakePrintable (Offset.Exp) diff --git a/src/cdomains/mval.mli b/src/cdomains/mval.mli new file mode 100644 index 0000000000..caffecbd4a --- /dev/null +++ b/src/cdomains/mval.mli @@ -0,0 +1,4 @@ +(** Domains for mvalues: simplified lvalues, which start with a {!GoblintCil.varinfo}. + Mvalues are the result of resolving {{!GoblintCil.Mem} pointer dereferences} in lvalues. *) + +include Mval_intf.Mval (** @inline *) diff --git a/src/cdomains/lvalMapDomain.ml b/src/cdomains/mvalMapDomain.ml similarity index 87% rename from src/cdomains/lvalMapDomain.ml rename to src/cdomains/mvalMapDomain.ml index 915ea039e2..d0d2f8da85 100644 --- a/src/cdomains/lvalMapDomain.ml +++ b/src/cdomains/mvalMapDomain.ml @@ -1,4 +1,6 @@ -open Prelude +(** Domains for {{!Mval} mvalue} maps. *) + +open Batteries open GoblintCil module M = Messages @@ -11,7 +13,7 @@ exception Error module type S = sig include Lattice.S - type k = Lval.CilLval.t (* key *) + type k = Mval.Exp.t (* key *) type s (* state is defined by Impl *) type r (* record *) @@ -66,13 +68,22 @@ module Value (Impl: sig val string_of_state: s -> string end) : S with type s = Impl.s = struct - type k = Lval.CilLval.t [@@deriving eq, ord, hash] + type k = Mval.Exp.t [@@deriving eq, ord, hash] type s = Impl.s [@@deriving eq, ord, hash] module R = struct - include Printable.Blank + include Printable.StdLeaf type t = { key: k; loc: Node.t list; state: s } [@@deriving eq, ord, hash] - let to_yojson _ = failwith "TODO to_yojson" - let name () = "LValMapDomainValue" + 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 @@ -87,13 +98,13 @@ struct 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 = Goblintutil.create_var @@ Cil.makeVarinfo false "@alias" Cil.voidType, `NoOffset + 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 = Lval.CilLval.show k + 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) = @@ -146,9 +157,9 @@ end module Domain (V: S) = struct - module K = Lval.CilLval + module K = Mval.Exp module V = V - module MD = MapDomain.MapBot (Lval.CilLval) (V) + module MD = MapDomain.MapBot (Mval.Exp) (V) include MD (* Map functions *) @@ -208,7 +219,7 @@ struct let add_all m1 m2 = add_list (bindings m2) m1 (* callstack for locations *) - let callstack_var = Goblintutil.create_var @@ Cil.makeVarinfo false "@callstack" Cil.voidType, `NoOffset + 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 @@ -261,23 +272,28 @@ struct (if may then Messages.warn else Messages.error) ~loc:(Node (List.last loc)) ~category ~tags "%s" msg (* getting keys from Cil Lvals *) - let sprint f x = Pretty.sprint ~width:80 (f () x) - let key_from_lval lval = match lval with (* TODO try to get a Lval.CilLval from Cil.Lval *) - | Var v1, o1 -> v1, Lval.CilLval.of_ciloffs o1 - | Mem Lval(Var v1, o1), o2 -> v1, Lval.CilLval.of_ciloffs (addOffset o1 o2) + 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 *) *) - | _ -> Goblintutil.create_var @@ Cil.makeVarinfo false ("?"^sprint d_exp (Lval lval)) Cil.voidType, `NoOffset (* TODO *) + | _ -> 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 + let query_addrs (ask: Queries.ask) exp = match ask.f (Queries.MayPointTo exp) with + | ad when not (Queries.AD.is_top ad) -> Queries.AD.elements ad | _ -> [] in let exp = AddrOf lval in - let xs = query_lv ask exp in (* MayPointTo -> LValSet *) + let addrs = query_addrs ask exp in (* MayPointTo -> LValSet *) + let keys = List.fold (fun vs addr -> + match addr with + | Queries.AD.Addr.Addr (v,o) -> (v, ValueDomain.Offs.to_exp o) :: vs + | _ -> vs + ) [] addrs + in 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 + Messages.debug ~category:Analyzer "MayPointTo %a = [%a]" d_exp exp (Pretty.docList ~sep:(Pretty.text ", ") pretty_key) keys; + keys end diff --git a/src/cdomains/mval_intf.ml b/src/cdomains/mval_intf.ml new file mode 100644 index 0000000000..7b956bf8b8 --- /dev/null +++ b/src/cdomains/mval_intf.ml @@ -0,0 +1,60 @@ +module type Printable = +sig + type idx + (** Type of indices in mvalue offset. *) + + type t = GoblintCil.varinfo * idx Offset.t + include Printable.S with type t := t (** @closed *) + + val is_definite: t -> bool + (** Whether offset of mvalue has only definite integer indexing (and fields). *) + + val add_offset: t -> idx Offset.t -> t + (** [add_offset m o] appends [o] to [m]. *) + + val prefix: t -> t -> idx Offset.t option + (** [prefix m1 m2] checks if [m1] is a prefix of [m2]. + + @return [Some o] if it is (such that the variables are equal and [add_offset m1 o = m2]), [None] if it is not. *) + + val top_indices: t -> t + (** Change all indices to top indices. *) + + val to_cil: t -> GoblintCil.lval + (** Convert to CIL lvalue. *) + + val to_cil_exp: t -> GoblintCil.exp + (** Convert to CIL lvalue expression. *) + + val type_of: t -> GoblintCil.typ + (** Type of mvalue. *) +end + +module type Lattice = +sig + include Printable (** @closed *) + include Lattice.S with type t := t (** @closed *) + + val semantic_equal: t -> t -> bool option + (** Check semantic equality of two mvalues. + + @return [Some true] if definitely equal, [Some false] if definitely not equal, [None] if unknown. *) +end + +module type Mval = +sig + module type Printable = Printable + module type Lattice = Lattice + + module MakePrintable (Offs: Offset.Printable): Printable with type idx = Offs.idx + (** Make {!Printable} mvalue from {{!Offset.Printable} printable offset}. *) + + module MakeLattice (Offs: Offset.Lattice): Lattice with type idx = Offs.idx + (** Make mvalue {!Lattice} from {{!Offset.Lattice} offset lattice}. *) + + (** Mvalue with {!Offset.Unit} indices in offset. *) + module Unit: Printable with type idx = unit + + (** Mvalue with {!Offset.Exp} indices in offset. *) + module Exp: Printable with type idx = GoblintCil.exp +end diff --git a/src/cdomains/offset.ml b/src/cdomains/offset.ml new file mode 100644 index 0000000000..eca85e08a4 --- /dev/null +++ b/src/cdomains/offset.ml @@ -0,0 +1,263 @@ +include Offset_intf + +open GoblintCil + +module M = Messages + + +module Index = +struct + include Index + + module Unit: Printable with type t = unit = + struct + include Lattice.UnitConf (struct let name = "?" end) + let name () = "unit index" + let equal_to _ _ = `Top + let to_int _ = None + end + + module Exp = + struct + include CilType.Exp + let name () = "exp index" + + let any = CastE (TInt (Cilfacade.ptrdiff_ikind (), []), mkString "any_index") + let all = CastE (TInt (Cilfacade.ptrdiff_ikind (), []), mkString "all_index") + + (* Override output *) + let pretty () x = + if equal x any then + Pretty.text "?" + else + dn_exp () x + + include Printable.SimplePretty ( + struct + type nonrec t = t + let pretty = pretty + end + ) + + let equal_to _ _ = `Top (* TODO: more precise for definite indices *) + let to_int _ = None (* TODO: more precise for definite indices *) + let top () = any + end +end + + + +module MakePrintable (Idx: Index.Printable): Printable with type idx = Idx.t = +struct + type idx = Idx.t + type t = Idx.t offs [@@deriving eq, ord, hash] + include Printable.StdLeaf + + let name () = Format.sprintf "offset (%s)" (Idx.name ()) + + let rec cmp_zero_offset : t -> [`MustZero | `MustNonzero | `MayZero] = function + | `NoOffset -> `MustZero + | `Index (x, o) -> + begin match cmp_zero_offset o, Idx.equal_to (IntOps.BigIntOps.zero) x with + | `MustNonzero, _ + | _, `Neq -> `MustNonzero + | `MustZero, `Eq -> `MustZero + | _, _ -> `MayZero + end + | `Field (x, o) -> + if Cilfacade.is_first_field x then cmp_zero_offset o else `MustNonzero + + let rec show: t -> string = function + | `NoOffset -> "" + | `Index (x,o) -> "[" ^ (Idx.show x) ^ "]" ^ (show o) + | `Field (x,o) -> "." ^ (x.fname) ^ (show o) + + include Printable.SimpleShow ( + struct + type nonrec t = t + let show = show + end + ) + + let rec is_definite: t -> bool = function + | `NoOffset -> true + | `Field (f,o) -> is_definite o + | `Index (i,o) -> Idx.to_int i <> None && is_definite o + + (* append offset o2 to o1 *) + let rec add_offset (o1: t) (o2: t): t = + match o1 with + | `NoOffset -> o2 + | `Field (f1,o1) -> `Field (f1,add_offset o1 o2) + | `Index (i1,o1) -> `Index (i1,add_offset o1 o2) + + let rec remove_offset: t -> t = function + | `NoOffset -> `NoOffset + | `Index (_,`NoOffset) | `Field (_,`NoOffset) -> `NoOffset + | `Index (i,o) -> `Index (i, remove_offset o) + | `Field (f,o) -> `Field (f, remove_offset o) + + let rec to_cil_offset (x:t) = (* TODO: rename/move *) + match x with + | `NoOffset -> NoOffset + | `Field(f,o) -> Field(f, to_cil_offset o) + | `Index(i,o) -> NoOffset (* array domain can not deal with this -> leads to being handeled as access to unknown part *) + + let rec to_exp: t -> exp offs = function + | `NoOffset -> `NoOffset + | `Index (i,o) -> + let i_exp = match Idx.to_int i with + | Some i -> Const (CInt (i, Cilfacade.ptrdiff_ikind (), Some (Z.to_string i))) + | None -> Index.Exp.any + in + `Index (i_exp, to_exp o) + | `Field (f,o) -> `Field (f, to_exp o) + + let rec to_cil: t -> offset = function + | `NoOffset -> NoOffset + | `Index (i,o) -> + let i_exp = match Idx.to_int i with + | Some i -> Const (CInt (i, Cilfacade.ptrdiff_ikind (), Some (Z.to_string i))) + | None -> Index.Exp.any + in + Index (i_exp, to_cil o) + | `Field (f,o) -> Field (f, to_cil o) + + let rec contains_index: t -> bool = function + | `NoOffset -> false + | `Field (_, os) -> contains_index os + | `Index _ -> true + + let rec map_indices g: t -> t = function + | `NoOffset -> `NoOffset + | `Field (f, o) -> `Field (f, map_indices g o) + | `Index (i, o) -> `Index (g i, map_indices g o) + + let top_indices = map_indices (fun _ -> Idx.top ()) + + (* tries to follow o in t *) + let rec type_of ~base:t o = match unrollType t, o with (* resolves TNamed *) + | t, `NoOffset -> t + | TArray (t,_,_), `Index (i,o) + | TPtr (t,_), `Index (i,o) -> type_of ~base:t o + | TComp (ci,_), `Field (f,o) -> + let fi = try getCompField ci f.fname + with Not_found -> + let s = GobPretty.sprintf "Addr.type_offset: field %s not found in type %a" f.fname d_plaintype t in + raise (Type_of_error (t, s)) + in type_of ~base:fi.ftype o + (* TODO: Why? Imprecise on zstd-thread-pool regression tests. *) + (* | TComp _, `Index (_,o) -> type_of ~base:t o (* this happens (hmmer, perlbench). safe? *) *) + | t,o -> + let s = GobPretty.sprintf "Addr.type_offset: could not follow offset in type. type: %a, offset: %a" d_plaintype t pretty o in + raise (Type_of_error (t, s)) + + let rec prefix (x: t) (y: t): t option = match x,y with + | `Index (x, xs), `Index (y, ys) when Idx.equal x y -> prefix xs ys + | `Field (x, xs), `Field (y, ys) when CilType.Fieldinfo.equal x y -> prefix xs ys + | `NoOffset, ys -> Some ys + | _ -> None +end + +module MakeLattice (Idx: Index.Lattice): Lattice with type idx = Idx.t = +struct + include MakePrintable (Idx) + + let rec leq x y = + match x, y with + | `NoOffset, `NoOffset -> true + | `Index (i1,o1), `Index (i2,o2) when Idx.leq i1 i2 -> leq o1 o2 + | `Field (f1,o1), `Field (f2,o2) when CilType.Fieldinfo.equal f1 f2 -> leq o1 o2 + | _ -> false + + let rec merge cop x y = + let op = match cop with `Join -> Idx.join | `Meet -> Idx.meet | `Widen -> Idx.widen | `Narrow -> Idx.narrow in + match x, y with + | `NoOffset, `NoOffset -> `NoOffset + | `Field (x1,y1), `Field (x2,y2) when CilType.Fieldinfo.equal x1 x2 -> `Field (x1, merge cop y1 y2) + | `Index (x1,y1), `Index (x2,y2) -> `Index (op x1 x2, merge cop y1 y2) + | _ -> raise Lattice.Uncomparable (* special case not used for AddressDomain any more due to splitting *) + + let join x y = merge `Join x y + let meet x y = merge `Meet x y + let widen x y = merge `Widen x y + let narrow x y = merge `Narrow x y + + (* NB! Currently we care only about concrete indexes. Base (seeing only a int domain + element) answers with any_index_exp on all non-concrete cases. *) + let rec of_exp: exp offs -> t = function + | `NoOffset -> `NoOffset + | `Index (Const (CInt (i,ik,s)),o) -> `Index (Idx.of_int ik i, of_exp o) + | `Index (_,o) -> `Index (Idx.top (), of_exp o) + | `Field (f,o) -> `Field (f, of_exp o) + + let to_index ?typ (offs: t): Idx.t = + let idx_of_int x = + Idx.of_int (Cilfacade.ptrdiff_ikind ()) (Z.of_int x) + in + let rec offset_to_index_offset ?typ offs = match offs with + | `NoOffset -> idx_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 bits_offset = idx_of_int bits_offset in + let remaining_offset = offset_to_index_offset ~typ:field.ftype o in + Idx.add bits_offset remaining_offset + | `Index (x, o) -> + let (item_typ, item_size_in_bits) = + match Option.map unrollType typ with + | Some TArray(item_typ, _, _) -> + let item_size_in_bits = bitsSizeOf item_typ in + (Some item_typ, idx_of_int item_size_in_bits) + | _ -> + (None, Idx.top ()) + in + let bits_offset = Idx.mul item_size_in_bits x in + let remaining_offset = offset_to_index_offset ?typ:item_typ o in + Idx.add bits_offset remaining_offset + in + offset_to_index_offset ?typ offs + + let semantic_equal ~typ1 xoffs ~typ2 yoffs = + let x_index = to_index ~typ:typ1 xoffs in + let y_index = to_index ~typ:typ2 yoffs in + if M.tracing then M.tracel "addr" "xoffs=%a typ1=%a xindex=%a yoffs=%a typ2=%a yindex=%a\n" pretty xoffs d_plaintype typ1 Idx.pretty x_index pretty yoffs d_plaintype typ2 Idx.pretty y_index; + Idx.to_bool (Idx.eq x_index y_index) + + include Lattice.NoBotTop + + let pretty_diff () (x,y) = + Pretty.dprintf "%s: %a not equal %a" (name ()) pretty x pretty y +end + +module Unit = +struct + include MakePrintable (Index.Unit) + + (* TODO: rename to of_poly? *) + let rec of_offs: 'i offs -> t = function + | `NoOffset -> `NoOffset + | `Field (f,o) -> `Field (f, of_offs o) + | `Index (i,o) -> `Index ((), of_offs o) + + let rec of_cil: offset -> t = function + | NoOffset -> `NoOffset + | Index (i,o) -> `Index ((), of_cil o) + | Field (f,o) -> `Field (f, of_cil o) +end + +module Exp = +struct + include MakePrintable (Index.Exp) + + let rec of_cil: offset -> t = function + | NoOffset -> `NoOffset + | Index (i,o) -> `Index (i, of_cil o) + | Field (f,o) -> `Field (f, of_cil o) + + (* Overrides MakePrintable.to_cil. *) + let rec to_cil: t -> offset = function + | `NoOffset -> NoOffset + | `Index (i,o) -> Index (i, to_cil o) + | `Field (f,o) -> Field (f, to_cil o) +end diff --git a/src/cdomains/offset.mli b/src/cdomains/offset.mli new file mode 100644 index 0000000000..ceaa0af5a5 --- /dev/null +++ b/src/cdomains/offset.mli @@ -0,0 +1,3 @@ +(** Domains for variable offsets, i.e. array indices and struct fields. *) + +include Offset_intf.Offset (** @inline *) diff --git a/src/cdomains/offset_intf.ml b/src/cdomains/offset_intf.ml new file mode 100644 index 0000000000..2bf3dedf6e --- /dev/null +++ b/src/cdomains/offset_intf.ml @@ -0,0 +1,175 @@ +type 'i t = [ + | `NoOffset (** No offset. Marks the end of offset list. *) + | `Field of CilType.Fieldinfo.t * 'i t (** Offset starting with a struct field. *) + | `Index of 'i * 'i t (** Offset starting with an array index. *) +] [@@deriving eq, ord, hash] + +type 'i offs = 'i t [@@deriving eq, ord, hash] +(** Outer alias to allow referring to {!t} in inner signatures. *) + +module Index = +struct + + (** Subinterface of {!IntDomain.Z} which is sufficient for Printable (but not Lattice) Offset. *) + module type Printable = + sig + include Printable.S (** @closed *) + + val top: unit -> t + (** Unknown index. *) + + val equal_to: Z.t -> t -> [`Eq | `Neq | `Top] + (** Check semantic equality of an integer and the index. + + @return [`Eq] if definitely equal, [`Neq] if definitely not equal, [`Top] if unknown. *) + + val to_int: t -> Z.t option + (** Convert to definite integer if possible. *) + end + + module type Lattice = IntDomain.Z +end + +exception Type_of_error of GoblintCil.typ * string + +module type Printable = +sig + type idx + (** Type of indices in offset. *) + + include Printable.S with type t = idx offs (** @closed *) + + val is_definite: t -> bool + (** Whether offset has only definite integer indexing (and fields). *) + + val contains_index: t -> bool + (** Whether offset contains any indexing. *) + + val add_offset: t -> t -> t + (** [add_offset o1 o2] appends [o2] to [o1]. *) + + val remove_offset: t -> t + (** Remove last indexing or field from offset. *) + + val prefix: t -> t -> t option + (** [prefix o1 o2] checks if [o1] is a prefix of [o2]. + + @return [Some o] if it is (such that [add_offset o1 o = o2]), [None] if it is not. *) + + val map_indices: (idx -> idx) -> t -> t + (** Apply function to all indexing. *) + + val top_indices: t -> t + (** Change all indices to top indices. *) + + val to_cil: t -> GoblintCil.offset + (** Convert to CIL offset. *) + + val to_exp: t -> GoblintCil.exp offs + (** Convert to Goblint offset with {!GoblintCil.exp} indices. *) + + val to_cil_offset: t -> GoblintCil.offset + (** Version of {!to_cil} which drops indices for {!ArrayDomain}. *) + + val cmp_zero_offset: t -> [`MustZero | `MustNonzero | `MayZero] + (** Compare offset to zero offset. + + Zero indices and first fields of a struct are in the same physical memory location as the outer object. + + @return [`MustZero] if definitely zero, [`MustNonzero] if definitely not zero, [`MayZero] if unknown.*) + + val type_of: base:GoblintCil.typ -> t -> GoblintCil.typ + (** Type of the offset on the [base] type. + + @raise Type_of_error if could not follow offset completely. *) +end + +module type Lattice = +sig + include Printable (** @closed *) + include Lattice.S with type t := t (** @closed *) + + val of_exp: GoblintCil.exp offs -> t + (** Convert from Goblint offset with {!GoblintCil.exp} indices. *) + + val to_index: ?typ:GoblintCil.typ -> t -> idx + (** Physical memory offset in bytes of the entire offset. + Used for {!semantic_equal}. + + @param typ base type. *) + + val semantic_equal: typ1:GoblintCil.typ -> t -> typ2:GoblintCil.typ -> t -> bool option + (** Check semantic equality of two offsets. + + @param typ1 base type of first offset. + @param typ2 base type of second offset. + @return [Some true] if definitely equal, [Some false] if definitely not equal, [None] if unknown. *) +end + +module type Offset = +sig + type nonrec 'i t = 'i t [@@deriving eq, ord, hash] + (** List of nested offsets. + + @param 'i Type of indices. *) + + (** Domains for offset indices. *) + module Index: + sig + include module type of Index + + module Unit: Printable with type t = unit + (** Unit index. + Usually represents an arbitrary index. *) + + module Exp: + sig + include Printable with type t = GoblintCil.exp + + (** Special index expression for some unknown index. + Weakly updates array in assignment. + Used for [exp.fast_global_inits]. *) + val any: GoblintCil.exp + + (** Special index expression for all indices. + Strongly updates array in assignment. + Used for Goblint-specific witness invariants. *) + val all: GoblintCil.exp + end + end + + exception Type_of_error of GoblintCil.typ * string + (** {!Printable.type_of} could not follow offset completely. *) + + module type Printable = Printable + module type Lattice = Lattice + + module MakePrintable (Idx: Index.Printable): Printable with type idx = Idx.t + (** Make {!Printable} offset from {{!Index.Printable} printable indices}. *) + + module MakeLattice (Idx: Index.Lattice): Lattice with type idx = Idx.t + (** Make offset {!Lattice} from {{!Index.Lattice} lattice indices}. *) + + (** Offset with {!Index.Unit} indices. *) + module Unit: + sig + include Printable with type idx = unit + val of_offs : 'i offs -> t + (** Convert from Goblint offset with arbitrary indices. *) + + val of_cil : GoblintCil.offset -> t + (** Convert from CIL offset. *) + end + + (** Offset with {!Index.Exp} indices. *) + module Exp: + sig + include Printable with type idx = GoblintCil.exp + + val of_cil : GoblintCil.offset -> t + (** Convert from CIL offset. *) + + val to_cil : t -> GoblintCil.offset + (** Convert to CIL offset. *) + end +end diff --git a/src/cdomains/preValueDomain.ml b/src/cdomains/preValueDomain.ml index 6097778ecb..9766a1ac60 100644 --- a/src/cdomains/preValueDomain.ml +++ b/src/cdomains/preValueDomain.ml @@ -1,5 +1,12 @@ module ID = IntDomain.IntDomTuple module FD = FloatDomain.FloatDomTupleImpl -module IndexDomain = IntDomain.IntDomWithDefaultIkind (ID) (IntDomain.PtrDiffIkind) -module AD = AddressDomain.AddressSet (IndexDomain) -module Addr = Lval.NormalLat (IndexDomain) +module IndexDomain = IntDomain.IntDomWithDefaultIkind (ID) (IntDomain.PtrDiffIkind) (* TODO: add ptrdiff cast into to_int? *) +module Offs = Offset.MakeLattice (IndexDomain) +module Mval = Mval.MakeLattice (Offs) +module AD = AddressDomain.AddressSet (Mval) (ID) +module Addr = +struct + include AD.Addr + module Offs = Offs + module Mval = Mval +end diff --git a/src/cdomains/pthreadDomain.ml b/src/cdomains/pthreadDomain.ml index 9f2d4dbad7..8cef57bdbd 100644 --- a/src/cdomains/pthreadDomain.ml +++ b/src/cdomains/pthreadDomain.ml @@ -1,4 +1,6 @@ -open Prelude +(** Domains for extraction of Pthread programs. *) + +open Batteries (** Thread ID *) module Tid = IntDomain.Flattened @@ -21,6 +23,8 @@ module Pred = struct end module D = struct + include Printable.StdLeaf + type domain = { tid : Tid.t; pred : Pred.t; ctx : Ctx.t } [@@deriving to_yojson] type t = domain @@ -71,10 +75,6 @@ module D = struct let meet = op_scheme Tid.meet Pred.meet Ctx.meet let narrow = meet - let arbitrary () = failwith "no arbitrary" - let tag x = failwith "no tag" - let relift x = x - let pretty_diff () (x,y) = if not (Tid.leq x.tid y.tid) then Tid.pretty_diff () (x.tid,y.tid) diff --git a/src/cdomains/regionDomain.ml b/src/cdomains/regionDomain.ml index c9748c7ce0..a9ffcd3e75 100644 --- a/src/cdomains/regionDomain.ml +++ b/src/cdomains/regionDomain.ml @@ -1,35 +1,23 @@ +(** Domains for disjoint heap regions. *) + open GoblintCil open GobConfig +open MusteqDomain -module GU = Goblintutil -module V = Basetype.Variables module B = Printable.UnitConf (struct let name = "•" end) -module F = Lval.Fields - -module VF = -struct - include Printable.ProdSimple (V) (F) - let show (v,fd) = - let v_str = V.show v in - let fd_str = F.show fd in - v_str ^ fd_str - let pretty () x = Pretty.text (show x) - - let printXml f (v,fi) = - BatPrintf.fprintf f "\n\n%s%a\n\n\n" (XmlUtil.escape (V.show v)) F.printInnerXml fi - - (* Indicates if the two var * offset pairs should collapse or not. *) - let collapse (v1,f1) (v2,f2) = V.equal v1 v2 && F.collapse f1 f2 - let leq (v1,f1) (v2,f2) = V.equal v1 v2 && F.leq f1 f2 - (* Joins the fields, assuming the vars are equal. *) - let join (v1,f1) (v2,f2) = (v1,F.join f1 f2) - let kill x (v,f) = v, F.kill x f - let replace x exp (v,fd) = v, F.replace x exp fd -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 () -> @@ -46,13 +34,13 @@ struct let leq x y = match x,y with | `Right (), `Right () -> true - | `Right (), _ | _, `Right () -> false + | `Right (), _ | _, `Right () -> false (* incomparable according to collapse *) | `Left x, `Left y -> VF.leq x y let join (x:t) (y:t) :t = match x,y with - | `Right (), _ -> `Right () - | _, `Right () -> `Right () + | `Right (), `Right () -> `Right () + | `Right (), _ | _, `Right () -> raise Lattice.Uncomparable (* incomparable according to collapse *) | `Left x, `Left y -> `Left (VF.join x y) let lift f y = match y with @@ -72,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 @@ -116,7 +105,7 @@ struct let closure p m = RegMap.map (RegPart.closure p) m - let remove v (p,m) = p, RegMap.remove (v,[]) m + let remove v (p,m) = p, RegMap.remove (v, `NoOffset) m let remove_vars (vs: varinfo list) (cp:t): t = List.fold_right remove vs cp @@ -139,16 +128,7 @@ struct type eval_t = (bool * elt * F.t) option let eval_exp (ask: Queries.ask) exp: eval_t = - let offsornot offs = if (get_bool "exp.region-offsets") then F.listify offs else [] in - let rec do_offs deref def = function - | Field (fd, offs) -> begin - match Goblintutil.is_blessed (TComp (fd.fcomp, [])) with - | Some v -> do_offs deref (Some (deref, (v, offsornot (Field (fd, offs))), [])) offs - | None -> do_offs deref def offs - end - | Index (_, offs) -> do_offs deref def offs - | NoOffset -> def - in + let offsornot offs = if (get_bool "exp.region-offsets") then F.of_cil offs else `NoOffset in (* The intuition for the offset computations is that we keep the static _suffix_ of an * access path. These can be used to partition accesses when fields do not overlap. * This means that for pointer dereferences and when obtaining the value from an lval @@ -156,7 +136,7 @@ struct * unknown in the region. *) let rec eval_rval deref rval = match rval with - | Lval lval -> BatOption.map (fun (deref, v, offs) -> (deref, v, [])) (eval_lval deref lval) + | Lval lval -> BatOption.map (fun (deref, v, offs) -> (deref, v, `NoOffset)) (eval_lval deref lval) | AddrOf lval -> eval_lval deref lval | CastE (typ, exp) -> eval_rval deref exp | BinOp (MinusPI, p, i, typ) @@ -165,17 +145,11 @@ struct | _ -> None and eval_lval deref lval = match lval with - | (Var x, NoOffset) when Goblintutil.is_blessed x.vtype <> None -> - begin match Goblintutil.is_blessed x.vtype with - | Some v -> Some (deref, (v,[]), []) - | _ when BaseUtil.is_global ask x -> Some (deref, (x, []), []) - | _ -> None - end - | (Var x, offs) -> do_offs deref (Some (deref, (x, offsornot offs), [])) offs + | (Var x, offs) -> Some (deref, (x, offsornot offs), `NoOffset) | (Mem exp,offs) -> match eval_rval true exp with - | Some (deref, v, _) -> do_offs deref (Some (deref, v, offsornot offs)) offs - | x -> do_offs deref x offs + | Some (deref, v, _) -> Some (deref, v, offsornot offs) + | x -> x in eval_rval false exp @@ -205,7 +179,7 @@ struct if VF.equal x y then st else let (p,m) = st in begin let append_offs_y = RS.map (function - | `Left (v, offs) -> `Left (v, offs @ offs_y) + | `Left (v, offs) -> `Left (v, F.add_offset offs offs_y) | `Right () -> `Right () ) in @@ -240,7 +214,7 @@ struct | _ -> p,m let related_globals (ask: Queries.ask) (deref_vfd: eval_t) (p,m: t): elt list = - let add_o o2 (v,o) = (v,o@o2) in + let add_o o2 (v,o) = (v, F.add_offset o o2) in match deref_vfd with | Some (true, vfd, os) -> let vfd_class = diff --git a/src/cdomains/specDomain.ml b/src/cdomains/specDomain.ml index 60a66fd704..75a9d8edc5 100644 --- a/src/cdomains/specDomain.ml +++ b/src/cdomains/specDomain.ml @@ -1,6 +1,8 @@ -open Prelude +(** Domains for finite automaton specification file analysis. *) -module D = LvalMapDomain +open Batteries + +module D = MvalMapDomain module Val = diff --git a/src/cdomains/stackDomain.ml b/src/cdomains/stackDomain.ml index b3300bb11b..3a83c78503 100644 --- a/src/cdomains/stackDomain.ml +++ b/src/cdomains/stackDomain.ml @@ -1,4 +1,4 @@ -module GU = Goblintutil +(** Call stack domains. *) module type S = sig diff --git a/src/cdomains/structDomain.ml b/src/cdomains/structDomain.ml index 33fb313f08..b2d47c7e53 100644 --- a/src/cdomains/structDomain.ml +++ b/src/cdomains/structDomain.ml @@ -98,6 +98,8 @@ struct (* invariant for one index *) | Index (i, offset) -> Invariant.none + + let relift = M.relift end module SetsCommon (Val:Arg) = @@ -230,6 +232,8 @@ struct (* let invariant = HS.invariant *) let invariant ~value_invariant ~offset ~lval _ = Invariant.none (* TODO *) + + let relift = HS.relift end module KeyedSets (Val: Arg) = @@ -438,6 +442,8 @@ struct (* let invariant c (x,_) = HS.invariant c x *) let invariant ~value_invariant ~offset ~lval _ = Invariant.none (* TODO *) + + let relift (hs, f) = (HS.relift hs, f) end diff --git a/src/cdomains/structDomain.mli b/src/cdomains/structDomain.mli index d83d807096..15b75c6d41 100644 --- a/src/cdomains/structDomain.mli +++ b/src/cdomains/structDomain.mli @@ -1,4 +1,4 @@ -(** Abstract domains representing structs. *) +(** Abstract domains for C structs. *) open GoblintCil diff --git a/src/cdomains/symbLocksDomain.ml b/src/cdomains/symbLocksDomain.ml index 6fdaf2ab28..4a44911a53 100644 --- a/src/cdomains/symbLocksDomain.ml +++ b/src/cdomains/symbLocksDomain.ml @@ -1,5 +1,6 @@ +(** Symbolic lockset domain. *) + open GoblintCil -open Pretty module M = Messages @@ -54,7 +55,7 @@ struct let eq_const c1 c2 = match c1, c2 with - | CInt (i1,_,_), CInt (i2,_,_) -> Cilint.compare_cilint i1 i2 = 0 + | CInt (i1,_,_), CInt (i2,_,_) -> Z.compare i1 i2 = 0 | CStr (s1,_) , CStr (s2,_) -> s1=s2 | CWStr (s1,_) , CWStr (s2,_) -> s1=s2 | CChr c1 , CChr c2 -> c1=c2 @@ -98,11 +99,11 @@ struct | Lval (Var _,_) | AddrOf (Var _,_) | StartOf (Var _,_) -> exp - | Lval (Mem e,o) when simple_eq e q -> Lval (Var v, addOffset o (Lval.CilLval.to_ciloffs offs)) + | Lval (Mem e,o) when simple_eq e q -> Lval (Var v, addOffset o (Offset.Exp.to_cil offs)) | Lval (Mem e,o) -> Lval (Mem (replace_base (v,offs) q e), o) - | AddrOf (Mem e,o) when simple_eq e q -> AddrOf (Var v, addOffset o (Lval.CilLval.to_ciloffs offs)) + | AddrOf (Mem e,o) when simple_eq e q -> AddrOf (Var v, addOffset o (Offset.Exp.to_cil offs)) | AddrOf (Mem e,o) -> AddrOf (Mem (replace_base (v,offs) q e), o) - | StartOf (Mem e,o) when simple_eq e q -> StartOf (Var v, addOffset o (Lval.CilLval.to_ciloffs offs)) + | StartOf (Mem e,o) when simple_eq e q -> StartOf (Var v, addOffset o (Offset.Exp.to_cil offs)) | StartOf (Mem e,o) -> StartOf (Mem (replace_base (v,offs) q e), o) | CastE (t,e) -> CastE (t, replace_base (v,offs) q e) @@ -113,7 +114,7 @@ struct | Index (i,o) -> isConstant i && conc o | Field (_,o) -> conc o - let star = Lval (Cil.var (Goblintutil.create_var (makeGlobalVar "*" intType))) + let star = Lval (Cil.var (Cilfacade.create_var (makeGlobalVar "*" intType))) let rec one_unknown_array_index exp = let rec separate_fields_index o = @@ -162,13 +163,9 @@ end module LockingPattern = struct - include Printable.Std - type t = Exp.t * Exp.t * Exp.t [@@deriving eq, ord, hash, to_yojson] + include Printable.Prod3 (Exp) (Exp) (Exp) let name () = "Per-Element locking triple" - let pretty () (x,y,z) = text "(" ++ d_exp () x ++ text ", "++ d_exp () y ++ text ", "++ d_exp () z ++ text ")" - let show (x,y,z) = sprint ~width:max_int (dprintf "(%a,%a,%a)" d_exp x d_exp y d_exp z) - type ee = EVar of varinfo | EAddr | EDeref @@ -276,7 +273,6 @@ struct Some (elem, fromEl a dummy, fromEl l dummy) | _ -> None with Invalid_argument _ -> None - let printXml f (x,y,z) = BatPrintf.fprintf f "\n\n1\n%a2\n%a3\n%a\n\n" Exp.printXml x Exp.printXml y Exp.printXml z end (** Index-based symbolic lock *) @@ -286,7 +282,7 @@ struct (** Index in index-based symbolic lock *) module Idx = struct - include Printable.Std + include Printable.StdLeaf type t = | Unknown (** Unknown index. Mutex index not synchronized with access index. *) | Star (** Star index. Mutex index synchronized with access index. Corresponds to star_0 in ASE16 paper, multiple star indices not supported in this implementation. *) @@ -306,9 +302,10 @@ struct let equal_to _ _ = `Top let to_int _ = None + let top () = Unknown end - include Lval.Normal (Idx) + include AddressDomain.AddressPrintable (Mval.MakePrintable (Offset.MakePrintable (Idx))) let rec conv_const_offset x = match x with @@ -317,5 +314,5 @@ struct | Index (_,o) -> `Index (Idx.Unknown, conv_const_offset o) | Field (f,o) -> `Field (f, conv_const_offset o) - let from_var_offset (v, o) = from_var_offset (v, conv_const_offset o) + let of_mval (v, o) = of_mval (v, conv_const_offset o) end diff --git a/src/cdomains/threadFlagDomain.ml b/src/cdomains/threadFlagDomain.ml index 09d19d9e74..80ba9b7a52 100644 --- a/src/cdomains/threadFlagDomain.ml +++ b/src/cdomains/threadFlagDomain.ml @@ -1,3 +1,5 @@ +(** Multi-threadedness flag domains. *) + module type S = sig include Lattice.S diff --git a/src/cdomains/threadIdDomain.ml b/src/cdomains/threadIdDomain.ml index c3f05b6c84..a9646cffd2 100644 --- a/src/cdomains/threadIdDomain.ml +++ b/src/cdomains/threadIdDomain.ml @@ -1,10 +1,12 @@ +(** Thread ID domains. *) + open GoblintCil open FlagHelper +open BatPervasives module type S = sig include Printable.S - include MapDomain.Groupable with type t := t val threadinit: varinfo -> multiple:bool -> t val is_main: t -> bool @@ -21,7 +23,7 @@ module type Stateless = sig include S - val threadenter: Node.t -> varinfo -> t + val threadenter: multiple:bool -> Node.t -> int option -> varinfo -> t end module type Stateful = @@ -30,21 +32,34 @@ sig module D: Lattice.S - val threadenter: t * D.t -> Node.t -> varinfo -> t list - val threadspawn: D.t -> Node.t -> 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 unique thread create thus far, get it *) + (** If it is possible to get a list of threads created thus far, get it *) val created: t -> D.t -> (t list) option end + (** Type to represent an abstract thread ID. *) module FunNode: Stateless = struct - include Printable.Prod (CilType.Varinfo) (Printable.Option (Node) (struct let name = "no node" end)) + include + Printable.Prod + (CilType.Varinfo) ( + Printable.Option ( + Printable.Prod + (Node) ( + Printable.Option + (WrapperFunctionAnalysis0.ThreadCreateUniqueCount) + (struct let name = "no index" end))) + (struct let name = "no node" end)) let show = function - | (f, Some n) -> f.vname ^ "@" ^ (CilType.Location.show (UpdateCil.getLoc n)) + | (f, Some (n, i)) -> + f.vname + ^ "@" ^ (CilType.Location.show (UpdateCil.getLoc n)) + ^ "#" ^ Option.fold ~none:"top" ~some:string_of_int i | (f, None) -> f.vname include Printable.SimpleShow ( @@ -55,14 +70,16 @@ struct ) let threadinit v ~multiple: t = (v, None) - let threadenter l v: t = + + let threadenter ~multiple l i v: t = if GobConfig.get_bool "ana.thread.include-node" then - (v, Some l) + let counter = Option.map (fun x -> if multiple then WrapperFunctionAnalysis0.ThreadCreateUniqueCount.top () else x) i in + (v, Some (l, counter)) else (v, None) let is_main = function - | ({vname = "main"; _}, None) -> true + | ({vname; _}, None) -> List.mem vname @@ GobConfig.get_string_list "mainfun" | _ -> false let is_unique _ = false (* TODO: should this consider main unique? *) @@ -77,8 +94,8 @@ struct module D = Lattice.Unit - let threadenter _ n v = [threadenter n v] - let threadspawn () _ _ = () + let threadenter ~multiple _ n i v = [threadenter ~multiple n i v] + let threadspawn ~multiple () _ _ _ = () let created _ _ = None end @@ -105,13 +122,16 @@ struct else Pretty.dprintf "%a, %a" P.pretty p S.pretty s - let show x = Pretty.sprint ~width:max_int (pretty () x) + let show x = GobPretty.sprint pretty x - module D = - struct + module D = Lattice.Prod (struct + include S + let name () = "created (once)" + end) (struct include S - let name () = "created" - end + let name () = "created (multiple times)" + end) + let is_unique (_, s) = S.is_empty s @@ -126,17 +146,15 @@ struct let may_create (p,s) (p',s') = S.subset (S.union (S.of_list p) s) (S.union (S.of_list p') s') - let compose ((p, s) as current) n = - if BatList.mem_cmp Base.compare n p then ( - (* TODO: can be optimized by implementing some kind of partition_while function *) - let s' = S.of_list (BatList.take_while (fun m -> not (Base.equal n m)) p) in - let p' = List.tl (BatList.drop_while (fun m -> not (Base.equal n m)) p) in - (p', S.add n (S.union s s')) + let compose ((p, s) as current) ni = + if BatList.mem_cmp Base.compare ni p then ( + let shared, unique = BatList.span (not % Base.equal ni) p in + (List.tl unique, S.of_list shared |> S.union s |> S.add ni) ) else if is_unique current then - (n :: p, s) + (ni :: p, s) else - (p, S.add n s) + (p, S.add ni s) let threadinit v ~multiple = let base_tid = Base.threadinit v ~multiple in @@ -145,20 +163,32 @@ struct else ([base_tid], S.empty ()) - let threadenter ((p, _ ) as current, cs) (n: Node.t) v = - let n = Base.threadenter n v in - let ((p', s') as composed) = compose current n in - if is_unique composed && S.mem n cs then - [(p, S.singleton n); composed] (* also respawn unique version of the thread to keep it reachable while thread ID sets refer to it *) + 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 || 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] - let created current cs = - let els = D.elements cs in - Some (List.map (compose current) els) + let created ((p, _ ) as current) (cs, cms) = + let els = S.elements cs in + let map_one e = + let ((p', s') as composed) = compose current e in + if is_unique composed && S.mem e cms then + (* Also construct the non-unique version that was spawned as e was encountered multiple times *) + [(p, S.singleton e); composed] + else + [composed] + in + Some (List.concat_map map_one els) - let threadspawn cs l v = - S.add (Base.threadenter l v) cs + 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, 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 @@ -182,7 +212,7 @@ struct (* Plain thread IDs *) module P = Unit(FunNode) - include GroupableFlagHelper(H)(P)(struct + include FlagHelper(H)(P)(struct let msg = "FlagConfiguredTID received a value where not exactly one component is set" let name = "FlagConfiguredTID" end) @@ -228,24 +258,24 @@ struct | (None, Some x'), `Top -> liftp x' (P.D.top ()) | _ -> None - let threadenter x n v = + let threadenter ~multiple x n i v = match x with - | ((Some x', None), `Lifted1 d) -> H.threadenter (x',d) n v |> List.map (fun t -> (Some t, None)) - | ((Some x', None), `Bot) -> H.threadenter (x',H.D.bot ()) n v |> List.map (fun t -> (Some t, None)) - | ((Some x', None), `Top) -> H.threadenter (x',H.D.top ()) n v |> List.map (fun t -> (Some t, None)) - | ((None, Some x'), `Lifted2 d) -> P.threadenter (x',d) n v |> List.map (fun t -> (None, Some t)) - | ((None, Some x'), `Bot) -> P.threadenter (x',P.D.bot ()) n v |> List.map (fun t -> (None, Some t)) - | ((None, Some x'), `Top) -> P.threadenter (x',P.D.top ()) n 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 v = + let threadspawn ~multiple x n i v = match x with - | `Lifted1 x' -> `Lifted1 (H.threadspawn x' n v) - | `Lifted2 x' -> `Lifted2 (P.threadspawn x' n v) - | `Bot when history_enabled () -> `Lifted1 (H.threadspawn (H.D.bot ()) n v) - | `Bot -> `Lifted2 (P.threadspawn (P.D.bot ()) n v) - | `Top when history_enabled () -> `Lifted1 (H.threadspawn (H.D.top ()) n v) - | `Top -> `Lifted2 (P.threadspawn (P.D.top ()) n 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/unionDomain.ml b/src/cdomains/unionDomain.ml index d2657621e7..ac25450c6a 100644 --- a/src/cdomains/unionDomain.ml +++ b/src/cdomains/unionDomain.ml @@ -1,3 +1,5 @@ +(** Abstract domains for C unions. *) + open GoblintCil module type Arg = @@ -13,16 +15,29 @@ sig val invariant: value_invariant:(offset:Cil.offset -> lval:Cil.lval -> value -> Invariant.t) -> offset:Cil.offset -> lval:Cil.lval -> t -> Invariant.t end -module Field = Lattice.Flat (CilType.Fieldinfo) (struct - let top_name = "Unknown field" - let bot_name = "If you see this, you are special!" - end) +module Field = struct + include Lattice.Flat (CilType.Fieldinfo) (struct + let top_name = "Unknown field" + let bot_name = "If you see this, you are special!" + end) + + let meet f g = + if equal f g then + f + else + raise Lattice.Uncomparable +end module Simple (Values: Arg) = struct include Lattice.Prod (Field) (Values) type value = Values.t + let meet (f, x) (g, y) = + let field = Field.meet f g in + let value = Values.meet x y in + (field, value) + let invariant ~value_invariant ~offset ~lval (lift_f, v) = match offset with (* invariants for all fields *) diff --git a/src/cdomains/valueDomain.ml b/src/cdomains/valueDomain.ml index aabc1e5dc4..cba4b04c18 100644 --- a/src/cdomains/valueDomain.ml +++ b/src/cdomains/valueDomain.ml @@ -1,13 +1,16 @@ +(** Domain for a single {!Base} analysis value. *) + open GoblintCil open Pretty open PrecisionUtil include PreValueDomain -module Offs = Lval.Offset (IndexDomain) +module Offs = Offset.MakeLattice (IndexDomain) module M = Messages -module GU = Goblintutil -module Q = Queries module BI = IntOps.BigIntOps +module MutexAttr = MutexAttrDomain +module VDQ = ValueDomainQueries +module AD = VDQ.AD module AddrSetDomain = SetDomain.ToppedSet(Addr)(struct let topname = "All" end) module ArrIdxDomain = IndexDomain @@ -15,18 +18,19 @@ module type S = sig include Lattice.S type offs - val eval_offset: Q.ask -> (AD.t -> t) -> t-> offs -> exp option -> lval option -> typ -> t - val update_offset: Q.ask -> t -> offs -> t -> exp option -> lval -> typ -> t + val eval_offset: VDQ.t -> (AD.t -> t) -> t-> offs -> exp option -> lval option -> typ -> t + val update_offset: VDQ.t -> t -> offs -> t -> exp option -> lval -> typ -> t val update_array_lengths: (exp -> t) -> t -> Cil.typ -> t - val affect_move: ?replace_with_const:bool -> Q.ask -> t -> varinfo -> (exp -> int option) -> t + val affect_move: ?replace_with_const:bool -> VDQ.t -> t -> varinfo -> (exp -> int option) -> t val affecting_vars: t -> varinfo list - val invalidate_value: Q.ask -> typ -> t -> t + val invalidate_value: VDQ.t -> typ -> t -> t val is_safe_cast: typ -> typ -> bool val cast: ?torg:typ -> typ -> t -> t val smart_join: (exp -> BI.t option) -> (exp -> BI.t option) -> t -> t -> t val smart_widen: (exp -> BI.t option) -> (exp -> BI.t option) -> t -> t -> t val smart_leq: (exp -> BI.t option) -> (exp -> BI.t option) -> t -> t -> bool val is_immediate_type: typ -> bool + val is_mutex_type: typ -> bool val bot_value: ?varAttr:attributes -> typ -> t val is_bot_value: t -> bool val init_value: ?varAttr:attributes -> typ -> t @@ -34,7 +38,8 @@ sig val is_top_value: t -> typ -> bool val zero_init_value: ?varAttr:attributes -> typ -> t - val project: Q.ask -> int_precision option-> ( attributes * attributes ) option -> t -> t + val project: VDQ.t -> int_precision option-> ( attributes * attributes ) option -> t -> t + val mark_jmpbufs_as_copied: t -> t end module type Blob = @@ -45,58 +50,72 @@ sig include Lattice.S with type t = value * size * origin val value: t -> value - val invalidate_value: Q.ask -> typ -> t -> t + val invalidate_value: VDQ.t -> typ -> t -> t end (* ZeroInit is true if malloc was used to allocate memory and it's false if calloc was used *) -module ZeroInit = Lattice.Fake(Basetype.RawBools) +module ZeroInit = +struct + include Lattice.Fake(Basetype.RawBools) + let name () = "no zeroinit" +end module Blob (Value: S) (Size: IntDomain.Z)= struct - include Lattice.Prod3 (Value) (Size) (ZeroInit) + include Lattice.Prod3 (struct include Value let name () = "value" end) (struct include Size let name () = "size" end) (ZeroInit) let name () = "blob" type value = Value.t type size = Size.t type origin = ZeroInit.t - let printXml f (x, y, z) = - BatPrintf.fprintf f "\n\n\n%s\n\n%a\nsize\n\n%a\norigin\n\n%a\n\n" (XmlUtil.escape (Value.name ())) Value.printXml x Size.printXml y ZeroInit.printXml z let value (a, b, c) = a + let relift (a, b, c) = Value.relift a, b, c let invalidate_value ask t (v, s, o) = Value.invalidate_value ask t v, s, o end module Threads = ConcDomain.ThreadSet - -module rec Compound: S with type t = [ - | `Top - | `Int of ID.t - | `Float of FD.t - | `Address of AD.t - | `Struct of Structs.t - | `Union of Unions.t - | `Array of CArrays.t - | `Blob of Blobs.t - | `Thread of Threads.t - | `Mutex - | `Bot - ] and type offs = (fieldinfo,IndexDomain.t) Lval.offs = +module JmpBufs = JmpBufDomain.JmpBufSetTaint + +module rec Compound: sig + type t = + | Top + | Int of ID.t + | Float of FD.t + | Address of AD.t + | Struct of Structs.t + | Union of Unions.t + | Array of CArrays.t + | Blob of Blobs.t + | Thread of Threads.t + | JmpBuf of JmpBufs.t + | Mutex + | MutexAttr of MutexAttrDomain.t + | Bot + include S with type t := t and type offs = IndexDomain.t Offset.t +end = struct - type t = [ - | `Top - | `Int of ID.t - | `Float of FD.t - | `Address of AD.t - | `Struct of Structs.t - | `Union of Unions.t - | `Array of CArrays.t - | `Blob of Blobs.t - | `Thread of Threads.t - | `Mutex - | `Bot - ] [@@deriving eq, ord, hash] + type t = + | Top + | Int of ID.t + | Float of FD.t + | Address of AD.t + | Struct of Structs.t + | Union of Unions.t + | Array of CArrays.t + | Blob of Blobs.t + | Thread of Threads.t + | JmpBuf of JmpBufs.t + | Mutex + | MutexAttr of MutexAttrDomain.t + | Bot + [@@deriving eq, ord, hash] + + let is_mutexattr_type (t:typ): bool = match t with + | TNamed (info, attr) -> info.tname = "pthread_mutexattr_t" + | _ -> 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 @@ -106,94 +125,111 @@ struct | TNamed ({tname = "pthread_t"; _}, _) -> true | _ -> false + let is_jmp_buf_type = function + | TNamed ({tname = "jmp_buf"; _}, _) -> true + | _ -> false + let array_length_idx default length = let l = BatOption.bind length (fun e -> Cil.getInteger (Cil.constFold true e)) in - BatOption.map_default (fun x-> IndexDomain.of_int (Cilfacade.ptrdiff_ikind ()) @@ Cilint.big_int_of_cilint x) default l + BatOption.map_default (IndexDomain.of_int (Cilfacade.ptrdiff_ikind ())) default l let rec bot_value ?(varAttr=[]) (t: typ): t = match t with - | _ when is_mutex_type t -> `Mutex - | TInt _ -> `Bot (*`Int (ID.bot ()) -- should be lower than any int or address*) - | TFloat _ -> `Bot - | TPtr _ -> `Address (AD.bot ()) - | TComp ({cstruct=true; _} as ci,_) -> `Struct (Structs.create (fun fd -> bot_value ~varAttr:fd.fattr fd.ftype) ci) - | TComp ({cstruct=false; _},_) -> `Union (Unions.bot ()) + | _ when is_mutex_type t -> Mutex + | t when is_jmp_buf_type t -> JmpBuf (JmpBufs.bot ()) + | TInt _ -> Bot (*Int (ID.bot ()) -- should be lower than any int or address*) + | TFloat _ -> Bot + | TPtr _ -> Address (AD.bot ()) + | TComp ({cstruct=true; _} as ci,_) -> Struct (Structs.create (fun fd -> bot_value ~varAttr:fd.fattr fd.ftype) ci) + | TComp ({cstruct=false; _},_) -> Union (Unions.bot ()) | TArray (ai, length, _) -> let typAttr = typeAttrs ai in let len = array_length_idx (IndexDomain.bot ()) length in - `Array (CArrays.make ~varAttr ~typAttr len (bot_value ai)) - | t when is_thread_type t -> `Thread (ConcDomain.ThreadSet.empty ()) + Array (CArrays.make ~varAttr ~typAttr len (bot_value ai)) + | t when is_thread_type t -> Thread (ConcDomain.ThreadSet.empty ()) + | t when is_mutexattr_type t -> MutexAttr (MutexAttrDomain.bot ()) + | t when is_jmp_buf_type t -> JmpBuf (JmpBufs.Bufs.empty (), false) | TNamed ({ttype=t; _}, _) -> bot_value ~varAttr (unrollType t) - | _ -> `Bot + | _ -> Bot let is_bot_value x = match x with - | `Int x -> ID.is_bot x - | `Float x -> FD.is_bot x - | `Address x -> AD.is_bot x - | `Struct x -> Structs.is_bot x - | `Union x -> Unions.is_bot x - | `Array x -> CArrays.is_bot x - | `Blob x -> Blobs.is_bot x - | `Thread x -> Threads.is_bot x - | `Mutex -> true - | `Bot -> true - | `Top -> false + | Int x -> ID.is_bot x + | Float x -> FD.is_bot x + | Address x -> AD.is_bot x + | Struct x -> Structs.is_bot x + | Union x -> Unions.is_bot x + | Array x -> CArrays.is_bot x + | Blob x -> Blobs.is_bot x + | Thread x -> Threads.is_bot x + | JmpBuf x -> JmpBufs.is_bot x + | Mutex -> true + | MutexAttr x -> MutexAttr.is_bot x + | Bot -> true + | Top -> false let rec init_value ?(varAttr=[]) (t: typ): t = (* top_value is not used here because structs, blob etc will not contain the right members *) match t with - | t when is_mutex_type t -> `Mutex - | TInt (ik,_) -> `Int (ID.top_of ik) - | TFloat ((FFloat | FDouble | FLongDouble as fkind), _) -> `Float (FD.top_of fkind) - | TPtr _ -> `Address AD.top_ptr - | TComp ({cstruct=true; _} as ci,_) -> `Struct (Structs.create (fun fd -> init_value ~varAttr:fd.fattr fd.ftype) ci) - | TComp ({cstruct=false; _},_) -> `Union (Unions.top ()) + | t when is_mutex_type t -> Mutex + | t when is_jmp_buf_type t -> JmpBuf (JmpBufs.top ()) + | t when is_mutexattr_type t -> MutexAttr (MutexAttrDomain.top ()) + | TInt (ik,_) -> Int (ID.top_of ik) + | TFloat (fkind, _) when not (Cilfacade.isComplexFKind fkind) -> Float (FD.top_of fkind) + | TPtr _ -> Address AD.top_ptr + | TComp ({cstruct=true; _} as ci,_) -> Struct (Structs.create (fun fd -> init_value ~varAttr:fd.fattr fd.ftype) ci) + | TComp ({cstruct=false; _},_) -> Union (Unions.top ()) | TArray (ai, length, _) -> let typAttr = typeAttrs ai in let can_recover_from_top = ArrayDomain.can_recover_from_top (ArrayDomain.get_domain ~varAttr ~typAttr) in let len = array_length_idx (IndexDomain.bot ()) length in - `Array (CArrays.make ~varAttr ~typAttr len (if can_recover_from_top then (init_value ai) else (bot_value ai))) - (* | t when is_thread_type t -> `Thread (ConcDomain.ThreadSet.empty ()) *) + Array (CArrays.make ~varAttr ~typAttr len (if can_recover_from_top then (init_value ai) else (bot_value ai))) + (* | t when is_thread_type t -> Thread (ConcDomain.ThreadSet.empty ()) *) | TNamed ({ttype=t; _}, _) -> init_value ~varAttr t - | _ -> `Top + | _ -> Top let rec top_value ?(varAttr=[]) (t: typ): t = match t with - | _ when is_mutex_type t -> `Mutex - | TInt (ik,_) -> `Int (ID.(cast_to ik (top_of ik))) - | TFloat ((FFloat | FDouble | FLongDouble as fkind), _) -> `Float (FD.top_of fkind) - | TPtr _ -> `Address AD.top_ptr - | TComp ({cstruct=true; _} as ci,_) -> `Struct (Structs.create (fun fd -> top_value ~varAttr:fd.fattr fd.ftype) ci) - | TComp ({cstruct=false; _},_) -> `Union (Unions.top ()) + | _ when is_mutex_type t -> Mutex + | t when is_jmp_buf_type t -> JmpBuf (JmpBufs.top ()) + | t when is_mutexattr_type t -> MutexAttr (MutexAttrDomain.top ()) + | TInt (ik,_) -> Int (ID.(cast_to ik (top_of ik))) + | TFloat (fkind, _) when not (Cilfacade.isComplexFKind fkind) -> Float (FD.top_of fkind) + | TPtr _ -> Address AD.top_ptr + | TComp ({cstruct=true; _} as ci,_) -> Struct (Structs.create (fun fd -> top_value ~varAttr:fd.fattr fd.ftype) ci) + | TComp ({cstruct=false; _},_) -> Union (Unions.top ()) | TArray (ai, length, _) -> let typAttr = typeAttrs ai in let can_recover_from_top = ArrayDomain.can_recover_from_top (ArrayDomain.get_domain ~varAttr ~typAttr) in let len = array_length_idx (IndexDomain.top ()) length in - `Array (CArrays.make ~varAttr ~typAttr len (if can_recover_from_top then (top_value ai) else (bot_value ai))) + Array (CArrays.make ~varAttr ~typAttr len (if can_recover_from_top then (top_value ai) else (bot_value ai))) | TNamed ({ttype=t; _}, _) -> top_value ~varAttr t - | _ -> `Top + | _ -> Top let is_top_value x (t: typ) = match x with - | `Int x -> ID.is_top_of (Cilfacade.get_ikind (t)) x - | `Float x -> FD.is_top x - | `Address x -> AD.is_top x - | `Struct x -> Structs.is_top x - | `Union x -> Unions.is_top x - | `Array x -> CArrays.is_top x - | `Blob x -> Blobs.is_top x - | `Thread x -> Threads.is_top x - | `Mutex -> true - | `Top -> true - | `Bot -> false + | Int x -> ID.is_top_of (Cilfacade.get_ikind (t)) x + | Float x -> FD.is_top x + | Address x -> AD.is_top x + | Struct x -> Structs.is_top x + | Union x -> Unions.is_top x + | Array x -> CArrays.is_top x + | Blob x -> Blobs.is_top x + | Thread x -> Threads.is_top x + | MutexAttr x -> MutexAttr.is_top x + | JmpBuf x -> JmpBufs.is_top x + | Mutex -> true + | Top -> true + | Bot -> false let rec zero_init_value ?(varAttr=[]) (t:typ): t = match t with - | _ when is_mutex_type t -> `Mutex - | TInt (ikind, _) -> `Int (ID.of_int ikind BI.zero) - | TFloat ((FFloat | FDouble | FLongDouble as fkind), _) -> `Float (FD.of_const fkind 0.0) - | TPtr _ -> `Address AD.null_ptr - | TComp ({cstruct=true; _} as ci,_) -> `Struct (Structs.create (fun fd -> zero_init_value ~varAttr:fd.fattr fd.ftype) ci) + | _ when is_mutex_type t -> Mutex + | t when is_jmp_buf_type t -> JmpBuf (JmpBufs.top ()) + | t when is_mutexattr_type t -> MutexAttr (MutexAttrDomain.top ()) + | TInt (ikind, _) -> Int (ID.of_int ikind BI.zero) + | TFloat (fkind, _) when not (Cilfacade.isComplexFKind fkind) -> Float (FD.of_const fkind 0.0) + | TPtr _ -> Address AD.null_ptr + | TComp ({cstruct=true; _} as ci,_) -> Struct (Structs.create (fun fd -> zero_init_value ~varAttr:fd.fattr fd.ftype) ci) | TComp ({cstruct=false; _} as ci,_) -> let v = try (* C99 6.7.8.10: the first named member is initialized (recursively) according to these rules *) @@ -203,69 +239,74 @@ struct (* Union with no members ò.O *) Failure _ -> Unions.top () in - `Union(v) + Union(v) | TArray (ai, length, _) -> let typAttr = typeAttrs ai in let len = array_length_idx (IndexDomain.top ()) length in - `Array (CArrays.make ~varAttr ~typAttr len (zero_init_value ai)) - (* | t when is_thread_type t -> `Thread (ConcDomain.ThreadSet.empty ()) *) + Array (CArrays.make ~varAttr ~typAttr len (zero_init_value ai)) + (* | t when is_thread_type t -> Thread (ConcDomain.ThreadSet.empty ()) *) | TNamed ({ttype=t; _}, _) -> zero_init_value ~varAttr t - | _ -> `Top + | _ -> Top let tag_name : t -> string = function - | `Top -> "Top" | `Int _ -> "Int" | `Float _ -> "Float" | `Address _ -> "Address" | `Struct _ -> "Struct" | `Union _ -> "Union" | `Array _ -> "Array" | `Blob _ -> "Blob" | `Thread _ -> "Thread" | `Mutex -> "Mutex" | `Bot -> "Bot" + | Top -> "Top" | Int _ -> "Int" | Float _ -> "Float" | Address _ -> "Address" | Struct _ -> "Struct" | Union _ -> "Union" | Array _ -> "Array" | Blob _ -> "Blob" | Thread _ -> "Thread" | Mutex -> "Mutex" | MutexAttr _ -> "MutexAttr" | JmpBuf _ -> "JmpBuf" | Bot -> "Bot" include Printable.Std let name () = "compound" - type offs = (fieldinfo,IndexDomain.t) Lval.offs + type offs = IndexDomain.t Offset.t - let bot () = `Bot - let is_bot x = x = `Bot + let bot () = Bot + let is_bot x = x = Bot let bot_name = "Uninitialized" - let top () = `Top - let is_top x = x = `Top + let top () = Top + let is_top x = x = Top let top_name = "Unknown" let pretty () state = match state with - | `Int n -> ID.pretty () n - | `Float n -> FD.pretty () n - | `Address n -> AD.pretty () n - | `Struct n -> Structs.pretty () n - | `Union n -> Unions.pretty () n - | `Array n -> CArrays.pretty () n - | `Blob n -> Blobs.pretty () n - | `Thread n -> Threads.pretty () n - | `Mutex -> text "mutex" - | `Bot -> text bot_name - | `Top -> text top_name + | Int n -> ID.pretty () n + | Float n -> FD.pretty () n + | Address n -> AD.pretty () n + | Struct n -> Structs.pretty () n + | Union n -> Unions.pretty () n + | Array n -> CArrays.pretty () n + | Blob n -> Blobs.pretty () n + | Thread n -> Threads.pretty () n + | MutexAttr n -> MutexAttr.pretty () n + | JmpBuf n -> JmpBufs.pretty () n + | Mutex -> text "mutex" + | Bot -> text bot_name + | Top -> text top_name let show state = match state with - | `Int n -> ID.show n - | `Float n -> FD.show n - | `Address n -> AD.show n - | `Struct n -> Structs.show n - | `Union n -> Unions.show n - | `Array n -> CArrays.show n - | `Blob n -> Blobs.show n - | `Thread n -> Threads.show n - | `Mutex -> "mutex" - | `Bot -> bot_name - | `Top -> top_name + | Int n -> ID.show n + | Float n -> FD.show n + | Address n -> AD.show n + | Struct n -> Structs.show n + | Union n -> Unions.show n + | Array n -> CArrays.show n + | Blob n -> Blobs.show n + | Thread n -> Threads.show n + | JmpBuf n -> JmpBufs.show n + | Mutex -> "mutex" + | MutexAttr x -> MutexAttr.show x + | Bot -> bot_name + | Top -> top_name let pretty_diff () (x,y) = match (x,y) with - | (`Int x, `Int y) -> ID.pretty_diff () (x,y) - | (`Float x, `Float y) -> FD.pretty_diff () (x,y) - | (`Address x, `Address y) -> AD.pretty_diff () (x,y) - | (`Struct x, `Struct y) -> Structs.pretty_diff () (x,y) - | (`Union x, `Union y) -> Unions.pretty_diff () (x,y) - | (`Array x, `Array y) -> CArrays.pretty_diff () (x,y) - | (`Blob x, `Blob y) -> Blobs.pretty_diff () (x,y) - | (`Thread x, `Thread y) -> Threads.pretty_diff () (x, y) + | (Int x, Int y) -> ID.pretty_diff () (x,y) + | (Float x, Float y) -> FD.pretty_diff () (x,y) + | (Address x, Address y) -> AD.pretty_diff () (x,y) + | (Struct x, Struct y) -> Structs.pretty_diff () (x,y) + | (Union x, Union y) -> Unions.pretty_diff () (x,y) + | (Array x, Array y) -> CArrays.pretty_diff () (x,y) + | (Blob x, Blob y) -> Blobs.pretty_diff () (x,y) + | (Thread x, Thread y) -> Threads.pretty_diff () (x, y) + | (JmpBuf x, JmpBuf y) -> JmpBufs.pretty_diff () (x, y) | _ -> dprintf "%s: %a not same type as %a" (name ()) pretty x pretty y (************************************************************ @@ -280,10 +321,13 @@ struct | TFloat (FDouble,_), TFloat (FFloat,_) -> true | TFloat (FLongDouble,_), TFloat (FFloat,_) -> true | TFloat (FLongDouble,_), TFloat (FDouble,_) -> true + | TFloat (FFloat128, _), TFloat (FFloat,_) -> true + | TFloat (FFloat128, _), TFloat (FDouble,_) -> true + | TFloat (FFloat128, _), TFloat (FLongDouble,_) -> true | _, TFloat _ -> false (* casting float to an integral type always looses the decimals *) - | TFloat ((FFloat | FDouble | FLongDouble), _), TInt((IBool | IChar | IUChar | ISChar | IShort | IUShort), _) -> true (* resonably small integers can be stored in all fkinds *) - | TFloat ((FDouble | FLongDouble), _), TInt((IInt | IUInt | ILong | IULong), _) -> true (* values stored in between 16 and 32 bits can only be stored in at least doubles *) - | TFloat _, _ -> false (* all wider integers can not be completly put into a float, partially because our internal representation of long double is the same as for doubles *) + | TFloat (fk, _), TInt((IBool | IChar | IUChar | ISChar | IShort | IUShort), _) when not (Cilfacade.isComplexFKind fk) -> true (* reasonably small integers can be stored in all fkinds *) + | TFloat ((FDouble | FLongDouble | FFloat128), _), TInt((IInt | IUInt | ILong | IULong), _) -> true (* values stored in between 16 and 32 bits can only be stored in at least doubles *) + | TFloat _, _ -> false (* all wider integers can not be completely put into a float, partially because our internal representation of long double is the same as for doubles *) | (TInt _ | TEnum _ | TPtr _) , (TInt _ | TEnum _ | TPtr _) -> IntDomain.Size.is_cast_injective ~from_type:t1 ~to_type:t2 && bitsSizeOf t2 >= bitsSizeOf t1 | _ -> false @@ -306,8 +350,8 @@ struct | t -> t in let rec adjust_offs v o d = - let ta = try Addr.type_offset v.vtype o with Addr.Type_offset (t,s) -> raise (CastError s) in - let info = Pretty.(sprint ~width:0 @@ dprintf "Ptr-Cast %a from %a to %a" Addr.pretty (Addr.Addr (v,o)) d_type ta d_type t) in + let ta = try Addr.Offs.type_of ~base:v.vtype o with Offset.Type_of_error (t,s) -> raise (CastError s) in + let info = GobPretty.sprintf "Ptr-Cast %a from %a to %a" Addr.pretty (Addr.Addr (v,o)) d_type ta d_type t in M.tracel "casta" "%s\n" info; let err s = raise (CastError (s ^ " (" ^ info ^ ")")) in match Stdlib.compare (bitsSizeOf (stripVarLenArr t)) (bitsSizeOf (stripVarLenArr ta)) with (* TODO is it enough to compare the size? -> yes? *) @@ -319,7 +363,7 @@ struct M.tracel "casta" "cast to bigger size\n"; if d = Some false then err "Ptr-cast to type of incompatible size!" else if o = `NoOffset then err "Ptr-cast to outer type, but no offset to remove." - else if Addr.is_zero_offset o then adjust_offs v (Addr.remove_offset o) (Some true) + else if Addr.Offs.cmp_zero_offset o = `MustZero then adjust_offs v (Addr.Offs.remove_offset o) (Some true) else err "Ptr-cast to outer type, but possibly from non-zero offset." | _ -> (* cast to smaller/inner type *) M.tracel "casta" "cast to smaller size\n"; @@ -328,15 +372,14 @@ struct (* struct to its first field *) | TComp ({cfields = fi::_; _}, _), _ -> M.tracel "casta" "cast struct to its first field\n"; - adjust_offs v (Addr.add_offsets o (`Field (fi, `NoOffset))) (Some false) + adjust_offs v (Addr.Offs.add_offset o (`Field (fi, `NoOffset))) (Some false) (* array of the same type but different length, e.g. assign array (with length) to array-ptr (no length) *) | TArray (t1, _, _), TArray (t2, _, _) when typ_eq t1 t2 -> o (* array to its first element *) | TArray _, _ -> M.tracel "casta" "cast array to its first element\n"; - adjust_offs v (Addr.add_offsets o (`Index (IndexDomain.of_int (Cilfacade.ptrdiff_ikind ()) BI.zero, `NoOffset))) (Some false) - | _ -> err @@ "Cast to neither array index nor struct field." - ^ Pretty.(sprint ~width:0 @@ dprintf " is_zero_offset: %b" (Addr.is_zero_offset o)) + adjust_offs v (Addr.Offs.add_offset o (`Index (IndexDomain.of_int (Cilfacade.ptrdiff_ikind ()) BI.zero, `NoOffset))) (Some false) + | _ -> err @@ Format.sprintf "Cast to neither array index nor struct field. is_zero_offset: %b" (Addr.Offs.cmp_zero_offset o = `MustZero) end in let one_addr = let open Addr in function @@ -367,58 +410,60 @@ struct * 2. dereferencing pointers (needed?) *) let cast ?torg t v = - (*if v = `Bot || (match torg with Some x -> is_safe_cast t x | None -> false) then v else*) + (*if v = Bot || (match torg with Some x -> is_safe_cast t x | None -> false) then v else*) match v with - | `Bot - | `Thread _ - | `Mutex -> + | Bot + | Thread _ + | Mutex + | MutexAttr _ + | JmpBuf _ -> v | _ -> let log_top (_,l,_,_) = Messages.tracel "cast" "log_top at %d: %a to %a is top!\n" l pretty v d_type t in let t = unrollType t in let v' = match t with | TInt (ik,_) -> - `Int (ID.cast_to ?torg ik (match v with - | `Int x -> x - | `Address x -> AD.to_int (module ID) x - | `Float x -> FD.to_int ik x - (*| `Struct x when Structs.cardinal x > 0 -> + Int (ID.cast_to ?torg ik (match v with + | Int x -> x + | Address x -> AD.to_int x + | Float x -> FD.to_int ik x + (*| Struct x when Structs.cardinal x > 0 -> let some = List.hd (Structs.keys x) in let first = List.hd some.fcomp.cfields in - (match Structs.get x first with `Int x -> x | _ -> raise CastError)*) + (match Structs.get x first with Int x -> x | _ -> raise CastError)*) | _ -> log_top __POS__; ID.top_of ik )) - | TFloat ((FFloat | FDouble | FLongDouble as fkind),_) -> + | TFloat (fkind,_) when not (Cilfacade.isComplexFKind fkind) -> (match v with - |`Int ix -> `Float (FD.of_int fkind ix) - |`Float fx -> `Float (FD.cast_to fkind fx) - | _ -> log_top __POS__; `Top) - | TFloat _ -> log_top __POS__; `Top (*ignore complex numbers by going to top*) + |Int ix -> Float (FD.of_int fkind ix) + |Float fx -> Float (FD.cast_to fkind fx) + | _ -> log_top __POS__; Top) + | TFloat _ -> log_top __POS__; Top (*ignore complex numbers by going to top*) | TEnum ({ekind=ik; _},_) -> - `Int (ID.cast_to ?torg ik (match v with - | `Int x -> (* TODO warn if x is not in the constant values of ei.eitems? (which is totally valid (only ik is relevant for wrapping), but might be unintended) *) x + Int (ID.cast_to ?torg ik (match v with + | Int x -> (* TODO warn if x is not in the constant values of ei.eitems? (which is totally valid (only ik is relevant for wrapping), but might be unintended) *) x | _ -> log_top __POS__; ID.top_of ik )) | TPtr (t,_) when isVoidType t || isVoidPtrType t -> (match v with - | `Address a -> v - | `Int i -> `Int(ID.cast_to ?torg (Cilfacade.ptr_ikind ()) i) - | _ -> v (* TODO: Does it make sense to have things here that are neither `Address nor `Int? *) + | Address a -> v + | Int i -> Int(ID.cast_to ?torg (Cilfacade.ptr_ikind ()) i) + | _ -> v (* TODO: Does it make sense to have things here that are neither Address nor Int? *) ) (* cast to voidPtr are ignored TODO what happens if our value does not fit? *) | TPtr (t,_) -> - `Address (match v with - | `Int x when ID.to_int x = Some BI.zero -> AD.null_ptr - | `Int x -> AD.top_ptr + Address (match v with + | Int x when ID.to_int x = Some BI.zero -> AD.null_ptr + | Int x -> AD.top_ptr (* we ignore casts to void*! TODO report UB! *) - | `Address x -> (match t with TVoid _ -> x | _ -> cast_addr t x) - (*| `Address x -> x*) + | Address x -> (match t with TVoid _ -> x | _ -> cast_addr t x) + (*| Address x -> x*) | _ -> log_top __POS__; AD.top_ptr ) | TArray (ta, l, _) -> (* TODO, why is the length exp option? *) (* TODO handle casts between different sizes? *) - `Array (match v with - | `Array x -> x + Array (match v with + | Array x -> x | _ -> log_top __POS__; CArrays.top () ) | TComp (ci,_) -> (* struct/union *) @@ -432,208 +477,216 @@ struct * 2. dereferencing a casted pointer works, but is undefined behavior because of the strict aliasing rule (compiler assumes that pointers of different type can never point to the same location) *) if ci.cstruct then - `Struct (match v with - | `Struct x when same_struct x -> x - | `Struct x when ci.cfields <> [] -> + Struct (match v with + | Struct x when same_struct x -> x + | Struct x when ci.cfields <> [] -> let first = List.hd ci.cfields in Structs.(replace (Structs.create (fun fd -> top_value ~varAttr:fd.fattr fd.ftype) ci) first (get x first)) | _ -> log_top __POS__; Structs.create (fun fd -> top_value ~varAttr:fd.fattr fd.ftype) ci ) else - `Union (match v with - | `Union x (* when same (Unions.keys x) *) -> x + Union (match v with + | Union x (* when same (Unions.keys x) *) -> x | _ -> log_top __POS__; Unions.top () ) - (* | _ -> log_top (); `Top *) - | TVoid _ -> log_top __POS__; `Top + (* | _ -> log_top (); Top *) + | TVoid _ -> log_top __POS__; Top | TBuiltin_va_list _ -> (* cast to __builtin_va_list only happens in preprocessed SV-COMP files where vararg declarations are more explicit *) - log_top __POS__; `Top + log_top __POS__; Top | _ -> log_top __POS__; assert false in - let s_torg = match torg with Some t -> Prelude.Ana.sprint d_type t | None -> "?" in + let s_torg = match torg with Some t -> CilType.Typ.show t | None -> "?" in Messages.tracel "cast" "cast %a from %s to %a is %a!\n" pretty v s_torg d_type t pretty v'; v' 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) (tag_name y) 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 !Tracing.current_loc pretty x pretty y let rec leq x y = match (x,y) with - | (_, `Top) -> true - | (`Top, _) -> false - | (`Bot, _) -> true - | (_, `Bot) -> false - | (`Int x, `Int y) -> ID.leq x y - | (`Float x, `Float y) -> FD.leq x y - | (`Int x, `Address y) when ID.to_int x = Some BI.zero && not (AD.is_not_null y) -> true - | (`Int _, `Address y) when AD.may_be_unknown y -> true - | (`Address _, `Int y) when ID.is_top_of (Cilfacade.ptrdiff_ikind ()) y -> true - | (`Address x, `Address y) -> AD.leq x y - | (`Struct x, `Struct y) -> Structs.leq x y - | (`Union x, `Union y) -> Unions.leq x y - | (`Array x, `Array y) -> CArrays.leq x y - | (`Blob x, `Blob y) -> Blobs.leq x y - | `Blob (x,s,o), y -> leq (x:t) y - | x, `Blob (y,s,o) -> leq x (y:t) - | (`Thread x, `Thread y) -> Threads.leq x y - | (`Int x, `Thread y) -> true - | (`Address x, `Thread y) -> true - | (`Mutex, `Mutex) -> true + | (_, Top) -> true + | (Top, _) -> false + | (Bot, _) -> true + | (_, Bot) -> false + | (Int x, Int y) -> ID.leq x y + | (Float x, Float y) -> FD.leq x y + | (Int x, Address y) when ID.to_int x = Some BI.zero && not (AD.is_not_null y) -> true + | (Int _, Address y) when AD.may_be_unknown y -> true + | (Address _, Int y) when ID.is_top_of (Cilfacade.ptrdiff_ikind ()) y -> true + | (Address x, Address y) -> AD.leq x y + | (Struct x, Struct y) -> Structs.leq x y + | (Union x, Union y) -> Unions.leq x y + | (Array x, Array y) -> CArrays.leq x y + | (Blob x, Blob y) -> Blobs.leq x y + | Blob (x,s,o), y -> leq (x:t) y + | x, Blob (y,s,o) -> leq x (y:t) + | (Thread x, Thread y) -> Threads.leq x y + | (Int x, Thread y) -> true + | (Address x, Thread y) -> true + | (JmpBuf x, JmpBuf y) -> JmpBufs.leq x y + | (Mutex, Mutex) -> true + | (MutexAttr x, MutexAttr y) -> MutexAttr.leq x y | _ -> warn_type "leq" x y; false let rec join x y = match (x,y) with - | (`Top, _) -> `Top - | (_, `Top) -> `Top - | (`Bot, x) -> x - | (x, `Bot) -> x - | (`Int x, `Int y) -> (try `Int (ID.join x y) with IntDomain.IncompatibleIKinds m -> Messages.warn ~category:Analyzer ~tags:[Category Imprecise] "%s" m; `Top) - | (`Float x, `Float y) -> `Float (FD.join x y) - | (`Int x, `Address y) - | (`Address y, `Int x) -> `Address (match ID.to_int x with + | (Top, _) -> Top + | (_, Top) -> Top + | (Bot, x) -> x + | (x, Bot) -> x + | (Int x, Int y) -> (try Int (ID.join x y) with IntDomain.IncompatibleIKinds m -> Messages.warn ~category:Analyzer ~tags:[Category Imprecise] "%s" m; Top) + | (Float x, Float y) -> Float (FD.join x y) + | (Int x, Address y) + | (Address y, Int x) -> Address (match ID.to_int x with | Some x when BI.equal x BI.zero -> AD.join AD.null_ptr y | Some x -> AD.(join y not_null) | None -> AD.join y AD.top_ptr) - | (`Address x, `Address y) -> `Address (AD.join x y) - | (`Struct x, `Struct y) -> `Struct (Structs.join x y) - | (`Union (f,x), `Union (g,y)) -> `Union (match UnionDomain.Field.join f g with - | `Lifted f -> (`Lifted f, join x y) (* f = g *) - | x -> (x, `Top)) (* f <> g *) - | (`Array x, `Array y) -> `Array (CArrays.join x y) - | (`Blob x, `Blob y) -> `Blob (Blobs.join x y) - | `Blob (x,s,o), y - | y, `Blob (x,s,o) -> `Blob (join (x:t) y, s, o) - | (`Thread x, `Thread y) -> `Thread (Threads.join x y) - | (`Int x, `Thread y) - | (`Thread y, `Int x) -> - `Thread y (* TODO: ignores int! *) - | (`Address x, `Thread y) - | (`Thread y, `Address x) -> - `Thread y (* TODO: ignores address! *) - | (`Mutex, `Mutex) -> `Mutex + | (Address x, Address y) -> Address (AD.join x y) + | (Struct x, Struct y) -> Struct (Structs.join x y) + | (Union x, Union y) -> Union (Unions.join x y) + | (Array x, Array y) -> Array (CArrays.join x y) + | (Blob x, Blob y) -> Blob (Blobs.join x y) + | Blob (x,s,o), y + | y, Blob (x,s,o) -> Blob (join (x:t) y, s, o) + | (Thread x, Thread y) -> Thread (Threads.join x y) + | (Int x, Thread y) + | (Thread y, Int x) -> + Thread y (* TODO: ignores int! *) + | (Address x, Thread y) + | (Thread y, Address x) -> + Thread y (* TODO: ignores address! *) + | (JmpBuf x, JmpBuf y) -> JmpBuf (JmpBufs.join x y) + | (Mutex, Mutex) -> Mutex + | (MutexAttr x, MutexAttr y) -> MutexAttr (MutexAttr.join x y) | _ -> warn_type "join" x y; - `Top + Top - let rec widen x y = + let widen x y = match (x,y) with - | (`Top, _) -> `Top - | (_, `Top) -> `Top - | (`Bot, x) -> x - | (x, `Bot) -> x - | (`Int x, `Int y) -> (try `Int (ID.widen x y) with IntDomain.IncompatibleIKinds m -> Messages.warn ~category:Analyzer "%s" m; `Top) - | (`Float x, `Float y) -> `Float (FD.widen x y) + | (Top, _) -> Top + | (_, Top) -> Top + | (Bot, x) -> x + | (x, Bot) -> x + | (Int x, Int y) -> (try Int (ID.widen x y) with IntDomain.IncompatibleIKinds m -> Messages.warn ~category:Analyzer "%s" m; Top) + | (Float x, Float y) -> Float (FD.widen x y) (* TODO: symmetric widen, wtf? *) - | (`Int x, `Address y) - | (`Address y, `Int x) -> `Address (match ID.to_int x with + | (Int x, Address y) + | (Address y, Int x) -> Address (match ID.to_int x with | Some x when BI.equal x BI.zero -> AD.widen AD.null_ptr (AD.join AD.null_ptr y) | Some x -> AD.(widen y (join y not_null)) | None -> AD.widen y (AD.join y AD.top_ptr)) - | (`Address x, `Address y) -> `Address (AD.widen x y) - | (`Struct x, `Struct y) -> `Struct (Structs.widen x y) - | (`Union (f,x), `Union (g,y)) -> `Union (match UnionDomain.Field.widen f g with - | `Lifted f -> (`Lifted f, widen x y) (* f = g *) - | x -> (x, `Top)) - | (`Array x, `Array y) -> `Array (CArrays.widen x y) - | (`Blob x, `Blob y) -> `Blob (Blobs.widen x y) - | (`Thread x, `Thread y) -> `Thread (Threads.widen x y) - | (`Int x, `Thread y) - | (`Thread y, `Int x) -> - `Thread y (* TODO: ignores int! *) - | (`Address x, `Thread y) - | (`Thread y, `Address x) -> - `Thread y (* TODO: ignores address! *) - | (`Mutex, `Mutex) -> `Mutex + | (Address x, Address y) -> Address (AD.widen x y) + | (Struct x, Struct y) -> Struct (Structs.widen x y) + | (Union x, Union y) -> Union (Unions.widen x y) + | (Array x, Array y) -> Array (CArrays.widen x y) + | (Blob x, Blob y) -> Blob (Blobs.widen x y) (* TODO: why no blob special cases like in join? *) + | (Thread x, Thread y) -> Thread (Threads.widen x y) + | (Int x, Thread y) + | (Thread y, Int x) -> + Thread y (* TODO: ignores int! *) + | (Address x, Thread y) + | (Thread y, Address x) -> + Thread y (* TODO: ignores address! *) + | (Mutex, Mutex) -> Mutex + | (JmpBuf x, JmpBuf y) -> JmpBuf (JmpBufs.widen x y) + | (MutexAttr x, MutexAttr y) -> MutexAttr (MutexAttr.widen x y) | _ -> warn_type "widen" x y; - `Top + Top let rec smart_join x_eval_int y_eval_int (x:t) (y:t):t = let join_elem: (t -> t -> t) = smart_join x_eval_int y_eval_int in (* does not compile without type annotation *) match (x,y) with - | (`Struct x, `Struct y) -> `Struct (Structs.join_with_fct join_elem x y) - | (`Union (f,x), `Union (g,y)) -> `Union (match UnionDomain.Field.join f g with - | `Lifted f -> (`Lifted f, join_elem x y) (* f = g *) - | x -> (x, `Top)) (* f <> g *) - | (`Array x, `Array y) -> `Array (CArrays.smart_join x_eval_int y_eval_int x y) + | (Struct x, Struct y) -> Struct (Structs.join_with_fct join_elem x y) + | (Union (f,x), Union (g,y)) -> + let field = UnionDomain.Field.join f g in + let value = join_elem x y in + Union (field, value) + | (Array x, Array y) -> Array (CArrays.smart_join x_eval_int y_eval_int x y) | _ -> join x y (* Others can not contain array -> normal join *) let rec smart_widen x_eval_int y_eval_int x y:t = let widen_elem: (t -> t -> t) = smart_widen x_eval_int y_eval_int in (* does not compile without type annotation *) match (x,y) with - | (`Struct x, `Struct y) -> `Struct (Structs.widen_with_fct widen_elem x y) - | (`Union (f,x), `Union (g,y)) -> `Union (match UnionDomain.Field.widen f g with - | `Lifted f -> `Lifted f, widen_elem x y (* f = g *) - | x -> x, `Top) (* f <> g *) - | (`Array x, `Array y) -> `Array (CArrays.smart_widen x_eval_int y_eval_int x y) + | (Struct x, Struct y) -> Struct (Structs.widen_with_fct widen_elem x y) + | (Union (f,x), Union (g,y)) -> + let field = UnionDomain.Field.widen f g in + let value = widen_elem x y in + Union (field, value) + | (Array x, Array y) -> Array (CArrays.smart_widen x_eval_int y_eval_int x y) | _ -> widen x y (* Others can not contain array -> normal widen *) let rec smart_leq x_eval_int y_eval_int x y = let leq_elem:(t ->t -> bool) = smart_leq x_eval_int y_eval_int in (* does not compile without type annotation *) match (x,y) with - | (`Struct x, `Struct y) -> + | (Struct x, Struct y) -> Structs.leq_with_fct leq_elem x y - | (`Union (f, x), `Union (g, y)) -> + | (Union (f, x), Union (g, y)) -> UnionDomain.Field.leq f g && leq_elem x y - | (`Array x, `Array y) -> CArrays.smart_leq x_eval_int y_eval_int x y + | (Array x, Array y) -> CArrays.smart_leq x_eval_int y_eval_int x y | _ -> leq x y (* Others can not contain array -> normal leq *) let rec meet x y = match (x,y) with - | (`Bot, _) -> `Bot - | (_, `Bot) -> `Bot - | (`Top, x) -> x - | (x, `Top) -> x - | (`Int x, `Int y) -> `Int (ID.meet x y) - | (`Float x, `Float y) -> `Float (FD.meet x y) - | (`Int _, `Address _) -> meet x (cast (TInt(Cilfacade.ptr_ikind (),[])) y) - | (`Address x, `Int y) -> `Address (AD.meet x (AD.of_int (module ID:IntDomain.Z with type t = ID.t) y)) - | (`Address x, `Address y) -> `Address (AD.meet x y) - | (`Struct x, `Struct y) -> `Struct (Structs.meet x y) - | (`Union x, `Union y) -> `Union (Unions.meet x y) - | (`Array x, `Array y) -> `Array (CArrays.meet x y) - | (`Blob x, `Blob y) -> `Blob (Blobs.meet x y) - | (`Thread x, `Thread y) -> `Thread (Threads.meet x y) - | (`Int x, `Thread y) - | (`Thread y, `Int x) -> - `Int x (* TODO: ignores thread! *) - | (`Address x, `Thread y) - | (`Thread y, `Address x) -> - `Address x (* TODO: ignores thread! *) - | (`Mutex, `Mutex) -> `Mutex + | (Bot, _) -> Bot + | (_, Bot) -> Bot + | (Top, x) -> x + | (x, Top) -> x + | (Int x, Int y) -> Int (ID.meet x y) + | (Float x, Float y) -> Float (FD.meet x y) + | (Int _, Address _) -> meet x (cast (TInt(Cilfacade.ptr_ikind (),[])) y) + | (Address x, Int y) -> Address (AD.meet x (AD.of_int y)) + | (Address x, Address y) -> Address (AD.meet x y) + | (Struct x, Struct y) -> Struct (Structs.meet x y) + | (Union x, Union y) -> Union (Unions.meet x y) + | (Array x, Array y) -> Array (CArrays.meet x y) + | (Blob x, Blob y) -> Blob (Blobs.meet x y) + | (Thread x, Thread y) -> Thread (Threads.meet x y) + | (Int x, Thread y) + | (Thread y, Int x) -> + Int x (* TODO: ignores thread! *) + | (Address x, Thread y) + | (Thread y, Address x) -> + Address x (* TODO: ignores thread! *) + | (Mutex, Mutex) -> Mutex + | (JmpBuf x, JmpBuf y) -> JmpBuf (JmpBufs.meet x y) + | (MutexAttr x, MutexAttr y) -> MutexAttr (MutexAttr.meet x y) | _ -> warn_type "meet" x y; - `Bot + Bot let rec narrow x y = match (x,y) with - | (`Int x, `Int y) -> `Int (ID.narrow x y) - | (`Float x, `Float y) -> `Float (FD.narrow x y) - | (`Int _, `Address _) -> narrow x (cast IntDomain.Size.top_typ y) - | (`Address x, `Int y) -> `Address (AD.narrow x (AD.of_int (module ID:IntDomain.Z with type t = ID.t) y)) - | (`Address x, `Address y) -> `Address (AD.narrow x y) - | (`Struct x, `Struct y) -> `Struct (Structs.narrow x y) - | (`Union x, `Union y) -> `Union (Unions.narrow x y) - | (`Array x, `Array y) -> `Array (CArrays.narrow x y) - | (`Blob x, `Blob y) -> `Blob (Blobs.narrow x y) - | (`Thread x, `Thread y) -> `Thread (Threads.narrow x y) - | (`Int x, `Thread y) - | (`Thread y, `Int x) -> - `Int x (* TODO: ignores thread! *) - | (`Address x, `Thread y) - | (`Thread y, `Address x) -> - `Address x (* TODO: ignores thread! *) - | (`Mutex, `Mutex) -> `Mutex - | x, `Top | `Top, x -> x - | x, `Bot | `Bot, x -> `Bot + | (Int x, Int y) -> Int (ID.narrow x y) + | (Float x, Float y) -> Float (FD.narrow x y) + | (Int _, Address _) -> narrow x (cast IntDomain.Size.top_typ y) + | (Address x, Int y) -> Address (AD.narrow x (AD.of_int y)) + | (Address x, Address y) -> Address (AD.narrow x y) + | (Struct x, Struct y) -> Struct (Structs.narrow x y) + | (Union x, Union y) -> Union (Unions.narrow x y) + | (Array x, Array y) -> Array (CArrays.narrow x y) + | (Blob x, Blob y) -> Blob (Blobs.narrow x y) + | (Thread x, Thread y) -> Thread (Threads.narrow x y) + | (JmpBuf x, JmpBuf y) -> JmpBuf (JmpBufs.narrow x y) + | (Int x, Thread y) + | (Thread y, Int x) -> + Int x (* TODO: ignores thread! *) + | (Address x, Thread y) + | (Thread y, Address x) -> + Address x (* TODO: ignores thread! *) + | (Mutex, Mutex) -> Mutex + | (MutexAttr x, MutexAttr y) -> MutexAttr (MutexAttr.narrow x y) + | x, Top | Top, x -> x + | x, Bot | Bot, x -> Bot | _ -> warn_type "narrow" x y; x - let rec invalidate_value (ask:Q.ask) typ (state:t) : t = + let rec invalidate_value (ask:VDQ.t) typ (state:t) : t = let typ = unrollType typ in let invalid_struct compinfo old = let nstruct = Structs.create (fun fd -> invalidate_value ask fd.ftype (Structs.get old fd)) compinfo in @@ -644,19 +697,20 @@ struct in let array_idx_top = (None, ArrIdxDomain.top ()) in match typ, state with - | _ , `Address n -> `Address (AD.join AD.top_ptr n) - | TComp (ci,_) , `Struct n -> `Struct (invalid_struct ci n) - | _ , `Struct n -> `Struct (Structs.map (fun x -> invalidate_value ask voidType x) n) - | TComp (ci,_) , `Union (`Lifted fd,n) -> `Union (`Lifted fd, invalidate_value ask fd.ftype n) - | TArray (t,_,_), `Array n -> + | _ , Address n -> Address (AD.join AD.top_ptr n) + | TComp (ci,_) , Struct n -> Struct (invalid_struct ci n) + | _ , Struct n -> Struct (Structs.map (fun x -> invalidate_value ask voidType x) n) + | TComp (ci,_) , Union (`Lifted fd,n) -> Union (`Lifted fd, invalidate_value ask fd.ftype n) + | TArray (t,_,_), Array n -> let v = invalidate_value ask t (CArrays.get ask n array_idx_top) in - `Array (CArrays.set ask n (array_idx_top) v) - | _ , `Array n -> + Array (CArrays.set ask n (array_idx_top) v) + | _ , Array n -> let v = invalidate_value ask voidType (CArrays.get ask n (array_idx_top)) in - `Array (CArrays.set ask n (array_idx_top) v) - | t , `Blob n -> `Blob (Blobs.invalidate_value ask t n) - | _ , `Thread _ -> state (* TODO: no top thread ID set! *) - | _, `Bot -> `Bot (* Leave uninitialized value (from malloc) alone in free to avoid trashing everything. TODO: sound? *) + Array (CArrays.set ask n (array_idx_top) v) + | t , Blob n -> Blob (Blobs.invalidate_value ask t n) + | _ , Thread _ -> state (* TODO: no top thread ID set! *) + | _ , JmpBuf _ -> state (* TODO: no top jmpbuf *) + | _, Bot -> Bot (* Leave uninitialized value (from malloc) alone in free to avoid trashing everything. TODO: sound? *) | t , _ -> top_value t @@ -676,7 +730,7 @@ struct end | _ -> None, None - let determine_offset (ask: Q.ask) left offset exp v = + let determine_offset (ask: VDQ.t) left offset exp v = let rec contains_pointer exp = (* CIL offsets containing pointers is no issue here, as pointers can only occur in `Index and the domain *) match exp with (* does not partition according to expressions having `Index in them *) | Const _ @@ -701,10 +755,10 @@ struct let equiv_expr exp start_of_array_lval = 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.f (Q.MayPointTo (Lval lval)) with - | v when Q.LS.cardinal v = 1 && not (Q.LS.is_top v) -> - begin match Q.LS.choose v with - | (var,`Index (i,`NoOffset)) when Cil.isZero (Cil.constFold true i) && CilType.Varinfo.equal var arr_start_var -> + begin match ask.may_point_to (Lval lval) with + | 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) @@ -761,77 +815,82 @@ struct if orig then (* This Blob came from malloc *) x - else if x = `Bot then + else if x = Bot then (* This Blob came from calloc *) zero_init_value t (* This should be zero initialized *) else x (* This already contains some value *) (* Funny, this does not compile without the final type annotation! *) - let rec eval_offset (ask: Q.ask) f (x: t) (offs:offs) (exp:exp option) (v:lval option) (t:typ): t = - let rec do_eval_offset (ask:Q.ask) f (x:t) (offs:offs) (exp:exp option) (l:lval option) (o:offset option) (v:lval option) (t:typ): t = + 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) -> + | Blob((va, _, orig) as c), `Index (_, ox) -> begin let l', o' = shift_one_over l o in let ev = do_eval_offset ask f (Blobs.value c) ox exp l' o' v t in zero_init_calloced_memory orig ev t end - | `Blob((va, _, orig) as c), `Field _ -> + | Blob((va, _, orig) as c), `Field _ -> begin let l', o' = shift_one_over l o in let ev = do_eval_offset ask f (Blobs.value c) offs exp l' o' v t in zero_init_calloced_memory orig ev t end - | `Blob((va, _, orig) as c), `NoOffset -> + | Blob((va, _, orig) as c), `NoOffset -> begin let l', o' = shift_one_over l o in let ev = do_eval_offset ask f (Blobs.value c) offs exp l' o' v t in zero_init_calloced_memory orig ev t end - | `Bot, _ -> `Bot + | Bot, _ -> Bot | _ -> match offs with | `NoOffset -> x | `Field (fld, offs) when fld.fcomp.cstruct -> begin match x with - | `Struct str -> + | Struct str -> let x = Structs.get str fld in let l', o' = shift_one_over l o in do_eval_offset ask f x offs exp l' o' v t - | `Top -> M.info ~category:Imprecise "Trying to read a field, but the struct is unknown"; top () + | Top -> M.info ~category:Imprecise "Trying to read a field, but the struct is unknown"; top () | _ -> M.warn ~category:Imprecise ~tags:[Category Program] "Trying to read a field, but was not given a struct"; top () end | `Field (fld, offs) -> begin match x with - | `Union (`Lifted l_fld, value) -> + | Union (`Lifted l_fld, value) -> (match value, fld.ftype with (* only return an actual value if we have a type and return actually the exact same type *) - | `Float f_value, TFloat(fkind, _) when FD.get_fkind f_value = fkind -> `Float f_value - | `Float _, t -> top_value t - | _, TFloat((FFloat | FDouble | FLongDouble as fkind), _) -> `Float (FD.top_of fkind) + | Float f_value, TFloat(fkind, _) when FD.get_fkind f_value = fkind -> Float f_value + | Float _, t -> top_value t + | _, TFloat(fkind, _) when not (Cilfacade.isComplexFKind fkind)-> Float (FD.top_of fkind) | _ -> let x = cast ~torg:l_fld.ftype fld.ftype value in let l', o' = shift_one_over l o in do_eval_offset ask f x offs exp l' o' v t) - | `Union _ -> top () - | `Top -> M.info ~category:Imprecise "Trying to read a field, but the union is unknown"; top () + | Union _ -> top () + | Top -> M.info ~category:Imprecise "Trying to read a field, but the union is unknown"; top () | _ -> M.warn ~category:Imprecise ~tags:[Category Program] "Trying to read a field, but was not given a union"; top () end | `Index (idx, offs) -> begin let l', o' = shift_one_over l o in match x with - | `Array x -> + | Array x -> let e = determine_offset ask l o exp v in do_eval_offset ask f (CArrays.get ask x (e, idx)) offs exp l' o' v t - | `Address _ -> + | Address _ -> begin do_eval_offset ask f x offs exp l' o' v t (* this used to be `blob `address -> we ignore the index *) end | x when GobOption.exists (BI.equal (BI.zero)) (IndexDomain.to_int idx) -> eval_offset ask f x offs exp v t - | `Top -> M.info ~category:Imprecise "Trying to read an index, but the array is unknown"; top () + | 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) @@ -839,30 +898,45 @@ struct in do_eval_offset ask f x offs exp l o v t - let update_offset (ask: Q.ask) (x:t) (offs:offs) (value:t) (exp:exp option) (v:lval) (t:typ): t = - let rec do_update_offset (ask:Q.ask) (x:t) (offs:offs) (value:t) (exp:exp option) (l:lval option) (o:offset option) (v:lval) (t:typ):t = - if M.tracing then M.traceli "update_offset" "do_update_offset %a %a %a\n" pretty x Offs.pretty offs pretty value; - let mu = function `Blob (`Blob (y, s', orig), s, orig2) -> `Blob (y, ID.join s s',orig) | x -> x in + let update_offset (ask: VDQ.t) (x:t) (offs:offs) (value:t) (exp:exp option) (v:lval) (t:typ): t = + let rec do_update_offset (ask:VDQ.t) (x:t) (offs:offs) (value:t) (exp:exp option) (l:lval option) (o:offset option) (v:lval) (t:typ):t = + if M.tracing then M.traceli "update_offset" "do_update_offset %a %a (%a) %a\n" pretty x Offs.pretty offs (Pretty.docOpt (CilType.Exp.pretty ())) exp pretty value; + let mu = function Blob (Blob (y, s', orig), s, orig2) -> Blob (y, ID.join s s',orig) | x -> x in let r = match x, offs with - | `Mutex, _ -> (* hide mutex structure contents, not updated anyway *) - `Mutex - | `Blob (x,s,orig), `Index (_,ofs) -> + | Mutex, _ -> (* hide mutex structure contents, not updated anyway *) + Mutex + | Blob (x,s,orig), `Index (_,ofs) -> begin let l', o' = shift_one_over l o in let x = zero_init_calloced_memory orig x t in - mu (`Blob (join x (do_update_offset ask x ofs value exp l' o' v t), s, orig)) + mu (Blob (join x (do_update_offset ask x ofs value exp l' o' v t), s, orig)) end - | `Blob (x,s,orig), `Field(f, _) -> + | Blob (x,s,orig), `Field(f, _) -> begin - (* We only have `Blob for dynamically allocated memory. In these cases t is the type of the lval used to access it, i.e. for a struct s {int x; int y;} a; accessed via a->x *) + (* We only have Blob for dynamically allocated memory. In these cases t is the type of the lval used to access it, i.e. for a struct s {int x; int y;} a; accessed via a->x *) (* will be int. Here, we need a zero_init of the entire contents of the blob though, which we get by taking the associated f.fcomp. Putting [] for attributes is ok, as we don't *) (* consider them in VD *) let l', o' = shift_one_over l o in let x = zero_init_calloced_memory orig x (TComp (f.fcomp, [])) in - mu (`Blob (join x (do_update_offset ask x offs value exp l' o' v t), s, orig)) + (* Strong update of scalar variable is possible if the variable is unique and size of written value matches size of blob being written to. *) + let do_strong_update = + match v with + | (Var var, Field (fld,_)) -> + let toptype = fld.fcomp in + let blob_size_opt = ID.to_int s in + not @@ ask.is_multiple var + && not @@ Cil.isVoidType t (* Size of value is known *) + && Option.is_some blob_size_opt (* Size of blob is known *) + && BI.equal (Option.get blob_size_opt) (BI.of_int @@ Cil.bitsSizeOf (TComp (toptype, []))/8) + | _ -> false + in + if do_strong_update then + Blob ((do_update_offset ask x offs value exp l' o' v t), s, orig) + else + mu (Blob (join x (do_update_offset ask x offs value exp l' o' v t), s, orig)) end - | `Blob (x,s,orig), _ -> + | Blob (x,s,orig), _ -> begin let l', o' = shift_one_over l o in let x = zero_init_calloced_memory orig x t in @@ -871,7 +945,7 @@ struct begin match v with | (Var var, _) -> let blob_size_opt = ID.to_int s in - not @@ ask.f (Q.IsMultiple var) + not @@ ask.is_multiple var && not @@ Cil.isVoidType t (* Size of value is known *) && Option.is_some blob_size_opt (* Size of blob is known *) && BI.equal (Option.get blob_size_opt) (BI.of_int @@ Cil.alignOf_int t) @@ -879,55 +953,66 @@ struct end in if do_strong_update then - `Blob ((do_update_offset ask x offs value exp l' o' v t), s, orig) + Blob ((do_update_offset ask x offs value exp l' o' v t), s, orig) else - mu (`Blob (join x (do_update_offset ask x offs value exp l' o' v t), s, orig)) + mu (Blob (join x (do_update_offset ask x offs value exp l' o' v t), s, orig)) end - | `Thread _, _ -> + | Thread _, _ -> (* hack for pthread_t variables *) begin match value with - | `Thread t -> value (* if actually assigning thread, use value *) + | Thread t -> value (* if actually assigning thread, use value *) | _ -> - if !GU.global_initialization then - `Thread (ConcDomain.ThreadSet.empty ()) (* if assigning global init (int on linux, ptr to struct on mac), use empty set instead *) + if !AnalysisState.global_initialization then + Thread (ConcDomain.ThreadSet.empty ()) (* if assigning global init (int on linux, ptr to struct on mac), use empty set instead *) else - `Top + Top end + | JmpBuf _, _ -> + (* hack for jmp_buf variables *) + begin match value with + | JmpBuf t -> value (* if actually assigning jmpbuf, use value *) + | Blob(Bot, _, _) -> Bot (* TODO: Stopgap for malloced jmp_bufs, there is something fundamentally flawed somewhere *) + | _ -> + if !AnalysisState.global_initialization then + JmpBuf (JmpBufs.Bufs.empty (), false) (* if assigning global init, use empty set instead *) + else + Top + end | _ -> let result = match offs with | `NoOffset -> begin match value with - | `Blob (y, s, orig) -> mu (`Blob (join x y, s, orig)) - | `Int _ -> cast t value + | Blob (y, s, orig) -> mu (Blob (join x y, s, orig)) + | Int _ -> cast t value | _ -> value end | `Field (fld, offs) when fld.fcomp.cstruct -> begin let t = fld.ftype in match x with - | `Struct str -> + | Struct str -> begin let l', o' = shift_one_over l o in let value' = do_update_offset ask (Structs.get str fld) offs value exp l' o' v t in - `Struct (Structs.replace str fld value') + Struct (Structs.replace str fld value') end - | `Bot -> + | Bot -> let init_comp compinfo = - let nstruct = Structs.create (fun fd -> `Bot) compinfo in - let init_field nstruct fd = Structs.replace nstruct fd `Bot in + let nstruct = Structs.create (fun fd -> Bot) compinfo in + let init_field nstruct fd = Structs.replace nstruct fd Bot in List.fold_left init_field nstruct compinfo.cfields in let strc = init_comp fld.fcomp in let l', o' = shift_one_over l o in - `Struct (Structs.replace strc fld (do_update_offset ask `Bot offs value exp l' o' v t)) - | `Top -> M.warn ~category:Imprecise "Trying to update a field, but the struct is unknown"; top () + Struct (Structs.replace strc fld (do_update_offset ask Bot offs value exp l' o' v t)) + | Top -> M.warn ~category:Imprecise "Trying to update a field, but the struct is unknown"; top () | _ -> M.warn ~category:Imprecise "Trying to update a field, but was not given a struct"; top () end | `Field (fld, offs) -> begin let t = fld.ftype in let l', o' = shift_one_over l o in match x with - | `Union (last_fld, prev_val) -> + | Union (last_fld, prev_val) -> let tempval, tempoffs = if UnionDomain.Field.equal last_fld (`Lifted fld) then prev_val, offs @@ -935,7 +1020,7 @@ struct match offs with | `Field (fldi, _) when fldi.fcomp.cstruct -> (top_value ~varAttr:fld.fattr fld.ftype), offs - | `Field (fldi, _) -> `Union (Unions.top ()), offs + | `Field (fldi, _) -> Union (Unions.top ()), offs | `NoOffset -> top (), offs | `Index (idx, _) when Cil.isArrayType fld.ftype -> begin @@ -943,7 +1028,7 @@ struct | TArray(_, l, _) -> let len = try Cil.lenOfArray l with Cil.LenOfArray -> 42 (* will not happen, VLA not allowed in union and struct *) in - `Array(CArrays.make (IndexDomain.of_int (Cilfacade.ptrdiff_ikind ()) (BI.of_int len)) `Top), offs + Array(CArrays.make (IndexDomain.of_int (Cilfacade.ptrdiff_ikind ()) (BI.of_int len)) Top), offs | _ -> top (), offs (* will not happen*) end | `Index (idx, _) when IndexDomain.equal idx (IndexDomain.of_int (Cilfacade.ptrdiff_ikind ()) BI.zero) -> @@ -953,36 +1038,36 @@ struct top (), offs end in - `Union (`Lifted fld, do_update_offset ask tempval tempoffs value exp l' o' v t) - | `Bot -> `Union (`Lifted fld, do_update_offset ask `Bot offs value exp l' o' v t) - | `Top -> M.warn ~category:Imprecise "Trying to update a field, but the union is unknown"; top () + Union (`Lifted fld, do_update_offset ask tempval tempoffs value exp l' o' v t) + | Bot -> Union (`Lifted fld, do_update_offset ask Bot offs value exp l' o' v t) + | Top -> M.warn ~category:Imprecise "Trying to update a field, but the union is unknown"; top () | _ -> M.warn ~category:Imprecise "Trying to update a field, but was not given a union"; top () end | `Index (idx, offs) -> begin let l', o' = shift_one_over l o in match x with - | `Array x' -> + | Array x' -> let t = (match t with | TArray(t1 ,_,_) -> t1 | _ -> t) in (* This is necessary because t is not a TArray in case of calloc *) let e = determine_offset ask l o exp (Some v) in let new_value_at_index = do_update_offset ask (CArrays.get ask x' (e,idx)) offs value exp l' o' v t in let new_array_value = CArrays.set ask x' (e, idx) new_value_at_index in - `Array new_array_value - | `Bot -> + Array new_array_value + | Bot -> let t,len = (match t with | TArray(t1 ,len,_) -> t1, len | _ -> t, None) in (* This is necessary because t is not a TArray in case of calloc *) let x' = CArrays.bot () in let e = determine_offset ask l o exp (Some v) in - let new_value_at_index = do_update_offset ask `Bot offs value exp l' o' v t in + let new_value_at_index = do_update_offset ask Bot offs value exp l' o' v t in let new_array_value = CArrays.set ask x' (e, idx) new_value_at_index in let len_ci = BatOption.bind len (fun e -> Cil.getInteger @@ Cil.constFold true e) in - let len_id = BatOption.map (fun ci -> IndexDomain.of_int (Cilfacade.ptrdiff_ikind ()) @@ Cilint.big_int_of_cilint ci) len_ci in + let len_id = BatOption.map (IndexDomain.of_int (Cilfacade.ptrdiff_ikind ())) len_ci in let newl = BatOption.default (ID.starting (Cilfacade.ptrdiff_ikind ()) Z.zero) len_id in let new_array_value = CArrays.update_length newl new_array_value in - `Array new_array_value - | `Top -> M.warn ~category:Imprecise "Trying to update an index, but the array is unknown"; top () + Array new_array_value + | Top -> M.warn ~category:Imprecise "Trying to update an index, but the array is unknown"; top () | x when GobOption.exists (BI.equal BI.zero) (IndexDomain.to_int idx) -> do_update_offset ask x offs value exp l' o' v t | _ -> M.warn ~category:Imprecise "Trying to update an index, but was not given an array(%a)" pretty x; top () end @@ -1000,17 +1085,17 @@ struct let rec affect_move ?(replace_with_const=false) ask (x:t) (v:varinfo) movement_for_expr:t = let move_fun x = affect_move ~replace_with_const:replace_with_const ask x v movement_for_expr in match x with - | `Array a -> + | Array a -> begin (* potentially move things (i.e. other arrays after arbitrarily deep nesting) in array first *) let moved_elems = CArrays.map move_fun a in (* then move the array itself *) let new_val = CArrays.move_if_affected ~replace_with_const:replace_with_const ask moved_elems v movement_for_expr in - `Array (new_val) + Array (new_val) end - | `Struct s -> `Struct (Structs.map (move_fun) s) - | `Union (f, v) -> `Union(f, move_fun v) - (* `Blob can not contain Array *) + | Struct s -> Struct (Structs.map (move_fun) s) + | Union (f, v) -> Union(f, move_fun v) + (* Blob can not contain Array *) | x -> x let rec affecting_vars (x:t) = @@ -1018,22 +1103,22 @@ struct list @ (affecting_vars va) in match x with - | `Array a -> + | Array a -> begin let immediately_affecting = CArrays.get_vars_in_e a in CArrays.fold_left add_affecting_one_level immediately_affecting a end - | `Struct s -> + | Struct s -> Structs.fold (fun x value acc -> add_affecting_one_level acc value) s [] - | `Union (f, v) -> + | Union (f, v) -> affecting_vars v - (* `Blob can not contain Array *) + (* Blob can not contain Array *) | _ -> [] (* Won't compile without the final :t annotation *) let rec update_array_lengths (eval_exp: exp -> t) (v:t) (typ:Cil.typ):t = match v, typ with - | `Array(n), TArray(ti, e, _) -> + | Array(n), TArray(ti, e, _) -> begin let update_fun x = update_array_lengths eval_exp x ti in let n' = CArrays.map (update_fun) n in @@ -1042,72 +1127,80 @@ struct | Some e -> begin match eval_exp e with - | `Int x -> ID.cast_to (Cilfacade.ptrdiff_ikind ()) x + | Int x -> ID.cast_to (Cilfacade.ptrdiff_ikind ()) x | _ -> M.debug ~category:Analyzer "Expression for size of VLA did not evaluate to Int at declaration"; ID.starting (Cilfacade.ptrdiff_ikind ()) Z.zero end in - `Array(CArrays.update_length newl n') + Array(CArrays.update_length newl n') end | _ -> v + let rec mark_jmpbufs_as_copied (v:t):t = + match v with + | JmpBuf (v,t) -> JmpBuf (v, true) + | Array n -> Array (CArrays.map (fun (x: t) -> mark_jmpbufs_as_copied x) n) + | Struct n -> Struct (Structs.map (fun (x: t) -> mark_jmpbufs_as_copied x) n) + | Union (f, n) -> Union (f, mark_jmpbufs_as_copied n) + | Blob (a,b,c) -> Blob (mark_jmpbufs_as_copied a, b,c) + | _ -> v let printXml f state = match state with - | `Int n -> ID.printXml f n - | `Float n -> FD.printXml f n - | `Address n -> AD.printXml f n - | `Struct n -> Structs.printXml f n - | `Union n -> Unions.printXml f n - | `Array n -> CArrays.printXml f n - | `Blob n -> Blobs.printXml f n - | `Thread n -> Threads.printXml f n - | `Mutex -> BatPrintf.fprintf f "\n\nmutex\n\n\n" - | `Bot -> BatPrintf.fprintf f "\n\nbottom\n\n\n" - | `Top -> BatPrintf.fprintf f "\n\ntop\n\n\n" + | Int n -> ID.printXml f n + | Float n -> FD.printXml f n + | Address n -> AD.printXml f n + | Struct n -> Structs.printXml f n + | Union n -> Unions.printXml f n + | Array n -> CArrays.printXml f n + | Blob n -> Blobs.printXml f n + | Thread n -> Threads.printXml f n + | MutexAttr n -> MutexAttr.printXml f n + | JmpBuf n -> JmpBufs.printXml f n + | Mutex -> BatPrintf.fprintf f "\n\nmutex\n\n\n" + | Bot -> BatPrintf.fprintf f "\n\nbottom\n\n\n" + | Top -> BatPrintf.fprintf f "\n\ntop\n\n\n" let to_yojson = function - | `Int n -> ID.to_yojson n - | `Float n -> FD.to_yojson n - | `Address n -> AD.to_yojson n - | `Struct n -> Structs.to_yojson n - | `Union n -> Unions.to_yojson n - | `Array n -> CArrays.to_yojson n - | `Blob n -> Blobs.to_yojson n - | `Thread n -> Threads.to_yojson n - | `Mutex -> `String "mutex" - | `Bot -> `String "⊥" - | `Top -> `String "⊤" - - let arbitrary () = QCheck.always `Bot (* S TODO: other elements *) + | Int n -> ID.to_yojson n + | Float n -> FD.to_yojson n + | Address n -> AD.to_yojson n + | Struct n -> Structs.to_yojson n + | Union n -> Unions.to_yojson n + | Array n -> CArrays.to_yojson n + | Blob n -> Blobs.to_yojson n + | Thread n -> Threads.to_yojson n + | MutexAttr n -> MutexAttr.to_yojson n + | JmpBuf n -> JmpBufs.to_yojson n + | Mutex -> `String "mutex" + | Bot -> `String "⊥" + | Top -> `String "⊤" + + let arbitrary () = QCheck.always Bot (* S TODO: other elements *) (*Changes the value: if p is present, change all Integer precisions. If array_attr=(varAttr, typeAttr) is present, change the top level array domain according to the attributes *) let rec project ask p array_attr (v: t): t = match v, p, array_attr with | _, None, None -> v (*Nothing to change*) (* as long as we only have one representation, project is a nop*) - | `Float n, _, _ -> `Float n - | `Int n, Some p, _-> `Int (ID.project p n) - | `Address n, Some p, _-> `Address (project_addr p n) - | `Struct n, _, _ -> `Struct (Structs.map (fun (x: t) -> project ask p None x) n) - | `Union (f, v), _, _ -> `Union (f, project ask p None v) - | `Array n , _, _ -> `Array (project_arr ask p array_attr n) - | `Blob (v, s, z), Some p', _ -> `Blob (project ask p None v, ID.project p' s, z) - | `Thread n, _, _ -> `Thread n - | `Bot, _, _ -> `Bot - | `Top, _, _ -> `Top + | Float n, _, _ -> Float n + | Int n, Some p, _-> Int (ID.project p n) + | Address n, Some p, _-> Address (project_addr p n) + | Struct n, _, _ -> Struct (Structs.map (fun (x: t) -> project ask p None x) n) + | Union (f, v), _, _ -> Union (f, project ask p None v) + | Array n , _, _ -> Array (project_arr ask p array_attr n) + | Blob (v, s, z), Some p', _ -> Blob (project ask p None v, ID.project p' s, z) + | Thread n, _, _ -> Thread n + | Bot, _, _ -> Bot + | Top, _, _ -> Top | _, _, _ -> v (*Nothing to change*) and project_addr p a = AD.map (fun addr -> match addr with | Addr.Addr (v, o) -> Addr.Addr (v, project_offs p o) | ptr -> ptr) a - and project_offs p offs = - match offs with - | `NoOffset -> `NoOffset - | `Field (field, offs') -> `Field (field, project_offs p offs') - | `Index (idx, offs') -> `Index (ID.project p idx, project_offs p offs') + and project_offs p offs = Offs.map_indices (ID.project p) offs and project_arr ask p array_attr n = let n = match array_attr with | Some (varAttr,typAttr) -> CArrays.project ~varAttr ~typAttr ask n @@ -1117,6 +1210,22 @@ struct | None, _ | _, None -> n' | Some l, Some p -> CArrays.update_length (ID.project p l) n' + + let relift state = + match state with + | Int n -> Int (ID.relift n) + | Float n -> Float (FD.relift n) + | Address n -> Address (AD.relift n) + | Struct n -> Struct (Structs.relift n) + | Union n -> Union (Unions.relift n) + | Array n -> Array (CArrays.relift n) + | Blob n -> Blob (Blobs.relift n) + | Thread n -> Thread (Threads.relift n) + | JmpBuf n -> JmpBuf (JmpBufs.relift n) + | MutexAttr n -> MutexAttr (MutexAttr.relift n) + | Mutex -> Mutex + | Bot -> Bot + | Top -> Top end and Structs: StructDomain.S with type field = fieldinfo and type value = Compound.t = @@ -1153,16 +1262,8 @@ struct | Addr.UnknownPtr -> None | Addr.Addr (vi, offs) when Addr.Offs.is_definite offs -> - let rec offs_to_offset = function - | `NoOffset -> NoOffset - | `Field (f, offs) -> Field (f, offs_to_offset offs) - | `Index (i, offs) -> - (* Addr.Offs.is_definite implies Idx.to_int returns Some *) - let i_definite = BatOption.get (IndexDomain.to_int i) in - let i_exp = Cil.(kinteger64 ILongLong (IntOps.BigIntOps.to_int64 i_definite)) in - Index (i_exp, offs_to_offset offs) - in - let offset = offs_to_offset offs in + (* Addr.Offs.is_definite implies to_cil doesn't contain Offset.any_index_exp. *) + let offset = Addr.Offs.to_cil offs in let cast_to_void_ptr e = Cilfacade.mkCast ~e ~newt:(TPtr (TVoid [], [])) @@ -1219,22 +1320,23 @@ struct vd_invariant ~vs ~offset ~lval v and vd_invariant ~vs ~offset ~lval = function - | `Int n -> + | Compound.Int n -> let e = Lval lval in if InvariantCil.(not (exp_contains_tmp e) && exp_is_in_scope scope e) then ID.invariant e n else Invariant.none - | `Float n -> + | Float n -> let e = Lval lval in if InvariantCil.(not (exp_contains_tmp e) && exp_is_in_scope scope e) then FD.invariant e n else Invariant.none - | `Address n -> ad_invariant ~vs ~offset ~lval n - | `Struct n -> Structs.invariant ~value_invariant:(vd_invariant ~vs) ~offset ~lval n - | `Union n -> Unions.invariant ~value_invariant:(vd_invariant ~vs) ~offset ~lval n - | `Blob n when GobConfig.get_bool "ana.base.invariant.blobs" -> blob_invariant ~vs ~offset ~lval n + | Address n -> ad_invariant ~vs ~offset ~lval n + | Struct n -> Structs.invariant ~value_invariant:(vd_invariant ~vs) ~offset ~lval n + | Union n -> Unions.invariant ~value_invariant:(vd_invariant ~vs) ~offset ~lval n + | Array n -> CArrays.invariant ~value_invariant:(vd_invariant ~vs) ~offset ~lval n + | Blob n when GobConfig.get_bool "ana.base.invariant.blobs" -> blob_invariant ~vs ~offset ~lval n | _ -> Invariant.none (* TODO *) and deref_invariant ~vs vi ~offset ~lval = diff --git a/src/cdomains/vectorMatrix.ml b/src/cdomains/vectorMatrix.ml index 03b9a44a4b..d652145032 100644 --- a/src/cdomains/vectorMatrix.ml +++ b/src/cdomains/vectorMatrix.ml @@ -1,4 +1,6 @@ -open Prelude.Ana +(** OCaml implementations of vectors and matrices. *) + +open Batteries module Array = Batteries.Array module M = Messages @@ -582,14 +584,14 @@ module ArrayMatrix: AbstractMatrix = match Array.bsearch Int.ord (Lazy.force p2) j with | `At pos -> let beta = m1_i.(j) in Array.iteri (fun j' x -> m1_i.(j') <- m1_i.(j') -: beta *: m2.(pos).(j') ) m1_i - | _ -> raise Exit; + | _ -> raise Stdlib.Exit; done; if m1_i. (num_cols m1 - 1) <>: A.zero then - raise Exit + raise Stdlib.Exit done; true ) - with Exit -> false;; + with Stdlib.Exit -> false;; let is_covered_by m1 m2 = timing_wrap "is_covered_by" (is_covered_by m1) m2 diff --git a/src/cdomains/basetype.ml b/src/common/cdomains/basetype.ml similarity index 85% rename from src/cdomains/basetype.ml rename to src/common/cdomains/basetype.ml index 7984734a13..55b5dbde07 100644 --- a/src/cdomains/basetype.ml +++ b/src/common/cdomains/basetype.ml @@ -1,36 +1,19 @@ -module GU = Goblintutil -open GoblintCil +(** Printables and domains for some common types. *) +open GoblintCil -(** Location with special alphanumeric output for extraction. *) -module ExtractLocation : Printable.S with type t = location = -struct - include CilType.Location - - let show loc = - let f i = (if i < 0 then "n" else "") ^ string_of_int (abs i) in - f loc.line ^ "b" ^ f loc.byte - include Printable.SimpleShow ( - struct - type nonrec t = t - let show = show - end - ) -end module Variables = struct include CilType.Varinfo - let trace_enabled = true let show x = if RichVarinfo.BiVarinfoMap.Collection.mem_varinfo x then let description = RichVarinfo.BiVarinfoMap.Collection.describe_varinfo x in "(" ^ x.vname ^ ", " ^ description ^ ")" else x.vname let pretty () x = Pretty.text (show x) - type group = Global | Local | Parameter | Temp [@@deriving show { with_path = false }] - let (%) = Batteries.(%) - let to_group = Option.some % function + type group = Global | Local | Parameter | Temp [@@deriving ord, show { with_path = false }] + let to_group = function | x when x.vglob -> Global | x when x.vdecl.line = -1 -> Temp | x when Cilfacade.is_varinfo_formal x -> Parameter @@ -43,7 +26,7 @@ end module RawStrings: Printable.S with type t = string = struct - include Printable.Std + include Printable.StdLeaf open Pretty type t = string [@@deriving eq, ord, hash, to_yojson] let show x = "\"" ^ x ^ "\"" @@ -60,7 +43,7 @@ module Strings: Lattice.S with type t = [`Bot | `Lifted of string | `Top] = module RawBools: Printable.S with type t = bool = struct - include Printable.Std + include Printable.StdLeaf open Pretty type t = bool [@@deriving eq, ord, hash, to_yojson] let show (x:t) = if x then "true" else "false" @@ -77,7 +60,6 @@ module Bools: Lattice.S with type t = [`Bot | `Lifted of bool | `Top] = module CilExp = struct - include Printable.Std (* for Groupable *) include CilType.Exp let name () = "expressions" @@ -174,8 +156,4 @@ struct let printXml f x = BatPrintf.fprintf f "\n\n%s\n\n\n" (XmlUtil.escape (show x)) end -module CilField = -struct - include Printable.Std (* for default MapDomain.Groupable *) - include CilType.Fieldinfo -end +module CilField = CilType.Fieldinfo diff --git a/src/common/common.mld b/src/common/common.mld new file mode 100644 index 0000000000..662c789572 --- /dev/null +++ b/src/common/common.mld @@ -0,0 +1,74 @@ +{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 +} + +{2 Specification} +{!modules: +AnalysisState +ControlSpecC +} + +{2 Configuration} +{!modules: +GobConfig +AfterConfig +JsonSchema +Options +} + + +{1 Domains} +{!modules: +Printable +Lattice +} + +{2 Analysis-specific} + +{3 Other} +{!modules:Basetype} + + +{1 I/O} +{!modules: +Messages +Tracing +} + + +{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} + +{2 Other libraries} +{!modules:MyCheck} diff --git a/src/domains/lattice.ml b/src/common/domains/lattice.ml similarity index 85% rename from src/domains/lattice.ml rename to src/common/domains/lattice.ml index 7e685c60f7..51306d637f 100644 --- a/src/domains/lattice.ml +++ b/src/common/domains/lattice.ml @@ -1,15 +1,15 @@ -(** The lattice signature and simple functors for building lattices. *) +(** Signature for lattices. + Functors for common lattices. *) module Pretty = GoblintCil.Pretty -module GU = Goblintutil (* 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 @@ -52,7 +52,7 @@ exception Invalid_widen of Pretty.doc let () = Printexc.register_printer (function | Invalid_widen doc -> - Some (Pretty.sprint ~width:max_int (Pretty.dprintf "Lattice.Invalid_widen(%a)" Pretty.insert doc)) + Some (GobPretty.sprintf "Lattice.Invalid_widen(%a)" Pretty.insert doc) | _ -> None (* for other exceptions *) ) @@ -151,12 +151,17 @@ end module HConsed (Base:S) = struct include Printable.HConsed (Base) + + (* 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 int_refine_active = GobConfig.get_string "ana.int.refinement" <> "never" + let lift_f2 f x y = f (unlift x) (unlift y) - let narrow x y = lift (lift_f2 Base.narrow x y) - let widen x y = lift (lift_f2 Base.widen x y) - let meet x y = lift (lift_f2 Base.meet x y) - let join x y = lift (lift_f2 Base.join x y) - let leq = lift_f2 Base.leq + let narrow x y = if (not int_refine_active) && 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 (not int_refine_active) && x.BatHashcons.tag == y.BatHashcons.tag then x else lift (lift_f2 Base.meet x y) + let join x y = if x.BatHashcons.tag == y.BatHashcons.tag then x else lift (lift_f2 Base.join x y) + let leq x y = (x.BatHashcons.tag == y.BatHashcons.tag) || lift_f2 Base.leq x y let is_top = lift_f Base.is_top let is_bot = lift_f Base.is_bot let top () = lift (Base.top ()) @@ -324,14 +329,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 @@ -462,6 +467,33 @@ struct let narrow = op_scheme Base1.narrow Base2.narrow Base3.narrow end +module Prod4 (Base1: S) (Base2: S) (Base3: S) (Base4: S) = +struct + include Printable.Prod4 (Base1) (Base2) (Base3) (Base4) + + let bot () = (Base1.bot (), Base2.bot (), Base3.bot (), Base4.bot ()) + let is_bot (x1,x2,x3,x4) = Base1.is_bot x1 && Base2.is_bot x2 && Base3.is_bot x3 && Base4.is_bot x4 + let top () = (Base1.top (), Base2.top (), Base3.top (), Base4.top ()) + let is_top (x1,x2,x3,x4) = Base1.is_top x1 && Base2.is_top x2 && Base3.is_top x3 && Base4.is_top x4 + let leq (x1,x2,x3,x4) (y1,y2,y3,y4) = Base1.leq x1 y1 && Base2.leq x2 y2 && Base3.leq x3 y3 && Base4.leq x4 y4 + + let pretty_diff () ((x1,x2,x3,x4:t),(y1,y2,y3,y4:t)): Pretty.doc = + if not (Base1.leq x1 y1) then + Base1.pretty_diff () (x1,y1) + else if not (Base2.leq x2 y2) then + Base2.pretty_diff () (x2,y2) + else if not (Base3.leq x3 y3) then + Base3.pretty_diff () (x3,y3) + else + Base4.pretty_diff () (x4,y4) + + let op_scheme op1 op2 op3 op4 (x1,x2,x3,x4) (y1,y2,y3,y4): t = (op1 x1 y1, op2 x2 y2, op3 x3 y3, op4 x4 y4) + let join = op_scheme Base1.join Base2.join Base3.join Base4.join + let meet = op_scheme Base1.meet Base2.meet Base3.meet Base4.meet + let widen = op_scheme Base1.widen Base2.widen Base3.widen Base4.widen + let narrow = op_scheme Base1.narrow Base2.narrow Base3.narrow Base4.narrow +end + module LiftBot (Base : S) = struct include Printable.LiftBot (Base) @@ -598,7 +630,7 @@ struct Pretty.dprintf "%a not leq %a" pretty x pretty y end -module Chain (P: Printable.ChainParams) = +module Chain (P: Printable.ChainParams) : S with type t = int = struct include Printable.Std include Printable.Chain (P) diff --git a/src/domains/myCheck.ml b/src/common/domains/myCheck.ml similarity index 83% rename from src/domains/myCheck.ml rename to src/common/domains/myCheck.ml index 86479ec76a..98583cd2c3 100644 --- a/src/domains/myCheck.ml +++ b/src/common/domains/myCheck.ml @@ -1,3 +1,5 @@ +(** {!QCheck} extensions. *) + open QCheck let shrink arb = BatOption.default Shrink.nil arb.shrink @@ -41,15 +43,14 @@ struct in set_shrink shrink int64 - let big_int: Big_int_Z.big_int arbitrary = - let open Big_int_Z in + let big_int: Z.t arbitrary = let shrink x yield = let y = ref x in - let two_big_int = big_int_of_int 2 in - while not (eq_big_int !y zero_big_int) do y := div_big_int !y two_big_int; yield !y; done; + let two_big_int = Z.of_int 2 in + while not (Z.equal !y Z.zero) do y := Z.ediv !y two_big_int; yield !y; done; () in - set_print string_of_big_int @@ set_shrink shrink @@ QCheck.map big_int_of_int64 int64 + set_print Z.to_string @@ set_shrink shrink @@ QCheck.map Z.of_int64 int64 let sequence (arbs: 'a arbitrary list): 'a list arbitrary = let gens = List.map gen arbs in diff --git a/src/domains/printable.ml b/src/common/domains/printable.ml similarity index 85% rename from src/domains/printable.ml rename to src/common/domains/printable.ml index c67c3c94a6..b0755fb730 100644 --- a/src/domains/printable.ml +++ b/src/common/domains/printable.ml @@ -1,4 +1,5 @@ -(** Some things are not quite lattices ... *) +(** Signature for comparable and outputtable elements. + Functors for common printables. *) module Pretty = GoblintCil.Pretty open Pretty @@ -42,32 +43,22 @@ struct let relift (x: t) = match x with _ -> . end +(** Default dummy definitions. + Include as the first thing to avoid these overriding actual definitions. *) module Std = struct - (* let equal = Util.equals - let hash = Hashtbl.hash*) - let name () = "std" - - (* start MapDomain.Groupable *) - type group = | - let show_group (x: group) = match x with _ -> . - let to_group _ = None - let trace_enabled = false - (* end MapDomain.Groupable *) - let tag _ = failwith "Std: no tag" let arbitrary () = failwith "no arbitrary" - let relift x = x end -module Blank = +(** Default dummy definitions for leaf types: primitive and CIL types, + which don't contain inner types that require relifting. *) +module StdLeaf = struct include Std - let pretty () _ = text "Output not supported" - let show _ = "Output not supported" - let name () = "blank" - let printXml f _ = BatPrintf.fprintf f "\n\nOutput not supported!\n\n\n" + + let relift x = x end @@ -92,7 +83,7 @@ end module SimplePretty (P: Prettyable) = struct - let show x = Pretty.sprint ~width:max_int (P.pretty () x) + let show x = GobPretty.sprint P.pretty x let printXml f x = BatPrintf.fprintf f "\n\n%s\n\n\n" (XmlUtil.escape (show x)) let to_yojson x = `String (show x) end @@ -102,14 +93,13 @@ module type Name = sig val name: string end module UnitConf (N: Name) = struct type t = unit [@@deriving eq, ord, hash] - include Std + include StdLeaf let pretty () _ = text N.name let show _ = N.name let name () = "Unit" let printXml f () = BatPrintf.fprintf f "\n\n%s\n\n\n" (XmlUtil.escape N.name) let to_yojson () = `String N.name let arbitrary () = QCheck.unit - let relift x = x end module Unit = UnitConf (struct let name = "()" end) @@ -137,14 +127,20 @@ struct let unlift x = x.BatHashcons.obj let lift = HC.hashcons htable let lift_f f (x:Base.t BatHashcons.hobj) = f (x.BatHashcons.obj) + + let show = lift_f Base.show + let pretty () = lift_f (Base.pretty ()) + + (* Debug printing with tags *) + (* let pretty () x = Pretty.dprintf "%a[%d,%d]" Base.pretty x.BatHashcons.obj x.BatHashcons.tag x.BatHashcons.hcode + let show x = (Base.show x.BatHashcons.obj) ^ "[" ^ string_of_int x.BatHashcons.tag ^ "," ^ string_of_int x.BatHashcons.hcode ^ "]" *) + let relift x = let y = Base.relift x.BatHashcons.obj in HC.hashcons htable y let name () = "HConsed "^Base.name () let hash x = x.BatHashcons.hcode let tag x = x.BatHashcons.tag let compare x y = Stdlib.compare x.BatHashcons.tag y.BatHashcons.tag - let show = lift_f Base.show let to_yojson = lift_f (Base.to_yojson) - let pretty () = lift_f (Base.pretty ()) let printXml f x = Base.printXml f x.BatHashcons.obj let equal_debug x y = (* This debug version checks if we call hashcons enough to have up-to-date tags. Comment out the equal below to use this. This will be even slower than with hashcons disabled! *) @@ -255,13 +251,13 @@ struct let pretty () (state:t) = match state with - | `Left n -> Base1.pretty () n - | `Right n -> Base2.pretty () n + | `Left n -> Pretty.dprintf "%s:%a" (Base1.name ()) Base1.pretty n + | `Right n -> Pretty.dprintf "%s:%a" (Base2.name ()) Base2.pretty n let show state = match state with - | `Left n -> Base1.show n - | `Right n -> Base2.show n + | `Left n -> (Base1.name ()) ^ ":" ^ Base1.show n + | `Right n -> (Base2.name ()) ^ ":" ^ Base2.show n let name () = "either " ^ Base1.name () ^ " or " ^ Base2.name () let printXml f = function @@ -370,9 +366,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)) @@ -407,12 +411,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) = @@ -427,6 +443,35 @@ struct let arbitrary () = QCheck.triple (Base1.arbitrary ()) (Base2.arbitrary ()) (Base3.arbitrary ()) end +module Prod4 (Base1: S) (Base2: S) (Base3: S) (Base4: S) = struct + type t = Base1.t * Base2.t * Base3.t * Base4.t [@@deriving eq, ord, hash] + include Std + + let show (x,y,z,w) = "(" ^ Base1.show x ^ ", " ^ Base2.show y ^ ", " ^ Base3.show z ^ ", " ^ Base4.show w ^ ")" + + let pretty () (x,y,z,w) = + text "(" ++ + Base1.pretty () x + ++ text ", " ++ + Base2.pretty () y + ++ text ", " ++ + Base3.pretty () z + ++ text ", " ++ + Base4.pretty () w + ++ text ")" + + let printXml f (x,y,z,w) = + BatPrintf.fprintf f "\n\n\n%s\n\n%a\n%s\n\n%a\n%s\n\n%a\n%s\n\n%a\n\n" (XmlUtil.escape (Base1.name ())) Base1.printXml x (XmlUtil.escape (Base2.name ())) Base2.printXml y (XmlUtil.escape (Base3.name ())) Base3.printXml z (XmlUtil.escape (Base4.name ())) Base4.printXml w + + let to_yojson (x, y, z, w) = + `Assoc [ (Base1.name (), Base1.to_yojson x); (Base2.name (), Base2.to_yojson y); (Base3.name (), Base3.to_yojson z); (Base4.name (), Base4.to_yojson w) ] + + let name () = Base1.name () ^ " * " ^ Base2.name () ^ " * " ^ Base3.name () ^ " * " ^ Base4.name () + + let relift (x,y,z,w) = (Base1.relift x, Base2.relift y, Base3.relift z, Base4.relift w) + let arbitrary () = QCheck.quad (Base1.arbitrary ()) (Base2.arbitrary ()) (Base3.arbitrary ()) (Base4.arbitrary ()) +end + module Liszt (Base: S) = struct type t = Base.t list [@@deriving eq, ord, hash, to_yojson] @@ -471,7 +516,8 @@ end module Chain (P: ChainParams): S with type t = int = struct type t = int [@@deriving eq, ord, hash] - include Std + include StdLeaf + let name () = "chain" let show x = P.names x let pretty () x = text (show x) @@ -479,7 +525,6 @@ struct let to_yojson x = `String (P.names x) let arbitrary () = QCheck.int_range 0 (P.n () - 1) - let relift x = x end module LiftBot (Base : S) = @@ -560,7 +605,7 @@ end module Strings = struct type t = string [@@deriving eq, ord, hash, to_yojson] - include Std + include StdLeaf let pretty () n = text n let show n = n let name () = "String" @@ -620,3 +665,25 @@ let get_short_list begin_str end_str list = let str = String.concat separator cut_str_list in begin_str ^ str ^ end_str + + +module Yojson = +struct + include StdLeaf + type t = Yojson.Safe.t [@@deriving eq] + let name () = "yojson" + + let compare = Stdlib.compare + let hash = Hashtbl.hash + + let pretty = GobYojson.pretty + + include SimplePretty ( + struct + type nonrec t = t + let pretty = pretty + end + ) + + let to_yojson x = x (* override SimplePretty *) +end diff --git a/src/common/dune b/src/common/dune new file mode 100644 index 0000000000..c8f1564782 --- /dev/null +++ b/src/common/dune @@ -0,0 +1,29 @@ +(include_subdirs unqualified) + +(library + (name goblint_common) + (public_name goblint.common) + (wrapped false) ; TODO: wrap + (libraries + batteries.unthreaded + zarith + goblint_std + goblint-cil + fpath + yojson + json-data-encoding + cpu + goblint_timing + goblint_build_info + goblint.sites + qcheck-core.runner) + (flags :standard -open Goblint_std) + (preprocess + (pps + ppx_deriving.std + ppx_deriving_hash + ppx_deriving_yojson + ppx_blob)) + (preprocessor_deps (file util/options.schema.json))) + +(documentation) diff --git a/src/common/framework/analysisState.ml b/src/common/framework/analysisState.ml new file mode 100644 index 0000000000..05a93741f8 --- /dev/null +++ b/src/common/framework/analysisState.ml @@ -0,0 +1,30 @@ +(** Global flags for analysis state. *) + +(** If this is true we output messages and collect accesses. + This is set to true in control.ml before we verify the result (or already before solving if warn = 'early') *) +let should_warn = ref false + +(** Whether signed overflow or underflow happened *) +let svcomp_may_overflow = 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 + + +(** Whether currently in postsolver evaluations (e.g. verify, warn) *) +let postsolving = ref false + +(* None if verification is disabled, Some true if verification succeeded, Some false if verification failed *) +let verified : bool option ref = ref None diff --git a/src/common/framework/controlSpecC.ml b/src/common/framework/controlSpecC.ml new file mode 100644 index 0000000000..eaec77f6c5 --- /dev/null +++ b/src/common/framework/controlSpecC.ml @@ -0,0 +1,49 @@ +module Failwith = Printable.Failwith ( + struct + let message = "uninitialized control_spec_c" + end + ) + +let control_spec_c: (module Printable.S) ref = ref (module Failwith: Printable.S) + + +type t = Obj.t (** represents [(val !control_spec_c).t] *) + +(* The extra level of indirection allows calls to this static module to go to a dynamic first-class module. *) + +let name () = + let module C = (val !control_spec_c) in + C.name () + +let equal x y = + let module C = (val !control_spec_c) in + C.equal (Obj.obj x) (Obj.obj y) +let compare x y = + let module C = (val !control_spec_c) in + C.compare (Obj.obj x) (Obj.obj y) +let hash x = + let module C = (val !control_spec_c) in + C.hash (Obj.obj x) +let tag x = + let module C = (val !control_spec_c) in + C.tag (Obj.obj x) + +let show x = + let module C = (val !control_spec_c) in + C.show (Obj.obj x) +let pretty () x = + let module C = (val !control_spec_c) in + C.pretty () (Obj.obj x) +let printXml f x = + let module C = (val !control_spec_c) in + C.printXml f (Obj.obj x) +let to_yojson x = + let module C = (val !control_spec_c) in + C.to_yojson (Obj.obj x) + +let arbitrary () = + let module C = (val !control_spec_c) in + QCheck.map ~rev:Obj.obj Obj.repr (C.arbitrary ()) +let relift x = + let module C = (val !control_spec_c) in + Obj.repr (C.relift (Obj.obj x)) diff --git a/src/common/framework/controlSpecC.mli b/src/common/framework/controlSpecC.mli new file mode 100644 index 0000000000..330fd4bf73 --- /dev/null +++ b/src/common/framework/controlSpecC.mli @@ -0,0 +1,8 @@ +(** {{!Analyses.Spec.C} Context module} for the dynamically composed analysis. *) + +(** Top-level Control Spec context as static module, which delegates to {!control_spec_c}. + This allows using top-level context values inside individual analyses. *) +include Printable.S + +(** Reference to top-level Control Spec context first-class module. *) +val control_spec_c: (module Printable.S) ref diff --git a/src/framework/edge.ml b/src/common/framework/edge.ml similarity index 63% rename from src/framework/edge.ml rename to src/common/framework/edge.ml index 6202f6bd31..e6f214a4c8 100644 --- a/src/framework/edge.ml +++ b/src/common/framework/edge.ml @@ -1,3 +1,6 @@ +(** CFG edge. + Corresponds to a (primitive) program statement between program points (and their states). *) + open GoblintCil open Pretty @@ -29,7 +32,7 @@ type t = * appeared *) | Skip (** This is here for historical reasons. I never use Skip edges! *) -[@@deriving eq, ord, hash, to_yojson] +[@@deriving eq, ord, hash] let pretty () = function @@ -55,3 +58,56 @@ let pretty_plain () = function | ASM _ -> text "ASM ..." | Skip -> text "Skip" | VDecl v -> dprintf "VDecl '%a %s;'" d_type v.vtype v.vname + +let to_yojson e = + let fields = match e with + | Assign (lval, exp) -> + [ + ("type", `String "assign"); + ("lval", CilType.Lval.to_yojson lval); + ("exp", CilType.Exp.to_yojson exp); + ] + | Test (exp, branch) -> + [ + ("type", `String "branch"); + ("exp", CilType.Exp.to_yojson exp); + ("branch", `Bool branch); + ] + | Proc (lval, function_, args) -> + [ + ("type", `String "call"); + ("lval", [%to_yojson: CilType.Lval.t option] lval); + ("function", CilType.Exp.to_yojson function_); + ("args", [%to_yojson: CilType.Exp.t list] args); + ] + | Entry function_ -> + [ + ("type", `String "entry"); + ("function", CilType.Fundec.to_yojson function_); + ] + | Ret (exp, function_) -> + [ + ("type", `String "return"); + ("function", CilType.Fundec.to_yojson function_); + ("exp", [%to_yojson: CilType.Exp.t option] exp); + ] + | ASM (instructions, output, input) -> + [ + ("type", `String "asm"); + ("instructions", [%to_yojson: string list] instructions); + ("output", asm_out_to_yojson output); + ("input", asm_in_to_yojson input); + ] + | VDecl variable -> + [ + ("type", `String "declare"); + ("variable", CilType.Varinfo.to_yojson variable); + ] + | Skip -> + [ + ("type", `String "nop"); + ] + in + `Assoc ([ + ("string", `String (GobPretty.sprint pretty e)) + ] @ fields) diff --git a/src/framework/myCFG.ml b/src/common/framework/myCFG.ml similarity index 89% rename from src/framework/myCFG.ml rename to src/common/framework/myCFG.ml index ad8ce433a3..76675f3c88 100644 --- a/src/framework/myCFG.ml +++ b/src/common/framework/myCFG.ml @@ -1,4 +1,6 @@ -(** Our Control-flow graph implementation. *) +(** Control-flow graph. + + Distinct from CIL's CFG. *) open GoblintCil @@ -20,7 +22,7 @@ type edge = Edge.t = | Skip -type edges = (location * edge) list +type edges = (CilType.Location.t * Edge.t) list [@@deriving eq, hash] type cfg = node -> (edges * node) list @@ -58,8 +60,6 @@ let unknown_exp : exp = mkString "__unknown_value__" let dummy_func = emptyFunction "__goblint_dummy_init" (* TODO get rid of this? *) let dummy_node = FunctionEntry Cil.dummyFunDec -let all_array_index_exp : exp = CastE(TInt(Cilfacade.ptrdiff_ikind (),[]), unknown_exp) - module type FileCfg = sig diff --git a/src/framework/node.ml b/src/common/framework/node.ml similarity index 91% rename from src/framework/node.ml rename to src/common/framework/node.ml index 4561006b01..906f9e1d77 100644 --- a/src/framework/node.ml +++ b/src/common/framework/node.ml @@ -1,7 +1,10 @@ +(** CFG node. + Corresponds to a program point between program statements. *) + open GoblintCil open Pretty -include Printable.Std +include Printable.StdLeaf include Node0 @@ -54,19 +57,19 @@ let show_cfg = function let find_fundec (node: t) = match node with | Statement stmt -> Cilfacade.find_stmt_fundec stmt - | Function fd -> fd + | Function fd | FunctionEntry fd -> fd +(** @raise Not_found *) let of_id s = let ix = Str.search_forward (Str.regexp {|[0-9]+$|}) s 0 in let id = int_of_string (Str.string_after s ix) in let prefix = Str.string_before s ix in match ix with - | 0 -> Statement { dummyStmt with sid = id } + | 0 -> Statement (Cilfacade.find_stmt_sid id) | _ -> let fundec = Cilfacade.find_varinfo_fundec {dummyFunDec.svar with vid = id} in match prefix with | "ret" -> Function fundec | "fun" -> FunctionEntry fundec - | _ -> invalid_arg "Node.of_id: invalid prefix" - + | _ -> raise Not_found 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/afterConfig.ml b/src/common/util/afterConfig.ml similarity index 70% rename from src/util/afterConfig.ml rename to src/common/util/afterConfig.ml index a49e4f31cc..ddc544ef0b 100644 --- a/src/util/afterConfig.ml +++ b/src/common/util/afterConfig.ml @@ -1,3 +1,5 @@ +(** Hooks which run after the runtime configuration is fully loaded. *) + let callbacks = ref [] let register callback = diff --git a/src/util/cilType.ml b/src/common/util/cilType.ml similarity index 88% rename from src/util/cilType.ml rename to src/common/util/cilType.ml index a7f07069e5..fe4f257da1 100644 --- a/src/util/cilType.ml +++ b/src/common/util/cilType.ml @@ -1,15 +1,16 @@ +(** Printables for CIL types. *) + open GoblintCil open Pretty module type S = sig include Printable.S - (* include MapDomain.Groupable *) (* FIXME: dependency cycle *) end module Std = struct - include Printable.Std + include Printable.StdLeaf end let hash_float = Hashtbl.hash (* TODO: float hash in ppx_deriving_hash *) @@ -99,7 +100,24 @@ module Ikind: S with type t = ikind = struct include Std - type t = ikind + (* Re-export constructors for monomorphization and deriving. *) + type t = ikind = + | IChar + | ISChar + | IUChar + | IBool + | IInt + | IUInt + | IShort + | IUShort + | ILong + | IULong + | ILongLong + | IULongLong + | IInt128 + | IUInt128 + [@@deriving hash] + (* Hashtbl.hash doesn't monomorphize, so derive instead. *) let name () = "ikind" @@ -108,7 +126,6 @@ struct (* Monomorphize polymorphic operations for optimization. *) let equal (x: t) (y: t) = x = y let compare (x: t) (y: t) = Stdlib.compare x y - let hash (x: t) = Hashtbl.hash x (* Output *) let pretty () x = d_ikind () x @@ -124,7 +141,18 @@ module Fkind: S with type t = fkind = struct include Std - type t = fkind + (* Re-export constructors for monomorphization and deriving. *) + type t = fkind = + | FFloat + | FDouble + | FLongDouble + | FFloat128 + | FComplexFloat + | FComplexDouble + | FComplexLongDouble + | FComplexFloat128 + [@@deriving hash] + (* Hashtbl.hash doesn't monomorphize, so derive instead. *) let name () = "fkind" @@ -133,7 +161,6 @@ struct (* Monomorphize polymorphic operations for optimization. *) let equal (x: t) (y: t) = x = y let compare (x: t) (y: t) = Stdlib.compare x y - let hash (x: t) = Hashtbl.hash x (* Output *) let pretty () x = d_fkind () x @@ -149,7 +176,13 @@ module Unop: S with type t = unop = struct include Std - type t = unop + (* Re-export constructors for monomorphization and deriving. *) + type t = unop = + | Neg + | BNot + | LNot + [@@deriving hash] + (* Hashtbl.hash doesn't monomorphize, so derive instead. *) let name () = "unop" @@ -158,7 +191,6 @@ struct (* Monomorphize polymorphic operations for optimization. *) let equal (x: t) (y: t) = x = y let compare (x: t) (y: t) = Stdlib.compare x y - let hash (x: t) = Hashtbl.hash x (* Output *) let pretty () x = d_unop () x @@ -174,7 +206,32 @@ module Binop: S with type t = binop = struct include Std - type t = binop + (* Re-export constructors for monomorphization and deriving. *) + type t = binop = + | PlusA + | PlusPI + | IndexPI + | MinusA + | MinusPI + | MinusPP + | Mult + | Div + | Mod + | Shiftlt + | Shiftrt + | Lt + | Gt + | Le + | Ge + | Eq + | Ne + | BAnd + | BXor + | BOr + | LAnd + | LOr + [@@deriving hash] + (* Hashtbl.hash doesn't monomorphize, so derive instead. *) let name () = "binop" @@ -183,7 +240,6 @@ struct (* Monomorphize polymorphic operations for optimization. *) let equal (x: t) (y: t) = x = y let compare (x: t) (y: t) = Stdlib.compare x y - let hash (x: t) = Hashtbl.hash x (* Output *) let pretty () x = d_binop () x @@ -199,7 +255,13 @@ module Wstring_type: S with type t = wstring_type = struct include Std - type t = wstring_type + (* Re-export constructors for monomorphization and deriving. *) + type t = wstring_type = + | Wchar_t + | Char16_t + | Char32_t + [@@deriving hash] + (* Hashtbl.hash doesn't monomorphize, so derive instead. *) let name () = "wstring_type" @@ -208,7 +270,6 @@ struct (* Monomorphize polymorphic operations for optimization. *) let equal (x: t) (y: t) = x = y let compare (x: t) (y: t) = Stdlib.compare x y - let hash (x: t) = Hashtbl.hash x (* Output *) let show = function @@ -227,7 +288,12 @@ module Encoding: S with type t = encoding = struct include Std - type t = encoding + (* Re-export constructors for monomorphization and deriving. *) + type t = encoding = + | No_encoding + | Utf8 + [@@deriving hash] + (* Hashtbl.hash doesn't monomorphize, so derive instead. *) let name () = "encoding" @@ -236,7 +302,6 @@ struct (* Monomorphize polymorphic operations for optimization. *) let equal (x: t) (y: t) = x = y let compare (x: t) (y: t) = Stdlib.compare x y - let hash (x: t) = Hashtbl.hash x (* Output *) let show = function diff --git a/src/util/cilfacade.ml b/src/common/util/cilfacade.ml similarity index 71% rename from src/util/cilfacade.ml rename to src/common/util/cilfacade.ml index 05e381d67d..ba57074e5a 100644 --- a/src/util/cilfacade.ml +++ b/src/common/util/cilfacade.ml @@ -1,12 +1,20 @@ -(** Helpful functions for dealing with [Cil]. *) +(** {!GoblintCil} utilities. *) open GobConfig open GoblintCil module E = Errormsg -module GU = Goblintutil include Cilfacade0 +(** Command for assigning an id to a varinfo. All varinfos directly created by Goblint should be modified by this method *) +let create_var (var: varinfo) = + (* TODO Hack: this offset should preempt conflicts with ids generated by CIL *) + let start_id = 10_000_000_000 in + let hash = Hashtbl.hash { var with vid = 0 } in + let hash = if hash < start_id then hash + start_id else hash in + { var with vid = hash } + + (** Is character type (N1570 6.2.5.15)? *) let isCharType t = match Cil.unrollType t with @@ -18,10 +26,22 @@ let isFloatType t = | TFloat _ -> true | _ -> false +let rec isVLAType t = + match Cil.unrollType t with + | TArray (et, len, _) -> + let variable_len = GobOption.exists (Fun.negate Cil.isConstant) len in + variable_len || isVLAType et + | _ -> false + +let is_first_field x = match x.fcomp.cfields with + | [] -> false + | f :: _ -> CilType.Fieldinfo.equal f x + 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 (); @@ -30,21 +50,29 @@ let init () = lowerConstants := true; Mergecil.ignore_merge_conflicts := true; (* lineDirectiveStyle := None; *) - Rmtmps.keepUnused := true; - print_CIL_Input := true + RmUnused.keepUnused := true; + print_CIL_Input := true; + Cabs2cil.allowDuplication := false; + Cabs2cil.silenceLongDoubleWarning := true let current_file = ref dummyFile +(** @raise GoblintCil.FrontC.ParseError + @raise GoblintCil.Errormsg.Error *) let parse fileName = let fileName_str = Fpath.to_string fileName in + Errormsg.hadErrors := false; (* reset because CIL doesn't *) let cabs2cil = Timing.wrap ~args:[("file", `String fileName_str)] "FrontC" Frontc.parse fileName_str in - Timing.wrap ~args:[("file", `String fileName_str)] "Cabs2cil" cabs2cil () + let file = Timing.wrap ~args:[("file", `String fileName_str)] "Cabs2cil" cabs2cil () in + if !E.hadErrors then + E.s (E.error "There were parsing errors in %s" fileName_str); + file let print (fileAST: file) = dumpFile defaultCilPrinter stdout "stdout" fileAST let rmTemps fileAST = - Rmtmps.removeUnusedTemps fileAST + RmUnused.removeUnused fileAST let visitors = ref [] @@ -60,6 +88,8 @@ let do_preprocess ast = iterGlobals ast (function GFun (fd,_) -> List.iter (f fd) !visitors | _ -> ()) +(** @raise GoblintCil.FrontC.ParseError + @raise GoblintCil.Errormsg.Error *) let getAST fileName = let fileAST = parse fileName in (* rmTemps fileAST; *) @@ -90,7 +120,9 @@ class addConstructors cons = object method! vtype _ = SkipChildren end +(** @raise GoblintCil.Errormsg.Error *) let getMergedAST fileASTs = + Errormsg.hadErrors := false; (* reset because CIL doesn't *) let merged = Timing.wrap "mergeCIL" (Mergecil.merge fileASTs) "stdout" in if !E.hadErrors then E.s (E.error "There were errors during merging\n"); @@ -137,7 +169,7 @@ let getFuns fileAST : startfuns = Printf.printf "Start function: %s\n" mn; set_string "mainfun[+]" mn; add_main def acc | GFun({svar={vname=mn; vattr=attr; _}; _} as def, _) when get_bool "kernel" && is_exit attr -> Printf.printf "Cleanup function: %s\n" mn; set_string "exitfun[+]" mn; add_exit def acc - | GFun ({svar={vstorage=NoStorage; _}; _} as def, _) when (get_bool "nonstatic") -> add_other def acc + | GFun ({svar={vstorage=NoStorage; vattr; _}; _} as def, _) when get_bool "nonstatic" && not (Cil.hasAttribute "goblint_stub" vattr) -> add_other def acc | GFun ({svar={vattr; _}; _} as def, _) when get_bool "allfuns" && not (Cil.hasAttribute "goblint_stub" vattr) -> add_other def acc | _ -> acc in @@ -201,13 +233,25 @@ let typeOfRealAndImagComponents t = | FFloat -> FFloat (* [float] *) | FDouble -> FDouble (* [double] *) | FLongDouble -> FLongDouble (* [long double] *) + | FFloat128 -> FFloat128 (* [float128] *) | FComplexFloat -> FFloat | FComplexDouble -> FDouble | FComplexLongDouble -> FLongDouble + | FComplexFloat128 -> FComplexFloat128 in TFloat (newfkind fkind, attrs) | _ -> raise (TypeOfError RealImag_NonNumerical) +let isComplexFKind = function + | FFloat + | FDouble + | FLongDouble + | FFloat128 -> false + | FComplexFloat + | FComplexDouble + | FComplexLongDouble + | FComplexFloat128 -> true + let rec typeOf (e: exp) : typ = match e with | Const(CInt (_, ik, _)) -> TInt(ik, []) @@ -280,6 +324,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 = @@ -300,6 +353,119 @@ 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 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' = 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: " ^ 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 @@ -423,6 +589,8 @@ let varinfo_roles: varinfo_role VarinfoH.t ResettableLazy.t = VarinfoH.replace h fd.svar Function; (* function itself can be used as a variable (function pointer) *) List.iter (fun vi -> VarinfoH.replace h vi (Formal fd)) fd.sformals; List.iter (fun vi -> VarinfoH.replace h vi (Local fd)) fd.slocals + | GVarDecl (vi, _) when Cil.isFunctionType vi.vtype -> + VarinfoH.replace h vi Function | GVar (vi, _, _) | GVarDecl (vi, _) -> VarinfoH.replace h vi Global @@ -474,6 +642,31 @@ let original_names: string VarinfoH.t ResettableLazy.t = If it was inserted by CIL (or Goblint), then returns [None]. *) let find_original_name vi = VarinfoH.find_opt (ResettableLazy.force original_names) vi (* vi argument must be explicit, otherwise force happens immediately *) +module IntH = Hashtbl.Make (struct type t = int [@@deriving eq, hash] end) + +class stmtSidVisitor h = object + inherit nopCilVisitor + method! vstmt s = + IntH.replace h s.sid s; + DoChildren +end + +let stmt_sids: stmt IntH.t ResettableLazy.t = + ResettableLazy.from_fun (fun () -> + let h = IntH.create 113 in + let visitor = new stmtSidVisitor h in + visitCilFileSameGlobals visitor !current_file; + h + ) + +let pseudo_return_stmt_sids: stmt IntH.t = IntH.create 13 + +(** Find [stmt] by its [sid]. + @raise Not_found *) +let find_stmt_sid sid = + try IntH.find pseudo_return_stmt_sids sid + with Not_found -> IntH.find (ResettableLazy.force stmt_sids) sid + let reset_lazy () = StmtH.clear pseudo_return_to_fun; @@ -481,7 +674,8 @@ let reset_lazy () = ResettableLazy.reset varinfo_fundecs; ResettableLazy.reset name_fundecs; ResettableLazy.reset varinfo_roles; - ResettableLazy.reset original_names + ResettableLazy.reset original_names; + ResettableLazy.reset stmt_sids let stmt_pretty_short () x = 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/gobConfig.ml b/src/common/util/gobConfig.ml similarity index 97% rename from src/util/gobConfig.ml rename to src/common/util/gobConfig.ml index c3553431ac..c517ba150d 100644 --- a/src/util/gobConfig.ml +++ b/src/common/util/gobConfig.ml @@ -1,3 +1,5 @@ +(** Configuration access. *) + (** New, untyped, path-based configuration subsystem. @@ -18,7 +20,7 @@ There is a "conf" [trace] option that traces setting. *) -open Prelude +open Batteries open Tracing open Printf @@ -387,7 +389,7 @@ struct (** Merge configurations form a file with current. *) let merge_file fn = let cwd = Fpath.v (Sys.getcwd ()) in - let config_dirs = cwd :: Goblint_sites.conf in + let config_dirs = cwd :: Fpath.(parent (v Sys.executable_name)) :: Goblint_sites.conf in let file = List.find_map_opt (fun custom_include_dir -> let path = Fpath.append custom_include_dir fn in if Sys.file_exists (Fpath.to_string path) then @@ -407,3 +409,12 @@ end include Impl let () = set_conf Options.defaults + + +(** Another hack to see if earlyglobs is enabled *) +let earlyglobs = ref false + +let jobs () = + match get_int "jobs" with + | 0 -> Cpu.numcores () + | n -> n diff --git a/src/util/gobFormat.ml b/src/common/util/gobFormat.ml similarity index 92% rename from src/util/gobFormat.ml rename to src/common/util/gobFormat.ml index a489e92a88..11beec524c 100644 --- a/src/util/gobFormat.ml +++ b/src/common/util/gobFormat.ml @@ -18,4 +18,4 @@ let pp_set_ansi_color_tags ppf = Format.pp_set_formatter_stag_functions ppf stag_functions'; Format.pp_set_mark_tags ppf true -let pp_print_nothing ppf () = () +let pp_print_nothing (ppf: Format.formatter) () = () diff --git a/src/util/jsonSchema.ml b/src/common/util/jsonSchema.ml similarity index 99% rename from src/util/jsonSchema.ml rename to src/common/util/jsonSchema.ml index 63295eb126..701c948f3a 100644 --- a/src/util/jsonSchema.ml +++ b/src/common/util/jsonSchema.ml @@ -1,4 +1,4 @@ -open Prelude +(** JSON schema validation. *) module JS = Json_schema.Make (Json_repr.Yojson) module JE = Json_encoding.Make (Json_repr.Yojson) diff --git a/src/util/lazyEval.ml b/src/common/util/lazyEval.ml similarity index 68% rename from src/util/lazyEval.ml rename to src/common/util/lazyEval.ml index e6c85cf9b2..9007cdd089 100644 --- a/src/util/lazyEval.ml +++ b/src/common/util/lazyEval.ml @@ -1,11 +1,14 @@ +(** Lazy evaluation with a fixed function. + Allows marshaling. *) + (* Lazy eval extracted here to avoid dependency cycle: 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 @@ -17,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 85% rename from src/util/messageCategory.ml rename to src/common/util/messageCategory.ml index ef8ee5d6a9..c70b8faf5f 100644 --- a/src/util/messageCategory.ml +++ b/src/common/util/messageCategory.ml @@ -11,7 +11,12 @@ type undefined_behavior = | ArrayOutOfBounds of array_oob | NullPointerDereference | UseAfterFree + | MemoryOutOfBoundsAccess + | DoubleFree + | InvalidMemoryDeallocation + | MemoryLeak | Uninitialized + | DoubleLocking | Other [@@deriving eq, ord, hash] @@ -62,7 +67,12 @@ 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 module ArrayOutOfBounds = @@ -97,7 +107,11 @@ 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 + | "double_locking" -> double_locking | "other" -> other | _ -> Unknown @@ -106,7 +120,12 @@ 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"] end @@ -214,7 +233,12 @@ 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" | ArrayOutOfBounds aob -> match aob with | PastEnd -> "PastEnd" @@ -236,8 +260,8 @@ let categoryName = function | Behavior x -> behaviorName x | Integer x -> (match x with - | Overflow -> "Overflow"; - | DivByZero -> "DivByZero") + | Overflow -> "Overflow"; + | DivByZero -> "DivByZero") | Float -> "Float" diff --git a/src/util/messageUtil.ml b/src/common/util/messageUtil.ml similarity index 96% rename from src/util/messageUtil.ml rename to src/common/util/messageUtil.ml index e1edc2d5be..17651fb05f 100644 --- a/src/util/messageUtil.ml +++ b/src/common/util/messageUtil.ml @@ -1,3 +1,5 @@ +(** Terminal color utilities. *) + open GobConfig let ansi_color_table = diff --git a/src/util/messages.ml b/src/common/util/messages.ml similarity index 74% rename from src/util/messages.ml rename to src/common/util/messages.ml index da66c7ae60..42a3118978 100644 --- a/src/util/messages.ml +++ b/src/common/util/messages.ml @@ -1,7 +1,8 @@ +(** Messages (e.g. warnings) from the analysis. *) + module Pretty = GoblintCil.Pretty open GobConfig -module GU = Goblintutil module Category = MessageCategory @@ -63,18 +64,22 @@ struct type t = { loc: Location.t option; (* only *_each warnings have this, used for deduplication *) text: string; - context: (Obj.t [@equal fun x y -> Hashtbl.hash (Obj.obj x) = Hashtbl.hash (Obj.obj y)] [@compare fun x y -> Stdlib.compare (Hashtbl.hash (Obj.obj x)) (Hashtbl.hash (Obj.obj y))] [@hash fun x -> Hashtbl.hash (Obj.obj x)] [@to_yojson fun x -> `Int (Hashtbl.hash (Obj.obj x))] [@of_yojson fun x -> Result.Ok Goblintutil.dummy_obj]) option; (* TODO: this equality is terrible... *) + context: (ControlSpecC.t [@of_yojson fun x -> Result.Error "ControlSpecC"]) option; } [@@deriving eq, ord, hash, yojson] let text_with_context {text; context; _} = match context with - | Some context when GobConfig.get_bool "dbg.warn_with_context" -> text ^ " in context " ^ string_of_int (Hashtbl.hash context) (* TODO: this is kind of useless *) + | Some context when GobConfig.get_bool "dbg.warn_with_context" -> text ^ " in context " ^ string_of_int (ControlSpecC.hash context) (* TODO: this is kind of useless *) | _ -> text end module MultiPiece = struct - type group = {group_text: string; pieces: Piece.t list} [@@deriving eq, ord, hash, yojson] + type group = { + group_text: string; + group_loc: Location.t option; + pieces: Piece.t list; + } [@@deriving eq, ord, hash, yojson] type t = | Single of Piece.t | Group of group @@ -172,6 +177,8 @@ let () = AfterConfig.register (fun () -> let xml_file_name = ref "" +(** The file where everything is output *) +let out = ref stdout let get_out name alternative = match get_string "dbg.dump" with | "" -> alternative @@ -187,9 +194,12 @@ let print ?(ppf= !formatter) (m: Message.t) = | Success -> "green" in let pp_prefix = Format.dprintf "@{<%s>[%a]%a@}" severity_stag Severity.pp m.severity Tags.pp m.tags in + let pp_loc ppf = Format.fprintf ppf " @{(%a)@}" CilType.Location.pp in + let pp_loc ppf loc = + Format.fprintf ppf "%a" (Format.pp_print_option pp_loc) (Option.map Location.to_cil loc) + in let pp_piece ppf piece = - let pp_loc ppf = Format.fprintf ppf " @{(%a)@}" CilType.Location.pp in - Format.fprintf ppf "@{<%s>%s@}%a" severity_stag (Piece.text_with_context piece) (Format.pp_print_option pp_loc) (Option.map Location.to_cil piece.loc) + Format.fprintf ppf "@{<%s>%s@}%a" severity_stag (Piece.text_with_context piece) pp_loc piece.loc in let pp_quote ppf (loc: GoblintCil.location) = let lines = BatFile.lines_of loc.file in @@ -214,32 +224,50 @@ let print ?(ppf= !formatter) (m: Message.t) = | _ -> assert false end in - let pp_piece ppf piece = + let pp_quote ppf loc = if get_bool "warn.quote-code" then ( let pp_cut_quote ppf = Format.fprintf ppf "@,@[%a@,@]" pp_quote in - Format.fprintf ppf "%a%a" pp_piece piece (Format.pp_print_option pp_cut_quote) (Option.map Location.to_cil piece.loc) + (Format.pp_print_option pp_cut_quote) ppf (Option.map Location.to_cil loc) ) - else - pp_piece ppf piece in + let pp_piece ppf piece = Format.fprintf ppf "%a%a" pp_piece piece pp_quote piece.loc in let pp_multipiece ppf = match m.multipiece with | Single piece -> pp_piece ppf piece - | Group {group_text; pieces} -> + | Group {group_text; group_loc; pieces} -> let pp_piece2 ppf = Format.fprintf ppf "@[%a@]" pp_piece in (* indented box for quote *) - Format.fprintf ppf "@{<%s>%s:@}@,@[%a@]" severity_stag group_text (Format.pp_print_list pp_piece2) pieces + Format.fprintf ppf "@{<%s>%s:@}%a%a@,@[%a@]" severity_stag group_text pp_loc group_loc pp_quote group_loc (Format.pp_print_list pp_piece2) pieces in Format.fprintf ppf "@[%t %t@]\n%!" pp_prefix pp_multipiece let add m = if not (Table.mem m) then ( - print m; + if not (get_bool "warn.deterministic") then + print 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: Obj.t option ref = ref None (** (Control.get_spec ()) context, represented type: (Control.get_spec ()).C.t *) +let current_context: ControlSpecC.t option ref = ref None let msg_context () = if GobConfig.get_bool "dbg.warn_with_context" then @@ -248,9 +276,9 @@ let msg_context () = None (* avoid identical messages from multiple contexts without any mention of context *) let msg severity ?loc ?(tags=[]) ?(category=Category.Unknown) fmt = - if !GU.should_warn && Severity.should_warn severity && (Category.should_warn category || Tags.should_warn tags) then ( + if !AnalysisState.should_warn && Severity.should_warn severity && (Category.should_warn category || Tags.should_warn tags) then ( let finish doc = - let text = Pretty.sprint ~width:max_int doc in + let text = GobPretty.show doc in let loc = match loc with | Some node -> Some node | None -> Option.map (fun node -> Location.Node node) !Node0.current_node @@ -260,33 +288,33 @@ let msg severity ?loc ?(tags=[]) ?(category=Category.Unknown) fmt = Pretty.gprintf finish fmt ) else - Tracing.mygprintf () fmt + GobPretty.igprintf () fmt let msg_noloc severity ?(tags=[]) ?(category=Category.Unknown) fmt = - if !GU.should_warn && Severity.should_warn severity && (Category.should_warn category || Tags.should_warn tags) then ( + if !AnalysisState.should_warn && Severity.should_warn severity && (Category.should_warn category || Tags.should_warn tags) then ( let finish doc = - let text = Pretty.sprint ~width:max_int doc in - add {tags = Category category :: tags; severity; multipiece = Single {loc = None; text; context = msg_context ()}} + let text = GobPretty.show doc in + add {tags = Category category :: tags; severity; multipiece = Single {loc = None; text; context = None}} in Pretty.gprintf finish fmt ) else - Tracing.mygprintf () fmt + GobPretty.igprintf () fmt -let msg_group severity ?(tags=[]) ?(category=Category.Unknown) fmt = - if !GU.should_warn && Severity.should_warn severity && (Category.should_warn category || Tags.should_warn tags) then ( +let msg_group severity ?loc ?(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 msgs = - let group_text = Pretty.sprint ~width:max_int doc in + let group_text = GobPretty.show doc in let piece_of_msg (doc, loc) = - let text = Pretty.sprint ~width:max_int doc in + let text = GobPretty.show doc in Piece.{loc; text; context = None} in - add {tags = Category category :: tags; severity; multipiece = Group {group_text; pieces = List.map piece_of_msg msgs}} + add {tags = Category category :: tags; severity; multipiece = Group {group_text; group_loc = loc; pieces = List.map piece_of_msg msgs}} in Pretty.gprintf finish fmt ) else - Tracing.mygprintf (fun msgs -> ()) fmt + GobPretty.igprintf (fun msgs -> ()) fmt (* must eta-expand to get proper (non-weak) polymorphism for format *) let warn ?loc = msg Warning ?loc @@ -300,4 +328,15 @@ let debug_noloc ?tags = msg_noloc Debug ?tags let success ?loc = msg Success ?loc let success_noloc ?tags = msg_noloc Success ?tags +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 Tracing diff --git a/src/util/options.ml b/src/common/util/options.ml similarity index 98% rename from src/util/options.ml rename to src/common/util/options.ml index 7fb6cabae9..3046f70809 100644 --- a/src/util/options.ml +++ b/src/common/util/options.ml @@ -1,3 +1,5 @@ +(** [src/common/util/options.schema.json] low-level access. *) + open Json_schema let schema = diff --git a/src/util/options.schema.json b/src/common/util/options.schema.json similarity index 91% rename from src/util/options.schema.json rename to src/common/util/options.schema.json index c3346677d6..4e282b19a4 100644 --- a/src/util/options.schema.json +++ b/src/common/util/options.schema.json @@ -155,7 +155,7 @@ "gobview": { "title": "gobview", "description": - "Include additional information for Gobview (e.g., the Goblint warning messages) in the directory specified by 'save_run'.", + "Include additional information for GobView (e.g., the Goblint warning messages) in the directory specified by 'save_run'.", "type": "boolean", "default": false }, @@ -176,6 +176,12 @@ "description": "Preprocessing options", "type": "object", "properties": { + "enabled": { + "title": "pre.enabled", + "description": "Run the C preprocessor.", + "type": "boolean", + "default": true + }, "keep": { "title": "pre.keep", "description": @@ -242,6 +248,12 @@ } }, "additionalProperties": false + }, + "transform-paths": { + "title": "pre.transform-paths", + "description": "Normalize and relativize paths in parsed CIL locations. Can cause issues locating YAML witness invariants due to differing paths.", + "type": "boolean", + "default": true } }, "additionalProperties": false @@ -276,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 @@ -334,7 +352,7 @@ "description": "List of path-sensitive analyses", "type": "array", "items": { "type": "string" }, - "default": [ "mutex", "malloc_null", "uninit", "expsplit" ] + "default": [ "mutex", "malloc_null", "uninit", "expsplit","activeSetjmp","memLeak" ] }, "ctx_insens": { "title": "ana.ctx_insens", @@ -343,6 +361,21 @@ "items": { "type": "string" }, "default": [ "stack_loc", "stack_trace_set" ] }, + "setjmp" : { + "title": "ana.setjmp", + "description": "Setjmp/Longjmp analysis", + "type": "object", + "properties": { + "split": { + "title": "ana.setjmp.split", + "description": "Split returns of setjmp", + "type": "string", + "enum": ["none", "coarse", "precise"], + "default": "precise" + } + }, + "additionalProperties": false + }, "int": { "title": "ana.int", "type": "object", @@ -357,7 +390,13 @@ "interval": { "title": "ana.int.interval", "description": - "Use IntDomain.Interval32: (int64 * int64) option.", + "Use IntDomain.Interval32: (Z.t * Z.t) option.", + "type": "boolean", + "default": false + }, + "interval_set": { + "title": "ana.int.interval_set", + "description": "Use IntDomain.IntervalSet: (Z.t * Z.t) list.", "type": "boolean", "default": false }, @@ -489,20 +528,6 @@ }, "additionalProperties": false }, - "mutex": { - "title": "ana.mutex", - "type": "object", - "properties": { - "disjoint_types": { - "title": "ana.mutex.disjoint_types", - "description": - "Do not propagate basic type writes to all struct fields", - "type": "boolean", - "default": true - } - }, - "additionalProperties": false - }, "autotune": { "title": "ana.autotune", "type": "object", @@ -519,7 +544,7 @@ "type": "array", "items": { "type": "string" }, "default": [ - "congruence", "singleThreaded", "specification", "mallocWrappers", "noRecursiveIntervals", "enums", "loopUnrollHeuristic", "arrayDomain", "octagon", "wideningThresholds" + "congruence", "singleThreaded", "specification", "mallocWrappers", "noRecursiveIntervals", "enums", "loopUnrollHeuristic", "arrayDomain", "octagon", "wideningThresholds", "memsafetySpecification" ] } }, @@ -589,6 +614,13 @@ "Integer values of the Interval domain in function contexts.", "type": "boolean", "default": true + }, + "interval_set": { + "title": "ana.base.context.interval_set", + "description": + "Integer values of the IntervalSet domain in function contexts.", + "type": "boolean", + "default": true } }, "additionalProperties": false @@ -935,6 +967,33 @@ "Whether the node at which a thread is created is part of its threadid", "type": "boolean", "default" : true + }, + "wrappers": { + "title": "ana.thread.wrappers", + "description": + "Loads a list of known thread spawn (pthread_create) wrapper functions.", + "type": "array", + "items": { "type": "string" }, + "default": [] + }, + "unique_thread_id_count": { + "title": "ana.thread.unique_thread_id_count", + "description": "Number of unique thread IDs allocated for each pthread_create node.", + "type": "integer", + "default": 0 + }, + "context": { + "title": "ana.thread.context", + "type": "object", + "properties": { + "create-edges": { + "title": "ana.thread.context.create-edges", + "description": "threadID analysis: Encountered create edges in context.", + "type": "boolean", + "default": true + } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -948,6 +1007,18 @@ "description": "Consider memory free as racing write.", "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.", + "type": "boolean", + "default": false + }, + "volatile" :{ + "title": "ana.race.volatile", + "description": "Report races for volatile variables.", + "type": "boolean", + "default": true } }, "additionalProperties": false @@ -1098,6 +1169,12 @@ "enum": ["ast", "cfg"], "default": "ast" }, + "detect-renames": { + "title": "incremental.detect-renames", + "description": "If Goblint should try to detect renamed local variables, function parameters, functions and global variables", + "type":"boolean", + "default": true + }, "force-reanalyze": { "title": "incremental.force-reanalyze", "type": "object", @@ -1191,6 +1268,48 @@ }, "additionalProperties": false }, + "lib": { + "title": "Library functions", + "description": "Options for library functions", + "type": "object", + "properties": { + "activated": { + "title": "lib.activated", + "description": "List of activated libraries.", + "type": "array", + "items": { + "type": "string", + "enum": [ + "c", + "posix", + "pthread", + "gcc", + "glibc", + "linux-userspace", + "linux-kernel", + "goblint", + "sv-comp", + "ncurses", + "zstd", + "pcre", + "zlib", + "liblzma" + ] + }, + "default": [ + "c", + "posix", + "pthread", + "gcc", + "glibc", + "linux-userspace", + "goblint", + "ncurses" + ] + } + }, + "additionalProperties": false + }, "sem": { "title": "Semantics", "description": "Options for semantics", @@ -1207,6 +1326,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", @@ -1357,7 +1483,10 @@ "description": "Lists of activated transformations. Transformations happen after analyses.", "type": "array", - "items": { "type": "string" }, + "items": { + "type": "string", + "enum": ["partial", "expeval", "assert", "remove_dead_code"] + }, "default": [] }, "expeval": { @@ -1430,7 +1559,7 @@ "type": "array", "items": { "type": "string", - "enum": ["base.no-non-ptr", "base.non-ptr", "base.no-int", "base.int", "base.no-interval", "base.interval", "relation.no-context", "relation.context", "no-widen", "widen"] + "enum": ["base.no-non-ptr", "base.non-ptr", "base.no-int", "base.int", "base.no-interval", "base.no-interval_set","base.interval", "base.interval_set","relation.no-context", "relation.context", "no-widen", "widen"] }, "default": [] } @@ -1569,9 +1698,9 @@ }, "g2html_path": { "title": "exp.g2html_path", - "description": "Location of the g2html.jar file.", + "description": "Location of the g2html.jar file. If empty, then goblint executable directory is used.", "type": "string", - "default": "." + "default": "" }, "extraspecials": { "title": "exp.extraspecials", @@ -1597,7 +1726,7 @@ "fast_global_inits": { "title": "exp.fast_global_inits", "description": - "Only generate one 'a[MyCFG.all_array_index_exp] = x' for all assignments a[...] = x for a global array a[n].", + "Only generate one 'a[any_index] = x' for all assignments a[...] = x for a global array a[n].", "type": "boolean", "default": true }, @@ -1634,6 +1763,18 @@ "description": "Hide standard extern globals (e.g. `stdout`) from cluttering local states.", "type": "boolean", "default": true + }, + "arg": { + "title": "exp.arg", + "description": "Construct abstract reachability graph (ARG).", + "type": "boolean", + "default": false + }, + "argdot": { + "title": "exp.argdot", + "description": "Output ARG as dot file.", + "type": "boolean", + "default": false } }, "additionalProperties": false @@ -1643,12 +1784,6 @@ "description": "Debugging options", "type": "object", "properties": { - "debug": { - "title": "dbg.debug", - "description": "Debug mode: for testing the analyzer itself.", - "type": "boolean", - "default": false - }, "verbose": { "title": "dbg.verbose", "description": "Prints some status information.", @@ -1716,14 +1851,14 @@ "solver-signal": { "title": "dbg.solver-signal", "description": - "Signal to print statistics while solving. Possible values: sigint (Ctrl+C), sigtstp (Ctrl+Z), sigquit (Ctrl+\\), sigusr1, sigusr2, sigalrm, sigprof etc. (see signal_of_string in goblintutil.ml).", + "Signal to print statistics while solving. Possible values: sigint (Ctrl+C), sigtstp (Ctrl+Z), sigquit (Ctrl+\\), sigusr1, sigusr2, sigalrm, sigprof etc. (see signal_of_string in gobSys.ml).", "type": "string", "default": "sigusr1" }, "backtrace-signal": { "title": "dbg.backtrace-signal", "description": - "Signal to print a raw backtrace on stderr. Possible values: sigint (Ctrl+C), sigtstp (Ctrl+Z), sigquit (Ctrl+\\), sigusr1, sigusr2, sigalrm, sigprof etc. (see signal_of_string in goblintutil.ml).", + "Signal to print a raw backtrace on stderr. Possible values: sigint (Ctrl+C), sigtstp (Ctrl+Z), sigquit (Ctrl+\\), sigusr1, sigusr2, sigalrm, sigprof etc. (see signal_of_string in gobSys.ml).", "type": "string", "default": "sigusr2" }, @@ -2017,6 +2152,12 @@ "description": "Races with confidence at least threshold are warnings, lower are infos.", "type": "integer", "default": 0 + }, + "deterministic": { + "title": "warn.deterministic", + "description": "Output messages in deterministic order. Useful for cram testing.", + "type": "boolean", + "default": false } }, "additionalProperties": false @@ -2138,24 +2279,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", @@ -2192,7 +2365,7 @@ "title": "witness.invariant.accessed", "description": "Only emit invariants for locally accessed variables", "type": "boolean", - "default": true + "default": false }, "full": { "title": "witness.invariant.full", @@ -2225,35 +2398,16 @@ "cond", "RETURN" ] + }, + "goblint": { + "title": "witness.invariant.goblint", + "description": "Emit non-standard Goblint-specific invariants. Currently array invariants with all_index offsets.", + "type": "boolean", + "default": false } }, "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", 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 64% rename from src/util/resettableLazy.mli rename to src/common/util/resettableLazy.mli index f4103a86dd..5b0db478bb 100644 --- a/src/util/resettableLazy.mli +++ b/src/common/util/resettableLazy.mli @@ -1,3 +1,5 @@ +(** Lazy type which can be reset to a closure. *) + type 'a t val from_fun: (unit -> 'a) -> 'a t diff --git a/src/util/richVarinfo.ml b/src/common/util/richVarinfo.ml similarity index 94% rename from src/util/richVarinfo.ml rename to src/common/util/richVarinfo.ml index 839dc83a97..d1918c40a6 100644 --- a/src/util/richVarinfo.ml +++ b/src/common/util/richVarinfo.ml @@ -1,6 +1,6 @@ open GoblintCil -let create_var name = Goblintutil.create_var @@ makeGlobalVar name voidType +let create_var name = Cilfacade.create_var @@ makeGlobalVar name voidType let single ~name = let vi = lazy (create_var name) in @@ -14,6 +14,7 @@ sig val to_varinfo : t -> varinfo val unmarshal: marshal option -> unit val marshal: unit -> marshal + val bindings: unit -> (t * varinfo) list end module type G = @@ -59,6 +60,8 @@ struct | Some xh_loaded -> xh := xh_loaded | None -> () + + let bindings () = List.of_seq (XH.to_seq !xh) end (* module to maintain bidirectional mappings between some type t and varinfo. @@ -134,6 +137,8 @@ struct M.unmarshal (Some xh_loaded); vh := vh_loaded | None -> () + + let bindings = M.bindings end (** Create a BiVarinfoMap and register it in the collection *) diff --git a/src/util/richVarinfo.mli b/src/common/util/richVarinfo.mli similarity index 86% rename from src/util/richVarinfo.mli rename to src/common/util/richVarinfo.mli index 50aeb90bed..4e682734ee 100644 --- a/src/util/richVarinfo.mli +++ b/src/common/util/richVarinfo.mli @@ -1,3 +1,5 @@ +(** Custom {!GoblintCil.varinfo} management. *) + open GoblintCil val single: name:string -> (unit -> varinfo) @@ -9,6 +11,7 @@ sig val to_varinfo : t -> varinfo val unmarshal: marshal option -> unit val marshal: unit -> marshal + val bindings: unit -> (t * varinfo) list end module type G = @@ -41,6 +44,7 @@ sig sig val mem_varinfo : varinfo -> bool val describe_varinfo : varinfo -> string + val mappings: (module S) list ref end module Make: diff --git a/src/util/timing.ml b/src/common/util/timing.ml similarity index 80% rename from src/util/timing.ml rename to src/common/util/timing.ml index d276a6f2f0..d5db4664aa 100644 --- a/src/util/timing.ml +++ b/src/common/util/timing.ml @@ -1,3 +1,5 @@ +(** Time measurement of computations. *) + module Default = Goblint_timing.Make (struct let name = "Default" end) module Program = Goblint_timing.Make (struct let name = "Program" end) diff --git a/src/util/tracing.ml b/src/common/util/tracing.ml similarity index 61% rename from src/util/tracing.ml rename to src/common/util/tracing.ml index ea1183ac98..ad8892c396 100644 --- a/src/util/tracing.ml +++ b/src/common/util/tracing.ml @@ -1,3 +1,5 @@ +(** Nested tracing system for debugging. *) + (* TRACING STUFF. A rewrite of Cil's tracing framework which is too slow for the * large domains we output. The original code generated the document object * even when the subsystem is not activated. *) @@ -8,7 +10,7 @@ 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 @@ -31,56 +33,6 @@ let indent_level = ref 0 let traceIndent () = indent_level := !indent_level + 2 let traceOutdent () = indent_level := !indent_level - 2 -(* Parses a format string to generate a nop-function of the correct type. *) -let mygprintf (finish: 'b) (format : ('a, unit, doc, 'b) format4) : 'a = - let format = string_of_format format in - let flen = String.length format in - let fget = String.unsafe_get format in - let rec literal acc i = - let rec skipChars j = - if j >= flen || (match fget j with '%' | '@' | '\n' -> true | _ -> false) then - collect nil j - else - skipChars (succ j) - in - skipChars (succ i) - and collect (acc: doc) (i: int) = - if i >= flen then begin - Obj.magic finish - end else begin - let c = fget i in - if c = '%' then begin - let j = skip_args (succ i) in - match fget j with - '%' -> literal acc j - | ',' -> collect acc (succ j) - | 's' | 'c' | 'd' | 'i' | 'o' | 'x' | 'X' | 'u' - | 'f' | 'e' | 'E' | 'g' | 'G' | 'b' | 'B' -> - Obj.magic(fun b -> collect nil (succ j)) - | 'L' | 'l' | 'n' -> Obj.magic(fun n -> collect nil (succ (succ j))) - | 'a' -> Obj.magic(fun pprinter arg -> collect nil (succ j)) - | 't' -> Obj.magic(fun pprinter -> collect nil (succ j)) - | c -> invalid_arg ("dprintf: unknown format %s" ^ String.make 1 c) - end else if c = '@' then begin - if i + 1 < flen then begin - match fget (succ i) with - '[' | ']' | '!' | '?' | '^' | '@' -> collect nil (i + 2) - | '<' | '>' -> collect nil (i + 1) - | c -> invalid_arg ("dprintf: unknown format @" ^ String.make 1 c) - end else - invalid_arg "dprintf: incomplete format @" - end else if c = '\n' then begin - collect nil (i + 1) - end else - literal acc i - end - and skip_args j = - match String.unsafe_get format j with - '0' .. '9' | ' ' | '.' | '-' -> skip_args (succ j) - | c -> j - in - collect nil 0 - let traceTag (sys : string) : Pretty.doc = let rec ind (i : int) : string = if (i <= 0) then "" else " " ^ (ind (i-1)) in (text ((ind !indent_level) ^ "%%% " ^ sys ^ ": ")) @@ -104,7 +56,7 @@ let gtrace always f sys var ?loc do_subsys fmt = do_subsys (); gprintf (f sys) fmt end else - mygprintf () fmt + GobPretty.igprintf () fmt let trace sys ?var fmt = gtrace true printtrace sys var ignore fmt diff --git a/src/util/xmlUtil.ml b/src/common/util/xmlUtil.ml similarity index 96% rename from src/util/xmlUtil.ml rename to src/common/util/xmlUtil.ml index a0cc4bc982..e33be1b215 100644 --- a/src/util/xmlUtil.ml +++ b/src/common/util/xmlUtil.ml @@ -1,3 +1,5 @@ +(** XML utilities. *) + (* XML escape extracted here to avoid dependency cycle: CilType -> Goblintutil -> GobConfig -> Tracing -> Node -> CilType *) diff --git a/src/domains/abstractionDomainProperties.ml b/src/domains/abstractionDomainProperties.ml index 208e433e86..1772af0b6b 100644 --- a/src/domains/abstractionDomainProperties.ml +++ b/src/domains/abstractionDomainProperties.ml @@ -1,3 +1,5 @@ +(** QCheck properties for abstract operations. *) + module type AbstractFunction = sig type c diff --git a/src/domains/access.ml b/src/domains/access.ml index e11f9a512d..3ba7aaee74 100644 --- a/src/domains/access.ml +++ b/src/domains/access.ml @@ -1,3 +1,5 @@ +(** Memory accesses and their manipulation. *) + open Batteries open GoblintCil open Pretty @@ -8,36 +10,140 @@ 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 = "lock_class_key"; _ }, _) -> true - | TInt (IInt, attr) when hasAttribute "mutex" attr -> true - | t when hasAttribute "atomic" (typeAttrs t) -> true (* C11 _Atomic *) +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, 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 + | "lock_class_key" -> true (* kernel? *) | _ -> false -let is_ignorable = function - | None -> false - | 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) -let typeVar = Hashtbl.create 101 -let typeIncl = Hashtbl.create 101 -let unsound = ref false +let typeVar = TSH.create 101 +let typeIncl = TSH.create 101 +let collect_direct_arithmetic = ref false let init (f:file) = - unsound := get_bool "ana.mutex.disjoint_types"; + collect_direct_arithmetic := get_bool "ana.race.direct-arithmetic"; let visited_vars = Hashtbl.create 100 in + let add tsh t v = + let rec add' ts = + TSH.add tsh ts v; + (* Account for aliasing to any level of array. + See 06-symbeq/50-type_array_via_ptr_rc.c. *) + match ts with + | TSArray (ts', _, _) -> add' ts' + | _ -> () + in + if not (is_ignorable_type t) then + add' (typeSig t) + in let visit_field fi = - Hashtbl.add typeIncl (typeSig fi.ftype) fi + (* TODO: is_ignorable_type? *) + (* TODO: Direct ignoring doesn't really work since it doesn't account for pthread inner structs/unions being only reachable via ignorable types. *) + add typeIncl fi.ftype fi in let visit_glob = function | GCompTag (c,_) -> - List.iter visit_field c.cfields + if not (is_ignorable_type (TComp (c, []))) then + List.iter visit_field c.cfields | GVarDecl (v,_) | GVar (v,_,_) -> if not (Hashtbl.mem visited_vars v.vid) then begin - Hashtbl.add typeVar (typeSig v.vtype) v; + (* TODO: is_ignorable? *) + add typeVar v.vtype v; (* ignore (printf "init adding %s : %a" v.vname d_typsig ((typeSig v.vtype))); *) Hashtbl.replace visited_vars v.vid true end @@ -46,57 +152,87 @@ let init (f:file) = List.iter visit_glob f.globals let reset () = - Hashtbl.clear typeVar; - Hashtbl.clear typeIncl + TSH.clear typeVar; + TSH.clear typeIncl + +type acc_typ = [ `Type of CilType.Typ.t | `Struct of CilType.Compinfo.t * Offset.Unit.t ] [@@deriving eq, ord, hash] +(** Old access type inferred from an expression. *) + +module MemoRoot = +struct + include Printable.StdLeaf + type t = [`Var of CilType.Varinfo.t | `Type of CilType.Typsig.t] [@@deriving eq, ord, hash] + + let name () = "memoroot" + let pretty () vt = + (* Imitate old printing for now *) + match vt with + | `Var v -> CilType.Varinfo.pretty () v + | `Type (TSComp (_, name, _)) -> Pretty.dprintf "(struct %s)" name + | `Type t -> Pretty.dprintf "(%a)" Cilfacade.pretty_typsig_like_typ t -type offs = [`NoOffset | `Index of offs | `Field of CilType.Fieldinfo.t * offs] [@@deriving eq, ord, hash] + include Printable.SimplePretty ( + struct + type nonrec t = t + let pretty = pretty + end + ) +end -let rec remove_idx : offset -> offs = function - | NoOffset -> `NoOffset - | Index (_,o) -> `Index (remove_idx o) - | Field (f,o) -> `Field (f, remove_idx o) +(** Memory location of an access. *) +module Memo = +struct + include Printable.StdLeaf + type t = MemoRoot.t * Offset.Unit.t [@@deriving eq, ord, hash] + + let name () = "memo" -let rec addOffs os1 os2 : offs = - match os1 with - | `NoOffset -> os2 - | `Index os -> `Index (addOffs os os2) - | `Field (f,os) -> `Field (f, addOffs os os2) + let pretty () (vt, o) = + (* Imitate old printing for now *) + match vt with + | `Var v -> Pretty.dprintf "%a%a" CilType.Varinfo.pretty v 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 -let rec d_offs () : offs -> doc = function - | `NoOffset -> nil - | `Index o -> dprintf "[?]%a" d_offs o - | `Field (f,o) -> dprintf ".%s%a" f.fname d_offs o + include Printable.SimplePretty ( + struct + type nonrec t = t + let pretty = pretty + end + ) -type acc_typ = [ `Type of CilType.Typ.t | `Struct of CilType.Compinfo.t * offs ] [@@deriving eq, ord, hash] + let of_ty (ty: acc_typ): t = + match ty with + | `Struct (c, o) -> (`Type (TSComp (c.cstruct, c.cname, [])), o) + | `Type t -> (`Type (Cil.typeSig t), `NoOffset) -let d_acct () = function - | `Type t -> dprintf "(%a)" d_type t - | `Struct (s,o) -> dprintf "(struct %s)%a" s.cname d_offs o + let to_mval: t -> Mval.Unit.t option = function + | (`Var v, o) -> Some (v, o) + | (`Type _, _) -> None -let d_memo () (t, lv) = - match lv with - | Some (v,o) -> dprintf "%a%a@@%a" Basetype.Variables.pretty v d_offs o CilType.Location.pretty v.vdecl - | None -> dprintf "%a" d_acct t + let add_offset ((vt, o): t) o2: t = (vt, Offset.Unit.add_offset o o2) +end -let rec get_type (fb: typ) : exp -> acc_typ = function +(* TODO: What is the logic for get_type? *) +let rec get_type (fb: typ Lazy.t) : exp -> acc_typ = function | AddrOf (h,o) | StartOf (h,o) -> let rec f htyp = match htyp with - | TComp (ci,_) -> `Struct (ci,remove_idx o) + | TComp (ci,_) -> `Struct (ci, Offset.Unit.of_cil o) | TNamed (ti,_) -> f ti.ttype - | _ -> `Type fb + | _ -> `Type (Lazy.force fb) (* TODO: Why fb not htyp? *) in begin match o with - | Field (f, on) -> `Struct (f.fcomp, remove_idx o) + | Field (f, on) -> `Struct (f.fcomp, Offset.Unit.of_cil o) | NoOffset | Index _ -> begin match h with | Var v -> f (v.vtype) - | Mem e -> f fb + | Mem e -> f (Lazy.force fb) (* TODO: type of Mem doesn't have to be the fallback type if offsets present? *) end end | SizeOf _ | SizeOfE _ | SizeOfStr _ | AlignOf _ | AlignOfE _ | AddrOfLabel _ -> - `Type (uintType) + `Type (uintType) (* TODO: Correct types from typeOf? *) | UnOp (_,_,t) -> `Type t | BinOp (_,_,_,t) -> `Type t | CastE (t,e) -> @@ -107,7 +243,7 @@ let rec get_type (fb: typ) : exp -> acc_typ = function | Question (_,b,c,t) -> begin match get_type fb b, get_type fb c with | `Struct (s1,o1), `Struct (s2,o2) - when CilType.Compinfo.equal s1 s2 && equal_offs o1 o2 -> + when CilType.Compinfo.equal s1 s2 && Offset.Unit.equal o1 o2 -> `Struct (s1, o1) | _ -> `Type t end @@ -115,122 +251,77 @@ let rec get_type (fb: typ) : exp -> acc_typ = function | Lval _ | Real _ | Imag _ -> - `Type fb (* TODO: is this right? *) + `Type (Lazy.force fb) (* TODO: is this right? *) let get_type fb e = (* printf "e = %a\n" d_plainexp e; *) let r = get_type fb e in (* printf "result = %a\n" d_acct r; *) match r with - | `Type (TPtr (t,a)) -> `Type t - | x -> x + | `Type (TPtr (t,a)) -> `Type t (* Why this special case? Almost always taken if not `Struct. *) + | x -> x (* Mostly for `Struct, but also rare cases with non-pointer `Type. Should they happen at all? *) +let get_val_type e: acc_typ = + let fb = lazy ( + try Cilfacade.typeOf e + with Cilfacade.TypeOfError _ -> voidType (* Why is this a suitable default? *) + ) + in + get_type fb e + + +(** Add access to {!Memo} after distributing. *) +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 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 actual access for non-recursive call, or empty access for recursive call when side is side_empty. *) + + (* distribute to variables of the type *) + let vars = TSH.find_all typeVar ts in + List.iter (fun v -> + (* 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: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 ~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 + | 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 *) + let (t, o) = match ty with (* convert acc_typ to type-based Memo (components) *) + | `Struct (c, o) -> (TComp (c, []), o) + | `Type t -> (t, `NoOffset) + in + match o with + | `NoOffset when not !collect_direct_arithmetic && isArithmeticType t -> () + | _ -> 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" -type var_o = varinfo option -type off_o = offset option - -let get_val_type e (vo: var_o) (oo: off_o) : acc_typ = - match Cilfacade.typeOf e with - | t -> - begin match vo, oo with - | Some v, Some o -> get_type t (AddrOf (Var v, o)) - | Some v, None -> get_type t (AddrOf (Var v, NoOffset)) - | _ -> get_type t e - end - | exception (Cilfacade.TypeOfError _) -> get_type voidType e - -let add_one side (e:exp) (kind:AccessKind.t) (conf:int) (ty:acc_typ) (lv:(varinfo*offs) option) a: unit = - if is_ignorable lv then () else begin - let loc = Option.get !Node.current_node in - side ty lv (conf, kind, loc, e, a) - end - -let type_from_type_offset : acc_typ -> typ = function - | `Type t -> t - | `Struct (s,o) -> - let deref t = - match unrollType t with - | TPtr (t,_) -> t (*?*) - | TArray (t,_,_) -> t - | _ -> failwith "type_from_type_offset: indexing non-pointer type" - in - let rec type_from_offs (t,o) = - match o with - | `NoOffset -> t - | `Index os -> type_from_offs (deref t, os) - | `Field (f,os) -> type_from_offs (f.ftype, os) - in - unrollType (type_from_offs (TComp (s, []), o)) - -let add_struct side (e:exp) (kind:AccessKind.t) (conf:int) (ty:acc_typ) (lv: (varinfo * offs) option) a: unit = - let rec dist_fields ty = - match unrollType ty with - | TComp (ci,_) -> - let one_field fld = - if is_ignorable_type fld.ftype then - [] - else - List.map (fun x -> `Field (fld,x)) (dist_fields fld.ftype) - in - List.concat_map one_field ci.cfields - | TArray (t,_,_) -> - List.map (fun x -> `Index x) (dist_fields t) - | _ -> [`NoOffset] - in - match ty with - | `Struct (s,os2) -> - let add_lv os = match lv with - | Some (v, os1) -> Some (v, addOffs os1 os) - | None -> None - in - begin try - let oss = dist_fields (type_from_type_offset ty) in - List.iter (fun os -> add_one side e kind conf (`Struct (s,addOffs os2 os)) (add_lv os) a) oss - with Failure _ -> - add_one side e kind conf ty lv a - end - | _ when lv = None && !unsound -> - (* don't recognize accesses to locations such as (long ) and (int ). *) - () - | _ -> - add_one side e kind conf ty lv a - -let add_propagate side e kind conf ty ls a = - (* ignore (printf "%a:\n" d_exp e); *) - let rec only_fields = function - | `NoOffset -> true - | `Field (_,os) -> only_fields os - | `Index _ -> false - in - let struct_inv (f:offs) = - let fi = - match f with - | `Field (fi,_) -> fi - | _ -> failwith "add_propagate: no field found" - in - let ts = typeSig (TComp (fi.fcomp,[])) in - let vars = Hashtbl.find_all typeVar ts in - (* List.iter (fun v -> ignore (printf " * %s : %a" v.vname d_typsig ts)) vars; *) - let add_vars v = add_struct side e kind conf (`Struct (fi.fcomp, f)) (Some (v, f)) a in - List.iter add_vars vars; - add_struct side e kind conf (`Struct (fi.fcomp, f)) None a; - in - let just_vars t v = - add_struct side e kind conf (`Type t) (Some (v, `NoOffset)) a; - in - add_struct side e kind conf ty None a; - match ty with - | `Struct (c,os) when only_fields os && os <> `NoOffset -> - (* ignore (printf " * type is a struct\n"); *) - struct_inv os - | _ -> - (* ignore (printf " * type is NOT a struct\n"); *) - let t = type_from_type_offset ty in - let incl = Hashtbl.find_all typeIncl (typeSig t) in - List.iter (fun fi -> struct_inv (`Field (fi,`NoOffset))) incl; - let vars = Hashtbl.find_all typeVar (typeSig t) in - List.iter (just_vars t) vars +(** Distribute to {!AddrOf} of all read lvals in subexpressions. *) let rec distribute_access_lval f lv = (* Use unoptimized AddrOf so RegionDomain.Reg.eval_exp knows about dereference *) @@ -282,24 +373,31 @@ and distribute_access_exp f = function distribute_access_exp f b; distribute_access_exp f t; distribute_access_exp f e + + | SizeOf t -> + distribute_access_type f t + | Const _ - | SizeOf _ | SizeOfStr _ | AlignOf _ | AddrOfLabel _ -> () -let add side e kind conf vo oo a = - let ty = get_val_type e vo oo in - (* let loc = !Tracing.current_loc in *) - (* ignore (printf "add %a %b -- %a\n" d_exp e w d_loc loc); *) - match vo, oo with - | Some v, Some o -> add_struct side e kind conf ty (Some (v, remove_idx o)) a - | _ -> - if !unsound && isArithmeticType (type_from_type_offset ty) then - add_struct side e kind conf ty None a - else - add_propagate side e kind conf ty None a +and distribute_access_type f = function + | TArray (et, len, _) -> + Option.may (distribute_access_exp f) len; + distribute_access_type f et + + | TVoid _ + | TInt _ + | TFloat _ + | TPtr _ + | TFun _ + | TNamed _ + | TComp _ + | TEnum _ + | TBuiltin_va_list _ -> + () (* Access table as Lattice. *) @@ -307,10 +405,18 @@ let add side e kind conf vo oo a = 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 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 name () = "access" + + 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 @@ -319,95 +425,146 @@ struct end ) - let conf (conf, _, _, _, _) = conf + let relift {conf; kind; node; exp; acc} = + {conf; kind; node; exp; acc = MCPAccess.A.relift acc} end + module AS = 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 -module T = -struct - include Printable.Std - type t = acc_typ [@@deriving eq, ord, hash] - let pretty = d_acct - include Printable.SimplePretty ( - struct - type nonrec t = t - let pretty = pretty - end - ) -end -module O = -struct - include Printable.Std - type t = offs [@@deriving eq, ord, hash] - let pretty = d_offs - include Printable.SimplePretty ( - struct - type nonrec t = t - let pretty = pretty - end - ) +(** 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 + | _, _ -> MCPAccess.A.may_race acc acc2 (* analysis-specific information excludes race *) + +(** 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 -module LV = Printable.Prod (CilType.Varinfo) (O) -module LVOpt = Printable.Option (LV) (struct let name = "NONE" 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 -let group_may_race accs = +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' accs visited todo = - let accs' = AS.diff accs todo in - let todo' = AS.fold (fun acc todo' -> - AS.fold (fun acc' todo' -> - if may_race acc acc' then - AS.add acc' todo' - else - todo' - ) accs' todo' - ) todo (AS.empty ()) + 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 + AS.add acc' todo' + else + todo' + ) accs todo' + ) todo (AS.empty ()) 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]) accs' visited' todo' + (bfs' [@tailcall]) warn_accs' ~todo:todo' ~visited:visited' in - let bfs accs acc = bfs' accs (AS.empty ()) (AS.singleton acc) 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 ) @@ -417,7 +574,7 @@ let race_conf accs = let is_all_safe = ref true (* Commenting your code is for the WEAK! *) -let incr_summary safe vulnerable unsafe (lv, ty) grouped_accs = +let incr_summary ~safe ~vulnerable ~unsafe grouped_accs = (* ignore(printf "Checking safety of %a:\n" d_memo (ty,lv)); *) let safety = grouped_accs @@ -432,24 +589,21 @@ let incr_summary safe vulnerable unsafe (lv, ty) grouped_accs = | Some n when n >= 100 -> is_all_safe := false; incr unsafe | Some n -> is_all_safe := false; incr vulnerable -let print_accesses (lv, ty) grouped_accs = +let print_accesses memo grouped_accs = let allglobs = get_bool "allglobs" in - let debug = get_bool "dbg.debug" 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 = - if debug then - dprintf "%t (exp: %a)" d_msg d_exp e - else - d_msg () - 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 |> List.map h in + let group_loc = match memo with + | (`Var v, _) -> Some (M.Location.CilLocation v.vdecl) (* TODO: offset location *) + | (`Type _, _) -> None (* TODO: type location *) + in grouped_accs |> List.fold_left (fun safe_accs accs -> match race_conf accs with @@ -462,15 +616,15 @@ let print_accesses (lv, ty) grouped_accs = else Info in - M.msg_group severity ~category:Race "Memory location %a (race with conf. %d)" d_memo (ty,lv) conf (msgs accs); + M.msg_group severity ?loc:group_loc ~category:Race "Memory location %a (race with conf. %d)" Memo.pretty memo conf (msgs accs); safe_accs ) (AS.empty ()) |> (fun safe_accs -> if allglobs && not (AS.is_empty safe_accs) then - M.msg_group Success ~category:Race "Memory location %a (safe)" d_memo (ty,lv) (msgs safe_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 g accs = - let grouped_accs = group_may_race accs in (* do expensive component finding only once *) - incr_summary safe vulnerable unsafe g grouped_accs; - print_accesses g grouped_accs +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/accessDomain.ml b/src/domains/accessDomain.ml index 8ea79b6166..5884214976 100644 --- a/src/domains/accessDomain.ml +++ b/src/domains/accessDomain.ml @@ -1,8 +1,10 @@ +(** Domain for memory accesses. *) + open GoblintCil.Pretty module Event = struct - include Printable.Std + include Printable.StdLeaf type t = { var_opt: CilType.Varinfo.t option; (** Access varinfo (unknown if None). *) offs_opt: CilType.Offset.t option; (** Access offset (unknown if None). *) diff --git a/src/domains/accessKind.ml b/src/domains/accessKind.ml index dbaeec0f2f..b36e8f3eca 100644 --- a/src/domains/accessKind.ml +++ b/src/domains/accessKind.ml @@ -1,7 +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. *) @@ -10,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/domains/boolDomain.ml b/src/domains/boolDomain.ml index 4fe060a961..e088c3605c 100644 --- a/src/domains/boolDomain.ml +++ b/src/domains/boolDomain.ml @@ -1,3 +1,5 @@ +(** Boolean domains. *) + module Bool = struct include Basetype.RawBools diff --git a/src/domains/disjointDomain.ml b/src/domains/disjointDomain.ml index cb7a361384..d8e59c4ba7 100644 --- a/src/domains/disjointDomain.ml +++ b/src/domains/disjointDomain.ml @@ -27,21 +27,21 @@ end Common choices for [B] are {!SetDomain.Joined} and {!HoareDomain.SetEM}. Handles {!Lattice.BotValue} from [B]. *) -module ProjectiveSet (E: Printable.S) (B: SetDomain.S with type elt = E.t) (R: Representative with type elt = E.t): SetDomain.S with type elt = E.t = +module ProjectiveSet (E: Printable.S) (B: SetDomain.S with type elt = E.t) (R: Representative with type elt = E.t): +sig + include SetDomain.S with type elt = E.t + val fold_buckets: (R.t -> B.t -> 'a -> 'a) -> t -> 'a -> 'a +end += struct type elt = E.t - module R = - struct - include Printable.Std (* for Groupable *) - include R - end module M = MapDomain.MapBot (R) (B) (** Invariant: no explicit bot buckets. Required for efficient [is_empty], [cardinal] and [choose]. *) - let name () = "Projective (" ^ B.name () ^ ")" + let name () = "ProjectiveSet (" ^ B.name () ^ ")" (* explicitly delegate, so we don't accidentally delegate too much *) @@ -174,6 +174,36 @@ struct let min_elt m = SetDomain.unsupported "Projective.min_elt" let max_elt m = SetDomain.unsupported "Projective.max_elt" let disjoint m1 m2 = is_empty (inter m1 m2) (* TODO: optimize? *) + + let fold_buckets = M.fold +end + +module type MayEqualSetDomain = +sig + include SetDomain.S + val may_be_equal: elt -> elt -> bool +end + +module ProjectiveSetPairwiseMeet (E: Printable.S) (B: MayEqualSetDomain with type elt = E.t) (R: Representative with type elt = E.t): SetDomain.S with type elt = E.t = struct + include ProjectiveSet (E) (B) (R) + + let meet m1 m2 = + let meet_buckets b1 b2 acc = + B.fold (fun e1 acc -> + B.fold (fun e2 acc -> + if B.may_be_equal e1 e2 then + add e1 (add e2 acc) + else + acc + ) b2 acc + ) b1 acc + in + fold_buckets (fun _ b1 acc -> + fold_buckets (fun _ b2 acc -> + meet_buckets b1 b2 acc + ) m2 acc + ) m1 (empty ()) + end (** {2 By congruence} *) @@ -427,6 +457,168 @@ module CombinedSet (E: Printable.S) (B: SetDomain.S with type elt = E.t) (RC: Re Generalization of above sets into maps, whose key set behaves like above sets, but each element can also be associated with a value. *) +(** {2 By projection} *) + +(** Map of keys [E.t] grouped into buckets by [R], + where each bucket is described by the map [B] with values [V.t]. + + Common choice for [B] is {!MapDomain.Joined}. + + Handles {!Lattice.BotValue} from [B]. *) +module ProjectiveMap (E: Printable.S) (V: Printable.S) (B: MapDomain.S with type key = E.t and type value = V.t) (R: Representative with type elt = E.t): MapDomain.S with type key = E.t and type value = B.value = +struct + type key = E.t + type value = B.value + + module M = MapDomain.MapBot (R) (B) + + (** Invariant: no explicit bot buckets. + Required for efficient [is_empty], [cardinal] and [choose]. *) + + let name () = "ProjectiveMap (" ^ B.name () ^ ")" + + (* explicitly delegate, so we don't accidentally delegate too much *) + + type t = M.t + let equal = M.equal + let compare = M.compare + let hash = M.hash + let tag = M.tag + let relift = M.relift + + let is_bot = M.is_bot + let bot = M.bot + let is_top = M.is_top + let top = M.top + + let is_empty = M.is_empty + let empty = M.empty + let cardinal = M.cardinal + + let leq = M.leq + let join = M.join + let pretty_diff = M.pretty_diff + + let fold f m a = M.fold (fun _ e a -> B.fold f e a) m a + let iter f m = M.iter (fun _ e -> B.iter f e) m + let exists p m = M.exists (fun _ e -> B.exists p e) m + let for_all p m = M.for_all (fun _ e -> B.for_all p e) m + + let singleton e v = M.singleton (R.of_elt e) (B.singleton e v) + let choose m = B.choose (snd (M.choose m)) + + let mem e m = + match M.find_opt (R.of_elt e) m with + | Some b -> B.mem e b + | None -> false + let find e m = + let r = R.of_elt e in + let b = M.find r m in (* raises Not_found *) + B.find e b (* raises Not_found *) + let find_opt e m = + let r = R.of_elt e in + match M.find_opt r m with + | Some b -> + B.find_opt e b + | None -> None + let add e v m = + let r = R.of_elt e in + let b' = match M.find_opt r m with + | Some b -> B.add e v b + | None -> B.singleton e v + in + M.add r b' m + let remove e m = + let r = R.of_elt e in + match M.find_opt r m with + | Some b -> + begin match B.remove e b with + | b' when B.is_bot b' -> + M.remove r m (* remove bot bucket to preserve invariant *) + | exception Lattice.BotValue -> + M.remove r m (* remove bot bucket to preserve invariant *) + | b' -> + M.add r b' m + end + | None -> m + + let add_list evs m = List.fold_left (fun acc (e, v) -> + add e v acc + ) m evs + let add_list_set es v m = List.fold_left (fun acc e -> + add e v acc + ) m es + let add_list_fun es f m = List.fold_left (fun acc e -> + add e (f e) acc + ) m es + let bindings m = fold (fun e v acc -> (e, v) :: acc) m [] (* no intermediate per-bucket lists *) + + let map f m = M.map (fun b -> + B.map f b + ) m + let mapi f m = M.map (fun b -> + B.mapi f b + ) m + let long_map2 f m1 m2 = M.long_map2 (fun b1 b2 -> + B.long_map2 f b1 b2 + ) m1 m2 + let map2 f m1 m2 = M.map2 (fun b1 b2 -> + B.map2 f b1 b2 + ) m1 m2 + let merge f m1 m2 = failwith "ProjectiveMap.merge" (* TODO: ? *) + + let widen m1 m2 = + Lattice.assert_valid_widen ~leq ~pretty_diff m1 m2; + M.widen m1 m2 + + let meet m1 m2 = + M.merge (fun _ b1 b2 -> + match b1, b2 with + | Some b1, Some b2 -> + begin match B.meet b1 b2 with + | b' when B.is_bot b' -> + None (* remove bot bucket to preserve invariant *) + | exception Lattice.BotValue -> + None (* remove bot bucket to preserve invariant *) + | b' -> + Some b' + end + | _, _ -> None + ) m1 m2 + let narrow m1 m2 = + M.merge (fun _ b1 b2 -> + match b1, b2 with + | Some b1, Some b2 -> + begin match B.narrow b1 b2 with + | b' when B.is_bot b' -> + None (* remove bot bucket to preserve invariant *) + | exception Lattice.BotValue -> + None (* remove bot bucket to preserve invariant *) + | b' -> + Some b' + end + | _, _ -> None + ) m1 m2 + + include MapDomain.Print (E) (V) ( + struct + type nonrec t = t + type nonrec key = key + type nonrec value = value + let fold = fold + let iter = iter + end + ) + + let arbitrary () = failwith "ProjectiveMap.arbitrary" + + let filter p m = failwith "ProjectiveMap.filter" + + let leq_with_fct _ _ _ = failwith "ProjectiveMap.leq_with_fct" + let join_with_fct _ _ _ = failwith "ProjectiveMap.join_with_fct" + let widen_with_fct _ _ _ = failwith "ProjectiveMap.widen_with_fct" +end + (** {2 By congruence} *) (** Map of keys [E.t] grouped into buckets by [C], @@ -666,17 +858,12 @@ struct in snd (S.fold f s2 (s1, S.empty ())) - module GroupableE = - struct - include Printable.Std (* for Groupable *) - include E - end - include MapDomain.Print (GroupableE) (R) ( + include MapDomain.Print (E) (R) ( struct type nonrec t = t type nonrec key = key type nonrec value = value - let bindings = bindings + let fold = fold let iter = iter end ) diff --git a/src/domains/domainProperties.ml b/src/domains/domainProperties.ml index 38f32ea059..b2f0f7671a 100644 --- a/src/domains/domainProperties.ml +++ b/src/domains/domainProperties.ml @@ -1,3 +1,5 @@ +(** QCheck properties for lattice properties. *) + open QCheck module DomainTest (D: Lattice.S) = diff --git a/src/domains/events.ml b/src/domains/events.ml index d2fba2abb7..06561bddbe 100644 --- a/src/domains/events.ml +++ b/src/domains/events.ml @@ -1,4 +1,7 @@ -open Prelude.Ana +(** Events. *) + +open GoblintCil +open Pretty type t = | Lock of LockDomain.Lockset.Lock.t @@ -7,11 +10,12 @@ 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 | Unassume of {exp: CilType.Exp.t; uuids: string list} + | Longjmped of {lval: CilType.Lval.t option} (** Should event be emitted after transfer function raises [Deadcode]? *) let emit_on_deadcode = function @@ -26,7 +30,8 @@ let emit_on_deadcode = function | Assign _ | UpdateExpSplit _ (* Pointless to split on dead. *) | Unassume _ (* Avoid spurious writes. *) - | Assert _ -> (* Pointless to refine dead. *) + | Assert _ (* Pointless to refine dead. *) + | Longjmped _ -> false let pretty () = function @@ -36,8 +41,9 @@ 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 | Unassume {exp; uuids} -> dprintf "Unassume {exp=%a; uuids=%a}" d_exp exp (docList Pretty.text) uuids + | Longjmped {lval} -> dprintf "Longjmped {lval=%a}" (docOpt (CilType.Lval.pretty ())) lval diff --git a/src/domains/flagHelper.ml b/src/domains/flagHelper.ml index f9ea023d08..c3bcb584b2 100644 --- a/src/domains/flagHelper.ml +++ b/src/domains/flagHelper.ml @@ -1,3 +1,5 @@ +(** Domain alternatives chosen by a runtime flag. *) + module type FlagError = sig val msg: string val name: string @@ -6,7 +8,7 @@ end module FlagHelper (L:Printable.S) (R:Printable.S) (Msg: FlagError) = struct - type t = L.t option * R.t option + type t = L.t option * R.t option [@@deriving eq, ord, hash] let unop opl opr (h,r) = match (h, r) with | (Some l, None) -> opl l @@ -28,9 +30,6 @@ struct | (None, Some t1), (None, Some t2) -> (None, Some(opr t1 t2)) | _ -> failwith Msg.msg - let equal = binop L.equal R.equal - let hash = unop L.hash R.hash - let compare = binop L.compare R.compare let show = unop L.show R.show let pretty () = unop (L.pretty ()) (R.pretty ()) let printXml f = unop (L.printXml f) (R.printXml f) @@ -41,28 +40,6 @@ struct let arbitrary () = failwith (Msg.name ^ ": no arbitrary") end -module GroupableFlagHelper (L:MapDomain.Groupable) (R:MapDomain.Groupable) (Msg: FlagError) = -struct - include FlagHelper (L) (R) (Msg) - type group = L.group option * R.group option - - let trace_enabled = false - - let show_group = unop L.show_group R.show_group - let to_group (h,p) = match (h, p) with - | (Some h, None) -> - (let r = L.to_group h in - match r with - | Some r -> Some (Some r, None) - | _ -> None) - | (None, Some p) -> - (let r = R.to_group p in - match r with - | Some r -> Some (None, Some r) - | _ -> None) - | _ -> failwith Msg.msg -end - module type LatticeFlagHelperArg = sig include Lattice.PO val is_top: t -> bool diff --git a/src/domains/hoareDomain.ml b/src/domains/hoareDomain.ml index f14cabf6dc..23b1a92240 100644 --- a/src/domains/hoareDomain.ml +++ b/src/domains/hoareDomain.ml @@ -255,12 +255,7 @@ end (* TODO: weaken R to Lattice.S ? *) module MapBot (SpecD:Lattice.S) (R:SetDomain.S) = struct - module SpecDGroupable = - struct - include Printable.Std - include SpecD - end - include MapDomain.MapBot (SpecDGroupable) (R) + include MapDomain.MapBot (SpecD) (R) (* TODO: get rid of these value-ignoring set-mimicing hacks *) let choose' = choose diff --git a/src/domains/intDomainProperties.ml b/src/domains/intDomainProperties.ml index 15c1bcb357..9dcb867efc 100644 --- a/src/domains/intDomainProperties.ml +++ b/src/domains/intDomainProperties.ml @@ -1,3 +1,5 @@ +(** QCheck properties for {!IntDomain}. *) + open GoblintCil module BI = IntOps.BigIntOps @@ -61,7 +63,7 @@ struct let top () = top_of (Ik.ikind ()) let is_top = is_top_of (Ik.ikind ()) - let name () = Pretty.(sprint ~width:80 (dprintf "%s (%a)" (name ()) Cil.d_ikind (Ik.ikind ()))) + let name () = GobPretty.sprintf "%s (%a)" (name ()) Cil.d_ikind (Ik.ikind ()) let arbitrary () = arbitrary (Ik.ikind ()) end diff --git a/src/domains/invariant.ml b/src/domains/invariant.ml index ff50aa801e..1a0c3c033c 100644 --- a/src/domains/invariant.ml +++ b/src/domains/invariant.ml @@ -1,3 +1,5 @@ +(** Invariants for witnesses. *) + open GoblintCil (** Symbolic (and fully syntactic) expression "lattice". *) @@ -41,10 +43,10 @@ let ( || ) = join type context = { path: int option; - lvals: CilLval.Set.t; + lvals: Lval.Set.t; } let default_context = { path = None; - lvals = CilLval.Set.top (); + lvals = Lval.Set.top (); } diff --git a/src/domains/invariantCil.ml b/src/domains/invariantCil.ml index 2e647f6920..8a1d8f0745 100644 --- a/src/domains/invariantCil.ml +++ b/src/domains/invariantCil.ml @@ -1,3 +1,5 @@ +(** Invariant manipulation related to CIL transformations. *) + open GoblintCil diff --git a/src/domains/mapDomain.ml b/src/domains/mapDomain.ml index b8d0e2ef4d..76dec6f0d2 100644 --- a/src/domains/mapDomain.ml +++ b/src/domains/mapDomain.ml @@ -1,9 +1,7 @@ -(** Specification and functors for maps. *) +(** Map domains. *) module Pretty = GoblintCil.Pretty open Pretty -module ME = Messages -module GU = Goblintutil module type PS = sig @@ -56,69 +54,89 @@ sig (* Leq test using a custom leq function for value rather than the default one provided for value *) end -module type Groupable = -sig - include Printable.S - type group (* use [@@deriving show { with_path = false }] *) - val show_group: group -> string - val to_group: t -> group option - val trace_enabled: bool (* Just a global hack for tracing individual variables. *) -end - (** Subsignature of {!S}, which is sufficient for {!Print}. *) module type Bindings = sig type t type key type value - val bindings: t -> (key * value) list + val fold: (key -> value -> 'a -> 'a) -> t -> 'a -> 'a val iter: (key -> value -> unit) -> t -> unit end (** Reusable output definitions for maps. *) -module Print (D: Groupable) (R: Printable.S) (M: Bindings with type key = D.t and type value = R.t) = +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 doc = M.fold (fun k v acc -> + acc ++ dprintf "%a ->@?@[%a@]\n" D.pretty k R.pretty v + ) map nil + in + if doc = Pretty.nil then + text "{}" + else + dprintf "@[{\n @[%a@]}@]" Pretty.insert doc + + let show map = GobPretty.sprint pretty map + + let printXml f map = + BatPrintf.fprintf f "\n\n"; + M.iter (fun k v -> + BatPrintf.fprintf f "\n%s\n%a" (XmlUtil.escape (D.show k)) R.printXml v + ) map; + BatPrintf.fprintf f "\n\n" + + let to_yojson map = + let l = M.fold (fun k v acc -> + (D.show k, R.to_yojson v) :: acc + ) map [] + in + `Assoc l +end + +module type Groupable = +sig + include Printable.S + type group (* use [@@deriving show { with_path = false }] *) + val compare_group: group -> group -> int + val show_group: group -> string + val to_group: t -> group +end + +(** Reusable output definitions for maps with key grouping. *) +module PrintGroupable (D: Groupable) (R: Printable.S) (M: Bindings with type key = D.t and type value = R.t) = struct - let show x = "mapping" (* TODO: WTF? *) + include Print (D) (R) (M) + + module Group = + struct + type t = D.group + let compare = D.compare_group + end + + module GroupMap = Map.Make (Group) let pretty () mapping = - let module MM = Map.Make (D) in let groups = - let h = Hashtbl.create 13 in - M.iter (fun k v -> BatHashtbl.modify_def MM.empty (D.to_group k) (MM.add k v) h) mapping; - let cmpBy f a b = Stdlib.compare (f a) (f b) in - (* sort groups (order of constructors in type group) *) - BatHashtbl.to_list h |> List.sort (cmpBy fst) - in - let f key st dok = - if ME.tracing && D.trace_enabled && !ME.tracevars <> [] && - not (List.mem (D.show key) !ME.tracevars) then - dok - else - dok ++ dprintf "%a ->@? @[%a@]\n" D.pretty key R.pretty st + M.fold (fun k v acc -> + GroupMap.update (D.to_group k) (fun doc -> + let doc = Option.value doc ~default:Pretty.nil in + let doc' = doc ++ dprintf "%a ->@? @[%a@]\n" D.pretty k R.pretty v in + Some doc' + ) acc + ) mapping GroupMap.empty in - let group_name a () = text (D.show_group a) in - let pretty_group map () = MM.fold f map nil in - let pretty_groups rest (group, map) = - match group with - | None -> rest ++ pretty_group map () - | Some g -> rest ++ dprintf "@[%t {\n @[%t@]}@]\n" (group_name g) (pretty_group map) in - let content () = List.fold_left pretty_groups nil groups in - dprintf "@[%s {\n @[%t@]}@]" (show mapping) content - - let printXml f xs = - let print_one k v = - BatPrintf.fprintf f "\n%s\n%a" (XmlUtil.escape (D.show k)) R.printXml v - in - BatPrintf.fprintf f "\n\n"; - M.iter print_one xs; - BatPrintf.fprintf f "\n\n" + let pretty_groups () = GroupMap.fold (fun group doc acc -> + acc ++ dprintf "@[%s {\n @[%a@]}@]\n" (D.show_group group) Pretty.insert doc + ) groups nil in + dprintf "@[{\n @[%t@]}@]" pretty_groups + + let show map = GobPretty.sprint pretty map - let to_yojson xs = - let f (k, v) = (D.show k, R.to_yojson v) in - `Assoc (xs |> M.bindings |> List.map f) + (* TODO: groups in XML, JSON? *) end -module PMap (Domain: Groupable) (Range: Lattice.S) : PS with +module PMap (Domain: Printable.S) (Range: Lattice.S) : PS with type key = Domain.t and type value = Range.t = struct @@ -130,6 +148,8 @@ struct type value = Range.t type t = Range.t M.t (* key -> value mapping *) + let name () = "map" + (* And one less brainy definition *) let for_all2 = M.equal let equal x y = x == y || for_all2 Range.equal x y @@ -173,7 +193,7 @@ struct type nonrec t = t type nonrec key = key type nonrec value = value - let bindings = bindings + let fold = fold let iter = iter end ) @@ -182,6 +202,11 @@ struct (* let add k v m = let _ = Pretty.printf "%a\n" pretty m in M.add k v m *) let arbitrary () = QCheck.always M.empty (* S TODO: non-empty map *) + + let relift m = + M.fold (fun k v acc -> + M.add (Domain.relift k) (Range.relift v) acc + ) m M.empty end (* TODO: why is HashCached.hash significantly slower as a functor compared to being inlined into PMap? *) @@ -230,7 +255,7 @@ struct let join_with_fct f = lift_f2' (M.join_with_fct f) let widen_with_fct f = lift_f2' (M.widen_with_fct f) - let relift x = x + let relift = lift_f' M.relift end (* TODO: this is very slow because every add/remove in a fold-loop relifts *) @@ -357,7 +382,7 @@ struct let relift x = M.relift x end -module MapBot (Domain: Groupable) (Range: Lattice.S) : S with +module MapBot (Domain: Printable.S) (Range: Lattice.S) : S with type key = Domain.t and type value = Range.t = struct @@ -379,17 +404,15 @@ struct let is_bot = is_empty let pretty_diff () ((m1:t),(m2:t)): Pretty.doc = - let p key value = - not (try Range.leq value (find key m2) with Not_found -> false) - in - let report key v1 v2 = - Pretty.dprintf "Map: %a =@?@[%a@]" - Domain.pretty key Range.pretty_diff (v1,v2) - in - let diff_key k v = function - | None when p k v -> Some (report k v (find k m2)) - | Some w when p k v -> Some (w++Pretty.line++report k v (find k m2)) - | x -> x + let diff_key k v acc_opt = + match find k m2 with + | v2 when not (Range.leq v v2) -> + let acc = BatOption.map_default (fun acc -> acc ++ line) Pretty.nil acc_opt in + Some (acc ++ dprintf "Map: %a =@?@[%a@]" Domain.pretty k Range.pretty_diff (v, v2)) + | exception Lattice.BotValue -> + let acc = BatOption.map_default (fun acc -> acc ++ line) Pretty.nil acc_opt in + Some (acc ++ dprintf "Map: %a =@?@[%a not leq bot@]" Domain.pretty k Range.pretty v) + | v2 -> acc_opt in match fold diff_key m1 None with | Some w -> w @@ -408,7 +431,7 @@ struct let narrow = map2 Range.narrow end -module MapTop (Domain: Groupable) (Range: Lattice.S) : S with +module MapTop (Domain: Printable.S) (Range: Lattice.S) : S with type key = Domain.t and type value = Range.t = struct @@ -443,17 +466,15 @@ struct let narrow = long_map2 Range.narrow let pretty_diff () ((m1:t),(m2:t)): Pretty.doc = - let p key value = - not (try Range.leq (find key m1) value with Not_found -> false) - in - let report key v1 v2 = - Pretty.dprintf "Map: %a =@?@[%a@]" - Domain.pretty key Range.pretty_diff (v1,v2) - in - let diff_key k v = function - | None when p k v -> Some (report k (find k m1) v) - | Some w when p k v -> Some (w++Pretty.line++report k (find k m1) v) - | x -> x + let diff_key k v acc_opt = + match find k m1 with + | v1 when not (Range.leq v1 v) -> + let acc = BatOption.map_default (fun acc -> acc ++ line) Pretty.nil acc_opt in + Some (acc ++ dprintf "Map: %a =@?@[%a@]" Domain.pretty k Range.pretty_diff (v1, v)) + | exception Lattice.TopValue -> + let acc = BatOption.map_default (fun acc -> acc ++ line) Pretty.nil acc_opt in + Some (acc ++ dprintf "Map: %a =@?@[top not leq %a@]" Domain.pretty k Range.pretty v) + | v1 -> acc_opt in match fold diff_key m2 None with | Some w -> w @@ -581,7 +602,7 @@ struct | `Lifted x -> `Lifted (M.mapi f x) end -module MapBot_LiftTop (Domain: Groupable) (Range: Lattice.S) : S with +module MapBot_LiftTop (Domain: Printable.S) (Range: Lattice.S) : S with type key = Domain.t and type value = Range.t = struct @@ -709,7 +730,7 @@ struct | `Lifted x -> `Lifted (M.mapi f x) end -module MapTop_LiftBot (Domain: Groupable) (Range: Lattice.S): S with +module MapTop_LiftBot (Domain: Printable.S) (Range: Lattice.S): S with type key = Domain.t and type value = Range.t = struct diff --git a/src/domains/partitionDomain.ml b/src/domains/partitionDomain.ml index 1cf453011d..9675e9bfce 100644 --- a/src/domains/partitionDomain.ml +++ b/src/domains/partitionDomain.ml @@ -27,6 +27,9 @@ struct let (s1', res) = fold f s2 (s1, empty ()) in union s1' res + (* TODO: inter-based meet is unsound? *) + let meet _ _ = failwith "PartitonDomain.Set.meet: unsound" + let collapse (s1:t) (s2:t): bool = let f vf2 res = res || exists (fun vf1 -> S.collapse vf1 vf2) s1 @@ -112,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/queries.ml b/src/domains/queries.ml index 089512d387..c706339bf2 100644 --- a/src/domains/queries.ml +++ b/src/domains/queries.ml @@ -1,70 +1,61 @@ -(** Structures for the querying subsystem. *) +(** Queries and their result lattices. *) open GoblintCil -module GU = Goblintutil -module ID = -struct - module I = IntDomain.IntDomTuple - include Lattice.Lift (I) (Printable.DefaultNames) - - let lift op x = `Lifted (op x) - let unlift op x = match x with - | `Lifted x -> op x - | _ -> failwith "Queries.ID.unlift" - let unlift_opt op x = match x with - | `Lifted x -> op x - | _ -> None - let unlift_is op x = match x with - | `Lifted x -> op x - | _ -> false - - let bot_of = lift I.bot_of - let top_of = lift I.top_of - - let of_int ik = lift (I.of_int ik) - let of_bool ik = lift (I.of_bool ik) - let of_interval ?(suppress_ovwarn=false) ik = lift (I.of_interval ~suppress_ovwarn ik) - let of_excl_list ik = lift (I.of_excl_list ik) - let of_congruence ik = lift (I.of_congruence ik) - let starting ?(suppress_ovwarn=false) ik = lift (I.starting ~suppress_ovwarn ik) - let ending ?(suppress_ovwarn=false) ik = lift (I.ending ~suppress_ovwarn ik) - - let to_int x = unlift_opt I.to_int x - let to_bool x = unlift_opt I.to_bool x - - let is_top_of ik = unlift_is (I.is_top_of ik) - - let is_bot_ikind = function - | `Bot -> false - | `Lifted x -> I.is_bot x - | `Top -> false -end -module LS = SetDomain.ToppedSet (Lval.CilLval) (struct let topname = "All" end) +module VDQ = ValueDomainQueries + +module ID = VDQ.ID + +module LS = VDQ.LS module TS = SetDomain.ToppedSet (CilType.Typ) (struct let topname = "All" end) module ES = SetDomain.Reverse (SetDomain.ToppedSet (CilType.Exp) (struct let topname = "All" end)) +module VS = SetDomain.ToppedSet (CilType.Varinfo) (struct let topname = "All" end) + +module 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" - let bot_name = "Unreachable line" -end) + let top_name = "Unknown line" + let bot_name = "Unreachable line" + end) type iterprevvar = int -> (MyCFG.node * Obj.t * int) -> MyARG.inline_edge -> unit type itervar = int -> unit let compare_itervar _ _ = 0 let compare_iterprevvar _ _ = 0 +module FlatYojson = Lattice.Flat (Printable.Yojson) (struct + let top_name = "top yojson" + let bot_name = "bot yojson" + end) + module SD = Basetype.Strings +module VD = ValueDomain.Compound +module AD = ValueDomain.AD module MayBool = BoolDomain.MayBool module MustBool = BoolDomain.MustBool module Unit = Lattice.Unit +(** Different notions of protection for a global variables g by a mutex m + m protects g strongly if: + - whenever g is accessed after the program went multi-threaded for the first time, m is held + + m protects g weakly if: + - whenever g is accessed and there are any threads other than the main thread that are created but not joined yet, m is held +*) +module Protection = struct + type t = Strong | Weak [@@deriving ord, hash] +end + (* Helper definitions for deriving complex parts of Any.compare below. *) -type maybepublic = {global: CilType.Varinfo.t; write: bool} [@@deriving ord, hash] -type maybepublicwithout = {global: CilType.Varinfo.t; write: bool; without_mutex: PreValueDomain.Addr.t} [@@deriving ord, hash] -type mustbeprotectedby = {mutex: PreValueDomain.Addr.t; global: CilType.Varinfo.t; write: bool} [@@deriving ord, hash] +type maybepublic = {global: CilType.Varinfo.t; write: bool; protection: Protection.t} [@@deriving ord, hash] +type maybepublicwithout = {global: CilType.Varinfo.t; write: bool; without_mutex: PreValueDomain.Addr.t; protection: Protection.t} [@@deriving ord, hash] +type mustbeprotectedby = {mutex: PreValueDomain.Addr.t; global: CilType.Varinfo.t; write: bool; protection: Protection.t} [@@deriving ord, hash] type mustprotectedvars = {mutex: PreValueDomain.Addr.t; write: bool} [@@deriving ord, hash] type memory_access = {exp: CilType.Exp.t; var_opt: CilType.Varinfo.t option; kind: AccessKind.t} [@@deriving ord, hash] type access = @@ -73,7 +64,7 @@ type access = [@@deriving ord, hash] (* TODO: fix ppx_deriving_hash on variant with inline record *) type invariant_context = Invariant.context = { path: int option; - lvals: CilLval.Set.t; + lvals: Lval.Set.t; } [@@deriving ord, hash] @@ -81,41 +72,62 @@ 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: 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 *) - | BlobSize: exp -> ID.t t (* size of a dynamically allocated `Blob pointed to by exp *) + | EvalValue: exp -> VD.t t + | 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 - | HeapVar: VI.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]. *) + | 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 (* Is no other copy of this local variable reachable via pointers? *) + | IsMultiple: varinfo -> MustBool.t t + (* For locals: Is another copy of this local variable reachable via pointers? *) + (* For dynamically allocated memory: Does this abstract variable corrrespond to a unique heap location? *) + (* For globals: Is it declared as thread-local? (https://github.com/goblint/analyzer/issues/1072) *) + | MutexType: Mval.Unit.t -> MutexAttrDomain.t t | EvalThread: exp -> ConcDomain.ThreadSet.t t + | EvalMutexAttr: exp -> MutexAttrDomain.t t + | EvalJumpBuf: exp -> JmpBufDomain.JmpBufSet.t t + | ActiveJumpBuf: JmpBufDomain.ActiveLongjmps.t t + | ValidLongJmp: JmpBufDomain.JmpBufSet.t t | CreatedThreads: ConcDomain.ThreadSet.t t | MustJoinedThreads: ConcDomain.MustThreadSet.t t - | MustProtectedVars: mustprotectedvars -> LS.t t + | ThreadsJoinedCleanly: MustBool.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: AD.t t + | MayBeModifiedSinceSetjmp: JmpBufDomain.BufferEntry.t -> VS.t t + | TmpSpecial: Mval.Exp.t -> ML.t t type 'a result = 'a @@ -129,45 +141,59 @@ type ask = { f: 'a. 'a t -> 'a result } (* Result cannot implement Lattice.S because the function types are different due to GADT. *) module Result = struct - let lattice (type a) (q: a t): (module Lattice.S with type t = a) = + let rec lattice: type a. a t -> (module Lattice.S with type t = a) = fun q -> match q with (* 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) + | MustBeSingleThreaded _ -> (module MustBool) | MustBeUniqueThread -> (module MustBool) | EvalInt _ -> (module ID) | EvalLength _ -> (module ID) + | EvalMutexAttr _ -> (module MutexAttrDomain) + | EvalValue _ -> (module VD) | BlobSize _ -> (module ID) | CurrentThreadId -> (module ThreadIdDomain.ThreadLifted) - | HeapVar -> (module VI) + | ThreadCreateIndexedNode -> (module ThreadNodeLattice) + | AllocVar _ -> (module VI) | EvalStr _ -> (module SD) | IterPrevVars _ -> (module Unit) | IterVars _ -> (module Unit) + | PathQuery (_, q) -> lattice q + | DYojson -> (module FlatYojson) | PartAccess _ -> Obj.magic (module Unit: Lattice.S) (* Never used, MCP handles PartAccess specially. Must still return module (instead of failwith) here, but the module is never used. *) | IsMultiple _ -> (module MustBool) (* see https://github.com/goblint/analyzer/pull/310#discussion_r700056687 on why this needs to be MustBool *) + | MutexType _ -> (module MutexAttrDomain) | EvalThread _ -> (module ConcDomain.ThreadSet) + | EvalJumpBuf _ -> (module JmpBufDomain.JmpBufSet) + | ActiveJumpBuf -> (module JmpBufDomain.ActiveLongjmps) + | ValidLongJmp -> (module JmpBufDomain.JmpBufSet) | CreatedThreads -> (module ConcDomain.ThreadSet) | MustJoinedThreads -> (module ConcDomain.MustThreadSet) - | MustProtectedVars _ -> (module LS) + | ThreadsJoinedCleanly -> (module MustBool) + | MustProtectedVars _ -> (module VS) | Invariant _ -> (module Invariant) | InvariantGlobal _ -> (module Invariant) | WarnGlobal _ -> (module Unit) | IterSysVars _ -> (module Unit) | MayAccessed -> (module AccessDomain.EventSet) + | MayBeTainted -> (module AD) + | MayBeModifiedSinceSetjmp _ -> (module VS) + | TmpSpecial _ -> (module ML) (** Get bottom result for query. *) let bot (type a) (q: a t): a result = @@ -175,9 +201,9 @@ struct Result.bot () (** Get top result for query. *) - let top (type a) (q: a t): a result = + let rec top: type a. a t -> a result = fun q -> (* let module Result = (val lattice q) in - Result.top () *) + Result.top () *) (* [lattice] and [top] manually inlined to avoid first-class module for every unsupported [query] implementation. See benchmarks at: https://github.com/goblint/analyzer/pull/221#issuecomment-842351621. *) @@ -185,40 +211,54 @@ 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 () - | MustBeSingleThreaded -> MustBool.top () + | MustBeSingleThreaded _ -> MustBool.top () | MustBeUniqueThread -> MustBool.top () | EvalInt _ -> ID.top () | EvalLength _ -> ID.top () + | EvalMutexAttr _ -> MutexAttrDomain.top () + | EvalValue _ -> VD.top () | BlobSize _ -> ID.top () | CurrentThreadId -> ThreadIdDomain.ThreadLifted.top () - | HeapVar -> VI.top () + | ThreadCreateIndexedNode -> ThreadNodeLattice.top () + | AllocVar _ -> VI.top () | EvalStr _ -> SD.top () | IterPrevVars _ -> Unit.top () | IterVars _ -> Unit.top () + | PathQuery (_, q) -> top q + | DYojson -> FlatYojson.top () | PartAccess _ -> failwith "Queries.Result.top: PartAccess" (* Never used, MCP handles PartAccess specially. *) | IsMultiple _ -> MustBool.top () | EvalThread _ -> ConcDomain.ThreadSet.top () + | EvalJumpBuf _ -> JmpBufDomain.JmpBufSet.top () + | ActiveJumpBuf -> JmpBufDomain.ActiveLongjmps.top () + | ValidLongJmp -> JmpBufDomain.JmpBufSet.top () | CreatedThreads -> ConcDomain.ThreadSet.top () | MustJoinedThreads -> ConcDomain.MustThreadSet.top () - | MustProtectedVars _ -> LS.top () + | ThreadsJoinedCleanly -> MustBool.top () + | MustProtectedVars _ -> VS.top () | Invariant _ -> Invariant.top () | InvariantGlobal _ -> Invariant.top () | WarnGlobal _ -> Unit.top () | IterSysVars _ -> Unit.top () | MayAccessed -> AccessDomain.EventSet.top () + | MayBeTainted -> AD.top () + | MayBeModifiedSinceSetjmp _ -> VS.top () + | TmpSpecial _ -> ML.top () end (* The type any_query can't be directly defined in Any as t, @@ -242,7 +282,7 @@ struct | Any (MustBeProtectedBy _) -> 9 | Any MustLockset -> 10 | Any MustBeAtomic -> 11 - | Any MustBeSingleThreaded -> 12 + | Any (MustBeSingleThreaded _)-> 12 | Any MustBeUniqueThread -> 13 | Any CurrentThreadId -> 14 | Any MayBeThreadReturn -> 15 @@ -255,7 +295,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 @@ -267,8 +307,22 @@ struct | Any (InvariantGlobal _) -> 38 | Any (MustProtectedVars _) -> 39 | Any MayAccessed -> 40 - - let compare a b = + | Any MayBeTainted -> 41 + | Any (PathQuery _) -> 42 + | Any DYojson -> 43 + | Any (EvalValue _) -> 44 + | Any (EvalJumpBuf _) -> 45 + | Any ActiveJumpBuf -> 46 + | Any ValidLongJmp -> 47 + | Any (MayBeModifiedSinceSetjmp _) -> 48 + | Any (MutexType _) -> 49 + | Any (EvalMutexAttr _ ) -> 50 + | Any ThreadCreateIndexedNode -> 51 + | Any ThreadsJoinedCleanly -> 52 + | Any (TmpSpecial _) -> 53 + | Any (IsAllocVar _) -> 54 + + let rec compare a b = let r = Stdlib.compare (order a) (order b) in if r <> 0 then r @@ -287,25 +341,44 @@ struct | Any (EvalInt e1), Any (EvalInt e2) -> CilType.Exp.compare e1 e2 | Any (EvalStr e1), Any (EvalStr e2) -> CilType.Exp.compare e1 e2 | Any (EvalLength e1), Any (EvalLength e2) -> CilType.Exp.compare e1 e2 - | Any (BlobSize e1), Any (BlobSize 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 {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 | Any (IterVars i1), Any (IterVars i2) -> compare_itervar i1 i2 + | Any (PathQuery (i1, q1)), Any (PathQuery (i2, q2)) -> + let r = Stdlib.compare i1 i2 in + if r <> 0 then + r + 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 (EvalThread e1), Any (EvalThread e2) -> CilType.Exp.compare e1 e2 - | Any (WarnGlobal vi1), Any (WarnGlobal vi2) -> compare (Hashtbl.hash vi1) (Hashtbl.hash vi2) + | 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) | Any (Invariant i1), Any (Invariant i2) -> compare_invariant_context i1 i2 - | Any (InvariantGlobal vi1), Any (InvariantGlobal vi2) -> compare (Hashtbl.hash vi1) (Hashtbl.hash vi2) + | Any (InvariantGlobal vi1), Any (InvariantGlobal vi2) -> Stdlib.compare (Hashtbl.hash vi1) (Hashtbl.hash vi2) | Any (IterSysVars (vq1, vf1)), Any (IterSysVars (vq2, vf2)) -> VarQuery.compare vq1 vq2 (* not comparing fs *) + | Any (MutexType m1), Any (MutexType m2) -> Mval.Unit.compare m1 m2 | Any (MustProtectedVars m1), Any (MustProtectedVars m2) -> compare_mustprotectedvars m1 m2 + | Any (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) let equal x y = compare x y = 0 - let hash_arg = function + let rec hash_arg = function | Any (EqualSet e) -> CilType.Exp.hash e | Any (MayPointTo e) -> CilType.Exp.hash e | Any (ReachableFrom e) -> CilType.Exp.hash e @@ -319,24 +392,36 @@ struct | Any (EvalInt e) -> CilType.Exp.hash e | Any (EvalStr e) -> CilType.Exp.hash e | Any (EvalLength e) -> CilType.Exp.hash e - | Any (BlobSize e) -> CilType.Exp.hash e + | Any (EvalMutexAttr e) -> CilType.Exp.hash e + | Any (EvalValue 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 (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 | Any (WarnGlobal vi) -> Hashtbl.hash vi | Any (Invariant i) -> hash_invariant_context i + | Any (MutexType m) -> Mval.Unit.hash m | Any (InvariantGlobal vi) -> Hashtbl.hash vi | 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. *) (* only argumentless queries should remain *) | _ -> 0 - let hash x = 31 * order x + hash_arg x + and hash x = 31 * order x + hash_arg x - let pretty () = function + let rec pretty () = function | Any (EqualSet e) -> Pretty.dprintf "EqualSet %a" CilType.Exp.pretty e | Any (MayPointTo e) -> Pretty.dprintf "MayPointTo %a" CilType.Exp.pretty e | Any (ReachableFrom e) -> Pretty.dprintf "ReachableFrom %a" CilType.Exp.pretty e @@ -348,43 +433,56 @@ struct | Any (MustBeProtectedBy x) -> Pretty.dprintf "MustBeProtectedBy _" | Any MustLockset -> Pretty.dprintf "MustLockset" | Any MustBeAtomic -> Pretty.dprintf "MustBeAtomic" - | Any MustBeSingleThreaded -> Pretty.dprintf "MustBeSingleThreaded" + | Any (MustBeSingleThreaded {since_start}) -> Pretty.dprintf "MustBeSingleThreaded since_start=%b" since_start | Any MustBeUniqueThread -> Pretty.dprintf "MustBeUniqueThread" | Any CurrentThreadId -> Pretty.dprintf "CurrentThreadId" + | Any ThreadCreateIndexedNode -> Pretty.dprintf "ThreadCreateIndexedNode" | Any MayBeThreadReturn -> Pretty.dprintf "MayBeThreadReturn" | Any (EvalFunvar e) -> Pretty.dprintf "EvalFunvar %a" CilType.Exp.pretty e | Any (EvalInt e) -> Pretty.dprintf "EvalInt %a" CilType.Exp.pretty e | Any (EvalStr e) -> Pretty.dprintf "EvalStr %a" CilType.Exp.pretty e | Any (EvalLength e) -> Pretty.dprintf "EvalLength %a" CilType.Exp.pretty e - | Any (BlobSize e) -> Pretty.dprintf "BlobSize %a" CilType.Exp.pretty e + | Any (EvalValue e) -> Pretty.dprintf "EvalValue %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 HeapVar -> Pretty.dprintf "HeapVar" + | Any (PathQuery (i, q)) -> Pretty.dprintf "PathQuery (%d, %a)" i pretty (Any q) + | 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 + | Any ActiveJumpBuf -> Pretty.dprintf "ActiveJumpBuf" + | Any ValidLongJmp -> Pretty.dprintf "ValidLongJmp" | Any CreatedThreads -> Pretty.dprintf "CreatedThreads" | Any MustJoinedThreads -> Pretty.dprintf "MustJoinedThreads" + | Any ThreadsJoinedCleanly -> Pretty.dprintf "ThreadsJoinedCleanly" | Any (MustProtectedVars m) -> Pretty.dprintf "MustProtectedVars _" | Any (Invariant i) -> Pretty.dprintf "Invariant _" | Any (WarnGlobal vi) -> Pretty.dprintf "WarnGlobal _" | Any (IterSysVars _) -> Pretty.dprintf "IterSysVars _" | Any (InvariantGlobal i) -> Pretty.dprintf "InvariantGlobal _" + | Any (MutexType (v,o)) -> Pretty.dprintf "MutexType _" + | Any (EvalMutexAttr a) -> Pretty.dprintf "EvalMutexAttr _" | Any MayAccessed -> Pretty.dprintf "MayAccessed" + | Any MayBeTainted -> Pretty.dprintf "MayBeTainted" + | Any DYojson -> Pretty.dprintf "DYojson" + | Any MayBeModifiedSinceSetjmp buf -> Pretty.dprintf "MayBeModifiedSinceSetjmp %a" JmpBufDomain.BufferEntry.pretty buf + | Any (TmpSpecial lv) -> Pretty.dprintf "TmpSpecial %a" Mval.Exp.pretty lv end +let to_value_domain_ask (ask: ask) = + let eval_int e = ask.f (EvalInt e) in + let may_point_to e = ask.f (MayPointTo e) in + let is_multiple v = ask.f (IsMultiple v) in + { VDQ.eval_int; may_point_to; is_multiple } let eval_int_binop (module Bool: Lattice.S with type t = bool) binop (ask: ask) e1 e2: Bool.t = - let e = Cilfacade.makeBinOp binop e1 e2 in - let i = ask.f (EvalInt e) in - if ID.is_bot i || ID.is_bot_ikind i then - Bool.top () (* base returns bot for non-int results, consider unknown *) - else - match ID.to_bool i with - | Some b -> b - | None -> Bool.top () + let eval_int e = ask.f (EvalInt e) in + VDQ.eval_int_binop (module Bool) binop eval_int e1 e2 (** Backwards-compatibility for former [MustBeEqual] query. *) let must_be_equal = eval_int_binop (module MustBool) Eq diff --git a/src/domains/setDomain.ml b/src/domains/setDomain.ml index ce531f1fd9..1b5239de80 100644 --- a/src/domains/setDomain.ml +++ b/src/domains/setDomain.ml @@ -1,4 +1,5 @@ -(** Abstract domains representing sets. *) +(** Set domains. *) + module Pretty = GoblintCil.Pretty open Pretty @@ -134,11 +135,11 @@ struct match x with | [] -> [] | [x] -> [x] - | (x::xs) -> x ++ (text ", ") :: separate xs + | (x::xs) -> x ++ (text "," ++ break) :: separate xs in let separated = separate content in let content = List.fold_left (++) nil separated in - (text "{") ++ content ++ (text "}") + (text "{" ++ align) ++ content ++ (unalign ++ text "}") (** Short summary for sets. *) let show x : string = @@ -160,7 +161,7 @@ module Make (Base: Printable.S): S with type elt = Base.t and type t = BatSet.Make (Base).t = (* TODO: remove, only needed in VarEq for some reason... *) struct - include Printable.Blank + include Printable.Std include BatSet.Make(Base) let name () = "Set (" ^ Base.name () ^ ")" let empty _ = empty @@ -174,10 +175,6 @@ struct let top () = unsupported "Make.top" let is_top _ = false - let map f s = - let add_to_it x s = add (f x) s in - fold add_to_it s (empty ()) - include Print (Base) ( struct type nonrec t = t @@ -187,10 +184,6 @@ struct end ) - let equal x y = - cardinal x = cardinal y - && for_all (fun e -> exists (Base.equal e) y) x - let hash x = fold (fun x y -> y + Base.hash x) x 0 let relift x = map Base.relift x diff --git a/src/domains/trieDomain.ml b/src/domains/trieDomain.ml new file mode 100644 index 0000000000..0bab7d8628 --- /dev/null +++ b/src/domains/trieDomain.ml @@ -0,0 +1,19 @@ +(** Trie domains. *) + +module Make (Key: Printable.S) (Value: Lattice.S) = +struct + module rec Trie: + sig + type key = Key.t + type value = Value.t + include Lattice.S with type t = value * ChildMap.t + end = + struct + type key = Key.t + type value = Value.t + include Lattice.Prod (Value) (ChildMap) + end + and ChildMap: MapDomain.S with type key = Key.t and type value = Trie.t = MapDomain.MapBot (Key) (Trie) + + include Trie +end diff --git a/src/domains/valueDomainQueries.ml b/src/domains/valueDomainQueries.ml new file mode 100644 index 0000000000..8266582ac2 --- /dev/null +++ b/src/domains/valueDomainQueries.ml @@ -0,0 +1,75 @@ +(** Queries within {!ValueDomain}. *) + +open GoblintCil +open BoolDomain + +module LS = SetDomain.ToppedSet (Mval.Exp) (struct let topname = "All" end) +module AD = PreValueDomain.AD + +module ID = +struct + module I = IntDomain.IntDomTuple + include Lattice.Lift (I) (Printable.DefaultNames) + + let lift op x = `Lifted (op x) + let unlift op x = match x with + | `Lifted x -> op x + | _ -> failwith "Queries.ID.unlift" + let unlift_opt op x = match x with + | `Lifted x -> op x + | _ -> None + let unlift_is op x = match x with + | `Lifted x -> op x + | _ -> false + + let bot_of = lift I.bot_of + let top_of = lift I.top_of + + let of_int ik = lift (I.of_int ik) + let of_bool ik = lift (I.of_bool ik) + let of_interval ?(suppress_ovwarn=false) ik = lift (I.of_interval ~suppress_ovwarn ik) + let of_excl_list ik = lift (I.of_excl_list ik) + let of_congruence ik = lift (I.of_congruence ik) + let starting ?(suppress_ovwarn=false) ik = lift (I.starting ~suppress_ovwarn ik) + let ending ?(suppress_ovwarn=false) ik = lift (I.ending ~suppress_ovwarn ik) + + let to_int x = unlift_opt I.to_int x + let to_bool x = unlift_opt I.to_bool x + + let is_top_of ik = unlift_is (I.is_top_of ik) + + let is_bot_ikind = function + | `Bot -> false + | `Lifted x -> I.is_bot x + | `Top -> false +end + +type eval_int = exp -> ID.t +type may_point_to = exp -> AD.t +type is_multiple = varinfo -> bool + +(** Subset of queries used by the valuedomain, using a simpler representation. *) +type t = { + eval_int: eval_int; + may_point_to: may_point_to; + is_multiple: is_multiple; +} + +let eval_int_binop (module Bool: Lattice.S with type t = bool) binop (eval_int: eval_int) e1 e2: Bool.t = + let e = Cilfacade.makeBinOp binop e1 e2 in + let i = eval_int e in + if ID.is_bot i || ID.is_bot_ikind i then + Bool.top () (* base returns bot for non-int results, consider unknown *) + else + match ID.to_bool i with + | Some b -> b + | None -> Bool.top () + +(** Backwards-compatibility for former [MustBeEqual] query. *) +let must_be_equal = eval_int_binop (module MustBool) Eq + +(** Backwards-compatibility for former [MayBeEqual] query. *) +let may_be_equal = eval_int_binop (module MayBool) Eq + +(** Backwards-compatibility for former [MayBeLess] query. *) +let may_be_less = eval_int_binop (module MayBool) Lt diff --git a/src/dune b/src/dune index 69996d50fa..acd5348acb 100644 --- a/src/dune +++ b/src/dune @@ -7,7 +7,7 @@ (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 + (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_common ; 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. @@ -56,11 +56,12 @@ (-> 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)) ) ; Workaround for alternative dependencies with unqualified subdirs. @@ -76,51 +77,35 @@ (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) + (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)) @@ -140,3 +125,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 7c8a303b58..eec811afc4 100644 --- a/src/framework/analyses.ml +++ b/src/framework/analyses.ml @@ -1,11 +1,10 @@ -(** Signatures for analyzers, analysis specifications, and result output. *) +(** {{!Spec} Analysis specification} and {{!MonSystem} constraint system} signatures. *) -open Prelude +open Batteries open GoblintCil open Pretty open GobConfig -module GU = Goblintutil module M = Messages (** Analysis starts from lists of functions: start functions, exit functions, and @@ -34,7 +33,7 @@ end module Var = struct type t = Node.t [@@deriving eq, ord, hash] - let relift x = x + let relift = Node.relift let printXml f n = let l = Node.location n in @@ -76,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 @@ -98,6 +98,7 @@ struct let printXml f c = BatPrintf.fprintf f "%a" printXml c (* wrap in for HTML printing *) end ) + let name () = "contexts" end include Lattice.Lift2 (G) (CSet) (Printable.DefaultNames) @@ -211,20 +212,24 @@ struct match loc with | Some loc -> let l = Messages.Location.to_cil loc in - BatPrintf.fprintf f "\n%s" l.file l.line l.column (GU.escape m) + BatPrintf.fprintf f "\n%s" l.file l.line l.column (XmlUtil.escape m) | None -> () (* TODO: not outputting warning without location *) in let one_w f (m: Messages.Message.t) = match m.multipiece with | Single piece -> one_text f piece - | Group {group_text = n; pieces = e} -> - BatPrintf.fprintf f "%a\n" n (BatList.print ~first:"" ~last:"" ~sep:"" one_text) e + | Group {group_text = n; pieces = e; group_loc} -> + let group_loc_text = match group_loc with + | None -> "" + | Some group_loc -> GobPretty.sprintf " (%a)" CilType.Location.pretty (Messages.Location.to_cil group_loc) + in + BatPrintf.fprintf f "%a\n" n group_loc_text (BatList.print ~first:"" ~last:"" ~sep:"" one_text) e in let one_w f x = BatPrintf.fprintf f "\n%a" one_w x in List.iter (one_w f) !Messages.Table.messages_list let output table gtable gtfxml (file: file) = - let out = Messages.get_out result_name !GU.out in + let out = Messages.get_out result_name !Messages.out in match get_string "result" with | "pretty" -> ignore (fprintf out "%a\n" pretty (Lazy.force table)) | "fast_xml" -> @@ -250,7 +255,7 @@ struct Messages.xml_file_name := fn; BatPrintf.printf "Writing xml to temp. file: %s\n%!" fn; BatPrintf.fprintf f ""; - BatPrintf.fprintf f "%s" Goblintutil.command_line; + BatPrintf.fprintf f "%s" GobSys.command_line; BatPrintf.fprintf f ""; let timing_ppf = BatFormat.formatter_of_out_channel f in Timing.Default.print timing_ppf; @@ -289,7 +294,7 @@ struct let p_file f x = fprintf f "{\n \"name\": \"%s\",\n \"path\": \"%s\",\n \"functions\": %a\n}" (Filename.basename x) x (p_list p_fun) (SH.find_all file2funs x) in let write_file f fn = printf "Writing json to temp. file: %s\n%!" fn; - fprintf f "{\n \"parameters\": \"%s\",\n " Goblintutil.command_line; + fprintf f "{\n \"parameters\": \"%s\",\n " GobSys.command_line; fprintf f "\"files\": %a,\n " (p_enum p_file) (SH.keys file2funs); fprintf f "\"results\": [\n %a\n]\n" printJson (Lazy.force table); (*gtfxml f gtable;*) @@ -317,63 +322,6 @@ struct end -(** Reference to top-level Control Spec context first-class module. *) -let control_spec_c: (module Printable.S) ref = - let module Failwith = Printable.Failwith ( - struct - let message = "uninitialized control_spec_c" - end - ) - in - ref (module Failwith: Printable.S) - -(** Top-level Control Spec context as static module, which delegates to {!control_spec_c}. - This allows using top-level context values inside individual analyses. *) -module ControlSpecC: Printable.S = -struct - type t = Obj.t (** represents [(val !control_spec_c).t] *) - - (* The extra level of indirection allows calls to this static module to go to a dynamic first-class module. *) - - let name () = - let module C = (val !control_spec_c) in - C.name () - - let equal x y = - let module C = (val !control_spec_c) in - C.equal (Obj.obj x) (Obj.obj y) - let compare x y = - let module C = (val !control_spec_c) in - C.compare (Obj.obj x) (Obj.obj y) - let hash x = - let module C = (val !control_spec_c) in - C.hash (Obj.obj x) - let tag x = - let module C = (val !control_spec_c) in - C.tag (Obj.obj x) - - let show x = - let module C = (val !control_spec_c) in - C.show (Obj.obj x) - let pretty () x = - let module C = (val !control_spec_c) in - C.pretty () (Obj.obj x) - let printXml f x = - let module C = (val !control_spec_c) in - C.printXml f (Obj.obj x) - let to_yojson x = - let module C = (val !control_spec_c) in - C.to_yojson (Obj.obj x) - - let arbitrary () = - let module C = (val !control_spec_c) in - QCheck.map ~rev:Obj.obj Obj.repr (C.arbitrary ()) - let relift x = - let module C = (val !control_spec_c) in - Obj.repr (C.relift (Obj.obj x)) -end - - (* Experiment to reduce the number of arguments on transfer functions and allow sub-analyses. The list sub contains the current local states of analyses in the same order as written in the dependencies list (in MCP). @@ -394,7 +342,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 } @@ -414,6 +362,7 @@ sig module G : Lattice.S module C : Printable.S module V: SpecSysVar (** Global constraint variables. *) + module P: DisjointDomain.Representative with type elt := D.t (** Path-representative. *) val name : unit -> string @@ -435,29 +384,72 @@ sig val morphstate : varinfo -> D.t -> D.t val exitstate : varinfo -> D.t - val should_join : D.t -> D.t -> bool val context : fundec -> D.t -> C.t val sync : (D.t, G.t, C.t, V.t) ctx -> [`Normal | `Join | `Return] -> D.t val query : (D.t, G.t, C.t, V.t) ctx -> 'a Queries.t -> 'a Queries.result + + (** A transfer function which handles the assignment of a rval to a lval, i.e., + it handles program points of the form "lval = rval;" *) val assign: (D.t, G.t, C.t, V.t) ctx -> lval -> exp -> D.t + + (** A transfer function used for declaring local variables. + By default only for variable-length arrays (VLAs). *) val vdecl : (D.t, G.t, C.t, V.t) ctx -> varinfo -> D.t + + (** A transfer function which handles conditional branching yielding the + truth value passed as a boolean argument *) val branch: (D.t, G.t, C.t, V.t) ctx -> exp -> bool -> D.t + + (** A transfer function which handles going from the start node of a function (fundec) into + its function body. Meant to handle, e.g., initialization of local variables *) val body : (D.t, G.t, C.t, V.t) ctx -> fundec -> D.t + + (** A transfer function which handles the return statement, i.e., + "return exp" or "return" in the passed function (fundec) *) val return: (D.t, G.t, C.t, V.t) ctx -> exp option -> fundec -> D.t + + (** A transfer function meant to handle inline assembler program points *) val asm : (D.t, G.t, C.t, V.t) ctx -> D.t - val skip : (D.t, G.t, C.t, V.t) ctx -> D.t + (** A transfer function which works as the identity function, i.e., it skips and does nothing. + Used for empty loops. *) + val skip : (D.t, G.t, C.t, V.t) ctx -> D.t + (** A transfer function which, for a call to a {e special} function f "lval = f(args)" or "f(args)", + computes the caller state after the function call *) val special : (D.t, G.t, C.t, V.t) ctx -> lval option -> varinfo -> exp list -> D.t + + (** For a function call "lval = f(args)" or "f(args)", + [enter] returns a caller state, and the initial state of the callee. + In [enter], the caller state can usually be returned unchanged, as [combine_env] and [combine_assign] (below) + will compute the caller state after the function call, given the return state of the callee *) val enter : (D.t, G.t, C.t, V.t) ctx -> lval option -> fundec -> exp list -> (D.t * D.t) list - val combine : (D.t, G.t, C.t, V.t) ctx -> lval option -> exp -> fundec -> exp list -> C.t option -> D.t -> D.t + + (* Combine is split into two steps: *) + + (** Combine environment (global variables, mutexes, etc) + between local state (first component from enter) and function return. + + This shouldn't yet assign to the lval. *) + val combine_env : (D.t, G.t, C.t, V.t) ctx -> lval option -> exp -> fundec -> exp list -> C.t option -> D.t -> Queries.ask -> D.t + + (** Combine return value assignment + to local state (result from combine_env) and function return. + + This should only assign to the lval. *) + val combine_assign : (D.t, G.t, C.t, V.t) ctx -> lval option -> exp -> fundec -> exp list -> C.t option -> D.t -> Queries.ask -> D.t + + (* Paths as sets: I know this is ugly! *) + 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 module type MCPA = @@ -470,7 +462,6 @@ end module type MCPSpec = sig include Spec - val event : (D.t, G.t, C.t, V.t) ctx -> Events.t -> (D.t, G.t, C.t, V.t) ctx -> D.t module A: MCPA val access: (D.t, G.t, C.t, V.t) ctx -> Queries.access -> A.t @@ -528,6 +519,7 @@ sig module D : Lattice.S module G : Lattice.S val system : LVar.t -> ((LVar.t -> D.t) -> (LVar.t -> D.t -> unit) -> (GVar.t -> G.t) -> (GVar.t -> G.t -> unit) -> D.t) option + val iter_vars: (LVar.t -> D.t) -> (GVar.t -> G.t) -> VarQuery.t -> LVar.t VarQuery.f -> GVar.t VarQuery.f -> unit val sys_change: (LVar.t -> D.t) -> (GVar.t -> G.t) -> [`L of LVar.t | `G of GVar.t] sys_change_info end @@ -623,12 +615,24 @@ struct let should_print _ = false end +module UnitP = +struct + include Printable.Unit + let of_elt _ = () +end + +module IdentityP (D: Lattice.S) = +struct + include D + let of_elt x = x +end (** Relatively safe default implementations of some boring Spec functions. *) module DefaultSpec = struct module G = Lattice.Unit module V = EmptyV + module P = UnitP type marshal = unit let init _ = () @@ -636,15 +640,11 @@ struct (* no inits nor finalize -- only analyses like Mutex, Base, ... need these to do postprocessing or other imperative hacks. *) - let should_join _ _ = true - (* hint for path sensitivity --- MCP no longer overrides this so if you want - your analysis to be path sensitive, do override this. To obtain a behavior - where all paths are kept apart, set this to D.equal x y *) - 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. *) @@ -663,6 +663,8 @@ struct let context fd x = x (* Everything is context sensitive --- override in MCP and maybe elsewhere*) + let paths_as_set ctx = [ctx.local] + module A = UnitA let access _ _ = () end @@ -686,14 +688,17 @@ struct let enter ctx (lval: lval option) (f:fundec) (args:exp list) = [ctx.local, ctx.local] - let combine ctx (lval:lval option) fexp (f:fundec) (args:exp list) fc au = + let combine_env ctx (lval:lval option) fexp (f:fundec) (args:exp list) fc au (f_ask: Queries.ask) = au + let combine_assign ctx (lval:lval option) fexp (f:fundec) (args:exp list) fc au (f_ask: Queries.ask) = + ctx.local + 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/cfgTools.ml b/src/framework/cfgTools.ml index a403502830..8f98a48e84 100644 --- a/src/framework/cfgTools.ml +++ b/src/framework/cfgTools.ml @@ -1,3 +1,5 @@ +(** Construction and output of CFGs. *) + open MyCFG open GoblintCil open Pretty @@ -122,7 +124,7 @@ let rec pretty_edges () = function 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 Goblintutil.create_var. We only add one return stmt per loop, so the hash from the functions vid should be unique. *) + 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 @@ -135,14 +137,27 @@ let () = Printexc.register_printer (function | _ -> None (* for other exceptions *) ) +(** Type of CFG "edges": keyed by 'from' and 'to' nodes, + along with the list of connecting instructions. *) +module CfgEdge = struct + type t = Node.t * MyCFG.edges * Node.t [@@deriving eq, hash] +end + +module CfgEdgeH = BatHashtbl.Make (CfgEdge) + let createCFG (file: file) = let cfgF = H.create 113 in let cfgB = H.create 113 in + + (* Track the list of pure control-flow statements between two CFG nodes, + which do not otherwise appear in the control flow graph. *) + let skippedByEdge = CfgEdgeH.create 113 in + if Messages.tracing then Messages.trace "cfg" "Starting to build the cfg.\n\n"; let fd_nodes = NH.create 113 in - let addEdges fromNode edges toNode = + let addEdges ?(skippedStatements = []) fromNode edges toNode = if Messages.tracing then Messages.trace "cfg" "Adding edges [%a] from\n\t%a\nto\n\t%a ... " pretty_edges edges @@ -152,23 +167,36 @@ let createCFG (file: file) = NH.replace fd_nodes toNode (); H.modify_def [] toNode (List.cons (edges,fromNode)) cfgB; H.modify_def [] fromNode (List.cons (edges,toNode)) cfgF; + CfgEdgeH.replace skippedByEdge (fromNode, edges, toNode) skippedStatements; if Messages.tracing then Messages.trace "cfg" "done\n\n" in - let addEdge fromNode edge toNode = addEdges fromNode [edge] toNode in - let addEdge_fromLoc fromNode edge toNode = addEdge fromNode (Node.location fromNode, edge) toNode in + let addEdge ?skippedStatements fromNode edge toNode = + addEdges ?skippedStatements fromNode [edge] toNode + in + let addEdge_fromLoc ?skippedStatements fromNode edge toNode = + addEdge ?skippedStatements fromNode (Node.location fromNode, edge) toNode + in (* Find real (i.e. non-empty) successor of statement. CIL CFG contains some unnecessary intermediate statements. + The first return value is the next real successor, the second argument + is the list of skipped intermediate statements. If stmt is succ of parent, then optional argument parent must be passed to also detect cycle ending with parent itself. If not_found is true, then a stmt without succs will raise Not_found instead of returning that stmt. *) let find_real_stmt ?parent ?(not_found=false) stmt = if Messages.tracing then Messages.tracei "cfg" "find_real_stmt not_found=%B stmt=%d\n" not_found stmt.sid; - let rec find visited_sids stmt = - if Messages.tracing then Messages.trace "cfg" "find_real_stmt visited=[%a] stmt=%d: %a\n" (d_list "; " (fun () x -> Pretty.text (string_of_int x))) visited_sids stmt.sid dn_stmt stmt; - if List.mem stmt.sid visited_sids then (* mem uses structural equality on ints, which is fine *) - stmt (* cycle *) + let rec find visited_stmts stmt = + if Messages.tracing then + Messages.trace "cfg" "find_real_stmt visited=[%a] stmt=%d: %a\n" + (d_list "; " (fun () x -> Pretty.text (string_of_int x))) + (List.map (fun s -> s.sid) visited_stmts) stmt.sid dn_stmt stmt; + if + GobOption.exists (CilType.Stmt.equal stmt) parent + || List.exists (CilType.Stmt.equal stmt) visited_stmts + then + stmt, visited_stmts (* cycle *) else match stmt.skind with | Goto _ (* 1 succ *) @@ -180,9 +208,9 @@ let createCFG (file: file) = if not_found then raise Not_found else - stmt + stmt, visited_stmts | [next] -> - find (stmt.sid :: visited_sids) next + find (stmt :: visited_stmts) next | _ -> (* >1 succ *) failwith "MyCFG.createCFG.find_real_stmt: >1 succ" end @@ -190,7 +218,7 @@ let createCFG (file: file) = | Instr _ | If _ | Return _ -> - stmt + stmt, visited_stmts | Continue _ | Break _ @@ -202,13 +230,10 @@ let createCFG (file: file) = failwith "MyCFG.createCFG: unsupported stmt" in try - let initial_visited_sids = match parent with - | Some parent -> [parent.sid] - | None -> [] - in - let r = find initial_visited_sids stmt in - if Messages.tracing then Messages.traceu "cfg" "-> %d\n" r.sid; - r + (* rev_path is the stack of all visited statements, excluding the final statement *) + let final_stmt, rev_path = find [] stmt in + if Messages.tracing then Messages.traceu "cfg" "-> %d\n" final_stmt.sid; + final_stmt, List.rev rev_path with Not_found -> if Messages.tracing then Messages.traceu "cfg" "-> Not_found\n"; raise Not_found @@ -226,18 +251,20 @@ let createCFG (file: file) = NH.clear fd_nodes; (* Find the first statement in the function *) - let entrynode = find_real_stmt (Cilfacade.getFirstStmt fd) in + let entrynode, skippedStatements = find_real_stmt (Cilfacade.getFirstStmt fd) in (* Add the entry edge to that node *) - addEdge (FunctionEntry fd) (fd_loc, Entry fd) (Statement entrynode); + addEdge ~skippedStatements (FunctionEntry fd) (fd_loc, Entry fd) (Statement entrynode); (* Return node to be used for infinite loop connection to end of function * lazy, so it's only added when actually needed *) let pseudo_return = lazy ( if Messages.tracing then Messages.trace "cfg" "adding pseudo-return to the function %s.\n" fd.svar.vname; - let newst = mkStmt (Return (None, fd_loc)) in + 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; 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 - addEdge newst_node (fd_loc, Ret (None, fd)) (Function fd); + addEdge newst_node (fd_end_loc, Ret (None, fd)) (Function fd); newst_node ) in @@ -257,10 +284,10 @@ let createCFG (file: file) = (* CIL uses empty Instr self-loop for empty Loop, so a Skip self-loop must be added to not lose the loop. *) begin match real_succs () with | [] -> () (* if stmt.succs is empty (which in other cases requires pseudo return), then it isn't a self-loop to add anyway *) - | [succ] -> + | [succ, skippedStatements] -> if CilType.Stmt.equal succ stmt then (* self-loop *) let loc = Cilfacade.get_stmtLoc stmt in (* get location from label because Instr [] itself doesn't have one *) - addEdge (Statement stmt) (loc, Skip) (Statement succ) + addEdge ~skippedStatements (Statement stmt) (loc, Skip) (Statement succ) | _ -> failwith "MyCFG.createCFG: >1 Instr [] succ" end @@ -272,10 +299,10 @@ let createCFG (file: file) = | VarDecl (v, loc) -> loc, VDecl(v) in let edges = List.map edge_of_instr instrs in - let add_succ_node succ_node = addEdges (Statement stmt) edges succ_node in + let add_succ_node ?skippedStatements succ_node = addEdges ?skippedStatements (Statement stmt) edges succ_node in begin match real_succs () with | [] -> add_succ_node (Lazy.force pseudo_return) (* stmt.succs can be empty if last instruction calls non-returning function (e.g. exit), so pseudo return instead *) - | [succ] -> add_succ_node (Statement succ) + | [succ, skippedStatements] -> add_succ_node ~skippedStatements (Statement succ) | _ -> failwith "MyCFG.createCFG: >1 non-empty Instr succ" end @@ -286,13 +313,13 @@ let createCFG (file: file) = First, true branch's succ is consed (to empty succs list). Second, false branch's succ is consed (to previous succs list). CIL doesn't cons duplicate succs, so if both branches have the same succ, then singleton list is returned instead. *) - let (true_stmt, false_stmt) = match real_succs () with + let ((true_stmt, true_skippedStatements), (false_stmt, false_skippedStatements)) = match real_succs () with | [false_stmt; true_stmt] -> (true_stmt, false_stmt) | [same_stmt] -> (same_stmt, same_stmt) | _ -> failwith "MyCFG.createCFG: invalid number of If succs" in - addEdge (Statement stmt) (Cilfacade.eloc_fallback ~eloc ~loc, Test (exp, true )) (Statement true_stmt); - addEdge (Statement stmt) (Cilfacade.eloc_fallback ~eloc ~loc, Test (exp, false)) (Statement false_stmt) + addEdge ~skippedStatements:true_skippedStatements (Statement stmt) (Cilfacade.eloc_fallback ~eloc ~loc, Test (exp, true )) (Statement true_stmt); + addEdge ~skippedStatements:false_skippedStatements (Statement stmt) (Cilfacade.eloc_fallback ~eloc ~loc, Test (exp, false)) (Statement false_stmt) | Loop (_, loc, eloc, Some cont, Some brk) -> (* TODO: use loc for something? *) (* CIL already converts Loop logic to Gotos and If. *) @@ -301,11 +328,11 @@ let createCFG (file: file) = An extra Neg(1) edge is added in such case. *) if Messages.tracing then Messages.trace "cfg" "loop %d cont=%d brk=%d\n" stmt.sid cont.sid brk.sid; begin match find_real_stmt ~not_found:true brk with (* don't specify stmt as parent because if find_real_stmt finds cycle, it should not return the Loop statement *) - | break_stmt -> + | break_stmt, _ -> (* break statement is what follows the (constant true) Loop *) (* Neg(1) edges are lazily added only when unconnectedness is detected at the end, so break statement is just remembered here *) - let loop_stmt = find_real_stmt stmt in + let loop_stmt, _ = find_real_stmt stmt in NH.add loop_head_neg1 (Statement loop_stmt) (Statement break_stmt) | exception Not_found -> (* if the (constant true) Loop and its break statement are at the end of the function, @@ -328,9 +355,9 @@ let createCFG (file: file) = (* stmt.succs for Goto just contains the target ref. *) begin match real_succs () with | [] -> failwith "MyCFG.createCFG: 0 Goto succ" (* target ref is always succ *) - | [succ] -> + | [succ, skippedStatements] -> if CilType.Stmt.equal succ stmt then (* self-loop *) - addEdge (Statement stmt) (loc, Skip) (Statement succ) + addEdge ~skippedStatements (Statement stmt) (loc, Skip) (Statement succ) | _ -> failwith "MyCFG.createCFG: >1 Goto succ" end @@ -340,10 +367,10 @@ let createCFG (file: file) = (* real_succs are used instead of stmt.succs to handle empty goto-based loops with multiple mutual gotos. *) begin match real_succs () with | [] -> () (* if stmt.succs is empty (which in other cases requires pseudo return), then it isn't a self-loop to add anyway *) - | [succ] -> + | [succ, skippedStatements] -> if CilType.Stmt.equal succ stmt then (* self-loop *) let loc = Cilfacade.get_stmtLoc stmt in (* get location from label because Block [] itself doesn't have one *) - addEdge (Statement stmt) (loc, Skip) (Statement succ) + addEdge ~skippedStatements (Statement stmt) (loc, Skip) (Statement succ) | _ -> failwith "MyCFG.createCFG: >1 Block [] succ" end @@ -355,10 +382,10 @@ let createCFG (file: file) = | Break _ | Switch _ -> (* Should be removed by Cil.prepareCFG. *) - failwith "MyCFG.createCFG: unprepared stmt" + failwith "CfgTools.createCFG: unprepared stmt" | ComputedGoto _ -> - failwith "MyCFG.createCFG: unsupported stmt" + failwith "CfgTools.createCFG: unsupported stmt" in Timing.wrap ~args:[("function", `String fd.svar.vname)] "handle" (List.iter handle) fd.sallstmts; @@ -449,12 +476,12 @@ 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)); - cfgF, cfgB + cfgF, cfgB, skippedByEdge let createCFG = Timing.wrap "createCFG" createCFG -let minimizeCFG (fw,bw) = +let minimizeCFG (fw,bw) sk = let keep = H.create (H.length bw) in let comp_keep t (_,f) = if (List.compare_length_with (H.find_default bw t []) 1 <> 0) || (List.compare_length_with (H.find_default fw t []) 1 <> 0) then @@ -467,23 +494,33 @@ let minimizeCFG (fw,bw) = (* H.iter comp_keep fw; *) let cfgB = H.create (H.length bw) in let cfgF = H.create (H.length fw) in + let skippedStmts = CfgEdgeH.create (CfgEdgeH.length sk) in let ready = H.create (H.length bw) in - let rec add a b t (e,f)= + let inbound t = + H.find_default bw t [] + |> List.map (fun (e, f) -> e, f, CfgEdgeH.find_default sk (f, e, t) []) + in + (* list of traversed edges, list of traversed skipped statements, + end of current path, current to node, (edge, current from node, skipped statements on edge) *) + let rec add a p b t (e, f, s) = + let a', p' = e @ a, s @ p in if H.mem keep f then begin - H.modify_def [] b (List.cons (e@a,f)) cfgB; - H.modify_def [] f (List.cons (e@a,b)) cfgF; + H.modify_def [] b (List.cons (a', f)) cfgB; + H.modify_def [] f (List.cons (a', b)) cfgF; + CfgEdgeH.replace skippedStmts (f, a', b) p'; if H.mem ready b then begin H.replace ready f (); - List.iter (add [] f f) (H.find_default bw f []) + List.iter (add [] [] f f) (inbound f) end end else begin - List.iter (add (e@a) b f) (H.find_default bw f []) + let f_stmt = match f with Statement s -> [s] | _ -> [] in + List.iter (add a' (f_stmt @ p') b f) (inbound f) end in - H.iter (fun k _ -> List.iter (add [] k k) (H.find_default bw k [])) keep; + H.iter (fun k _ -> List.iter (add [] [] k k) (inbound k)) keep; H.clear ready; H.clear keep; - cfgF, cfgB + cfgF, cfgB, skippedStmts module type CfgPrinters = @@ -506,7 +543,7 @@ struct let p_node out n = Format.fprintf out "%s" (Node.show_id n) (* escape string in label, otherwise dot might fail *) - let p_edge (out: Format.formatter) x = Format.fprintf out "%s" (String.escaped (Pretty.sprint ~width:max_int (Edge.pretty () x))) + let p_edge (out: Format.formatter) x = Format.fprintf out "%s" (String.escaped (GobPretty.sprint Edge.pretty x)) let rec p_edges out = function | [] -> Format.fprintf out "" @@ -523,7 +560,7 @@ struct in let shape = match n with | Statement {skind=If (_,_,_,_,_); _} -> ["shape=diamond"] - | Statement _ -> [] (* use default shape *) + | Statement _ -> [] (* use default shape *) | Function _ | FunctionEntry _ -> ["shape=box"] in @@ -577,16 +614,16 @@ let fprint_hash_dot cfg = close_out out -let getCFG (file: file) : cfg * cfg = - let cfgF, cfgB = createCFG file in - let cfgF, cfgB = +let getCFG (file: file) : cfg * cfg * stmt list CfgEdgeH.t = + let cfgF, cfgB, skippedByEdge = createCFG file in + let cfgF, cfgB, skippedByEdge = if get_bool "exp.mincfg" then - Timing.wrap "minimizing the cfg" minimizeCFG (cfgF, cfgB) + Timing.wrap "minimizing the cfg" minimizeCFG (cfgF, cfgB) skippedByEdge else - (cfgF, cfgB) + (cfgF, cfgB, skippedByEdge) in if get_bool "justcfg" then fprint_hash_dot cfgB; - (fun n -> H.find_default cfgF n []), (fun n -> H.find_default cfgB n []) + (fun n -> H.find_default cfgF n []), (fun n -> H.find_default cfgB n []), skippedByEdge let iter_fd_edges (module Cfg : CfgBackward) fd = @@ -624,10 +661,10 @@ let dead_code_cfg (module FileCfg: MyCFG.FileCfg) live = match glob with | GFun (fd,loc) -> (* ignore (Printf.printf "fun: %s\n" fd.svar.vname); *) - let base_dir = Goblintutil.create_dir (Fpath.v "cfgs") in + let base_dir = GobSys.mkdir_or_exists_absolute (Fpath.v "cfgs") in let c_file_name = Str.global_substitute (Str.regexp Filename.dir_sep) (fun _ -> "%2F") loc.file in let dot_file_name = fd.svar.vname^".dot" in - let file_dir = Goblintutil.create_dir Fpath.(base_dir / c_file_name) in + let file_dir = GobSys.mkdir_or_exists_absolute Fpath.(base_dir / c_file_name) in let fname = Fpath.(file_dir / dot_file_name) in let out = open_out (Fpath.to_string fname) in let ppf = Format.formatter_of_out_channel out in @@ -647,12 +684,12 @@ let getGlobalInits (file: file) : edges = doInit (addOffsetLval offs lval) loc init is_zero; lval in - let rec all_index = function - | Index (e,o) -> Index (all_array_index_exp, all_index o) - | Field (f,o) -> Field (f, all_index o) + let rec any_index_offset = function + | Index (e,o) -> Index (Offset.Index.Exp.any, any_index_offset o) + | Field (f,o) -> Field (f, any_index_offset o) | NoOffset -> NoOffset in - let all_index (lh,offs) = lh, all_index offs in + let any_index (lh,offs) = lh, any_index_offset offs in match init with | SingleInit exp -> let assign lval = (loc, Assign (lval, exp)) in @@ -660,8 +697,8 @@ let getGlobalInits (file: file) : edges = Instead, we get one assign for each distinct value in the array *) if not fast_global_inits then Hashtbl.add inits (assign lval) () - else if not (Hashtbl.mem inits (assign (all_index lval))) then - Hashtbl.add inits (assign (all_index lval)) () + else if not (Hashtbl.mem inits (assign (any_index lval))) then + Hashtbl.add inits (assign (any_index lval)) () else () | CompoundInit (typ, lst) -> diff --git a/src/framework/constraints.ml b/src/framework/constraints.ml index e0a00be588..cc54c91a5a 100644 --- a/src/framework/constraints.ml +++ b/src/framework/constraints.ml @@ -1,6 +1,7 @@ -(** How to generate constraints for a solver using specifications described in [Analyses]. *) +(** Construction of a {{!Analyses.MonSystem} constraint system} from an {{!Analyses.Spec} analysis specification} and {{!MyCFG.CfgBackward} CFGs}. + Transformatons of analysis specifications as functors. *) -open Prelude +open Batteries open GoblintCil open MyCFG open Analyses @@ -8,6 +9,7 @@ open GobConfig 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) @@ -19,6 +21,11 @@ struct module G = S.G module C = S.C module V = S.V + module P = + struct + include S.P + let of_elt x = of_elt (D.unlift x) + end let name () = S.name () ^" hashconsed" @@ -26,8 +33,6 @@ struct let init = S.init let finalize = S.finalize - let should_join x y = S.should_join (D.unlift x) (D.unlift y) - let startstate v = D.lift (S.startstate v) let exitstate v = D.lift (S.exitstate v) let morphstate v d = D.lift (S.morphstate v (D.unlift d)) @@ -72,14 +77,23 @@ struct let special ctx r f args = D.lift @@ S.special (conv ctx) r f args - let combine ctx r fe f args fc es = - D.lift @@ S.combine (conv ctx) r fe f args fc (D.unlift es) + let combine_env ctx r fe f args fc es f_ask = + D.lift @@ S.combine_env (conv ctx) r fe f args fc (D.unlift es) f_ask + + let combine_assign ctx r fe f args fc es f_ask = + D.lift @@ S.combine_assign (conv ctx) r fe f args fc (D.unlift es) f_ask + + let threadenter ctx ~multiple lval f args = + List.map D.lift @@ S.threadenter (conv ctx) ~multiple lval f args - let threadenter ctx lval f args = - List.map D.lift @@ S.threadenter (conv ctx) lval f args + let threadspawn ctx ~multiple lval f args fctx = + D.lift @@ S.threadspawn (conv ctx) ~multiple lval f args (conv fctx) - let threadspawn ctx lval f args fctx = - D.lift @@ S.threadspawn (conv ctx) lval f args (conv fctx) + let paths_as_set ctx = + List.map (fun x -> D.lift x) @@ S.paths_as_set (conv ctx) + + let event ctx e octx = + D.lift @@ S.event (conv ctx) e (conv octx) end (** Lifts a [Spec] so that the context is [Hashcons]d. *) @@ -93,6 +107,7 @@ struct module G = S.G module C = Printable.HConsed (S.C) module V = S.V + module P = S.P let name () = S.name () ^" context hashconsed" @@ -100,8 +115,6 @@ struct let init = S.init let finalize = S.finalize - let should_join = S.should_join - let startstate = S.startstate let exitstate = S.exitstate let morphstate = S.morphstate @@ -148,14 +161,20 @@ struct let special ctx r f args = S.special (conv ctx) r f args - let combine ctx r fe f args fc es = - S.combine (conv ctx) r fe f args (Option.map C.unlift fc) es + let combine_env ctx r fe f args fc es f_ask = + S.combine_env (conv ctx) r fe f args (Option.map C.unlift fc) es f_ask + + let combine_assign ctx r fe f args fc es f_ask = + S.combine_assign (conv ctx) r fe f args (Option.map C.unlift fc) es f_ask - let threadenter ctx 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) end (* see option ana.opt.equal *) @@ -177,6 +196,11 @@ struct module G = S.G module C = S.C module V = S.V + module P = + struct + include S.P + let of_elt (x, _) = of_elt x + end let name () = S.name ()^" level sliced" @@ -190,8 +214,6 @@ struct let finalize = S.finalize - let should_join (x,_) (y,_) = S.should_join x y - let startstate v = (S.startstate v, !start_level) let exitstate v = (S.exitstate v, !start_level) let morphstate v (d,l) = (S.morphstate v d, l) @@ -224,10 +246,11 @@ struct let asm ctx = lift_fun ctx (lift ctx) S.asm identity let skip ctx = lift_fun ctx (lift ctx) S.skip identity let special ctx r f args = lift_fun ctx (lift ctx) S.special ((|>) args % (|>) f % (|>) r) - let combine' ctx r fe f args fc es = lift_fun ctx (lift ctx) S.combine (fun p -> p r fe f args fc (fst es)) + 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 @@ -242,6 +265,13 @@ struct | `Lifted x -> `Lifted (Int64.add x 1L) | x -> x + let paths_as_set ctx = + let liftmap = List.map (fun x -> (x, snd ctx.local)) in + lift_fun ctx liftmap S.paths_as_set (Fun.id) + + let event ctx e octx = + lift_fun ctx (lift ctx) S.event ((|>) (conv octx) % (|>) e) + let enter ctx r f args = let (d,l) = ctx.local in if leq0 l then @@ -249,13 +279,22 @@ struct else enter' {ctx with local=(d, sub1 l)} r f args - let combine ctx r fe f args fc es = + let combine_env ctx r fe f args fc es f_ask = let (d,l) = ctx.local in let l = add1 l in if leq0 l then (d, l) else - let d',_ = combine' ctx r fe f args fc es in + let d',_ = combine_env' ctx r fe f args fc es f_ask in + (d', l) + + let combine_assign ctx r fe f args fc es f_ask = + let (d,l) = ctx.local in + (* No need to add1 here, already done in combine_env. *) + if leq0 l then + (d, l) + else + let d',_ = combine_assign' ctx r fe f args fc es f_ask in (d', l) let query ctx (type a) (q: a Queries.t): a Queries.result = @@ -263,7 +302,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 @@ -287,7 +326,7 @@ struct let h = H.create 13 let incr k = H.modify_def 1 k (fun v -> - if v >= !limit then failwith ("LimitLifter: Reached limit ("^string_of_int !limit^") for node "^Ana.sprint Node.pretty_plain_short (Option.get !MyCFG.current_node)); + if v >= !limit then failwith (GobPretty.sprintf "LimitLifter: Reached limit (%d) for node %a" !limit Node.pretty_plain_short (Option.get !MyCFG.current_node)); v+1 ) h; module D = struct @@ -315,6 +354,11 @@ struct module G = S.G module C = S.C module V = S.V + module P = + struct + include S.P + let of_elt (x, _) = of_elt x + end let name () = S.name ()^" with widened contexts" @@ -323,8 +367,6 @@ struct let init = S.init let finalize = S.finalize - let should_join (x,_) (y,_) = S.should_join x y - let inj f x = f x, M.bot () let startstate = inj S.startstate @@ -350,8 +392,10 @@ struct let skip ctx = lift_fun ctx S.skip identity let special ctx r f args = lift_fun ctx S.special ((|>) args % (|>) f % (|>) r) - 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 event ctx e octx = lift_fun ctx S.event ((|>) (conv octx) % (|>) e) + + let threadenter ctx ~multiple lval f args = S.threadenter (conv ctx) ~multiple lval f args |> List.map (fun d -> (d, snd ctx.local)) + let threadspawn ctx ~multiple lval f args fctx = lift_fun ctx (S.threadspawn ~multiple) ((|>) (conv fctx) % (|>) args % (|>) f % (|>) lval) let enter ctx r f args = let m = snd ctx.local in @@ -368,7 +412,12 @@ struct S.enter (conv ctx) r f args |> List.map (fun (c,v) -> (c,m), d' v) (* c: caller, v: callee *) - let combine ctx r fe f args fc es = lift_fun ctx S.combine (fun p -> p r fe f args fc (fst es)) + let paths_as_set ctx = + let m = snd ctx.local in + S.paths_as_set (conv ctx) |> List.map (fun v -> (v,m)) + + let combine_env ctx r fe f args fc es f_ask = lift_fun ctx S.combine_env (fun p -> p r fe f args fc (fst es) f_ask) + let combine_assign ctx r fe f args fc es f_ask = lift_fun ctx S.combine_assign (fun p -> p r fe f args fc (fst es) f_ask) end @@ -383,6 +432,14 @@ struct module G = S.G module C = S.C module V = S.V + module P = + struct + include Printable.Option (S.P) (struct let name = "None" end) + + let of_elt = function + | `Lifted x -> Some (S.P.of_elt x) + | _ -> None + end let name () = S.name ()^" lifted" @@ -390,11 +447,6 @@ struct let init = S.init let finalize = S.finalize - let should_join x y = - match x, y with - | `Lifted a, `Lifted b -> S.should_join a b - | _ -> true - let startstate v = `Lifted (S.startstate v) let exitstate v = `Lifted (S.exitstate v) let morphstate v d = try `Lifted (S.morphstate v (D.unlift d)) with Deadcode -> d @@ -416,6 +468,10 @@ struct let liftmap = List.map (fun (x,y) -> D.lift x, D.lift y) in lift_fun ctx liftmap S.enter ((|>) args % (|>) f % (|>) r) [] + let paths_as_set ctx = + let liftmap = List.map (fun x -> D.lift x) in + lift_fun ctx liftmap S.paths_as_set (Fun.id) [D.bot ()] (* One dead path instead of none, such that combine_env gets called for functions with dead normal return (and thus longjmpy returns can be correctly handled by lifter). *) + let query ctx (type a) (q: a Queries.t): a Queries.result = lift_fun ctx identity S.query (fun (x) -> x q) (Queries.Result.bot q) let assign ctx lv e = lift_fun ctx D.lift S.assign ((|>) e % (|>) lv) `Bot @@ -426,10 +482,13 @@ struct let asm ctx = lift_fun ctx D.lift S.asm identity `Bot let skip ctx = lift_fun ctx D.lift S.skip identity `Bot let special ctx r f args = lift_fun ctx D.lift S.special ((|>) args % (|>) f % (|>) r) `Bot - let combine ctx r fe f args fc es = lift_fun ctx D.lift S.combine (fun p -> p r fe f args fc (D.unlift es)) `Bot + let combine_env ctx r fe f args fc es f_ask = lift_fun ctx D.lift S.combine_env (fun p -> p r fe f args fc (D.unlift es) f_ask) `Bot + let combine_assign ctx r fe f args fc es f_ask = lift_fun ctx D.lift S.combine_assign (fun p -> p r fe f args fc (D.unlift es) f_ask) `Bot - let threadenter ctx 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 module type Increment = @@ -450,8 +509,8 @@ struct | `G x -> `G (GV.relift x) let pretty_trace () = function - | `L a -> LV.pretty_trace () a - | `G a -> GV.pretty_trace () a + | `L a -> Pretty.dprintf "L:%a" LV.pretty_trace a + | `G a -> Pretty.dprintf "G:%a" GV.pretty_trace a let printXml f = function | `L a -> LV.printXml f a @@ -494,15 +553,17 @@ struct 2. fundec -> set of S.C -- used for IterSysVars Node *) let sync ctx = - match Cfg.prev ctx.prev_node with - | _ :: _ :: _ -> S.sync ctx `Join - | _ -> S.sync ctx `Normal + match ctx.prev_node, Cfg.prev ctx.prev_node with + | _, _ :: _ :: _ (* Join in CFG. *) + | FunctionEntry _, _ -> (* Function entry, also needs sync because partial contexts joined by solver, see 00-sanity/35-join-contexts. *) + S.sync ctx `Join + | _, _ -> S.sync ctx `Normal let side_context sideg f c = - if !GU.postsolving then + 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 ... *) @@ -520,12 +581,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 @@ -557,14 +618,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) @@ -615,25 +676,56 @@ struct let ctx, r, spawns = common_ctx var edge prev_node d getl sidel getg sideg in common_join ctx (S.branch ctx e tv) !r !spawns - let tf_normal_call ctx lv e (f:fundec) args getl sidel getg sideg = + let tf_normal_call ctx lv e (f:fundec) args getl sidel getg sideg = let combine (cd, fc, fd) = if M.tracing then M.traceli "combine" "local: %a\n" S.D.pretty cd; - (* Extra sync in case function has multiple returns. - Each `Return sync is done before joining, so joined value may be unsound. - Since sync is normally done before tf (in common_ctx), simulate it here for fd. *) - (* TODO: don't do this extra sync here *) - let fd = - (* TODO: more accurate ctx? *) - let rec sync_ctx = { ctx with + if M.tracing then M.trace "combine" "function: %a\n" S.D.pretty fd; + let rec cd_ctx = + { ctx with + ask = (fun (type a) (q: a Queries.t) -> S.query cd_ctx q); + local = cd; + } + in + let fd_ctx = + (* Inner scope to prevent unsynced fd_ctx from being used. *) + (* Extra sync in case function has multiple returns. + Each `Return sync is done before joining, so joined value may be unsound. + Since sync is normally done before tf (in common_ctx), simulate it here for fd. *) + (* TODO: don't do this extra sync here *) + let rec sync_ctx = + { ctx with ask = (fun (type a) (q: a Queries.t) -> S.query sync_ctx q); local = fd; - prev_node = Function f + prev_node = Function f; + } + in + (* TODO: more accurate ctx? *) + let synced = sync sync_ctx in + let rec fd_ctx = + { sync_ctx with + ask = (fun (type a) (q: a Queries.t) -> S.query fd_ctx q); + local = synced; } in - sync sync_ctx + fd_ctx + in + let r = List.fold_left (fun acc fd1 -> + let rec fd1_ctx = + { fd_ctx with + ask = (fun (type a) (q: a Queries.t) -> S.query fd1_ctx q); + local = fd1; + } + in + let combine_enved = S.combine_env cd_ctx lv e f args fc fd1_ctx.local (Analyses.ask_of_ctx fd1_ctx) in + let rec combine_assign_ctx = + { cd_ctx with + ask = (fun (type a) (q: a Queries.t) -> S.query combine_assign_ctx q); + local = combine_enved; + } + in + S.D.join acc (S.combine_assign combine_assign_ctx lv e f args fc fd1_ctx.local (Analyses.ask_of_ctx fd1_ctx)) + ) (S.D.bot ()) (S.paths_as_set fd_ctx) in - if M.tracing then M.trace "combine" "function: %a\n" S.D.pretty fd; - let r = S.combine {ctx with local = cd} lv e f args fc fd in if M.tracing then M.traceu "combine" "combined local: %a\n" S.D.pretty r; r in @@ -641,7 +733,8 @@ struct let paths = List.map (fun (c,v) -> (c, S.context f v, v)) paths in List.iter (fun (c,fc,v) -> if not (S.D.is_bot v) then sidel (FunctionEntry f, fc) v) paths; let paths = List.map (fun (c,fc,v) -> (c, fc, if S.D.is_bot v then v else getl (Function f, fc))) paths in - let paths = List.filter (fun (c,fc,v) -> not (D.is_bot v)) paths in + (* Don't filter bot paths, otherwise LongjmpLifter is not called. *) + (* let paths = List.filter (fun (c,fc,v) -> not (D.is_bot v)) paths in *) let paths = List.map (Tuple3.map2 Option.some) paths in if M.tracing then M.traceli "combine" "combining\n"; let paths = List.map combine paths in @@ -661,8 +754,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 @@ -683,16 +776,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 @@ -751,7 +845,7 @@ struct Timing.Program.enter new_fd.svar.vname; let old_context = !M.current_context in current_node := Some u; - M.current_context := Some (Obj.repr c); + M.current_context := Some (Obj.magic c); (* magic is fine because Spec is top-level Control Spec *) Fun.protect ~finally:(fun () -> current_node := old_node; M.current_context := old_context; @@ -776,15 +870,17 @@ struct (* TODO: Is it possible to do soundly for multi-entry loops? *) let stricts = NodeH.find_default scc.prev v [] in let xs_stricts = List.map tf' stricts in + (* Evaluate non-strict for dead code warnings. See 00-sanity/36-strict-loop-dead. *) + let equal = [%eq: (CilType.Location.t * Edge.t) list * Node.t] in + let is_strict eu = List.exists (equal eu) stricts in + let non_stricts = List.filter (neg is_strict) (Cfg.prev v) in + let xs_non_stricts = List.map tf' non_stricts in if List.for_all S.D.is_bot xs_stricts then S.D.bot () - else + else ( let xs_strict = List.fold_left S.D.join (S.D.bot ()) xs_stricts in - let equal = [%eq: (CilType.Location.t * Edge.t) list * Node.t] in - let is_strict eu = List.exists (equal eu) stricts in - let non_stricts = List.filter (neg is_strict) (Cfg.prev v) in - let xs_non_stricts = List.map tf' non_stricts in List.fold_left S.D.join xs_strict xs_non_stricts + ) | _ -> let xs = List.map tf' (Cfg.prev v) in List.fold_left S.D.join (S.D.bot ()) xs @@ -803,7 +899,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.") } @@ -829,7 +925,7 @@ struct | Some {changes; _} -> changes | None -> empty_change_info () in - List.(Printf.printf "change_info = { unchanged = %d; changed = %d; added = %d; removed = %d }\n" (length c.unchanged) (length c.changed) (length c.added) (length c.removed)); + List.(Printf.printf "change_info = { unchanged = %d; changed = %d (with unchangedHeader = %d); added = %d; removed = %d }\n" (length c.unchanged) (length c.changed) (BatList.count_matching (fun c -> c.unchangedHeader) c.changed) (length c.added) (length c.removed)); let changed_funs = List.filter_map (function | {old = {def = Some (Fun f); _}; diff = None; _} -> @@ -1095,13 +1191,13 @@ struct module D = struct (* TODO is it really worth it to check every time instead of just using sets and joining later? *) - module C = + module R = struct + include Spec.P type elt = Spec.D.t - let cong = Spec.should_join end module J = SetDomain.Joined (Spec.D) - include DisjointDomain.PairwiseSet (Spec.D) (J) (C) + include DisjointDomain.ProjectiveSet (Spec.D) (J) (R) let name () = "PathSensitive (" ^ name () ^ ")" let printXml f x = @@ -1114,6 +1210,7 @@ struct module G = Spec.G module C = Spec.C module V = Spec.V + module P = UnitP let name () = "PathSensitive2("^Spec.name ()^")" @@ -1121,8 +1218,6 @@ struct let init = Spec.init let finalize = Spec.finalize - let should_join x y = true - let exitstate v = D.singleton (Spec.exitstate v) let startstate v = D.singleton (Spec.startstate v) let morphstate v d = D.map (Spec.morphstate v) d @@ -1164,12 +1259,17 @@ struct let skip ctx = map ctx Spec.skip identity let special ctx l f a = map ctx Spec.special (fun h -> h l f a) - let threadenter ctx lval f args = + let event ctx e octx = + let fd1 = D.choose octx.local in + map ctx Spec.event (fun h -> h e (conv octx fd1)) + + let threadenter ctx ~multiple lval f args = let g xs ys = (List.map (fun y -> D.singleton y) ys) @ xs in - fold' ctx Spec.threadenter (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) @@ -1183,13 +1283,34 @@ struct let g xs ys = (List.map (fun (x,y) -> D.singleton x, D.singleton y) ys) @ xs in fold' ctx Spec.enter (fun h -> h l f a) g [] - let combine ctx l fe f a fc d = + let paths_as_set ctx = + (* Path-sensitivity is only here, not below! *) + let elems = D.elements ctx.local in + List.map (D.singleton) elems + + let combine_env ctx l fe f a fc d f_ask = + assert (D.cardinal ctx.local = 1); + let cd = D.choose ctx.local in + let k x y = + if M.tracing then M.traceli "combine" "function: %a\n" Spec.D.pretty x; + try + let r = Spec.combine_env (conv ctx cd) l fe f a fc x f_ask in + if M.tracing then M.traceu "combine" "combined function: %a\n" Spec.D.pretty r; + D.add r y + with Deadcode -> + if M.tracing then M.traceu "combine" "combined function: dead\n"; + y + in + let d = D.fold k d (D.bot ()) in + if D.is_bot d then raise Deadcode else d + + let combine_assign ctx l fe f a fc d f_ask = assert (D.cardinal ctx.local = 1); let cd = D.choose ctx.local in let k x y = if M.tracing then M.traceli "combine" "function: %a\n" Spec.D.pretty x; try - let r = Spec.combine (conv ctx cd) l fe f a fc x in + let r = Spec.combine_assign (conv ctx cd) l fe f a fc x f_ask in if M.tracing then M.traceu "combine" "combined function: %a\n" Spec.D.pretty r; D.add r y with Deadcode -> @@ -1213,6 +1334,7 @@ struct module V = struct include Printable.Either (S.V) (Node) + let name () = "DeadBranch" let s x = `Left x let node x = `Right x let is_write_only = function @@ -1220,11 +1342,16 @@ struct | `Right _ -> true end - module EM = MapDomain.MapBot (Basetype.CilExp) (Basetype.Bools) + module EM = + struct + include MapDomain.MapBot (Basetype.CilExp) (Basetype.Bools) + let name () = "branches" + end module G = struct include Lattice.Lift2 (S.G) (EM) (Printable.DefaultNames) + let name () = "deadbranch" let s = function | `Bot -> S.G.bot () @@ -1265,6 +1392,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 *) @@ -1298,7 +1426,7 @@ struct let branch ctx = S.branch (conv ctx) let branch ctx exp tv = - if !GU.postsolving then ( + if !AnalysisState.postsolving then ( try let r = branch ctx exp tv in (* branch is live *) @@ -1317,15 +1445,256 @@ struct let assign ctx = S.assign (conv ctx) let vdecl ctx = S.vdecl (conv ctx) let enter ctx = S.enter (conv ctx) + let paths_as_set ctx = S.paths_as_set (conv ctx) let body ctx = S.body (conv ctx) let return ctx = S.return (conv ctx) - let combine ctx = S.combine (conv ctx) + let combine_env ctx = S.combine_env (conv ctx) + let combine_assign ctx = S.combine_assign (conv ctx) let special ctx = S.special (conv ctx) let threadenter ctx = S.threadenter (conv ctx) - let threadspawn ctx 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 + +module LongjmpLifter (S: Spec): Spec = +struct + include S + + let name () = "Longjmp (" ^ S.name () ^ ")" + + module V = + struct + include Printable.Either (S.V) (Printable.Either (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 is_write_only = function + | `Left x -> S.V.is_write_only x + | `Right _ -> false + end + + module G = + struct + include Lattice.Lift2 (S.G) (S.D) (Printable.DefaultNames) + + let s = function + | `Bot -> S.G.bot () + | `Lifted1 x -> x + | _ -> failwith "LongjmpLifter.s" + let local = function + | `Bot -> S.D.bot () + | `Lifted2 x -> x + | _ -> failwith "LongjmpLifter.local" + let create_s s = `Lifted1 s + let create_local local = `Lifted2 local + + let printXml f = function + | `Lifted1 x -> S.G.printXml f x + | `Lifted2 x -> BatPrintf.fprintf f "%a" S.D.printXml x + | x -> BatPrintf.fprintf f "%a" printXml x + end + + let conv (ctx: (_, G.t, _, V.t) ctx): (_, S.G.t, _, S.V.t) ctx = + { ctx with + global = (fun v -> G.s (ctx.global (V.s v))); + sideg = (fun v g -> ctx.sideg (V.s v) (G.create_s g)); + } + + let query ctx (type a) (q: a Queries.t): a Queries.result = + match q with + | WarnGlobal g -> + let g: V.t = Obj.obj g in + begin match g with + | `Left g -> + S.query (conv ctx) (WarnGlobal (Obj.repr g)) + | `Right g -> + Queries.Result.top q + end + | InvariantGlobal g -> + let g: V.t = Obj.obj g in + begin match g with + | `Left g -> + S.query (conv ctx) (InvariantGlobal (Obj.repr g)) + | `Right g -> + Queries.Result.top q + end + | IterSysVars (vq, vf) -> + (* vars for S *) + let vf' x = vf (Obj.repr (V.s (Obj.obj x))) in + S.query (conv ctx) (IterSysVars (vq, vf')); + (* TODO: vars? *) + | _ -> + S.query (conv ctx) q + + + let branch ctx = S.branch (conv ctx) + let assign ctx = S.assign (conv ctx) + let vdecl ctx = S.vdecl (conv ctx) + let enter ctx = S.enter (conv ctx) + let paths_as_set ctx = S.paths_as_set (conv ctx) + let body ctx = S.body (conv ctx) + let return ctx = S.return (conv ctx) + + let combine_env ctx lv e f args fc fd f_ask = + let conv_ctx = conv ctx in + let current_fundec = Node.find_fundec ctx.node in + let handle_longjmp (cd, fc, longfd) = + (* This is called per-path. *) + let rec cd_ctx = + { conv_ctx with + ask = (fun (type a) (q: a Queries.t) -> S.query cd_ctx q); + local = cd; + } + in + let longfd_ctx = + (* Inner scope to prevent unsynced longfd_ctx from being used. *) + (* Extra sync like with normal combine. *) + let rec sync_ctx = + { conv_ctx with + ask = (fun (type a) (q: a Queries.t) -> S.query sync_ctx q); + local = longfd; + prev_node = Function f; + } + in + let synced = S.sync sync_ctx `Join in + let rec longfd_ctx = + { sync_ctx with + ask = (fun (type a) (q: a Queries.t) -> S.query longfd_ctx q); + local = synced; + } + in + longfd_ctx + in + let combined = lazy ( (* does not depend on target, do at most once *) + (* Globals are non-problematic here, as they are always carried around without any issues! *) + (* A combine call is mostly needed to ensure locals have appropriate values. *) + (* Using f from called function on purpose here! Needed? *) + S.combine_env cd_ctx None e f args fc longfd_ctx.local (Analyses.ask_of_ctx longfd_ctx) (* no lval because longjmp return skips return value assignment *) + ) + in + let returned = lazy ( (* does not depend on target, do at most once *) + let rec combined_ctx = + { cd_ctx with + ask = (fun (type a) (q: a Queries.t) -> S.query combined_ctx q); + local = Lazy.force combined; + } + in + S.return combined_ctx None current_fundec + ) + in + let (active_targets, _) = longfd_ctx.ask ActiveJumpBuf in + let valid_targets = cd_ctx.ask ValidLongJmp in + let handle_target target = match target with + | JmpBufDomain.BufferEntryOrTop.AllTargets -> () (* The warning is already emitted at the point where the longjmp happens *) + | Target (target_node, target_context) -> + let target_fundec = Node.find_fundec target_node in + if CilType.Fundec.equal target_fundec current_fundec && ControlSpecC.equal target_context (ctx.control_context ()) then ( + if M.tracing then Messages.tracel "longjmp" "Fun: Potentially from same context, side-effect to %a\n" Node.pretty target_node; + ctx.sideg (V.longjmpto (target_node, ctx.context ())) (G.create_local (Lazy.force combined)) + (* No need to propagate this outwards here, the set of valid longjumps is part of the context, we can never have the same context setting the longjmp multiple times *) + ) + (* Appropriate setjmp is not in current function & current context *) + else if JmpBufDomain.JmpBufSet.mem target valid_targets then + ctx.sideg (V.longjmpret (current_fundec, ctx.context ())) (G.create_local (Lazy.force returned)) + else + (* It actually is not handled here but was propagated here spuriously, we already warned at the location where this issue is caused *) + (* As the validlongjumps inside the callee is a a superset of the ones inside the caller *) + () + in + JmpBufDomain.JmpBufSet.iter handle_target active_targets + in + if M.tracing then M.tracel "longjmp" "longfd getg %a\n" CilType.Fundec.pretty f; + let longfd = G.local (ctx.global (V.longjmpret (f, Option.get fc))) in + if M.tracing then M.tracel "longjmp" "longfd %a\n" D.pretty longfd; + if not (D.is_bot longfd) then + handle_longjmp (ctx.local, fc, longfd); + S.combine_env (conv_ctx) lv e f args fc fd f_ask + + let combine_assign ctx lv e f args fc fd f_ask = + S.combine_assign (conv ctx) lv e f args fc fd f_ask + + let special ctx lv f args = + let conv_ctx = conv ctx in + match (LibraryFunctions.find f).special args with + | Setjmp {env} -> + (* Handling of returning for the first time *) + let normal_return = S.special conv_ctx lv f args in + let jmp_return = G.local (ctx.global (V.longjmpto (ctx.prev_node, ctx.context ()))) in + if S.D.is_bot jmp_return then + normal_return + else ( + let rec jmp_ctx = + { conv_ctx with + ask = (fun (type a) (q: a Queries.t) -> S.query jmp_ctx q); + local = jmp_return; + } + in + let longjmped = S.event jmp_ctx (Events.Longjmped {lval=lv}) jmp_ctx in + S.D.join normal_return longjmped + ) + | Longjmp {env; value} -> + let current_fundec = Node.find_fundec ctx.node in + let handle_path path = ( + let rec path_ctx = + { conv_ctx with + ask = (fun (type a) (q: a Queries.t) -> S.query path_ctx q); + local = path; + } + in + let specialed = lazy ( (* does not depend on target, do at most once *) + S.special path_ctx lv f args + ) + in + let returned = lazy ( (* does not depend on target, do at most once *) + let rec specialed_ctx = + { path_ctx with + ask = (fun (type a) (q: a Queries.t) -> S.query specialed_ctx q); + local = Lazy.force specialed; + } + in + S.return specialed_ctx None current_fundec + ) + in + (* Eval `env` again to avoid having to construct bespoke ctx to ask *) + let targets = path_ctx.ask (EvalJumpBuf env) in + let valid_targets = path_ctx.ask ValidLongJmp in + if M.tracing then Messages.tracel "longjmp" "Jumping to %a\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.msg_final Error ~category:Unsound ~tags:[Category Imprecise; Category Call] "Longjmp to unknown target ignored" + | Target (target_node, target_context) -> + let target_fundec = Node.find_fundec target_node in + if CilType.Fundec.equal target_fundec current_fundec && ControlSpecC.equal target_context (ctx.control_context ()) then ( + if M.tracing then Messages.tracel "longjmp" "Potentially from same context, side-effect to %a\n" Node.pretty target_node; + ctx.sideg (V.longjmpto (target_node, ctx.context ())) (G.create_local (Lazy.force specialed)) + ) + else if JmpBufDomain.JmpBufSet.mem target valid_targets then ( + if M.tracing then Messages.tracel "longjmp" "Longjmp to somewhere else, side-effect to %i\n" (S.C.hash (ctx.context ())); + ctx.sideg (V.longjmpret (current_fundec, ctx.context ())) (G.create_local (Lazy.force returned)) + ) + else + M.warn ~category:(Behavior (Undefined Other)) "Longjmp to potentially invalid target! (Target %a in Function %a which may have already returned or is in a different thread)" Node.pretty target_node CilType.Fundec.pretty target_fundec + in + if JmpBufDomain.JmpBufSet.is_empty targets then + M.warn ~category:(Behavior (Undefined Other)) "Longjmp to potentially invalid target (%a is bot?!)" d_exp env + else + JmpBufDomain.JmpBufSet.iter handle_target targets + ) + in + List.iter handle_path (S.paths_as_set conv_ctx); + S.D.bot () + | _ -> S.special conv_ctx lv f args + let threadenter ctx = S.threadenter (conv ctx) + let threadspawn ctx ~multiple lv f args fctx = S.threadspawn (conv ctx) ~multiple lv f args (conv fctx) let sync ctx = S.sync (conv ctx) let skip ctx = S.skip (conv ctx) let asm ctx = S.asm (conv ctx) + let event ctx e octx = S.event (conv ctx) e (conv octx) end module CompareGlobSys (SpecSys: SpecSys) = @@ -1467,6 +1836,7 @@ struct struct include Printable.Std include Var + let name () = "var" let pretty = pretty_trace include Printable.SimplePretty ( diff --git a/src/framework/control.ml b/src/framework/control.ml index aa417fef75..5c938cfd08 100644 --- a/src/framework/control.ml +++ b/src/framework/control.ml @@ -1,6 +1,8 @@ +(** Main internal functionality: analysis of the program by abstract interpretation via constraint solving. *) + (** An analyzer that takes the CFG from [MyCFG], a solver from [Selector], constraints from [Constraints] (using the specification from [MCP]) *) -open Prelude +open Batteries open GoblintCil open MyCFG open Analyses @@ -12,6 +14,7 @@ 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 "witness.graphml.enabled" || get_bool "exp.arg" in 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 @@ -19,10 +22,10 @@ let spec_module: (module Spec) Lazy.t = lazy ( (module MCP.MCP2 : Spec) |> lift true (module WidenContextLifterSide) (* option checked in functor *) (* hashcons before witness to reduce duplicates, because witness re-uses contexts in domain and requires tag for PathSensitive3 *) - |> lift (get_bool "ana.opt.hashcons" || get_bool "ana.sv-comp.enabled") (module HashconsContextLifter) - |> lift (get_bool "ana.sv-comp.enabled") (module HashconsLifter) - |> lift (get_bool "ana.sv-comp.enabled") (module WitnessConstraints.PathSensitive3) - |> lift (not (get_bool "ana.sv-comp.enabled")) (module PathSensitive2) + |> lift (get_bool "ana.opt.hashcons" || arg_enabled) (module HashconsContextLifter) + |> lift arg_enabled (module HashconsLifter) + |> lift arg_enabled (module WitnessConstraints.PathSensitive3) + |> lift (not arg_enabled) (module PathSensitive2) |> lift (get_bool "ana.dead-code.branches") (module DeadBranchLifter) |> lift true (module DeadCodeLifter) |> lift (get_bool "dbg.slice.on") (module LevelSliceLifter) @@ -32,9 +35,10 @@ let spec_module: (module Spec) Lazy.t = lazy ( (* Widening tokens must be outside of hashcons, because widening token domain ignores token sets for identity, so hashcons doesn't allow adding tokens. Also must be outside of deadcode, because deadcode splits (like mutex lock event) don't pass on tokens. *) |> lift (get_bool "ana.widen.tokens") (module WideningTokens.Lifter) + |> lift true (module LongjmpLifter) ) in GobConfig.building_spec := false; - Analyses.control_spec_c := (module S1.C); + ControlSpecC.control_spec_c := (module S1.C); (module S1) ) @@ -42,7 +46,9 @@ let spec_module: (module Spec) Lazy.t = lazy ( let get_spec (): (module Spec) = Lazy.force spec_module -let current_node_state_json : (Node.t -> Yojson.Safe.t) ref = ref (fun _ -> assert false) +let current_node_state_json : (Node.t -> Yojson.Safe.t option) ref = ref (fun _ -> None) + +let current_varquery_global_state_json: (VarQuery.t option -> Yojson.Safe.t) ref = ref (fun _ -> `Null) (** Given a [Cfg], a [Spec], and an [Inc], computes the solution to [MCP.Path] *) module AnalyzeCFG (Cfg:CfgBidir) (Spec:Spec) (Inc:Increment) = @@ -98,19 +104,24 @@ struct let live_lines = ref StringMap.empty in let dead_lines = ref StringMap.empty in let add_one n v = - (* Not using Node.location here to have updated locations in incremental analysis. - See: https://github.com/goblint/analyzer/issues/290#issuecomment-881258091. *) - let l = UpdateCil.getLoc n in - let f = Node.find_fundec n in - 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 - if is_dead then ( - dead_lines := StringMap.modify_def StringMap.empty l.file add_file !dead_lines - ) else ( - live_lines := StringMap.modify_def StringMap.empty l.file add_file !live_lines; - NH.add live_nodes n () - ); + match n with + | Statement s when Cilfacade.(StmtH.mem pseudo_return_to_fun s) -> + (* Exclude pseudo returns from dead lines counting. No user code at "}". *) + () + | _ -> + (* Not using Node.location here to have updated locations in incremental analysis. + See: https://github.com/goblint/analyzer/issues/290#issuecomment-881258091. *) + let l = UpdateCil.getLoc n in + let f = Node.find_fundec n in + 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 + if is_dead then ( + dead_lines := StringMap.modify_def StringMap.empty l.file add_file !dead_lines + ) else ( + live_lines := StringMap.modify_def StringMap.empty l.file add_file !live_lines; + NH.add live_nodes n () + ); in Result.iter add_one xs; let live_count = StringMap.fold (fun _ file_lines acc -> @@ -154,7 +165,7 @@ struct ) xs [] in let msgs = List.rev msgs in (* lines in ascending order *) - M.msg_group Warning ~category:Deadcode "Function '%s' has dead code" f msgs + M.msg_group Warning ~category:Deadcode "Function '%s' has dead code" f msgs (* TODO: function location for group *) in let warn_file f = StringMap.iter (warn_func f) in if get_bool "ana.dead-code.lines" then ( @@ -198,7 +209,7 @@ struct res (** The main function to preform the selected analyses. *) - let analyze (file: file) (startfuns, exitfuns, otherfuns: Analyses.fundecs) = + let analyze (file: file) (startfuns, exitfuns, otherfuns: Analyses.fundecs) skippedByEdge = let module FileCfg: FileCfg = struct let file = file @@ -206,7 +217,7 @@ struct end in - Goblintutil.should_warn := false; (* reset for server mode *) + AnalysisState.should_warn := false; (* reset for server mode *) (* exctract global xml from result *) let make_global_fast_xml f g = @@ -232,7 +243,9 @@ struct | {vname = ("__tzname" | "__daylight" | "__timezone"); _} (* unix time.h *) | {vname = ("tzname" | "daylight" | "timezone"); _} (* unix time.h *) | {vname = "getdate_err"; _} (* unix time.h, but somehow always in MacOS even without include *) - | {vname = ("stdin" | "stdout" | "stderr"); _} -> (* standard stdio.h *) + | {vname = ("stdin" | "stdout" | "stderr"); _} (* standard stdio.h *) + | {vname = ("optarg" | "optind" | "opterr" | "optopt" ); _} (* unix unistd.h *) + | {vname = ("__environ"); _} -> (* Linux Standard Base Core Specification *) true | _ -> false in @@ -267,7 +280,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)) } @@ -310,7 +323,7 @@ struct in let print_globals glob = - let out = M.get_out (Spec.name ()) !GU.out in + let out = M.get_out (Spec.name ()) !M.out in let print_one v st = ignore (Pretty.fprintf out "%a -> %a\n" EQSys.GVar.pretty_trace v EQSys.G.pretty st) in @@ -321,8 +334,8 @@ struct if get_bool "ana.sv-comp.enabled" then Witness.init (module FileCfg); (* TODO: move this out of analyze_loop *) - GU.global_initialization := true; - GU.earlyglobs := get_bool "exp.earlyglobs"; + AnalysisState.global_initialization := true; + GobConfig.earlyglobs := get_bool "exp.earlyglobs"; let marshal: Spec.marshal option = if get_string "load_run" <> "" then Some (Serialize.unmarshal Fpath.(v (get_string "load_run") / "spec_marshal")) @@ -333,10 +346,10 @@ struct in (* Some happen in init, so enable this temporarily (if required by option). *) - Goblintutil.should_warn := PostSolverArg.should_warn; + AnalysisState.should_warn := PostSolverArg.should_warn; Spec.init marshal; Access.init file; - Goblintutil.should_warn := false; + AnalysisState.should_warn := false; let test_domain (module D: Lattice.S): unit = let module DP = DomainProperties.All (D) in @@ -372,7 +385,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)) } @@ -404,13 +417,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 *) @@ -419,7 +432,7 @@ struct if startvars = [] then failwith "BUG: Empty set of start variables; may happen if enter_func of any analysis returns an empty list."; - GU.global_initialization := false; + AnalysisState.global_initialization := false; let startvars' = if get_bool "exp.forward" then @@ -496,7 +509,7 @@ struct in if get_bool "dbg.verbose" then print_endline ("Solving the constraint system with " ^ get_string "solver" ^ ". Solver statistics are shown every " ^ string_of_int (get_int "dbg.solver-stats-interval") ^ "s or by signal " ^ get_string "dbg.solver-signal" ^ "."); - Goblintutil.should_warn := get_string "warn_at" = "early" || gobview; + AnalysisState.should_warn := get_string "warn_at" = "early" || gobview; let (lh, gh), solver_data = Timing.wrap "solving" (Slvr.solve entrystates entrystates_global startvars') solver_data in if GobConfig.get_bool "incremental.save" then Serialize.Cache.(update_data SolverData solver_data); @@ -510,27 +523,26 @@ struct let warnings = Fpath.(save_run / "warnings.marshalled") in let stats = Fpath.(save_run / "stats.marshalled") in if get_bool "dbg.verbose" then ( - Format.printf "Saving the current configuration to %a, meta-data about this run to %a, and solver statistics to %a" Fpath.pp config Fpath.pp meta Fpath.pp solver_stats; + Format.printf "Saving the current configuration to %a, meta-data about this run to %a, and solver statistics to %a\n" Fpath.pp config Fpath.pp meta Fpath.pp solver_stats; ); GobSys.mkdir_or_exists save_run; 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 = GU.command_line; version = Version.goblint; timestamp = Unix.time (); localtime = 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; *) Yojson.Safe.pretty_to_channel (Stdlib.open_out (Fpath.to_string meta)) Meta.json; (* the above is compact, this is pretty-printed *) if gobview then ( if get_bool "dbg.verbose" then ( - Format.printf "Saving the analysis table to %a, the CIL state to %a, the warning table to %a, and the runtime stats to %a" Fpath.pp analyses Fpath.pp cil Fpath.pp warnings Fpath.pp stats; + Format.printf "Saving the analysis table to %a, the CIL state to %a, the warning table to %a, and the runtime stats to %a\n" Fpath.pp analyses Fpath.pp cil Fpath.pp warnings Fpath.pp stats; ); Serialize.marshal MCPRegistry.registered_name analyses; Serialize.marshal (file, Cabs2cil.environment) cil; Serialize.marshal !Messages.Table.messages_list warnings; - Serialize.marshal (Timing.Default.root, Gc.quick_stat ()) stats ); - Goblintutil.(self_signal (signal_of_string (get_string "dbg.solver-signal"))); (* write solver_stats after solving (otherwise no rows if faster than dbg.solver-stats-interval). TODO better way to write solver_stats without terminal output? *) + GobSys.(self_signal (signal_of_string (get_string "dbg.solver-signal"))); (* write solver_stats after solving (otherwise no rows if faster than dbg.solver-stats-interval). TODO better way to write solver_stats without terminal output? *) ); lh, gh ) @@ -553,7 +565,7 @@ struct ); (* Most warnings happen before during postsolver, but some happen later (e.g. in finalize), so enable this for the rest (if required by option). *) - Goblintutil.should_warn := PostSolverArg.should_warn; + AnalysisState.should_warn := PostSolverArg.should_warn; let insrt k _ s = match k with | (MyCFG.Function fn,_) -> if not (get_bool "exp.forward") then Set.Int.add fn.svar.vid s else s @@ -580,7 +592,7 @@ struct (* check for dead code at the last state: *) let main_sol = try LHT.find lh (List.hd startvars') with Not_found -> Spec.D.bot () in - if get_bool "dbg.debug" && Spec.D.is_bot main_sol then + if Spec.D.is_bot main_sol then M.warn_noloc ~category:Deadcode "Function 'main' does not return"; if get_bool "dump_globs" then @@ -589,27 +601,51 @@ struct (* run activated transformations with the analysis result *) let active_transformations = get_string_list "trans.activated" in (if active_transformations <> [] then - (* Transformations work using Cil visitors which use the location, so we join all contexts per location. *) - let joined = - let open Batteries in let open Enum in - let e = LHT.enum lh |> map (Tuple2.map1 (Node.location % fst)) in (* drop context from key and get location from node *) - let h = Hashtbl.create (if fast_count e then count e else 123) in - iter (fun (k,v) -> - (* join values for the same location *) - let v' = try Spec.D.join (Hashtbl.find h k) v with Not_found -> v in - Hashtbl.replace h k v') e; - h + + (* Most transformations use the locations of statements, since they run using Cil visitors. + Join abstract values once per location and once per node. *) + let joined_by_loc, joined_by_node = + let open Enum in + let node_values = LHT.enum lh |> map (Tuple2.map1 fst) in (* drop context from key *) + let hashtbl_size = if fast_count node_values then count node_values else 123 in + let by_loc, by_node = Hashtbl.create hashtbl_size, NodeH.create hashtbl_size in + node_values |> iter (fun (node, v) -> + let loc = Node.location node in + (* join values once for the same location and once for the same node *) + let join = Option.some % function None -> v | Some v' -> Spec.D.join v v' in + Hashtbl.modify_opt loc join by_loc; + NodeH.modify_opt node join by_node; + ); + by_loc, by_node in - let ask ~node loc = (fun (type a) (q: a Queries.t) -> - let local = Hashtbl.find_option joined loc in - match local with + + let ask ?(node = MyCFG.dummy_node) loc = + let f (type a) (q : a Queries.t) : a = + match Hashtbl.find_option joined_by_loc loc with | None -> Queries.Result.bot q - | Some local -> - Query.ask_local_node gh node local q - ) + | Some local -> Query.ask_local_node gh node local q + in + ({ f } : Queries.ask) in - let ask ?(node=MyCFG.dummy_node) loc = { Queries.f = fun (type a) (q: a Queries.t) -> ask ~node loc q } in - List.iter (fun name -> Transform.run name ask file) active_transformations + + (* A node is dead when its abstract value is bottom in all contexts; + it holds that: bottom in all contexts iff. bottom in the join of all contexts. + Therefore, we just answer whether the (stored) join is bottom. *) + let must_be_dead node = + NodeH.find_option joined_by_node node + (* nodes that didn't make it into the result are definitely dead (hence for_all) *) + |> GobOption.for_all Spec.D.is_bot + in + + let must_be_uncalled fd = not @@ BatSet.Int.mem fd.svar.vid calledFuns in + + let skipped_statements from_node edge to_node = + CfgTools.CfgEdgeH.find_default skippedByEdge (from_node, edge, to_node) [] + in + + Transform.run_transformations file active_transformations + { ask ; must_be_dead ; must_be_uncalled ; + cfg_forward = Cfg.next ; cfg_backward = Cfg.prev ; skipped_statements }; ); lh, gh @@ -622,10 +658,10 @@ struct (* Can't call Generic.SolverStats...print_stats :( print_stats is triggered by dbg.solver-signal, so we send that signal to ourself in maingoblint before re-raising Timeout. The alternative would be to catch the below Timeout, print_stats and re-raise in each solver (or include it in some functor above them). *) - raise GU.Timeout + raise Timeout.Timeout in - let timeout = get_string "dbg.timeout" |> Goblintutil.seconds_of_duration_string in - let lh, gh = Goblintutil.timeout solve_and_postprocess () (float_of_int timeout) timeout_reached in + let timeout = get_string "dbg.timeout" |> TimeUtil.seconds_of_duration_string in + let lh, gh = Timeout.wrap solve_and_postprocess () (float_of_int timeout) timeout_reached in let module SpecSysSol: SpecSysSol with module SpecSys = SpecSys = struct module SpecSys = SpecSys @@ -636,7 +672,33 @@ struct let module R: ResultQuery.SpecSysSol2 with module SpecSys = SpecSys = ResultQuery.Make (FileCfg) (SpecSysSol) in let local_xml = solver2source_result lh in - current_node_state_json := (fun node -> LT.to_yojson (Result.find local_xml node)); + current_node_state_json := (fun node -> Option.map LT.to_yojson (Result.find_option local_xml node)); + + current_varquery_global_state_json := (fun vq_opt -> + let iter_vars f = match vq_opt with + | None -> GHT.iter (fun v _ -> f v) gh + | Some vq -> + EQSys.iter_vars + (fun x -> try LHT.find lh x with Not_found -> EQSys.D.bot ()) + (fun x -> try GHT.find gh x with Not_found -> EQSys.G.bot ()) + vq + (fun _ -> ()) + f + in + (* TODO: optimize this once server has a way to properly convert vid -> varinfo *) + let vars = GHT.create 113 in + iter_vars (fun x -> + GHT.replace vars x () + ); + let assoc = GHT.fold (fun x g acc -> + if GHT.mem vars x then + (EQSys.GVar.show x, EQSys.G.to_yojson g) :: acc + else + acc + ) gh [] + in + `Assoc assoc + ); let liveness = if get_bool "ana.dead-code.lines" || get_bool "ana.dead-code.branches" then @@ -658,6 +720,23 @@ struct in Timing.wrap "warn_global" (GHT.iter warn_global) gh; + if get_bool "exp.arg" then ( + let module ArgTool = ArgTools.Make (R) in + let module Arg = (val ArgTool.create entrystates) in + if get_bool "exp.argdot" then ( + let module ArgDot = ArgTools.Dot (Arg) in + let oc = Batteries.open_out "arg.dot" in + Fun.protect (fun () -> + let ppf = Format.formatter_of_out_channel oc in + ArgDot.dot ppf; + Format.pp_print_flush ppf () + ) ~finally:(fun () -> + Batteries.close_out oc + ) + ); + ArgTools.current_arg := Some (module Arg); + ); + (* Before SV-COMP, so result can depend on YAML witness validation. *) if get_string "witness.yaml.validate" <> "" then ( let module YWitness = YamlWitness.Validator (R) in @@ -684,9 +763,12 @@ struct ); if get_bool "incremental.save" then ( Serialize.Cache.(update_data AnalysisData marshal); - Serialize.Cache.store_data () + if not (get_bool "server.enabled") then + Serialize.Cache.store_data () ); if get_bool "dbg.verbose" && get_string "result" <> "none" then print_endline ("Generating output: " ^ get_string "result"); + + Messages.finalize (); Timing.wrap "result output" (Result.output (lazy local_xml) gh make_global_fast_xml) file end @@ -695,26 +777,28 @@ end [analyze_loop] cannot reside in it anymore since each invocation of [get_spec] in the loop might/should return a different module, and we cannot swap the functor parameter from inside [AnalyzeCFG]. *) -let rec analyze_loop (module CFG : CfgBidir) file fs change_info = +let rec analyze_loop (module CFG : CfgBidir) file fs change_info skippedByEdge = try let (module Spec) = get_spec () in let module A = AnalyzeCFG (CFG) (Spec) (struct let increment = change_info end) in - GobConfig.with_immutable_conf (fun () -> A.analyze file fs) + GobConfig.with_immutable_conf (fun () -> A.analyze file fs skippedByEdge) with Refinement.RestartAnalysis -> (* Tail-recursively restart the analysis again, when requested. All solving starts from scratch. Whoever raised the exception should've modified some global state to do a more precise analysis next time. *) (* TODO: do some more incremental refinement and reuse parts of solution *) - analyze_loop (module CFG) file fs change_info + analyze_loop (module CFG) file fs change_info skippedByEdge + +let compute_cfg_skips file = + let cfgF, cfgB, skippedByEdge = CfgTools.getCFG file in + (module struct let prev = cfgB let next = cfgF end : CfgBidir), skippedByEdge -let compute_cfg file = - let cfgF, cfgB = CfgTools.getCFG file in - (module struct let prev = cfgB let next = cfgF end : CfgBidir) +let compute_cfg = fst % compute_cfg_skips (** The main function to perform the selected analyses. *) let analyze change_info (file: file) fs = if (get_bool "dbg.verbose") then print_endline "Generating the control flow graph."; - let (module CFG) = compute_cfg file in + let (module CFG), skippedByEdge = compute_cfg_skips file in MyCFG.current_cfg := (module CFG); - analyze_loop (module CFG) file fs change_info + analyze_loop (module CFG) file fs change_info skippedByEdge diff --git a/src/framework/refinement.ml b/src/framework/refinement.ml index e23aea0095..8c6181b9d6 100644 --- a/src/framework/refinement.ml +++ b/src/framework/refinement.ml @@ -1,3 +1,5 @@ +(** Experimental analysis refinement. *) + (** Restarts the analysis from scratch in Control. Its raiser is expected to have modified modified some global state to do a more precise analysis next time. diff --git a/src/framework/resultQuery.ml b/src/framework/resultQuery.ml index 63b0765fdb..c676c41c14 100644 --- a/src/framework/resultQuery.ml +++ b/src/framework/resultQuery.ml @@ -1,3 +1,5 @@ +(** Perform {{!Queries.t} queries} on the constraint system solution. *) + open Analyses module Query (SpecSys: SpecSys) = @@ -16,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.") } @@ -35,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.") } @@ -55,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/framework/varQuery.mli b/src/framework/varQuery.mli index 77894b62ef..86abc389fc 100644 --- a/src/framework/varQuery.mli +++ b/src/framework/varQuery.mli @@ -1,3 +1,5 @@ +(** Queries for constraint variables related to semantic elements. *) + open GoblintCil type t = diff --git a/src/goblint.ml b/src/goblint.ml index 893160022d..4ea3a3d242 100644 --- a/src/goblint.ml +++ b/src/goblint.ml @@ -1,8 +1,6 @@ open Goblint_lib open GobConfig -open Goblintutil open Maingoblint -open Prelude open Printf (** the main function *) @@ -37,8 +35,8 @@ let main () = GoblintDir.init (); if get_bool "dbg.verbose" then ( - print_endline (localtime ()); - print_endline Goblintutil.command_line; + print_endline (GobUnix.localtime ()); + print_endline GobSys.command_line; ); let file = lazy (Fun.protect ~finally:GoblintDir.finalize preprocess_parse_merge) in if get_bool "server.enabled" then ( @@ -58,26 +56,28 @@ let main () = else None in + (* This is run independant of the autotuner being enabled or not be sound for programs with longjmp *) + AutoTune.activateLongjmpAnalysesWhenRequired (); if get_bool "ana.autotune.enabled" then AutoTune.chooseConfig file; file |> do_analyze changeInfo; do_html_output (); - do_gobview (); + do_gobview file; do_stats (); Goblint_timing.teardown_tef (); - if !verified = Some false then exit 3 (* verifier failed! *) + if !AnalysisState.verified = Some false then exit 3 (* verifier failed! *) ) with - | Exit -> + | Stdlib.Exit -> do_stats (); Goblint_timing.teardown_tef (); 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 *) - | Timeout -> + | Timeout.Timeout -> do_stats (); eprintf "%s\n" (MessageUtil.colorize ~fd:Unix.stderr ("{RED}Analysis was aborted because it reached the set timeout of " ^ get_string "dbg.timeout" ^ " or was signalled SIGPROF!")); Goblint_timing.teardown_tef (); diff --git a/src/goblint_lib.ml b/src/goblint_lib.ml new file mode 100644 index 0000000000..a71a0c9684 --- /dev/null +++ b/src/goblint_lib.ml @@ -0,0 +1,473 @@ +(** Main library. *) + +(** {1 Framework} *) + +module Maingoblint = Maingoblint +module Control = Control +module Server = Server + +(** {2 CFG} + + Control-flow graphs (CFGs) are used to represent each function. *) + +module Node = Node +module Edge = Edge +module MyCFG = MyCFG +module CfgTools = CfgTools + +(** {2 Specification} + + Analyses are specified by domains and transfer functions. + A dynamic composition of analyses is combined with CFGs to produce a constraint system. *) + +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. *) + +module MCP = MCP +module MCPRegistry = MCPRegistry +module MCPAccess = MCPAccess + +(** MCP allows activated analyses to query each other during the analysis. + Query results from different analyses for the same query are {{!Lattice.S.meet} met} for precision. *) + +module Queries = Queries + +(** MCP allows activated analyses to emit events to each other during the analysis. *) + +module Events = Events + +(** {2 Results} + + The following modules help query the constraint system solution using semantic information. *) + +module ResultQuery = ResultQuery +module VarQuery = VarQuery + +(** {2 Configuration} + + Runtime configuration is represented as JSON. + Options are specified and documented by the JSON schema [src/common/util/options.schema.json]. *) + +module GobConfig = GobConfig +module AfterConfig = AfterConfig + +module AutoTune = AutoTune + +module JsonSchema = JsonSchema +module Options = Options + + +(** {1 Analyses} + + Analyses activatable under MCP. *) + +(** {2 Value} + + Analyses related to values of program variables. *) + +module Base = Base +module RelationAnalysis = RelationAnalysis +module ApronAnalysis = ApronAnalysis +module AffineEqualityAnalysis = AffineEqualityAnalysis +module VarEq = VarEq +module CondVars = CondVars +module TmpSpecial = TmpSpecial + +(** {2 Heap} + + Analyses related to the heap. *) + +module Region = Region +module MallocFresh = MallocFresh +module Malloc_null = Malloc_null +module MemLeak = MemLeak +module UseAfterFree = UseAfterFree +module MemOutOfBounds = MemOutOfBounds + +(** {2 Concurrency} + + Analyses related to concurrency. *) + +(** {3 Locks} + + Analyses related to locking. *) + +module MutexEventsAnalysis = MutexEventsAnalysis +module LocksetAnalysis = LocksetAnalysis +module MutexTypeAnalysis = MutexTypeAnalysis +module MutexAnalysis = MutexAnalysis +module MayLocks = MayLocks +module SymbLocks = SymbLocks +module Deadlock = Deadlock + +(** {3 Threads} + + Analyses related to threads. *) + +module ThreadFlag = ThreadFlag +module ThreadId = ThreadId +module ThreadAnalysis = ThreadAnalysis +module ThreadJoins = ThreadJoins +module MHPAnalysis = MHPAnalysis +module ThreadReturn = ThreadReturn + +(** {3 Other} *) + +module RaceAnalysis = RaceAnalysis +module BasePriv = BasePriv +module RelationPriv = RelationPriv +module ThreadEscape = ThreadEscape +module PthreadSignals = PthreadSignals +module ExtractPthread = ExtractPthread + +(** {2 Longjmp} + + Analyses related to [longjmp] and [setjmp]. *) + +module ActiveSetjmp = ActiveSetjmp +module ModifiedSinceLongjmp = ModifiedSinceLongjmp +module ActiveLongjmp = ActiveLongjmp +module PoisonVariables = PoisonVariables +module Vla = Vla + +(** {2 Tutorial} + + Analyses for didactic purposes. *) + +module Constants = Constants +module Signs = Signs +module Taint = Taint +module UnitAnalysis = UnitAnalysis + +(** {2 Other} *) + +module Assert = Assert +module FileUse = FileUse +module Uninit = Uninit +module Termination = Termination +module Expsplit = Expsplit +module StackTrace = StackTrace +module Spec = Spec + +(** {2 Helper} + + Analyses which only support other analyses. *) + +module AccessAnalysis = AccessAnalysis +module WrapperFunctionAnalysis = WrapperFunctionAnalysis +module TaintPartialContexts = TaintPartialContexts +module UnassumeAnalysis = UnassumeAnalysis +module ExpRelation = ExpRelation +module AbortUnless = AbortUnless + + +(** {1 Domains} + + Domains used by analysis specifications and constraint systems are {{!Lattice.S} lattices}. + + Besides lattice operations, their elements can also be compared and output (in various formats). + Those operations are specified by {!Printable.S}. *) + +module Printable = Printable +module Lattice = Lattice + +(** {2 General} + + Standard general-purpose domains and domain functors. *) + +module BoolDomain = BoolDomain +module SetDomain = SetDomain +module MapDomain = MapDomain +module TrieDomain = TrieDomain +module DisjointDomain = DisjointDomain +module HoareDomain = HoareDomain +module PartitionDomain = PartitionDomain +module FlagHelper = FlagHelper + +(** {2 Analysis-specific} + + Domains for specific analyses. *) + +(** {3 Value} *) + +(** {4 Non-relational} + + Domains for {!Base} analysis. *) + +(** {5 Numeric} *) + +module IntDomain = IntDomain +module FloatDomain = FloatDomain + +(** {5 Addresses} + + Memory locations are identified by {{!Mval} mvalues}, which consist of a {{!GoblintCil.varinfo} variable} and an {{!Offset.t} offset}. + Mvalues are used throughout Goblint, not just the {!Base} analysis. + + Addresses extend mvalues with [NULL], unknown pointers and string literals. *) + +module Mval = Mval +module Offset = Offset +module AddressDomain = AddressDomain + +(** {5 Complex} *) + +module StructDomain = StructDomain +module UnionDomain = UnionDomain +module ArrayDomain = ArrayDomain +module JmpBufDomain = JmpBufDomain + +(** {5 Combined} + + These combine the above domains together for {!Base} analysis. *) + +module BaseDomain = BaseDomain +module ValueDomain = ValueDomain +module ValueDomainQueries = ValueDomainQueries + +(** {4 Relational} + + Domains for {!RelationAnalysis}. *) + +module RelationDomain = RelationDomain +module ApronDomain = ApronDomain +module AffineEqualityDomain = AffineEqualityDomain + +(** {3 Concurrency} *) + +module MutexAttrDomain = MutexAttrDomain +module LockDomain = LockDomain +module SymbLocksDomain = SymbLocksDomain +module DeadlockDomain = DeadlockDomain + +module ThreadFlagDomain = ThreadFlagDomain +module ThreadIdDomain = ThreadIdDomain +module ConcDomain = ConcDomain +module MHP = MHP + +module EscapeDomain = EscapeDomain +module PthreadDomain = PthreadDomain + +(** {3 Other} *) + +module Basetype = Basetype +module Lval = Lval +module Access = Access +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. *) + +module DomainProperties = DomainProperties +module AbstractionDomainProperties = AbstractionDomainProperties +module IntDomainProperties = IntDomainProperties + + +(** {1 Incremental} + + Incremental analysis is for analyzing multiple versions of the same program and reusing as many results as possible. *) + +module CompareCIL = CompareCIL +module CompareAST = CompareAST +module CompareCFG = CompareCFG +module UpdateCil = UpdateCil +module MaxIdUtil = MaxIdUtil +module Serialize = Serialize +module CilMaps = CilMaps + + +(** {1 Solvers} + + Generic solvers are used to solve {{!Analyses.MonSystem} (side-effecting) constraint systems}. *) + +(** {2 Top-down} + + The top-down solver family. *) + +module Td3 = Td3 +module TopDown = TopDown +module TopDown_term = TopDown_term +module TopDown_space_cache_term = TopDown_space_cache_term +module TopDown_deprecated = TopDown_deprecated + +(** {2 SLR} + + The SLR solver family. *) + +module SLRphased = SLRphased +module SLRterm = SLRterm +module SLR = SLR + +(** {2 Other} *) + +module EffectWConEq = EffectWConEq +module Worklist = Worklist +module Generic = Generic +module Selector = Selector + +module PostSolver = PostSolver +module LocalFixpoint = LocalFixpoint +module SolverStats = SolverStats +module SolverBox = SolverBox + + +(** {1 I/O} + + Various input/output interfaces and formats. *) + +module Messages = Messages +module Tracing = Tracing + +(** {2 Front-end} + + The following modules handle program input. *) + +module Preprocessor = Preprocessor +module CompilationDatabase = CompilationDatabase +module MakefileUtil = MakefileUtil + +(** {2 Witnesses} + + Witnesses are an exchangeable format for analysis results. *) + +module Svcomp = Svcomp +module SvcompSpec = SvcompSpec + +module Invariant = Invariant +module InvariantCil = InvariantCil +module WitnessUtil = WitnessUtil + +(** {3 GraphML} + + Automaton-based GraphML witnesses used in SV-COMP. *) + +module MyARG = MyARG +module WitnessConstraints = WitnessConstraints +module ArgTools = ArgTools +module Witness = Witness +module Graphml = Graphml + +(** {3 YAML} + + Entry-based YAML witnesses to be used in SV-COMP. *) + +module YamlWitness = YamlWitness +module YamlWitnessType = YamlWitnessType +module WideningTokens = WideningTokens + +(** {3 Violation} + + Experimental generation of violation witness automata or refinement with observer automata. *) + +module Violation = Violation +module ViolationZ3 = ViolationZ3 +module ObserverAutomaton = ObserverAutomaton +module ObserverAnalysis = ObserverAnalysis +module Refinement = Refinement + +(** {2 SARIF} *) + +module Sarif = Sarif +module SarifType = SarifType +module SarifRules = SarifRules + + +(** {1 Transformations} + + Transformations can be activated to transform the program using analysis results. *) + +module Transform = Transform +module DeadCode = DeadCode +module EvalAssert = EvalAssert +module ExpressionEvaluation = ExpressionEvaluation + + +(** {1 Utilities} *) + +module Timing = Timing +module GoblintDir = GoblintDir + +(** {2 General} *) + +module IntOps = IntOps +module FloatOps = FloatOps + +module LazyEval = LazyEval +module ResettableLazy = ResettableLazy + +module ProcessPool = ProcessPool +module Timeout = Timeout + +module TimeUtil = TimeUtil +module MessageUtil = MessageUtil +module XmlUtil = XmlUtil + +(** {2 CIL} *) + +module CilType = CilType +module Cilfacade = Cilfacade +module RichVarinfo = RichVarinfo + +module CilCfg = CilCfg +module LoopUnrolling = LoopUnrolling + +(** {2 Library specification} + + For more precise analysis of C standard library, etc functions, whose definitions are not available, custom specifications can be added. *) + +module AccessKind = AccessKind +module LibraryDesc = LibraryDesc +module LibraryDsl = LibraryDsl +module LibraryFunctions = LibraryFunctions + +(** {2 Analysis-specific} *) + +module BaseUtil = BaseUtil +module PrecisionUtil = PrecisionUtil +module ContextUtil = ContextUtil +module BaseInvariant = BaseInvariant +module CommonPriv = CommonPriv +module WideningThresholds = WideningThresholds + +module VectorMatrix = VectorMatrix +module SharedFunctions = SharedFunctions + +(** {2 Precision comparison} *) + +module PrecCompare = PrecCompare +module PrecCompareUtil = PrecCompareUtil +module PrivPrecCompareUtil = PrivPrecCompareUtil +module RelationPrecCompareUtil = RelationPrecCompareUtil +module ApronPrecCompareUtil = ApronPrecCompareUtil + +(** {1 Library extensions} + + 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 + +(** {2 Other libraries} + + External library extensions. *) + +module MyCheck = MyCheck diff --git a/src/incremental/compareAST.ml b/src/incremental/compareAST.ml index 671effcad6..c79735c2b1 100644 --- a/src/incremental/compareAST.ml +++ b/src/incremental/compareAST.ml @@ -1,36 +1,73 @@ +(** Comparison of CIL ASTs. *) + open GoblintCil open CilMaps module StringMap = Map.Make(String) -type method_rename_assumption = {original_method_name: string; new_method_name: string; parameter_renames: string StringMap.t} -type method_rename_assumptions = method_rename_assumption VarinfoMap.t +(* Mapping with rename assumptions about functions collected during the comparison. An assumption means that the + comparison result so far is only correct, if the varinfos of a key-value pair in the mapping represent the same but + renamed function. It is a mapping from a varinfo in the old version to one in the new version. *) +type method_rename_assumptions = varinfo VarinfoMap.t + +(* Similiarly to method_rename_assumptions, just that rename assumptions about global variables are collected. *) +type glob_var_rename_assumptions = varinfo VarinfoMap.t + +(* On a successful match, these compinfo and enuminfo names have to be set to the snd element of the tuple. *) +type renamesOnSuccess = (compinfo * compinfo) list * (enuminfo * enuminfo) list -(*rename_mapping is carried through the stack when comparing the AST. Holds a list of rename assumptions.*) -type rename_mapping = (string StringMap.t) * (method_rename_assumptions) +(* The rename_mapping is carried through the stack when comparing the AST. Holds a list of rename assumptions. The first + component is a map of rename assumptions about locals, i.e., parameters and local variables and is only used when + comparing functions. *) +type rename_mapping = (string StringMap.t) * method_rename_assumptions * glob_var_rename_assumptions * renamesOnSuccess -(*Compares two names, being aware of the rename_mapping. Returns true iff: - 1. there is a rename for name1 -> name2 = rename(name1) - 2. there is no rename for name1 -> name1 = name2*) +(* Compares two names, being aware of the rename_mapping. Returns true iff: + 1. there is a rename for name1 -> name2 = rename(name1) + 2. there is no rename for name1 -> name1 = name2 *) let rename_mapping_aware_name_comparison (name1: string) (name2: string) (rename_mapping: rename_mapping) = - let (local_c, method_c) = rename_mapping in - let existingAssumption: string option = StringMap.find_opt name1 local_c in - - match existingAssumption with - | Some now -> - now = name2 - | None -> - name1 = name2 (*Var names differ, but there is no assumption, so this can't be good*) - -let rename_mapping_to_string (rename_mapping: rename_mapping) = - let (local, methods) = rename_mapping in - let local_string = [%show: (string * string) list] (List.of_seq (StringMap.to_seq local)) in - let methods_string: string = List.of_seq (VarinfoMap.to_seq methods |> Seq.map snd) |> - List.map (fun x -> match x with {original_method_name; new_method_name; parameter_renames} -> - "(methodName: " ^ original_method_name ^ " -> " ^ new_method_name ^ - "; renamed_params=" ^ [%show: (string * string) list] (List.of_seq (StringMap.to_seq parameter_renames)) ^ ")") |> - String.concat ", " in - "(local=" ^ local_string ^ "; methods=[" ^ methods_string ^ "])" + if GobConfig.get_bool "incremental.detect-renames" then ( + let (local_c, method_c, _, _) = rename_mapping in + let existingAssumption: string option = StringMap.find_opt name1 local_c in + + match existingAssumption with + | Some now -> + now = name2 + | None -> + name1 = name2 (* Var names differ, but there is no assumption, so this can't be good *) + ) + else name1 = name2 + +(* Creates the mapping of local renames. If the locals do not match in size, an empty mapping is returned. *) +let create_locals_rename_mapping (originalLocalNames: string list) (updatedLocalNames: string list): string StringMap.t = + if List.compare_lengths originalLocalNames updatedLocalNames = 0 then + List.combine originalLocalNames updatedLocalNames |> + List.filter (fun (original, now) -> not (original = now)) |> + List.map (fun (original, now) -> (original, now)) |> + (fun list -> + List.fold_left (fun map mapping -> StringMap.add (fst mapping) (snd mapping) map) StringMap.empty list + ) + else StringMap.empty + +let is_rename_mapping_empty (rename_mapping: rename_mapping) = + let local, methods, glob_vars, _= rename_mapping in + StringMap.is_empty local && VarinfoMap.is_empty methods && VarinfoMap.is_empty glob_vars + +(* rename mapping forward propagation, takes the result from a call and propagates the rename mapping to the next call. + the second call is only executed if the previous call returned true *) +let (&&>>) (prev_result: bool * rename_mapping) f : bool * rename_mapping = + let (prev_equal, updated_rename_mapping) = prev_result in + if prev_equal then f ~rename_mapping:updated_rename_mapping else false, updated_rename_mapping + +(* Same as && but propagates the rename mapping *) +let (&&>) (prev_result: bool * rename_mapping) (b: bool) : bool * rename_mapping = + let (prev_equal, rename_mapping) = prev_result in + (prev_equal && b, rename_mapping) + +(* Same as Goblist.eq but propagates the rename_mapping *) +let forward_list_equal ?(propF = (&&>>)) f l1 l2 ~(rename_mapping: rename_mapping) : bool * rename_mapping = + if ((List.compare_lengths l1 l2) = 0) then + List.fold_left2 (fun (b, r) x y -> propF (b, r) (f x y)) (true, rename_mapping) l1 l2 + else false, rename_mapping (* hack: CIL generates new type names for anonymous types - we want to ignore these *) let compare_name (a: string) (b: string) = @@ -38,36 +75,37 @@ let compare_name (a: string) (b: string) = let anon_union = "__anonunion_" in if a = b then true else BatString.(starts_with a anon_struct && starts_with b anon_struct || starts_with a anon_union && starts_with b anon_union) -let rec eq_constant ~(rename_mapping: rename_mapping) ~(acc: (typ * typ) list) (a: constant) (b: constant) = +let rec eq_constant ~(rename_mapping: rename_mapping) ~(acc: (typ * typ) list) (a: constant) (b: constant) : bool * rename_mapping = match a, b with - | CInt (val1, kind1, str1), CInt (val2, kind2, str2) -> Cilint.compare_cilint val1 val2 = 0 && kind1 = kind2 (* Ignore string representation, i.e. 0x2 == 2 *) + | CInt (val1, kind1, str1), CInt (val2, kind2, str2) -> Z.compare val1 val2 = 0 && kind1 = kind2, rename_mapping (* Ignore string representation, i.e. 0x2 == 2 *) | CEnum (exp1, str1, enuminfo1), CEnum (exp2, str2, enuminfo2) -> eq_exp exp1 exp2 ~rename_mapping ~acc (* Ignore name and enuminfo *) - | a, b -> a = b + | a, b -> a = b, rename_mapping -and eq_exp ?(no_const_vals = false) (a: exp) (b: exp) ~(rename_mapping: rename_mapping) ~(acc: (typ * typ) list) = +and eq_exp (a: exp) (b: exp) ~(rename_mapping: rename_mapping) ~(acc: (typ * typ) list) : bool * rename_mapping = match a, b with | Const c1, Const c2 -> eq_constant c1 c2 ~rename_mapping ~acc | Lval lv1, Lval lv2 -> eq_lval lv1 lv2 ~rename_mapping ~acc | SizeOf typ1, SizeOf typ2 -> eq_typ_acc typ1 typ2 ~rename_mapping ~acc | SizeOfE exp1, SizeOfE exp2 -> eq_exp exp1 exp2 ~rename_mapping ~acc - | SizeOfStr str1, SizeOfStr str2 -> str1 = str2 (* possibly, having the same length would suffice *) + | SizeOfStr str1, SizeOfStr str2 -> str1 = str2, rename_mapping (* possibly, having the same length would suffice *) | AlignOf typ1, AlignOf typ2 -> eq_typ_acc typ1 typ2 ~rename_mapping ~acc | AlignOfE exp1, AlignOfE exp2 -> eq_exp exp1 exp2 ~rename_mapping ~acc - | UnOp (op1, exp1, typ1), UnOp (op2, exp2, typ2) -> op1 == op2 && eq_exp exp1 exp2 ~rename_mapping ~acc && eq_typ_acc typ1 typ2 ~rename_mapping ~acc - | BinOp (op1, left1, right1, typ1), BinOp (op2, left2, right2, typ2) -> op1 = op2 && eq_exp left1 left2 ~rename_mapping ~acc && eq_exp right1 right2 ~rename_mapping ~acc && eq_typ_acc typ1 typ2 ~rename_mapping ~acc - | CastE (typ1, exp1), CastE (typ2, exp2) -> eq_typ_acc typ1 typ2 ~rename_mapping ~acc && eq_exp exp1 exp2 ~rename_mapping ~acc + | UnOp (op1, exp1, typ1), UnOp (op2, exp2, typ2) -> + ((op1 == op2), rename_mapping) &&>> eq_exp exp1 exp2 ~acc &&>> eq_typ_acc typ1 typ2 ~acc + | BinOp (op1, left1, right1, typ1), BinOp (op2, left2, right2, typ2) -> (op1 = op2, rename_mapping) &&>> eq_exp left1 left2 ~acc &&>> eq_exp right1 right2 ~acc &&>> eq_typ_acc typ1 typ2 ~acc + | CastE (typ1, exp1), CastE (typ2, exp2) -> eq_typ_acc typ1 typ2 ~rename_mapping ~acc &&>> eq_exp exp1 exp2 ~acc | AddrOf lv1, AddrOf lv2 -> eq_lval lv1 lv2 ~rename_mapping ~acc | StartOf lv1, StartOf lv2 -> eq_lval lv1 lv2 ~rename_mapping ~acc | Real exp1, Real exp2 -> eq_exp exp1 exp2 ~rename_mapping ~acc | Imag exp1, Imag exp2 -> eq_exp exp1 exp2 ~rename_mapping ~acc - | Question (b1, t1, f1, typ1), Question (b2, t2, f2, typ2) -> eq_exp b1 b2 ~rename_mapping ~acc && eq_exp t1 t2 ~rename_mapping ~acc && eq_exp f1 f2 ~rename_mapping ~acc && eq_typ_acc typ1 typ2 ~rename_mapping ~acc - | AddrOfLabel _, AddrOfLabel _ -> false (* TODO: what to do? *) - | _, _ -> false + | Question (b1, t1, f1, typ1), Question (b2, t2, f2, typ2) -> eq_exp b1 b2 ~rename_mapping ~acc &&>> eq_exp t1 t2 ~acc &&>> eq_exp f1 f2 ~acc &&>> eq_typ_acc typ1 typ2 ~acc + | AddrOfLabel _, AddrOfLabel _ -> false, rename_mapping (* TODO: what to do? *) + | _, _ -> false, rename_mapping and eq_lhost (a: lhost) (b: lhost) ~(rename_mapping: rename_mapping) ~(acc: (typ * typ) list) = match a, b with Var v1, Var v2 -> eq_varinfo v1 v2 ~rename_mapping ~acc | Mem exp1, Mem exp2 -> eq_exp exp1 exp2 ~rename_mapping ~acc - | _, _ -> false + | _, _ -> false, rename_mapping and global_typ_acc: (typ * typ) list ref = ref [] (* TODO: optimize with physical Hashtbl? *) @@ -75,140 +113,180 @@ and mem_typ_acc (a: typ) (b: typ) acc = List.exists (fun p -> match p with (x, y and pretty_length () l = Pretty.num (List.length l) -and eq_typ_acc (a: typ) (b: typ) ~(acc: (typ * typ) list) ~(rename_mapping: rename_mapping) = +and eq_typ_acc ?(fun_parameter_name_comparison_enabled: bool = true) (a: typ) (b: typ) ~(rename_mapping: rename_mapping) ~(acc: (typ * typ) list) : bool * rename_mapping = + (* Registers a compinfo rename or a enum rename*) + let register_rename_on_success = fun rename_mapping compinfo_option enum_option -> + let maybeAddTuple list option = + BatOption.map_default (fun v -> v :: list) list option + in + + let (a, b, c, renames_on_success) = rename_mapping in + let (compinfoRenames, enumRenames) = renames_on_success in + + let updatedCompinfoRenames = maybeAddTuple compinfoRenames compinfo_option in + let updatedEnumRenames = maybeAddTuple enumRenames enum_option in + + a, b, c, (updatedCompinfoRenames, updatedEnumRenames) + in + if Messages.tracing then Messages.tracei "compareast" "eq_typ_acc %a vs %a (%a, %a)\n" d_type a d_type b pretty_length acc pretty_length !global_typ_acc; (* %a makes List.length calls lazy if compareast isn't being traced *) - let r = match a, b with - | TPtr (typ1, attr1), TPtr (typ2, attr2) -> eq_typ_acc typ1 typ2 ~rename_mapping ~acc && GobList.equal (eq_attribute ~rename_mapping ~acc) attr1 attr2 - | TArray (typ1, (Some lenExp1), attr1), TArray (typ2, (Some lenExp2), attr2) -> eq_typ_acc typ1 typ2 ~rename_mapping ~acc && eq_exp lenExp1 lenExp2 ~rename_mapping ~acc && GobList.equal (eq_attribute ~rename_mapping ~acc) attr1 attr2 - | TArray (typ1, None, attr1), TArray (typ2, None, attr2) -> eq_typ_acc typ1 typ2 ~rename_mapping ~acc && GobList.equal (eq_attribute ~rename_mapping ~acc) attr1 attr2 - | TFun (typ1, (Some list1), varArg1, attr1), TFun (typ2, (Some list2), varArg2, attr2) - -> eq_typ_acc typ1 typ2 ~rename_mapping ~acc && GobList.equal (eq_args rename_mapping acc) list1 list2 && varArg1 = varArg2 && - GobList.equal (eq_attribute ~rename_mapping ~acc) attr1 attr2 - | TFun (typ1, None, varArg1, attr1), TFun (typ2, None, varArg2, attr2) - -> eq_typ_acc typ1 typ2 ~rename_mapping ~acc && varArg1 = varArg2 && - GobList.equal (eq_attribute ~rename_mapping ~acc) attr1 attr2 - | TNamed (typinfo1, attr1), TNamed (typinfo2, attr2) -> eq_typ_acc typinfo1.ttype typinfo2.ttype ~rename_mapping ~acc && GobList.equal (eq_attribute ~rename_mapping ~acc) attr1 attr2 (* Ignore tname, treferenced *) - | TNamed (tinf, attr), b -> eq_typ_acc tinf.ttype b ~rename_mapping ~acc (* Ignore tname, treferenced. TODO: dismiss attributes, or not? *) - | a, TNamed (tinf, attr) -> eq_typ_acc a tinf.ttype ~rename_mapping ~acc (* Ignore tname, treferenced . TODO: dismiss attributes, or not? *) + let r, updated_rename_mapping = match a, b with + | TPtr (typ1, attr1), TPtr (typ2, attr2) -> eq_typ_acc typ1 typ2 ~rename_mapping ~acc &&>> forward_list_equal (eq_attribute ~acc) attr1 attr2 + | TArray (typ1, (Some lenExp1), attr1), TArray (typ2, (Some lenExp2), attr2) -> eq_typ_acc typ1 typ2 ~rename_mapping ~acc &&>> eq_exp lenExp1 lenExp2 ~acc &&>> forward_list_equal (eq_attribute ~acc) attr1 attr2 + | TArray (typ1, None, attr1), TArray (typ2, None, attr2) -> eq_typ_acc typ1 typ2 ~rename_mapping ~acc &&>> forward_list_equal (eq_attribute ~acc) attr1 attr2 + | TFun (typ1, (Some list1), varArg1, attr1), TFun (typ2, (Some list2), varArg2, attr2) -> + eq_typ_acc typ1 typ2 ~rename_mapping ~acc &&>> + forward_list_equal (eq_args ~fun_parameter_name_comparison_enabled ~acc) list1 list2 &&> + (varArg1 = varArg2) &&>> + forward_list_equal (eq_attribute ~acc) attr1 attr2 + | TFun (typ1, None, varArg1, attr1), TFun (typ2, None, varArg2, attr2) -> + eq_typ_acc typ1 typ2 ~rename_mapping ~acc &&> + (varArg1 = varArg2) &&>> + forward_list_equal (eq_attribute ~acc) attr1 attr2 + | TNamed (typinfo1, attr1), TNamed (typeinfo2, attr2) -> + eq_typ_acc typinfo1.ttype typeinfo2.ttype ~rename_mapping ~acc &&>> forward_list_equal (eq_attribute ~acc) attr1 attr2 (* Ignore tname, treferenced *) + | TNamed (tinf, attr), b -> eq_typ_acc tinf.ttype b ~rename_mapping ~acc (* Ignore tname, treferenced. TODO: dismiss attributes, or not? *) + | a, TNamed (tinf, attr) -> eq_typ_acc a tinf.ttype ~rename_mapping ~acc (* Ignore tname, treferenced . TODO: dismiss attributes, or not? *) (* The following two lines are a hack to ensure that anonymous types get the same name and thus, the same typsig *) | TComp (compinfo1, attr1), TComp (compinfo2, attr2) -> if mem_typ_acc a b acc || mem_typ_acc a b !global_typ_acc then ( if Messages.tracing then Messages.trace "compareast" "in acc\n"; - true + true, rename_mapping ) else ( let acc = (a, b) :: acc in - let res = eq_compinfo compinfo1 compinfo2 acc rename_mapping && GobList.equal (eq_attribute ~rename_mapping ~acc) attr1 attr2 in - if res then begin - global_typ_acc := (a, b) :: !global_typ_acc; - - (* Reset cnames and ckeys to the old value. Only affects anonymous structs/unions where names are not checked for equality. *) - compinfo2.cname <- compinfo1.cname; - compinfo2.ckey <- compinfo1.ckey; - end; - res + let (res, rm) = eq_compinfo compinfo1 compinfo2 acc rename_mapping &&>> forward_list_equal (eq_attribute ~acc) attr1 attr2 in + let updated_rm = + if res then ( + global_typ_acc := (a, b) :: !global_typ_acc; + register_rename_on_success rm (Some((compinfo2, compinfo1))) None + ) else rm + in + res, updated_rm ) - | TEnum (enuminfo1, attr1), TEnum (enuminfo2, attr2) -> let res = eq_enuminfo enuminfo1 enuminfo2 ~rename_mapping ~acc && GobList.equal (eq_attribute ~rename_mapping ~acc) attr1 attr2 in (if res && enuminfo1.ename <> enuminfo2.ename then enuminfo2.ename <- enuminfo1.ename); res - | TBuiltin_va_list attr1, TBuiltin_va_list attr2 -> GobList.equal (eq_attribute ~rename_mapping ~acc) attr1 attr2 - | TVoid attr1, TVoid attr2 -> GobList.equal (eq_attribute ~rename_mapping ~acc) attr1 attr2 - | TInt (ik1, attr1), TInt (ik2, attr2) -> ik1 = ik2 && GobList.equal (eq_attribute ~rename_mapping ~acc) attr1 attr2 - | TFloat (fk1, attr1), TFloat (fk2, attr2) -> fk1 = fk2 && GobList.equal (eq_attribute ~rename_mapping ~acc) attr1 attr2 - | _, _ -> false + | TEnum (enuminfo1, attr1), TEnum (enuminfo2, attr2) -> + let (res, rm) = eq_enuminfo enuminfo1 enuminfo2 ~rename_mapping ~acc &&>> forward_list_equal (eq_attribute ~acc) attr1 attr2 in + if res && enuminfo1.ename <> enuminfo2.ename then + res, register_rename_on_success rm None (Some((enuminfo2, enuminfo1))) + else res, rm + | TBuiltin_va_list attr1, TBuiltin_va_list attr2 -> forward_list_equal (eq_attribute ~acc) attr1 attr2 ~rename_mapping + | TVoid attr1, TVoid attr2 -> forward_list_equal (eq_attribute ~acc) attr1 attr2 ~rename_mapping + | TInt (ik1, attr1), TInt (ik2, attr2) -> (ik1 = ik2, rename_mapping) &&>> forward_list_equal (eq_attribute ~acc) attr1 attr2 + | TFloat (fk1, attr1), TFloat (fk2, attr2) -> (fk1 = fk2, rename_mapping) &&>> forward_list_equal (eq_attribute ~acc) attr1 attr2 + | _, _ -> false, rename_mapping in if Messages.tracing then Messages.traceu "compareast" "eq_typ_acc %a vs %a\n" d_type a d_type b; - r - + (r, updated_rename_mapping) -and eq_eitems ~(rename_mapping: rename_mapping) ~(acc: (typ * typ) list) (a: string * exp * location) (b: string * exp * location) = match a, b with - (name1, exp1, _l1), (name2, exp2, _l2) -> name1 = name2 && eq_exp exp1 exp2 ~rename_mapping ~acc +and eq_eitems (a: string * exp * location) (b: string * exp * location) ~(rename_mapping: rename_mapping) ~(acc: (typ * typ) list) = match a, b with + (name1, exp1, _l1), (name2, exp2, _l2) -> (name1 = name2, rename_mapping) &&>> eq_exp exp1 exp2 ~acc (* Ignore location *) and eq_enuminfo (a: enuminfo) (b: enuminfo) ~(rename_mapping: rename_mapping) ~(acc: (typ * typ) list) = - compare_name a.ename b.ename && - GobList.equal (eq_attribute ~rename_mapping ~acc) a.eattr b.eattr && - GobList.equal (eq_eitems ~rename_mapping ~acc) a.eitems b.eitems + (compare_name a.ename b.ename, rename_mapping) &&>> + forward_list_equal (eq_attribute ~acc) a.eattr b.eattr &&>> + forward_list_equal (eq_eitems ~acc) a.eitems b.eitems (* Ignore ereferenced *) -and eq_args (rename_mapping: rename_mapping) (acc: (typ * typ) list) (a: string * typ * attributes) (b: string * typ * attributes) = match a, b with +(*param: fun_parameter_name_comparison_enabled when set to false, skips the comparison of the names*) +and eq_args ?(fun_parameter_name_comparison_enabled: bool = true) (a: string * typ * attributes) (b: string * typ * attributes) ~(rename_mapping: rename_mapping) ~(acc: (typ * typ) list) : bool * rename_mapping = match a, b with (name1, typ1, attr1), (name2, typ2, attr2) -> - rename_mapping_aware_name_comparison name1 name2 rename_mapping && eq_typ_acc typ1 typ2 ~rename_mapping ~acc && GobList.equal (eq_attribute ~rename_mapping ~acc) attr1 attr2 + ((not fun_parameter_name_comparison_enabled) || rename_mapping_aware_name_comparison name1 name2 rename_mapping, rename_mapping) &&>> + eq_typ_acc typ1 typ2 ~acc &&>> + forward_list_equal (eq_attribute ~acc) attr1 attr2 -and eq_attrparam ~(acc: (typ * typ) list) ~(rename_mapping: rename_mapping) (a: attrparam) (b: attrparam) = match a, b with - | ACons (str1, attrparams1), ACons (str2, attrparams2) -> str1 = str2 && GobList.equal (eq_attrparam ~rename_mapping ~acc) attrparams1 attrparams2 +and eq_attrparam (a: attrparam) (b: attrparam) ~(rename_mapping: rename_mapping) ~(acc: (typ * typ) list) : bool * rename_mapping = match a, b with + | ACons (str1, attrparams1), ACons (str2, attrparams2) -> (str1 = str2, rename_mapping) &&>> forward_list_equal (eq_attrparam ~acc) attrparams1 attrparams2 | ASizeOf typ1, ASizeOf typ2 -> eq_typ_acc typ1 typ2 ~rename_mapping ~acc | ASizeOfE attrparam1, ASizeOfE attrparam2 -> eq_attrparam attrparam1 attrparam2 ~rename_mapping ~acc - | ASizeOfS typsig1, ASizeOfS typsig2 -> typsig1 = typsig2 + | ASizeOfS typsig1, ASizeOfS typsig2 -> typsig1 = typsig2, rename_mapping | AAlignOf typ1, AAlignOf typ2 -> eq_typ_acc typ1 typ2 ~rename_mapping ~acc | AAlignOfE attrparam1, AAlignOfE attrparam2 -> eq_attrparam attrparam1 attrparam2 ~rename_mapping ~acc - | AAlignOfS typsig1, AAlignOfS typsig2 -> typsig1 = typsig2 - | AUnOp (op1, attrparam1), AUnOp (op2, attrparam2) -> op1 = op2 && eq_attrparam attrparam1 attrparam2 ~rename_mapping ~acc - | ABinOp (op1, left1, right1), ABinOp (op2, left2, right2) -> op1 = op2 && eq_attrparam left1 left2 ~rename_mapping ~acc && eq_attrparam right1 right2 ~rename_mapping ~acc - | ADot (attrparam1, str1), ADot (attrparam2, str2) -> eq_attrparam attrparam1 attrparam2 ~rename_mapping ~acc && str1 = str2 + | AAlignOfS typsig1, AAlignOfS typsig2 -> typsig1 = typsig2, rename_mapping + | AUnOp (op1, attrparam1), AUnOp (op2, attrparam2) -> (op1 = op2, rename_mapping) &&>> eq_attrparam attrparam1 attrparam2 ~acc + | ABinOp (op1, left1, right1), ABinOp (op2, left2, right2) -> (op1 = op2, rename_mapping) &&>> eq_attrparam left1 left2 ~acc &&>> eq_attrparam right1 right2 ~acc + | ADot (attrparam1, str1), ADot (attrparam2, str2) -> (str1 = str2, rename_mapping) &&>> eq_attrparam attrparam1 attrparam2 ~acc | AStar attrparam1, AStar attrparam2 -> eq_attrparam attrparam1 attrparam2 ~rename_mapping ~acc | AAddrOf attrparam1, AAddrOf attrparam2 -> eq_attrparam attrparam1 attrparam2 ~rename_mapping ~acc - | AIndex (left1, right1), AIndex (left2, right2) -> eq_attrparam left1 left2 ~rename_mapping ~acc && eq_attrparam right1 right2 ~rename_mapping ~acc - | AQuestion (left1, middle1, right1), AQuestion (left2, middle2, right2) -> eq_attrparam left1 left2 ~rename_mapping ~acc && eq_attrparam middle1 middle2 ~rename_mapping ~acc && eq_attrparam right1 right2 ~rename_mapping ~acc - | a, b -> a = b + | AIndex (left1, right1), AIndex (left2, right2) -> eq_attrparam left1 left2 ~rename_mapping ~acc &&>> eq_attrparam right1 right2 ~acc + | AQuestion (left1, middle1, right1), AQuestion (left2, middle2, right2) -> eq_attrparam left1 left2 ~rename_mapping ~acc &&>> eq_attrparam middle1 middle2 ~acc &&>> eq_attrparam right1 right2 ~acc + | a, b -> a = b, rename_mapping -and eq_attribute ~(acc: (typ * typ) list) ~(rename_mapping: rename_mapping) (a: attribute) (b: attribute) = match a, b with - | Attr (name1, params1), Attr (name2, params2) -> name1 = name2 && GobList.equal (eq_attrparam ~rename_mapping ~acc ) params1 params2 +and eq_attribute (a: attribute) (b: attribute) ~(acc: (typ * typ) list) ~(rename_mapping: rename_mapping) = match a, b with + | Attr (name1, params1), Attr (name2, params2) -> (name1 = name2, rename_mapping) &&>> forward_list_equal (eq_attrparam ~acc) params1 params2 -and eq_varinfo (a: varinfo) (b: varinfo) ~(acc: (typ * typ) list) ~(rename_mapping: rename_mapping) = +and eq_varinfo (a: varinfo) (b: varinfo) ~(acc: (typ * typ) list) ~(rename_mapping: rename_mapping) = - let (_, method_rename_mappings) = rename_mapping in + let (locals_renames, method_rename_mappings, glob_vars, renames_on_success) = rename_mapping in + + let compare_local_and_global_var = + if a.vglob then + let present_mapping = VarinfoMap.find_opt a glob_vars in + + match present_mapping with + | Some (knownNowVarinfo) -> + b.vname = knownNowVarinfo.vname, method_rename_mappings, glob_vars + | None -> ( + let update_glob_vars = VarinfoMap.add a b glob_vars in + true, method_rename_mappings, update_glob_vars + ) + else rename_mapping_aware_name_comparison a.vname b.vname rename_mapping, method_rename_mappings, glob_vars + in (*When we compare function names, we can directly compare the naming from the rename_mapping if it exists.*) - let isNamingOk = match b.vtype with - | TFun(_, _, _, _) -> ( + let isNamingOk, updated_method_rename_mappings, updatedGlobVarMapping = match a.vtype, b.vtype with + | TFun(_, aParamSpec, _, _), TFun(_, bParamSpec, _, _) -> ( let specific_method_rename_mapping = VarinfoMap.find_opt a method_rename_mappings in match specific_method_rename_mapping with - | Some method_rename_mapping -> method_rename_mapping.original_method_name = a.vname && method_rename_mapping.new_method_name = b.vname - | None -> a.vname = b.vname + | Some new_varinfo -> + let is_naming_ok = new_varinfo.vname = b.vname in + is_naming_ok, method_rename_mappings, glob_vars + | None -> + if a.vname <> b.vname then + true, VarinfoMap.add a b method_rename_mappings, glob_vars + else true, method_rename_mappings, glob_vars ) - | _ -> rename_mapping_aware_name_comparison a.vname b.vname rename_mapping + | _, _ -> compare_local_and_global_var in (*If the following is a method call, we need to check if we have a mapping for that method call. *) - let typ_rename_mapping = match b.vtype with - | TFun(_, _, _, _) -> ( - let new_locals = VarinfoMap.find_opt a method_rename_mappings in - - match new_locals with - | Some locals -> - (locals.parameter_renames, method_rename_mappings) - | None -> (StringMap.empty, method_rename_mappings) - ) - | _ -> rename_mapping + let fun_parameter_name_comparison_enabled = match b.vtype with + | TFun(_, _, _, _) -> false + | _ -> true in - let typeCheck = eq_typ_acc a.vtype b.vtype ~rename_mapping:typ_rename_mapping ~acc in - let attrCheck = GobList.equal (eq_attribute ~rename_mapping ~acc ) a.vattr b.vattr in + (*Ignore rename mapping for type check, as it doesn't change anyway. We only need the renames_on_success*) + let (typeCheck, (_, _, _, updated_renames_on_success)) = eq_typ_acc ~fun_parameter_name_comparison_enabled a.vtype b.vtype ~rename_mapping:(StringMap.empty, VarinfoMap.empty, VarinfoMap.empty, renames_on_success) ~acc in - isNamingOk && typeCheck && attrCheck && a.vstorage = b.vstorage && a.vglob = b.vglob && a.vaddrof = b.vaddrof + (isNamingOk && typeCheck, (locals_renames, updated_method_rename_mappings, updatedGlobVarMapping, updated_renames_on_success)) &&>> + forward_list_equal (eq_attribute ~acc) a.vattr b.vattr &&> + (a.vstorage = b.vstorage) &&> (a.vglob = b.vglob) &&> (a.vaddrof = b.vaddrof) (* Ignore the location, vid, vreferenced, vdescr, vdescrpure, vinline *) (* Accumulator is needed because of recursive types: we have to assume that two types we already encountered in a previous step of the recursion are equivalent *) -and eq_compinfo (a: compinfo) (b: compinfo) (acc: (typ * typ) list) (rename_mapping: rename_mapping) = - a.cstruct = b.cstruct && - compare_name a.cname b.cname && - GobList.equal (fun a b-> eq_fieldinfo a b ~rename_mapping ~acc ) a.cfields b.cfields && - GobList.equal (eq_attribute ~rename_mapping ~acc ) a.cattr b.cattr && - a.cdefined = b.cdefined (* Ignore ckey, and ignore creferenced *) +and eq_compinfo (a: compinfo) (b: compinfo) (acc: (typ * typ) list) (rename_mapping: rename_mapping) : bool * rename_mapping = + (a.cstruct = b.cstruct, rename_mapping) &&> + compare_name a.cname b.cname &&>> + forward_list_equal (fun a b -> eq_fieldinfo a b ~acc) a.cfields b.cfields &&>> + forward_list_equal (eq_attribute ~acc ) a.cattr b.cattr &&> + (a.cdefined = b.cdefined) (* Ignore ckey, and ignore creferenced *) and eq_fieldinfo (a: fieldinfo) (b: fieldinfo) ~(acc: (typ * typ) list) ~(rename_mapping: rename_mapping) = if Messages.tracing then Messages.tracei "compareast" "fieldinfo %s vs %s\n" a.fname b.fname; - let r = a.fname = b.fname && eq_typ_acc a.ftype b.ftype ~rename_mapping ~acc && a.fbitfield = b.fbitfield && GobList.equal (eq_attribute ~rename_mapping ~acc) a.fattr b.fattr in + let (r, rm) = (a.fname = b.fname, rename_mapping) &&>> + eq_typ_acc a.ftype b.ftype ~acc &&> (a.fbitfield = b.fbitfield) &&>> + forward_list_equal (eq_attribute ~acc) a.fattr b.fattr in if Messages.tracing then Messages.traceu "compareast" "fieldinfo %s vs %s\n" a.fname b.fname; - r + (r, rm) -and eq_offset (a: offset) (b: offset) ~(rename_mapping: rename_mapping) ~(acc: (typ * typ) list) = match a, b with - NoOffset, NoOffset -> true - | Field (info1, offset1), Field (info2, offset2) -> eq_fieldinfo info1 info2 ~rename_mapping ~acc && eq_offset offset1 offset2 ~rename_mapping ~acc - | Index (exp1, offset1), Index (exp2, offset2) -> eq_exp exp1 exp2 ~rename_mapping ~acc && eq_offset offset1 offset2 ~rename_mapping ~acc - | _, _ -> false +and eq_offset (a: offset) (b: offset) ~(rename_mapping: rename_mapping) ~(acc: (typ * typ) list) : bool * rename_mapping = match a, b with + NoOffset, NoOffset -> true, rename_mapping + | Field (info1, offset1), Field (info2, offset2) -> eq_fieldinfo info1 info2 ~rename_mapping ~acc &&>> eq_offset offset1 offset2 ~acc + | Index (exp1, offset1), Index (exp2, offset2) -> eq_exp exp1 exp2 ~rename_mapping ~acc &&>> eq_offset offset1 offset2 ~acc + | _, _ -> false, rename_mapping -and eq_lval (a: lval) (b: lval) ~(rename_mapping: rename_mapping) ~(acc: (typ * typ) list) = match a, b with - (host1, off1), (host2, off2) -> eq_lhost host1 host2 ~rename_mapping ~acc && eq_offset off1 off2 ~rename_mapping ~acc +and eq_lval (a: lval) (b: lval) ~(rename_mapping: rename_mapping) ~(acc: (typ * typ) list) : bool * rename_mapping = match a, b with + (host1, off1), (host2, off2) -> eq_lhost host1 host2 ~rename_mapping ~acc &&>> eq_offset off1 off2 ~acc let eq_typ = eq_typ_acc ~acc:[] @@ -226,14 +304,19 @@ let eq_offset = eq_offset ~acc:[] let eq_instr (a: instr) (b: instr) ~(rename_mapping: rename_mapping) = match a, b with - | Set (lv1, exp1, _l1, _el1), Set (lv2, exp2, _l2, _el2) -> eq_lval lv1 lv2 ~rename_mapping && eq_exp exp1 exp2 ~rename_mapping + | Set (lv1, exp1, _l1, _el1), Set (lv2, exp2, _l2, _el2) -> eq_lval lv1 lv2 ~rename_mapping &&>> eq_exp exp1 exp2 | Call (Some lv1, f1, args1, _l1, _el1), Call (Some lv2, f2, args2, _l2, _el2) -> - eq_lval lv1 lv2 ~rename_mapping && eq_exp f1 f2 ~rename_mapping && GobList.equal (eq_exp ~rename_mapping) args1 args2 + eq_lval lv1 lv2 ~rename_mapping &&>> eq_exp f1 f2 &&>> forward_list_equal eq_exp args1 args2 | Call (None, f1, args1, _l1, _el1), Call (None, f2, args2, _l2, _el2) -> - eq_exp f1 f2 ~rename_mapping && GobList.equal (eq_exp ~rename_mapping) args1 args2 - | Asm (attr1, tmp1, ci1, dj1, rk1, l1), Asm (attr2, tmp2, ci2, dj2, rk2, l2) -> GobList.equal String.equal tmp1 tmp2 && GobList.equal(fun (x1,y1,z1) (x2,y2,z2)-> x1 = x2 && y1 = y2 && eq_lval z1 z2 ~rename_mapping) ci1 ci2 && GobList.equal(fun (x1,y1,z1) (x2,y2,z2)-> x1 = x2 && y1 = y2 && eq_exp z1 z2 ~rename_mapping) dj1 dj2 && GobList.equal String.equal rk1 rk2(* ignore attributes and locations *) + eq_exp f1 f2 ~rename_mapping &&>> forward_list_equal eq_exp args1 args2 + | Asm (attr1, tmp1, ci1, dj1, rk1, l1), Asm (attr2, tmp2, ci2, dj2, rk2, l2) -> + (GobList.equal String.equal tmp1 tmp2, rename_mapping) &&>> + forward_list_equal (fun (x1,y1,z1) (x2,y2,z2) ~rename_mapping:x-> (x1 = x2, x) &&> (y1 = y2) &&>> eq_lval z1 z2) ci1 ci2 &&>> + forward_list_equal (fun (x1,y1,z1) (x2,y2,z2) ~rename_mapping:x-> (x1 = x2, x) &&> (y1 = y2) &&>> eq_exp z1 z2) dj1 dj2 &&> + GobList.equal String.equal rk1 rk2(* ignore attributes and locations *) | VarDecl (v1, _l1), VarDecl (v2, _l2) -> eq_varinfo v1 v2 ~rename_mapping - | _, _ -> false + | _, _ -> false, rename_mapping + let eq_label (a: label) (b: label) = match a, b with Label (lb1, _l1, s1), Label (lb2, _l2, s2) -> lb1 = lb2 && s1 = s2 @@ -251,35 +334,40 @@ let eq_stmt_with_location ((a, af): stmt * fundec) ((b, bf): stmt * fundec) = through the cfg and only compares the currently visited node (The cil blocks inside an if statement should not be compared together with its condition to avoid a to early and not precise detection of a changed node inside). Switch, break and continue statements are removed during cfg preparation and therefore need not to be handeled *) + let rec eq_stmtkind ?(cfg_comp = false) ((a, af): stmtkind * fundec) ((b, bf): stmtkind * fundec) ~(rename_mapping: rename_mapping) = - let eq_block' = fun x y -> if cfg_comp then true else eq_block (x, af) (y, bf) ~rename_mapping in + let eq_block' = fun x y ~(rename_mapping:rename_mapping) -> if cfg_comp then true, rename_mapping else eq_block (x, af) (y, bf) ~rename_mapping in match a, b with - | Instr is1, Instr is2 -> GobList.equal (eq_instr ~rename_mapping) is1 is2 + | Instr is1, Instr is2 -> forward_list_equal eq_instr is1 is2 ~rename_mapping | Return (Some exp1, _l1), Return (Some exp2, _l2) -> eq_exp exp1 exp2 ~rename_mapping - | Return (None, _l1), Return (None, _l2) -> true - | Return _, Return _ -> false - | Goto (st1, _l1), Goto (st2, _l2) -> eq_stmt_with_location (!st1, af) (!st2, bf) - | Break _, Break _ -> if cfg_comp then failwith "CompareCFG: Invalid stmtkind in CFG" else true - | Continue _, Continue _ -> if cfg_comp then failwith "CompareCFG: Invalid stmtkind in CFG" else true - | If (exp1, then1, else1, _l1, _el1), If (exp2, then2, else2, _l2, _el2) -> eq_exp exp1 exp2 ~rename_mapping && eq_block' then1 then2 && eq_block' else1 else2 - | Switch (exp1, block1, stmts1, _l1, _el1), Switch (exp2, block2, stmts2, _l2, _el2) -> if cfg_comp then failwith "CompareCFG: Invalid stmtkind in CFG" else eq_exp exp1 exp2 ~rename_mapping && eq_block' block1 block2 && GobList.equal (fun a b -> eq_stmt (a,af) (b,bf) ~rename_mapping) stmts1 stmts2 - | Loop (block1, _l1, _el1, _con1, _br1), Loop (block2, _l2, _el2, _con2, _br2) -> eq_block' block1 block2 - | Block block1, Block block2 -> eq_block' block1 block2 - | _, _ -> false + | Return (None, _l1), Return (None, _l2) -> true, rename_mapping + | Return _, Return _ -> false, rename_mapping + | Goto (st1, _l1), Goto (st2, _l2) -> eq_stmt_with_location (!st1, af) (!st2, bf), rename_mapping + | Break _, Break _ -> if cfg_comp then failwith "CompareCFG: Invalid stmtkind in CFG" else true, rename_mapping + | Continue _, Continue _ -> if cfg_comp then failwith "CompareCFG: Invalid stmtkind in CFG" else true, rename_mapping + | If (exp1, then1, else1, _l1, _el1), If (exp2, then2, else2, _l2, _el2) -> eq_exp exp1 exp2 ~rename_mapping &&>> + eq_block' then1 then2 &&>> + eq_block' else1 else2 + | Switch (exp1, block1, stmts1, _l1, _el1), Switch (exp2, block2, stmts2, _l2, _el2) -> if cfg_comp then failwith "CompareCFG: Invalid stmtkind in CFG" else eq_exp exp1 exp2 ~rename_mapping &&>> eq_block' block1 block2 &&>> forward_list_equal (fun a b -> eq_stmt (a,af) (b,bf)) stmts1 stmts2 + | Loop (block1, _l1, _el1, _con1, _br1), Loop (block2, _l2, _el2, _con2, _br2) -> eq_block' block1 block2 ~rename_mapping + | Block block1, Block block2 -> eq_block' block1 block2 ~rename_mapping + | _, _ -> false, rename_mapping and eq_stmt ?cfg_comp ((a, af): stmt * fundec) ((b, bf): stmt * fundec) ~(rename_mapping: rename_mapping) = - GobList.equal eq_label a.labels b.labels && - eq_stmtkind ?cfg_comp (a.skind, af) (b.skind, bf) ~rename_mapping + (GobList.equal eq_label a.labels b.labels, rename_mapping) &&>> + eq_stmtkind ?cfg_comp (a.skind, af) (b.skind, bf) -and eq_block ((a, af): Cil.block * fundec) ((b, bf): Cil.block * fundec) ~(rename_mapping: rename_mapping) = - a.battrs = b.battrs && GobList.equal (fun x y -> eq_stmt (x, af) (y, bf) ~rename_mapping) a.bstmts b.bstmts +and eq_block ((a, af): Cil.block * fundec) ((b, bf): Cil.block * fundec) ~(rename_mapping: rename_mapping) : bool * rename_mapping = + (a.battrs = b.battrs, rename_mapping) &&>> forward_list_equal (fun x y -> eq_stmt (x, af) (y, bf)) a.bstmts b.bstmts let rec eq_init (a: init) (b: init) ~(rename_mapping: rename_mapping) = match a, b with | SingleInit e1, SingleInit e2 -> eq_exp e1 e2 ~rename_mapping - | CompoundInit (t1, l1), CompoundInit (t2, l2) -> eq_typ t1 t2 ~rename_mapping && GobList.equal (fun (o1, i1) (o2, i2) -> eq_offset o1 o2 ~rename_mapping && eq_init i1 i2 ~rename_mapping) l1 l2 - | _, _ -> false + | CompoundInit (t1, l1), CompoundInit (t2, l2) -> + eq_typ t1 t2 ~rename_mapping &&>> + forward_list_equal (fun (o1, i1) (o2, i2) ~rename_mapping:rename_mapping -> eq_offset o1 o2 ~rename_mapping &&>> eq_init i1 i2) l1 l2 + | _, _ -> false, rename_mapping -let eq_initinfo (a: initinfo) (b: initinfo) (rename_mapping: rename_mapping) = match a.init, b.init with +let eq_initinfo (a: initinfo) (b: initinfo) ~(rename_mapping: rename_mapping) = match a.init, b.init with | (Some init_a), (Some init_b) -> eq_init init_a init_b ~rename_mapping - | None, None -> true - | _, _ -> false + | None, None -> true, rename_mapping + | _, _ -> false, rename_mapping diff --git a/src/incremental/compareCFG.ml b/src/incremental/compareCFG.ml index 93bb855606..225cbb1c76 100644 --- a/src/incremental/compareCFG.ml +++ b/src/incremental/compareCFG.ml @@ -1,12 +1,21 @@ +(** Comparison of CFGs. *) + open MyCFG open Queue open GoblintCil -open CilMaps include CompareAST -let eq_node (x, fun1) (y, fun2) = - let empty_rename_mapping: rename_mapping = (StringMap.empty, VarinfoMap.empty) in - (* don't allow pseudo return node to be equal to normal return node, could make function unchanged, but have different sallstmts *) +(*Non propagating version of &&>>. Discards the new rename_mapping and alwas propagates the one in prev_result. However propagates the renames_on_success*) +let (&&<>) (prev_result: bool * rename_mapping) f : bool * rename_mapping = + let (prev_equal, prev_rm) = prev_result in + let (a, b, c, _) = prev_rm in + + if prev_equal then + let (r, ((_, _, _, updated_renames_on_success) : rename_mapping)) = f ~rename_mapping:prev_rm in + (r, (a, b, c, updated_renames_on_success)) + else false, prev_rm + +let eq_node (x, fun1) (y, fun2) ~rename_mapping = let isPseudoReturn f sid = let pid = CfgTools.get_pseudo_return_id f in sid == pid in @@ -14,30 +23,29 @@ let eq_node (x, fun1) (y, fun2) = | Statement s1, Statement s2 -> let p1 = isPseudoReturn fun1 s1.sid in let p2 = isPseudoReturn fun2 s2.sid in - ((p1 && p2) || not (p1 || p2)) && eq_stmt ~cfg_comp:true (s1, fun1) (s2, fun2) ~rename_mapping:empty_rename_mapping - | Function f1, Function f2 -> eq_varinfo f1.svar f2.svar ~rename_mapping:empty_rename_mapping - | FunctionEntry f1, FunctionEntry f2 -> eq_varinfo f1.svar f2.svar ~rename_mapping:empty_rename_mapping - | _ -> false + ((p1 && p2) || not (p1 || p2), rename_mapping) &&>> eq_stmt ~cfg_comp:true (s1, fun1) (s2, fun2) + | Function f1, Function f2 -> eq_varinfo f1.svar f2.svar ~rename_mapping + | FunctionEntry f1, FunctionEntry f2 -> eq_varinfo f1.svar f2.svar ~rename_mapping + | _ -> false, rename_mapping (* TODO: compare ASMs properly instead of simply always assuming that they are not the same *) -let eq_edge x y = - let empty_rename_mapping: rename_mapping = (StringMap.empty, VarinfoMap.empty) in +let eq_edge x y ~rename_mapping = match x, y with - | Assign (lv1, rv1), Assign (lv2, rv2) -> eq_lval lv1 lv2 ~rename_mapping:empty_rename_mapping && eq_exp rv1 rv2 ~rename_mapping:empty_rename_mapping - | Proc (None,f1,ars1), Proc (None,f2,ars2) -> eq_exp f1 f2 ~rename_mapping:empty_rename_mapping && GobList.equal (eq_exp ~rename_mapping:empty_rename_mapping) ars1 ars2 + | Assign (lv1, rv1), Assign (lv2, rv2) -> eq_lval lv1 lv2 ~rename_mapping &&<> eq_exp rv1 rv2 + | Proc (None,f1,ars1), Proc (None,f2,ars2) -> eq_exp f1 f2 ~rename_mapping &&<> forward_list_equal eq_exp ars1 ars2 | Proc (Some r1,f1,ars1), Proc (Some r2,f2,ars2) -> - eq_lval r1 r2 ~rename_mapping:empty_rename_mapping && eq_exp f1 f2 ~rename_mapping:empty_rename_mapping && GobList.equal (eq_exp ~rename_mapping:empty_rename_mapping) ars1 ars2 - | Entry f1, Entry f2 -> eq_varinfo f1.svar f2.svar ~rename_mapping:empty_rename_mapping - | Ret (None,fd1), Ret (None,fd2) -> eq_varinfo fd1.svar fd2.svar ~rename_mapping:empty_rename_mapping - | Ret (Some r1,fd1), Ret (Some r2,fd2) -> eq_exp r1 r2 ~rename_mapping:empty_rename_mapping && eq_varinfo fd1.svar fd2.svar ~rename_mapping:empty_rename_mapping - | Test (p1,b1), Test (p2,b2) -> eq_exp p1 p2 ~rename_mapping:empty_rename_mapping && b1 = b2 - | ASM _, ASM _ -> false - | Skip, Skip -> true - | VDecl v1, VDecl v2 -> eq_varinfo v1 v2 ~rename_mapping:empty_rename_mapping - | _ -> false + eq_lval r1 r2 ~rename_mapping &&<> eq_exp f1 f2 &&<> forward_list_equal eq_exp ars1 ars2 + | Entry f1, Entry f2 -> eq_varinfo f1.svar f2.svar ~rename_mapping + | Ret (None,fd1), Ret (None,fd2) -> eq_varinfo fd1.svar fd2.svar ~rename_mapping + | Ret (Some r1,fd1), Ret (Some r2,fd2) -> eq_exp r1 r2 ~rename_mapping &&<> eq_varinfo fd1.svar fd2.svar + | Test (p1,b1), Test (p2,b2) -> eq_exp p1 p2 ~rename_mapping &&> (b1 = b2) + | ASM _, ASM _ -> false, rename_mapping + | Skip, Skip -> true, rename_mapping + | VDecl v1, VDecl v2 -> eq_varinfo v1 v2 ~rename_mapping + | _ -> false, rename_mapping (* The order of the edges in the list is relevant. Therefore compare them one to one without sorting first *) -let eq_edge_list xs ys = GobList.equal eq_edge xs ys +let eq_edge_list xs ys = forward_list_equal ~propF:(&&<>) eq_edge xs ys let to_edge_list ls = List.map (fun (loc, edge) -> edge) ls @@ -50,13 +58,14 @@ type biDirectionNodeMap = {node1to2: node NH.t; node2to1: node NH.t} * in the succesors of fromNode2 in the new CFG. Matching node tuples are added to the waitingList to repeat the matching * process on their successors. If a node from the old CFG can not be matched, it is added to diff and no further * comparison is done for its successors. The two function entry nodes make up the tuple to start the comparison from. *) -let compareCfgs (module CfgOld : CfgForward) (module CfgNew : CfgForward) fun1 fun2 = + +let compareCfgs (module CfgOld : CfgForward) (module CfgNew : CfgForward) fun1 fun2 rename_mapping : biDirectionNodeMap * unit NH.t * rename_mapping = let diff = NH.create 113 in let same = {node1to2=NH.create 113; node2to1=NH.create 113} in let waitingList : (node * node) t = Queue.create () in - let rec compareNext () = - if Queue.is_empty waitingList then () + let rec compareNext rename_mapping : rename_mapping = + if Queue.is_empty waitingList then rename_mapping else let fromNode1, fromNode2 = Queue.take waitingList in let outList1 = CfgOld.next fromNode1 in @@ -64,23 +73,25 @@ let compareCfgs (module CfgOld : CfgForward) (module CfgNew : CfgForward) fun1 f (* Find a matching edge and successor node for (edgeList1, toNode1) in the list of successors of fromNode2. * If successful, add the matching node tuple to same, else add toNode1 to the differing nodes. *) - let findMatch (edgeList1, toNode1) = - let rec aux remSuc = match remSuc with - | [] -> NH.replace diff toNode1 () + let findMatch (edgeList1, toNode1) rename_mapping : rename_mapping = + let rec aux remSuc rename_mapping : rename_mapping = match remSuc with + | [] -> NH.replace diff toNode1 (); rename_mapping | (locEdgeList2, toNode2)::remSuc' -> let edgeList2 = to_edge_list locEdgeList2 in - if eq_node (toNode1, fun1) (toNode2, fun2) && eq_edge_list edgeList1 edgeList2 then + let (isEq, updatedRenameMapping) = (true, rename_mapping) &&>> eq_node (toNode1, fun1) (toNode2, fun2) &&>> eq_edge_list edgeList1 edgeList2 in + if isEq then begin match NH.find_opt same.node1to2 toNode1 with - | Some n2 -> if not (Node.equal n2 toNode2) then NH.replace diff toNode1 () - | None -> NH.replace same.node1to2 toNode1 toNode2; NH.replace same.node2to1 toNode2 toNode1; Queue.add (toNode1, toNode2) waitingList + | Some n2 -> if not (Node.equal n2 toNode2) then NH.replace diff toNode1 (); updatedRenameMapping + | None -> NH.replace same.node1to2 toNode1 toNode2; NH.replace same.node2to1 toNode2 toNode1; Queue.add (toNode1, toNode2) waitingList; + updatedRenameMapping end - else aux remSuc' in - aux outList2 in + else aux remSuc' updatedRenameMapping in + aux outList2 rename_mapping in (* For a toNode1 from the list of successors of fromNode1, check whether it might have duplicate matches. * In that case declare toNode1 as differing node. Else, try finding a match in the list of successors * of fromNode2 in the new CFG using findMatch. *) - let iterOuts (locEdgeList1, toNode1) = + let iterOuts (locEdgeList1, toNode1) rename_mapping : rename_mapping = let edgeList1 = to_edge_list locEdgeList1 in (* Differentiate between a possibly duplicate Test(1,false) edge and a single occurence. In the first * case the edge is directly added to the diff set to avoid undetected ambiguities during the recursive @@ -91,13 +102,18 @@ let compareCfgs (module CfgOld : CfgForward) (module CfgNew : CfgForward) fun1 f let posAmbigEdge edgeList = let findTestFalseEdge (ll,_) = testFalseEdge (snd (List.hd ll)) in let numDuplicates l = List.length (List.find_all findTestFalseEdge l) in testFalseEdge (List.hd edgeList) && (numDuplicates outList1 > 1 || numDuplicates outList2 > 1) in - if posAmbigEdge edgeList1 then NH.replace diff toNode1 () - else findMatch (edgeList1, toNode1) in - List.iter iterOuts outList1; compareNext () in + if posAmbigEdge edgeList1 then (NH.replace diff toNode1 (); rename_mapping) + else findMatch (edgeList1, toNode1) rename_mapping in + let updatedRenameMapping = List.fold_left (fun rm e -> iterOuts e rm) rename_mapping outList1 in + compareNext updatedRenameMapping + in let entryNode1, entryNode2 = (FunctionEntry fun1, FunctionEntry fun2) in - NH.replace same.node1to2 entryNode1 entryNode2; NH.replace same.node2to1 entryNode2 entryNode1; - Queue.push (entryNode1,entryNode2) waitingList; compareNext (); (same, diff) + NH.replace same.node1to2 entryNode1 entryNode2; + NH.replace same.node2to1 entryNode2 entryNode1; + Queue.push (entryNode1,entryNode2) waitingList; + let updatedRenameMapping = compareNext rename_mapping in + same, diff, updatedRenameMapping (* This is the second phase of the CFG comparison of functions. It removes the nodes from the matching node set 'same' * that have an incoming backedge in the new CFG that can be reached from a differing new node. This is important to @@ -120,7 +136,8 @@ let reexamine f1 f2 (same : biDirectionNodeMap) (diffNodes1 : unit NH.t) (module repeat (); NH.to_seq same.node1to2, NH.to_seq_keys diffNodes1 -let compareFun (module CfgOld : CfgForward) (module CfgNew : CfgBidir) fun1 fun2 = - let same, diff = Timing.wrap "compare-phase1" (fun () -> compareCfgs (module CfgOld) (module CfgNew) fun1 fun2) () in + +let compareFun (module CfgOld : CfgForward) (module CfgNew : CfgBidir) fun1 fun2 rename_mapping : (node * node) list * node list * rename_mapping = + let same, diff, rename_mapping = Timing.wrap "compare-phase1" (fun () -> compareCfgs (module CfgOld) (module CfgNew) fun1 fun2 rename_mapping) () in let unchanged, diffNodes1 = Timing.wrap "compare-phase2" (fun () -> reexamine fun1 fun2 same diff (module CfgOld) (module CfgNew)) () in - List.of_seq unchanged, List.of_seq diffNodes1 + List.of_seq unchanged, List.of_seq diffNodes1, rename_mapping diff --git a/src/incremental/compareCIL.ml b/src/incremental/compareCIL.ml index 64c3cae43f..befaee6c58 100644 --- a/src/incremental/compareCIL.ml +++ b/src/incremental/compareCIL.ml @@ -1,14 +1,38 @@ +(** Comparison of CIL files. *) + open GoblintCil open MyCFG -open CilMaps include CompareAST include CompareCFG +include CilMaps module GlobalMap = Map.Make(String) type global_def = Var of varinfo | Fun of fundec type global_col = {decls: varinfo option; def: global_def option} +let name_of_global_col gc = match gc.def with + | Some (Fun f) -> f.svar.vname + | Some (Var v) -> v.vname + | None -> match gc.decls with + | Some v -> v.vname + | None -> raise (Failure "empty global record") + +let compare_global_col gc1 gc2 = compare (name_of_global_col gc1) (name_of_global_col gc2) +let equal_name_global_col gc1 gc2 = compare_global_col gc1 gc2 == 0 + +let get_varinfo gc = match gc.decls, gc.def with + | _, Some (Var v) -> v + | _, Some (Fun f) -> f.svar + | Some v, _ -> v + | _ -> failwith "A global should have at least a declaration or a definition" + +module GlobalColMap = Map.Make( + struct + type t = global_col + let compare = compare_global_col + end) + let name_of_global g = match g with | GVar (v,_,_) -> v.vname | GFun (f,_) -> f.svar.vname @@ -57,80 +81,218 @@ let unchanged_to_change_status = function | true -> Unchanged | false -> Changed +let empty_rename_mapping: rename_mapping = (StringMap.empty, VarinfoMap.empty, VarinfoMap.empty, ([], [])) + let should_reanalyze (fdec: Cil.fundec) = List.mem fdec.svar.vname (GobConfig.get_string_list "incremental.force-reanalyze.funs") +let performRenames (renamesOnSuccess: renamesOnSuccess) = + begin + let (compinfoRenames, enumRenames) = renamesOnSuccess in + (* Reset cnames and ckeys to the old value. Only affects anonymous structs/unions where names are not checked for equality. *) + List.iter (fun (compinfo2, compinfo1) -> compinfo2.cname <- compinfo1.cname; compinfo2.ckey <- compinfo1.ckey) compinfoRenames; + List.iter (fun (enum2, enum1) -> enum2.ename <- enum1.ename) enumRenames; + end + +let preservesSameNameMatches n_old oldMap n_new newMap = n_old = n_new || (not (GlobalMap.mem n_old newMap) && not (GlobalMap.mem n_new oldMap)) + +let addToFinalMatchesMapping oV nV final_matches = + VarinfoMap.add oV nV (fst final_matches), VarinfoMap.add nV oV (snd final_matches) + +let empty_rename_assms m = VarinfoMap.for_all (fun vo vn -> vo.vname = vn.vname) m + +let already_matched oV nV final_matches = + match VarinfoMap.find_opt oV (fst final_matches) with + | None -> false + | Some v -> v.vid = oV.vid + +(* looks up the result of the already executed comparison and returns true if it is unchanged, false if it is changed. + Throws an exception if not found. *) +let change_info_lookup old_glob new_glob change_info = + List.exists (fun (u : unchanged_global) -> equal_name_global_col u.old old_glob && equal_name_global_col u.current new_glob) change_info.unchanged + +(* Compares two varinfos of globals. finalizeOnlyExactMatch=true allows to check a rename assumption and discard the comparison result in case they do not match *) +let eq_glob_var ?(finalizeOnlyExactMatch=false) oV gc_old oldMap nV gc_new newMap change_info final_matches = + if already_matched oV nV final_matches then + (* check if this function was already matched and lookup the result *) + change_info_lookup gc_old gc_new change_info, change_info, final_matches + else if not (preservesSameNameMatches oV.vname oldMap nV.vname newMap) then + (* do not allow for matches between differently named variables if one of the variables names exists in both, the new and old file *) + false, change_info, final_matches + else ( + let identical, (_, function_dependencies, global_var_dependencies, renamesOnSuccess) = eq_varinfo oV nV ~rename_mapping:empty_rename_mapping in + + if not finalizeOnlyExactMatch || identical then + performRenames renamesOnSuccess; (* updates enum names and compinfo names and keys that were collected during comparison of this matched function *) + if identical then ( + change_info.unchanged <- {old = gc_old; current = gc_new} :: change_info.unchanged; + true, change_info, addToFinalMatchesMapping oV nV final_matches + ) else if not finalizeOnlyExactMatch then ( + change_info.changed <- {old = gc_old; current = gc_new; unchangedHeader = true; diff = None} :: change_info.changed; + false, change_info, addToFinalMatchesMapping oV nV final_matches + ) else + false, change_info, final_matches + ) +let compare_varinfo_exact = eq_glob_var ~finalizeOnlyExactMatch:true + (* If some CFGs of the two functions to be compared are provided, a fine-grained CFG comparison is done that also determines which * nodes of the function changed. If on the other hand no CFGs are provided, the "old" AST comparison on the CIL.file is * used for functions. Then no information is collected regarding which parts/nodes of the function changed. *) -let eqF (old: Cil.fundec) (current: Cil.fundec) (cfgs : (cfg * (cfg * cfg)) option) (global_rename_mapping: method_rename_assumptions) = - if should_reanalyze current then - ForceReanalyze current, None - else - (* let unchangedHeader = eq_varinfo old.svar current.svar && GobList.equal eq_varinfo old.sformals current.sformals in *) - let emptyRenameMapping = (StringMap.empty, VarinfoMap.empty) in - - (* Compares the two varinfo lists, returning as a first element, if the size of the two lists are equal, - and as a second a rename_mapping, holding the rename assumptions *) - let rec rename_mapping_aware_compare (alocals: varinfo list) (blocals: varinfo list) (rename_mapping: string StringMap.t) = match alocals, blocals with - | [], [] -> true, rename_mapping - | origLocal :: als, nowLocal :: bls -> - let new_mapping = StringMap.add origLocal.vname nowLocal.vname rename_mapping in - - (*TODO: maybe optimize this with eq_varinfo*) - rename_mapping_aware_compare als bls new_mapping - | _, _ -> false, rename_mapping - in - - let unchangedHeader, headerRenameMapping = match cfgs with - | None -> ( - let headerSizeEqual, headerRenameMapping = rename_mapping_aware_compare old.sformals current.sformals (StringMap.empty) in - let actHeaderRenameMapping = (headerRenameMapping, global_rename_mapping) in - eq_varinfo old.svar current.svar ~rename_mapping:actHeaderRenameMapping && GobList.equal (eq_varinfo ~rename_mapping:actHeaderRenameMapping) old.sformals current.sformals, headerRenameMapping - ) - | Some _ -> ( - eq_varinfo old.svar current.svar ~rename_mapping:emptyRenameMapping && GobList.equal (eq_varinfo ~rename_mapping:emptyRenameMapping) old.sformals current.sformals, StringMap.empty - ) - in - if not unchangedHeader then ChangedFunHeader current, None +let eqF (old: Cil.fundec) (current: Cil.fundec) (cfgs : (cfg * (cfg * cfg)) option) (global_function_rename_mapping: method_rename_assumptions) (global_var_rename_mapping: glob_var_rename_assumptions) = + let identical, diffOpt, (_, renamed_method_dependencies, renamed_global_vars_dependencies, renamesOnSuccess) = + if should_reanalyze current then + ForceReanalyze current, None, empty_rename_mapping else - (* Here the local variables are checked to be equal *) - (*flag: when running on cfg, true iff the locals are identical; on ast: if the size of the locals stayed the same*) - let sameLocals, local_rename = - match cfgs with - | None -> rename_mapping_aware_compare old.slocals current.slocals headerRenameMapping - | Some _ -> GobList.equal (eq_varinfo ~rename_mapping:emptyRenameMapping) old.slocals current.slocals, StringMap.empty - in - let rename_mapping: rename_mapping = (local_rename, global_rename_mapping) in - - if not sameLocals then - (Changed, None) + + let add_locals_to_rename_mapping la lb map = + try + List.fold_left2 (fun map a b -> StringMap.add a.vname b.vname map) map la lb + with Invalid_argument _ -> map in + + let parameterMapping = add_locals_to_rename_mapping old.sformals current.sformals StringMap.empty in + let renameMapping = (parameterMapping, global_function_rename_mapping, global_var_rename_mapping, ([], [])) in + + (* compare the function header based on the collected rename assumptions for parameters *) + let unchangedHeader, renameMapping = eq_varinfo old.svar current.svar ~rename_mapping:renameMapping + &&>> forward_list_equal eq_varinfo old.sformals current.sformals in + + if not unchangedHeader then ChangedFunHeader current, None, empty_rename_mapping + else + (* include matching of local variables into rename mapping *) + let renameMapping = match renameMapping with + | (pm, gf, gv, re) -> (add_locals_to_rename_mapping old.slocals current.slocals pm, gf, gv, re) in + let sameLocals, renameMapping = forward_list_equal eq_varinfo old.slocals current.slocals ~rename_mapping:renameMapping in + + if not sameLocals then + (Changed, None, empty_rename_mapping) + else + match cfgs with + | None -> + let (identical, new_rename_mapping) = eq_block (old.sbody, old) (current.sbody, current) ~rename_mapping:renameMapping in + unchanged_to_change_status identical, None, new_rename_mapping + | Some (cfgOld, (cfgNew, cfgNewBack)) -> + let module CfgOld : MyCFG.CfgForward = struct let next = cfgOld end in + let module CfgNew : MyCFG.CfgBidir = struct let prev = cfgNewBack let next = cfgNew end in + let matches, diffNodes1, updated_rename_mapping = compareFun (module CfgOld) (module CfgNew) old current renameMapping in + if diffNodes1 = [] then (Unchanged, None, updated_rename_mapping) + else (Changed, Some {unchangedNodes = matches; primObsoleteNodes = diffNodes1}, updated_rename_mapping) + in + identical, diffOpt, renamed_method_dependencies, renamed_global_vars_dependencies, renamesOnSuccess + +let eqF_only_consider_exact_match gc_old gc_new change_info final_matches oldMap newMap = + match gc_old.def, gc_new.def with + | None, None -> ( + match gc_old.decls, gc_new.decls with + | Some old_var, Some new_var -> + compare_varinfo_exact old_var gc_old oldMap new_var gc_new newMap change_info final_matches + | _ -> failwith "A global collection should never be empty") + | Some (Fun f1), Some (Fun f2) -> ( + if already_matched f1.svar f2.svar final_matches then + (* check if this function was already matched and lookup the result *) + change_info_lookup gc_old gc_new change_info, change_info, final_matches + else if not (preservesSameNameMatches f1.svar.vname oldMap f2.svar.vname newMap) then + (* check that names of match are each only contained in new or old file *) + false, change_info, final_matches else - match cfgs with - | None -> unchanged_to_change_status (eq_block (old.sbody, old) (current.sbody, current) ~rename_mapping), None - | Some (cfgOld, (cfgNew, cfgNewBack)) -> - let module CfgOld : MyCFG.CfgForward = struct let next = cfgOld end in - let module CfgNew : MyCFG.CfgBidir = struct let prev = cfgNewBack let next = cfgNew end in - let matches, diffNodes1 = compareFun (module CfgOld) (module CfgNew) old current in - if diffNodes1 = [] then (Unchanged, None) - else (Changed, Some {unchangedNodes = matches; primObsoleteNodes = diffNodes1}) - -let eq_glob (old: global_col) (current: global_col) (cfgs : (cfg * (cfg * cfg)) option) (global_rename_mapping: method_rename_assumptions) = - match old.def, current.def with - | Some (Var x), Some (Var y) -> unchanged_to_change_status (eq_varinfo x y ~rename_mapping:(StringMap.empty, VarinfoMap.empty)), None (* ignore the init_info - a changed init of a global will lead to a different start state *) - | Some (Fun f), Some (Fun g) -> eqF f g cfgs global_rename_mapping - | None, None -> (match old.decls, current.decls with - | Some x, Some y -> unchanged_to_change_status (eq_varinfo x y ~rename_mapping:(StringMap.empty, VarinfoMap.empty)), None - | _, _ -> failwith "should never collect any empty entries in GlobalMap") - | _, _ -> Changed, None (* it is considered to be changed (not added or removed) because a global collection only exists in the map - if there is at least one declaration or definition for this global *) + (* the exact comparison is always uses the AST comparison because only when unchanged this match is manifested *) + let doMatch, diff, fun_deps, global_deps, renamesOnSuccess = eqF f1 f2 None VarinfoMap.empty VarinfoMap.empty in + match doMatch with + | Unchanged when empty_rename_assms (VarinfoMap.filter (fun vo vn -> not (vo.vname = f1.svar.vname && vn.vname = f2.svar.vname)) fun_deps) && empty_rename_assms global_deps -> + performRenames renamesOnSuccess; + change_info.unchanged <- {old = gc_old; current = gc_new} :: change_info.unchanged; + let final_matches = addToFinalMatchesMapping f1.svar f2.svar final_matches in + true, change_info, final_matches + | Unchanged -> false, change_info, final_matches + | Changed -> false, change_info, final_matches + | ChangedFunHeader _ -> false, change_info, final_matches + | ForceReanalyze _ -> false, change_info, final_matches) + | _, _ -> false, change_info, final_matches + +let eqF_check_contained_renames ~renameDetection f1 f2 oldMap newMap cfgs gc_old gc_new (change_info, final_matches) = + let doMatch, diff, function_dependencies, global_var_dependencies, renamesOnSuccess = eqF f1 f2 cfgs VarinfoMap.empty VarinfoMap.empty in + performRenames renamesOnSuccess; (* updates enum names and compinfo names and keys that were collected during comparison of this matched function *) + + (* for rename detection, check whether the rename assumptions collected during the function comparison actually match exactly, + otherwise check that the function comparison was successful without collecting any rename assumptions *) + let dependenciesMatch, change_info, final_matches = + if renameDetection then + let extract_globs _ gc map = + let v = get_varinfo gc in + VarinfoMap.add v gc map in + let var_glob_old = GlobalMap.fold extract_globs oldMap VarinfoMap.empty in + let var_glob_new = GlobalMap.fold extract_globs newMap VarinfoMap.empty in + let funDependenciesMatch, change_info, final_matches = VarinfoMap.fold (fun f_old_var f_new_var (acc, ci, fm) -> + let gc_old = VarinfoMap.find f_old_var var_glob_old in + let gc_new = VarinfoMap.find f_new_var var_glob_new in + if acc then + eqF_only_consider_exact_match gc_old gc_new ci fm oldMap newMap + else false, ci, fm + ) function_dependencies (true, change_info, final_matches) in + let globalDependenciesMatch, change_info, final_matches = VarinfoMap.fold (fun old_var new_var (acc, ci, fm) -> + let glob_old = VarinfoMap.find old_var var_glob_old in + let glob_new = VarinfoMap.find new_var var_glob_new in + if acc then + compare_varinfo_exact old_var glob_old oldMap new_var glob_new newMap ci fm + else false, ci, fm + ) global_var_dependencies (true, change_info, final_matches) in + funDependenciesMatch && globalDependenciesMatch, change_info, final_matches + else + empty_rename_assms function_dependencies && empty_rename_assms global_var_dependencies, change_info, final_matches in + + let append_to_changed ~unchangedHeader ~diff = + change_info.changed <- {current = gc_new; old = gc_old; unchangedHeader; diff} :: change_info.changed + in + (match doMatch with + | Unchanged when dependenciesMatch -> + change_info.unchanged <- {old = gc_old; current = gc_new} :: change_info.unchanged + | Unchanged -> + (* no diff is stored, also when comparing functions based on CFG because currently there is no mechanism to detect which part was affected by the *) + append_to_changed ~unchangedHeader:true ~diff:None + | Changed -> append_to_changed ~unchangedHeader:true ~diff:diff + | _ -> (* this can only be ForceReanalyze or ChangedFunHeader *) + change_info.exclude_from_rel_destab <- VarinfoSet.add f1.svar change_info.exclude_from_rel_destab; + append_to_changed ~unchangedHeader:false ~diff:None); + change_info, addToFinalMatchesMapping f1.svar f2.svar final_matches + +let eq_glob ?(matchVars=true) ?(matchFuns=true) ?(renameDetection=false) oldMap newMap cfgs gc_old gc_new (change_info, final_matches) = + match gc_old.def, gc_new.def with + | Some (Var v1), Some (Var v2) when matchVars -> let _, ci, fm = eq_glob_var v1 gc_old oldMap v2 gc_new newMap change_info final_matches in ci, fm + | Some (Fun f1), Some (Fun f2) when matchFuns -> + eqF_check_contained_renames ~renameDetection f1 f2 oldMap newMap cfgs gc_old gc_new (change_info, final_matches) + | None, None -> (match gc_old.decls, gc_new.decls with + | Some v1, Some v2 when matchVars -> let _, ci, fm = eq_glob_var v1 gc_old oldMap v2 gc_new newMap change_info final_matches in ci, fm + | _ -> change_info, final_matches (* a global collection should never be empty *)) + (* Without rename detection a global definition or declaration that does not have respective counterpart in the other version is considered to be changed (not added or removed) + because a global collection only exists in the map if there is at least one declaration or definition for this global. + For the rename detection they can only be added to changed when the according flag is set, because there would be duplicates when iterating over the globals several times. *) + | Some (Var _), None + | None, Some (Var _) -> if matchVars then ( + change_info.changed <- {old = gc_old; current = gc_new; diff = None; unchangedHeader = true} :: change_info.changed; + change_info, addToFinalMatchesMapping (get_varinfo gc_old) (get_varinfo gc_new) final_matches) + else + change_info, final_matches + | _, _ -> if matchVars && matchFuns then ( + change_info.changed <- {old = gc_old; current = gc_new; diff = None; unchangedHeader = true} :: change_info.changed; + change_info, addToFinalMatchesMapping (get_varinfo gc_old) (get_varinfo gc_new) final_matches) + else + change_info, final_matches + +let addNewGlobals name gc_new (change_info, final_matches) = + if not (VarinfoMap.mem (get_varinfo gc_new) (snd final_matches)) then + change_info.added <- gc_new :: change_info.added; + (change_info, final_matches) + +let addOldGlobals name gc_old (change_info, final_matches) = + if not (VarinfoMap.mem (get_varinfo gc_old) (fst final_matches)) then + change_info.removed <- gc_old :: change_info.removed; + (change_info, final_matches) let compareCilFiles ?(eq=eq_glob) (oldAST: file) (newAST: file) = let cfgs = if GobConfig.get_string "incremental.compare" = "cfg" - then Some (CfgTools.getCFG oldAST |> fst, CfgTools.getCFG newAST) + then Some Batteries.(CfgTools.getCFG oldAST |> Tuple3.first, CfgTools.getCFG newAST |> Tuple3.get12) else None in - let addGlobal map global = + let addGlobal map global = try let name, col = match global with | GVar (v,_,_) -> v.vname, {decls = None; def = Some (Var v)} @@ -150,63 +312,38 @@ let compareCilFiles ?(eq=eq_glob) (oldAST: file) (newAST: file) = Not_found -> map in - (* Store a map from functionNames in the old file to the function definition*) + (* Store a map from global names in the old file to the globals declarations and/or definition *) let oldMap = Cil.foldGlobals oldAST addGlobal GlobalMap.empty in let newMap = Cil.foldGlobals newAST addGlobal GlobalMap.empty in - let generate_global_rename_mapping name current_global = - try - let old_global = GlobalMap.find name oldMap in - match old_global.def, current_global.def with - | Some (Fun f1), Some (Fun f2) -> - let renamed_params: string StringMap.t = if (List.length f1.sformals) = (List.length f2.sformals) then - let mappings = List.combine f1.sformals f2.sformals |> - List.filter (fun (original, now) -> not (original.vname = now.vname)) |> - List.map (fun (original, now) -> (original.vname, now.vname)) |> - List.to_seq - in - StringMap.add_seq mappings StringMap.empty - else StringMap.empty in - if not (f1.svar.vname = f2.svar.vname) || (StringMap.cardinal renamed_params) > 0 then - Some (f1.svar, {original_method_name = f1.svar.vname; new_method_name = f2.svar.vname; parameter_renames = renamed_params}) - else None - | _, _ -> None - with Not_found -> None - in - - let global_rename_mapping: method_rename_assumptions = GlobalMap.fold (fun name global_col current_global_rename_mapping -> - match generate_global_rename_mapping name global_col with - | Some (funVar, rename_mapping) -> VarinfoMap.add funVar rename_mapping current_global_rename_mapping - | None -> current_global_rename_mapping - ) newMap VarinfoMap.empty in - let changes = empty_change_info () in global_typ_acc := []; - let findChanges map name current_global global_rename_mapping = + + let findChanges ?(matchVars=true) ?(matchFuns=true) ?(renameDetection=false) oldMap newMap cfgs name gc_new (change_info, final_matches) = try - let old_global = GlobalMap.find name map in - (* Do a (recursive) equal comparison ignoring location information *) - let change_status, diff = eq old_global current_global cfgs global_rename_mapping in - let append_to_changed ~unchangedHeader = - changes.changed <- {current = current_global; old = old_global; unchangedHeader; diff} :: changes.changed - in - match change_status with - | Changed -> - append_to_changed ~unchangedHeader:true - | Unchanged -> changes.unchanged <- {current = current_global; old = old_global} :: changes.unchanged - | ChangedFunHeader f - | ForceReanalyze f -> - changes.exclude_from_rel_destab <- VarinfoSet.add f.svar changes.exclude_from_rel_destab; - append_to_changed ~unchangedHeader:false; - with Not_found -> changes.added <- current_global::changes.added (* Global could not be found in old map -> added *) - in + let gc_old = GlobalMap.find name oldMap in + eq ~matchVars ~matchFuns ~renameDetection oldMap newMap cfgs gc_old gc_new (change_info, final_matches) + with Not_found -> + if not renameDetection then + change_info.added <- gc_new::change_info.added; (* Global could not be found in old map -> added *) + change_info, final_matches in - (* For each function in the new file, check whether a function with the same name - already existed in the old version, and whether it is the same function. *) - GlobalMap.iter (fun name glob_col -> findChanges oldMap name glob_col global_rename_mapping) newMap; + if GobConfig.get_bool "incremental.detect-renames" then ( + let _ = + (changes, (VarinfoMap.empty, VarinfoMap.empty)) (* change_info and final_matches (bi-directional) is propagated *) + |> GlobalMap.fold (findChanges ~matchVars:true ~matchFuns:false ~renameDetection:true oldMap newMap cfgs) newMap + |> GlobalMap.fold (findChanges ~matchVars:false ~matchFuns:true ~renameDetection:true oldMap newMap cfgs) newMap + |> GlobalMap.fold addNewGlobals newMap + |> GlobalMap.fold addOldGlobals oldMap in - (* We check whether functions have been added or removed *) - GlobalMap.iter (fun name glob -> if not (GlobalMap.mem name newMap) then changes.removed <- (glob::changes.removed)) oldMap; + () + ) else ( + let _ = + (changes, (VarinfoMap.empty, VarinfoMap.empty)) (* change_info and final_matches (bi-directional) is propagated *) + |> GlobalMap.fold (findChanges oldMap newMap cfgs) newMap + |> GlobalMap.fold addOldGlobals oldMap in + () + ); changes (** Given an (optional) equality function between [Cil.global]s, an old and a new [Cil.file], this function computes a [change_info], diff --git a/src/incremental/makefileUtil.ml b/src/incremental/makefileUtil.ml index a262849156..843981ee38 100644 --- a/src/incremental/makefileUtil.ml +++ b/src/incremental/makefileUtil.ml @@ -1,4 +1,5 @@ -open Prelude +(** Input program from a real-world project using a Makefile. *) + open Unix let buff_size = 1024 @@ -63,7 +64,7 @@ let run_cilly (path: Fpath.t) ~all_cppflags = remove_comb_files path; (* Combine source files with make using cilly as compiler *) let gcc_path = GobConfig.get_string "exp.gcc_path" in - let cflags = if all_cppflags = [] then "" else " CFLAGS+=" ^ Filename.quote (String.join " " all_cppflags) in + let cflags = if all_cppflags = [] then "" else " CFLAGS+=" ^ Filename.quote (BatString.join " " all_cppflags) in let (exit_code, output) = exec_command ~path ("make CC=\"cilly --gcc=" ^ gcc_path ^ " --merge --keepmerged\"" ^cflags ^ " " ^ "LD=\"cilly --gcc=" ^ gcc_path ^ " --merge --keepmerged\"") in print_string output; diff --git a/src/incremental/maxIdUtil.ml b/src/incremental/maxIdUtil.ml index 7854fd8a59..a5c4fdda61 100644 --- a/src/incremental/maxIdUtil.ml +++ b/src/incremental/maxIdUtil.ml @@ -1,3 +1,5 @@ +(** Tracking of maximum CIL IDs in use. *) + open GoblintCil type max_ids = { diff --git a/src/incremental/serialize.ml b/src/incremental/serialize.ml index 5a521c3daa..bddf3aa383 100644 --- a/src/incremental/serialize.ml +++ b/src/incremental/serialize.ml @@ -1,4 +1,6 @@ -open Prelude +(** Serialization/deserialization of incremental analysis data. *) + +open Batteries (* TODO: GoblintDir *) let incremental_data_file_name = "analysis.data" @@ -26,7 +28,7 @@ let marshal obj fileName = let unmarshal fileName = if GobConfig.get_bool "dbg.verbose" then - (* Do NOT replace with Printf because of Gobview: https://github.com/goblint/gobview/issues/10 *) + (* Do NOT replace with Printf because of GobView: https://github.com/goblint/gobview/issues/10 *) print_endline ("Unmarshalling " ^ Fpath.to_string fileName ^ "... If type of content changed, this will result in a segmentation fault!"); Marshal.input (open_in_bin (Fpath.to_string fileName)) diff --git a/src/incremental/updateCil.ml b/src/incremental/updateCil.ml index 2f9628b4c1..e761be7671 100644 --- a/src/incremental/updateCil.ml +++ b/src/incremental/updateCil.ml @@ -1,3 +1,5 @@ +(** Combination of CIL files using comparison results. *) + open GoblintCil open CompareCIL open MaxIdUtil @@ -32,6 +34,7 @@ let update_ids (old_file: file) (ids: max_ids) (new_file: file) (changes: change target.svar <- src.svar; in let reset_fun (f: fundec) (old_f: fundec) = + old_f.svar.vname <- f.svar.vname; f.svar.vid <- old_f.svar.vid; List.iter2 (fun l o_l -> l.vid <- o_l.vid; o_l.vname <- l.vname) f.slocals old_f.slocals; List.iter2 (fun lo o_f -> lo.vid <- o_f.vid; o_f.vname <- lo.vname) f.sformals old_f.sformals; @@ -48,6 +51,7 @@ let update_ids (old_file: file) (ids: max_ids) (new_file: file) (changes: change in let reset_var (v: varinfo) (old_v: varinfo)= v.vid <- old_v.vid; + old_v.vname <- v.vname; update_vid_max v.vid; in let reset_globals (glob: unchanged_global) = diff --git a/src/index.mld b/src/index.mld new file mode 100644 index 0000000000..2afbbc97ae --- /dev/null +++ b/src/index.mld @@ -0,0 +1,51 @@ +{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.common} +This {{!page-common}unwrapped library} contains various common 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} + + +{1 Vendored} +The following libraries are vendored in Goblint. + +{2 Library goblint.zarith.mlgmpidl} +{!modules:Z_mlgmpidl} diff --git a/src/maingoblint.ml b/src/maingoblint.ml index 15097f255e..1512e63b47 100644 --- a/src/maingoblint.ml +++ b/src/maingoblint.ml @@ -1,20 +1,19 @@ -(** This is the main program! *) +(** Main external executable functionality: command-line, front-end and analysis execution. *) -open Prelude +open Batteries open GobConfig open Printf -open Goblintutil open GoblintCil 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) -> @@ -55,13 +54,13 @@ let rec option_spec_list: Arg_complete.speclist Lazy.t = lazy ( 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 - else (prerr_endline "Goblint has been compiled without tracing, recompile in trace profile (./scripts/trace_on.sh)"; raise Exit) + else (prerr_endline "Goblint has been compiled without tracing, recompile in trace profile (./scripts/trace_on.sh)"; raise Stdlib.Exit) in let configure_html () = if (get_string "outfile" = "") then set_string "outfile" "result"; if get_string "exp.g2html_path" = "" then - set_string "exp.g2html_path" (Fpath.to_string exe_dir); + set_string "exp.g2html_path" (Fpath.to_string GobSys.exe_dir); set_bool "exp.cfgdot" true; set_bool "g2html" true; set_string "result" "fast_xml" @@ -126,16 +125,17 @@ and rest_all_complete = lazy (Arg_complete.Rest_all_compat.create complete Arg_c and complete args = Arg_complete.complete_argv args (Lazy.force option_spec_list) Arg_complete.empty |> List.iter print_endline; - raise Exit + raise Stdlib.Exit let eprint_color m = eprintf "%s\n" (MessageUtil.colorize ~fd:Unix.stderr m) let check_arguments () = - let fail m = (let m = "Option failure: " ^ m in eprint_color ("{red}"^m); failwith m) in(* unused now, but might be useful for future checks here *) + let fail m = (let m = "Option failure: " ^ m in eprint_color ("{red}"^m); failwith m) in let warn m = eprint_color ("{yellow}Option warning: "^m) in 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"); @@ -143,8 +143,25 @@ let check_arguments () = if get_bool "incremental.restart.sided.enabled" && get_string_list "incremental.restart.list" <> [] then warn "Passing a non-empty list to incremental.restart.list (manual restarting) while incremental.restart.sided.enabled (automatic restarting) is activated."; if get_bool "ana.autotune.enabled" && get_bool "incremental.load" then (set_bool "ana.autotune.enabled" false; warn "ana.autotune.enabled implicitly disabled by incremental.load"); if get_bool "exp.basic-blocks" && not (get_bool "justcil") && List.mem "assert" @@ get_string_list "trans.activated" then (set_bool "exp.basic-blocks" false; warn "The option exp.basic-blocks implicitely disabled by activating the \"assert\" tranformation."); + if List.mem "remove_dead_code" @@ get_string_list "trans.activated" then ( + (* 'assert' transform happens before 'remove_dead_code' transform *) + ignore @@ List.fold_left + (fun deadcodeTransOccurred t -> + if deadcodeTransOccurred && t = "assert" then + fail "trans.activated: the 'assert' transform may not occur after the 'remove_dead_code' transform"; + deadcodeTransOccurred || t = "remove_dead_code") + false (get_string_list "trans.activated"); + (* compressing basic blocks or minimizing CFG makes dead code transformation much less + precise, since liveness information is then effectively only stored per-block *) + let imprecise_options = List.filter get_bool ["exp.basic-blocks"; "exp.mincfg"] in + if imprecise_options <> [] then + warn ( + "trans.activated: to increase the precision of 'remove_dead_code' transform, disable " + ^ 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 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 () = @@ -154,19 +171,24 @@ let handle_flags () = Errormsg.verboseFlag := true ); - if get_bool "dbg.debug" then - set_bool "warn.debug" true; + if get_bool "ana.sv-comp.functions" then + set_auto "lib.activated[+]" "sv-comp"; + + if get_bool "kernel" then + set_auto "lib.activated[+]" "linux-kernel"; match get_string "dbg.dump" with | "" -> () | path -> - Messages.formatter := Format.formatter_of_out_channel (Legacy.open_out (Legacy.Filename.concat path "warnings.out")); + Messages.formatter := Format.formatter_of_out_channel (open_out (Legacy.Filename.concat path "warnings.out")); set_string "outfile" "" let handle_options () = check_arguments (); AfterConfig.run (); - Sys.set_signal (Goblintutil.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. *) + 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 (); Cilfacade.init_options (); handle_flags () @@ -180,37 +202,51 @@ let parse_arguments () = begin match !writeconffile with | Some writeconffile -> GobConfig.write_file writeconffile; - raise Exit + raise Stdlib.Exit | None -> () end; handle_options (); if not (get_bool "server.enabled") && get_string_list "files" = [] then ( prerr_endline "No files for Goblint?"; prerr_endline "Try `goblint --help' for more information."; - raise Exit + raise Stdlib.Exit ) + +exception FrontendError of string + let basic_preprocess_counts = Preprocessor.FpathH.create 3 (** Use gcc to preprocess a file. Returns the path to the preprocessed file. *) -let basic_preprocess ~all_cppflags fname = - (* The actual filename of the preprocessed sourcefile *) - let basename = Fpath.rem_ext (Fpath.base fname) in - (* generate unique preprocessed filename in case multiple basic files have same basename (from different directories), happens in ddverify *) - let count = Preprocessor.FpathH.find_default basic_preprocess_counts basename 0 in - let unique_name = - if count = 0 then - basename - else - Fpath.add_ext (string_of_int count) basename +let basic_preprocess ?preprocess ~all_cppflags fname = + let preprocess = match preprocess with + | Some b -> b (* Explicitly forced *) + | None when not (GobConfig.get_bool "pre.enabled") -> false (* Globally disabled *) + | None -> + let ext = Fpath.get_ext fname in + ext <> ".i" in - Preprocessor.FpathH.replace basic_preprocess_counts basename (count + 1); - let nname = Fpath.append (GoblintDir.preprocessed ()) (Fpath.add_ext ".i" unique_name) in - (* Preprocess using cpp. *) - let arguments = all_cppflags @ Fpath.to_string fname :: "-o" :: Fpath.to_string nname :: [] in - let command = Filename.quote_command (Preprocessor.get_cpp ()) arguments in - if get_bool "dbg.verbose" then print_endline command; - (nname, Some {ProcessPool.command; cwd = None}) + if preprocess then ( + (* The actual filename of the preprocessed sourcefile *) + let basename = Fpath.rem_ext (Fpath.base fname) in + (* generate unique preprocessed filename in case multiple basic files have same basename (from different directories), happens in ddverify *) + let count = Preprocessor.FpathH.find_default basic_preprocess_counts basename 0 in + let unique_name = + if count = 0 then + basename + else + Fpath.add_ext (string_of_int count) basename + in + Preprocessor.FpathH.replace basic_preprocess_counts basename (count + 1); + (* Preprocess using cpp. *) + let nname = Fpath.append (GoblintDir.preprocessed ()) (Fpath.add_ext ".i" unique_name) in + let arguments = all_cppflags @ Fpath.to_string fname :: "-o" :: Fpath.to_string nname :: [] in + let command = Filename.quote_command (Preprocessor.get_cpp ()) arguments in + if get_bool "dbg.verbose" then print_endline command; + (nname, Some {ProcessPool.command; cwd = None}) + ) + else + (fname, None) (** Preprocess all files. Return list of preprocessed files and the temp directory name. *) let preprocess_files () = @@ -223,7 +259,7 @@ let preprocess_files () = (* the base include directory *) (* TODO: any better way? dune executable promotion doesn't add _build sites *) let source_lib_dirs = - let source_lib = Fpath.(exe_dir / "lib") in + let source_lib = Fpath.(GobSys.exe_dir / "lib") in if Sys.file_exists (Fpath.to_string source_lib) && Sys.is_directory (Fpath.to_string source_lib) then ( Sys.readdir Fpath.(to_string source_lib) |> Array.to_list @@ -254,13 +290,17 @@ let preprocess_files () = print_endline "Warning, cannot find goblint's custom include files."; let find_custom_include subpath = - List.find_map (fun custom_include_dir -> + let custom_include_opt = List.find_map_opt (fun custom_include_dir -> let path = Fpath.append custom_include_dir subpath in if Sys.file_exists (Fpath.to_string path) then Some path else None ) custom_include_dirs + in + match custom_include_opt with + | Some custom_include -> custom_include + | None -> raise (FrontendError (Format.asprintf "custom include %a not found" Fpath.pp subpath)) in (* include flags*) @@ -282,7 +322,7 @@ let preprocess_files () = else [] end @ [ - Fpath.(exe_dir / "linux-headers"); + Fpath.(GobSys.exe_dir / "linux-headers"); (* linux-headers not installed with goblint package *) ] in @@ -290,8 +330,7 @@ let preprocess_files () = try List.find (Sys.file_exists % Fpath.to_string) kernel_roots with Not_found -> - prerr_endline "Root directory for kernel include files not found!"; - raise Exit + raise (FrontendError "root directory for kernel include files not found") in let kernel_dir = Fpath.(kernel_root / "include") in @@ -321,49 +360,64 @@ let preprocess_files () = (* preprocess all the files *) if get_bool "dbg.verbose" then print_endline "Preprocessing files."; - let rec preprocess_arg_file = function + let rec preprocess_arg_file ?preprocess = function + | filename when not (Sys.file_exists (Fpath.to_string filename)) -> + raise (FrontendError (Format.asprintf "file argument %a not found" Fpath.pp filename)) + | filename when Fpath.filename filename = "Makefile" -> let comb_file = MakefileUtil.generate_and_combine filename ~all_cppflags in - [basic_preprocess ~all_cppflags comb_file] + [basic_preprocess ?preprocess ~all_cppflags comb_file] (* TODO: isn't combined file already preprocessed? *) | filename when Fpath.filename filename = CompilationDatabase.basename -> - CompilationDatabase.load_and_preprocess ~all_cppflags filename + CompilationDatabase.load_and_preprocess ~all_cppflags filename (* TODO: pass ?preprocess? *) | filename when Sys.is_directory (Fpath.to_string filename) -> let dir_files = Sys.readdir (Fpath.to_string filename) in if Array.mem CompilationDatabase.basename dir_files then (* prefer compilation database to Makefile in case both exist, because compilation database is more robust *) - preprocess_arg_file (Fpath.add_seg filename CompilationDatabase.basename) + preprocess_arg_file ?preprocess (Fpath.add_seg filename CompilationDatabase.basename) else if Array.mem "Makefile" dir_files then - preprocess_arg_file (Fpath.add_seg filename "Makefile") + preprocess_arg_file ?preprocess (Fpath.add_seg filename "Makefile") else [] (* don't recurse for anything else *) | filename when Fpath.get_ext filename = ".json" -> - Format.eprintf "Unexpected JSON file argument (possibly missing --conf): %a\n" Fpath.pp filename; - raise Exit + raise (FrontendError (Format.asprintf "unexpected JSON file argument %a (possibly missing --conf)" Fpath.pp filename)) | filename -> - [basic_preprocess ~all_cppflags filename] + [basic_preprocess ?preprocess ~all_cppflags filename] in let extra_files = ref [] in - extra_files := find_custom_include (Fpath.v "stdlib.c") :: find_custom_include (Fpath.v "pthread.c") :: !extra_files; + if List.mem "c" (get_string_list "lib.activated") then + extra_files := find_custom_include (Fpath.v "stdlib.c") :: !extra_files; - if get_bool "ana.sv-comp.functions" then + if List.mem "pthread" (get_string_list "lib.activated") then + extra_files := find_custom_include (Fpath.v "pthread.c") :: !extra_files; + + if List.mem "sv-comp" (get_string_list "lib.activated") then extra_files := find_custom_include (Fpath.v "sv-comp.c") :: !extra_files; - let preprocessed = List.concat_map preprocess_arg_file (!extra_files @ List.map Fpath.v (get_string_list "files")) in + let preprocessed = + List.concat_map preprocess_arg_file (List.map Fpath.v (get_string_list "files")) + @ + List.concat_map (preprocess_arg_file ~preprocess:true) !extra_files + in if not (get_bool "pre.exist") then ( let preprocess_tasks = List.filter_map snd preprocessed in - let terminated task = function + let terminated (task: ProcessPool.task) = function | Unix.WEXITED 0 -> () - | process_status -> failwith (GobUnix.string_of_process_status process_status) + | process_status -> + raise (FrontendError (Format.sprintf "preprocessor %s: %s" (GobUnix.string_of_process_status process_status) task.command)) in - Timing.wrap "preprocess" (ProcessPool.run ~jobs:(Goblintutil.jobs ()) ~terminated) preprocess_tasks + Timing.wrap "preprocess" (ProcessPool.run ~jobs:(GobConfig.jobs ()) ~terminated) preprocess_tasks ); 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 *) @@ -371,14 +425,24 @@ 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 dir = (Option.get task_opt).ProcessPool.cwd |? goblint_cwd in (* relative to compilation database directory or goblint's cwd *) - let path' = Fpath.normalize @@ Fpath.append dir path in - let path' = Fpath.rem_prefix goblint_cwd path' |? path' in (* remove goblint cwd prefix (if has one) for readability *) + let path' = if get_bool "pre.transform-paths" then ( + let cwd_opt = + let open GobOption.Syntax in + let* task = task_opt in + task.ProcessPool.cwd + in + let dir = cwd_opt |? goblint_cwd in (* relative to compilation database directory or goblint's cwd *) + let path' = Fpath.normalize @@ Fpath.append dir path in + Fpath.rem_prefix goblint_cwd path' |? path' (* remove goblint cwd prefix (if has one) for readability *) + ) + else + path + in Preprocessor.FpathH.modify_def Fpath.Map.empty preprocessed_file (Fpath.Map.add path' system_header) Preprocessor.dependencies; (* record dependency *) (Fpath.to_string path', system_header) in @@ -388,7 +452,13 @@ let parse_preprocessed preprocessed = in Errormsg.transformLocation := transformLocation; - Cilfacade.getAST preprocessed_file + try + Cilfacade.getAST preprocessed_file + with + | Frontc.ParseError s -> + raise (FrontendError (Format.sprintf "Frontc.ParseError: %s" s)) + | Errormsg.Error -> + raise (FrontendError "Errormsg.Error") in List.map get_ast_and_record_deps preprocessed @@ -405,9 +475,12 @@ let merge_parsed parsed = match parsed with | [one] -> Cilfacade.callConstructors one | [] -> - prerr_endline "No files to analyze!"; - raise Exit - | xs -> Cilfacade.getMergedAST xs |> Cilfacade.callConstructors + raise (FrontendError "no files to analyze") + | xs -> + try + Cilfacade.getMergedAST xs |> Cilfacade.callConstructors + with Errormsg.Error -> + raise (FrontendError "Errormsg.Error") in Cilfacade.rmTemps merged_AST; @@ -425,17 +498,15 @@ let preprocess_parse_merge () = let do_stats () = if get_bool "dbg.timing.enabled" then ( print_newline (); - ignore (Pretty.printf "vars = %d evals = %d narrow_reuses = %d\n" !Goblintutil.vars !Goblintutil.evals !Goblintutil.narrow_reuses); + SolverStats.print (); print_newline (); print_string "Timings:\n"; - Timing.Default.print (Format.formatter_of_out_channel @@ Messages.get_out "timing" Legacy.stderr); + Timing.Default.print (Stdlib.Format.formatter_of_out_channel @@ Messages.get_out "timing" Legacy.stderr); flush_all () ) let reset_stats () = - Goblintutil.vars := 0; - Goblintutil.evals := 0; - Goblintutil.narrow_reuses := 0; + SolverStats.reset (); Timing.Default.reset (); Timing.Program.reset () @@ -443,9 +514,9 @@ let reset_stats () = let do_analyze change_info merged_AST = (* direct the output to file if requested *) if not (get_bool "g2html" || get_string "outfile" = "") then ( - if !Goblintutil.out <> Legacy.stdout then - Legacy.close_out !Goblintutil.out; - Goblintutil.out := Legacy.open_out (get_string "outfile")); + if !Messages.out <> Legacy.stdout then + Legacy.close_out !Messages.out; + Messages.out := Legacy.open_out (get_string "outfile")); let module L = Printable.Liszt (CilType.Fundec) in if get_bool "justcil" then @@ -455,7 +526,7 @@ let do_analyze change_info merged_AST = (* we first find the functions to analyze: *) if get_bool "dbg.verbose" then print_endline "And now... the Goblin!"; let (stf,exf,otf as funs) = Cilfacade.getFuns merged_AST in - if stf@exf@otf = [] then failwith "No suitable function to start from."; + if stf@exf@otf = [] then raise (FrontendError "no suitable function to start from"); if get_bool "dbg.verbose" then ignore (Pretty.printf "Startfuns: %a\nExitfuns: %a\nOtherfuns: %a\n" L.pretty stf L.pretty exf L.pretty otf); (* and here we run the analysis! *) @@ -470,14 +541,14 @@ let do_analyze change_info merged_AST = try Control.analyze change_info ast funs with e -> let backtrace = Printexc.get_raw_backtrace () in (* capture backtrace immediately, otherwise the following loses it (internal exception usage without raise_notrace?) *) - Goblintutil.should_warn := true; (* such that the `about to crash` message gets printed *) + AnalysisState.should_warn := true; (* such that the `about to crash` message gets printed *) let pretty_mark () = match Goblint_backtrace.find_marks e with | m :: _ -> Pretty.dprintf " at mark %s" (Goblint_backtrace.mark_to_string m) | [] -> Pretty.nil in Messages.error ~category:Analyzer "About to crash%t!" pretty_mark; (* trigger Generic.SolverStats...print_stats *) - Goblintutil.(self_signal (signal_of_string (get_string "dbg.solver-signal"))); + GobSys.(self_signal (signal_of_string (get_string "dbg.solver-signal"))); do_stats (); print_newline (); Printexc.raise_with_backtrace e backtrace (* re-raise with captured inner backtrace *) @@ -488,48 +559,69 @@ let do_analyze change_info merged_AST = ) let do_html_output () = - (* TODO: Fpath *) - let jar = Filename.concat (get_string "exp.g2html_path") "g2html.jar" in if get_bool "g2html" then ( - if Sys.file_exists jar then ( - let command = "java -jar "^ jar ^" --num-threads " ^ (string_of_int (jobs ())) ^ " --dot-timeout 0 --result-dir "^ (get_string "outfile")^" "^ !Messages.xml_file_name in - try match Timing.wrap "g2html" Unix.system command with - | Unix.WEXITED 0 -> () - | _ -> eprintf "HTML generation failed! Command: %s\n" command - with Unix.Unix_error (e, f, a) -> + let jar = Fpath.(v (get_string "exp.g2html_path") / "g2html.jar") in + if Sys.file_exists (Fpath.to_string jar) then ( + let command = Filename.quote_command "java" [ + "-jar"; Fpath.to_string jar; + "--num-threads"; string_of_int (jobs ()); + "--dot-timeout"; "0"; + "--result-dir"; get_string "outfile"; + !Messages.xml_file_name + ] + in + match Timing.wrap "g2html" Unix.system command with + | Unix.WEXITED 0 -> () + | _ -> eprintf "HTML generation failed! Command: %s\n" command + | exception Unix.Unix_error (e, f, a) -> eprintf "%s at syscall %s with argument \"%s\".\n" (Unix.error_message e) f a ) else - eprintf "Warning: jar file %s not found.\n" jar + Format.eprintf "Warning: jar file %a not found.\n" Fpath.pp jar ) -let do_gobview () = - (* TODO: Fpath *) - let create_symlink target link = - if not (Sys.file_exists link) then Unix.symlink target link - in +let do_gobview cilfile = let gobview = GobConfig.get_bool "gobview" in - let goblint_root = - Filename.concat (Unix.getcwd ()) (Filename.dirname Sys.argv.(0)) - in - let dist_dir = Filename.concat goblint_root "_build/default/gobview/dist" in - let js_file = Filename.concat dist_dir "main.js" in + let goblint_root = GobFpath.cwd_append (fst (Fpath.split_base (Fpath.v Sys.argv.(0)))) in + let dist_dir = Fpath.(goblint_root // (Fpath.v "_build/default/gobview/dist")) in + let js_file = Fpath.(dist_dir / "main.js") in if gobview then ( - if Sys.file_exists js_file then ( + if Sys.file_exists (Fpath.to_string js_file) then ( let save_run = GobConfig.get_string "save_run" in - let run_dir = if save_run <> "" then save_run else "run" in + let run_dir = Fpath.v(if save_run <> "" then save_run else "run") in + (* copy relevant c files to gobview directory *) + let file_dir = Fpath.(run_dir / "files") in + GobSys.mkdir_or_exists file_dir; + let file_loc = Hashtbl.create 113 in + 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 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 + let source_paths = Preprocessor.FpathH.to_list Preprocessor.dependencies |> List.concat_map (fun (_, m) -> Fpath.Map.fold (fun p _ acc -> p::acc) m []) in + 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 + Serialize.marshal (Timing.Default.root, Gc.quick_stat ()) stats; let dist_files = - Sys.files_of dist_dir + Sys.files_of (Fpath.to_string dist_dir) |> Enum.filter (fun n -> n <> "dune") |> List.of_enum in List.iter (fun n -> - create_symlink - (Filename.concat dist_dir n) - (Filename.concat run_dir n) + FileUtil.cp + [Fpath.to_string (Fpath.(dist_dir / n))] + (Fpath.to_string (Fpath.(run_dir / n))) ) dist_files ) else - eprintf "Warning: Cannot locate Gobview.\n" + eprintf "Warning: Cannot locate GobView.\n" ) let handle_extraspecials () = @@ -584,4 +676,4 @@ let () = (* signal for printing backtrace; other signals in Generic.SolverStats let open Sys in (* whether interactive interrupt (ctrl-C) terminates the program or raises the Break exception which we use below to print a backtrace. https://ocaml.org/api/Sys.html#VALcatch_break *) catch_break true; - set_signal (Goblintutil.signal_of_string (get_string "dbg.backtrace-signal")) (Signal_handle (fun _ -> Printexc.get_callstack 999 |> Printexc.print_raw_backtrace Stdlib.stderr; print_endline "\n...\n")) (* e.g. `pkill -SIGUSR2 goblint`, or `kill`, `htop` *) + set_signal (GobSys.signal_of_string (get_string "dbg.backtrace-signal")) (Signal_handle (fun _ -> Printexc.get_callstack 999 |> Printexc.print_raw_backtrace Stdlib.stderr; print_endline "\n...\n")) (* e.g. `pkill -SIGUSR2 goblint`, or `kill`, `htop` *) diff --git a/src/mainspec.ml b/src/mainspec.ml index e3551be534..4509645f98 100644 --- a/src/mainspec.ml +++ b/src/mainspec.ml @@ -1,5 +1,5 @@ open Goblint_lib -open Prelude (* otherwise open_in would return wrong type for SpecUtil *) +open Batteries (* otherwise open_in would return wrong type for SpecUtil *) open SpecUtil let _ = diff --git a/src/prelude.ml b/src/prelude.ml deleted file mode 100644 index f98df3b202..0000000000 --- a/src/prelude.ml +++ /dev/null @@ -1,37 +0,0 @@ -(* header for all files *) -module All = struct - include (Batteries : module type of Batteries with module Format := Batteries.Format) - module Format = Batteries.Legacy.Format - let comp2 f g a b = f (g a) (g b) - let compareBy ?cmp:(cmp=compare) f = comp2 cmp f - let str_remove m s = String.nreplace ~str:s ~sub:m ~by:"" - - (* Sys.time gives runtime in seconds as float *) - let split_time () = (* gives CPU time in h,m,s,ms *) - let f = Sys.time () in - let i = int_of_float f in - let ms = int_of_float (Float.modulo f 1.0 *. 1000.) in - i / 3600, i / 60 mod 60, i mod 60, ms - let string_of_time () = (* CPU time as hh:mm:ss.ms *) - let h,m,s,ms = split_time () in - Printf.sprintf "%02d:%02d:%02d.%03d" h m s ms - - let localtime () = - let open Unix in - let tm = time () |> localtime in - Printf.sprintf "%d-%02d-%02d %02d:%02d:%02d" (tm.tm_year + 1900) (tm.tm_mon + 1) tm.tm_mday tm.tm_hour tm.tm_min tm.tm_sec -end -include All (* shortcut so that 'open Prelude' is enough *) - -(* header for files in analyses *) -module Ana = struct - include All - (* CIL *) - include GoblintCil - let d_varinfo () x = d_lval () (Var x, NoOffset) - include Pretty - let sprint f x = Pretty.sprint ~width:80 (f () x) - (* Analyses.Spec etc. *) - (* include Analyses (* circular build :( *) *) - (* module M = Messages (* same, but this is in Analyses anyway *) *) -end diff --git a/src/solvers/effectWConEq.ml b/src/solvers/effectWConEq.ml index a7f1991225..c6dcf8f0e9 100644 --- a/src/solvers/effectWConEq.ml +++ b/src/solvers/effectWConEq.ml @@ -1,4 +1,6 @@ -open Prelude +(** ([effectWConEq]). *) + +open Batteries open Analyses open Constraints diff --git a/src/solvers/generic.ml b/src/solvers/generic.ml index d4577df273..2569341dd1 100644 --- a/src/solvers/generic.ml +++ b/src/solvers/generic.ml @@ -1,4 +1,6 @@ -open Prelude +(** Various simple/old solvers and solver utilities. *) + +open Batteries open GobConfig open Analyses @@ -11,7 +13,7 @@ module LoadRunSolver: GenericEqSolver = let load_run = Fpath.v (get_string "load_run") in let solver = Fpath.(load_run / solver_file) in if get_bool "dbg.verbose" then - (* Do NOT replace with Printf because of Gobview: https://github.com/goblint/gobview/issues/10 *) + (* Do NOT replace with Printf because of GobView: https://github.com/goblint/gobview/issues/10 *) print_endline ("Loading the solver result of a saved run from " ^ (Fpath.to_string solver)); let vh: S.d VH.t = Serialize.unmarshal solver in if get_bool "ana.opt.hashcons" then ( @@ -35,8 +37,6 @@ struct open S open Messages - module GU = Goblintutil - let stack_d = ref 0 let full_trace = false let start_c = 0 @@ -64,7 +64,7 @@ struct let stop_event () = () let new_var_event x = - incr Goblintutil.vars; + incr SolverStats.vars; if tracing then trace "sol" "New %a\n" Var.pretty_trace x let get_var_event x = @@ -72,7 +72,7 @@ struct let eval_rhs_event x = if full_trace then trace "sol" "(Re-)evaluating %a\n" Var.pretty_trace x; - incr Goblintutil.evals; + incr SolverStats.evals; if (get_bool "dbg.solver-progress") then (incr stack_d; print_int !stack_d; flush stdout) let update_var_event x o n = @@ -89,7 +89,7 @@ struct let ncontexts = ref 0 let print_context_stats rho = let histo = Hashtbl.create 13 in (* histogram: node id -> number of contexts *) - let str k = S.Var.pretty_trace () k |> Pretty.sprint ~width:max_int in (* use string as key since k may have cycles which lead to exception *) + let str k = GobPretty.sprint S.Var.pretty_trace k in (* use string as key since k may have cycles which lead to exception *) let is_fun k = match S.Var.node k with FunctionEntry _ -> true | _ -> false in (* only count function entries since other nodes in function will have leq number of contexts *) HM.iter (fun k _ -> if is_fun k then Hashtbl.modify_def 0 (str k) ((+)1) histo) rho; (* let max_k, n = Hashtbl.fold (fun k v (k',v') -> if v > v' then k,v else k',v') histo (Obj.magic (), 0) in *) @@ -115,8 +115,8 @@ struct let print_stats _ = print_newline (); (* print_endline "# Generic solver stats"; *) - Printf.printf "runtime: %s\n" (string_of_time ()); - Printf.printf "vars: %d, evals: %d\n" !Goblintutil.vars !Goblintutil.evals; + Printf.printf "runtime: %s\n" (GobSys.string_of_time ()); + Printf.printf "vars: %d, evals: %d\n" !SolverStats.vars !SolverStats.evals; Option.may (fun v -> ignore @@ Pretty.printf "max updates: %d for var %a\n" !max_c Var.pretty_trace v) !max_var; print_newline (); (* print_endline "# Solver specific stats"; *) @@ -124,9 +124,9 @@ struct print_newline (); (* Timing.print (M.get_out "timing" Legacy.stdout) "Timings:\n"; *) (* Gc.print_stat stdout; (* too verbose, slow and words instead of MB *) *) - let gc = Goblintutil.print_gc_quick_stat Legacy.stdout in + let gc = GobGc.print_quick_stat Legacy.stdout in print_newline (); - Option.may (write_csv [string_of_time (); string_of_int !Goblintutil.vars; string_of_int !Goblintutil.evals; string_of_int !ncontexts; string_of_int gc.Gc.top_heap_words]) stats_csv; + Option.may (write_csv [GobSys.string_of_time (); string_of_int !SolverStats.vars; string_of_int !SolverStats.evals; string_of_int !ncontexts; string_of_int gc.Gc.top_heap_words]) stats_csv; (* print_string "Do you want to continue? [Y/n]"; *) flush stdout (* if read_line () = "n" then raise Break *) @@ -135,7 +135,7 @@ struct let write_header = write_csv ["runtime"; "vars"; "evals"; "contexts"; "max_heap"] (* TODO @ !solver_stats_headers *) in Option.may write_header stats_csv; (* call print_stats on dbg.solver-signal *) - Sys.set_signal (Goblintutil.signal_of_string (get_string "dbg.solver-signal")) (Signal_handle print_stats); + Sys.set_signal (GobSys.signal_of_string (get_string "dbg.solver-signal")) (Signal_handle print_stats); (* call print_stats every dbg.solver-stats-interval *) Sys.set_signal Sys.sigvtalrm (Signal_handle print_stats); (* https://ocaml.org/api/Unix.html#TYPEinterval_timer ITIMER_VIRTUAL is user time; sends sigvtalarm; ITIMER_PROF/sigprof is already used in Timeout.Unix.timeout *) diff --git a/src/solvers/postSolver.ml b/src/solvers/postSolver.ml index a0a105282b..f96ca832a1 100644 --- a/src/solvers/postSolver.ml +++ b/src/solvers/postSolver.ml @@ -1,4 +1,6 @@ -open Prelude +(** Extra constraint system evaluation pass for warning generation, verification, pruning, etc. *) + +open Batteries open Analyses open GobConfig module Pretty = GoblintCil.Pretty @@ -61,7 +63,7 @@ module Prune: F = include Unit (S) (VH) let finalize ~vh ~reachable = - if get_bool "dbg.debug" then + if get_bool "dbg.verbose" then print_endline "Pruning result"; VH.filteri_inplace (fun x _ -> @@ -76,14 +78,17 @@ module Verify: F = include Unit (S) (VH) let init () = - Goblintutil.verified := Some true + AnalysisState.verified := Some true let complain_constraint x ~lhs ~rhs = - Goblintutil.verified := Some false; + 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 = - Goblintutil.verified := Some false; + 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 = @@ -108,11 +113,11 @@ module Warn: F = let old_should_warn = ref None let init () = - old_should_warn := Some !Goblintutil.should_warn; - Goblintutil.should_warn := true + old_should_warn := Some !AnalysisState.should_warn; + AnalysisState.should_warn := true let finalize ~vh ~reachable = - Goblintutil.should_warn := Option.get !old_should_warn + AnalysisState.should_warn := Option.get !old_should_warn end (** Postsolver for save_run option. *) @@ -129,7 +134,7 @@ module SaveRun: F = let save_run = Fpath.v save_run_str in let solver = Fpath.(save_run / solver_file) in if get_bool "dbg.verbose" then - Format.printf "Saving the solver result to %a" Fpath.pp solver; + Format.printf "Saving the solver result to %a\n" Fpath.pp solver; GobSys.mkdir_or_exists save_run; Serialize.marshal vh solver end @@ -189,7 +194,7 @@ struct in let module S = EqConstrSysFromStartEqConstrSys (StartS) in - Goblintutil.postsolving := true; + AnalysisState.postsolving := true; PS.init (); let reachable = PS.init_reachable ~vh in @@ -217,7 +222,7 @@ struct (Timing.wrap "postsolver_iter" (List.iter one_var)) vs; PS.finalize ~vh ~reachable; - Goblintutil.postsolving := false + AnalysisState.postsolving := false let post xs vs vh = Timing.wrap "postsolver" (post xs vs) vh diff --git a/src/solvers/sLR.ml b/src/solvers/sLR.ml index c3c3b4746a..4904731b61 100644 --- a/src/solvers/sLR.ml +++ b/src/solvers/sLR.ml @@ -1,6 +1,8 @@ -(** The 'slr*' solvers. *) +(** Various SLR solvers. -open Prelude + @see Apinis, K. Frameworks for analyzing multi-threaded C. *) + +open Batteries open Analyses open Constraints open Messages @@ -204,7 +206,7 @@ module Make0 = try HM.find keys x with Not_found -> - incr Goblintutil.vars; + incr SolverStats.vars; decr last_key; HM.add keys x !last_key; !last_key @@ -212,7 +214,7 @@ module Make0 = let get_index c = try (HM.find keys c, true) with Not_found -> - incr Goblintutil.vars; + incr SolverStats.vars; decr last_key; HM.add keys c !last_key; (!last_key, false) @@ -391,7 +393,7 @@ module Make0 = and solve x = if not (P.has_item stable x) then begin - incr Goblintutil.evals; + incr SolverStats.evals; let _ = P.insert stable x in let old = X.get_value x in @@ -482,7 +484,7 @@ module PrintInfluence = let r = S1.solve x y in let f k _ = let q = if HM.mem S1.wpoint k then " shape=box style=rounded" else "" in - let s = Pretty.sprint ~width:80 (S.Var.pretty_trace () k) ^ " " ^ string_of_int (try HM.find S1.X.keys k with Not_found -> 0) in + let s = GobPretty.sprintf "%a %d" S.Var.pretty_trace k (try HM.find S1.X.keys k with Not_found -> 0) in ignore (Pretty.fprintf ch "%d [label=\"%s\"%s];\n" (S.Var.hash k) (XmlUtil.escape s) q); let f y = if try HM.find S1.X.keys k > HM.find S1.X.keys y with Not_found -> false then diff --git a/src/solvers/sLRphased.ml b/src/solvers/sLRphased.ml index 760b4614d8..c120a7bc6c 100644 --- a/src/solvers/sLRphased.ml +++ b/src/solvers/sLRphased.ml @@ -1,4 +1,6 @@ -open Prelude +(** Two-phased terminating SLR3 solver ([slr3tp]). *) + +open Batteries open Analyses open Constraints open Messages @@ -71,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 ( @@ -107,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 @@ -161,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 0108af84a1..eb11447d11 100644 --- a/src/solvers/sLRterm.ml +++ b/src/solvers/sLRterm.ml @@ -1,4 +1,7 @@ -open Prelude +(** Terminating SLR3 solver ([slr3t]). + Simpler version of {!SLRphased} without phases. *) + +open Batteries open Analyses open Constraints open Messages @@ -61,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 () @@ -119,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 ( @@ -153,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/selector.ml b/src/solvers/selector.ml index 5869bd8f92..664cbe0513 100644 --- a/src/solvers/selector.ml +++ b/src/solvers/selector.ml @@ -1,4 +1,6 @@ -open Prelude +(** Solver, which delegates at runtime to the configured solver. *) + +open Batteries open Analyses open GobConfig diff --git a/src/solvers/solverBox.ml b/src/solvers/solverBox.ml index a261570e74..6472dd7870 100644 --- a/src/solvers/solverBox.ml +++ b/src/solvers/solverBox.ml @@ -1,3 +1,5 @@ +(** Box operator for warrowing solvers. *) + module type S = functor (D: Lattice.S) -> sig diff --git a/src/solvers/solverStats.ml b/src/solvers/solverStats.ml new file mode 100644 index 0000000000..f8429f0868 --- /dev/null +++ b/src/solvers/solverStats.ml @@ -0,0 +1,13 @@ +(** Statistics for solvers. *) + +let vars = ref 0 +let evals = ref 0 +let narrow_reuses = ref 0 + +let print () = + ignore (GoblintCil.Pretty.printf "vars = %d evals = %d narrow_reuses = %d\n" !vars !evals !narrow_reuses) + +let reset () = + vars := 0; + evals := 0; + narrow_reuses := 0 diff --git a/src/solvers/td3.ml b/src/solvers/td3.ml index f32115c926..07edc632c7 100644 --- a/src/solvers/td3.ml +++ b/src/solvers/td3.ml @@ -1,3 +1,8 @@ +(** Incremental/interactive terminating top-down solver, which supports space-efficiency and caching ([td3]). + + @see Seidl, H., Vogler, R. Three improvements to the top-down solver. + @see Interactive Abstract Interpretation: Reanalyzing Whole Programs for Cheap. *) + (** Incremental terminating top down solver that optionally only keeps values at widening points and restores other values afterwards. *) (* Incremental: see paper 'Incremental Abstract Interpretation' https://link.springer.com/chapter/10.1007/978-3-030-41103-9_5 *) (* TD3: see paper 'Three Improvements to the Top-Down Solver' https://dl.acm.org/doi/10.1145/3236950.3236967 @@ -9,7 +14,7 @@ * For simpler (but unmaintained) versions without the incremental parts see the paper or topDown{,_space_cache_term}.ml. *) -open Prelude +open Batteries open Analyses open Messages @@ -43,6 +48,7 @@ module Base = open SolverBox.Warrow (S.Dom) include Generic.SolverStats (S) (HM) module VS = Set.Make (S.Var) + let exists_key f hm = HM.fold (fun k _ a -> a || f k) hm false type solver_data = { st: (S.Var.t * S.Dom.t) list; (* needed to destabilize start functions if their start state changed because of some changed global initializer *) @@ -74,11 +80,23 @@ module Base = dep = HM.create 10; } - let print_data data str = + let print_data data = + Printf.printf "|rho|=%d\n" (HM.length data.rho); + Printf.printf "|stable|=%d\n" (HM.length data.stable); + Printf.printf "|infl|=%d\n" (HM.length data.infl); + Printf.printf "|wpoint|=%d\n" (HM.length data.wpoint); + Printf.printf "|sides|=%d\n" (HM.length data.sides); + Printf.printf "|side_dep|=%d\n" (HM.length data.side_dep); + Printf.printf "|side_infl|=%d\n" (HM.length data.side_infl); + Printf.printf "|var_messages|=%d\n" (HM.length data.var_messages); + Printf.printf "|rho_write|=%d\n" (HM.length data.rho_write); + Printf.printf "|dep|=%d\n" (HM.length data.dep); + Hooks.print_data () + + let print_data_verbose data str = if GobConfig.get_bool "dbg.verbose" then ( - Printf.printf "%s:\n|rho|=%d\n|stable|=%d\n|infl|=%d\n|wpoint|=%d\n|side_dep|=%d\n|side_infl|=%d\n|var_messages|=%d\n|rho_write|=%d\n|dep|=%d\n" - str (HM.length data.rho) (HM.length data.stable) (HM.length data.infl) (HM.length data.wpoint) (HM.length data.side_dep) (HM.length data.side_infl) (HM.length data.var_messages) (HM.length data.rho_write) (HM.length data.dep); - Hooks.print_data () + Printf.printf "%s:\n" str; + print_data data ) let verify_data data = @@ -108,7 +126,7 @@ module Base = dep = HM.copy data.dep; } - (* This hack is for fixing hashconsing. + (* The following hack is for fixing hashconsing. If hashcons is enabled now, then it also was for the loaded values (otherwise it would crash). If it is off, we don't need to do anything. HashconsLifter uses BatHashcons.hashcons on Lattice operations like join, so we call join (with bot) to make sure that the old values will populate the empty hashcons table via side-effects and at the same time get new tags that are conform with its state. The tags are used for `equals` and `compare` to avoid structural comparisons. TODO could this be replaced by `==` (if values are shared by hashcons they should be physically equal)? @@ -172,16 +190,7 @@ module Base = ) data.dep; {st; infl; sides; rho; wpoint; stable; side_dep; side_infl; var_messages; rho_write; dep} - let exists_key f hm = HM.fold (fun k _ a -> a || f k) hm false - - module P = - struct - type t = S.Var.t * S.Var.t [@@deriving eq, hash] - end - - module HPM = Hashtbl.Make (P) - - type phase = Widen | Narrow [@@deriving show] + type phase = Widen | Narrow [@@deriving show] (* used in inner solve *) module CurrentVarS = Constraints.CurrentVarEqConstrSys (S) module S = CurrentVarS.S @@ -197,8 +206,10 @@ module Base = HM.clear data.stable; HM.clear data.infl ); - if not reuse_wpoint then + if not reuse_wpoint then ( HM.clear data.wpoint; + HM.clear data.sides + ); data | None -> create_empty_data () @@ -241,14 +252,13 @@ module Base = let dep = data.dep in let () = print_solver_stats := fun () -> - Printf.printf "|rho|=%d\n|called|=%d\n|stable|=%d\n|infl|=%d\n|wpoint|=%d\n|side_dep|=%d\n|side_infl|=%d\n|var_messages|=%d\n|rho_write|=%d\n|dep|=%d\n" - (HM.length rho) (HM.length called) (HM.length stable) (HM.length infl) (HM.length wpoint) (HM.length side_dep) (HM.length side_infl) (HM.length var_messages) (HM.length rho_write) (HM.length dep); - Hooks.print_data (); + print_data data; + Printf.printf "|called|=%d\n" (HM.length called); print_context_stats rho in if GobConfig.get_bool "incremental.load" then ( - print_data data "Loaded data for incremental analysis"; + print_data_verbose data "Loaded data for incremental analysis"; verify_data data ); @@ -264,6 +274,7 @@ module Base = let destabilize_ref: (S.v -> unit) ref = ref (fun _ -> failwith "no destabilize yet") in let destabilize x = !destabilize_ref x in (* must be eta-expanded to use changed destabilize_ref *) + (* Same as destabilize, but returns true if it destabilized a called var, or a var in vs which was stable. *) let rec destabilize_vs x = (* TODO remove? Only used for side_widen cycle. *) if tracing then trace "sol2" "destabilize_vs %a\n" S.Var.pretty_trace x; let w = HM.find_default infl x VS.empty in @@ -272,10 +283,10 @@ module Base = let was_stable = HM.mem stable y in HM.remove stable y; HM.remove superstable y; - HM.mem called y || destabilize_vs y || b || was_stable && List.mem y vs + HM.mem called y || destabilize_vs y || b || was_stable && List.mem_cmp S.Var.compare y vs ) w false and solve ?reuse_eq x phase = - if tracing then trace "sol2" "solve %a, phase: %s, called: %b, stable: %b\n" S.Var.pretty_trace x (show_phase phase) (HM.mem called x) (HM.mem stable x); + if tracing then trace "sol2" "solve %a, phase: %s, called: %b, stable: %b, wpoint: %b\n" S.Var.pretty_trace x (show_phase phase) (HM.mem called x) (HM.mem stable x) (HM.mem wpoint x); init x; assert (Hooks.system x <> None); if not (HM.mem called x || HM.mem stable x) then ( @@ -287,47 +298,51 @@ module Base = This doesn't matter during normal solving (?), because old would be bot. This matters during incremental loading, when wpoints have been removed (or not marshaled) and are redetected. Then the previous local wpoint value is discarded automagically and not joined/widened, providing limited restarting of local wpoints. (See eval for more complete restarting.) *) - let wp = HM.mem wpoint x in - let l = HM.create 10 in - let tmp = + let wp = HM.mem wpoint x in (* if x becomes a wpoint during eq, checking this will delay widening until next solve *) + let l = HM.create 10 in (* local cache *) + let eqd = (* d from equation/rhs *) match reuse_eq with | Some d when narrow_reuse -> (* Do not reset deps for reuse of eq *) if tracing then trace "sol2" "eq reused %a\n" S.Var.pretty_trace x; - incr Goblintutil.narrow_reuses; + incr SolverStats.narrow_reuses; d | _ -> (* The RHS is re-evaluated, all deps are re-trigerred *) HM.replace dep x VS.empty; eq x (eval l x) (side ~x) in - let new_eq = tmp in - (* let tmp = if GobConfig.get_bool "ana.opt.hashcons" then S.Dom.join (S.Dom.bot ()) tmp else tmp in (* Call hashcons via dummy join so that the tag of the rhs value is up to date. Otherwise we might get the same value as old, but still with a different tag (because no lattice operation was called after a change), and since Printable.HConsed.equal just looks at the tag, we would unnecessarily destabilize below. Seems like this does not happen. *) *) - if tracing then trace "sol" "Var: %a\n" S.Var.pretty_trace x ; - if tracing then trace "sol" "Contrib:%a\n" S.Dom.pretty tmp; HM.remove called x; - let old = HM.find rho x in (* find old value after eq since wpoint restarting in eq/eval might have changed it meanwhile *) - let tmp = - if not wp then tmp + let old = HM.find rho x in (* d from older solve *) (* find old value after eq since wpoint restarting in eq/eval might have changed it meanwhile *) + let wpd = (* d after widen/narrow (if wp) *) + if not wp then eqd else if term then - match phase with Widen -> S.Dom.widen old (S.Dom.join old tmp) | Narrow -> S.Dom.narrow old tmp + match phase with + | Widen -> S.Dom.widen old (S.Dom.join old eqd) + | Narrow when GobConfig.get_bool "exp.no-narrow" -> old (* no narrow *) + | Narrow -> + (* assert S.Dom.(leq eqd old || not (leq old eqd)); (* https://github.com/goblint/analyzer/pull/490#discussion_r875554284 *) *) + S.Dom.narrow old eqd else - box old tmp + box old eqd in - if tracing then trace "sol" "Old value:%a\n" S.Dom.pretty old; - if tracing then trace "sol" "New Value:%a\n" S.Dom.pretty tmp; - 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 tmp) ()) then ( + 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"; - update_var_event x old tmp; - HM.replace rho x tmp; + (* 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; (solve[@tailcall]) x phase ) else ( (* TODO: why non-equal and non-stable checks in switched order compared to TD3 paper? *) - if not (HM.mem stable x) then ( + if not (HM.mem stable x) then ( (* value unchanged, but not stable, i.e. destabilized itself during rhs *) if tracing then trace "sol2" "solve still unstable %a\n" S.Var.pretty_trace x; (solve[@tailcall]) x Widen ) else ( @@ -337,7 +352,7 @@ module Base = HM.remove stable x; HM.remove superstable x; Hooks.stable_remove x; - (solve[@tailcall]) ~reuse_eq:new_eq x Narrow + (solve[@tailcall]) ~reuse_eq:eqd x Narrow ) else if remove_wpoint && not space && (not term || phase = Narrow) then ( (* this makes e.g. nested loops precise, ex. tests/regression/34-localization/01-nested.c - if we do not remove wpoint, the inner loop head will stay a wpoint and widen the outer loop variable. *) if tracing then trace "sol2" "solve removing wpoint %a (%b)\n" S.Var.pretty_trace x (HM.mem wpoint x); HM.remove wpoint x @@ -359,10 +374,10 @@ module Base = if cache && HM.mem l y then HM.find l y else ( HM.replace called y (); - let tmp = eq y (eval l x) (side ~x) in + let eqd = eq y (eval l x) (side ~x) in HM.remove called y; if HM.mem wpoint y then (HM.remove l y; solve y Widen; HM.find rho y) - else (if cache then HM.replace l y tmp; tmp) + else (if cache then HM.replace l y eqd; eqd) ) and eval l x y = if tracing then trace "sol2" "eval %a ## %a\n" S.Var.pretty_trace x S.Var.pretty_trace y; @@ -379,6 +394,7 @@ module Base = HM.replace restarted_wpoint y (); ) ); + if tracing then trace "sol2" "eval adding wpoint %a from %a\n" S.Var.pretty_trace y S.Var.pretty_trace x; HM.replace wpoint y (); ); let tmp = simple_solve l x y in @@ -415,6 +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: %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 @@ -426,7 +444,12 @@ module Base = HM.replace rho y tmp; if side_widen <> "cycle" then destabilize y; (* make y a widening point if ... This will only matter for the next side _ y. *) - let wpoint_if e = if e then HM.replace wpoint y () in + let wpoint_if e = + if e then ( + if tracing then trace "sol2" "side adding wpoint %a from %a\n" S.Var.pretty_trace y (Pretty.docOpt (S.Var.pretty_trace ())) x; + HM.replace wpoint y () + ) + in match side_widen with | "always" -> (* Any side-effect after the first one will be widened which will unnecessarily lose precision. *) wpoint_if true @@ -480,7 +503,7 @@ module Base = if tracing then trace "sol2" "stable remove %a\n" S.Var.pretty_trace y; HM.remove stable y; HM.remove superstable y; - Hooks.stable_remove x; + Hooks.stable_remove y; if not (HM.mem called y) then destabilize_normal y ) w in @@ -547,7 +570,7 @@ module Base = if tracing then trace "sol2" "stable remove %a\n" S.Var.pretty_trace y; HM.remove stable y; HM.remove superstable y; - Hooks.stable_remove x; + Hooks.stable_remove y; destabilize_with_side ~side_fuel y ) w_side_dep; ); @@ -558,7 +581,7 @@ module Base = if tracing then trace "sol2" "stable remove %a\n" S.Var.pretty_trace y; HM.remove stable y; HM.remove superstable y; - Hooks.stable_remove x; + Hooks.stable_remove y; destabilize_with_side ~side_fuel y ) w_infl; @@ -576,7 +599,7 @@ module Base = if tracing then trace "sol2" "stable remove %a\n" S.Var.pretty_trace y; HM.remove stable y; HM.remove superstable y; - Hooks.stable_remove x; + Hooks.stable_remove y; destabilize_with_side ~side_fuel:side_fuel' y ) w_side_infl ) @@ -649,7 +672,7 @@ module Base = if tracing then trace "sol2" "stable remove %a\n" S.Var.pretty_trace y; HM.remove stable y; HM.remove superstable y; - Hooks.stable_remove x; + Hooks.stable_remove y; destabilize_normal y ) w ) @@ -712,10 +735,21 @@ module Base = (* delete from incremental postsolving/warning structures to remove spurious warnings *) delete_marked superstable; delete_marked var_messages; + + if restart_write_only then ( + (* restart write-only *) + (* before delete_marked because we also want to restart write-only side effects from deleted nodes *) + HM.iter (fun x w -> + HM.iter (fun y d -> + ignore (Pretty.printf "Restarting write-only to bot %a\n" S.Var.pretty_trace y); + HM.replace rho y (S.Dom.bot ()); + ) w + ) rho_write + ); delete_marked rho_write; HM.iter (fun x w -> delete_marked w) rho_write; - print_data data "Data after clean-up"; + print_data_verbose data "Data after clean-up"; (* TODO: reluctant doesn't call destabilize on removed functions or old copies of modified functions (e.g. after removing write), so those globals don't get restarted *) @@ -811,13 +845,13 @@ module Base = HM.filteri_inplace (fun x _ -> HM.mem visited x) rho in Timing.wrap "restore" restore (); - if GobConfig.get_bool "dbg.verbose" then ignore @@ Pretty.printf "Solved %d vars. Total of %d vars after restore.\n" !Goblintutil.vars (HM.length rho); + if GobConfig.get_bool "dbg.verbose" then ignore @@ Pretty.printf "Solved %d vars. Total of %d vars after restore.\n" !SolverStats.vars (HM.length rho); let avg xs = if List.is_empty !cache_sizes then 0.0 else float_of_int (BatList.sum xs) /. float_of_int (List.length xs) in - if tracing then trace "cache" "#caches: %d, max: %d, avg: %.2f\n" (List.length !cache_sizes) (List.max !cache_sizes) (avg !cache_sizes); + if tracing && cache then trace "cache" "#caches: %d, max: %d, avg: %.2f\n" (List.length !cache_sizes) (List.max !cache_sizes) (avg !cache_sizes); ); stop_event (); - print_data data "Data after solve completed"; + print_data_verbose data "Data after solve completed"; if GobConfig.get_bool "dbg.print_wpoints" then ( Printf.printf "\nWidening points:\n"; @@ -905,16 +939,6 @@ module Base = HM.create 0 (* doesn't matter, not used *) in - if restart_write_only then ( - (* restart write-only *) - HM.iter (fun x w -> - HM.iter (fun y d -> - ignore (Pretty.printf "Restarting write-only to bot %a\n" S.Var.pretty_trace y); - HM.replace rho y (S.Dom.bot ()); - ) w - ) rho_write - ); - if incr_verify then ( HM.filteri_inplace (fun x _ -> HM.mem reachable_and_superstable x) var_messages; HM.filteri_inplace (fun x _ -> HM.mem reachable_and_superstable x) rho_write @@ -1015,7 +1039,7 @@ module Base = let module Post = PostSolver.MakeIncrList (MakeIncrListArg) in Post.post st (stable_reluctant_vs @ vs) rho; - print_data data "Data after postsolve"; + print_data_verbose data "Data after postsolve"; verify_data data; (rho, {st; infl; sides; rho; wpoint; stable; side_dep; side_infl; var_messages; rho_write; dep}) diff --git a/src/solvers/topDown.ml b/src/solvers/topDown.ml index 1497e6be36..c6b20d28db 100644 --- a/src/solvers/topDown.ml +++ b/src/solvers/topDown.ml @@ -1,6 +1,7 @@ -(** Top down solver using box/warrow. This is superseded by td3 but kept as a simple version without term & space (& incremental). *) +(** Warrowing top-down solver ([topdown]). + Simpler version of {!Td3} without terminating, space-efficiency and incremental. *) -open Prelude +open Batteries open Analyses open Constraints open Messages diff --git a/src/solvers/topDown_deprecated.ml b/src/solvers/topDown_deprecated.ml index 9948dc5954..1f51244458 100644 --- a/src/solvers/topDown_deprecated.ml +++ b/src/solvers/topDown_deprecated.ml @@ -1,10 +1,10 @@ -open Prelude +(** Deprecated top-down solver ([topdown_deprecated]). *) + +open Batteries open Analyses open Constraints open Messages -module GU = Goblintutil - exception SolverCannotDoGlobals diff --git a/src/solvers/topDown_space_cache_term.ml b/src/solvers/topDown_space_cache_term.ml index 6c0ed9f36b..a78d90559d 100644 --- a/src/solvers/topDown_space_cache_term.ml +++ b/src/solvers/topDown_space_cache_term.ml @@ -1,7 +1,7 @@ -(** Terminating top down solver that only keeps values at widening points and restores other values afterwards. *) -(* This is superseded by td3 but kept as a simpler version without the incremental parts. *) +(** Terminating top-down solver, which supports space-efficiency and caching ([topdown_space_cache_term]). + Simpler version of {!Td3} without incremental. *) -open Prelude +open Batteries open Analyses open Constraints open Messages @@ -182,7 +182,7 @@ module WP = List.iter get vs in Timing.wrap "restore" restore (); - if (GobConfig.get_bool "dbg.verbose") then ignore @@ Pretty.printf "Solved %d vars. Total of %d vars after restore.\n" !Goblintutil.vars (HM.length rho); + if (GobConfig.get_bool "dbg.verbose") then ignore @@ Pretty.printf "Solved %d vars. Total of %d vars after restore.\n" !SolverStats.vars (HM.length rho); ); let avg xs = float_of_int (BatList.sum xs) /. float_of_int (List.length xs) in if tracing then trace "cache" "#caches: %d, max: %d, avg: %.2f\n" (List.length !cache_sizes) (List.max !cache_sizes) (avg !cache_sizes); diff --git a/src/solvers/topDown_term.ml b/src/solvers/topDown_term.ml index 730a80ebc8..ec07995586 100644 --- a/src/solvers/topDown_term.ml +++ b/src/solvers/topDown_term.ml @@ -1,6 +1,7 @@ -(** Top down solver. *) +(** Terminating top-down solver ([topdown_term]). + Simpler version of {!Td3} without space-efficiency and incremental. *) -open Prelude +open Batteries open Analyses open Constraints open Messages diff --git a/src/solvers/worklist.ml b/src/solvers/worklist.ml index d6c1f12f9e..b525764c74 100644 --- a/src/solvers/worklist.ml +++ b/src/solvers/worklist.ml @@ -1,4 +1,6 @@ -open Prelude +(** Worklist solver ([WL]). *) + +open Batteries open Analyses open Constraints diff --git a/src/spec/specCore.ml b/src/spec/specCore.ml index 5710ab1c88..9d0ce35624 100644 --- a/src/spec/specCore.ml +++ b/src/spec/specCore.ml @@ -1,6 +1,6 @@ (* types used by specParser and functions for handling the constructed types *) -open Prelude +open Batteries exception Endl exception Eof diff --git a/src/spec/specUtil.ml b/src/spec/specUtil.ml index 925cdbdfcd..55e0b51135 100644 --- a/src/spec/specUtil.ml +++ b/src/spec/specUtil.ml @@ -1,6 +1,6 @@ (* functions for driving specParser *) -open Prelude +open Batteries (* config *) let save_dot = true diff --git a/src/transform/deadCode.ml b/src/transform/deadCode.ml new file mode 100644 index 0000000000..1b9db8d06a --- /dev/null +++ b/src/transform/deadCode.ml @@ -0,0 +1,190 @@ +(** Dead code elimination transformation ([remove_dead_code]). *) + +open Batteries +open GoblintCil +open GobConfig +open MyCFG + +(* create a new empty block; fields are mutable hence the function *) +let empty_block () = { battrs = [] ; bstmts = [] } + +(** Filter statements out of a block (recursively). CFG fields (prev/next, + Loop continue/break) are no longer valid after calling. Returns true if + anything is left in block, false if the block is now empty. Invariants: + - f (goto label) ==> f (labelled stmt), i.e. if a goto statement is not + filtered out, the target may not be filtered out either. + - block may not contain switch statements. *) +let filter_map_block ?(unchecked_condition = Fun.const (GoblintCil.integer 1)) f block = + (* blocks and statements: modify in place, then return true if should be kept *) + let rec impl_block block = + block.bstmts <- List.filter impl_stmt block.bstmts; + block.bstmts <> [] + and impl_stmt stmt = + (* whether the current statement should be kept, for 'if' and 'loop' + statements this corresponds to keeping the condition node in the CFG *) + let keep_stmt = f stmt in + let skind'_opt = match stmt.skind with + | If (cond, b_true, b_false, loc, exp_loc) -> + (* filter statements (recursively) from the true and false blocks *) + let keep_b_true, keep_b_false = impl_block b_true, impl_block b_false in + let keep_both, keep_one = keep_b_true && keep_b_false, keep_b_true || keep_b_false in + + (* - If both the true and false blocks are to be kept, we want to keep the 'if' statement. + In case the condition node isn't being kept (keep_stmt is false), we replace the + condition with an unchecked placeholder (we can't remove it entirely, since we need to + preserve control flow from the end of the true block to after the false block). + - If at least one of the blocks is to be kept, we also keep the 'if' statement, + replacing the other block with an empty block. *) + if keep_both || (keep_stmt && keep_one) then + Option.some @@ If ( + (if keep_stmt then cond else unchecked_condition ()), + (if keep_b_true then b_true else empty_block ()), + (if keep_b_false then b_false else empty_block ()), + loc, exp_loc) + (* Only one block is being kept, replace the entire 'if' statement with it. *) + else if keep_one then + Option.some @@ Block (if keep_b_true then b_true else b_false) + (* Neither block is being kept. Remove entire 'if' (including condition) in this case. *) + else + None + + | Loop (body, loc, exp_loc, _, _) -> + (* filter statements (recursively) from the body of the loop *) + let keep_body = impl_block body in + + (* If the condition node is to be kept: keep the entire loop. Unlike for an 'if' statement, + keep the loop with an empty body if just the body is to be removed, since an empty + infinite loop is different from having nothing at all. *) + if keep_stmt then + Option.some @@ Loop ( + (if keep_body then body else empty_block ()), + loc, exp_loc, None, None) + (* If only the body is to be kept, remove the condition. Thus, the condition must really be + dead for control flow to not change, as execution will no longer loop. *) + else if keep_body then + Option.some @@ Block body + else + None + + | Block b as skind -> + (* Filter the statements inside the block, keep + the block if it still contains any statements. *) + let keep_block = impl_block b in + if keep_stmt || keep_block then Some skind else None + + | Instr _ | Return _ | Goto _ | ComputedGoto _ | Break _ | Continue _ as skind -> + (* no further statements are contained recursively here, so nothing left to do *) + if keep_stmt then Some skind else None + + | Switch _ -> + (* Handling switch statements correctly would be very difficult; consider that + the switch labels may be located within arbitrarily nested statements within + the switch statement's block. *) + failwith "filter_map_block: statements must be removed" + in + + Stdlib.Option.iter (fun skind' -> stmt.skind <- skind') skind'_opt; + Option.is_some skind'_opt + in + impl_block block + +(** Is it possible for this statement to begin executing normally, but not finish? *) +let may_stop_execution stmt = + match stmt.skind with + | Instr is -> List.exists (function Call _ | Asm _ -> true | _ -> false) is + | _ -> false + +(** Perform a depth first search over the CFG. Record the IDs of live statements; + for each traversed edge, record the skipped statements along the edge as live, + if the nodes on both ends of the edge are live. Record live statements in the nodes + themselves as well. *) +let find_live_statements + (node_live : node -> bool) + (skipped_statements : node -> edges -> node -> stmt list) + (cfg : cfg) (start : node) + = + let seen_nodes = NodeH.create 13 in + let module IS = BatSet.Int in + let node_id_set = function Statement stmt -> IS.singleton stmt.sid | _ -> IS.empty in + + let rec impl (n : node) (ns : node list) (live_stmts : IS.t) = + NodeH.replace seen_nodes n (); + let n_outbound = cfg n in + + (* If the 'from' node is dead, the statements along all outbound edges are definitely not live. + If just the 'to' node is dead, some traversed statement along the edge stops execution; + to be safe, mark all statements up to and including the last such statement as live. + For example, if we have, along an edge: f() -> x += 1 -> g() -> z = 0, then f() or g() are + not pure control-flow, so we must keep everything up to g(), but can drop z = 0. + If both nodes are live, we keep everything along the edge. *) + let live_stmts' = + if node_live n then + n_outbound + |> List.map (fun (edges, n') -> + let skipped = skipped_statements n edges n' in + (if node_live n' then skipped + (* drop after the last non-control flow statement *) + else List.rev skipped |> BatList.drop_while (not % may_stop_execution)) + |> List.map (fun stmt -> stmt.sid) + |> IS.of_list) + |> List.fold_left IS.union live_stmts + |> IS.union (node_id_set n) + else + live_stmts + in + + match (n_outbound |> List.map snd |> List.filter (not % NodeH.mem seen_nodes)) @ ns with + | [] -> live_stmts' + | n' :: ns' -> impl n' ns' live_stmts' + in + + impl start [] (if node_live start then node_id_set start else IS.empty) + +module RemoveDeadCode : Transform.S = struct + let transform (q : Transform.queries) (file : file) : unit = + + (* whether a global function (might) still be live, and should therefore be kept *) + let fundec_live : fundec -> bool = not % q.must_be_uncalled in + + (* Step 1: Remove statements found to be dead. *) + Cil.iterGlobals file + (function + | GFun (fd, _) -> + (* called functions: filter statement-by-statement *) + if fundec_live fd then + let live_statements = + find_live_statements + (not % q.must_be_dead) + q.skipped_statements + q.cfg_forward + (FunctionEntry fd) + in + filter_map_block + ~unchecked_condition:GoblintCil.(Fun.const @@ mkString "UNCHECKED CONDITION") + (fun stmt -> BatSet.Int.mem stmt.sid live_statements) + fd.sbody + |> ignore (* ignore empty block: might be empty, but uncalled *) + (* uncalled functions: as an optimaztion, clear the body immediately; + if they are also unreferenced, they will be removed in the next step *) + else + fd.sbody <- empty_block () + | _ -> ()); + + (* Step 2: Remove globals that are (transitively, syntactically) unreferenced by + the main function(s). Dead functions and globals are removed, since there is no + chain of syntactic references to them from the main function(s). *) + let open GoblintCil.RmUnused in + GobRef.wrap keepUnused false @@ fun () -> + removeUnused + ~isRoot:(function + | GFun (fd, _) -> List.mem fd.svar.vname (get_string_list "mainfun") + | _ -> false) + file + + let name = "remove_dead_code" + + let requires_file_output = true + +end + +let _ = Transform.register (module RemoveDeadCode) diff --git a/src/transform/evalAssert.ml b/src/transform/evalAssert.ml index e02bccb0e9..91bdb82ce1 100644 --- a/src/transform/evalAssert.ml +++ b/src/transform/evalAssert.ml @@ -1,4 +1,5 @@ -open Prelude +(** Transformation for instrumenting the program with computed invariants as assertions ([assert]). *) + open GoblintCil open Formatcil @@ -51,8 +52,8 @@ module EvalAssert = struct let make_assert ~node loc lval = let lvals = match lval with - | None -> CilLval.Set.top () - | Some lval -> CilLval.(Set.singleton lval) + | None -> Lval.Set.top () + | Some lval -> Lval.(Set.singleton lval) in let context = {Invariant.default_context with lvals} in match (ask ~node loc).f (Queries.Invariant context) with @@ -139,15 +140,16 @@ module EvalAssert = struct ChangeDoChildrenPost (s, instrument_statement) end - let transform (ask: ?node:Node.t -> Cil.location -> Queries.ask) file = begin - visitCilFile (new visitor ask) file; + let transform (q : Transform.queries) file = + visitCilFile (new visitor q.ask) file; (* Add function declarations before function definitions. This way, asserts may reference functions defined later. *) - Cilfacade.add_function_declarations file; + Cilfacade.add_function_declarations file + + let name = "assert" + + let requires_file_output = true - let assert_filename = GobConfig.get_string "trans.output" in - let oc = Stdlib.open_out assert_filename in - dumpFile defaultCilPrinter oc assert_filename file; end end -let _ = Transform.register "assert" (module EvalAssert) +let _ = Transform.register (module EvalAssert) diff --git a/src/transform/expressionEvaluation.ml b/src/transform/expressionEvaluation.ml index 5013f662b6..815e5742f6 100644 --- a/src/transform/expressionEvaluation.ml +++ b/src/transform/expressionEvaluation.ml @@ -1,3 +1,6 @@ +(** Transformation for evaluating expressions on the analysis results ([expeval]). + {e Hack for Gobview}. *) + open Batteries open GoblintCil open Syntacticsearch @@ -19,7 +22,7 @@ type query = } [@@deriving yojson] -(* These are meant to be used by Gobview *) +(* These are meant to be used by GobView *) let gv_query = ref None let gv_results = ref [] @@ -32,7 +35,7 @@ struct let (~!) value_option = match value_option with | Some value -> value - | None -> raise Exit + | None -> raise Stdlib.Exit let is_debug () = GobConfig.get_bool "dbg.verbose" @@ -162,7 +165,7 @@ struct let file_compare (_, l, _, _) (_, l', _, _) = let open Cil in compare l.file l'.file let byte_compare (_, l, _, _) (_, l', _, _) = let open Cil in compare l.byte l'.byte - let transform (ask : ?node:Node.t -> Cil.location -> Queries.ask) (file : Cil.file) = + let transform (q : Transform.queries) (file : Cil.file) = let query = match !gv_query with | Some q -> Ok q | _ -> query_from_file (GobConfig.get_string transformation_query_file_name_identifier) @@ -170,7 +173,7 @@ struct match query with | Ok query -> (* Create an evaluator *) - let evaluator = new evaluator file ask in + let evaluator = new evaluator file q.ask in (* Syntactic query *) let query_syntactic : CodeQuery.query = { @@ -206,7 +209,11 @@ struct List.iter print results | Error e -> prerr_endline e + let name = transformation_identifier + + let requires_file_output = false + end let _ = - Transform.register transformation_identifier (module ExpEval) + Transform.register (module ExpEval) diff --git a/src/transform/transform.ml b/src/transform/transform.ml index e366b5d2a6..fe3abb6f08 100644 --- a/src/transform/transform.ml +++ b/src/transform/transform.ml @@ -1,16 +1,48 @@ -open Prelude +(** Signatures and registry for transformations. *) + open GoblintCil +module M = Messages + +(** data about analysis available to queries *) +type queries = { + ask : ?node:Node.t -> Cil.location -> Queries.ask ; + must_be_dead : Node.t -> bool ; + must_be_uncalled : fundec -> bool ; + + cfg_forward : MyCFG.cfg ; + cfg_backward : MyCFG.cfg ; + skipped_statements : Node.t -> MyCFG.edges -> Node.t -> stmt list ; +} module type S = sig - val transform : (?node:Node.t -> Cil.location -> Queries.ask) -> file -> unit (* modifications are done in-place by CIL :( *) + val transform : queries -> file -> unit (* modifications are done in-place by CIL :( *) + val name : string + val requires_file_output : bool end let h = Hashtbl.create 13 -let register name (module T : S) = Hashtbl.add h name (module T : S) -let run name = - let module T = (val try Hashtbl.find h name with Not_found -> failwith @@ "Transformation "^name^" does not exist!") in - if GobConfig.get_bool "dbg.verbose" then print_endline @@ "Starting transformation " ^ name; - T.transform +let register (module T : S) = Hashtbl.replace h T.name (module T : S) + +let run_transformations ?(file_output = true) file names ask = + let active_transformations = + List.filter_map + (fun name -> + match BatHashtbl.find_option h name with + | Some t -> Some (name, t) + | None -> failwith "Transformation %s does not exist!") + names + in + + List.iter (fun (name, (module T : S)) -> T.transform ask file) active_transformations; + + if file_output && List.exists (fun (_, (module T : S)) -> T.requires_file_output) active_transformations then + let filename = GobConfig.get_string "trans.output" in + let oc = Stdlib.open_out filename in + GobRef.wrap GoblintCil.lineDirectiveStyle None @@ fun () -> + dumpFile defaultCilPrinter oc filename file; + Stdlib.close_out oc + +let run file name = run_transformations ~file_output:false file [name] module PartialEval = struct let loc = ref locUnknown (* when we visit an expression, we need the current location -> store at stmts *) @@ -33,7 +65,11 @@ module PartialEval = struct | Const _ -> SkipChildren | _ -> ChangeDoChildrenPost (e, eval) end - let transform ask file = - visitCilFile (new visitor ask) file + let transform q file = + visitCilFile (new visitor q.ask) file + + let name = "partial" + + let requires_file_output = false end -let _ = register "partial" (module PartialEval) +let _ = register (module PartialEval) 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/apron/apronPrecCompareUtil.apron.ml b/src/util/apron/apronPrecCompareUtil.apron.ml index 046b9126ff..b276e0953b 100644 --- a/src/util/apron/apronPrecCompareUtil.apron.ml +++ b/src/util/apron/apronPrecCompareUtil.apron.ml @@ -1,3 +1,5 @@ +(** {!ApronDomain} precision comparison. *) + open PrecCompareUtil open ApronDomain diff --git a/src/util/apron/relationPrecCompareUtil.apron.ml b/src/util/apron/relationPrecCompareUtil.apron.ml index 7e4c60f4d3..15b59c2e22 100644 --- a/src/util/apron/relationPrecCompareUtil.apron.ml +++ b/src/util/apron/relationPrecCompareUtil.apron.ml @@ -1,3 +1,5 @@ +(** {!RelationPriv} precison comparison. *) + open PrecCompareUtil module MyNode = diff --git a/src/util/cilCfg.ml b/src/util/cilCfg.ml index 84b4797c53..2c8ec646c3 100644 --- a/src/util/cilCfg.ml +++ b/src/util/cilCfg.ml @@ -1,3 +1,5 @@ +(** Creation of CIL CFGs. *) + open GobConfig open GoblintCil @@ -30,9 +32,9 @@ class countLoopsVisitor(count) = object | Loop _ -> count := !count + 1; DoChildren | _ -> DoChildren -end +end -let loopCount file = +let loopCount file = let count = ref 0 in let visitor = new countLoopsVisitor(count) in ignore (visitCilFileSameGlobals visitor file); diff --git a/src/util/cilMaps.ml b/src/util/cilMaps.ml index 9b3b91f5c6..b186370d07 100644 --- a/src/util/cilMaps.ml +++ b/src/util/cilMaps.ml @@ -1,5 +1,16 @@ +(** Special maps used for incremental comparison. *) + open GoblintCil +module FundecForMap = struct + type t = Cil.fundec + + (*x.svar.uid cannot be used, as they may overlap between old and now AST*) + let compare x y = String.compare x.svar.vname y.svar.vname +end + +module FundecMap = Map.Make(FundecForMap) + module VarinfoOrdered = struct type t = varinfo diff --git a/src/util/compilationDatabase.ml b/src/util/compilationDatabase.ml index 8d3e098a87..2443b8d3ab 100644 --- a/src/util/compilationDatabase.ml +++ b/src/util/compilationDatabase.ml @@ -1,4 +1,6 @@ -open Prelude +(** Input program from a real-world project using a compilation database. *) + +open Batteries let basename = "compile_commands.json" diff --git a/src/util/contextUtil.ml b/src/util/contextUtil.ml index 75f5f3461e..fc70e50dda 100644 --- a/src/util/contextUtil.ml +++ b/src/util/contextUtil.ml @@ -1,3 +1,5 @@ +(** Goblint-specific C attribute handling. *) + open GoblintCil (** Definition of Goblint specific user defined C attributes and their alternatives via options **) @@ -35,3 +37,18 @@ let should_keep ~isAttr ~keepOption ~removeAttr ~keepAttr fd = | false, _, false | _, true, false -> false + +(* Like should_keep above, but `keepOption` is directly the configuration value instead of its name *) +let should_keep_int_domain ~isAttr ~keepOption ~removeAttr ~keepAttr fd = + let al = fd.svar.vattr in + let s = attribute_to_string isAttr in + let has_annot a = has_option s a fd || has_attribute s a al in + match keepOption, has_annot removeAttr, has_annot keepAttr with + | _, true, true -> + failwith (Printf.sprintf "ContextUtil.should_remove: conflicting context attributes %s and %s on %s" removeAttr keepAttr (CilType.Fundec.show fd)) + | _, false, true + | true, false, false -> + true + | false, _, false + | _, true, false -> + false diff --git a/src/util/gobSys.ml b/src/util/gobSys.ml deleted file mode 100644 index 835c10a10c..0000000000 --- a/src/util/gobSys.ml +++ /dev/null @@ -1,22 +0,0 @@ -open Prelude - -let rec mkdir_parents filename = - let dirname = Fpath.parent filename in - let dirname_str = Fpath.to_string dirname in - if not (Sys.file_exists dirname_str) then ( - mkdir_parents dirname; - Unix.mkdir dirname_str 0o770; (* TODO: what permissions? *) - ) - -let mkdir_or_exists dirname = - let dirname_str = Fpath.to_string dirname in - try - Unix.mkdir dirname_str 0o770 (* TODO: what permissions? *) - with Unix.Unix_error (Unix.EEXIST, _, _) -> - assert (Sys.is_directory dirname_str) (* may exist, but as a file *) - -let rmdir_if_empty dirname = - try - Unix.rmdir (Fpath.to_string dirname) - with Unix.Unix_error (Unix.ENOTEMPTY, _, _) -> - () diff --git a/src/util/goblintDir.ml b/src/util/goblintDir.ml index 0b8bf04e7a..41f1ef76a3 100644 --- a/src/util/goblintDir.ml +++ b/src/util/goblintDir.ml @@ -1,3 +1,5 @@ +(** Intermediate data directory. *) + open GobConfig let root () = Fpath.v (get_string "goblint-dir") @@ -11,5 +13,5 @@ let init () = let finalize () = if not (get_bool "pre.keep") then - ignore (Goblintutil.rm_rf (preprocessed ())); + ignore (GobSys.rmdir_recursive (preprocessed ())); GobSys.rmdir_if_empty (root ()) diff --git a/src/util/goblintutil.ml b/src/util/goblintutil.ml deleted file mode 100644 index ae2ee45cd2..0000000000 --- a/src/util/goblintutil.ml +++ /dev/null @@ -1,157 +0,0 @@ -(** Globally accessible flags and utility functions. *) - -open GoblintCil -open GobConfig - - -(** Outputs information about what the goblin is doing *) -(* let verbose = ref false *) - -(** If this is true we output messages and collect accesses. - This is set to true in control.ml before we verify the result (or already before solving if warn = 'early') *) -let should_warn = ref false - -(** Whether signed overflow or underflow happened *) -let svcomp_may_overflow = ref false - -(** The file where everything is output *) -let out = ref stdout - -(** Command for assigning an id to a varinfo. All varinfos directly created by Goblint should be modified by this method *) -let create_var (var: varinfo) = - (* TODO Hack: this offset should preempt conflicts with ids generated by CIL *) - let start_id = 10_000_000_000 in - let hash = Hashtbl.hash { var with vid = 0 } in - let hash = if hash < start_id then hash + start_id else hash in - { var with vid = hash } - -(* Type invariant variables. *) -let type_inv_tbl = Hashtbl.create 13 -let type_inv (c:compinfo) : varinfo = - try Hashtbl.find type_inv_tbl c.ckey - with Not_found -> - let i = create_var (makeGlobalVar ("{struct "^c.cname^"}") (TComp (c,[]))) in - Hashtbl.add type_inv_tbl c.ckey i; - i - -let is_blessed (t:typ): varinfo option = - let me_gusta x = List.mem x (get_string_list "exp.unique") in - match unrollType t with - | TComp (ci,_) when me_gusta ci.cname -> Some (type_inv ci) - | _ -> (None : varinfo option) - - -(** A hack to see if we are currently doing global inits *) -let global_initialization = ref false - -(** Another hack to see if earlyglobs is enabled *) -let earlyglobs = ref false - -(** Whether currently in postsolver evaluations (e.g. verify, warn) *) -let postsolving = ref false - -(* None if verification is disabled, Some true if verification succeeded, Some false if verification failed *) -let verified : bool option ref = ref None - -let escape = XmlUtil.escape (* TODO: inline everywhere *) - - -(** Creates a directory and returns the absolute path **) -let create_dir name = - let dirName = GobFpath.cwd_append name in - GobSys.mkdir_or_exists dirName; - dirName - -(** Remove directory and its content, as "rm -rf" would do. *) -let rm_rf path = - let rec f path = - let path_str = Fpath.to_string path in - if Sys.is_directory path_str then begin - let files = Array.map (Fpath.add_seg path) (Sys.readdir path_str) in - Array.iter f files; - Unix.rmdir path_str - end else - Sys.remove path_str - in - f path - - -exception Timeout - -let timeout = Timeout.timeout - -let seconds_of_duration_string = - let unit = function - | "" | "s" -> 1 - | "m" -> 60 - | "h" -> 60 * 60 - | s -> failwith ("Unkown duration unit " ^ s ^ ". Supported units are h, m, s.") - in - let int_rest f s = Scanf.sscanf s "%u%s" f in - let split s = BatString.(head s 1, tail s 1) in - let rec f i s = - let u, r = split s in (* unit, rest *) - i * (unit u) + if r = "" then 0 else int_rest f r - in - int_rest f - -let vars = ref 0 -let evals = ref 0 -let narrow_reuses = ref 0 - -(* print GC statistics; taken from Cil.Stats.print which also includes timing; there's also Gc.print_stat, but it's in words instead of MB and more info than we want (also slower than quick_stat since it goes through the heap) *) -let print_gc_quick_stat chn = - let gc = Gc.quick_stat () in - let printM (w: float) : string = - let coeff = float_of_int (Sys.word_size / 8) in - Printf.sprintf "%.2fMB" (w *. coeff /. 1000000.0) - in - Printf.fprintf chn - "Memory statistics: total=%s, max=%s, minor=%s, major=%s, promoted=%s\n minor collections=%d major collections=%d compactions=%d\n" - (printM (gc.Gc.minor_words +. gc.Gc.major_words - -. gc.Gc.promoted_words)) - (printM (float_of_int gc.Gc.top_heap_words)) - (printM gc.Gc.minor_words) - (printM gc.Gc.major_words) - (printM gc.Gc.promoted_words) - gc.Gc.minor_collections - gc.Gc.major_collections - gc.Gc.compactions; - gc - -let exe_dir = Fpath.(parent (v Sys.executable_name)) -let command_line = match Array.to_list Sys.argv with - | command :: arguments -> Filename.quote_command command arguments - | [] -> assert false - -(* https://ocaml.org/api/Sys.html#2_SignalnumbersforthestandardPOSIXsignals *) -(* https://ocaml.github.io/ocamlunix/signals.html *) -let signal_of_string = let open Sys in function - | "sigint" -> sigint (* Ctrl+C Interactive interrupt *) - | "sigtstp" -> sigtstp (* Ctrl+Z Interactive stop *) - | "sigquit" -> sigquit (* Ctrl+\ Interactive termination *) - | "sigalrm" -> sigalrm (* Timeout *) - | "sigkill" -> sigkill (* Termination (cannot be ignored) *) - | "sigsegv" -> sigsegv (* Invalid memory reference, https://github.com/goblint/analyzer/issues/206 *) - | "sigterm" -> sigterm (* Termination *) - | "sigusr1" -> sigusr1 (* Application-defined signal 1 *) - | "sigusr2" -> sigusr2 (* Application-defined signal 2 *) - | "sigstop" -> sigstop (* Stop *) - | "sigprof" -> sigprof (* Profiling interrupt *) - | "sigxcpu" -> sigxcpu (* Timeout in cpu time *) - | s -> failwith ("Unhandled signal " ^ s) - -let self_signal signal = Unix.kill (Unix.getpid ()) signal - -let rec for_all_in_range (a, b) f = - let module BI = IntOps.BigIntOps in - if BI.compare a b > 0 - then true - else f a && (for_all_in_range (BI.add a (BI.one), b) f) - -let dummy_obj = Obj.repr () - -let jobs () = - match get_int "jobs" with - | 0 -> Cpu.numcores () - | n -> n diff --git a/src/util/intOps.ml b/src/util/intOps.ml index 49581e97b3..7c8e5d31e1 100644 --- a/src/util/intOps.ml +++ b/src/util/intOps.ml @@ -1,6 +1,4 @@ -(* -------------------------------------------------------------- - * IntOps Basics - * -------------------------------------------------------------- *) +(** Unified interface for integer types. *) open Batteries @@ -53,8 +51,8 @@ sig val to_int64 : t -> int64 val of_string : string -> t (* TODO: unused *) val to_string : t -> string - val of_bigint : Big_int_Z.big_int -> t - val to_bigint : t -> Big_int_Z.big_int + val of_bigint : Z.t -> t + val to_bigint : t -> Z.t end module type IntOps = @@ -112,8 +110,8 @@ struct let to_int64 = Int64.of_int let of_string = int_of_string let to_string = string_of_int - let of_bigint = Big_int_Z.int_of_big_int - let to_bigint = Big_int_Z.big_int_of_int + let of_bigint = Z.to_int + let to_bigint = Z.of_int end module Int32OpsBase : IntOpsBase with type t = int32 = @@ -157,8 +155,8 @@ struct let to_int64 = Int64.of_int32 let of_string = Int32.of_string let to_string = Int32.to_string - let of_bigint = Big_int_Z.int32_of_big_int - let to_bigint = Big_int_Z.big_int_of_int32 + let of_bigint = Z.to_int32 + let to_bigint = Z.of_int32 end module Int64OpsBase : IntOpsBase with type t = int64 = @@ -202,37 +200,29 @@ struct let to_int64 x = x let of_string = Int64.of_string let to_string = Int64.to_string - let of_bigint = Big_int_Z.int64_of_big_int - let to_bigint = Big_int_Z.big_int_of_int64 + let of_bigint = Z.to_int64 + let to_bigint = Z.of_int64 end -module BigIntOpsBase : IntOpsBase with type t = Big_int_Z.big_int = +module BigIntOpsBase : IntOpsBase with type t = Z.t = struct - type t = Big_int_Z.big_int - let zero = Big_int_Z.zero_big_int - let one = Big_int_Z.unit_big_int + type t = Z.t + let zero = Z.zero + let one = Z.one let upper_bound = None let lower_bound = None - let neg = Big_int_Z.minus_big_int - let abs = Big_int_Z.abs_big_int - let add = Big_int_Z.add_big_int - let sub = Big_int_Z.sub_big_int - let mul = Big_int_Z.mult_big_int - - (* If the first operand of a div is negative, Zarith rounds the result away from zero. - We thus always transform this into a division with a non-negative first operand. - *) - let div a b = if Big_int_Z.lt_big_int a zero then Big_int_Z.minus_big_int (Big_int_Z.div_big_int (Big_int_Z.minus_big_int a) b) else Big_int_Z.div_big_int a b - - (* Big_int_Z.mod_big_int computes the Euclidian Modulus, but what we want here is the remainder, as returned by mod on ints - -1 rem 5 == -1, whereas -1 Euclid-Mod 5 == 4 - *) - let rem a b = Big_int_Z.sub_big_int a (mul b (div a b)) - - let gcd x y = abs @@ Big_int_Z.gcd_big_int x y - let compare = Big_int_Z.compare_big_int - let equal = Big_int_Z.eq_big_int + let neg = Z.neg + let abs = Z.abs + let add = Z.add + let sub = Z.sub + let mul = Z.mul + let div = Z.div + let rem = Z.rem + + let gcd = Z.gcd + let compare = Z.compare + let equal = Z.equal let hash = Z.hash let top_range _ _ = false @@ -240,21 +230,21 @@ struct let max = Z.max let min = Z.min - let of_int = Big_int_Z.big_int_of_int - let to_int = Big_int_Z.int_of_big_int - let of_int64 x = Big_int_Z.big_int_of_int64 x - let to_int64 x = Big_int_Z.int64_of_big_int x - let of_string = Big_int_Z.big_int_of_string - let to_string = Big_int_Z.string_of_big_int + let of_int = Z.of_int + let to_int = Z.to_int + let of_int64 x = Z.of_int64 x + let to_int64 x = Z.to_int64 x + let of_string = Z.of_string + let to_string = Z.to_string let of_bigint x = x let to_bigint x = x - let shift_left = Big_int_Z.shift_left_big_int - let shift_right = Big_int_Z.shift_right_big_int + let shift_left = Z.shift_left + let shift_right = Z.shift_right let bitnot x = sub (neg x) one - let bitand = Big_int_Z.and_big_int - let bitor = Big_int_Z.or_big_int - let bitxor = Big_int_Z.xor_big_int + let bitand = Z.logand + let bitor = Z.logor + let bitxor = Z.logxor end diff --git a/src/util/loopUnrolling.ml b/src/util/loopUnrolling.ml index 45b5e558ae..4ce8fc06b4 100644 --- a/src/util/loopUnrolling.ml +++ b/src/util/loopUnrolling.ml @@ -1,3 +1,5 @@ +(** Syntactic loop unrolling. *) + open GobConfig open GoblintCil @@ -106,12 +108,12 @@ class findAssignmentConstDiff((diff: Z.t option ref), var) = object | Set ((Var v, NoOffset), BinOp (PlusA, Lval (Var v2, NoOffset), Const (CInt (cint,_,_)), _ ),_,_) when v.vid = var.vid && v2.vid = var.vid -> ( match !diff with | Some _ -> raise WrongOrMultiple - | _ -> diff := Some (Cilint.big_int_of_cilint cint); SkipChildren + | _ -> diff := Some cint; SkipChildren ) | Set ((Var v, NoOffset), BinOp (MinusA, Lval (Var v2, NoOffset), Const (CInt (cint,_,_)), _ ),_,_) when v.vid = var.vid && v2.vid = var.vid -> ( match !diff with | Some _ -> raise WrongOrMultiple - | _ -> diff := Some (Z.neg (Cilint.big_int_of_cilint cint)); SkipChildren + | _ -> diff := Some (Z.neg cint); SkipChildren ) | Set ((Var v, NoOffset), _,_,_) when v.vid = var.vid -> raise WrongOrMultiple | _ -> SkipChildren @@ -134,7 +136,7 @@ type assignment = | Other let classifyInstruction var = function - | Set (((Var info), NoOffset), Const(CInt (i,_,_)), _,_) when info.vid = var.vid -> Const (Cilint.big_int_of_cilint i) + | Set (((Var info), NoOffset), Const(CInt (i,_,_)), _,_) when info.vid = var.vid -> Const i | Set (((Var info), NoOffset), _ , _,_) when info.vid = var.vid -> Other | _ -> NoAssign @@ -227,16 +229,16 @@ let rec loopIterations start diff comp = else if shouldBeExact then None else - Some (Z.add roundedDown Z.one) + Some (Z.succ roundedDown) ) in match comp with | BinOp (op, (Const _ as c), var, t) -> loopIterations start diff (BinOp (flip op, var, c, t)) - | BinOp (Lt, _, (Const (CInt (cint,_,_) )), _) -> if Z.lt diff Z.zero then None else loopIterations' (Cilint.big_int_of_cilint cint) false - | BinOp (Gt, _, (Const (CInt (cint,_,_) )), _) -> if Z.gt diff Z.zero then None else loopIterations' (Cilint.big_int_of_cilint cint) false - | BinOp (Le, _, (Const (CInt (cint,_,_) )), _) -> if Z.lt diff Z.zero then None else loopIterations' (Z.add Z.one @@ Cilint.big_int_of_cilint cint) false - | BinOp (Ge, _, (Const (CInt (cint,_,_) )), _) -> if Z.gt diff Z.zero then None else loopIterations' (Z.pred @@ Cilint.big_int_of_cilint cint ) false - | BinOp (Ne, _, (Const (CInt (cint,_,_) )), _) -> loopIterations' (Cilint.big_int_of_cilint cint) true + | BinOp (Lt, _, (Const (CInt (cint,_,_) )), _) -> if Z.lt diff Z.zero then None else loopIterations' cint false + | BinOp (Gt, _, (Const (CInt (cint,_,_) )), _) -> if Z.gt diff Z.zero then None else loopIterations' cint false + | BinOp (Le, _, (Const (CInt (cint,_,_) )), _) -> if Z.lt diff Z.zero then None else loopIterations' (Z.succ cint) false + | BinOp (Ge, _, (Const (CInt (cint,_,_) )), _) -> if Z.gt diff Z.zero then None else loopIterations' (Z.pred cint) false + | BinOp (Ne, _, (Const (CInt (cint,_,_) )), _) -> loopIterations' cint true | _ -> failwith "unexpected comparison in loopIterations" let ( >>= ) = Option.bind @@ -273,7 +275,7 @@ let fixedLoopSize loopStatement func = constBefore var loopStatement func >>= fun start -> assignmentDifference loopStatement var >>= fun diff -> print_endline "comparison: "; - Pretty.fprint stdout (dn_exp () comparison) ~width:50; + Pretty.fprint stdout (dn_exp () comparison) ~width:max_int; print_endline ""; print_endline "variable: "; print_endline var.vname; @@ -322,9 +324,8 @@ class loopUnrollingCallVisitor = object 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/precCompare.ml b/src/util/precCompare.ml index dafa835f80..45b3e32ed8 100644 --- a/src/util/precCompare.ml +++ b/src/util/precCompare.ml @@ -1,4 +1,6 @@ -open Prelude +(** Precison comparison. *) + +open Batteries module Pretty = GoblintCil.Pretty open Pretty diff --git a/src/util/precCompareUtil.ml b/src/util/precCompareUtil.ml index a8f0908f01..e00447fd60 100644 --- a/src/util/precCompareUtil.ml +++ b/src/util/precCompareUtil.ml @@ -1,5 +1,6 @@ -open Prelude +(** Signatures for precision comparison. *) +open Batteries (** A printable, where each element is related to one location. Multiple elements might be related to the same location. *) diff --git a/src/util/precisionUtil.ml b/src/util/precisionUtil.ml index 0b0480251d..047043b4aa 100644 --- a/src/util/precisionUtil.ml +++ b/src/util/precisionUtil.ml @@ -1,19 +1,67 @@ +(** Integer and floating-point option and attribute handling. *) + (* We define precision by the number of IntDomains activated. - * We currently have 4 types: DefExc, Interval, Enums, Congruence *) -type int_precision = (bool * bool * bool * bool) + * We currently have 5 types: DefExc, Interval, Enums, Congruence, IntervalSet *) +type int_precision = (bool * bool * bool * bool * bool) (* Same applies for FloatDomain * We currently have only an interval type analysis *) type float_precision = (bool) +let def_exc: bool option ref = ref None +let interval: bool option ref = ref None +let enums: bool option ref = ref None +let congruence: bool option ref = ref None +let interval_set: bool option ref = ref None + +let get_def_exc () = + if !def_exc = None then + def_exc := Some (GobConfig.get_bool "ana.int.def_exc"); + Option.get !def_exc + +let get_interval () = + if !interval = None then + interval := Some (GobConfig.get_bool "ana.int.interval"); + Option.get !interval + +let get_enums () = + if !enums = None then + enums := Some (GobConfig.get_bool "ana.int.enums"); + Option.get !enums + +let get_congruence () = + if !congruence = None then + congruence := Some (GobConfig.get_bool "ana.int.congruence"); + Option.get !congruence + +let get_interval_set () = + if !interval_set = None then + interval_set := Some (GobConfig.get_bool "ana.int.interval_set"); + Option.get !interval_set + +let annotation_int_enabled: bool option ref = ref None + +let get_annotation_int_enabled () = + if !annotation_int_enabled = None then + annotation_int_enabled := Some (GobConfig.get_bool "annotation.int.enabled"); + Option.get !annotation_int_enabled + +let reset_lazy () = + def_exc := None; + interval := None; + enums := None; + congruence := None; + interval_set := None; + annotation_int_enabled := None (* Thus for maximum precision we activate all Domains *) -let max_int_precision : int_precision = (true, true, true, true) +let max_int_precision : int_precision = (true, true, true, true, true) let max_float_precision : float_precision = (true) let int_precision_from_fundec (fd: GoblintCil.fundec): int_precision = - ((ContextUtil.should_keep ~isAttr:GobPrecision ~keepOption:"ana.int.def_exc" ~removeAttr:"no-def_exc" ~keepAttr:"def_exc" fd), - (ContextUtil.should_keep ~isAttr:GobPrecision ~keepOption:"ana.int.interval" ~removeAttr:"no-interval" ~keepAttr:"interval" fd), - (ContextUtil.should_keep ~isAttr:GobPrecision ~keepOption:"ana.int.enums" ~removeAttr:"no-enums" ~keepAttr:"enums" fd), - (ContextUtil.should_keep ~isAttr:GobPrecision ~keepOption:"ana.int.congruence" ~removeAttr:"no-congruence" ~keepAttr:"congruence" fd)) + ((ContextUtil.should_keep_int_domain ~isAttr:GobPrecision ~keepOption:(get_def_exc ()) ~removeAttr:"no-def_exc" ~keepAttr:"def_exc" fd), + (ContextUtil.should_keep_int_domain ~isAttr:GobPrecision ~keepOption:(get_interval ()) ~removeAttr:"no-interval" ~keepAttr:"interval" fd), + (ContextUtil.should_keep_int_domain ~isAttr:GobPrecision ~keepOption:(get_enums ()) ~removeAttr:"no-enums" ~keepAttr:"enums" fd), + (ContextUtil.should_keep_int_domain ~isAttr:GobPrecision ~keepOption:(get_congruence ()) ~removeAttr:"no-congruence" ~keepAttr:"congruence" fd), + (ContextUtil.should_keep_int_domain ~isAttr:GobPrecision ~keepOption:(get_interval_set ()) ~removeAttr:"no-interval_set" ~keepAttr:"interval_set" fd)) let float_precision_from_fundec (fd: GoblintCil.fundec): float_precision = ((ContextUtil.should_keep ~isAttr:GobPrecision ~keepOption:"ana.float.interval" ~removeAttr:"no-float-interval" ~keepAttr:"float-interval" fd)) @@ -22,7 +70,7 @@ let int_precision_from_node (): int_precision = | Some n -> int_precision_from_fundec (Node.find_fundec n) | _ -> max_int_precision (* In case a Node is None we have to handle Globals, i.e. we activate all IntDomains (TODO: verify this assumption) *) -let is_congruence_active (_, _, _, c: int_precision): bool = c +let is_congruence_active (_, _, _, c,_: int_precision): bool = c let float_precision_from_node (): float_precision = match !MyCFG.current_node with @@ -30,11 +78,10 @@ let float_precision_from_node (): float_precision = | _ -> max_float_precision let int_precision_from_node_or_config (): int_precision = - if GobConfig.get_bool "annotation.int.enabled" then + if get_annotation_int_enabled () then int_precision_from_node () else - let f n = GobConfig.get_bool ("ana.int."^n) in - (f "def_exc", f "interval", f "enums", f "congruence") + (get_def_exc (), get_interval (), get_enums (), get_congruence (), get_interval_set ()) let float_precision_from_node_or_config (): float_precision = if GobConfig.get_bool "annotation.float.enabled" then diff --git a/src/util/preprocessor.ml b/src/util/preprocessor.ml index eb24e5c839..1da3aa25ce 100644 --- a/src/util/preprocessor.ml +++ b/src/util/preprocessor.ml @@ -1,4 +1,6 @@ -open Prelude +(** Detection of suitable C preprocessor. *) + +open Batteries let bad_cpp_version_regexp = Str.regexp_case_fold "clang\\|apple\\|darwin" diff --git a/src/util/privPrecCompareUtil.ml b/src/util/privPrecCompareUtil.ml index 367f01e8a7..8f0a24db3b 100644 --- a/src/util/privPrecCompareUtil.ml +++ b/src/util/privPrecCompareUtil.ml @@ -1,3 +1,5 @@ +(** {!BasePriv} precison comparison. *) + open GoblintCil open PrecCompareUtil diff --git a/src/util/processPool.ml b/src/util/processPool.ml index e93aa10548..89228fd6ac 100644 --- a/src/util/processPool.ml +++ b/src/util/processPool.ml @@ -1,3 +1,5 @@ +(** Process pool for running processes in parallel. *) + type task = { command: string; cwd: Fpath.t option; diff --git a/src/util/sarif.ml b/src/util/sarif.ml index 99fb78f377..7620384cc4 100644 --- a/src/util/sarif.ml +++ b/src/util/sarif.ml @@ -1,5 +1,7 @@ +(** SARIF output of {!Messages}. *) + (** The Sarif format is a standardised output format for static analysis tools. https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html *) -open Prelude +open Batteries open SarifType open SarifRules @@ -24,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) }; } @@ -86,11 +88,16 @@ let result_of_message (message: Messages.Message.t): Result.t list = } in [result] - | Group {group_text; pieces} -> + | Group {group_text; group_loc; pieces} -> (* each grouped piece becomes a separate result with the other locations as related *) + (* TODO: use group_loc instead of distributing? *) + let group_loc_text = match group_loc with + | None -> "" + | Some group_loc -> GobPretty.sprintf " (%a)" CilType.Location.pretty (Messages.Location.to_cil group_loc) + in let piece_locations = List.map piece_location pieces in List.map2i (fun i piece locations -> - let text = prefix ^ group_text ^ "\n" ^ piece.Messages.Piece.text in + let text = prefix ^ group_text ^ group_loc_text ^ "\n" ^ piece.Messages.Piece.text in let relatedLocations = List.unique ~eq:Location.equal (List.flatten (List.remove_at i piece_locations)) in let result: Result.t = { ruleId; @@ -135,7 +142,7 @@ let to_yojson messages = schema = "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.5.json"; runs = [{ invocations = [{ - commandLine = Goblintutil.command_line; + commandLine = GobSys.command_line; executionSuccessful = true; }]; artifacts = artifacts_of_messages messages; diff --git a/src/util/sarifRules.ml b/src/util/sarifRules.ml index 17f4f5f263..866ae123f8 100644 --- a/src/util/sarifRules.ml +++ b/src/util/sarifRules.ml @@ -1,3 +1,4 @@ +(** SARIF rule definitions for Goblint. *) type categoryInformation = { name:string; @@ -185,6 +186,14 @@ let rules = [ shortDescription="The software reads or writes to a buffer using an index or pointer that references a memory location after the end of the buffer. "; helpUri="https://cwe.mitre.org/data/definitions/788.html"; longDescription=""; + }; + { + name="415"; + ruleId="GO0022"; + helpText="Double Free"; + shortDescription="The product calls free() twice on the same memory address, potentially leading to modification of unexpected memory locations."; + helpUri="https://cwe.mitre.org/data/definitions/415.html"; + longDescription="" } ] diff --git a/src/util/sarifType.ml b/src/util/sarifType.ml index 47b7261e7d..793ba24d01 100644 --- a/src/util/sarifType.ml +++ b/src/util/sarifType.ml @@ -1,3 +1,4 @@ +(** SARIF format types. *) module Invocation = struct diff --git a/src/util/server.ml b/src/util/server.ml index 96b3a0e5ce..22f5a03350 100644 --- a/src/util/server.ml +++ b/src/util/server.ml @@ -1,10 +1,25 @@ +(** Interactive server mode using JSON-RPC. *) + open Batteries open Jsonrpc open GoblintCil +module InvariantParser = WitnessUtil.InvariantParser + +module type ArgWrapper = +sig + module Arg: ArgTools.BiArg + module Locator: module type of WitnessUtil.Locator (Arg.Node) + val locator: Locator.t + val find_node: string -> Arg.Node.t + val find_cfg_node: string -> Arg.Node.t list +end + type t = { mutable file: Cil.file option; mutable max_ids: MaxIdUtil.max_ids; + arg_wrapper: (module ArgWrapper) ResettableLazy.t; + invariant_parser: InvariantParser.t ResettableLazy.t; input: IO.input; output: unit IO.output; } @@ -106,6 +121,41 @@ let serve serv = |> Seq.map Packet.t_of_yojson |> Seq.iter (handle_packet serv) +let arg_wrapper: (module ArgWrapper) ResettableLazy.t = + ResettableLazy.from_fun (fun () -> + let module Arg = (val (Option.get_exn !ArgTools.current_arg Response.Error.(E (make ~code:RequestFailed ~message:"not analyzed or arg disabled" ())))) in + let module Locator = WitnessUtil.Locator (Arg.Node) in + let module StringH = Hashtbl.Make (Printable.Strings) in + + let locator = Locator.create () in + let ids = StringH.create 113 in + let cfg_nodes = StringH.create 113 in + Arg.iter_nodes (fun n -> + let cfgnode = Arg.Node.cfgnode n in + let loc = UpdateCil.getLoc cfgnode in + if not loc.synthetic then + Locator.add locator loc n; + StringH.replace ids (Arg.Node.to_string n) n; + StringH.add cfg_nodes (Node.show_id cfgnode) n (* add for find_all *) + ); + + let module ArgWrapper = + struct + module Arg = Arg + module Locator = Locator + let locator = locator + let find_node = StringH.find ids + let find_cfg_node = StringH.find_all cfg_nodes + end + in + (module ArgWrapper: ArgWrapper) + ) + +let invariant_parser: InvariantParser.t ResettableLazy.t = + ResettableLazy.from_fun (fun () -> + InvariantParser.create !Cilfacade.current_file + ) + let make ?(input=stdin) ?(output=stdout) file : t = let max_ids = match file with @@ -115,6 +165,8 @@ let make ?(input=stdin) ?(output=stdout) file : t = { file; max_ids; + arg_wrapper; + invariant_parser; input; output } @@ -157,9 +209,11 @@ let reparse (s: t) = (* Only called when the file has not been reparsed, so we can skip the expensive CFG comparison. *) let virtual_changes file = - let eq (glob: CompareCIL.global_col) _ _ _ = match glob.def with - | Some (Fun fdec) when CompareCIL.should_reanalyze fdec -> CompareCIL.ForceReanalyze fdec, None - | _ -> Unchanged, None + let eq ?(matchVars=true) ?(matchFuns=true) ?(renameDetection=false) _ _ _ gc_old (gc_new: CompareCIL.global_col) ((change_info : CompareCIL.change_info), final_matches) = (match gc_new.def with + | Some (Fun fdec) when CompareCIL.should_reanalyze fdec -> + change_info.exclude_from_rel_destab <- CompareCIL.VarinfoSet.add fdec.svar change_info.exclude_from_rel_destab + | _ -> change_info.unchanged <- {old = gc_old; current= gc_new} :: change_info.unchanged); + change_info, final_matches in CompareCIL.compareCilFiles ~eq file file @@ -176,8 +230,41 @@ let increment_data (s: t) file reparsed = match Serialize.Cache.get_opt_data Sol Some { server = true; Analyses.changes; solver_data; restarting = [] }, false | _ -> None, true + +module Locator = WitnessUtil.Locator (Node) +let node_locator: Locator.t ResettableLazy.t = + ResettableLazy.from_fun (fun () -> + let module Cfg = (val !MyCFG.current_cfg) in + let locator = Locator.create () in + + (* DFS, copied from CfgTools.find_backwards_reachable *) + let module NH = MyCFG.NodeH in + let reachable = NH.create 100 in + let rec iter_node node = + if not (NH.mem reachable node) then begin + NH.replace reachable node (); + let loc = UpdateCil.getLoc node in + if not loc.synthetic then + Locator.add locator loc node; + List.iter (fun (_, prev_node) -> + iter_node prev_node + ) (Cfg.prev node) + end + in + + Cil.iterGlobals !Cilfacade.current_file (function + | GFun (fd, _) -> + let return_node = Node.Function fd in + iter_node return_node + | _ -> () + ); + + locator + ) + 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 ( @@ -186,19 +273,25 @@ let analyze ?(reset=false) (s: t) = Serialize.Cache.reset_data SolverData; Serialize.Cache.reset_data AnalysisData); let increment_data, fresh = increment_data s file reparsed in + ResettableLazy.reset node_locator; + ResettableLazy.reset s.arg_wrapper; + ResettableLazy.reset s.invariant_parser; Cilfacade.reset_lazy (); InvariantCil.reset_lazy (); WideningThresholds.reset_lazy (); IntDomain.reset_lazy (); + PrecisionUtil.reset_lazy (); ApronDomain.reset_lazy (); AutoTune.reset_lazy (); + LibraryFunctions.reset_lazy (); Access.reset (); s.file <- Some file; GobConfig.set_bool "incremental.load" (not fresh); Fun.protect ~finally:(fun () -> GobConfig.set_bool "incremental.load" true ) (fun () -> - Maingoblint.do_analyze increment_data (Option.get s.file) + Maingoblint.do_analyze increment_data (Option.get s.file); + Maingoblint.do_gobview (Option.get s.file); ) let () = @@ -215,9 +308,12 @@ let () = let process { reset } serve = try analyze serve ~reset; - {status = if !Goblintutil.verified = Some false then VerifyError else Success} - with Sys.Break -> + {status = if !AnalysisState.verified = Some false then VerifyError else Success} + with + | Sys.Break -> {status = Aborted} + | Maingoblint.FrontendError message -> + Response.Error.(raise (make ~code:RequestFailed ~message ())) end); register (module struct @@ -289,20 +385,92 @@ let () = type params = unit [@@deriving of_yojson] type response = Yojson.Safe.t [@@deriving to_yojson] let process () s = - if GobConfig.get_bool "server.reparse" then ( - GoblintDir.init (); - Fun.protect ~finally:GoblintDir.finalize (fun () -> - ignore Maingoblint.(preprocess_files () |> parse_preprocessed) - ) - ); - Preprocessor.dependencies_to_yojson () + try + if GobConfig.get_bool "server.reparse" then ( + GoblintDir.init (); + Fun.protect ~finally:GoblintDir.finalize (fun () -> + ignore Maingoblint.(preprocess_files () |> parse_preprocessed) + ) + ); + Preprocessor.dependencies_to_yojson () + with Maingoblint.FrontendError message -> + Response.Error.(raise (make ~code:RequestFailed ~message ())) end); register (module struct let name = "functions" type params = unit [@@deriving of_yojson] type response = Function.t list [@@deriving to_yojson] - let process () serv = Function.getFunctionsList (Option.get serv.file).globals + let process () serv = + match serv.file with + | Some file -> Function.getFunctionsList file.globals + | None -> Response.Error.(raise (make ~code:RequestFailed ~message:"not analyzed" ())) + end); + + register (module struct + let name = "cil/varinfos" + type params = unit [@@deriving of_yojson] + type varinfo_data = { + vid: int; + name: string; + type_: CilType.Typ.t [@key "type"]; + location: CilType.Location.t; + original_name: string option; + role: string; + function_: CilType.Fundec.t option [@key "function"] [@default None]; + } [@@deriving to_yojson] + type response = varinfo_data list [@@deriving to_yojson] + let process () serv = + Cilfacade.VarinfoH.fold (fun vi role acc -> + let role_str = match role with + | Cilfacade.Formal _ -> "formal" + | Local _ -> "local" + | Function -> "function" + | Global -> "global" + in + let function_ = match role with + | Cilfacade.Formal fd + | Local fd -> + Some fd + | Function + | Global -> + None + in + let data = { + vid = vi.vid; + name = vi.vname; + type_ = vi.vtype; + location = vi.vdecl; + original_name = Cilfacade.find_original_name vi; + role = role_str; + function_; + } + in + data :: acc + ) (ResettableLazy.force Cilfacade.varinfo_roles) [] + end); + + register (module struct + let name = "richvarinfos" + type params = unit [@@deriving of_yojson] + type varinfo_data = { + vid: int; + name: string; + description: string; + } [@@deriving to_yojson] + type response = varinfo_data list [@@deriving to_yojson] + let process () serv = + !RichVarinfo.BiVarinfoMap.Collection.mappings + |> List.concat_map (fun (module VarinfoMap: RichVarinfo.BiVarinfoMap.S) -> + VarinfoMap.bindings () + |> List.map (fun (x, (vi: Cil.varinfo)) -> + { + vid = vi.vid; + name = vi.vname; + description = VarinfoMap.describe_varinfo vi x; + } + ) + ) end); register (module struct @@ -316,15 +484,302 @@ let () = { cfg } end); + register (module struct + let name = "cfg/lookup" + type params = { + node: string option [@default None]; + location: CilType.Location.t option [@default None]; + } [@@deriving of_yojson] + type response = { + node: string; + location: CilType.Location.t; + function_: CilType.Fundec.t [@key "function"]; + next: (Edge.t list * string) list; + prev: (Edge.t list * string) list; + } [@@deriving to_yojson] + let process (params: params) serv = + let node = match params.node, params.location with + | Some node_id, None -> + begin try + Node.of_id node_id + with Not_found -> + Response.Error.(raise (make ~code:RequestFailed ~message:"not analyzed or non-existent node" ())) + end + | None, Some location -> + let node_opt = + let open GobOption.Syntax in + let* nodes = Locator.find_opt (ResettableLazy.force node_locator) location in + Locator.ES.choose_opt nodes + in + Option.get_exn node_opt Response.Error.(E (make ~code:RequestFailed ~message:"cannot find node for location" ())) + | _, _ -> + Response.Error.(raise (make ~code:RequestFailed ~message:"requires node xor location" ())) + in + let node_id = Node.show_id node in + let location = UpdateCil.getLoc node in + let function_ = Node.find_fundec node in + let module Cfg = (val !MyCFG.current_cfg) in + let next = + Cfg.next node + |> List.map (fun (edges, to_node) -> + (List.map snd edges, Node.show_id to_node) + ) + in + let prev = + Cfg.prev node + |> List.map (fun (edges, to_node) -> + (List.map snd edges, Node.show_id to_node) + ) + in + {node = node_id; location; function_; next; prev} + end); + + register (module struct + let name = "arg/dot" + type params = unit [@@deriving of_yojson] + type response = { + arg: string + } [@@deriving to_yojson] + let process () serv = + let module ArgWrapper = (val (ResettableLazy.force serv.arg_wrapper)) in + let module ArgDot = ArgTools.Dot (ArgWrapper.Arg) in + let arg = Format.asprintf "%t" ArgDot.dot in + {arg} + end); + + register (module struct + let name = "arg/lookup" + type params = { + node: string option [@default None]; + location: CilType.Location.t option [@default None]; + cfg_node: string option [@default None]; + } [@@deriving of_yojson] + + type edge_node = { + edge: MyARG.inline_edge; + node: string; + cfg_node: string; + context: string; + path: string; + location: CilType.Location.t; + function_: CilType.Fundec.t [@key "function"]; + } [@@deriving to_yojson] + type one_response = { + node: string; + cfg_node: string; + context: string; + path: string; + location: CilType.Location.t; + function_: CilType.Fundec.t [@key "function"]; + next: edge_node list; + prev: edge_node list; + } [@@deriving to_yojson] + type response = one_response list [@@deriving to_yojson] + + let process (params: params) serv = + let module ArgWrapper = (val (ResettableLazy.force serv.arg_wrapper)) in + let open ArgWrapper in + let open GobList.Syntax in + let+ n: Arg.Node.t = match params.node, params.location, params.cfg_node with + | None, None, None -> + [Arg.main_entry] + | Some node_id, None, None -> + begin try + [ArgWrapper.find_node node_id] + with Not_found -> + [] (* non-existent node *) + end + | None, Some location, None -> + let nodes_opt = + let open GobOption.Syntax in + let+ nodes = Locator.find_opt locator location in + Locator.ES.elements nodes + in + Option.default [] nodes_opt (* cannot find node for location *) + | None, None, Some cfg_node -> + ArgWrapper.find_cfg_node cfg_node + | _, _, _ -> + Response.Error.(raise (make ~code:RequestFailed ~message:"requires at most one of node, location and cfg_node" ())) + in + let cfg_node = Arg.Node.cfgnode n in + let cfg_node_id = Node.show_id cfg_node in + let location = UpdateCil.getLoc cfg_node in + let next = + Arg.next n + |> List.map (fun (edge, to_node) -> + let cfg_to_node = Arg.Node.cfgnode to_node in + { + edge; + node = Arg.Node.to_string to_node; + cfg_node = Node.show_id cfg_to_node; + context = string_of_int (Arg.Node.context_id to_node); + path = string_of_int (Arg.Node.path_id to_node); + location = UpdateCil.getLoc cfg_to_node; + function_ = Node.find_fundec cfg_to_node; + } + ) + in + let prev = + Arg.prev n + |> List.map (fun (edge, to_node) -> + let cfg_to_node = Arg.Node.cfgnode to_node in + { + edge; + node = Arg.Node.to_string to_node; + cfg_node = Node.show_id cfg_to_node; + context = string_of_int (Arg.Node.context_id to_node); + path = string_of_int (Arg.Node.path_id to_node); + location = UpdateCil.getLoc cfg_to_node; + function_ = Node.find_fundec cfg_to_node; + } + ) + in + { + node = Arg.Node.to_string n; + cfg_node = cfg_node_id; + context = string_of_int (Arg.Node.context_id n); + path = string_of_int (Arg.Node.path_id n); + location; + function_ = Node.find_fundec cfg_node; + next; + prev + } + end); + register (module struct let name = "node_state" type params = { nid: string } [@@deriving of_yojson] type response = Yojson.Safe.t [@@deriving to_yojson] let process { nid } serv = - let f = !Control.current_node_state_json in - let n = Node.of_id nid in - let json = f n in - json + match Node.of_id nid with + | n -> + begin match !Control.current_node_state_json n with + | Some json -> json + | None -> Response.Error.(raise (make ~code:RequestFailed ~message:"not analyzed, non-existent or dead node" ())) + end + | exception Not_found -> Response.Error.(raise (make ~code:RequestFailed ~message:"not analyzed or non-existent node" ())) + end); + + register (module struct + let name = "global-state" + type params = { + vid: int option [@default None]; + node: string option [@default None]; + } [@@deriving of_yojson] + type response = Yojson.Safe.t [@@deriving to_yojson] + let process (params: params) serv = + let vq_opt = match params.vid, params.node with + | None, None -> + None + | Some vid, None -> + let vi = {Cil.dummyFunDec.svar with vid} in (* Equal to actual varinfo by vid. *) + Some (VarQuery.Global vi) + | None, Some node_id -> + let node = try + Node.of_id node_id + with Not_found -> + Response.Error.(raise (make ~code:RequestFailed ~message:"not analyzed or non-existent node" ())) + in + Some (VarQuery.Node {node; fundec = None}) + | Some _, Some _ -> + Response.Error.(raise (make ~code:RequestFailed ~message:"requires at most one of vid and node" ())) + in + !Control.current_varquery_global_state_json vq_opt + end); + + register (module struct + let name = "arg/state" + type params = { + node: string + } [@@deriving of_yojson] + type response = Yojson.Safe.t [@@deriving to_yojson] + let process {node} serv = + let module ArgWrapper = (val (ResettableLazy.force serv.arg_wrapper)) in + match ArgWrapper.find_node node with + | n -> + begin match ArgWrapper.Arg.query n DYojson with + | `Lifted json -> json + | (`Bot | `Top) as r -> Response.Error.(raise (make ~code:RequestFailed ~message:("query returned " ^ Queries.FlatYojson.show r) ())) + end + | exception Not_found -> Response.Error.(raise (make ~code:RequestFailed ~message:"non-existent node" ())) + end); + + register (module struct + let name = "arg/eval" + type params = { + node: string; + exp: string option [@default None]; + vid: int option [@default None]; (* eval varinfo by vid to avoid exp parsing problems *) + } [@@deriving of_yojson] + type response = Queries.VD.t [@@deriving to_yojson] + let process (params: params) serv = + let module ArgWrapper = (val (ResettableLazy.force serv.arg_wrapper)) in + let open ArgWrapper in + match ArgWrapper.find_node params.node with + | n -> + let exp = match params.exp, params.vid with + | Some exp, None -> + begin match InvariantParser.parse_cabs exp with + | Ok exp_cabs -> + let cfg_node = Arg.Node.cfgnode n in + let fundec = Node.find_fundec cfg_node in + let loc = UpdateCil.getLoc cfg_node in + + begin match InvariantParser.parse_cil (ResettableLazy.force serv.invariant_parser) ~fundec ~loc exp_cabs with + | Ok exp -> exp + | Error e -> + Response.Error.(raise (make ~code:RequestFailed ~message:"CIL couldn't parse expression (undefined variables or side effects)" ())) + end + | Error e -> + Response.Error.(raise (make ~code:RequestFailed ~message:"Frontc couldn't parse expression (invalid syntax)" ())) + end + | None, Some vid -> + let vi = {Cil.dummyFunDec.svar with vid} in (* Equal to actual varinfo by vid. *) + Lval (Cil.var vi) + | _, _ -> + Response.Error.(raise (make ~code:RequestFailed ~message:"requires exp xor vid" ())) + in + Arg.query n (EvalValue exp) + | exception Not_found -> Response.Error.(raise (make ~code:RequestFailed ~message:"non-existent node" ())) + end); + + register (module struct + let name = "arg/eval-int" + type params = { + node: string; + exp: string; + } [@@deriving of_yojson] + type response = { + raw: Yojson.Safe.t; + int: GobZ.t option; + bool: bool option; + } [@@deriving to_yojson] + let process {node; exp} serv = + let module ArgWrapper = (val (ResettableLazy.force serv.arg_wrapper)) in + let open ArgWrapper in + match ArgWrapper.find_node node with + | n -> + begin match InvariantParser.parse_cabs exp with + | Ok exp_cabs -> + let cfg_node = Arg.Node.cfgnode n in + let fundec = Node.find_fundec cfg_node in + let loc = UpdateCil.getLoc cfg_node in + + begin match InvariantParser.parse_cil (ResettableLazy.force serv.invariant_parser) ~fundec ~loc exp_cabs with + | Ok exp -> + let x = Arg.query n (EvalInt exp) in + { + raw = Queries.ID.to_yojson x; + int = Queries.ID.to_int x; + bool = Queries.ID.to_bool x; (* Separate, because Not{0} has to_int = None, but to_bool = Some true. *) + } + | Error e -> + Response.Error.(raise (make ~code:RequestFailed ~message:"CIL couldn't parse expression (undefined variables or side effects)" ())) + end + | Error e -> + Response.Error.(raise (make ~code:RequestFailed ~message:"Frontc couldn't parse expression (invalid syntax)" ())) + end + | exception Not_found -> Response.Error.(raise (make ~code:RequestFailed ~message:"non-existent node" ())) end); register (module struct diff --git a/src/util/std/dune b/src/util/std/dune new file mode 100644 index 0000000000..c6961a1725 --- /dev/null +++ b/src/util/std/dune @@ -0,0 +1,17 @@ +(include_subdirs no) + +(library + (name goblint_std) + (public_name goblint.std) + (libraries + batteries.unthreaded + zarith + goblint-cil + fpath + yojson + yaml) + (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/std/gobGc.ml b/src/util/std/gobGc.ml new file mode 100644 index 0000000000..3755658d42 --- /dev/null +++ b/src/util/std/gobGc.ml @@ -0,0 +1,19 @@ +(* print GC statistics; taken from Cil.Stats.print which also includes timing; there's also Gc.print_stat, but it's in words instead of MB and more info than we want (also slower than quick_stat since it goes through the heap) *) +let print_quick_stat chn = + let gc = Gc.quick_stat () in + let printM (w: float) : string = + let coeff = float_of_int (Sys.word_size / 8) in + Printf.sprintf "%.2fMB" (w *. coeff /. 1000000.0) + in + Printf.fprintf chn + "Memory statistics: total=%s, max=%s, minor=%s, major=%s, promoted=%s\n minor collections=%d major collections=%d compactions=%d\n" + (printM (gc.Gc.minor_words +. gc.Gc.major_words + -. gc.Gc.promoted_words)) + (printM (float_of_int gc.Gc.top_heap_words)) + (printM gc.Gc.minor_words) + (printM gc.Gc.major_words) + (printM gc.Gc.promoted_words) + gc.Gc.minor_collections + gc.Gc.major_collections + gc.Gc.compactions; + gc diff --git a/src/util/gobHashtbl.ml b/src/util/std/gobHashtbl.ml similarity index 100% rename from src/util/gobHashtbl.ml rename to src/util/std/gobHashtbl.ml 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/std/gobPretty.ml b/src/util/std/gobPretty.ml new file mode 100644 index 0000000000..557d995cb2 --- /dev/null +++ b/src/util/std/gobPretty.ml @@ -0,0 +1,61 @@ +open GoblintCil + +let show = Pretty.sprint ~width:max_int + +let sprint f x = show (f () x) + +let sprintf (fmt: ('a, unit, Pretty.doc, string) format4): 'a = + Pretty.gprintf show fmt + + +open Pretty + +(* Parses a format string to generate a nop-function of the correct type. *) +let igprintf (finish: 'b) (format : ('a, unit, doc, 'b) format4) : 'a = + let format = string_of_format format in + let flen = String.length format in + let fget = String.unsafe_get format in + let rec literal acc i = + let rec skipChars j = + if j >= flen || (match fget j with '%' | '@' | '\n' -> true | _ -> false) then + collect nil j + else + skipChars (succ j) + in + skipChars (succ i) + and collect (acc: doc) (i: int) = + if i >= flen then begin + Obj.magic finish + end else begin + let c = fget i in + if c = '%' then begin + let j = skip_args (succ i) in + match fget j with + '%' -> literal acc j + | ',' -> collect acc (succ j) + | 's' | 'c' | 'd' | 'i' | 'o' | 'x' | 'X' | 'u' + | 'f' | 'e' | 'E' | 'g' | 'G' | 'b' | 'B' -> + Obj.magic(fun b -> collect nil (succ j)) + | 'L' | 'l' | 'n' -> Obj.magic(fun n -> collect nil (succ (succ j))) + | 'a' -> Obj.magic(fun pprinter arg -> collect nil (succ j)) + | 't' -> Obj.magic(fun pprinter -> collect nil (succ j)) + | c -> invalid_arg ("dprintf: unknown format %s" ^ String.make 1 c) + end else if c = '@' then begin + if i + 1 < flen then begin + match fget (succ i) with + '[' | ']' | '!' | '?' | '^' | '@' -> collect nil (i + 2) + | '<' | '>' -> collect nil (i + 1) + | c -> invalid_arg ("dprintf: unknown format @" ^ String.make 1 c) + end else + invalid_arg "dprintf: incomplete format @" + end else if c = '\n' then begin + collect nil (i + 1) + end else + literal acc i + end + and skip_args j = + match String.unsafe_get format j with + '0' .. '9' | ' ' | '.' | '-' -> skip_args (succ j) + | c -> j + in + collect nil 0 diff --git a/src/util/std/gobRef.ml b/src/util/std/gobRef.ml new file mode 100644 index 0000000000..912f975467 --- /dev/null +++ b/src/util/std/gobRef.ml @@ -0,0 +1,5 @@ +(** call [f], with [r] temporarily set to [x] *) +let wrap r x = + let x0 = !r in + r := x; + Fun.protect ~finally:(fun () -> r := x0) 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/std/gobSys.ml b/src/util/std/gobSys.ml new file mode 100644 index 0000000000..a2c69419c4 --- /dev/null +++ b/src/util/std/gobSys.ml @@ -0,0 +1,80 @@ +let rec mkdir_parents filename = + let dirname = Fpath.parent filename in + let dirname_str = Fpath.to_string dirname in + if not (Sys.file_exists dirname_str) then ( + mkdir_parents dirname; + Unix.mkdir dirname_str 0o770; (* TODO: what permissions? *) + ) + +let mkdir_or_exists dirname = + let dirname_str = Fpath.to_string dirname in + try + Unix.mkdir dirname_str 0o770 (* TODO: what permissions? *) + with Unix.Unix_error (Unix.EEXIST, _, _) -> + assert (Sys.is_directory dirname_str) (* may exist, but as a file *) + +(** Creates a directory and returns the absolute path **) +let mkdir_or_exists_absolute name = + let dirName = GobFpath.cwd_append name in + mkdir_or_exists dirName; + dirName + +let rmdir_if_empty dirname = + try + Unix.rmdir (Fpath.to_string dirname) + with Unix.Unix_error (Unix.ENOTEMPTY, _, _) -> + () + +(** Remove directory and its content, as "rm -rf" would do. *) +let rmdir_recursive path = + let rec f path = + let path_str = Fpath.to_string path in + if Sys.is_directory path_str then begin + let files = Array.map (Fpath.add_seg path) (Sys.readdir path_str) in + Array.iter f files; + Unix.rmdir path_str + end else + Sys.remove path_str + in + f path + + +let exe_dir = Fpath.(parent (v Sys.executable_name)) + +let command_line = match Array.to_list Sys.argv with + | command :: arguments -> Filename.quote_command command arguments + | [] -> assert false + + +(* Sys.time gives runtime in seconds as float *) +let split_time () = (* gives CPU time in h,m,s,ms *) + let f = Sys.time () in + let i = int_of_float f in + let ms = int_of_float (BatFloat.modulo f 1.0 *. 1000.) in + i / 3600, i / 60 mod 60, i mod 60, ms + +let string_of_time () = (* CPU time as hh:mm:ss.ms *) + let h,m,s,ms = split_time () in + Printf.sprintf "%02d:%02d:%02d.%03d" h m s ms + + +(* https://ocaml.org/api/Sys.html#2_SignalnumbersforthestandardPOSIXsignals *) +(* https://ocaml.github.io/ocamlunix/signals.html *) +let signal_of_string = + let open Sys in + function + | "sigint" -> sigint (* Ctrl+C Interactive interrupt *) + | "sigtstp" -> sigtstp (* Ctrl+Z Interactive stop *) + | "sigquit" -> sigquit (* Ctrl+\ Interactive termination *) + | "sigalrm" -> sigalrm (* Timeout *) + | "sigkill" -> sigkill (* Termination (cannot be ignored) *) + | "sigsegv" -> sigsegv (* Invalid memory reference, https://github.com/goblint/analyzer/issues/206 *) + | "sigterm" -> sigterm (* Termination *) + | "sigusr1" -> sigusr1 (* Application-defined signal 1 *) + | "sigusr2" -> sigusr2 (* Application-defined signal 2 *) + | "sigstop" -> sigstop (* Stop *) + | "sigprof" -> sigprof (* Profiling interrupt *) + | "sigxcpu" -> sigxcpu (* Timeout in cpu time *) + | s -> invalid_arg ("Unhandled signal " ^ s) + +let self_signal signal = Unix.kill (Unix.getpid ()) signal diff --git a/src/util/gobUnix.ml b/src/util/std/gobUnix.ml similarity index 53% rename from src/util/gobUnix.ml rename to src/util/std/gobUnix.ml index b731cc02bd..f34cae64c4 100644 --- a/src/util/gobUnix.ml +++ b/src/util/std/gobUnix.ml @@ -1,7 +1,12 @@ -open Prelude open Unix let string_of_process_status = function | WEXITED n -> "terminated with exit code " ^ string_of_int n | WSIGNALED n -> "killed by signal " ^ string_of_int n | WSTOPPED n -> "stopped by signal " ^ string_of_int n + + +let localtime () = + let open Unix in + let tm = time () |> localtime in + Printf.sprintf "%d-%02d-%02d %02d:%02d:%02d" (tm.tm_year + 1900) (tm.tm_mon + 1) tm.tm_mday tm.tm_hour tm.tm_min tm.tm_sec diff --git a/src/util/gobYaml.ml b/src/util/std/gobYaml.ml similarity index 100% rename from src/util/gobYaml.ml rename to src/util/std/gobYaml.ml 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/std/gobZ.ml b/src/util/std/gobZ.ml new file mode 100644 index 0000000000..598b8448dc --- /dev/null +++ b/src/util/std/gobZ.ml @@ -0,0 +1,10 @@ +type t = Z.t + +let to_yojson z = + `Intlit (Z.to_string z) + +let rec for_all_range f (a, b) = + if Z.compare a b > 0 then + true + else + f a && for_all_range f (Z.succ a, b) diff --git a/src/util/std/goblint_std.ml b/src/util/std/goblint_std.ml new file mode 100644 index 0000000000..e716d1df5b --- /dev/null +++ b/src/util/std/goblint_std.ml @@ -0,0 +1,24 @@ +(** 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 GobYaml = GobYaml +module GobYojson = GobYojson +module GobZ = GobZ diff --git a/src/util/timeout.ml b/src/util/timeout.ml index 908fbb9b8e..ce1352ef67 100644 --- a/src/util/timeout.ml +++ b/src/util/timeout.ml @@ -1,3 +1,5 @@ +(** Timeout utilities. *) + module Unix = struct let timeout f arg tsecs timeout_fn = let oldsig = Sys.signal Sys.sigprof (Sys.Signal_handle (fun _ -> timeout_fn ())) in @@ -13,6 +15,9 @@ module Js = struct (* TODO: Implement this *) end -let timeout = match Sys.backend_type with +let wrap = match Sys.backend_type with | Other "js_of_ocaml" -> Js.timeout | _ -> Unix.timeout + + +exception Timeout diff --git a/src/util/timing/goblint_timing.ml b/src/util/timing/goblint_timing.ml index c4603f5804..a65ce0ecf4 100644 --- a/src/util/timing/goblint_timing.ml +++ b/src/util/timing/goblint_timing.ml @@ -22,19 +22,6 @@ struct incr next_tef_pid; tef_pid - let start options' = - options := options'; - if !options.tef then ( - (* Override TEF process and thread name for track rendering. *) - Catapult.Tracing.emit ~pid:tef_pid "thread_name" ~cat:["__firefox_profiler_hack__"] ~args:[("name", `String Name.name)] Catapult.Event_type.M; - (* First event must have category, otherwise Firefox Profiler refuses to open. *) - Catapult.Tracing.emit ~pid:tef_pid "process_name" ~args:[("name", `String Name.name)] Catapult.Event_type.M - ); - enabled := true - - let stop () = - enabled := false - let create_tree name = { name = name; @@ -76,21 +63,35 @@ struct } (** Stack of currently active timing frames. *) - let current: frame Stack.t = - let current = Stack.create () in - Stack.push - { - tree = root; - start_cputime = current_cputime (); - start_walltime = current_walltime (); - start_allocated = current_allocated () - } current; - (* TODO: root frame should actually be created after {!start}, otherwise options are wrong in {!create_frame} *) - (* Stack.push (create_frame root) current; *) - current + let current: frame Stack.t = Stack.create () let reset () = - root.children <- [] (* TODO: reset cputime, etc? *) + (* Reset tree. *) + root.cputime <- 0.0; + root.walltime <- 0.0; + root.allocated <- 0.0; + root.count <- 0; + root.children <- []; + (* Reset frames. *) + if not (Stack.is_empty current) then ( (* If ever started. In case reset before first start. *) + Stack.clear current; + Stack.push (create_frame root) current + ) + + let start options' = + options := options'; + if !options.tef then ( + (* Override TEF process and thread name for track rendering. *) + Catapult.Tracing.emit ~pid:tef_pid "thread_name" ~cat:["__firefox_profiler_hack__"] ~args:[("name", `String Name.name)] Catapult.Event_type.M; + (* First event must have category, otherwise Firefox Profiler refuses to open. *) + Catapult.Tracing.emit ~pid:tef_pid "process_name" ~args:[("name", `String Name.name)] Catapult.Event_type.M + ); + enabled := true; + if Stack.is_empty current then (* If never started. *) + Stack.push (create_frame root) current + + let stop () = + enabled := false let enter ?args name = (* Find the right tree. *) diff --git a/src/util/wideningThresholds.ml b/src/util/wideningThresholds.ml index a1cfd95404..0d93be76ff 100644 --- a/src/util/wideningThresholds.ml +++ b/src/util/wideningThresholds.ml @@ -7,7 +7,6 @@ module Thresholds = Set.Make(Z) (* differentiating between upper and lower bounds, because e.g. expr > 10 is definitely true for an interval [11, x] and definitely false for an interval [x, 10] *) (* apron octagons use thresholds for c in inequalities +/- x +/- y <= c *) let addThreshold t_ref z = t_ref := Thresholds.add z !t_ref -let one = Z.of_int 1 class extractThresholdsFromConditionsVisitor(upper_thresholds,lower_thresholds, octagon_thresholds) = object inherit nopCilVisitor @@ -19,9 +18,9 @@ class extractThresholdsFromConditionsVisitor(upper_thresholds,lower_thresholds, | BinOp (Lt, _, (Const (CInt(i,_,_))), (TInt _)) | BinOp (Gt, (Const (CInt(i,_,_))), _, (TInt _)) -> addThreshold upper_thresholds @@ i; - addThreshold lower_thresholds @@ Z.sub i one; + addThreshold lower_thresholds @@ Z.pred i; - let negI = Z.add one @@ Z.neg i in + let negI = Z.succ @@ Z.neg i in addThreshold octagon_thresholds @@ i; (* upper, just large enough: x + Y <= i *) addThreshold octagon_thresholds @@ negI; (* lower, just small enough: -X -Y <= -i+1 -> X + Y >= i-1 -> X + Y >= i-1 *) addThreshold octagon_thresholds @@ Z.add i i; (* double upper: X + X <= 2i -> X <= i *) @@ -33,11 +32,11 @@ class extractThresholdsFromConditionsVisitor(upper_thresholds,lower_thresholds, | BinOp (Gt, _, (Const (CInt(i,_,_))), (TInt _)) | BinOp (Le, _, (Const (CInt(i,_,_))), (TInt _)) | BinOp (Ge, (Const (CInt(i,_,_))), _, (TInt _)) -> - let i = Z.add i one in (* The same as above with i+1 because for integers expr <= 10 <=> expr < 11 *) + let i = Z.succ i in (* The same as above with i+1 because for integers expr <= 10 <=> expr < 11 *) addThreshold upper_thresholds @@ i; - addThreshold lower_thresholds @@ Z.sub i one; + addThreshold lower_thresholds @@ Z.pred i; - let negI = Z.add one @@ Z.neg i in + let negI = Z.succ @@ Z.neg i in addThreshold octagon_thresholds @@ i; addThreshold octagon_thresholds @@ negI; addThreshold octagon_thresholds @@ Z.add i i; diff --git a/src/util/wideningThresholds.mli b/src/util/wideningThresholds.mli index df58fed65b..69e48695dd 100644 --- a/src/util/wideningThresholds.mli +++ b/src/util/wideningThresholds.mli @@ -1,3 +1,5 @@ +(** Widening threshold utilities. *) + val thresholds : unit -> Z.t list val thresholds_incl_mul2 : unit -> Z.t list val exps: GoblintCil.exp list ResettableLazy.t diff --git a/src/util/wideningTokens.ml b/src/util/wideningTokens.ml index 917b184688..1816de90c7 100644 --- a/src/util/wideningTokens.ml +++ b/src/util/wideningTokens.ml @@ -1,4 +1,5 @@ (** Widening tokens are a generic and dynamic mechanism for delaying widening. + All abstract elements carry a set of tokens, which analyses can add into. Lifted abstract elements are only widened if the token set does not increase, i.e. adding a widening token delays a widening. @@ -50,7 +51,7 @@ let with_local_side_tokens f = with_side_tokens !local_tokens f -open Prelude +open Batteries open Analyses (** Lift {!D} to carry widening tokens. @@ -109,6 +110,11 @@ struct end module C = S.C module V = S.V + module P = + struct + include S.P + let of_elt (x, _) = of_elt x + end let name () = S.name ()^" with widening tokens" @@ -116,8 +122,6 @@ struct let init = S.init let finalize = S.finalize - let should_join (x, _) (y, _) = S.should_join x y - let startstate v = (S.startstate v, TS.bot ()) let exitstate v = (S.exitstate v, TS.bot ()) let morphstate v (d, t) = (S.morphstate v d, t) @@ -152,6 +156,10 @@ struct let lift' d ts = (d, ts) + let paths_as_set ctx = + let liftmap l ts = List.map (fun x -> (x, ts)) l in + lift_fun ctx liftmap S.paths_as_set (Fun.id) + let sync ctx reason = lift_fun ctx lift' S.sync ((|>) reason) let enter ctx r f args = @@ -168,8 +176,10 @@ struct let asm ctx = lift_fun ctx lift' S.asm identity let skip ctx = lift_fun ctx lift' S.skip identity let special ctx r f args = lift_fun ctx lift' S.special ((|>) args % (|>) f % (|>) r) - let combine ctx r fe f args fc es = lift_fun ctx lift' S.combine (fun p -> p r fe f args fc (D.unlift es)) (* TODO: use tokens from es *) + 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/argTools.ml b/src/witness/argTools.ml new file mode 100644 index 0000000000..2d65911a5f --- /dev/null +++ b/src/witness/argTools.ml @@ -0,0 +1,178 @@ +(** Construction of {{!MyARG} ARGs} from constraint system solutions. *) + +open MyCFG + +module M = Messages + +module type BiArg = +sig + include MyARG.S with module Edge = MyARG.InlineEdge + + val prev: Node.t -> (Edge.t * Node.t) list + val iter_nodes: (Node.t -> unit) -> unit + + val query: Node.t -> 'a Queries.t -> 'a Queries.result +end + +module Dot (Arg: BiArg) = +struct + let dot_node_name ppf node = + Format.fprintf ppf "\"%s\"" (Arg.Node.to_string node) + + let dot_edge ppf from_node (edge, to_node) = + Format.fprintf ppf "@,%a -> %a [label=\"%s\"];" dot_node_name from_node dot_node_name to_node (String.escaped (Arg.Edge.to_string edge)) + + let dot_node ppf node = + let shape = match Arg.Node.cfgnode node with + | Statement {skind=If (_,_,_,_,_); _} -> "diamond" + | Statement _ -> "oval" + | Function _ + | FunctionEntry _ -> "box" + in + Format.fprintf ppf "@,%a [shape=%s];" dot_node_name node shape; + List.iter (dot_edge ppf node) (Arg.next node) + + let dot_nodes ppf = + Arg.iter_nodes (dot_node ppf) + + let dot ppf = + Format.fprintf ppf "@[digraph arg {%t@]@,}@\n" dot_nodes +end + +let current_arg: (module BiArg) option ref = ref None + +module Make (R: ResultQuery.SpecSysSol2) = +struct + open R + open SpecSys + + module Query = ResultQuery.Query (SpecSys) + + let get: node * Spec.C.t -> Spec.D.t = + fun nc -> LHT.find_default lh nc (Spec.D.bot ()) + + let ask_indices lvar = + let indices = ref [] in + ignore (ask_local lvar (Queries.IterVars (fun i -> + indices := i :: !indices + ))); + !indices + + module CfgNode = Node + + module Node = + struct + type t = MyCFG.node * Spec.C.t * int + + let equal (n1, c1, i1) (n2, c2, i2) = + EQSys.LVar.equal (n1, c1) (n2, c2) && i1 = i2 + + let compare (n1, c1, i1) (n2, c2, i2) = + let r = EQSys.LVar.compare (n1, c1) (n2, c2) in + if r <> 0 then + r + else + Int.compare i1 i2 + + let hash (n, c, i) = 31 * EQSys.LVar.hash (n, c) + i + + let cfgnode (n, c, i) = n + let context_id (n, c, i) = Spec.C.tag c + let path_id (n, c, i) = i + + let to_string (n, c, i) = + (* copied from NodeCtxStackGraphMlWriter *) + let c_tag = Spec.C.tag c in + let i_str = string_of_int i in + match n with + | Statement stmt -> Printf.sprintf "s%d(%d)[%s]" stmt.sid c_tag i_str + | Function f -> Printf.sprintf "ret%d%s(%d)[%s]" f.svar.vid f.svar.vname c_tag i_str + | FunctionEntry f -> Printf.sprintf "fun%d%s(%d)[%s]" f.svar.vid f.svar.vname c_tag i_str + + (* TODO: less hacky way (without ask_indices) to move node *) + let is_live (n, c, i) = not (Spec.D.is_bot (get (n, c))) + let move_opt (n, c, i) to_n = + match ask_indices (to_n, c) with + | [] -> None + | [to_i] -> + let to_node = (to_n, c, to_i) in + BatOption.filter is_live (Some to_node) + | _ :: _ :: _ -> + failwith "Node.move_opt: ambiguous moved index" + let equal_node_context (n1, c1, i1) (n2, c2, i2) = + EQSys.LVar.equal (n1, c1) (n2, c2) + end + + module NHT = BatHashtbl.Make (Node) + + let create entrystates: (module BiArg with type Node.t = MyCFG.node * Spec.C.t * int) = + let (witness_prev_map, witness_prev, witness_next) = + (* Get all existing vars *) + let vars = NHT.create 100 in + LHT.iter (fun lvar local -> + ask_local lvar ~local (IterVars (fun i -> + let lvar' = (fst lvar, snd lvar, i) in + NHT.replace vars lvar' () + )) + ) lh; + + let prev = NHT.create 100 in + let next = NHT.create 100 in + LHT.iter (fun lvar local -> + ignore (ask_local lvar ~local (Queries.IterPrevVars (fun i (prev_node, prev_c_obj, j) edge -> + let prev_lvar: NHT.key = (prev_node, Obj.obj prev_c_obj, j) in + (* Exclude accumulated prevs, which were pruned *) + if NHT.mem vars prev_lvar then ( + let lvar' = (fst lvar, snd lvar, i) in + if M.tracing then M.trace "witness" "%s -( %a )-> %s\n" (Node.to_string prev_lvar) MyARG.pretty_inline_edge edge (Node.to_string lvar'); + NHT.modify_def [] lvar' (fun prevs -> (edge, prev_lvar) :: prevs) prev; + NHT.modify_def [] prev_lvar (fun nexts -> (edge, lvar') :: nexts) next + ) + ))) + ) lh; + + (prev, + (fun n -> + NHT.find_default prev n []), (* main entry is not in prev at all *) + (fun n -> + NHT.find_default next n [])) (* main return is not in next at all *) + in + let witness_main = + let lvar = WitnessUtil.find_main_entry entrystates in + let main_indices = ask_indices lvar in + (* TODO: get rid of this hack for getting index of entry state *) + assert (List.compare_length_with main_indices 1 = 0); + let main_index = List.hd main_indices in + (fst lvar, snd lvar, main_index) + in + + let module Arg = + struct + module Node = Node + module Edge = MyARG.InlineEdge + let main_entry = witness_main + let next = witness_next + end + in + let module Arg = + struct + open MyARG + module ArgIntra = UnCilTernaryIntra (UnCilLogicIntra (CfgIntra (FileCfg.Cfg))) + include Intra (ArgIntra) (Arg) + + let prev = witness_prev + let iter_nodes f = + f main_entry; + NHT.iter (fun n _ -> + f n + ) witness_prev_map + + let query ((n, c, i): Node.t) q = + R.ask_local (n, c) (PathQuery (i, q)) + end + in + (module Arg: BiArg with type Node.t = MyCFG.node * Spec.C.t * int) + + let create entrystates = + Timing.wrap "arg create" create entrystates +end diff --git a/src/witness/graphml.ml b/src/witness/graphml.ml index f23daf57fd..022cbee8ef 100644 --- a/src/witness/graphml.ml +++ b/src/witness/graphml.ml @@ -1,3 +1,5 @@ +(** Streaming GraphML output. *) + module type GraphMlWriter = sig type t @@ -19,7 +21,7 @@ struct type t = unit BatIO.output type node = string - open Goblintutil + let escape = XmlUtil.escape let start out = let f = BatIO.output_channel out in diff --git a/src/witness/myARG.ml b/src/witness/myARG.ml index a4ffeef5bd..373a66d3d6 100644 --- a/src/witness/myARG.ml +++ b/src/witness/myARG.ml @@ -1,11 +1,16 @@ +(** Abstract reachability graph. *) + open MyCFG open GoblintCil module type Node = sig include Hashtbl.HashedType + include Set.OrderedType with type t := t val cfgnode: t -> MyCFG.node + val context_id: t -> int + val path_id: t -> int val to_string: t -> string val move_opt: t -> MyCFG.node -> t option @@ -25,24 +30,62 @@ struct type t = edge let embed e = e - let to_string e = Pretty.sprint ~width:80 (Edge.pretty_plain () e) + let to_string e = GobPretty.sprint Edge.pretty_plain e end type inline_edge = | CFGEdge of Edge.t - | InlineEntry of CilType.Exp.t list - | InlineReturn of CilType.Lval.t option -[@@deriving eq, ord, hash, to_yojson] + | InlineEntry of CilType.Lval.t option * CilType.Fundec.t * CilType.Exp.t list + | InlineReturn of CilType.Lval.t option * CilType.Fundec.t * CilType.Exp.t list + | InlinedEdge of Edge.t + | ThreadEntry of CilType.Lval.t option * CilType.Varinfo.t * CilType.Exp.t list +[@@deriving eq, ord, hash] let pretty_inline_edge () = function | CFGEdge e -> Edge.pretty_plain () e - | InlineEntry args -> Pretty.dprintf "InlineEntry '(%a)'" (Pretty.d_list ", " Cil.d_exp) args - | InlineReturn None -> Pretty.dprintf "InlineReturn" - | InlineReturn (Some ret) -> Pretty.dprintf "InlineReturn '%a'" Cil.d_lval ret + | InlineEntry (_, _, args) -> Pretty.dprintf "InlineEntry '(%a)'" (Pretty.d_list ", " Cil.d_exp) args + | InlineReturn (None, _, _) -> Pretty.dprintf "InlineReturn" + | InlineReturn (Some ret, _, _) -> Pretty.dprintf "InlineReturn '%a'" Cil.d_lval ret + | InlinedEdge e -> Pretty.dprintf "Inlined %a" Edge.pretty_plain e + | ThreadEntry (_, _, args) -> Pretty.dprintf "ThreadEntry '(%a)'" (Pretty.d_list ", " Cil.d_exp) args + +let inline_edge_to_yojson = function + | CFGEdge e -> + `Assoc [ + ("cfg", Edge.to_yojson e) + ] + | InlineEntry (lval, function_, args) -> + `Assoc [ + ("entry", `Assoc [ + ("lval", [%to_yojson: CilType.Lval.t option] lval); + ("function", CilType.Fundec.to_yojson function_); + ("args", [%to_yojson: CilType.Exp.t list] args); + ]); + ] + | InlineReturn (lval, function_, args) -> + `Assoc [ + ("return", `Assoc [ + ("lval", [%to_yojson: CilType.Lval.t option] lval); + ("function", CilType.Fundec.to_yojson function_); + ("args", [%to_yojson: CilType.Exp.t list] args); + ]); + ] + | InlinedEdge e -> + `Assoc [ + ("inlined", Edge.to_yojson e) + ] + | ThreadEntry (lval, function_, args) -> + `Assoc [ + ("thread", `Assoc [ + ("lval", [%to_yojson: CilType.Lval.t option] lval); + ("function", CilType.Varinfo.to_yojson function_); + ("args", [%to_yojson: CilType.Exp.t list] args); + ]); + ] module InlineEdgePrintable: Printable.S with type t = inline_edge = struct - include Printable.Std + include Printable.StdLeaf type t = inline_edge [@@deriving eq, ord, hash, to_yojson] let name () = "inline edge" @@ -78,9 +121,11 @@ end module StackNode (Node: Node): Node with type t = Node.t list = struct - type t = Node.t list [@@deriving eq, hash] + type t = Node.t list [@@deriving eq, ord, hash] let cfgnode nl = Node.cfgnode (List.hd nl) + let context_id nl = Node.context_id (List.hd nl) + let path_id nl = Node.path_id (List.hd nl) let to_string nl = nl |> List.map Node.to_string @@ -96,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) @@ -111,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 @@ -275,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? *) @@ -328,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 ec39fd9020..e8daf56155 100644 --- a/src/witness/observerAnalysis.ml +++ b/src/witness/observerAnalysis.ml @@ -1,4 +1,6 @@ -open Prelude.Ana +(** Path-sensitive analysis using an {!ObserverAutomaton}. *) + +open GoblintCil open Analyses open MyCFG @@ -29,8 +31,7 @@ struct end module D = Lattice.Flat (Printable.Chain (ChainParams)) (Printable.DefaultNames) module C = D - - let should_join x y = D.equal x y (* fully path-sensitive *) + module P = IdentityP (D) (* fully path-sensitive *) let step d prev_node node = match d with @@ -65,15 +66,18 @@ struct (* ctx.local doesn't matter here? *) [ctx.local, step ctx.local ctx.prev_node (FunctionEntry f)] - let combine ctx (lval:lval option) fexp (f:fundec) (args:exp list) fc (au:D.t) : D.t = - step au (Function f) ctx.node + let combine_env ctx lval fexp f args fc au f_ask = + ctx.local (* Don't yet consider call edge done before assign. *) + + let combine_assign ctx (lval:lval option) fexp (f:fundec) (args:exp list) fc (au:D.t) (f_ask: Queries.ask) : D.t = + step au (Function f) ctx.node (* Consider call edge done after entire call-assign. *) let special ctx (lval: lval option) (f:varinfo) (arglist:exp list) : D.t = 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/observerAutomaton.ml b/src/witness/observerAutomaton.ml index 4556bbe449..a5205b2b98 100644 --- a/src/witness/observerAutomaton.ml +++ b/src/witness/observerAutomaton.ml @@ -1,5 +1,4 @@ -open Prelude.Ana - +(** Finite automaton for matching an infeasible ARG path. *) module type S = sig diff --git a/src/witness/svcomp.ml b/src/witness/svcomp.ml index d5fdac4859..89487ea8d4 100644 --- a/src/witness/svcomp.ml +++ b/src/witness/svcomp.ml @@ -1,3 +1,5 @@ +(** SV-COMP tasks and results. *) + open GoblintCil open Batteries @@ -6,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 @@ -14,11 +16,16 @@ end let task: (module Task) option ref = ref None +let is_error_function' f spec = + let module Task = (val (Option.get !task)) in + 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 = @@ -26,11 +33,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 @@ -50,6 +53,10 @@ struct | UnreachCall _ -> "unreach-call" | NoOverflow -> "no-overflow" | NoDataRace -> "no-data-race" (* not yet in SV-COMP/Benchexec *) + | ValidFree -> "valid-free" + | ValidDeref -> "valid-deref" + | ValidMemtrack -> "valid-memtrack" + | ValidMemcleanup -> "valid-memcleanup" in "false(" ^ result_spec ^ ")" | Unknown -> "unknown" @@ -69,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 4f846f282d..66b3b83ac8 100644 --- a/src/witness/svcompSpec.ml +++ b/src/witness/svcompSpec.ml @@ -1,14 +1,23 @@ +(** SV-COMP specification strings and files. *) + open Batteries type t = | UnreachCall of string | NoDataRace | NoOverflow + | 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 + 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 @@ -21,9 +30,30 @@ 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 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 @@ -36,9 +66,22 @@ 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 print_output spec_str is_neg = + if is_neg then + Printf.sprintf "CHECK( init(main()), LTL(G ! %s) )" spec_str + else + Printf.sprintf "CHECK( init(main()), LTL(G %s) )" spec_str + in + let spec_str, is_neg = match spec with + | UnreachCall f -> "call(" ^ f ^ "())", true + | NoDataRace -> "data-race", true + | NoOverflow -> "overflow", true + | ValidFree -> "valid-free", false + | ValidDeref -> "valid-deref", false + | ValidMemtrack -> "valid-memtrack", false + | ValidMemcleanup -> "valid-memcleanup", false in - "CHECK( init(main()), LTL(G ! " ^ global_not ^ ") )" + print_output spec_str is_neg + +let to_string spec = + String.concat "\n" (List.map to_string spec) diff --git a/src/witness/timeUtil.ml b/src/witness/timeUtil.ml index d3d779dc92..e1736f4fca 100644 --- a/src/witness/timeUtil.ml +++ b/src/witness/timeUtil.ml @@ -1,3 +1,5 @@ +(** Date and time utilities. *) + open Unix let iso8601_of_tm {tm_year; tm_mon; tm_mday; tm_hour; tm_min; tm_sec; _} = @@ -5,3 +7,18 @@ let iso8601_of_tm {tm_year; tm_mon; tm_mday; tm_hour; tm_min; tm_sec; _} = Printf.sprintf "%04u-%02u-%02uT%02u:%02u:%02uZ" (1900 + tm_year) (tm_mon + 1) tm_mday tm_hour tm_min tm_sec let iso8601_now () = iso8601_of_tm (gmtime (time ())) + +let seconds_of_duration_string = + let unit = function + | "" | "s" -> 1 + | "m" -> 60 + | "h" -> 60 * 60 + | s -> invalid_arg ("Unkown duration unit " ^ s ^ ". Supported units are h, m, s.") + in + let int_rest f s = Scanf.sscanf s "%u%s" f in + let split s = BatString.(head s 1, tail s 1) in + let rec f i s = + let u, r = split s in (* unit, rest *) + i * (unit u) + if r = "" then 0 else int_rest f r + in + int_rest f diff --git a/src/witness/violation.ml b/src/witness/violation.ml index 51952bb3c9..d48005a988 100644 --- a/src/witness/violation.ml +++ b/src/witness/violation.ml @@ -1,3 +1,5 @@ +(** Violation checking in an ARG. *) + module type ViolationArg = sig include MyARG.S with module Edge = MyARG.InlineEdge @@ -94,8 +96,14 @@ let find_path (type node) (module Arg:ViolationArg with type Node.t = node) (mod else if not (NHT.mem itered_nodes node) then begin NHT.replace itered_nodes node (); List.iter (fun (edge, prev_node) -> - if not (NHT.mem itered_nodes prev_node) then - NHT.replace next_nodes prev_node (edge, node) + match edge with + | MyARG.CFGEdge _ + | InlineEntry _ + | InlineReturn _ -> + if not (NHT.mem itered_nodes prev_node) then + NHT.replace next_nodes prev_node (edge, node) + | InlinedEdge _ + | ThreadEntry _ -> () ) (Arg.prev node); bfs curs' (List.map snd (Arg.prev node) @ nexts) end diff --git a/src/witness/witness.ml b/src/witness/witness.ml index 937f83473f..235461c348 100644 --- a/src/witness/witness.ml +++ b/src/witness/witness.ml @@ -1,16 +1,20 @@ +(** Output of ARG as GraphML. *) + open MyCFG open Graphml open Svcomp open GobConfig +module M = Messages + module type WitnessTaskResult = TaskResult with module Arg.Edge = MyARG.InlineEdge let write_file filename (module Task:Task) (module TaskResult:WitnessTaskResult): unit = 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) ) @@ -20,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 *) @@ -54,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 @@ -114,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; @@ -147,7 +151,7 @@ let write_file filename (module Task:Task) (module TaskResult:WitnessTaskResult) end; (* begin match cfgnode with | Statement s -> - [("sourcecode", Pretty.sprint 80 (Basetype.CilStmt.pretty () s))] (* TODO: sourcecode not official? especially on node? *) + [("sourcecode", GobPretty.sprint Basetype.CilStmt.pretty s)] (* TODO: sourcecode not official? especially on node? *) | _ -> [] end; *) (* violation actually only allowed in violation witness *) @@ -225,19 +229,38 @@ let write_file filename (module Task:Task) (module TaskResult:WitnessTaskResult) NH.add itered_nodes node (); write_node node; let is_sink = TaskResult.is_violation node || TaskResult.is_sink node in + if M.tracing then M.tracei "witness" "iter_node %s\n" (N.to_string node); if not is_sink then begin let edge_to_nodes = Arg.next node (* TODO: keep control (Test) edges to dead (sink) nodes for violation witness? *) + |> List.filter_map (fun ((edge, to_node) as edge_to_node) -> + match edge with + | MyARG.CFGEdge _ -> + Some edge_to_node + | InlineEntry (_, f, args) -> + Some (InlineEntry (None, f, args), to_node) (* remove lval to avoid duplicate edges in witness *) + | InlineReturn (lval, f, _) -> + Some (InlineReturn (lval, f, []), to_node) (* remove args to avoid duplicate edges in witness *) + | InlinedEdge _ + | ThreadEntry _ -> + None + ) + (* deduplicate after removed lvals/args *) + |> BatList.unique_cmp ~cmp:[%ord: MyARG.inline_edge * N.t] in List.iter (fun (edge, to_node) -> + if M.tracing then M.tracec "witness" "edge %a to_node %s\n" MyARG.pretty_inline_edge edge (N.to_string to_node); write_node to_node; write_edge node edge to_node ) edge_to_nodes; + if M.tracing then M.traceu "witness" "iter_node %s\n" (N.to_string node); List.iter (fun (edge, to_node) -> iter_node to_node ) edge_to_nodes end + else + if M.tracing then M.traceu "witness" "iter_node %s\n" (N.to_string node); end in @@ -271,107 +294,51 @@ struct open Svcomp module Query = ResultQuery.Query (SpecSys) + module ArgTool = ArgTools.Make (R) + module NHT = ArgTool.NHT - let determine_result entrystates (module Task:Task): (module WitnessTaskResult) = - let get: node * Spec.C.t -> Spec.D.t = - fun nc -> LHT.find_default lh nc (Spec.D.bot ()) - in - let ask_indices lvar = - let indices = ref [] in - ignore (ask_local lvar (Queries.IterVars (fun i -> - indices := i :: !indices - ))); - !indices - in - - let module CfgNode = Node in - - let module Node = - struct - type t = MyCFG.node * Spec.C.t * int - - let equal (n1, c1, i1) (n2, c2, i2) = - EQSys.LVar.equal (n1, c1) (n2, c2) && i1 = i2 - - let hash (n, c, i) = 31 * EQSys.LVar.hash (n, c) + i - - let cfgnode (n, c, i) = n - - let to_string (n, c, i) = - (* copied from NodeCtxStackGraphMlWriter *) - let c_tag = Spec.C.tag c in - let i_str = string_of_int i in - match n with - | Statement stmt -> Printf.sprintf "s%d(%d)[%s]" stmt.sid c_tag i_str - | Function f -> Printf.sprintf "ret%d%s(%d)[%s]" f.svar.vid f.svar.vname c_tag i_str - | FunctionEntry f -> Printf.sprintf "fun%d%s(%d)[%s]" f.svar.vid f.svar.vname c_tag i_str - - (* TODO: less hacky way (without ask_indices) to move node *) - let is_live (n, c, i) = not (Spec.D.is_bot (get (n, c))) - let move_opt (n, c, i) to_n = - match ask_indices (to_n, c) with - | [] -> None - | [to_i] -> - let to_node = (to_n, c, to_i) in - BatOption.filter is_live (Some to_node) - | _ :: _ :: _ -> - failwith "Node.move_opt: ambiguous moved index" - let equal_node_context (n1, c1, i1) (n2, c2, i2) = - EQSys.LVar.equal (n1, c1) (n2, c2) - end - in - - let module NHT = BatHashtbl.Make (Node) in - - let (witness_prev_map, witness_prev, witness_next) = - let prev = NHT.create 100 in - let next = NHT.create 100 in - LHT.iter (fun lvar local -> - ignore (ask_local lvar ~local (Queries.IterPrevVars (fun i (prev_node, prev_c_obj, j) edge -> - let lvar' = (fst lvar, snd lvar, i) in - let prev_lvar: NHT.key = (prev_node, Obj.obj prev_c_obj, j) in - NHT.modify_def [] lvar' (fun prevs -> (edge, prev_lvar) :: prevs) prev; - NHT.modify_def [] prev_lvar (fun nexts -> (edge, lvar') :: nexts) next - ))) - ) lh; - - (prev, - (fun n -> - NHT.find_default prev n []), (* main entry is not in prev at all *) - (fun n -> - NHT.find_default next n [])) (* main return is not in next at all *) - in - let witness_main = - let lvar = WitnessUtil.find_main_entry entrystates in - let main_indices = ask_indices lvar in - (* TODO: get rid of this hack for getting index of entry state *) - assert (List.compare_length_with main_indices 1 = 0); - let main_index = List.hd main_indices in - (fst lvar, snd lvar, main_index) - in - - let module Arg = - struct - module Node = Node - module Edge = MyARG.InlineEdge - let main_entry = witness_main - let next = witness_next - end - in - let module Arg = - struct - open MyARG - module ArgIntra = UnCilTernaryIntra (UnCilLogicIntra (CfgIntra (FileCfg.Cfg))) - include Intra (ArgIntra) (Arg) - end - 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 = @@ -390,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 @@ -398,27 +365,27 @@ 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 = - NHT.fold (fun lvar _ acc -> + (* TODO: fold_nodes?s *) + let acc = ref [] in + Arg.iter_nodes (fun lvar -> if is_violation lvar then - lvar :: acc - else - acc - ) witness_prev_map [] + acc := lvar :: !acc + ); + !acc in let module ViolationArg = struct include Arg - let prev = witness_prev let violations = violations end in @@ -429,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 @@ -443,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 @@ -515,12 +482,12 @@ struct let next _ = [] end in - if not !Goblintutil.svcomp_may_overflow then + if not !AnalysisState.svcomp_may_overflow then let module TaskResult = 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 @@ -538,22 +505,163 @@ 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 = - match !Goblintutil.verified with + match !AnalysisState.verified with | Some false -> print_svcomp_result "ERROR (verify)" | _ -> if get_string "witness.yaml.validate" <> "" then ( @@ -566,7 +674,4 @@ struct ) 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 d5f1f96e92..8dedf77a79 100644 --- a/src/witness/witnessConstraints.ml +++ b/src/witness/witnessConstraints.ml @@ -1,6 +1,6 @@ -(** An analysis specification for witnesses. *) +(** Analysis specification transformation for ARG construction. *) -open Prelude.Ana +open Batteries open Analyses @@ -40,19 +40,20 @@ struct let narrow x y = y end - module SpecDMap (R: Lattice.S) = + module SpecDMap (V: Lattice.S) = struct - module C = + module R = struct + include Spec.P type elt = Spec.D.t - let cong = Spec.should_join end - module J = MapDomain.Joined (Spec.D) (R) - include DisjointDomain.PairwiseMap (Spec.D) (R) (J) (C) + module J = MapDomain.Joined (Spec.D) (V) + include DisjointDomain.ProjectiveMap (Spec.D) (V) (J) (R) end module Dom = struct + module V = R include SpecDMap (R) let name () = "PathSensitive (" ^ name () ^ ")" @@ -60,7 +61,7 @@ struct let printXml f x = let print_one x r = (* BatPrintf.fprintf f "\n%a" Spec.D.printXml x *) - BatPrintf.fprintf f "\n%a%a" Spec.D.printXml x R.printXml r + BatPrintf.fprintf f "\n%a%a" Spec.D.printXml x V.printXml r in iter print_one x @@ -94,6 +95,7 @@ struct module G = Spec.G module C = Spec.C module V = Spec.V + module P = UnitP let name () = "PathSensitive3("^Spec.name ()^")" @@ -101,8 +103,6 @@ struct let init = Spec.init let finalize = Spec.finalize - let should_join x y = true - let exitstate v = (Dom.singleton (Spec.exitstate v) (R.bot ()), Sync.bot ()) let startstate v = (Dom.singleton (Spec.startstate v) (R.bot ()), Sync.bot ()) let morphstate v (d, _) = (Dom.map_keys (Spec.morphstate v) d, Sync.bot ()) @@ -113,14 +113,6 @@ struct else Spec.context fd @@ Dom.choose_key l - let conv ctx x = - (* TODO: R.bot () isn't right here *) - let rec ctx' = { ctx with ask = (fun (type a) (q: a Queries.t) -> Spec.query ctx' q) - ; local = x - ; split = (ctx.split % (fun x -> (Dom.singleton x (R.bot ()), Sync.bot ()))) } - in - ctx' - let step n c i e = R.singleton ((n, c, i), e) let step n c i e sync = match Sync.find i sync with @@ -137,6 +129,22 @@ struct with Ctx_failure _ -> R.bot () let step_ctx_edge ctx x = step_ctx ctx x (CFGEdge ctx.edge) + let step_ctx_inlined_edge ctx x = step_ctx ctx x (InlinedEdge ctx.edge) + + let nosync x = Sync.singleton x (SyncSet.singleton x) + + let conv ctx x = + let rec ctx' = + { ctx with + local = x; + ask = (fun (type a) (q: a Queries.t) -> Spec.query ctx' q); + split; + } + and split y es = + let yr = step_ctx_edge ctx x in + ctx.split (Dom.singleton y yr, Sync.bot ()) es + in + ctx' let map ctx f g = (* we now use Sync for every tf such that threadspawn after tf could look up state before tf *) @@ -149,6 +157,19 @@ struct let d = Dom.fold_keys h (fst ctx.local) (Dom.empty (), Sync.bot ()) in if Dom.is_bot (fst d) then raise Deadcode else d + (* TODO???? *) + let map_event ctx e = + (* we now use Sync for every tf such that threadspawn after tf could look up state before tf *) + let h x (xs, sync) = + try + let x' = Spec.event (conv ctx x) e (conv ctx x) in + (Dom.add x' (step_ctx_edge ctx x) xs, Sync.add x' (SyncSet.singleton x) sync) + with Deadcode -> (xs, sync) + in + let d = Dom.fold_keys h (fst ctx.local) (Dom.empty (), Sync.bot ()) in + if Dom.is_bot (fst d) then raise Deadcode else d + + let fold' ctx f g h a = let k x a = try h a x @@ g @@ f @@ conv ctx x @@ -171,23 +192,26 @@ struct let asm ctx = map ctx Spec.asm identity let skip ctx = map ctx Spec.skip identity let special ctx l f a = map ctx Spec.special (fun h -> h l f a) + let event ctx e octx = map_event ctx e (* TODO: ???? *) - (* TODO: do additional witness things here *) - let threadenter ctx lval f args = + let paths_as_set ctx = + let (a,b) = ctx.local in + let r = Dom.bindings a in + List.map (fun (x,v) -> (Dom.singleton x v, b)) r + + let threadenter ctx ~multiple lval f args = let g xs x' ys = let ys' = List.map (fun y -> - (* R.bot () isn't right here? doesn't actually matter? *) - let yr = R.bot () in - (* keep left syncs so combine gets them for no-inline case *) + let yr = step ctx.prev_node (ctx.context ()) x' (ThreadEntry (lval, f, args)) (nosync x') in (* threadenter called on before-sync state *) (Dom.singleton y yr, Sync.bot ()) ) ys 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' -> @@ -197,10 +221,16 @@ struct let query ctx (type a) (q: a Queries.t): a Queries.result = match q with | Queries.IterPrevVars f -> + if M.tracing then M.tracei "witness" "IterPrevVars\n"; Dom.iter (fun x r -> + if M.tracing then M.tracei "witness" "x = %a\n" Spec.D.pretty x; R.iter (function ((n, c, j), e) -> + if M.tracing then M.tracec "witness" "n = %a\n" Node.pretty_plain n; + if M.tracing then M.tracec "witness" "c = %a\n" Spec.C.pretty c; + if M.tracing then M.tracec "witness" "j = %a\n" Spec.D.pretty j; f (I.to_int x) (n, Obj.repr c, I.to_int j) e - ) r + ) r; + if M.tracing then M.traceu "witness" "\n" ) (fst ctx.local); (* check that sync mappings don't leak into solution (except Function) *) (* TODO: disabled because we now use and leave Sync for every tf, @@ -209,12 +239,18 @@ struct | Function _ -> () (* returns post-sync in FromSpec *) | _ -> assert (Sync.is_bot (snd ctx.local)); end; *) + if M.tracing then M.traceu "witness" "\n"; () | Queries.IterVars f -> Dom.iter (fun x r -> f (I.to_int x) ) (fst ctx.local); () + | Queries.PathQuery (i, q) -> + (* TODO: optimize indexing, using inner hashcons somehow? *) + (* let (d, _) = List.at (S.elements s) i in *) + let (d, _) = List.find (fun (x, _) -> I.to_int x = i) (Dom.bindings (fst ctx.local)) in + Spec.query (conv ctx d) q | Queries.Invariant ({path=Some i; _} as c) -> (* TODO: optimize indexing, using inner hashcons somehow? *) (* let (d, _) = List.at (S.elements s) i in *) @@ -237,7 +273,7 @@ struct (* R.bot () isn't right here? doesn't actually matter? *) let yr = if should_inline f then - step_ctx ctx x' (InlineEntry a) + step_ctx ctx x' (InlineEntry (l, f, a)) else R.bot () in @@ -249,20 +285,35 @@ struct in fold' ctx Spec.enter (fun h -> h l f a) g [] - let combine ctx l fe f a fc d = + let combine_env ctx l fe f a fc d f_ask = + (* Don't yet consider call edge done before assign. *) + assert (Dom.cardinal (fst ctx.local) = 1); + let (cd, cdr) = Dom.choose (fst ctx.local) in + let k x (y, sync) = + try + let x' = Spec.combine_env (conv ctx cd) l fe f a fc x f_ask in + (Dom.add x' cdr y, Sync.add x' (Sync.find cd (snd ctx.local)) sync) (* keep predecessors and sync from ctx, sync required for step_ctx_inlined_edge in combine_assign *) + with Deadcode -> (y, sync) + in + let d = Dom.fold_keys k (fst d) (Dom.bot (), Sync.bot ()) in + if Dom.is_bot (fst d) then raise Deadcode else d + + let combine_assign ctx l fe f a fc d f_ask = + (* Consider call edge done after entire call-assign. *) assert (Dom.cardinal (fst ctx.local) = 1); let cd = Dom.choose_key (fst ctx.local) in let k x (y, sync) = let r = if should_inline f then - let nosync = (Sync.singleton x (SyncSet.singleton x)) in (* returns already post-sync in FromSpec *) - step (Function f) (Option.get fc) x (InlineReturn l) nosync (* fc should be Some outside of MCP *) + let returnr = step (Function f) (Option.get fc) x (InlineReturn (l, f, a)) (nosync x) in (* fc should be Some outside of MCP *) + let procr = step_ctx_inlined_edge ctx cd in + R.join procr returnr else step_ctx_edge ctx cd in try - let x' = Spec.combine (conv ctx cd) l fe f a fc x in + let x' = Spec.combine_assign (conv ctx cd) l fe f a fc x f_ask in (Dom.add x' r y, Sync.add x' (SyncSet.singleton x) sync) with Deadcode -> (y, sync) in diff --git a/src/witness/witnessUtil.ml b/src/witness/witnessUtil.ml index 0664e56584..12bc598be5 100644 --- a/src/witness/witnessUtil.ml +++ b/src/witness/witnessUtil.ml @@ -1,3 +1,5 @@ +(** Utilities for witness generation and witness invariants. *) + open MyCFG open GoblintCil @@ -114,32 +116,62 @@ end module InvariantParser = struct type t = { + genv: (string, Cabs2cil.envdata * Cil.location) Hashtbl.t; global_vars: Cil.varinfo list; } + module VarinfoH = Cilfacade.VarinfoH + let create (file: Cil.file): t = - let global_vars = List.filter_map (function - | Cil.GVar (v, _, _) - | Cil.GFun ({svar=v; _}, _) -> Some v - | _ -> None - ) file.globals - in - {global_vars} + (* Reconstruct genv from CIL file instead of using genvironment, + because genvironment contains data from all versions of the file + and incremental update doesn't remove the excess. *) + let genv = Hashtbl.create (Hashtbl.length Cabs2cil.genvironment) in + let global_vars = VarinfoH.create 113 in (* Deduplicates varinfos from declarations and definitions. *) + Cil.iterGlobals file (function + | Cil.GType ({tname; _} as t, loc) -> + let name = "type " ^ tname in + Hashtbl.replace genv name (Cabs2cil.EnvTyp (TNamed (t, [])), loc) + | Cil.GCompTag ({cstruct; cname; _} as c, loc) + | Cil.GCompTagDecl ({cstruct; cname; _} as c, loc) -> + let name = (if cstruct then "struct" else "union") ^ " " ^ cname in + Hashtbl.replace genv name (Cabs2cil.EnvTyp (TComp (c, [])), loc) + | Cil.GEnumTag ({ename; eitems; _} as e, loc) + | Cil.GEnumTagDecl ({ename; eitems; _} as e, loc) -> + let typ = TEnum (e, []) in + let name = "enum " ^ ename in + Hashtbl.replace genv name (Cabs2cil.EnvTyp typ, loc); + List.iter (fun (name, exp, loc) -> + Hashtbl.replace genv name (Cabs2cil.EnvEnum (exp, typ), loc) + ) eitems + | Cil.GVar (v, _, loc) + | Cil.GVarDecl (v, loc) + | Cil.GFun ({svar=v; _}, loc) -> + Hashtbl.replace genv v.vname (Cabs2cil.EnvVar v, loc); + VarinfoH.replace global_vars v () + | _ -> () + ); + let global_vars = List.of_seq (VarinfoH.to_seq_keys global_vars) in + {genv; global_vars} let parse_cabs (inv: string): (Cabs.expression, string) result = + Errormsg.hadErrors := false; (* reset because CIL doesn't *) match Timing.wrap "FrontC" Frontc.parse_standalone_exp inv with - | inv_cabs -> Ok inv_cabs + | _ when !Errormsg.hadErrors -> + Error "hadErrors" + | inv_cabs -> + Ok inv_cabs | exception (Frontc.ParseError e) -> Errormsg.log "\n"; (* CIL prints garbage without \n before *) Error e - let parse_cil {global_vars} ~(fundec: Cil.fundec) ~loc (inv_cabs: Cabs.expression): (Cil.exp, string) result = - let genv = Cabs2cil.genvironment in + let parse_cil {genv; global_vars} ?(check=true) ~(fundec: Cil.fundec) ~loc (inv_cabs: Cabs.expression): (Cil.exp, string) result = let env = Hashtbl.copy genv in List.iter (fun (v: Cil.varinfo) -> Hashtbl.replace env v.vname (Cabs2cil.EnvVar v, v.vdecl) ) (fundec.sformals @ fundec.slocals); + Errormsg.hadErrors := false; (* reset because CIL doesn't *) let inv_exp_opt = Cil.currentLoc := loc; Cil.currentExpLoc := loc; @@ -157,7 +189,9 @@ struct let vars = fundec.sformals @ fundec.slocals @ global_vars in match inv_exp_opt with - | Some inv_exp when Check.checkStandaloneExp ~vars inv_exp -> + | _ when !Errormsg.hadErrors -> + Error "hadErrors" + | Some inv_exp when not check || Check.checkStandaloneExp ~vars inv_exp -> Ok inv_exp | _ -> Error "parse_cil" diff --git a/src/witness/yamlWitness.ml b/src/witness/yamlWitness.ml index 3f5354d488..9e8ebeff51 100644 --- a/src/witness/yamlWitness.ml +++ b/src/witness/yamlWitness.ml @@ -1,3 +1,5 @@ +(** YAML witness generation and validation. *) + open Analyses open GoblintCil @@ -15,8 +17,8 @@ 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; - command_line = Goblintutil.command_line; + version = Goblint_build_info.version; + command_line = Some GobSys.command_line; } let metadata ?task (): Metadata.t = @@ -169,15 +171,15 @@ struct if GobConfig.get_bool "witness.invariant.accessed" then ( match R.ask_local_node n ~local MayAccessed with | `Top -> - CilLval.Set.top () + Lval.Set.top () | (`Lifted _) as es -> let lvals = AccessDomain.EventSet.fold (fun e lvals -> match e with | {var_opt = Some var; offs_opt = Some offs; kind = Write} -> - CilLval.Set.add (Var var, offs) lvals + Lval.Set.add (Var var, offs) lvals | _ -> lvals - ) es (CilLval.Set.empty ()) + ) es (Lval.Set.empty ()) in let lvals = FileCfg.Cfg.next n @@ -190,7 +192,7 @@ struct |> fun es -> AccessDomain.EventSet.fold (fun e lvals -> match e with | {var_opt = Some var; offs_opt = Some offs; kind = Read} -> - CilLval.Set.add (Var var, offs) lvals + Lval.Set.add (Var var, offs) lvals | _ -> lvals ) es lvals @@ -198,7 +200,7 @@ struct lvals ) else - CilLval.Set.top () + Lval.Set.top () in let entries = [] in @@ -357,7 +359,7 @@ struct | None | Some [] -> acc | Some (x::xs) -> - begin match List.fold_left (fun acc inv -> Invariant.(acc || inv)) x xs with + begin match List.fold_left (fun acc inv -> Invariant.(acc || inv) [@coverage off]) x xs with (* bisect_ppx cannot handle redefined (||) *) | `Lifted inv -> let invs = WitnessUtil.InvariantExp.process_exp inv in let c_inv = InvariantCil.exp_replace_original_name c_inv in (* cannot be split *) @@ -390,6 +392,9 @@ struct ]; yaml_entries_to_file yaml_entries (Fpath.v (GobConfig.get_string "witness.yaml.path")) + + let write () = + Timing.wrap "yaml witness" write () end @@ -447,7 +452,12 @@ struct let loop_locator = Locator.create () in LHT.iter (fun ((n, _) as lvar) _ -> let loc = Node.location n in - if not loc.synthetic then + (* TODO: filter synthetic? + + Almost all loops are transformed by CIL, so the loop constructs all get synthetic locations. Filtering them from the locator could give some odd behavior: if the location is right before the loop and all the synthetic loop head stuff is filtered, then the first non-synthetic node is already inside the loop, not outside where the location actually was. + Similarly, if synthetic locations are then filtered, witness.invariant.loop-head becomes essentially useless. + I guess at some point during testing and benchmarking I achieved better results with the filtering removed. *) + if WitnessInvariant.is_invariant_node n then Locator.add locator loc lvar; if WitnessUtil.NH.mem WitnessInvariant.loop_heads n then Locator.add loop_locator loc lvar diff --git a/src/witness/yamlWitnessType.ml b/src/witness/yamlWitnessType.ml index aad206d0fc..3390c1e3ab 100644 --- a/src/witness/yamlWitnessType.ml +++ b/src/witness/yamlWitnessType.ml @@ -1,25 +1,32 @@ +(** YAML witness format types. *) + module Producer = struct type t = { name: string; version: string; (* TODO: configuration *) - command_line: string; + command_line: string option; (* TODO: description *) } let to_yaml {name; version; command_line} = - `O [ - ("name", `String name); - ("version", `String version); - ("command_line", `String command_line); - ] + `O ([ + ("name", `String name); + ("version", `String version); + ] @ match command_line with + | Some command_line -> [ + ("command_line", `String command_line); + ] + | None -> + [] + ) let of_yaml y = let open GobYaml in let+ name = y |> find "name" >>= to_string and+ version = y |> find "version" >>= to_string - and+ command_line = y |> find "command_line" >>= to_string in + and+ command_line = y |> Yaml.Util.find "command_line" >>= option_map to_string in {name; version; command_line} end diff --git a/src/witness/z3/violationZ3.no-z3.ml b/src/witness/z3/violationZ3.no-z3.ml index 0c61eb3b29..0771f6862d 100644 --- a/src/witness/z3/violationZ3.no-z3.ml +++ b/src/witness/z3/violationZ3.no-z3.ml @@ -1 +1,3 @@ +(** ARG path feasibility checking using weakest precondition and {!Z3} ({b not installed!}). *) + module WP = Violation.UnknownFeasibility (* default to always unknown if no Z3 installed *) diff --git a/src/witness/z3/violationZ3.z3.ml b/src/witness/z3/violationZ3.z3.ml index 6f7995cb64..a93684bdb3 100644 --- a/src/witness/z3/violationZ3.z3.ml +++ b/src/witness/z3/violationZ3.z3.ml @@ -1,3 +1,5 @@ +(** ARG path feasibility checking using weakest precondition and {!Z3}. *) + open Violation module WP (Node: MyARG.Node): Feasibility with module Node = Node = @@ -76,10 +78,10 @@ struct | UnOp (LNot, e, TInt _) -> bool_to_int (Boolean.mk_not ctx (int_to_bool (exp_to_expr env e))) | e -> - failwith @@ Pretty.sprint ~width:80 @@ Pretty.dprintf "exp_to_expr: %a" Cil.d_exp e + failwith @@ GobPretty.sprintf "exp_to_expr: %a" Cil.d_exp e - let get_arg_vname i = Goblintutil.create_var (Cil.makeVarinfo false ("_arg" ^ string_of_int i) Cil.intType) (* TODO: correct type in general *) - let return_vname = Goblintutil.create_var (Cil.makeVarinfo false "_return" Cil.intType) (* TODO: correct type in general *) + let get_arg_vname i = Cilfacade.create_var (Cil.makeVarinfo false ("_arg" ^ string_of_int i) Cil.intType) (* TODO: correct type in general *) + let return_vname = Cilfacade.create_var (Cil.makeVarinfo false "_return" Cil.intType) (* TODO: correct type in general *) let wp_assert env (from_node, (edge: MyARG.inline_edge), _) = match edge with | MyARG.CFGEdge (MyCFG.Assign ((Var v, NoOffset), e)) -> @@ -100,7 +102,7 @@ struct ) fd.sformals in (env', eqs) - | MyARG.InlineEntry args -> + | MyARG.InlineEntry (_, _, args) -> let env' = BatList.fold_lefti (fun acc i arg -> let arg_vname = get_arg_vname i in Env.freshen acc arg_vname @@ -117,14 +119,14 @@ struct | MyARG.CFGEdge (MyCFG.Ret (Some e, fd)) -> let env' = Env.freshen env return_vname in (env', [Boolean.mk_eq ctx (Env.get_const env return_vname) (exp_to_expr env' e)]) - | MyARG.InlineReturn None -> + | MyARG.InlineReturn (None, _, _) -> (env, []) - | MyARG.InlineReturn (Some (Var v, NoOffset)) -> + | MyARG.InlineReturn (Some (Var v, NoOffset), _, _) -> let env' = Env.freshen env v in (env', [Boolean.mk_eq ctx (Env.get_const env v) (Env.get_const env' return_vname)]) | _ -> (* (env, Boolean.mk_true ctx) *) - failwith @@ Pretty.sprint ~width:80 @@ Pretty.dprintf "wp_assert: %a" MyARG.pretty_inline_edge edge + failwith @@ GobPretty.sprintf "wp_assert: %a" MyARG.pretty_inline_edge edge let const_get_symbol (expr: Expr.expr): Symbol.symbol = assert (Expr.is_const expr); diff --git a/sv-comp/my-bench-sv-comp/yaml/goblint-bench-validate.xml b/sv-comp/my-bench-sv-comp/yaml/goblint-bench-validate.xml deleted file mode 100644 index dfb24a2ae3..0000000000 --- a/sv-comp/my-bench-sv-comp/yaml/goblint-bench-validate.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - evals = (\d+) - - - - - - - - - - - - - RESULTSDIR/LOGDIR/${rundefinition_name}/${taskdef_name}/witness.yml - - - - /mnt/goblint-svcomp/goblint-bench/bench/Pthread.set - - - diff --git a/sv-comp/my-bench-sv-comp/yaml/goblint-bench.sh b/sv-comp/my-bench-sv-comp/yaml/goblint-bench.sh deleted file mode 100755 index ca37845ced..0000000000 --- a/sv-comp/my-bench-sv-comp/yaml/goblint-bench.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/bash - -shopt -s extglob - -MYBENCHDIR=/mnt/goblint-svcomp/benchexec/my-bench-sv-comp/yaml -RESULTSDIR=/mnt/goblint-svcomp/benchexec/results/yaml-xy-bench -GOBLINTPARALLEL=14 -VALIDATEPARALLEL=14 - -mkdir $RESULTSDIR - -# Run verification -cd /mnt/goblint-svcomp/sv-comp/goblint -# read-only and overlay dirs for Value too large for defined data type workaround -benchexec --read-only-dir / --overlay-dir . --hidden-dir /home --outputpath $RESULTSDIR --numOfThreads $GOBLINTPARALLEL $MYBENCHDIR/goblint-bench.xml - -# Extract witness directory -cd $RESULTSDIR -LOGDIR=`echo goblint-bench.*.files` -echo $LOGDIR - -# Construct validation XMLs -cd $MYBENCHDIR -sed -e "s|RESULTSDIR|$RESULTSDIR|" -e "s/LOGDIR/$LOGDIR/" goblint-bench-validate.xml > goblint-bench-validate-tmp.xml - -# Run validation -cd /mnt/goblint-svcomp/sv-comp/goblint -benchexec --read-only-dir / --overlay-dir . --hidden-dir /home --outputpath $RESULTSDIR --numOfThreads $VALIDATEPARALLEL $MYBENCHDIR/goblint-bench-validate-tmp.xml - -# Merge witness validation results -cd $RESULTSDIR -# python3 /mnt/goblint-svcomp/benchexec/benchexec/contrib/mergeBenchmarkSets.py -o . goblint.*.results.*.xml.bz2 goblint-validate-tmp.*.results.*.xml.bz2 -python3 /mnt/goblint-svcomp/benchexec/benchexec/contrib/mergeBenchmarkSets.py -o . goblint-bench.*.results.all.Pthread.xml.bz2 goblint-bench-validate-tmp.*.results.all.Pthread.xml.bz2 -python3 /mnt/goblint-svcomp/benchexec/benchexec/contrib/mergeBenchmarkSets.py -o . goblint-bench.*.results.loop-head.Pthread.xml.bz2 goblint-bench-validate-tmp.*.results.loop-head.Pthread.xml.bz2 - -# Generate table with merged results and witness validation results -sed -e "s/LOGDIR/$LOGDIR/" $MYBENCHDIR/table-generator-bench.xml > table-generator.xml -table-generator -x table-generator.xml - -# Decompress all tool outputs for table HTML links -unzip -o goblint-bench.*.logfiles.zip -unzip -o goblint-bench-validate-tmp.*.logfiles.zip diff --git a/sv-comp/my-bench-sv-comp/yaml/goblint-bench.xml b/sv-comp/my-bench-sv-comp/yaml/goblint-bench.xml deleted file mode 100644 index 547d67b8cd..0000000000 --- a/sv-comp/my-bench-sv-comp/yaml/goblint-bench.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - **.yml - - - - - - evals = (\d+) - - - - - - - - - - - - - - /mnt/goblint-svcomp/goblint-bench/bench/Pthread.set - - - diff --git a/sv-comp/my-bench-sv-comp/yaml/goblint-validate.xml b/sv-comp/my-bench-sv-comp/yaml/goblint-validate.xml deleted file mode 100644 index 5a3cac3065..0000000000 --- a/sv-comp/my-bench-sv-comp/yaml/goblint-validate.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - evals = (\d+) - - - - - - - - - - - - - RESULTSDIR/LOGDIR/${rundefinition_name}/${taskdef_name}/witness.yml - - - - /mnt/goblint-svcomp/benchexec/sv-benchmarks/c/ReachSafety-Loops-Simple.set - /mnt/goblint-svcomp/benchexec/sv-benchmarks/c/properties/unreach-call.prp - - - - - diff --git a/sv-comp/my-bench-sv-comp/yaml/goblint.sh b/sv-comp/my-bench-sv-comp/yaml/goblint.sh deleted file mode 100755 index ffbfa9f1a2..0000000000 --- a/sv-comp/my-bench-sv-comp/yaml/goblint.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/bash - -shopt -s extglob - -MYBENCHDIR=/mnt/goblint-svcomp/benchexec/my-bench-sv-comp/yaml -RESULTSDIR=/mnt/goblint-svcomp/benchexec/results/yaml-xy -GOBLINTPARALLEL=14 -VALIDATEPARALLEL=14 - -mkdir $RESULTSDIR - -# Run verification -cd /mnt/goblint-svcomp/sv-comp/goblint -# read-only and overlay dirs for Value too large for defined data type workaround -benchexec --read-only-dir / --overlay-dir . --hidden-dir /home --outputpath $RESULTSDIR --numOfThreads $GOBLINTPARALLEL $MYBENCHDIR/goblint.xml - -# Extract witness directory -cd $RESULTSDIR -LOGDIR=`echo goblint.*.files` -echo $LOGDIR - -# Construct validation XMLs -cd $MYBENCHDIR -sed -e "s|RESULTSDIR|$RESULTSDIR|" -e "s/LOGDIR/$LOGDIR/" goblint-validate.xml > goblint-validate-tmp.xml - -# Run validation -cd /mnt/goblint-svcomp/sv-comp/goblint -benchexec --read-only-dir / --overlay-dir . --hidden-dir /home --outputpath $RESULTSDIR --numOfThreads $VALIDATEPARALLEL $MYBENCHDIR/goblint-validate-tmp.xml - -# Merge witness validation results -cd $RESULTSDIR -# python3 /mnt/goblint-svcomp/benchexec/benchexec/contrib/mergeBenchmarkSets.py -o . goblint.*.results.*.xml.bz2 goblint-validate-tmp.*.results.*.xml.bz2 -python3 /mnt/goblint-svcomp/benchexec/benchexec/contrib/mergeBenchmarkSets.py -o . goblint.*.results.all.ReachSafety-Loops-Simple.xml.bz2 goblint-validate-tmp.*.results.all.ReachSafety-Loops-Simple.xml.bz2 -python3 /mnt/goblint-svcomp/benchexec/benchexec/contrib/mergeBenchmarkSets.py -o . goblint.*.results.loop-head.ReachSafety-Loops-Simple.xml.bz2 goblint-validate-tmp.*.results.loop-head.ReachSafety-Loops-Simple.xml.bz2 - -# Generate table with merged results and witness validation results -sed -e "s/LOGDIR/$LOGDIR/" $MYBENCHDIR/table-generator.xml > table-generator.xml -table-generator -x table-generator.xml - -# Decompress all tool outputs for table HTML links -unzip -o goblint.*.logfiles.zip -unzip -o goblint-validate-tmp.*.logfiles.zip diff --git a/sv-comp/my-bench-sv-comp/yaml/goblint.xml b/sv-comp/my-bench-sv-comp/yaml/goblint.xml deleted file mode 100644 index 5ca9a58d06..0000000000 --- a/sv-comp/my-bench-sv-comp/yaml/goblint.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - **.yml - - - - - - evals = (\d+) - - - - - - - - - - - - - - /mnt/goblint-svcomp/benchexec/sv-benchmarks/c/ReachSafety-Loops-Simple.set - /mnt/goblint-svcomp/benchexec/sv-benchmarks/c/properties/unreach-call.prp - - - - - diff --git a/sv-comp/my-bench-sv-comp/yaml/table-generator-bench.xml b/sv-comp/my-bench-sv-comp/yaml/table-generator-bench.xml deleted file mode 100644 index 4bf2676eef..0000000000 --- a/sv-comp/my-bench-sv-comp/yaml/table-generator-bench.xml +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - - witness - total generation entries: (\d+) - - - - solving +([0-9.]+) ?s - vars = (\d+) - evals = (\d+) - live: (\d+) - - - - - - - - solving +([0-9.]+) ?s - vars = (\d+) - evals = (\d+) - live: (\d+) - - - - - - witness - total generation entries: (\d+) - - - - solving +([0-9.]+) ?s - vars = (\d+) - evals = (\d+) - live: (\d+) - - - - - - - - solving +([0-9.]+) ?s - vars = (\d+) - evals = (\d+) - live: (\d+) - - -
diff --git a/sv-comp/my-bench-sv-comp/yaml/table-generator.xml b/sv-comp/my-bench-sv-comp/yaml/table-generator.xml deleted file mode 100644 index 790fac39b0..0000000000 --- a/sv-comp/my-bench-sv-comp/yaml/table-generator.xml +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - - witness - total generation entries: (\d+) - - - - solving +([0-9.]+) ?s - vars = (\d+) - evals = (\d+) - live: (\d+) - - - - - - - - solving +([0-9.]+) ?s - vars = (\d+) - evals = (\d+) - live: (\d+) - - - - - - witness - total generation entries: (\d+) - - - - solving +([0-9.]+) ?s - vars = (\d+) - evals = (\d+) - live: (\d+) - - - - - - - - solving +([0-9.]+) ?s - vars = (\d+) - evals = (\d+) - live: (\d+) - - -
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/incremental/00-basic/09-unreach.json b/tests/incremental/00-basic/09-unreach.json index c1e5e17542..6b4665a772 100644 --- a/tests/incremental/00-basic/09-unreach.json +++ b/tests/incremental/00-basic/09-unreach.json @@ -1,5 +1,5 @@ { - "dbg": { + "warn": { "debug": true }, "incremental" : { diff --git a/tests/incremental/00-basic/10-reach.json b/tests/incremental/00-basic/10-reach.json index c1e5e17542..6b4665a772 100644 --- a/tests/incremental/00-basic/10-reach.json +++ b/tests/incremental/00-basic/10-reach.json @@ -1,5 +1,5 @@ { - "dbg": { + "warn": { "debug": true }, "incremental" : { diff --git a/tests/incremental/00-basic/11-unreach-reusesuper.json b/tests/incremental/00-basic/11-unreach-reusesuper.json index ef6bdab239..c0f0363135 100644 --- a/tests/incremental/00-basic/11-unreach-reusesuper.json +++ b/tests/incremental/00-basic/11-unreach-reusesuper.json @@ -1,5 +1,5 @@ { - "dbg": { + "warn": { "debug": true }, "incremental" : { diff --git a/tests/incremental/04-var-rename/02-rename_and_shuffle.c b/tests/incremental/04-var-rename/01-rename_and_shuffle.c similarity index 58% rename from tests/incremental/04-var-rename/02-rename_and_shuffle.c rename to tests/incremental/04-var-rename/01-rename_and_shuffle.c index 9917738055..7d6ea81e6f 100644 --- a/tests/incremental/04-var-rename/02-rename_and_shuffle.c +++ b/tests/incremental/04-var-rename/01-rename_and_shuffle.c @@ -1,6 +1,6 @@ #include -//a is renamed to c, but the usage of a is replaced by b +// a is renamed to c, but the usage of a is replaced by b (semantic changes) int main() { int a = 0; int b = 1; diff --git a/tests/incremental/04-var-rename/01-unused_rename.json b/tests/incremental/04-var-rename/01-rename_and_shuffle.json similarity index 100% rename from tests/incremental/04-var-rename/01-unused_rename.json rename to tests/incremental/04-var-rename/01-rename_and_shuffle.json diff --git a/tests/incremental/04-var-rename/01-rename_and_shuffle.patch b/tests/incremental/04-var-rename/01-rename_and_shuffle.patch new file mode 100644 index 0000000000..94e27d9a80 --- /dev/null +++ b/tests/incremental/04-var-rename/01-rename_and_shuffle.patch @@ -0,0 +1,15 @@ +--- tests/incremental/04-var-rename/01-rename_and_shuffle.c ++++ tests/incremental/04-var-rename/01-rename_and_shuffle.c +@@ -2,10 +2,10 @@ + + // a is renamed to c, but the usage of a is replaced by b (semantic changes) + int main() { +- int a = 0; ++ int c = 0; + int b = 1; + +- printf("Print %d", a); ++ printf("Print %d", b); + + return 0; + } diff --git a/tests/incremental/04-var-rename/01-rename_and_shuffle.t b/tests/incremental/04-var-rename/01-rename_and_shuffle.t new file mode 100644 index 0000000000..8f3b57f797 --- /dev/null +++ b/tests/incremental/04-var-rename/01-rename_and_shuffle.t @@ -0,0 +1,14 @@ +Run Goblint on initial program version + + $ goblint --conf 01-rename_and_shuffle.json --enable incremental.save 01-rename_and_shuffle.c > /dev/null 2>&1 + +Apply patch + + $ chmod +w 01-rename_and_shuffle.c + $ patch -b <01-rename_and_shuffle.patch + patching file 01-rename_and_shuffle.c + +Run Goblint incrementally on new program version and check the change detection result + + $ goblint --conf 01-rename_and_shuffle.json --enable incremental.load 01-rename_and_shuffle.c | grep 'change_info' | sed -r 's/^change_info = \{ unchanged = [[:digit:]]+; (.*) \}$/\1/' + changed = 1 (with unchangedHeader = 1); added = 0; removed = 0 diff --git a/tests/incremental/04-var-rename/01-unused_rename.c b/tests/incremental/04-var-rename/01-unused_rename.c deleted file mode 100644 index 31eacd5bf9..0000000000 --- a/tests/incremental/04-var-rename/01-unused_rename.c +++ /dev/null @@ -1,4 +0,0 @@ -int main() { - int a = 0; - return 0; -} diff --git a/tests/incremental/04-var-rename/01-unused_rename.patch b/tests/incremental/04-var-rename/01-unused_rename.patch deleted file mode 100644 index 977470ad53..0000000000 --- a/tests/incremental/04-var-rename/01-unused_rename.patch +++ /dev/null @@ -1,8 +0,0 @@ ---- tests/incremental/04-var-rename/01-unused_rename.c -+++ tests/incremental/04-var-rename/01-unused_rename.c -@@ -1,4 +1,4 @@ - int main() { -- int a = 0; -+ int b = 0; - return 0; - } diff --git a/tests/incremental/04-var-rename/01-unused_rename.txt b/tests/incremental/04-var-rename/01-unused_rename.txt deleted file mode 100644 index a317916ad1..0000000000 --- a/tests/incremental/04-var-rename/01-unused_rename.txt +++ /dev/null @@ -1,3 +0,0 @@ -local variable a is renamed to b. -a/b is not used. -No semantic changes. diff --git a/tests/incremental/04-var-rename/02-rename_and_shuffle.patch b/tests/incremental/04-var-rename/02-rename_and_shuffle.patch deleted file mode 100644 index 5c1dc4785e..0000000000 --- a/tests/incremental/04-var-rename/02-rename_and_shuffle.patch +++ /dev/null @@ -1,15 +0,0 @@ ---- tests/incremental/04-var-rename/02-rename_and_shuffle.c -+++ tests/incremental/04-var-rename/02-rename_and_shuffle.c -@@ -2,10 +2,10 @@ - - //a is renamed to c, but the usage of a is replaced by b - int main() { -- int a = 0; -+ int c = 0; - int b = 1; - -- printf("Print %d", a); -+ printf("Print %d", b); - - return 0; - } diff --git a/tests/incremental/04-var-rename/02-rename_and_shuffle.txt b/tests/incremental/04-var-rename/02-rename_and_shuffle.txt deleted file mode 100644 index 8c0ab5ac05..0000000000 --- a/tests/incremental/04-var-rename/02-rename_and_shuffle.txt +++ /dev/null @@ -1,2 +0,0 @@ -a is renamed to c, but the usage of a is replaced by b. -Semantic changes. diff --git a/tests/incremental/04-var-rename/03-rename_with_usage.c b/tests/incremental/04-var-rename/02-rename_with_usage.c similarity index 100% rename from tests/incremental/04-var-rename/03-rename_with_usage.c rename to tests/incremental/04-var-rename/02-rename_with_usage.c diff --git a/tests/incremental/04-var-rename/02-rename_and_shuffle.json b/tests/incremental/04-var-rename/02-rename_with_usage.json similarity index 100% rename from tests/incremental/04-var-rename/02-rename_and_shuffle.json rename to tests/incremental/04-var-rename/02-rename_with_usage.json diff --git a/tests/incremental/04-var-rename/03-rename_with_usage.patch b/tests/incremental/04-var-rename/02-rename_with_usage.patch similarity index 62% rename from tests/incremental/04-var-rename/03-rename_with_usage.patch rename to tests/incremental/04-var-rename/02-rename_with_usage.patch index 26fb98b340..6cfe41bbb1 100644 --- a/tests/incremental/04-var-rename/03-rename_with_usage.patch +++ b/tests/incremental/04-var-rename/02-rename_with_usage.patch @@ -1,15 +1,15 @@ ---- tests/incremental/04-var-rename/03-rename_with_usage.c -+++ tests/incremental/04-var-rename/03-rename_with_usage.c +--- tests/incremental/04-var-rename/02-rename_with_usage.c ++++ tests/incremental/04-var-rename/02-rename_with_usage.c @@ -2,10 +2,10 @@ - + //a is renamed to c, but its usages stay the same int main() { - int a = 0; + int c = 0; int b = 1; - + - printf("Print %d", a); + printf("Print %d", c); - + return 0; } diff --git a/tests/incremental/04-var-rename/02-rename_with_usage.t b/tests/incremental/04-var-rename/02-rename_with_usage.t new file mode 100644 index 0000000000..1e2818ed4d --- /dev/null +++ b/tests/incremental/04-var-rename/02-rename_with_usage.t @@ -0,0 +1,14 @@ +Run Goblint on initial program version + + $ goblint --conf 02-rename_with_usage.json --enable incremental.save 02-rename_with_usage.c > /dev/null 2>&1 + +Apply patch + + $ chmod +w 02-rename_with_usage.c + $ patch -b <02-rename_with_usage.patch + patching file 02-rename_with_usage.c + +Run Goblint incrementally on new program version and check the change detection result + + $ goblint --conf 02-rename_with_usage.json --enable incremental.load 02-rename_with_usage.c | grep 'change_info' | sed -r 's/^change_info = \{ unchanged = [[:digit:]]+; (.*) \}$/\1/' + changed = 0 (with unchangedHeader = 0); added = 0; removed = 0 diff --git a/tests/incremental/04-var-rename/03-rename_with_usage.txt b/tests/incremental/04-var-rename/03-rename_with_usage.txt deleted file mode 100644 index 18ff7e94d4..0000000000 --- a/tests/incremental/04-var-rename/03-rename_with_usage.txt +++ /dev/null @@ -1,2 +0,0 @@ -a is renamed to c, but the usage stays the same. -No semantic changes. diff --git a/tests/incremental/04-var-rename/04-renamed_assert.c b/tests/incremental/04-var-rename/03-renamed_assert.c similarity index 62% rename from tests/incremental/04-var-rename/04-renamed_assert.c rename to tests/incremental/04-var-rename/03-renamed_assert.c index 665e44251c..b9f484ba01 100644 --- a/tests/incremental/04-var-rename/04-renamed_assert.c +++ b/tests/incremental/04-var-rename/03-renamed_assert.c @@ -1,9 +1,10 @@ #include +// local var used in assert is renamed (no semantic changes) int main() { int myVar = 0; __goblint_check(myVar < 11); return 0; -} \ No newline at end of file +} diff --git a/tests/incremental/04-var-rename/03-rename_with_usage.json b/tests/incremental/04-var-rename/03-renamed_assert.json similarity index 100% rename from tests/incremental/04-var-rename/03-rename_with_usage.json rename to tests/incremental/04-var-rename/03-renamed_assert.json diff --git a/tests/incremental/04-var-rename/04-renamed_assert.patch b/tests/incremental/04-var-rename/03-renamed_assert.patch similarity index 64% rename from tests/incremental/04-var-rename/04-renamed_assert.patch rename to tests/incremental/04-var-rename/03-renamed_assert.patch index d7dfe6ae8e..c672e68044 100644 --- a/tests/incremental/04-var-rename/04-renamed_assert.patch +++ b/tests/incremental/04-var-rename/03-renamed_assert.patch @@ -1,5 +1,5 @@ ---- tests/incremental/04-var-rename/04-renamed_assert.c -+++ tests/incremental/04-var-rename/04-renamed_assert.c +--- tests/incremental/04-var-rename/03-renamed_assert.c ++++ tests/incremental/04-var-rename/03-renamed_assert.c @@ -1,7 +1,7 @@ int main() { - int myVar = 0; diff --git a/tests/incremental/04-var-rename/04-renamed_assert.txt b/tests/incremental/04-var-rename/04-renamed_assert.txt deleted file mode 100644 index 1afc289347..0000000000 --- a/tests/incremental/04-var-rename/04-renamed_assert.txt +++ /dev/null @@ -1,2 +0,0 @@ -local var used in assert is renamed. -No semantic changes. diff --git a/tests/incremental/04-var-rename/05-renamed_param.c b/tests/incremental/04-var-rename/04-renamed_param.c similarity index 60% rename from tests/incremental/04-var-rename/05-renamed_param.c rename to tests/incremental/04-var-rename/04-renamed_param.c index 72fdfaf0e9..770af2683c 100644 --- a/tests/incremental/04-var-rename/05-renamed_param.c +++ b/tests/incremental/04-var-rename/04-renamed_param.c @@ -1,3 +1,4 @@ +// function param is renamed (no semantic changes) void method(int a) { int c = a; } @@ -5,4 +6,4 @@ void method(int a) { int main() { method(0); return 0; -} \ No newline at end of file +} diff --git a/tests/incremental/04-var-rename/04-renamed_assert.json b/tests/incremental/04-var-rename/04-renamed_param.json similarity index 100% rename from tests/incremental/04-var-rename/04-renamed_assert.json rename to tests/incremental/04-var-rename/04-renamed_param.json diff --git a/tests/incremental/04-var-rename/04-renamed_param.patch b/tests/incremental/04-var-rename/04-renamed_param.patch new file mode 100644 index 0000000000..50a9b69f6a --- /dev/null +++ b/tests/incremental/04-var-rename/04-renamed_param.patch @@ -0,0 +1,10 @@ +--- tests/incremental/04-var-rename/04-renamed_param.c ++++ tests/incremental/04-var-rename/04-renamed_param.c +@@ -2,5 +2,5 @@ +-void method(int a) { +- int c = a; ++void method(int b) { ++ int c = b; + } + + int main() { diff --git a/tests/incremental/04-var-rename/04-renamed_param.t b/tests/incremental/04-var-rename/04-renamed_param.t new file mode 100644 index 0000000000..9da6d5e888 --- /dev/null +++ b/tests/incremental/04-var-rename/04-renamed_param.t @@ -0,0 +1,14 @@ +Run Goblint on initial program version + + $ goblint --conf 04-renamed_param.json --enable incremental.save 04-renamed_param.c > /dev/null 2>&1 + +Apply patch + + $ chmod +w 04-renamed_param.c + $ patch -b <04-renamed_param.patch + patching file 04-renamed_param.c + +Run Goblint incrementally on new program version and check the change detection result + + $ goblint --conf 04-renamed_param.json --enable incremental.load 04-renamed_param.c | grep 'change_info' | sed -r 's/^change_info = \{ unchanged = [[:digit:]]+; (.*) \}$/\1/' + changed = 0 (with unchangedHeader = 0); added = 0; removed = 0 diff --git a/tests/incremental/04-var-rename/05-renamed_param.patch b/tests/incremental/04-var-rename/05-renamed_param.patch deleted file mode 100644 index 944566b05c..0000000000 --- a/tests/incremental/04-var-rename/05-renamed_param.patch +++ /dev/null @@ -1,10 +0,0 @@ ---- tests/incremental/04-var-rename/05-renamed_param.c -+++ tests/incremental/04-var-rename/05-renamed_param.c -@@ -1,5 +1,5 @@ --void method(int a) { -- int c = a; -+void method(int b) { -+ int c = b; - } - - int main() { diff --git a/tests/incremental/04-var-rename/05-renamed_param.txt b/tests/incremental/04-var-rename/05-renamed_param.txt deleted file mode 100644 index 09bca47979..0000000000 --- a/tests/incremental/04-var-rename/05-renamed_param.txt +++ /dev/null @@ -1,2 +0,0 @@ -Function param is renamed. -No semantic changes. diff --git a/tests/incremental/04-var-rename/06-renamed_param_usage_changed.c b/tests/incremental/04-var-rename/05-renamed_param_usage_changed.c similarity index 100% rename from tests/incremental/04-var-rename/06-renamed_param_usage_changed.c rename to tests/incremental/04-var-rename/05-renamed_param_usage_changed.c diff --git a/tests/incremental/04-var-rename/05-renamed_param.json b/tests/incremental/04-var-rename/05-renamed_param_usage_changed.json similarity index 100% rename from tests/incremental/04-var-rename/05-renamed_param.json rename to tests/incremental/04-var-rename/05-renamed_param_usage_changed.json diff --git a/tests/incremental/04-var-rename/06-renamed_param_usage_changed.patch b/tests/incremental/04-var-rename/05-renamed_param_usage_changed.patch similarity index 55% rename from tests/incremental/04-var-rename/06-renamed_param_usage_changed.patch rename to tests/incremental/04-var-rename/05-renamed_param_usage_changed.patch index a93e45c4c5..9ffc2c1cea 100644 --- a/tests/incremental/04-var-rename/06-renamed_param_usage_changed.patch +++ b/tests/incremental/04-var-rename/05-renamed_param_usage_changed.patch @@ -1,8 +1,8 @@ ---- tests/incremental/04-var-rename/06-renamed_param_usage_changed.c -+++ tests/incremental/04-var-rename/06-renamed_param_usage_changed.c +--- tests/incremental/04-var-rename/05-renamed_param_usage_changed.c ++++ tests/incremental/04-var-rename/05-renamed_param_usage_changed.c @@ -1,6 +1,6 @@ //This test should mark foo and main as changed - + -void foo(int a, int b) { +void foo(int b, int a) { int x = a; diff --git a/tests/incremental/04-var-rename/05-renamed_param_usage_changed.t b/tests/incremental/04-var-rename/05-renamed_param_usage_changed.t new file mode 100644 index 0000000000..a465b2b6f2 --- /dev/null +++ b/tests/incremental/04-var-rename/05-renamed_param_usage_changed.t @@ -0,0 +1,14 @@ +Run Goblint on initial program version + + $ goblint --conf 05-renamed_param_usage_changed.json --enable incremental.save 05-renamed_param_usage_changed.c > /dev/null 2>&1 + +Apply patch + + $ chmod +w 05-renamed_param_usage_changed.c + $ patch -b <05-renamed_param_usage_changed.patch + patching file 05-renamed_param_usage_changed.c + +Run Goblint incrementally on new program version and check the change detection result + + $ goblint --conf 05-renamed_param_usage_changed.json --enable incremental.load 05-renamed_param_usage_changed.c | grep 'change_info' | sed -r 's/^change_info = \{ unchanged = [[:digit:]]+; (.*) \}$/\1/' + changed = 1 (with unchangedHeader = 1); added = 0; removed = 0 diff --git a/tests/incremental/04-var-rename/06-renamed_param_usage_changed.json b/tests/incremental/04-var-rename/06-renamed_param_usage_changed.json deleted file mode 100644 index 544b7b4ddd..0000000000 --- a/tests/incremental/04-var-rename/06-renamed_param_usage_changed.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - -} \ No newline at end of file diff --git a/tests/incremental/04-var-rename/06-renamed_param_usage_changed.txt b/tests/incremental/04-var-rename/06-renamed_param_usage_changed.txt deleted file mode 100644 index 0dc90594c7..0000000000 --- a/tests/incremental/04-var-rename/06-renamed_param_usage_changed.txt +++ /dev/null @@ -1,2 +0,0 @@ -function parameters a and b and swapped in the function header. But the function body stays the same. -Semantic changes. diff --git a/tests/incremental/04-var-rename/dune b/tests/incremental/04-var-rename/dune new file mode 100644 index 0000000000..1b37756f98 --- /dev/null +++ b/tests/incremental/04-var-rename/dune @@ -0,0 +1,2 @@ +(cram + (deps (glob_files *.{c,json,patch}) (sandbox preserve_file_kind))) diff --git a/tests/incremental/05-method-rename/00-simple_rename.c b/tests/incremental/05-method-rename/00-simple_rename.c new file mode 100644 index 0000000000..5d1e6fe872 --- /dev/null +++ b/tests/incremental/05-method-rename/00-simple_rename.c @@ -0,0 +1,10 @@ +#include + +void foo() { + printf("foo"); +} + +int main() { + foo(); + return 0; +} diff --git a/tests/incremental/05-method-rename/00-simple_rename.json b/tests/incremental/05-method-rename/00-simple_rename.json new file mode 100644 index 0000000000..0db3279e44 --- /dev/null +++ b/tests/incremental/05-method-rename/00-simple_rename.json @@ -0,0 +1,3 @@ +{ + +} diff --git a/tests/incremental/05-method-rename/00-simple_rename.patch b/tests/incremental/05-method-rename/00-simple_rename.patch new file mode 100644 index 0000000000..ed7b40014c --- /dev/null +++ b/tests/incremental/05-method-rename/00-simple_rename.patch @@ -0,0 +1,15 @@ +--- tests/incremental/05-method-rename/00-simple_rename.c ++++ tests/incremental/05-method-rename/00-simple_rename.c +@@ -1,10 +1,10 @@ + #include + +-void foo() { ++void bar() { + printf("foo"); + } + + int main() { +- foo(); ++ bar(); + return 0; + } diff --git a/tests/incremental/05-method-rename/00-simple_rename.t b/tests/incremental/05-method-rename/00-simple_rename.t new file mode 100644 index 0000000000..1855b903eb --- /dev/null +++ b/tests/incremental/05-method-rename/00-simple_rename.t @@ -0,0 +1,14 @@ +Run Goblint on initial program version + + $ goblint --conf 00-simple_rename.json --enable incremental.save 00-simple_rename.c > /dev/null 2>&1 + +Apply patch + + $ chmod +w 00-simple_rename.c + $ patch -b <00-simple_rename.patch + patching file 00-simple_rename.c + +Run Goblint incrementally on new program version and check the change detection result + + $ goblint --conf 00-simple_rename.json --enable incremental.load 00-simple_rename.c | grep 'change_info' | sed -r 's/^change_info = \{ unchanged = [[:digit:]]+; (.*) \}$/\1/' + changed = 0 (with unchangedHeader = 0); added = 0; removed = 0 diff --git a/tests/incremental/05-method-rename/01-dependent_rename.c b/tests/incremental/05-method-rename/01-dependent_rename.c new file mode 100644 index 0000000000..66c1a5a634 --- /dev/null +++ b/tests/incremental/05-method-rename/01-dependent_rename.c @@ -0,0 +1,14 @@ +#include + +void fun1() { + printf("fun1"); +} + +void fun2() { + fun1(); +} + +int main() { + fun2(); + return 0; +} diff --git a/tests/incremental/05-method-rename/01-dependent_rename.json b/tests/incremental/05-method-rename/01-dependent_rename.json new file mode 100644 index 0000000000..0db3279e44 --- /dev/null +++ b/tests/incremental/05-method-rename/01-dependent_rename.json @@ -0,0 +1,3 @@ +{ + +} diff --git a/tests/incremental/05-method-rename/01-dependent_rename.patch b/tests/incremental/05-method-rename/01-dependent_rename.patch new file mode 100644 index 0000000000..f3a4a9a3f8 --- /dev/null +++ b/tests/incremental/05-method-rename/01-dependent_rename.patch @@ -0,0 +1,21 @@ +--- tests/incremental/05-method-rename/01-dependent_rename.c ++++ tests/incremental/05-method-rename/01-dependent_rename.c +@@ -1,14 +1,14 @@ + #include + +-void fun1() { ++void bar1() { + printf("fun1"); + } + +-void fun2() { +- fun1(); ++void bar2() { ++ bar1(); + } + + int main() { +- fun2(); ++ bar2(); + return 0; + } diff --git a/tests/incremental/05-method-rename/01-dependent_rename.t b/tests/incremental/05-method-rename/01-dependent_rename.t new file mode 100644 index 0000000000..bb0628447b --- /dev/null +++ b/tests/incremental/05-method-rename/01-dependent_rename.t @@ -0,0 +1,14 @@ +Run Goblint on initial program version + + $ goblint --conf 01-dependent_rename.json --enable incremental.save 01-dependent_rename.c > /dev/null 2>&1 + +Apply patch + + $ chmod +w 01-dependent_rename.c + $ patch -b <01-dependent_rename.patch + patching file 01-dependent_rename.c + +Run Goblint incrementally on new program version and check the change detection result + + $ goblint --conf 01-dependent_rename.json --enable incremental.load 01-dependent_rename.c | grep 'change_info' | sed -r 's/^change_info = \{ unchanged = [[:digit:]]+; (.*) \}$/\1/' + changed = 1 (with unchangedHeader = 1); added = 2; removed = 2 diff --git a/tests/incremental/05-method-rename/02-cyclic_rename_dependency.c b/tests/incremental/05-method-rename/02-cyclic_rename_dependency.c new file mode 100644 index 0000000000..331a5e25cb --- /dev/null +++ b/tests/incremental/05-method-rename/02-cyclic_rename_dependency.c @@ -0,0 +1,15 @@ +#include + +void foo1(int c) { + if (c < 10) foo2(c + 1); +} + +void foo2(int c) { + if (c < 10) foo1(c + 1); +} + +int main() { + foo1(0); + foo2(0); + return 0; +} diff --git a/tests/incremental/05-method-rename/02-cyclic_rename_dependency.json b/tests/incremental/05-method-rename/02-cyclic_rename_dependency.json new file mode 100644 index 0000000000..0db3279e44 --- /dev/null +++ b/tests/incremental/05-method-rename/02-cyclic_rename_dependency.json @@ -0,0 +1,3 @@ +{ + +} diff --git a/tests/incremental/05-method-rename/02-cyclic_rename_dependency.patch b/tests/incremental/05-method-rename/02-cyclic_rename_dependency.patch new file mode 100644 index 0000000000..7f15d88c3a --- /dev/null +++ b/tests/incremental/05-method-rename/02-cyclic_rename_dependency.patch @@ -0,0 +1,23 @@ +--- tests/incremental/05-method-rename/02-cyclic_rename_dependency.c ++++ tests/incremental/05-method-rename/02-cyclic_rename_dependency.c +@@ -2,14 +2,14 @@ + +-void foo1(int c) { +- if (c < 10) foo2(c + 1); ++void bar1(int c) { ++ if (c < 10) bar2(c + 1); + } + +-void foo2(int c) { +- if (c < 10) foo1(c + 1); ++void bar2(int c) { ++ if (c < 10) bar1(c + 1); + } + + int main() { +- foo1(0); +- foo2(0); ++ bar1(0); ++ bar2(0); + return 0; + } diff --git a/tests/incremental/05-method-rename/02-cyclic_rename_dependency.t b/tests/incremental/05-method-rename/02-cyclic_rename_dependency.t new file mode 100644 index 0000000000..de9aa48e6c --- /dev/null +++ b/tests/incremental/05-method-rename/02-cyclic_rename_dependency.t @@ -0,0 +1,14 @@ +Run Goblint on initial program version + + $ goblint --conf 02-cyclic_rename_dependency.json --enable incremental.save 02-cyclic_rename_dependency.c > /dev/null 2>&1 + +Apply patch + + $ chmod +w 02-cyclic_rename_dependency.c + $ patch -b <02-cyclic_rename_dependency.patch + patching file 02-cyclic_rename_dependency.c + +Run Goblint incrementally on new program version and check the change detection result + + $ goblint --conf 02-cyclic_rename_dependency.json --enable incremental.load 02-cyclic_rename_dependency.c | grep 'change_info' | sed -r 's/^change_info = \{ unchanged = [[:digit:]]+; (.*) \}$/\1/' + changed = 1 (with unchangedHeader = 1); added = 2; removed = 2 diff --git a/tests/incremental/05-method-rename/03-cyclic_with_swap.c b/tests/incremental/05-method-rename/03-cyclic_with_swap.c new file mode 100644 index 0000000000..34026afa92 --- /dev/null +++ b/tests/incremental/05-method-rename/03-cyclic_with_swap.c @@ -0,0 +1,14 @@ +#include + +void foo1(int c) { + if (c < 10) foo2(c + 1); +} + +void foo2(int c) { + if (c < 10) foo1(c + 1); +} + +int main() { + foo1(0); + return 0; +} diff --git a/tests/incremental/05-method-rename/03-cyclic_with_swap.json b/tests/incremental/05-method-rename/03-cyclic_with_swap.json new file mode 100644 index 0000000000..0db3279e44 --- /dev/null +++ b/tests/incremental/05-method-rename/03-cyclic_with_swap.json @@ -0,0 +1,3 @@ +{ + +} diff --git a/tests/incremental/05-method-rename/03-cyclic_with_swap.patch b/tests/incremental/05-method-rename/03-cyclic_with_swap.patch new file mode 100644 index 0000000000..0886106162 --- /dev/null +++ b/tests/incremental/05-method-rename/03-cyclic_with_swap.patch @@ -0,0 +1,25 @@ +--- tests/incremental/05-method-rename/03-cyclic_with_swap.c ++++ tests/incremental/05-method-rename/03-cyclic_with_swap.c +@@ -2,13 +2,17 @@ + +-void foo1(int c) { +- if (c < 10) foo2(c + 1); ++void newFun(int c) { ++ printf("newfun"); + } + +-void foo2(int c) { +- if (c < 10) foo1(c + 1); ++void bar1(int c) { ++ if (c < 10) bar2(c + 1); ++} ++ ++void bar2(int c) { ++ if (c < 10) newFun(c + 1); + } + + int main() { +- foo1(0); ++ bar1(0); + return 0; + } diff --git a/tests/incremental/05-method-rename/03-cyclic_with_swap.t b/tests/incremental/05-method-rename/03-cyclic_with_swap.t new file mode 100644 index 0000000000..d2e8dd6d97 --- /dev/null +++ b/tests/incremental/05-method-rename/03-cyclic_with_swap.t @@ -0,0 +1,14 @@ +Run Goblint on initial program version + + $ goblint --conf 03-cyclic_with_swap.json --enable incremental.save 03-cyclic_with_swap.c > /dev/null 2>&1 + +Apply patch + + $ chmod +w 03-cyclic_with_swap.c + $ patch -b <03-cyclic_with_swap.patch + patching file 03-cyclic_with_swap.c + +Run Goblint incrementally on new program version and check the change detection result + + $ goblint --conf 03-cyclic_with_swap.json --enable incremental.load 03-cyclic_with_swap.c | grep 'change_info' | sed -r 's/^change_info = \{ unchanged = [[:digit:]]+; (.*) \}$/\1/' + changed = 1 (with unchangedHeader = 1); added = 3; removed = 2 diff --git a/tests/incremental/05-method-rename/04-deep_change.c b/tests/incremental/05-method-rename/04-deep_change.c new file mode 100644 index 0000000000..80037f934d --- /dev/null +++ b/tests/incremental/05-method-rename/04-deep_change.c @@ -0,0 +1,18 @@ +#include + +void zap() { + printf("zap"); +} + +void bar() { + zap(); +} + +void foo() { + bar(); +} + +int main() { + foo(); + return 0; +} diff --git a/tests/incremental/05-method-rename/04-deep_change.json b/tests/incremental/05-method-rename/04-deep_change.json new file mode 100644 index 0000000000..0db3279e44 --- /dev/null +++ b/tests/incremental/05-method-rename/04-deep_change.json @@ -0,0 +1,3 @@ +{ + +} diff --git a/tests/incremental/05-method-rename/04-deep_change.patch b/tests/incremental/05-method-rename/04-deep_change.patch new file mode 100644 index 0000000000..687b8f74bc --- /dev/null +++ b/tests/incremental/05-method-rename/04-deep_change.patch @@ -0,0 +1,11 @@ +--- tests/incremental/05-method-rename/04-deep_change.c ++++ tests/incremental/05-method-rename/04-deep_change.c +@@ -1,7 +1,7 @@ + #include + + void zap() { +- printf("zap"); ++ printf("drap"); + } + + void bar() { diff --git a/tests/incremental/05-method-rename/04-deep_change.t b/tests/incremental/05-method-rename/04-deep_change.t new file mode 100644 index 0000000000..1adcb56276 --- /dev/null +++ b/tests/incremental/05-method-rename/04-deep_change.t @@ -0,0 +1,14 @@ +Run Goblint on initial program version + + $ goblint --conf 04-deep_change.json --enable incremental.save 04-deep_change.c > /dev/null 2>&1 + +Apply patch + + $ chmod +w 04-deep_change.c + $ patch -b <04-deep_change.patch + patching file 04-deep_change.c + +Run Goblint incrementally on new program version and check the change detection result + + $ goblint --conf 04-deep_change.json --enable incremental.load 04-deep_change.c | grep 'change_info' | sed -r 's/^change_info = \{ unchanged = [[:digit:]]+; (.*) \}$/\1/' + changed = 1 (with unchangedHeader = 1); added = 0; removed = 0 diff --git a/tests/incremental/05-method-rename/05-common_rename.c b/tests/incremental/05-method-rename/05-common_rename.c new file mode 100644 index 0000000000..ce72a6dda1 --- /dev/null +++ b/tests/incremental/05-method-rename/05-common_rename.c @@ -0,0 +1,20 @@ +#include + +void foo() { + printf("foo"); +} + +void fun1() { + foo(); +} + +void fun2() { + foo(); +} + +int main() { + fun1(); + fun2(); + foo(); + return 0; +} diff --git a/tests/incremental/05-method-rename/05-common_rename.json b/tests/incremental/05-method-rename/05-common_rename.json new file mode 100644 index 0000000000..0db3279e44 --- /dev/null +++ b/tests/incremental/05-method-rename/05-common_rename.json @@ -0,0 +1,3 @@ +{ + +} diff --git a/tests/incremental/05-method-rename/05-common_rename.patch b/tests/incremental/05-method-rename/05-common_rename.patch new file mode 100644 index 0000000000..93904d5780 --- /dev/null +++ b/tests/incremental/05-method-rename/05-common_rename.patch @@ -0,0 +1,27 @@ +--- tests/incremental/05-method-rename/05-common_rename.c ++++ tests/incremental/05-method-rename/05-common_rename.c +@@ -1,20 +1,20 @@ + #include + +-void foo() { ++void bar() { + printf("foo"); + } + + void fun1() { +- foo(); ++ bar(); + } + + void fun2() { +- foo(); ++ bar(); + } + + int main() { + fun1(); + fun2(); +- foo(); ++ bar(); + return 0; + } diff --git a/tests/incremental/05-method-rename/05-common_rename.t b/tests/incremental/05-method-rename/05-common_rename.t new file mode 100644 index 0000000000..62e99c6c80 --- /dev/null +++ b/tests/incremental/05-method-rename/05-common_rename.t @@ -0,0 +1,14 @@ +Run Goblint on initial program version + + $ goblint --conf 05-common_rename.json --enable incremental.save 05-common_rename.c > /dev/null 2>&1 + +Apply patch + + $ chmod +w 05-common_rename.c + $ patch -b <05-common_rename.patch + patching file 05-common_rename.c + +Run Goblint incrementally on new program version and check the change detection result + + $ goblint --conf 05-common_rename.json --enable incremental.load 05-common_rename.c | grep 'change_info' | sed -r 's/^change_info = \{ unchanged = [[:digit:]]+; (.*) \}$/\1/' + changed = 0 (with unchangedHeader = 0); added = 0; removed = 0 diff --git a/tests/incremental/05-method-rename/06-recursive_rename.c b/tests/incremental/05-method-rename/06-recursive_rename.c new file mode 100644 index 0000000000..dc9ac72e94 --- /dev/null +++ b/tests/incremental/05-method-rename/06-recursive_rename.c @@ -0,0 +1,7 @@ +void foo(int x) { + if(x > 1) foo(x - 1); +} + +int main() { + foo(10); +} diff --git a/tests/incremental/05-method-rename/06-recursive_rename.json b/tests/incremental/05-method-rename/06-recursive_rename.json new file mode 100644 index 0000000000..0db3279e44 --- /dev/null +++ b/tests/incremental/05-method-rename/06-recursive_rename.json @@ -0,0 +1,3 @@ +{ + +} diff --git a/tests/incremental/05-method-rename/06-recursive_rename.patch b/tests/incremental/05-method-rename/06-recursive_rename.patch new file mode 100644 index 0000000000..356f959256 --- /dev/null +++ b/tests/incremental/05-method-rename/06-recursive_rename.patch @@ -0,0 +1,13 @@ +--- tests/incremental/05-method-rename/06-recursive_rename.c ++++ tests/incremental/05-method-rename/06-recursive_rename.c +@@ -1,7 +1,7 @@ +-void foo(int x) { +- if(x > 1) foo(x - 1); ++void bar(int x) { ++ if(x > 1) bar(x - 1); + } + + int main() { +- foo(10); ++ bar(10); + } diff --git a/tests/incremental/05-method-rename/06-recursive_rename.t b/tests/incremental/05-method-rename/06-recursive_rename.t new file mode 100644 index 0000000000..dce0894ff1 --- /dev/null +++ b/tests/incremental/05-method-rename/06-recursive_rename.t @@ -0,0 +1,14 @@ +Run Goblint on initial program version + + $ goblint --conf 06-recursive_rename.json --enable incremental.save 06-recursive_rename.c > /dev/null 2>&1 + +Apply patch + + $ chmod +w 06-recursive_rename.c + $ patch -b <06-recursive_rename.patch + patching file 06-recursive_rename.c + +Run Goblint incrementally on new program version and check the change detection result + + $ goblint --conf 06-recursive_rename.json --enable incremental.load 06-recursive_rename.c | grep 'change_info' | sed -r 's/^change_info = \{ unchanged = [[:digit:]]+; (.*) \}$/\1/' + changed = 0 (with unchangedHeader = 0); added = 0; removed = 0 diff --git a/tests/incremental/05-method-rename/dune b/tests/incremental/05-method-rename/dune new file mode 100644 index 0000000000..1b37756f98 --- /dev/null +++ b/tests/incremental/05-method-rename/dune @@ -0,0 +1,2 @@ +(cram + (deps (glob_files *.{c,json,patch}) (sandbox preserve_file_kind))) diff --git a/tests/incremental/06-glob-var-rename/00-simple_rename.c b/tests/incremental/06-glob-var-rename/00-simple_rename.c new file mode 100644 index 0000000000..56650e98ed --- /dev/null +++ b/tests/incremental/06-glob-var-rename/00-simple_rename.c @@ -0,0 +1,9 @@ +#include + +int foo = 1; + +int main() { + printf("%d", foo); + + return 0; +} diff --git a/tests/incremental/06-glob-var-rename/00-simple_rename.json b/tests/incremental/06-glob-var-rename/00-simple_rename.json new file mode 100644 index 0000000000..0db3279e44 --- /dev/null +++ b/tests/incremental/06-glob-var-rename/00-simple_rename.json @@ -0,0 +1,3 @@ +{ + +} diff --git a/tests/incremental/06-glob-var-rename/00-simple_rename.patch b/tests/incremental/06-glob-var-rename/00-simple_rename.patch new file mode 100644 index 0000000000..1e0f3b2565 --- /dev/null +++ b/tests/incremental/06-glob-var-rename/00-simple_rename.patch @@ -0,0 +1,14 @@ +--- tests/incremental/06-glob-var-rename/00-simple_rename.c ++++ tests/incremental/06-glob-var-rename/00-simple_rename.c +@@ -1,9 +1,9 @@ + #include + +-int foo = 1; ++int bar = 1; + + int main() { +- printf("%d", foo); ++ printf("%d", bar); + + return 0; + } diff --git a/tests/incremental/06-glob-var-rename/00-simple_rename.t b/tests/incremental/06-glob-var-rename/00-simple_rename.t new file mode 100644 index 0000000000..1855b903eb --- /dev/null +++ b/tests/incremental/06-glob-var-rename/00-simple_rename.t @@ -0,0 +1,14 @@ +Run Goblint on initial program version + + $ goblint --conf 00-simple_rename.json --enable incremental.save 00-simple_rename.c > /dev/null 2>&1 + +Apply patch + + $ chmod +w 00-simple_rename.c + $ patch -b <00-simple_rename.patch + patching file 00-simple_rename.c + +Run Goblint incrementally on new program version and check the change detection result + + $ goblint --conf 00-simple_rename.json --enable incremental.load 00-simple_rename.c | grep 'change_info' | sed -r 's/^change_info = \{ unchanged = [[:digit:]]+; (.*) \}$/\1/' + changed = 0 (with unchangedHeader = 0); added = 0; removed = 0 diff --git a/tests/incremental/06-glob-var-rename/01-duplicate_local_global.c b/tests/incremental/06-glob-var-rename/01-duplicate_local_global.c new file mode 100644 index 0000000000..9ad715e50d --- /dev/null +++ b/tests/incremental/06-glob-var-rename/01-duplicate_local_global.c @@ -0,0 +1,14 @@ +#include + +int foo = 1; + +int main() { + + printf("%d", foo); + + int foo = 2; + + printf("%d", foo); + + return 0; +} diff --git a/tests/incremental/06-glob-var-rename/01-duplicate_local_global.json b/tests/incremental/06-glob-var-rename/01-duplicate_local_global.json new file mode 100644 index 0000000000..0db3279e44 --- /dev/null +++ b/tests/incremental/06-glob-var-rename/01-duplicate_local_global.json @@ -0,0 +1,3 @@ +{ + +} diff --git a/tests/incremental/06-glob-var-rename/01-duplicate_local_global.patch b/tests/incremental/06-glob-var-rename/01-duplicate_local_global.patch new file mode 100644 index 0000000000..1d65c5672a --- /dev/null +++ b/tests/incremental/06-glob-var-rename/01-duplicate_local_global.patch @@ -0,0 +1,21 @@ +--- tests/incremental/06-glob-var-rename/01-duplicate_local_global.c ++++ tests/incremental/06-glob-var-rename/01-duplicate_local_global.c +@@ -1,14 +1,14 @@ + #include + +-int foo = 1; ++int bar = 1; + + int main() { + +- printf("%d", foo); ++ printf("%d", bar); + +- int foo = 2; ++ int bar = 2; + +- printf("%d", foo); ++ printf("%d", bar); + + return 0; + } diff --git a/tests/incremental/06-glob-var-rename/01-duplicate_local_global.t b/tests/incremental/06-glob-var-rename/01-duplicate_local_global.t new file mode 100644 index 0000000000..cd2c5c0fea --- /dev/null +++ b/tests/incremental/06-glob-var-rename/01-duplicate_local_global.t @@ -0,0 +1,14 @@ +Run Goblint on initial program version + + $ goblint --conf 01-duplicate_local_global.json --enable incremental.save 01-duplicate_local_global.c > /dev/null 2>&1 + +Apply patch + + $ chmod +w 01-duplicate_local_global.c + $ patch -b <01-duplicate_local_global.patch + patching file 01-duplicate_local_global.c + +Run Goblint incrementally on new program version and check the change detection result + + $ goblint --conf 01-duplicate_local_global.json --enable incremental.load 01-duplicate_local_global.c | grep 'change_info' | sed -r 's/^change_info = \{ unchanged = [[:digit:]]+; (.*) \}$/\1/' + changed = 0 (with unchangedHeader = 0); added = 0; removed = 0 diff --git a/tests/incremental/06-glob-var-rename/02-add_new_gvar.c b/tests/incremental/06-glob-var-rename/02-add_new_gvar.c new file mode 100644 index 0000000000..5efe319981 --- /dev/null +++ b/tests/incremental/06-glob-var-rename/02-add_new_gvar.c @@ -0,0 +1,8 @@ +#include + +int myVar = 1; + +int main() { + printf("%d", myVar); + printf("%d", myVar); +} diff --git a/tests/incremental/06-glob-var-rename/02-add_new_gvar.json b/tests/incremental/06-glob-var-rename/02-add_new_gvar.json new file mode 100644 index 0000000000..0db3279e44 --- /dev/null +++ b/tests/incremental/06-glob-var-rename/02-add_new_gvar.json @@ -0,0 +1,3 @@ +{ + +} diff --git a/tests/incremental/06-glob-var-rename/02-add_new_gvar.patch b/tests/incremental/06-glob-var-rename/02-add_new_gvar.patch new file mode 100644 index 0000000000..f0c145107a --- /dev/null +++ b/tests/incremental/06-glob-var-rename/02-add_new_gvar.patch @@ -0,0 +1,13 @@ +--- tests/incremental/06-glob-var-rename/02-add_new_gvar.c ++++ tests/incremental/06-glob-var-rename/02-add_new_gvar.c +@@ -1,8 +1,9 @@ + #include + + int myVar = 1; ++int foo = 1; + + int main() { + printf("%d", myVar); +- printf("%d", myVar); ++ printf("%d", foo); + } diff --git a/tests/incremental/06-glob-var-rename/02-add_new_gvar.t b/tests/incremental/06-glob-var-rename/02-add_new_gvar.t new file mode 100644 index 0000000000..c71cd6808f --- /dev/null +++ b/tests/incremental/06-glob-var-rename/02-add_new_gvar.t @@ -0,0 +1,14 @@ +Run Goblint on initial program version + + $ goblint --conf 02-add_new_gvar.json --enable incremental.save 02-add_new_gvar.c > /dev/null 2>&1 + +Apply patch + + $ chmod +w 02-add_new_gvar.c + $ patch -b <02-add_new_gvar.patch + patching file 02-add_new_gvar.c + +Run Goblint incrementally on new program version and check the change detection result + + $ goblint --conf 02-add_new_gvar.json --enable incremental.load 02-add_new_gvar.c | grep 'change_info' | sed -r 's/^change_info = \{ unchanged = [[:digit:]]+; (.*) \}$/\1/' + changed = 1 (with unchangedHeader = 1); added = 1; removed = 0 diff --git a/tests/incremental/06-glob-var-rename/dune b/tests/incremental/06-glob-var-rename/dune new file mode 100644 index 0000000000..1b37756f98 --- /dev/null +++ b/tests/incremental/06-glob-var-rename/dune @@ -0,0 +1,2 @@ +(cram + (deps (glob_files *.{c,json,patch}) (sandbox preserve_file_kind))) diff --git a/tests/incremental/13-restart-write/05-race-call-remove.c b/tests/incremental/13-restart-write/05-race-call-remove.c index 599b753320..c07962ad78 100644 --- a/tests/incremental/13-restart-write/05-race-call-remove.c +++ b/tests/incremental/13-restart-write/05-race-call-remove.c @@ -16,4 +16,4 @@ int main() { pthread_create(&id, NULL, t_fun, NULL); foo(); return 0; -} \ No newline at end of file +} diff --git a/tests/incremental/13-restart-write/09-mutex-self-fix.c b/tests/incremental/13-restart-write/09-mutex-self-fix.c new file mode 100644 index 0000000000..35b0e6db41 --- /dev/null +++ b/tests/incremental/13-restart-write/09-mutex-self-fix.c @@ -0,0 +1,17 @@ +#include + +pthread_mutex_t A = PTHREAD_MUTEX_INITIALIZER; +int g; + +void *t_fun(void *arg) { + g++; // RACE! + return NULL; +} + +int main() { + pthread_t id; + while (1) { + pthread_create(&id, NULL, t_fun, NULL); + } + return 0; +} diff --git a/tests/incremental/13-restart-write/09-mutex-self-fix.json b/tests/incremental/13-restart-write/09-mutex-self-fix.json new file mode 100644 index 0000000000..357fd7e288 --- /dev/null +++ b/tests/incremental/13-restart-write/09-mutex-self-fix.json @@ -0,0 +1,10 @@ +{ + "incremental": { + "restart": { + "sided": { + "enabled": false + }, + "write-only": true + } + } +} diff --git a/tests/incremental/13-restart-write/09-mutex-self-fix.patch b/tests/incremental/13-restart-write/09-mutex-self-fix.patch new file mode 100644 index 0000000000..a19ecb593f --- /dev/null +++ b/tests/incremental/13-restart-write/09-mutex-self-fix.patch @@ -0,0 +1,13 @@ +--- tests/incremental/13-restart-write/09-mutex-self-fix.c ++++ tests/incremental/13-restart-write/09-mutex-self-fix.c +@@ -4,7 +4,9 @@ pthread_mutex_t A = PTHREAD_MUTEX_INITIALIZER; + int g; + + void *t_fun(void *arg) { +- g++; // RACE! ++ pthread_mutex_lock(&A); ++ g++; // NORACE ++ pthread_mutex_unlock(&A); + return NULL; + } + diff --git a/tests/incremental/dune b/tests/incremental/dune new file mode 100644 index 0000000000..fdb1d941c2 --- /dev/null +++ b/tests/incremental/dune @@ -0,0 +1,3 @@ +(cram + (applies_to :whole_subtree) + (deps %{bin:goblint} (package goblint))) ; need entire package for includes/ diff --git a/tests/regression/00-sanity/01-assert.t b/tests/regression/00-sanity/01-assert.t index a0a26e4bed..9142f805f9 100644 --- a/tests/regression/00-sanity/01-assert.t +++ b/tests/regression/00-sanity/01-assert.t @@ -1,7 +1,8 @@ - $ goblint 01-assert.c - [Success][Assert] Assertion "success" will succeed (01-assert.c:10:3-10:28) - [Warning][Assert] Assertion "unknown == 4" is unknown. (01-assert.c:11:3-11:33) + $ goblint --enable warn.deterministic 01-assert.c [Error][Assert] Assertion "fail" will fail. (01-assert.c:12:3-12:25) + [Warning][Assert] Assertion "unknown == 4" is unknown. (01-assert.c:11:3-11:33) + [Success][Assert] Assertion "success" will succeed (01-assert.c:10:3-10:28) + [Warning][Deadcode] Function 'main' does not return [Warning][Deadcode] Function 'main' has dead code: on lines 13..14 (01-assert.c:13-14) [Warning][Deadcode] Logical lines of code (LLoC) summary: diff --git a/tests/regression/00-sanity/02-minimal.c b/tests/regression/00-sanity/02-minimal.c index b1ec6b10fa..01a86918af 100644 --- a/tests/regression/00-sanity/02-minimal.c +++ b/tests/regression/00-sanity/02-minimal.c @@ -1,4 +1,4 @@ -// TERM. int main() { + __goblint_check(1); // reachable, formerly TERM return 0; } diff --git a/tests/regression/00-sanity/03-no_succ.c b/tests/regression/00-sanity/03-no_succ.c index 33f35483fa..bda5eb8f47 100644 --- a/tests/regression/00-sanity/03-no_succ.c +++ b/tests/regression/00-sanity/03-no_succ.c @@ -1,5 +1,5 @@ -// TERM! int main() { + __goblint_check(1); // reachable, formerly TERM return 0; } diff --git a/tests/regression/00-sanity/05-inf_loop.c b/tests/regression/00-sanity/05-inf_loop.c index ba82fe2209..006844e3b6 100644 --- a/tests/regression/00-sanity/05-inf_loop.c +++ b/tests/regression/00-sanity/05-inf_loop.c @@ -1,6 +1,6 @@ -// NONTERM int main() { while (1); + __goblint_check(0); // NOWARN (unreachable), formerly NONTERM return 0; } diff --git a/tests/regression/00-sanity/06-term1.c b/tests/regression/00-sanity/06-term1.c index 1c57cb5abd..6ea19d58be 100644 --- a/tests/regression/00-sanity/06-term1.c +++ b/tests/regression/00-sanity/06-term1.c @@ -1,6 +1,6 @@ -// NONTERM int main() { int i; while (1); + __goblint_check(0); // NOWARN (unreachable), formerly NONTERM //return 0; // with this line it is okay) } diff --git a/tests/regression/00-sanity/07-term2.c b/tests/regression/00-sanity/07-term2.c index 9c26c07ad1..fdb8622e61 100644 --- a/tests/regression/00-sanity/07-term2.c +++ b/tests/regression/00-sanity/07-term2.c @@ -1,4 +1,3 @@ -// NONTERM #include void f() { @@ -7,5 +6,6 @@ void f() { int main() { while (1); + __goblint_check(0); // NOWARN (unreachable), formerly NONTERM return 0; } diff --git a/tests/regression/00-sanity/08-asm_nop.c b/tests/regression/00-sanity/08-asm_nop.c index 99780cea9a..e9d778fdb2 100644 --- a/tests/regression/00-sanity/08-asm_nop.c +++ b/tests/regression/00-sanity/08-asm_nop.c @@ -1,5 +1,5 @@ -// TERM. int main() { __asm__ ("nop"); + __goblint_check(1); // reachable, formerly TERM return (0); } diff --git a/tests/regression/00-sanity/10-loop_label.c b/tests/regression/00-sanity/10-loop_label.c index 8b76b3804d..dcdbbb08bc 100644 --- a/tests/regression/00-sanity/10-loop_label.c +++ b/tests/regression/00-sanity/10-loop_label.c @@ -1,7 +1,7 @@ -// NONTERM int main () { while (1) { while_1_continue: /* CIL label */ ; } + __goblint_check(0); // NOWARN (unreachable), formerly NONTERM return 0; } diff --git a/tests/regression/00-sanity/35-join-contexts.c b/tests/regression/00-sanity/35-join-contexts.c new file mode 100644 index 0000000000..3c081a1425 --- /dev/null +++ b/tests/regression/00-sanity/35-join-contexts.c @@ -0,0 +1,29 @@ +// PARAM: --set ana.ctx_insens[+] threadflag --set ana.ctx_insens[+] threadid --set ana.ctx_insens[+] base +// Fully context-insensitive +#include +#include + +int g = 1; + +void foo() { + // Single-threaded: g = 1 in local state + // Multi-threaded: g = 2 in global unprotected invariant + // Joined contexts: g is unprotected, so read g = 2 from global unprotected invariant (only) + // Was soundly claiming that check will succeed! + int x = g; + __goblint_check(x == 2); // UNKNOWN! +} + +void *t_fun(void *arg) { + foo(); +} + +int main() { + foo(); + g = 2; + + pthread_t id; + pthread_create(&id, NULL, t_fun, NULL); + + return 0; +} diff --git a/tests/regression/00-sanity/36-strict-loop-dead.c b/tests/regression/00-sanity/36-strict-loop-dead.c new file mode 100644 index 0000000000..02b7b659df --- /dev/null +++ b/tests/regression/00-sanity/36-strict-loop-dead.c @@ -0,0 +1,19 @@ +// only called with negative n +int basic2(int n) { + int a = 0; + + if (n < 0) + return 0; + + for (int i = 0; i < n; i++) // NOWARN + // Was bug in dead code warnings: no dead code warning was emitted, because body was not included + // in the result. Transformation checks all CFG nodes, and therefore worked. + a += i + n; // NOWARN + + return a; // NOWARN +} + + +int main() { + basic2(-3); +} diff --git a/tests/regression/00-sanity/36-strict-loop-dead.t b/tests/regression/00-sanity/36-strict-loop-dead.t new file mode 100644 index 0000000000..a985909480 --- /dev/null +++ b/tests/regression/00-sanity/36-strict-loop-dead.t @@ -0,0 +1,10 @@ + $ goblint --enable warn.deterministic 36-strict-loop-dead.c + [Warning][Deadcode] Function 'basic2' has dead code: + on line 8 (36-strict-loop-dead.c:8-8) + on line 11 (36-strict-loop-dead.c:11-11) + on line 13 (36-strict-loop-dead.c:13-13) + [Warning][Deadcode] Logical lines of code (LLoC) summary: + live: 7 + dead: 3 + total lines: 10 + [Warning][Deadcode][CWE-571] condition 'n < 0' is always true (36-strict-loop-dead.c:5:7-5:12) 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/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/01-cpa/74-rand.c b/tests/regression/01-cpa/74-rand.c new file mode 100644 index 0000000000..dc5e5b3bba --- /dev/null +++ b/tests/regression/01-cpa/74-rand.c @@ -0,0 +1,12 @@ +//PARAM: --enable ana.int.interval +#include +#include +#include + +int main () { + int r = rand(); + + __goblint_check(r >= 0); + + return 0; +} diff --git a/tests/regression/02-base/91-ad-meet.c b/tests/regression/02-base/91-ad-meet.c index 456234afb1..08ae47a929 100644 --- a/tests/regression/02-base/91-ad-meet.c +++ b/tests/regression/02-base/91-ad-meet.c @@ -1,16 +1,59 @@ -// SKIP -// TODO: be sound and claim that assert may hold instead of must not hold -// assert passes when compiled +//PARAM: --set ana.malloc.unique_address_count 1 #include +#include struct s { int fst; }; +void blob_offsets(){ + int *a = malloc(10 * sizeof(int)); + int *b = a; + + // Relies on malloc uniqueness, but zero offset can be determined to be equal. + __goblint_check(a == b); + + // TODO: Determine bitoffsets for blocks (currently top) + __goblint_check(a + 4 == b + 4); //TODO + __goblint_check(a == b + 1 ); //TODO +} + +void array_offsets(){ + int a[10]; + int *b = a; + int *c = a + 2; + + __goblint_check(a == b); + __goblint_check(a + 2 == c); + __goblint_check(b + 2 == c); + + __goblint_check(a + 1 == b + 1); + __goblint_check(a + 1 + 2 == c + 1 ); + __goblint_check(b + 1 + 2 == c + 1 ); +} + +typedef struct { + int x; + int y; +} tuple; + +void array_with_tuple_offsets(){ + tuple ts[10]; + + __goblint_check(&(ts[3].x) == &(ts[3].x)); + __goblint_check(&(ts[3].x) != &(ts[3].y)); + __goblint_check((void*) &(ts[3]) != (void*) &(ts[3].y)); +} + int main() { struct s a; void *p = &a.fst; void *q = ((int(*)[1]) (&a))[0]; - assert(p == q); + // as an __goblint_check, this passes when compiled + __goblint_check(p == q); + + blob_offsets(); + array_offsets(); + array_with_tuple_offsets(); return 0; } diff --git a/tests/regression/02-base/92-ad-union-fields.c b/tests/regression/02-base/92-ad-union-fields.c index 2545d88a54..2da2194595 100644 --- a/tests/regression/02-base/92-ad-union-fields.c +++ b/tests/regression/02-base/92-ad-union-fields.c @@ -1,4 +1,3 @@ -// SKIP // TODO: be sound and claim that assert may hold instead of must not hold // assert passes when compiled #include diff --git a/tests/regression/02-base/93-ad-struct-offset.c b/tests/regression/02-base/93-ad-struct-offset.c index f9ce8f8987..c1ea876700 100644 --- a/tests/regression/02-base/93-ad-struct-offset.c +++ b/tests/regression/02-base/93-ad-struct-offset.c @@ -1,4 +1,3 @@ -// SKIP #include struct str{ int a; @@ -21,8 +20,8 @@ int main(){ z = 2; } - // Aaccording to the C standard (section 6.2.8 in the C11 standard), + // According to the C standard (section 6.2.8 in the C11 standard), // the alignment of fields in structs is implementation defined. // When compiling with GCC, the following check as an assert happens to hold. - __goblint_check(z==1); //UNKNOWN! + __goblint_check(z==1); //UNKNOWN } diff --git a/tests/regression/03-practical/05-deslash.c b/tests/regression/03-practical/05-deslash.c index d1767db4ab..844cc1b039 100644 --- a/tests/regression/03-practical/05-deslash.c +++ b/tests/regression/03-practical/05-deslash.c @@ -1,4 +1,3 @@ -// TERM. int deslash(unsigned char *str) { unsigned char *wp, *rp; @@ -70,6 +69,7 @@ int main() { char *x = "kala"; deslash(x); printf("%s",x); + __goblint_check(1); // reachable, formerly TERM return 0; } diff --git a/tests/regression/03-practical/10-big_init.c b/tests/regression/03-practical/10-big_init.c index 6c8cd29a55..0914420e4e 100644 --- a/tests/regression/03-practical/10-big_init.c +++ b/tests/regression/03-practical/10-big_init.c @@ -1,4 +1,4 @@ -// TERM. Well, just an example of slow initialization. +// Just an example of slow initialization. typedef unsigned char BYTE; BYTE Buffer[4096]; @@ -7,5 +7,6 @@ typedef TEXT TABLE[20]; TABLE MessageSystem[20]; int main() { + __goblint_check(1); // reachable, formerly TERM return 0; } diff --git a/tests/regression/03-practical/16-union_index.c b/tests/regression/03-practical/16-union_index.c index c39fd87466..de69c5bba3 100644 --- a/tests/regression/03-practical/16-union_index.c +++ b/tests/regression/03-practical/16-union_index.c @@ -1,4 +1,3 @@ -// TERM. typedef union { char c[4] ; // c needs to be at least as big as l long l ; @@ -7,5 +6,6 @@ typedef union { u uv; int main(){ + __goblint_check(1); // reachable, formerly TERM return 0; } diff --git a/tests/regression/03-practical/25-zstd-customMem.c b/tests/regression/03-practical/25-zstd-customMem.c index be7b65429e..dd1db7fb5e 100644 --- a/tests/regression/03-practical/25-zstd-customMem.c +++ b/tests/regression/03-practical/25-zstd-customMem.c @@ -1,3 +1,4 @@ +// PARAM: --set lib.activated[+] zstd // Extracted from zstd #include #include diff --git a/tests/regression/03-practical/28-bool.c b/tests/regression/03-practical/28-bool.c new file mode 100644 index 0000000000..abcf63da01 --- /dev/null +++ b/tests/regression/03-practical/28-bool.c @@ -0,0 +1,20 @@ +#include +#include + +int main() { + int a; + int *p = &a; + + bool x = p; + __goblint_check(x); + bool y = !!p; + __goblint_check(y); + bool z = p != 0; + __goblint_check(z); + + int b = 10; + bool bb = b; + __goblint_check(bb); + + return 0; +} \ No newline at end of file diff --git a/tests/regression/03-practical/29-bool-interval.c b/tests/regression/03-practical/29-bool-interval.c new file mode 100644 index 0000000000..ad55e37701 --- /dev/null +++ b/tests/regression/03-practical/29-bool-interval.c @@ -0,0 +1,21 @@ +//PARAM: --enable ana.int.interval --disable ana.int.def_exc +#include +#include + +int main() { + int a; + int *p = &a; + + bool x = p; + __goblint_check(x); //UNKNOWN (not null cannot be expressed in interval domain) + bool y = !!p; + __goblint_check(y); + bool z = p != 0; + __goblint_check(z); + + int b = 10; + bool bb = b; + __goblint_check(bb); + + return 0; +} \ No newline at end of file diff --git a/tests/regression/03-practical/30-bool-congr.c b/tests/regression/03-practical/30-bool-congr.c new file mode 100644 index 0000000000..86249848c1 --- /dev/null +++ b/tests/regression/03-practical/30-bool-congr.c @@ -0,0 +1,13 @@ +//PARAM: --enable ana.int.congruence +#include +#include + +int main() { + + bool x = 1; + bool y = 1; + bool z = x + y; + __goblint_check(z); + + return 0; +} 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/01-simple_rc.t b/tests/regression/04-mutex/01-simple_rc.t index c77cf1074c..b61650c6d3 100644 --- a/tests/regression/04-mutex/01-simple_rc.t +++ b/tests/regression/04-mutex/01-simple_rc.t @@ -1,16 +1,15 @@ - $ goblint 01-simple_rc.c - [Info][Deadcode] Logical lines of code (LLoC) summary: - live: 12 - dead: 0 - total lines: 12 - [Warning][Race] Memory location myglobal@01-simple_rc.c:4:5-4:13 (race with conf. 110): - write with [mhp:{tid=[main, t_fun@01-simple_rc.c:17:3-17:40]}, - lock:{mutex1}, thread:[main, t_fun@01-simple_rc.c:17:3-17:40]] (conf. 110) (01-simple_rc.c:10:3-10:22) - write with [mhp:{tid=[main]; created={[main, t_fun@01-simple_rc.c:17:3-17:40]}}, lock:{mutex2}, thread:[main]] (conf. 110) (01-simple_rc.c:19:3-19:22) - read with [mhp:{tid=[main, t_fun@01-simple_rc.c:17:3-17:40]}, lock:{mutex1}, thread:[main, t_fun@01-simple_rc.c:17:3-17:40]] (conf. 110) (01-simple_rc.c:10:3-10:22) - read with [mhp:{tid=[main]; created={[main, t_fun@01-simple_rc.c:17:3-17:40]}}, lock:{mutex2}, thread:[main]] (conf. 110) (01-simple_rc.c:19:3-19:22) + $ goblint --enable warn.deterministic 01-simple_rc.c + [Warning][Race] Memory location myglobal (race with conf. 110): (01-simple_rc.c:4:5-4:13) + write with [mhp:{tid=[main, t_fun@01-simple_rc.c:17:3-17:40#top]}, lock:{mutex1}, thread:[main, t_fun@01-simple_rc.c:17:3-17:40#top]] (conf. 110) (exp: & myglobal) (01-simple_rc.c:10:3-10:22) + write with [mhp:{tid=[main]; created={[main, t_fun@01-simple_rc.c:17:3-17:40#top]}}, lock:{mutex2}, thread:[main]] (conf. 110) (exp: & myglobal) (01-simple_rc.c:19:3-19:22) + read with [mhp:{tid=[main, t_fun@01-simple_rc.c:17:3-17:40#top]}, lock:{mutex1}, thread:[main, t_fun@01-simple_rc.c:17:3-17:40#top]] (conf. 110) (exp: & myglobal) (01-simple_rc.c:10:3-10:22) + read with [mhp:{tid=[main]; created={[main, t_fun@01-simple_rc.c:17:3-17:40#top]}}, lock:{mutex2}, thread:[main]] (conf. 110) (exp: & myglobal) (01-simple_rc.c:19:3-19:22) [Info][Race] Memory locations race summary: safe: 0 vulnerable: 0 unsafe: 1 total memory locations: 1 + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 12 + dead: 0 + total lines: 12 diff --git a/tests/regression/04-mutex/49-type-invariants.c b/tests/regression/04-mutex/49-type-invariants.c index 9dda6f16eb..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: --disable ana.mutex.disjoint_types #include #include diff --git a/tests/regression/04-mutex/49-type-invariants.t b/tests/regression/04-mutex/49-type-invariants.t new file mode 100644 index 0000000000..4c105d1559 --- /dev/null +++ b/tests/regression/04-mutex/49-type-invariants.t @@ -0,0 +1,49 @@ + $ 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: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: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:21:3-21:21) + [Info][Unsound] Unknown value in {?} could be an escaped pointer address! (49-type-invariants.c:21:3-21: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: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: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:21:3-21:21) + [Info][Unsound] Unknown value in {?} could be an escaped pointer address! (49-type-invariants.c:21:3-21: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/68-vla_rc.c b/tests/regression/04-mutex/68-vla_rc.c new file mode 100644 index 0000000000..5644197f66 --- /dev/null +++ b/tests/regression/04-mutex/68-vla_rc.c @@ -0,0 +1,17 @@ +#include +#include + +int g; + +void *t_fun(void *arg) { + g=g+1; // RACE! + return NULL; +} + +int main(void) { + pthread_t id; + pthread_create(&id, NULL, t_fun, NULL); + int a[g]; // RACE! + int b[2][g]; // RACE! + return 0; +} diff --git a/tests/regression/04-mutex/69-sizeof_rc.c b/tests/regression/04-mutex/69-sizeof_rc.c new file mode 100644 index 0000000000..973eb72a00 --- /dev/null +++ b/tests/regression/04-mutex/69-sizeof_rc.c @@ -0,0 +1,17 @@ +#include +#include + +int g; + +void *t_fun(void *arg) { + g=g+1; // RACE! + return NULL; +} + +int main(void) { + pthread_t id; + pthread_create(&id, NULL, t_fun, NULL); + int a = sizeof(int[g]); // RACE! + int b = sizeof(int[2][g]); // RACE! + return 0; +} diff --git a/tests/regression/04-mutex/74-combine-env-assign-imprecise.c b/tests/regression/04-mutex/74-combine-env-assign-imprecise.c new file mode 100644 index 0000000000..32a90ffcef --- /dev/null +++ b/tests/regression/04-mutex/74-combine-env-assign-imprecise.c @@ -0,0 +1,25 @@ +#include + +int myglobal; +pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER; + +int foo() { + pthread_mutex_lock(&mutex1); + return myglobal+1; // NORACE +} + +void *t_fun(void *arg) { + myglobal = foo(); // NORACE + pthread_mutex_unlock(&mutex1); + return NULL; +} + +int main(void) { + pthread_t id; + pthread_create(&id, NULL, t_fun, NULL); + pthread_mutex_lock(&mutex1); + myglobal=myglobal+1; // NORACE + pthread_mutex_unlock(&mutex1); + pthread_join (id, NULL); + return 0; +} diff --git a/tests/regression/04-mutex/75-combine-env-assign-unsound.c b/tests/regression/04-mutex/75-combine-env-assign-unsound.c new file mode 100644 index 0000000000..b16772fcc4 --- /dev/null +++ b/tests/regression/04-mutex/75-combine-env-assign-unsound.c @@ -0,0 +1,26 @@ +#include + +int myglobal; +pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER; + +int foo() { + int x = myglobal + 1; // NORACE + pthread_mutex_unlock(&mutex1); + return x; +} + +void *t_fun(void *arg) { + pthread_mutex_lock(&mutex1); + myglobal=foo(); // RACE! + return NULL; +} + +int main(void) { + pthread_t id; + pthread_create(&id, NULL, t_fun, NULL); + pthread_mutex_lock(&mutex1); + myglobal=myglobal+1; // RACE! + pthread_mutex_unlock(&mutex1); + pthread_join (id, NULL); + return 0; +} diff --git a/tests/regression/04-mutex/76-empty-if_rc.c b/tests/regression/04-mutex/76-empty-if_rc.c new file mode 100644 index 0000000000..9fc81b30c7 --- /dev/null +++ b/tests/regression/04-mutex/76-empty-if_rc.c @@ -0,0 +1,25 @@ +#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); + if (myglobal) { // RACE! + + } + pthread_mutex_unlock(&mutex1); + return NULL; +} + +int main(void) { + pthread_t id; + pthread_create(&id, NULL, t_fun, NULL); + pthread_mutex_lock(&mutex2); + myglobal=myglobal+1; // RACE! + pthread_mutex_unlock(&mutex2); + pthread_join (id, NULL); + return 0; +} diff --git a/tests/regression/04-mutex/77-type-nested-fields.c b/tests/regression/04-mutex/77-type-nested-fields.c new file mode 100644 index 0000000000..00b21e3fcf --- /dev/null +++ b/tests/regression/04-mutex/77-type-nested-fields.c @@ -0,0 +1,40 @@ +#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); + getT()->s.field = 2; // RACE! + return 0; +} 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..bb935cb0ed --- /dev/null +++ b/tests/regression/04-mutex/77-type-nested-fields.t @@ -0,0 +1,30 @@ + $ 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] Unknown address in {&tmp} has escaped. (77-type-nested-fields.c:31:3-31:20) + [Info][Unsound] Unknown value in {?} could be an escaped pointer address! (77-type-nested-fields.c:31:3-31:20) + [Info][Unsound] Write to unknown address: privatization is unsound. (77-type-nested-fields.c:31:3-31:20) + [Info][Unsound] Unknown address in {&tmp} has escaped. (77-type-nested-fields.c:38:3-38:22) + [Info][Unsound] Unknown value in {?} could be an escaped pointer address! (77-type-nested-fields.c:38:3-38:22) + [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 new file mode 100644 index 0000000000..835f6163a3 --- /dev/null +++ b/tests/regression/04-mutex/78-type-array.c @@ -0,0 +1,22 @@ +#include +#include + +struct S { + int field; + int arr[2]; +}; + +extern struct S* getS(); + +void *t_fun(void *arg) { + // should not distribute access to (struct S).field + getS()->arr[1] = 1; // NORACE + return NULL; +} + +int main(void) { + pthread_t id; + pthread_create(&id, NULL, t_fun, NULL); + getS()->field = 2; // NORACE + return 0; +} diff --git a/tests/regression/04-mutex/79-type-nested-fields-deep1.c b/tests/regression/04-mutex/79-type-nested-fields-deep1.c new file mode 100644 index 0000000000..e100404960 --- /dev/null +++ b/tests/regression/04-mutex/79-type-nested-fields-deep1.c @@ -0,0 +1,45 @@ +#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 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); + getU()->t.s.field = 2; // RACE! + return 0; +} 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..ba1399d225 --- /dev/null +++ b/tests/regression/04-mutex/79-type-nested-fields-deep1.t @@ -0,0 +1,30 @@ + $ 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] Unknown address in {&tmp} has escaped. (79-type-nested-fields-deep1.c:36:3-36:20) + [Info][Unsound] Unknown value in {?} could be an escaped pointer address! (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:36:3-36:20) + [Info][Unsound] Unknown address in {&tmp} has escaped. (79-type-nested-fields-deep1.c:43:3-43:24) + [Info][Unsound] Unknown value in {?} could be an escaped pointer address! (79-type-nested-fields-deep1.c:43:3-43:24) + [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 new file mode 100644 index 0000000000..4ddd4684f7 --- /dev/null +++ b/tests/regression/04-mutex/80-type-nested-fields-deep2.c @@ -0,0 +1,45 @@ +#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? + getT()->s.field = 1; // RACE! + return NULL; +} + +int main(void) { + pthread_t id; + pthread_create(&id, NULL, t_fun, NULL); + getU()->t.s.field = 2; // RACE! + return 0; +} 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..71bdcfb2e2 --- /dev/null +++ b/tests/regression/04-mutex/80-type-nested-fields-deep2.t @@ -0,0 +1,30 @@ + $ 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] Unknown address in {&tmp} has escaped. (80-type-nested-fields-deep2.c:36:3-36:22) + [Info][Unsound] Unknown value in {?} could be an escaped pointer address! (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:36:3-36:22) + [Info][Unsound] Unknown address in {&tmp} has escaped. (80-type-nested-fields-deep2.c:43:3-43:24) + [Info][Unsound] Unknown value in {?} could be an escaped pointer address! (80-type-nested-fields-deep2.c:43:3-43:24) + [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/82-thread-local-storage.c b/tests/regression/04-mutex/82-thread-local-storage.c new file mode 100644 index 0000000000..e4eae3adaf --- /dev/null +++ b/tests/regression/04-mutex/82-thread-local-storage.c @@ -0,0 +1,23 @@ +#include +#include + +__thread 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; // NORACE + pthread_mutex_unlock(&mutex1); + return NULL; +} + +int main(void) { + pthread_t id; + pthread_create(&id, NULL, t_fun, NULL); + pthread_mutex_lock(&mutex2); + myglobal=myglobal+1; // NORACE + pthread_mutex_unlock(&mutex2); + pthread_join (id, NULL); + return 0; +} diff --git a/tests/regression/04-mutex/83-thread-local-storage-escape.c b/tests/regression/04-mutex/83-thread-local-storage-escape.c new file mode 100644 index 0000000000..6698b57baa --- /dev/null +++ b/tests/regression/04-mutex/83-thread-local-storage-escape.c @@ -0,0 +1,24 @@ +#include +#include + +__thread int myglobal; +pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER; +pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER; + +void *t_fun(void *arg) { + int* ptr = (int*) arg; + pthread_mutex_lock(&mutex1); + *ptr=*ptr+1; //RACE + pthread_mutex_unlock(&mutex1); + return NULL; +} + +int main(void) { + pthread_t id; + pthread_create(&id, NULL, t_fun, (void*) &myglobal); + pthread_mutex_lock(&mutex2); + myglobal=myglobal+1; //RACE + pthread_mutex_unlock(&mutex2); + pthread_join (id, NULL); + return 0; +} \ No newline at end of file diff --git a/tests/regression/04-mutex/84-distribute-fields-1.c b/tests/regression/04-mutex/84-distribute-fields-1.c new file mode 100644 index 0000000000..f57de5ebd8 --- /dev/null +++ b/tests/regression/04-mutex/84-distribute-fields-1.c @@ -0,0 +1,23 @@ +#include +#include + +struct S { + int data; + int data2; +}; + +struct S s; + +void *t_fun(void *arg) { + s.data = 1; // RACE! + return NULL; +} + +int main(void) { + pthread_t id; + pthread_create(&id, NULL, t_fun, NULL); + struct S s2; + s = s2; // RACE! + pthread_join (id, NULL); + return 0; +} diff --git a/tests/regression/04-mutex/84-distribute-fields-1.t b/tests/regression/04-mutex/84-distribute-fields-1.t new file mode 100644 index 0000000000..7c79572d88 --- /dev/null +++ b/tests/regression/04-mutex/84-distribute-fields-1.t @@ -0,0 +1,15 @@ + $ goblint --enable warn.deterministic --enable allglobs 84-distribute-fields-1.c + [Warning][Race] Memory location s.data (race with conf. 110): (84-distribute-fields-1.c:9:10-9:11) + write with [mhp:{tid=[main, t_fun@84-distribute-fields-1.c:18:3-18:40#top]}, thread:[main, t_fun@84-distribute-fields-1.c:18:3-18:40#top]] (conf. 110) (exp: & s.data) (84-distribute-fields-1.c:12:3-12:13) + write with [mhp:{tid=[main]; created={[main, t_fun@84-distribute-fields-1.c:18:3-18:40#top]}}, thread:[main]] (conf. 110) (exp: & s) (84-distribute-fields-1.c:20:3-20:9) + [Info][Race] Memory locations race summary: + safe: 1 + vulnerable: 0 + unsafe: 1 + total memory locations: 2 + [Success][Race] Memory location s (safe): (84-distribute-fields-1.c:9:10-9:11) + write with [mhp:{tid=[main]; created={[main, t_fun@84-distribute-fields-1.c:18:3-18:40#top]}}, thread:[main]] (conf. 110) (exp: & s) (84-distribute-fields-1.c:20:3-20:9) + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 8 + dead: 0 + total lines: 8 diff --git a/tests/regression/04-mutex/85-distribute-fields-2.c b/tests/regression/04-mutex/85-distribute-fields-2.c new file mode 100644 index 0000000000..ff06f80abe --- /dev/null +++ b/tests/regression/04-mutex/85-distribute-fields-2.c @@ -0,0 +1,29 @@ +#include +#include + +struct S { + int data; + int data2; +}; + +struct T { + struct S s; + struct S s2; + int data3; +}; + +struct T t; + +void *t_fun(void *arg) { + t.s.data = 1; // RACE! + return NULL; +} + +int main(void) { + pthread_t id; + pthread_create(&id, NULL, t_fun, NULL); + struct S s2; + t.s = s2; // RACE! + pthread_join (id, NULL); + return 0; +} diff --git a/tests/regression/04-mutex/85-distribute-fields-2.t b/tests/regression/04-mutex/85-distribute-fields-2.t new file mode 100644 index 0000000000..303c10dccd --- /dev/null +++ b/tests/regression/04-mutex/85-distribute-fields-2.t @@ -0,0 +1,15 @@ + $ goblint --enable warn.deterministic --enable allglobs 85-distribute-fields-2.c + [Warning][Race] Memory location t.s.data (race with conf. 110): (85-distribute-fields-2.c:15:10-15:11) + write with [mhp:{tid=[main, t_fun@85-distribute-fields-2.c:24:3-24:40#top]}, thread:[main, t_fun@85-distribute-fields-2.c:24:3-24:40#top]] (conf. 110) (exp: & t.s.data) (85-distribute-fields-2.c:18:3-18:15) + write with [mhp:{tid=[main]; created={[main, t_fun@85-distribute-fields-2.c:24:3-24:40#top]}}, thread:[main]] (conf. 110) (exp: & t.s) (85-distribute-fields-2.c:26:3-26:11) + [Info][Race] Memory locations race summary: + safe: 1 + vulnerable: 0 + unsafe: 1 + total memory locations: 2 + [Success][Race] Memory location t.s (safe): (85-distribute-fields-2.c:15:10-15:11) + write with [mhp:{tid=[main]; created={[main, t_fun@85-distribute-fields-2.c:24:3-24:40#top]}}, thread:[main]] (conf. 110) (exp: & t.s) (85-distribute-fields-2.c:26:3-26:11) + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 8 + dead: 0 + total lines: 8 diff --git a/tests/regression/04-mutex/86-distribute-fields-3.c b/tests/regression/04-mutex/86-distribute-fields-3.c new file mode 100644 index 0000000000..f375a6f19c --- /dev/null +++ b/tests/regression/04-mutex/86-distribute-fields-3.c @@ -0,0 +1,29 @@ +#include +#include + +struct S { + int data; + int data2; +}; + +struct T { + struct S s; + struct S s2; + int data3; +}; + +struct T t; + +void *t_fun(void *arg) { + t.s.data = 1; // RACE! + return NULL; +} + +int main(void) { + pthread_t id; + pthread_create(&id, NULL, t_fun, NULL); + struct T t2; + t = t2; // RACE! + pthread_join (id, NULL); + return 0; +} diff --git a/tests/regression/04-mutex/86-distribute-fields-3.t b/tests/regression/04-mutex/86-distribute-fields-3.t new file mode 100644 index 0000000000..084148392c --- /dev/null +++ b/tests/regression/04-mutex/86-distribute-fields-3.t @@ -0,0 +1,15 @@ + $ goblint --enable warn.deterministic --enable allglobs 86-distribute-fields-3.c + [Warning][Race] Memory location t.s.data (race with conf. 110): (86-distribute-fields-3.c:15:10-15:11) + write with [mhp:{tid=[main, t_fun@86-distribute-fields-3.c:24:3-24:40#top]}, thread:[main, t_fun@86-distribute-fields-3.c:24:3-24:40#top]] (conf. 110) (exp: & t.s.data) (86-distribute-fields-3.c:18:3-18:15) + write with [mhp:{tid=[main]; created={[main, t_fun@86-distribute-fields-3.c:24:3-24:40#top]}}, thread:[main]] (conf. 110) (exp: & t) (86-distribute-fields-3.c:26:3-26:9) + [Info][Race] Memory locations race summary: + safe: 1 + vulnerable: 0 + unsafe: 1 + total memory locations: 2 + [Success][Race] Memory location t (safe): (86-distribute-fields-3.c:15:10-15:11) + write with [mhp:{tid=[main]; created={[main, t_fun@86-distribute-fields-3.c:24:3-24:40#top]}}, thread:[main]] (conf. 110) (exp: & t) (86-distribute-fields-3.c:26:3-26:9) + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 8 + dead: 0 + total lines: 8 diff --git a/tests/regression/04-mutex/87-distribute-fields-4.c b/tests/regression/04-mutex/87-distribute-fields-4.c new file mode 100644 index 0000000000..1fc31f3805 --- /dev/null +++ b/tests/regression/04-mutex/87-distribute-fields-4.c @@ -0,0 +1,24 @@ +#include +#include + +struct S { + int data; + int data2; +}; + +struct S s; + +void *t_fun(void *arg) { + struct S s3; + s = s3; // RACE! + return NULL; +} + +int main(void) { + pthread_t id; + pthread_create(&id, NULL, t_fun, NULL); + struct S s2; + s = s2; // RACE! + pthread_join (id, NULL); + return 0; +} diff --git a/tests/regression/04-mutex/87-distribute-fields-4.t b/tests/regression/04-mutex/87-distribute-fields-4.t new file mode 100644 index 0000000000..5c26e80592 --- /dev/null +++ b/tests/regression/04-mutex/87-distribute-fields-4.t @@ -0,0 +1,13 @@ + $ goblint --enable warn.deterministic --enable allglobs 87-distribute-fields-4.c + [Warning][Race] Memory location s (race with conf. 110): (87-distribute-fields-4.c:9:10-9:11) + write with [mhp:{tid=[main, t_fun@87-distribute-fields-4.c:19:3-19:40#top]}, thread:[main, t_fun@87-distribute-fields-4.c:19:3-19:40#top]] (conf. 110) (exp: & s) (87-distribute-fields-4.c:13:3-13:9) + write with [mhp:{tid=[main]; created={[main, t_fun@87-distribute-fields-4.c:19:3-19:40#top]}}, thread:[main]] (conf. 110) (exp: & s) (87-distribute-fields-4.c:21:3-21:9) + [Info][Race] Memory locations race summary: + safe: 0 + vulnerable: 0 + unsafe: 1 + total memory locations: 1 + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 8 + dead: 0 + total lines: 8 diff --git a/tests/regression/04-mutex/88-distribute-fields-5.c b/tests/regression/04-mutex/88-distribute-fields-5.c new file mode 100644 index 0000000000..6e321375b7 --- /dev/null +++ b/tests/regression/04-mutex/88-distribute-fields-5.c @@ -0,0 +1,30 @@ +#include +#include + +struct S { + int data; + int data2; +}; + +struct T { + struct S s; + struct S s2; + int data3; +}; + +struct T t; + +void *t_fun(void *arg) { + struct S s3; + t.s = s3; // RACE! + return NULL; +} + +int main(void) { + pthread_t id; + pthread_create(&id, NULL, t_fun, NULL); + struct S s2; + t.s = s2; // RACE! + pthread_join (id, NULL); + return 0; +} diff --git a/tests/regression/04-mutex/88-distribute-fields-5.t b/tests/regression/04-mutex/88-distribute-fields-5.t new file mode 100644 index 0000000000..8f0014e34c --- /dev/null +++ b/tests/regression/04-mutex/88-distribute-fields-5.t @@ -0,0 +1,13 @@ + $ goblint --enable warn.deterministic --enable allglobs 88-distribute-fields-5.c + [Warning][Race] Memory location t.s (race with conf. 110): (88-distribute-fields-5.c:15:10-15:11) + write with [mhp:{tid=[main, t_fun@88-distribute-fields-5.c:25:3-25:40#top]}, thread:[main, t_fun@88-distribute-fields-5.c:25:3-25:40#top]] (conf. 110) (exp: & t.s) (88-distribute-fields-5.c:19:3-19:11) + write with [mhp:{tid=[main]; created={[main, t_fun@88-distribute-fields-5.c:25:3-25:40#top]}}, thread:[main]] (conf. 110) (exp: & t.s) (88-distribute-fields-5.c:27:3-27:11) + [Info][Race] Memory locations race summary: + safe: 0 + vulnerable: 0 + unsafe: 1 + total memory locations: 1 + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 8 + dead: 0 + total lines: 8 diff --git a/tests/regression/04-mutex/89-distribute-fields-6.c b/tests/regression/04-mutex/89-distribute-fields-6.c new file mode 100644 index 0000000000..c3da6f33f7 --- /dev/null +++ b/tests/regression/04-mutex/89-distribute-fields-6.c @@ -0,0 +1,30 @@ +#include +#include + +struct S { + int data; + int data2; +}; + +struct T { + struct S s; + struct S s2; + int data3; +}; + +struct T t; + +void *t_fun(void *arg) { + struct T t3; + t = t3; // RACE! + return NULL; +} + +int main(void) { + pthread_t id; + pthread_create(&id, NULL, t_fun, NULL); + struct T t2; + t = t2; // RACE! + pthread_join (id, NULL); + return 0; +} diff --git a/tests/regression/04-mutex/89-distribute-fields-6.t b/tests/regression/04-mutex/89-distribute-fields-6.t new file mode 100644 index 0000000000..aeb5050395 --- /dev/null +++ b/tests/regression/04-mutex/89-distribute-fields-6.t @@ -0,0 +1,13 @@ + $ goblint --enable warn.deterministic --enable allglobs 89-distribute-fields-6.c + [Warning][Race] Memory location t (race with conf. 110): (89-distribute-fields-6.c:15:10-15:11) + write with [mhp:{tid=[main, t_fun@89-distribute-fields-6.c:25:3-25:40#top]}, thread:[main, t_fun@89-distribute-fields-6.c:25:3-25:40#top]] (conf. 110) (exp: & t) (89-distribute-fields-6.c:19:3-19:9) + write with [mhp:{tid=[main]; created={[main, t_fun@89-distribute-fields-6.c:25:3-25:40#top]}}, thread:[main]] (conf. 110) (exp: & t) (89-distribute-fields-6.c:27:3-27:9) + [Info][Race] Memory locations race summary: + safe: 0 + vulnerable: 0 + unsafe: 1 + total memory locations: 1 + [Info][Deadcode] Logical lines of code (LLoC) summary: + live: 8 + dead: 0 + total lines: 8 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..46435045b9 --- /dev/null +++ b/tests/regression/04-mutex/90-distribute-fields-type-1.t @@ -0,0 +1,32 @@ + $ 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] Unknown address in {&tmp} has escaped. (90-distribute-fields-type-1.c:31:3-31:20) + [Info][Unsound] Unknown value in {?} could be an escaped pointer address! (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:31:3-31:20) + [Info][Unsound] Unknown address in {&tmp} has escaped. (90-distribute-fields-type-1.c:39:3-39:17) + [Info][Unsound] Unknown value in {?} could be an escaped pointer address! (90-distribute-fields-type-1.c:39:3-39:17) + [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..c7e66c0527 --- /dev/null +++ b/tests/regression/04-mutex/91-distribute-fields-type-2.t @@ -0,0 +1,32 @@ + $ 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] Unknown address in {&tmp} has escaped. (91-distribute-fields-type-2.c:32:3-32:17) + [Info][Unsound] Unknown value in {?} could be an escaped pointer address! (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:32:3-32:17) + [Info][Unsound] Unknown address in {&tmp} has escaped. (91-distribute-fields-type-2.c:40:3-40:17) + [Info][Unsound] Unknown value in {?} could be an escaped pointer address! (91-distribute-fields-type-2.c:40:3-40: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..4fc1c7e101 --- /dev/null +++ b/tests/regression/04-mutex/92-distribute-fields-type-deep.t @@ -0,0 +1,32 @@ + $ 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] Unknown address in {&tmp} has escaped. (92-distribute-fields-type-deep.c:36:3-36:20) + [Info][Unsound] Unknown value in {?} could be an escaped pointer address! (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:36:3-36:20) + [Info][Unsound] Unknown address in {&tmp} has escaped. (92-distribute-fields-type-deep.c:44:3-44:17) + [Info][Unsound] Unknown value in {?} could be an escaped pointer address! (92-distribute-fields-type-deep.c:44:3-44:17) + [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..bf34d99936 --- /dev/null +++ b/tests/regression/04-mutex/93-distribute-fields-type-global.t @@ -0,0 +1,26 @@ + $ 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] Unknown address in {&tmp} has escaped. (93-distribute-fields-type-global.c:13:3-13:29) + [Info][Unsound] Unknown value in {?} could be an escaped pointer address! (93-distribute-fields-type-global.c:13:3-13:29) + [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/04-mutex/94-thread-unsafe_fun_rc.c b/tests/regression/04-mutex/94-thread-unsafe_fun_rc.c new file mode 100644 index 0000000000..8f2f01fc6d --- /dev/null +++ b/tests/regression/04-mutex/94-thread-unsafe_fun_rc.c @@ -0,0 +1,22 @@ +#include +#include + +pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER; +pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER; + +void *t_fun(void *arg) { + pthread_mutex_lock(&mutex1); + rand(); // RACE! + pthread_mutex_unlock(&mutex1); + return NULL; +} + +int main(void) { + pthread_t id; + pthread_create(&id, NULL, t_fun, NULL); + pthread_mutex_lock(&mutex2); + 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/98-thread-local-eq.c b/tests/regression/04-mutex/98-thread-local-eq.c new file mode 100644 index 0000000000..801494de21 --- /dev/null +++ b/tests/regression/04-mutex/98-thread-local-eq.c @@ -0,0 +1,26 @@ +#include +#include +#include + +__thread int myglobal; +pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER; +pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER; + +void *t_fun(void *arg) { + int top; + int* ptr = (int*) arg; + + if(top) { + ptr = &myglobal; + } + + __goblint_check(&myglobal == ptr); //UNKNOWN! + return NULL; +} + +int main(void) { + pthread_t id; + pthread_create(&id, NULL, t_fun, (void*) &myglobal); + pthread_join (id, NULL); + return 0; +} \ No newline at end of file diff --git a/tests/regression/04-mutex/99-volatile.c b/tests/regression/04-mutex/99-volatile.c new file mode 100644 index 0000000000..7c2a255902 --- /dev/null +++ b/tests/regression/04-mutex/99-volatile.c @@ -0,0 +1,53 @@ +// PARAM: --disable ana.race.volatile +#include + +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) { + 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, 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; +} diff --git a/tests/regression/05-lval_ls/19-idxunknown_unlock_precise.c b/tests/regression/05-lval_ls/19-idxunknown_unlock_precise.c new file mode 100644 index 0000000000..4b62b52cff --- /dev/null +++ b/tests/regression/05-lval_ls/19-idxunknown_unlock_precise.c @@ -0,0 +1,40 @@ +// PARAM: --enable ana.int.interval +// TODO because queries don't pass lvalue index intervals +extern int __VERIFIER_nondet_int(); +extern void abort(void); +void assume_abort_if_not(int cond) { + if(!cond) {abort();} +} + +#include +#include + +int data; +pthread_mutexattr_t mutexattr; +pthread_mutex_t m[10]; + +void *t_fun(void *arg) { + pthread_mutex_lock(&m[4]); + data++; // TODO NORACE + pthread_mutex_unlock(&m[4]); + return NULL; +} + +int main() { + pthread_mutexattr_init(&mutexattr); + pthread_mutexattr_settype(&mutexattr, PTHREAD_MUTEX_ERRORCHECK); + for (int i = 0; i < 10; i++) + pthread_mutex_init(&m[i], &mutexattr); + + int i = __VERIFIER_nondet_int(); + __goblint_assume(5 <= i); + __goblint_assume(i < 10); + pthread_t id; + pthread_create(&id, NULL, t_fun, NULL); + pthread_mutex_lock(&m[4]); + pthread_mutex_unlock(&m[i]); // no UB because ERRORCHECK + data++; // TODO NORACE + pthread_mutex_unlock(&m[4]); + return 0; +} + 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/01-symbeq_ints.c b/tests/regression/06-symbeq/01-symbeq_ints.c index a56c5a983f..0d0b6278fd 100644 --- a/tests/regression/06-symbeq/01-symbeq_ints.c +++ b/tests/regression/06-symbeq/01-symbeq_ints.c @@ -1,4 +1,4 @@ -// PARAM: --disable ana.mutex.disjoint_types --set ana.activated[+] "'var_eq'" +// PARAM: --enable ana.race.direct-arithmetic --set ana.activated[+] "'var_eq'" #include #include diff --git a/tests/regression/06-symbeq/02-funloop_norace.c b/tests/regression/06-symbeq/02-funloop_norace.c index bac9333349..e5bbc82a0c 100644 --- a/tests/regression/06-symbeq/02-funloop_norace.c +++ b/tests/regression/06-symbeq/02-funloop_norace.c @@ -1,4 +1,4 @@ -// PARAM: --disable ana.mutex.disjoint_types --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'" #include #include diff --git a/tests/regression/06-symbeq/03-funloop_simple.c b/tests/regression/06-symbeq/03-funloop_simple.c index 69c9006ef7..263cfa8124 100644 --- a/tests/regression/06-symbeq/03-funloop_simple.c +++ b/tests/regression/06-symbeq/03-funloop_simple.c @@ -1,4 +1,4 @@ -// PARAM: --disable ana.mutex.disjoint_types --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'" #include #include diff --git a/tests/regression/06-symbeq/04-funloop_hard1.c b/tests/regression/06-symbeq/04-funloop_hard1.c index b3fd1479eb..b62775aa33 100644 --- a/tests/regression/06-symbeq/04-funloop_hard1.c +++ b/tests/regression/06-symbeq/04-funloop_hard1.c @@ -1,4 +1,4 @@ -// PARAM: --disable ana.mutex.disjoint_types --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'" #include #include diff --git a/tests/regression/06-symbeq/05-funloop_hard2.c b/tests/regression/06-symbeq/05-funloop_hard2.c index 49e7f3f42d..29d38a7875 100644 --- a/tests/regression/06-symbeq/05-funloop_hard2.c +++ b/tests/regression/06-symbeq/05-funloop_hard2.c @@ -1,4 +1,4 @@ -// PARAM: --disable ana.mutex.disjoint_types --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'" #include #include diff --git a/tests/regression/06-symbeq/06-tricky_address1.c b/tests/regression/06-symbeq/06-tricky_address1.c index fe83b3cf4f..25c7705c8c 100644 --- a/tests/regression/06-symbeq/06-tricky_address1.c +++ b/tests/regression/06-symbeq/06-tricky_address1.c @@ -1,4 +1,4 @@ -// PARAM: --disable ana.mutex.disjoint_types --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'" extern int __VERIFIER_nondet_int(); extern void abort(void); void assume_abort_if_not(int cond) { diff --git a/tests/regression/06-symbeq/07-tricky_address2.c b/tests/regression/06-symbeq/07-tricky_address2.c index edf22cc354..8a25bbd3a3 100644 --- a/tests/regression/06-symbeq/07-tricky_address2.c +++ b/tests/regression/06-symbeq/07-tricky_address2.c @@ -1,4 +1,4 @@ -// PARAM: --disable ana.mutex.disjoint_types --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'" extern int __VERIFIER_nondet_int(); extern void abort(void); void assume_abort_if_not(int cond) { diff --git a/tests/regression/06-symbeq/08-tricky_address3.c b/tests/regression/06-symbeq/08-tricky_address3.c index 6372b6c27e..1a8160ea6f 100644 --- a/tests/regression/06-symbeq/08-tricky_address3.c +++ b/tests/regression/06-symbeq/08-tricky_address3.c @@ -1,4 +1,4 @@ -// PARAM: --disable ana.mutex.disjoint_types --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'" extern int __VERIFIER_nondet_int(); extern void abort(void); void assume_abort_if_not(int cond) { diff --git a/tests/regression/06-symbeq/09-tricky_address4.c b/tests/regression/06-symbeq/09-tricky_address4.c index 929832ca81..1d1e10861f 100644 --- a/tests/regression/06-symbeq/09-tricky_address4.c +++ b/tests/regression/06-symbeq/09-tricky_address4.c @@ -1,4 +1,4 @@ -// PARAM: --disable ana.mutex.disjoint_types --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'" extern int __VERIFIER_nondet_int(); extern void abort(void); void assume_abort_if_not(int cond) { diff --git a/tests/regression/06-symbeq/10-equ_rc.c b/tests/regression/06-symbeq/10-equ_rc.c index 9f7fbf47f9..51f09b59ed 100644 --- a/tests/regression/06-symbeq/10-equ_rc.c +++ b/tests/regression/06-symbeq/10-equ_rc.c @@ -1,4 +1,4 @@ -// PARAM: --disable ana.mutex.disjoint_types --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'" extern int __VERIFIER_nondet_int(); #include diff --git a/tests/regression/06-symbeq/11-equ_nr.c b/tests/regression/06-symbeq/11-equ_nr.c index f3525ce9c2..5e1d6cd9ff 100644 --- a/tests/regression/06-symbeq/11-equ_nr.c +++ b/tests/regression/06-symbeq/11-equ_nr.c @@ -1,4 +1,4 @@ -// PARAM: --disable ana.mutex.disjoint_types --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'" extern int __VERIFIER_nondet_int(); #include diff --git a/tests/regression/06-symbeq/13-equ_proc_nr.c b/tests/regression/06-symbeq/13-equ_proc_nr.c index e13dd898b3..0a33f8780f 100644 --- a/tests/regression/06-symbeq/13-equ_proc_nr.c +++ b/tests/regression/06-symbeq/13-equ_proc_nr.c @@ -1,4 +1,4 @@ -// PARAM: --disable ana.mutex.disjoint_types --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'" extern int __VERIFIER_nondet_int(); #include diff --git a/tests/regression/06-symbeq/14-list_entry_rc.c b/tests/regression/06-symbeq/14-list_entry_rc.c index cdf4e3caee..ccf5da4234 100644 --- a/tests/regression/06-symbeq/14-list_entry_rc.c +++ b/tests/regression/06-symbeq/14-list_entry_rc.c @@ -1,4 +1,4 @@ -// PARAM: --disable ana.mutex.disjoint_types --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'" #include #include #include diff --git a/tests/regression/06-symbeq/15-list_entry_nr.c b/tests/regression/06-symbeq/15-list_entry_nr.c index 84397101d9..419815915b 100644 --- a/tests/regression/06-symbeq/15-list_entry_nr.c +++ b/tests/regression/06-symbeq/15-list_entry_nr.c @@ -1,4 +1,4 @@ -// PARAM: --disable ana.mutex.disjoint_types --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'" #include #include #include diff --git a/tests/regression/06-symbeq/16-type_rc.c b/tests/regression/06-symbeq/16-type_rc.c index b3767fe1bb..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: --disable ana.mutex.disjoint_types --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'" #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 new file mode 100644 index 0000000000..b63471a45e --- /dev/null +++ b/tests/regression/06-symbeq/16-type_rc.t @@ -0,0 +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: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: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: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: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/17-type_nr.c b/tests/regression/06-symbeq/17-type_nr.c index b6237ab054..8919f0ad99 100644 --- a/tests/regression/06-symbeq/17-type_nr.c +++ b/tests/regression/06-symbeq/17-type_nr.c @@ -1,4 +1,4 @@ -// PARAM: --disable ana.mutex.disjoint_types --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'" #include struct s { diff --git a/tests/regression/06-symbeq/18-symbeq_addrs.c b/tests/regression/06-symbeq/18-symbeq_addrs.c index 63c6d68340..6cd5e8e49e 100644 --- a/tests/regression/06-symbeq/18-symbeq_addrs.c +++ b/tests/regression/06-symbeq/18-symbeq_addrs.c @@ -1,4 +1,4 @@ -// PARAM: --disable ana.mutex.disjoint_types --set ana.activated[+] "'var_eq'" +// PARAM: --enable ana.race.direct-arithmetic --set ana.activated[+] "'var_eq'" #include #include diff --git a/tests/regression/06-symbeq/19-symbeq_funcs.c b/tests/regression/06-symbeq/19-symbeq_funcs.c index ab5e8bd1b7..f9d85349a0 100644 --- a/tests/regression/06-symbeq/19-symbeq_funcs.c +++ b/tests/regression/06-symbeq/19-symbeq_funcs.c @@ -1,4 +1,4 @@ -// PARAM: --disable ana.mutex.disjoint_types --set ana.activated[+] "'var_eq'" +// PARAM: --enable ana.race.direct-arithmetic --set ana.activated[+] "'var_eq'" #include void inc(int * a){ diff --git a/tests/regression/06-symbeq/20-mult_accs_nr.c b/tests/regression/06-symbeq/20-mult_accs_nr.c index 859349fc94..7d66e3f5d2 100644 --- a/tests/regression/06-symbeq/20-mult_accs_nr.c +++ b/tests/regression/06-symbeq/20-mult_accs_nr.c @@ -1,4 +1,4 @@ -// SKIP PARAM: --disable ana.mutex.disjoint_types --set ana.activated[+] "'var_eq'" --set ana.activated[+] "'symb_locks'" +// SKIP PARAM: --enable ana.race.direct-arithmetic --set ana.activated[+] "'var_eq'" --set ana.activated[+] "'symb_locks'" #include struct s { @@ -10,7 +10,7 @@ struct s { extern struct s *get_s(); void *t_fun(void *arg) { - struct s *s; + struct s *s; s = get_s(); pthread_mutex_lock(&s->mutex); s->data = 5; // NORACE diff --git a/tests/regression/06-symbeq/21-mult_accs_rc.c b/tests/regression/06-symbeq/21-mult_accs_rc.c index b7aa6d9c7e..62550fab55 100644 --- a/tests/regression/06-symbeq/21-mult_accs_rc.c +++ b/tests/regression/06-symbeq/21-mult_accs_rc.c @@ -1,4 +1,4 @@ -// PARAM: --disable ana.mutex.disjoint_types --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'" #include struct s { diff --git a/tests/regression/06-symbeq/21-mult_accs_rc.t b/tests/regression/06-symbeq/21-mult_accs_rc.t new file mode 100644 index 0000000000..227c66058e --- /dev/null +++ b/tests/regression/06-symbeq/21-mult_accs_rc.t @@ -0,0 +1,34 @@ +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'" 21-mult_accs_rc.c + [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (21-mult_accs_rc.c:14:3-14:32) + [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (21-mult_accs_rc.c:16:3-16:14) + [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (21-mult_accs_rc.c:17:3-17:32) + [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (21-mult_accs_rc.c:28:3-28:16) + [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (21-mult_accs_rc.c:29:3-29:15) + [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (21-mult_accs_rc.c:34:3-34:9) + [Warning][Race] Memory location (struct s).data (race with conf. 100): + write with [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) + [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (21-mult_accs_rc.c:16:3-16:14) + [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (21-mult_accs_rc.c:17:3-17:32) + [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (21-mult_accs_rc.c:28:3-28:16) + [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (21-mult_accs_rc.c:29:3-29:15) + [Warning][Behavior > Undefined > NullPointerDereference][CWE-476] May dereference NULL pointer (21-mult_accs_rc.c:34:3-34:9) + [Success][Race] Memory location (struct s).data (safe): + write with [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/06-symbeq/22-var_eq_types.c b/tests/regression/06-symbeq/22-var_eq_types.c index 853691b914..348ea1574a 100644 --- a/tests/regression/06-symbeq/22-var_eq_types.c +++ b/tests/regression/06-symbeq/22-var_eq_types.c @@ -1,4 +1,4 @@ -// PARAM: --disable ana.mutex.disjoint_types --set ana.activated[+] "'var_eq'" +// PARAM: --enable ana.race.direct-arithmetic --set ana.activated[+] "'var_eq'" #include #include diff --git a/tests/regression/06-symbeq/31-zstd-thread-pool.c b/tests/regression/06-symbeq/31-zstd-thread-pool.c index 561f4d6c70..b1782a01a2 100644 --- a/tests/regression/06-symbeq/31-zstd-thread-pool.c +++ b/tests/regression/06-symbeq/31-zstd-thread-pool.c @@ -1,4 +1,5 @@ -// PARAM: --set ana.activated[+] symb_locks +// PARAM: --set ana.activated[+] symb_locks --set lib.activated[+] zstd --disable ana.race.free +// disabled free races because unsound: https://github.com/goblint/analyzer/pull/978 /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) Facebook, Inc. diff --git a/tests/regression/06-symbeq/35-zstd-thread-pool-multi.c b/tests/regression/06-symbeq/35-zstd-thread-pool-multi.c index 1ef6cf869a..0bf79fbc7f 100644 --- a/tests/regression/06-symbeq/35-zstd-thread-pool-multi.c +++ b/tests/regression/06-symbeq/35-zstd-thread-pool-multi.c @@ -1,4 +1,5 @@ -// PARAM: --set ana.activated[+] symb_locks --set ana.activated[+] mallocFresh +// PARAM: --set ana.activated[+] symb_locks --set ana.activated[+] mallocFresh --set lib.activated[+] zstd --disable ana.race.free +// disabled free races because unsound: https://github.com/goblint/analyzer/pull/978 /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) Facebook, Inc. diff --git a/tests/regression/06-symbeq/36-zstd-thread-pool-add.c b/tests/regression/06-symbeq/36-zstd-thread-pool-add.c index 8544975a29..1807edda9e 100644 --- a/tests/regression/06-symbeq/36-zstd-thread-pool-add.c +++ b/tests/regression/06-symbeq/36-zstd-thread-pool-add.c @@ -1,4 +1,5 @@ -// PARAM: --set ana.activated[+] symb_locks --set ana.activated[+] var_eq --set exp.extraspecials[+] ZSTD_customMalloc --set exp.extraspecials[+] ZSTD_customCalloc +// PARAM: --set ana.activated[+] symb_locks --set ana.activated[+] var_eq --set lib.activated[+] zstd --set exp.extraspecials[+] ZSTD_customMalloc --set exp.extraspecials[+] ZSTD_customCalloc --disable ana.race.free +// disabled free races because unsound: https://github.com/goblint/analyzer/pull/978 /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) Facebook, Inc. diff --git a/tests/regression/06-symbeq/37-funloop_index.c b/tests/regression/06-symbeq/37-funloop_index.c index d4c269cc05..2bb9929ffb 100644 --- a/tests/regression/06-symbeq/37-funloop_index.c +++ b/tests/regression/06-symbeq/37-funloop_index.c @@ -1,4 +1,4 @@ -// PARAM: --disable ana.mutex.disjoint_types --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'" // copy of 06/02 with additional index accesses #include #include diff --git a/tests/regression/06-symbeq/38-chrony-name2ipaddress.c b/tests/regression/06-symbeq/38-chrony-name2ipaddress.c index db9abf7123..7ab012e225 100644 --- a/tests/regression/06-symbeq/38-chrony-name2ipaddress.c +++ b/tests/regression/06-symbeq/38-chrony-name2ipaddress.c @@ -1,4 +1,5 @@ -// PARAM: --set ana.activated[+] "'var_eq'" --set ana.activated[+] "'symb_locks'" --set ana.activated[+] "'mallocFresh'" --set ana.malloc.wrappers '["Malloc"]' --disable sem.unknown_function.spawn --disable sem.unknown_function.invalidate.globals --set pre.cppflags[+] -D_FORTIFY_SOURCE=2 --set pre.cppflags[+] -O3 +// PARAM: --set ana.activated[+] "'var_eq'" --set ana.activated[+] "'symb_locks'" --set ana.activated[+] "'mallocFresh'" --set ana.malloc.wrappers '["Malloc"]' --disable sem.unknown_function.spawn --disable sem.unknown_function.invalidate.globals --set pre.cppflags[+] -D_FORTIFY_SOURCE=2 --set pre.cppflags[+] -O3 --disable ana.race.free +// Disabled races from free because type-based memory locations don't know the getaddrinfo-free pattern is safe. #include #include // #include diff --git a/tests/regression/06-symbeq/39-funloop_index_bad.c b/tests/regression/06-symbeq/39-funloop_index_bad.c index 1983887796..122b82d6c9 100644 --- a/tests/regression/06-symbeq/39-funloop_index_bad.c +++ b/tests/regression/06-symbeq/39-funloop_index_bad.c @@ -1,4 +1,4 @@ -// PARAM: --disable ana.mutex.disjoint_types --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'" // copy of 06/02 with additional index accesses (that are wrong) #include #include diff --git a/tests/regression/06-symbeq/41-var_eq_multithread.c b/tests/regression/06-symbeq/41-var_eq_multithread.c new file mode 100644 index 0000000000..dd92a75503 --- /dev/null +++ b/tests/regression/06-symbeq/41-var_eq_multithread.c @@ -0,0 +1,38 @@ +// SKIP PARAM: --set ana.activated[+] var_eq +#include +//#include +#include +#include + +pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; + +void f(int *zptr){ + pthread_mutex_unlock(&mutex); +} + +void *t_fun1(void *arg) { + int x; + int *zptr; + zptr = arg; + + *zptr = x; + sleep(10); + // here the other thread (id2) could potentially change *zptr, as it is linked to the same int z + //assert(*zptr == x); //UNKNOWN! + __goblint_check(*zptr == x); //UNKNOWN! + return NULL; +} + +void *t_fun2(void *arg) { + *(int*)arg = 27; +} + +int main() { + pthread_t id1, id2; + int z; + + pthread_create(&id1, NULL, t_fun1, &z); + pthread_create(&id2, NULL, t_fun2, &z); + pthread_join (id1, NULL); + pthread_join (id2, NULL); +} diff --git a/tests/regression/06-symbeq/43-type_nr_disjoint_types.c b/tests/regression/06-symbeq/43-type_nr_disjoint_types.c new file mode 100644 index 0000000000..d279b8d154 --- /dev/null +++ b/tests/regression/06-symbeq/43-type_nr_disjoint_types.c @@ -0,0 +1,31 @@ +// PARAM: --disable ana.race.direct-arithmetic --set ana.activated[+] "'var_eq'" --set ana.activated[+] "'symb_locks'" +#include + +struct s { + int datum; + pthread_mutex_t mutex; +}; + +extern struct s *get_s(); + +void *t_fun(void *arg) { + struct s *s = get_s(); + s->datum = 5; // NORACE (disjoint types) + return NULL; +} + +int main () { + int *d; + struct s *s; + pthread_t id; + pthread_mutex_t *m; + + s = get_s(); + m = &s->mutex; + d = &s->datum; + + pthread_create(&id,NULL,t_fun,NULL); + *d = 8; // NORACE (disjoint types) + + return 0; +} diff --git a/tests/regression/06-symbeq/44-type_rc_type_field.c b/tests/regression/06-symbeq/44-type_rc_type_field.c new file mode 100644 index 0000000000..d9d1149c7f --- /dev/null +++ b/tests/regression/06-symbeq/44-type_rc_type_field.c @@ -0,0 +1,31 @@ +// PARAM: --disable ana.race.direct-arithmetic --set ana.activated[+] "'var_eq'" --set ana.activated[+] "'symb_locks'" +#include + +struct s { + int datum; + pthread_mutex_t mutex; +}; + +extern struct s *get_s(); + +void *t_fun(void *arg) { + struct s *s = get_s(); + s->datum = 5; // RACE! + return NULL; +} + +int main () { + int *d; + struct s *s; + pthread_t id; + pthread_mutex_t *m; + + s = get_s(); + m = &s->mutex; + d = &s->datum; + + pthread_create(&id,NULL,t_fun,NULL); + s->datum = 5; // RACE! + + return 0; +} diff --git a/tests/regression/06-symbeq/45-zstd-thread-pool-free.c b/tests/regression/06-symbeq/45-zstd-thread-pool-free.c new file mode 100644 index 0000000000..248e02a728 --- /dev/null +++ b/tests/regression/06-symbeq/45-zstd-thread-pool-free.c @@ -0,0 +1,264 @@ +// PARAM: --set ana.activated[+] symb_locks +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) Facebook, Inc. + * All rights reserved. + * + * This code is a challenging example for race detection extracted from zstd. + * Copyright (c) The Goblint Contributors + */ + +#include +#include +#include +#define ZSTD_pthread_mutex_t pthread_mutex_t +#define ZSTD_pthread_mutex_init(a, b) pthread_mutex_init((a), (b)) +#define ZSTD_pthread_mutex_destroy(a) pthread_mutex_destroy((a)) +#define ZSTD_pthread_mutex_lock(a) pthread_mutex_lock((a)) +#define ZSTD_pthread_mutex_unlock(a) pthread_mutex_unlock((a)) + +#define ZSTD_pthread_cond_t pthread_cond_t +#define ZSTD_pthread_cond_init(a, b) pthread_cond_init((a), (b)) +#define ZSTD_pthread_cond_destroy(a) pthread_cond_destroy((a)) +#define ZSTD_pthread_cond_wait(a, b) pthread_cond_wait((a), (b)) +#define ZSTD_pthread_cond_signal(a) pthread_cond_signal((a)) +#define ZSTD_pthread_cond_broadcast(a) pthread_cond_broadcast((a)) + +#define ZSTD_pthread_t pthread_t +#define ZSTD_pthread_create(a, b, c, d) pthread_create((a), (b), (c), (d)) +#define ZSTD_pthread_join(a, b) pthread_join((a),(b)) + +#define ZSTD_malloc(s) malloc(s) +#define ZSTD_calloc(n,s) calloc((n), (s)) +#define ZSTD_free(p) free((p)) +#define ZSTD_memset(d,s,n) __builtin_memset((d),(s),(n)) + +typedef struct POOL_ctx_s POOL_ctx; + +typedef void* (*ZSTD_allocFunction) (void* opaque, size_t size); +typedef void (*ZSTD_freeFunction) (void* opaque, void* address); +typedef struct { ZSTD_allocFunction customAlloc; ZSTD_freeFunction customFree; void* opaque; } ZSTD_customMem; +typedef struct POOL_ctx_s ZSTD_threadPool; + + +void* ZSTD_customMalloc(size_t size, ZSTD_customMem customMem) +{ + if (customMem.customAlloc) + return customMem.customAlloc(customMem.opaque, size); + return ZSTD_malloc(size); +} + +void* ZSTD_customCalloc(size_t size, ZSTD_customMem customMem) +{ + if (customMem.customAlloc) { + /* calloc implemented as malloc+memset; + * not as efficient as calloc, but next best guess for custom malloc */ + void* const ptr = customMem.customAlloc(customMem.opaque, size); + ZSTD_memset(ptr, 0, size); + return ptr; + } + return ZSTD_calloc(1, size); +} + +void ZSTD_customFree(void* ptr, ZSTD_customMem customMem) +{ + if (ptr!=NULL) { + if (customMem.customFree) + customMem.customFree(customMem.opaque, ptr); + else + ZSTD_free(ptr); // RACE + } +} + + + +/*! POOL_create() : + * Create a thread pool with at most `numThreads` threads. + * `numThreads` must be at least 1. + * The maximum number of queued jobs before blocking is `queueSize`. + * @return : POOL_ctx pointer on success, else NULL. +*/ +POOL_ctx* POOL_create(size_t numThreads, size_t queueSize); + +POOL_ctx* POOL_create_advanced(size_t numThreads, size_t queueSize, ZSTD_customMem customMem); + +/*! POOL_free() : + * Free a thread pool returned by POOL_create(). + */ +void POOL_free(POOL_ctx* ctx); + + +/*! POOL_function : + * The function type that can be added to a thread pool. + */ +typedef void (*POOL_function)(void*); + + +static +#ifdef __GNUC__ +__attribute__((__unused__)) +#endif +ZSTD_customMem const ZSTD_defaultCMem = { NULL, NULL, NULL }; /**< this constant defers to stdlib's functions */ + + +/* A job is a function and an opaque argument */ +typedef struct POOL_job_s { + POOL_function function; + void *opaque; +} POOL_job; + +struct POOL_ctx_s { + ZSTD_customMem customMem; + /* Keep track of the threads */ + ZSTD_pthread_t* threads; + size_t threadCapacity; + size_t threadLimit; + + /* The queue is a circular buffer */ + POOL_job *queue; + size_t queueHead; + size_t queueTail; + size_t queueSize; + + /* The number of threads working on jobs */ + size_t numThreadsBusy; + /* Indicates if the queue is empty */ + int queueEmpty; + + /* The mutex protects the queue */ + ZSTD_pthread_mutex_t queueMutex; + /* Condition variable for pushers to wait on when the queue is full */ + ZSTD_pthread_cond_t queuePushCond; + /* Condition variables for poppers to wait on when the queue is empty */ + ZSTD_pthread_cond_t queuePopCond; + /* Indicates if the queue is shutting down */ + int shutdown; +}; + +/* POOL_thread() : + * Work thread for the thread pool. + * Waits for jobs and executes them. + * @returns : NULL on failure else non-null. + */ +static void* POOL_thread(void* opaque) { + POOL_ctx* const ctx = (POOL_ctx*)opaque; + if (!ctx) { return NULL; } + for (;;) { + /* Lock the mutex and wait for a non-empty queue or until shutdown */ + ZSTD_pthread_mutex_lock(&ctx->queueMutex); + + while ( ctx->queueEmpty // RACE! (threadLimit) + || (ctx->numThreadsBusy >= ctx->threadLimit) ) { + if (ctx->shutdown) { + /* even if !queueEmpty, (possible if numThreadsBusy >= threadLimit), + * a few threads will be shutdown while !queueEmpty, + * but enough threads will remain active to finish the queue */ + ZSTD_pthread_mutex_unlock(&ctx->queueMutex); + return opaque; + } + ZSTD_pthread_cond_wait(&ctx->queuePopCond, &ctx->queueMutex); + } + /* Pop a job off the queue */ + { POOL_job const job = ctx->queue[ctx->queueHead]; // TODO NORACE + ctx->queueHead = (ctx->queueHead + 1) % ctx->queueSize; // RACE + ctx->numThreadsBusy++; // RACE + ctx->queueEmpty = (ctx->queueHead == ctx->queueTail); // RACE + /* Unlock the mutex, signal a pusher, and run the job */ + ZSTD_pthread_cond_signal(&ctx->queuePushCond); + ZSTD_pthread_mutex_unlock(&ctx->queueMutex); + + job.function(job.opaque); + + /* If the intended queue size was 0, signal after finishing job */ + ZSTD_pthread_mutex_lock(&ctx->queueMutex); + ctx->numThreadsBusy--; // RACE + ZSTD_pthread_cond_signal(&ctx->queuePushCond); + ZSTD_pthread_mutex_unlock(&ctx->queueMutex); + } + } /* for (;;) */ + __goblint_check(0); //NOWARN (unreachable) +} + +POOL_ctx* POOL_create(size_t numThreads, size_t queueSize) { + return POOL_create_advanced(numThreads, queueSize, ZSTD_defaultCMem); +} + +POOL_ctx* POOL_create_advanced(size_t numThreads, size_t queueSize, + ZSTD_customMem customMem) +{ + POOL_ctx* ctx; + /* Check parameters */ + if (!numThreads) { return NULL; } + /* Allocate the context and zero initialize */ + ctx = (POOL_ctx*)ZSTD_customCalloc(sizeof(POOL_ctx), customMem); + if (!ctx) { return NULL; } + /* Initialize the job queue. + * It needs one extra space since one space is wasted to differentiate + * empty and full queues. + */ + ctx->queueSize = queueSize + 1; + ctx->queue = (POOL_job*)ZSTD_customMalloc(ctx->queueSize * sizeof(POOL_job), customMem); + ctx->queueHead = 0; + ctx->queueTail = 0; + ctx->numThreadsBusy = 0; + ctx->queueEmpty = 1; + { + int error = 0; + error |= ZSTD_pthread_mutex_init(&ctx->queueMutex, NULL); + error |= ZSTD_pthread_cond_init(&ctx->queuePushCond, NULL); + error |= ZSTD_pthread_cond_init(&ctx->queuePopCond, NULL); + if (error) { POOL_free(ctx); return NULL; } + } + ctx->shutdown = 0; + /* Allocate space for the thread handles */ + ctx->threads = (ZSTD_pthread_t*)ZSTD_customMalloc(numThreads * sizeof(ZSTD_pthread_t), customMem); + ctx->threadCapacity = 0; + ctx->customMem = customMem; + /* Check for errors */ + if (!ctx->threads || !ctx->queue) { POOL_free(ctx); return NULL; } + /* Initialize the threads */ + { size_t i; + for (i = 0; i < numThreads; ++i) { + if (ZSTD_pthread_create(&ctx->threads[i], NULL, &POOL_thread, ctx)) { + ctx->threadCapacity = i; + POOL_free(ctx); + return NULL; + } } + ctx->threadCapacity = numThreads; + ctx->threadLimit = numThreads; // RACE! + } + return ctx; +} + +/*! POOL_join() : + Shutdown the queue, wake any sleeping threads, and join all of the threads. +*/ +static void POOL_join(POOL_ctx* ctx) { + /* Shut down the queue */ + ZSTD_pthread_mutex_lock(&ctx->queueMutex); + ctx->shutdown = 1; //NORACE + ZSTD_pthread_mutex_unlock(&ctx->queueMutex); + /* Wake up sleeping threads */ + ZSTD_pthread_cond_broadcast(&ctx->queuePushCond); + ZSTD_pthread_cond_broadcast(&ctx->queuePopCond); + /* Join all of the threads */ + { size_t i; + for (i = 0; i < ctx->threadCapacity; ++i) { + ZSTD_pthread_join(ctx->threads[i], NULL); /* note : could fail */ + } } +} + +void POOL_free(POOL_ctx *ctx) { + if (!ctx) { return; } + POOL_join(ctx); + ZSTD_pthread_mutex_destroy(&ctx->queueMutex); + ZSTD_pthread_cond_destroy(&ctx->queuePushCond); + ZSTD_pthread_cond_destroy(&ctx->queuePopCond); + ZSTD_customFree(ctx->queue, ctx->customMem); + ZSTD_customFree(ctx->threads, ctx->customMem); + ZSTD_customFree(ctx, ctx->customMem); +} + +int main() { + POOL_ctx* const ctx = POOL_create(20, 10); +} diff --git a/tests/regression/06-symbeq/46-calloc-free.c b/tests/regression/06-symbeq/46-calloc-free.c new file mode 100644 index 0000000000..e0fdd9ad3a --- /dev/null +++ b/tests/regression/06-symbeq/46-calloc-free.c @@ -0,0 +1,63 @@ +// PARAM: --set ana.activated[+] symb_locks +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) Facebook, Inc. + * All rights reserved. + * + * This code is a challenging example for race detection extracted from zstd. + * Copyright (c) The Goblint Contributors + */ + +#include +#include + +typedef struct POOL_ctx_s POOL_ctx; + +struct POOL_ctx_s { + pthread_t* threads; + size_t numThreadsBusy; + pthread_mutex_t queueMutex; +}; + +static void* POOL_thread(void* opaque) { + POOL_ctx* const ctx = (POOL_ctx*)opaque; + for (;;) { + /* Lock the mutex and wait for a non-empty queue or until shutdown */ + pthread_mutex_lock(&ctx->queueMutex); + ctx->numThreadsBusy++; // RACE! + pthread_mutex_unlock(&ctx->queueMutex); + } +} + +void POOL_free(POOL_ctx *ctx) { + pthread_mutex_destroy(&ctx->queueMutex); + free(ctx->threads); + free(ctx); // RACE! +} + +POOL_ctx* POOL_create(size_t numThreads) { + POOL_ctx* ctx; + ctx = (POOL_ctx*)calloc(1, sizeof(POOL_ctx)); + if (!ctx) { return NULL; } + ctx->numThreadsBusy = 0; + { + int error = 0; + error |= pthread_mutex_init(&ctx->queueMutex, NULL); + if (error) { POOL_free(ctx); return NULL; } + } + ctx->threads = (pthread_t*)malloc(numThreads * sizeof(pthread_t)); + if (!ctx->threads) { POOL_free(ctx); return NULL; } + { size_t i; + for (i = 0; i < numThreads; ++i) { + if (pthread_create(&ctx->threads[i], NULL, &POOL_thread, ctx)) { + POOL_free(ctx); + return NULL; + } } + } + return ctx; +} + + +int main() { + POOL_ctx* const ctx = POOL_create(20); +} diff --git a/tests/regression/06-symbeq/50-type_array_via_ptr_rc.c b/tests/regression/06-symbeq/50-type_array_via_ptr_rc.c new file mode 100644 index 0000000000..1c407f1110 --- /dev/null +++ b/tests/regression/06-symbeq/50-type_array_via_ptr_rc.c @@ -0,0 +1,35 @@ +// PARAM: --enable ana.race.direct-arithmetic --set ana.activated[+] "'var_eq'" --set ana.activated[+] "'symb_locks'" +#include + +struct s { + int datum[2]; + int datums[2][2][2]; + pthread_mutex_t mutex; +}; + +extern struct s *get_s(); + +void *t_fun(void *arg) { + struct s *s = get_s(); + s->datum[1] = 5; // RACE! + s->datums[1][1][1] = 5; // RACE! + return NULL; +} + +int main () { + int *d, *e; + struct s *s; + pthread_t id; + pthread_mutex_t *m; + + s = get_s(); + m = &s->mutex; + d = &s->datum[1]; + e = &s->datums[1][1][1]; + + pthread_create(&id,NULL,t_fun,NULL); + *d = 8; // RACE! + *e = 8; // RACE! + + return 0; +} diff --git a/tests/regression/06-symbeq/51-typedef_rc.c b/tests/regression/06-symbeq/51-typedef_rc.c new file mode 100644 index 0000000000..a4faa1fb8a --- /dev/null +++ b/tests/regression/06-symbeq/51-typedef_rc.c @@ -0,0 +1,30 @@ +// PARAM: --enable ana.race.direct-arithmetic --set ana.activated[+] "'var_eq'" --set ana.activated[+] "'symb_locks'" +// Simplified example from the silver searcher +#include + +typedef struct { + char *color_match; +} cli_options; + +struct print_context { + char **context_prev_lines; +}; + +extern struct print_context *get_print_context(); + +cli_options opts; + +void *t_fun(void *arg) { + opts.color_match = "\033[30;43m"; // RACE! + return NULL; +} + +int main(void) { + struct print_context *s; + pthread_t id; + + s = get_print_context(); + pthread_create(&id,NULL,t_fun,NULL); + char *x = s->context_prev_lines[2]; // RACE! + return 0; +} diff --git a/tests/regression/06-symbeq/52-typedef2_rc.c b/tests/regression/06-symbeq/52-typedef2_rc.c new file mode 100644 index 0000000000..920d443b52 --- /dev/null +++ b/tests/regression/06-symbeq/52-typedef2_rc.c @@ -0,0 +1,26 @@ +// PARAM: --enable ana.race.direct-arithmetic --set ana.activated[+] "'var_eq'" --set ana.activated[+] "'symb_locks'" +// MANUAL must have race on (int), not safe on (int) and (int2) +#include + +typedef int int2; + +extern int *get_s(); + +void *t_fun(void *arg) { + int2 *s = get_s(); + *s = 5; // RACE! + return NULL; +} + +int main () { + int *d; + pthread_t id; + pthread_mutex_t *m; + + d = get_s(); + + pthread_create(&id,NULL,t_fun,NULL); + *d = 8; // RACE! + + return 0; +} diff --git a/tests/regression/06-symbeq/dune b/tests/regression/06-symbeq/dune new file mode 100644 index 0000000000..23c0dd3290 --- /dev/null +++ b/tests/regression/06-symbeq/dune @@ -0,0 +1,2 @@ +(cram + (deps (glob_files *.c))) diff --git a/tests/regression/09-regions/39-evilcollapse_rc_wpoint.c b/tests/regression/09-regions/39-evilcollapse_rc_wpoint.c new file mode 100644 index 0000000000..b56377940a --- /dev/null +++ b/tests/regression/09-regions/39-evilcollapse_rc_wpoint.c @@ -0,0 +1,28 @@ +// FIXPOINT issue with some old solvers: https://github.com/goblint/analyzer/pull/66 +// Currently also with topdown_term and topdown_space_cache_term +#include + +struct list_head { + struct list_head *next ; + struct list_head *prev ; +}; + +struct list_head c[10] ; + +static void INIT_LIST_HEAD(struct list_head *list) { + return; +} + +static struct list_head *lookup () { + int i = 0; + return c[i].next; +} + +int main() { + struct list_head *p1, *p2; + INIT_LIST_HEAD(&c[0]); + for ( ; 0; ) { } + p1 = lookup(); + p1->next = p2; + 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/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/11-heap/14-list_entry_rc-unroll.c b/tests/regression/11-heap/14-list_entry_rc-unroll.c index 37e262b611..dfe103dd3e 100644 --- a/tests/regression/11-heap/14-list_entry_rc-unroll.c +++ b/tests/regression/11-heap/14-list_entry_rc-unroll.c @@ -1,4 +1,4 @@ -// PARAM: --disable ana.mutex.disjoint_types --set ana.activated[+] "'var_eq'" --set ana.activated[+] "'symb_locks'" --set ana.malloc.unique_address_count 1 +// PARAM: --enable ana.race.direct-arithmetic --set ana.activated[+] "'var_eq'" --set ana.activated[+] "'symb_locks'" --set ana.malloc.unique_address_count 1 // Copied from 06-symbeq/14-list_entry_rc, proven safe thanks to unique address #include #include diff --git a/tests/regression/11-heap/15-malloc_unique_addresses_struct.c b/tests/regression/11-heap/15-malloc_unique_addresses_struct.c new file mode 100644 index 0000000000..1f49c53dc2 --- /dev/null +++ b/tests/regression/11-heap/15-malloc_unique_addresses_struct.c @@ -0,0 +1,26 @@ +// PARAM: --set ana.malloc.unique_address_count 1 + +#include +#include +#include + +struct s { + int x; + int y; +}; + +int main() { + struct s *ptr = malloc(sizeof(struct s)); + int p; + + ptr->x = 0; + ptr->y = 1; + + __goblint_check(ptr->x == 0); + __goblint_check(ptr->y == 1); + + ptr->x = 1; + ptr->y = 0; + __goblint_check(ptr->x == 1); + __goblint_check(ptr->y == 0); +} diff --git a/tests/regression/27-inv_invariants/18-union-float-int.c b/tests/regression/27-inv_invariants/18-union-float-int.c new file mode 100644 index 0000000000..d56f7d6c56 --- /dev/null +++ b/tests/regression/27-inv_invariants/18-union-float-int.c @@ -0,0 +1,28 @@ +// PARAM: --enable ana.float.interval +#include +union u { + int x; + float f; +}; + +int main(){ + union u a; + union u b; + + a.x = 12; + b.f = 129.0; + + int i = 0; + if(a.x == b.x){ + i++; + } + // Should not be dead after if + __goblint_check(1); + + if(a.f == b.f){ + i++; + } + // Should not be dead after if + __goblint_check(1); + return 0; +} diff --git a/tests/regression/27-inv_invariants/19-union-char-int.c b/tests/regression/27-inv_invariants/19-union-char-int.c new file mode 100644 index 0000000000..1cfbc1f0b1 --- /dev/null +++ b/tests/regression/27-inv_invariants/19-union-char-int.c @@ -0,0 +1,38 @@ +// PARAM: --enable ana.float.interval +#include +union u { + int x; + char c; +}; + +int main(){ + union u a; + union u b; + + a.x = 12; + b.c = 12; + + int i = 0; + if(a.x == b.x){ + i++; + } + // Should not be dead after if + __goblint_check(1); + + a.x = 257; + b.c = 1; + + if(a.x == b.x){ + i++; + } + // Should not be dead after if + __goblint_check(1); + + if(a.c == b.c){ + i++; + } + // Should not be dead after if + __goblint_check(1); + + return 0; +} 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 492e836d1f..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: --disable ana.mutex.disjoint_types --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 92fa29967b..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: --disable ana.mutex.disjoint_types --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 9fc24a96e1..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: --disable ana.mutex.disjoint_types --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 67028e10f9..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: --disable ana.mutex.disjoint_types --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 e98dd0e9a9..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: --disable ana.mutex.disjoint_types --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 d69025c5df..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: --disable ana.mutex.disjoint_types --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 6787175741..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: --disable ana.mutex.disjoint_types --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 fbe705cc10..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: --disable ana.mutex.disjoint_types --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 703ed7cce5..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: --disable ana.mutex.disjoint_types --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 fcea2bb341..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: --disable ana.mutex.disjoint_types --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/34-localwn_restart/04-hh.c b/tests/regression/34-localwn_restart/04-hh.c index 4566ffc1aa..bfbfb1b2ab 100644 --- a/tests/regression/34-localwn_restart/04-hh.c +++ b/tests/regression/34-localwn_restart/04-hh.c @@ -5,7 +5,7 @@ // Example from Halbwachs-Henry, SAS 2012 // Localized widening or restart policy should be able to prove that i <= j+3 // if the abstract domain is powerful enough. -#include > +#include void main() { 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/40-threadid/04-recover.c b/tests/regression/40-threadid/04-recover.c new file mode 100644 index 0000000000..2c2110fea8 --- /dev/null +++ b/tests/regression/40-threadid/04-recover.c @@ -0,0 +1,38 @@ +// PARAM: --set ana.activated[+] threadJoins +#include +#include + +// not marked as a wrapper +int my_pthread_create( + pthread_t *restrict thread, + const pthread_attr_t *restrict attr, + void *(*start_routine)(void *), + void *restrict arg +) { + return pthread_create(thread, attr, start_routine, arg); +} + + +int g = 0; +pthread_mutex_t A = PTHREAD_MUTEX_INITIALIZER; + +void *t_fun(void *arg) { + pthread_mutex_lock(&A); + g = 1; + pthread_mutex_unlock(&A); + return NULL; +} + +int main() { + pthread_t id1; + my_pthread_create(&id1, NULL, t_fun, 0); + pthread_t id2; + my_pthread_create(&id2, NULL, t_fun, 0); + + pthread_join(id1, NULL); + + + g = 2; // RACE + + return 0; +} diff --git a/tests/regression/40-threadid/05-nc-simple.c b/tests/regression/40-threadid/05-nc-simple.c new file mode 100644 index 0000000000..a5c198097c --- /dev/null +++ b/tests/regression/40-threadid/05-nc-simple.c @@ -0,0 +1,32 @@ +// PARAM: --disable ana.thread.context.create-edges +#include +#include + +int glob; + +void *t_FST(void *arg) { +} + +void *t_SND(void *arg) { + glob = 1; //NORACE +} + +int nothing () { +} + + +int main() { + + pthread_t id; + pthread_create(&id, NULL, t_FST, NULL); + + nothing(); + + glob = 2; //NORACE + + pthread_t id; + pthread_create(&id, NULL, t_SND, NULL); + + nothing(); + +} diff --git a/tests/regression/40-threadid/06-nc-deep.c b/tests/regression/40-threadid/06-nc-deep.c new file mode 100644 index 0000000000..6578bb355b --- /dev/null +++ b/tests/regression/40-threadid/06-nc-deep.c @@ -0,0 +1,76 @@ +// PARAM: --disable ana.thread.context.create-edges +#include +#include + +int glob_noCreate; +int glob_create; + +void *t_INIT(void *arg) { +} + +void *t_noCreate(void *arg) { + glob_noCreate =1; //NORACE +} + +void *t_create(void *arg) { + glob_create =1; //RACE +} + +void noCreate1 () { + noCreate2(); +} +void noCreate2 () { + noCreate3(); +} +void noCreate3 () { + noCreate4(); +} +void noCreate4 () { + noCreate5(); +} +void noCreate5 () { +} + +void create1 () { + create2(); +} +void create2 () { + create3(); +} +void create3 () { + create4(); +} +void create4 () { + create5(); +} +void create5 () { + pthread_t id; + pthread_create(&id, NULL, t_create, NULL); +} + +int main() { + + pthread_t id; + pthread_create(&id, NULL, t_INIT, NULL); + + //no create + noCreate1(); + + glob_noCreate = 2; //NORACE + + pthread_t id; + pthread_create(&id, NULL, t_noCreate, NULL); + + noCreate1(); + + //create + create1(); + + glob_create = 2; //RACE + + pthread_t id; + pthread_create(&id, NULL, t_create, NULL); + + create1(); + +} diff --git a/tests/regression/40-threadid/07-nc-createEdges.c b/tests/regression/40-threadid/07-nc-createEdges.c new file mode 100644 index 0000000000..2d11e13fc1 --- /dev/null +++ b/tests/regression/40-threadid/07-nc-createEdges.c @@ -0,0 +1,37 @@ +// PARAM: --disable ana.thread.context.create-edges +#include +#include + +int glob; + +void *t_init(void *arg) { +} + +void *t_norace(void *arg) { + glob = 1; //NORACE +} + +void *t_other(void *arg) { +} + +int create_other () { + pthread_t id; + pthread_create(&id, NULL, t_other, NULL); +} + + +int main() { + //enter multithreaded mode + pthread_t id; + pthread_create(&id, NULL, t_init, NULL); + + create_other(); + + glob = 2; //NORACE + + pthread_t id; + pthread_create(&id, NULL, t_norace, NULL); + + create_other(); + +} diff --git a/tests/regression/40-threadid/08-nc-fromThread.c b/tests/regression/40-threadid/08-nc-fromThread.c new file mode 100644 index 0000000000..4fd37b6f87 --- /dev/null +++ b/tests/regression/40-threadid/08-nc-fromThread.c @@ -0,0 +1,35 @@ +// PARAM: --disable ana.thread.context.create-edges +#include +#include + +int glob; + +void *t_norace(void *arg) { + glob = 1; //NORACE +} + +void *t_other(void *arg) { +} + +int create_other () { + pthread_t id; + pthread_create(&id, NULL, t_other, NULL); +} + +void *t_fun(void *arg) { + create_other(); + + glob = 2; //NORACE + + pthread_t id; + pthread_create(&id, NULL, t_norace, NULL); + + create_other(); +} + +int main() { + pthread_t id; + pthread_create(&id, NULL, t_fun, NULL); + + create_other(); +} 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/45-escape/06-local-escp.c b/tests/regression/45-escape/06-local-escp.c new file mode 100644 index 0000000000..50d27e200e --- /dev/null +++ b/tests/regression/45-escape/06-local-escp.c @@ -0,0 +1,38 @@ +// PARAM: --enable ana.int.interval +#include +#include +#include +#include + +int g = 0; +int *p = &g; + + +void *thread1(void *pp){ + int x = 23; + __goblint_check(x == 23); + p = &x; + sleep(2); + __goblint_check(x == 23); //UNKNOWN! + __goblint_check(x <= 23); + __goblint_check(x >= 1); + + return NULL; +} + +void *thread2(void *ignored){ + sleep(1); + int *i = p; + *p = 1; + return NULL; +} + +int main(){ + pthread_t t1; + pthread_t t2; + pthread_create(&t1, NULL, thread1, NULL); + pthread_create(&t2, NULL, thread2, NULL); + pthread_join(t1, NULL); + pthread_join(t2, NULL); +} + diff --git a/tests/regression/45-escape/07-local-in-global-after-create.c b/tests/regression/45-escape/07-local-in-global-after-create.c new file mode 100644 index 0000000000..fbb955e1fc --- /dev/null +++ b/tests/regression/45-escape/07-local-in-global-after-create.c @@ -0,0 +1,22 @@ +// SKIP +#include +#include + +int* gptr; + +void *foo(void* p){ + *gptr = 17; + return NULL; +} + +int main(){ + int x = 0; + __goblint_check(x==0); + pthread_t thread; + pthread_create(&thread, NULL, foo, NULL); + gptr = &x; + sleep(3); + __goblint_check(x == 0); // UNKNOWN! + pthread_join(thread, NULL); + return 0; +} diff --git a/tests/regression/45-escape/08-local-escp-main.c b/tests/regression/45-escape/08-local-escp-main.c new file mode 100644 index 0000000000..19b4bc7940 --- /dev/null +++ b/tests/regression/45-escape/08-local-escp-main.c @@ -0,0 +1,31 @@ +//PARAM: --enable ana.int.interval +#include +#include +#include +#include + +int g = 0; +int *p = &g; + + +void *thread1(void *pp){ + int x = 23; + __goblint_check(x == 23); + p = &x; + sleep(2); + __goblint_check(x == 23); //UNKNOWN! + __goblint_check(x <= 23); + __goblint_check(x >= 1); + + int y = x; + return NULL; +} + +int main(){ + pthread_t t1; + pthread_t t2; + pthread_create(&t1, NULL, thread1, NULL); + sleep(1); + *p = 1; +} + diff --git a/tests/regression/46-apron2/24-pipeline-no-threadflag.c b/tests/regression/46-apron2/24-pipeline-no-threadflag.c index 96346800fe..8e066a0f34 100644 --- a/tests/regression/46-apron2/24-pipeline-no-threadflag.c +++ b/tests/regression/46-apron2/24-pipeline-no-threadflag.c @@ -1,16 +1,20 @@ -// SKIP PARAM: --set ana.activated[+] apron --set ana.activated[-] threadflag +// SKIP PARAM: --set ana.activated[+] apron --set ana.activated[-] threadflag --set ana.activated[-] thread --set ana.activated[-] threadid // Minimized from sv-benchmarks/c/systemc/pipeline.cil-1.c #include +#include int main_clk_pos_edge; int main_in1_req_up; int main() { - // main_clk_pos_edge = 2; // TODO: uncomment to unskip apron test - if (main_in1_req_up == 1) // TODO: both branches are dead - assert(0); // TODO: uncomment to unskip apron test, FAIL (unreachable) + int litmus; + main_clk_pos_edge = 2; + if (main_in1_req_up == 1) + litmus = 0; // unreachable else - assert(1); // reachable + litmus = 1; + + __goblint_check(litmus == 1); return (0); } diff --git a/tests/regression/46-apron2/30-autotune-stub.c b/tests/regression/46-apron2/30-autotune-stub.c new file mode 100644 index 0000000000..e1b7603c3b --- /dev/null +++ b/tests/regression/46-apron2/30-autotune-stub.c @@ -0,0 +1,15 @@ +//SKIP PARAM: --enable ana.int.interval --sets sem.int.signed_overflow assume_none --set ana.activated[+] apron --enable ana.autotune.enabled +// Check that autotuner respect goblint_stub attributes as hints to not track variables. +#include + +int main () { + // Normally these appear only inside our stubs to prevent tracking relational information for variables which will never have interesting values associated with them + int x __attribute__((goblint_stub)); + int y __attribute__((goblint_stub)); + + if( x < y) { + __goblint_check(x < y); //UNKNOWN + } + + return 0; +} diff --git a/tests/regression/46-apron2/31-taint-apron.c b/tests/regression/46-apron2/31-taint-apron.c new file mode 100644 index 0000000000..05d37d31ea --- /dev/null +++ b/tests/regression/46-apron2/31-taint-apron.c @@ -0,0 +1,39 @@ +// SKIP PARAM: --set ana.activated[+] taintPartialContexts --set ana.activated[+] apron --set ana.ctx_insens[+] apron +#include + +int a_glob, b_glob; + +int f1(int *aptr, int *bptr) { + __goblint_check(*aptr - *bptr == 0); // UNKNOWN +} + +int f2(int *aptr, int *bptr) { + *aptr = 7; +} + +int f3 (int *aptr, int *bptr) { + *aptr = *bptr; +} + +int main() { + int a1, b1; + int a2, b2; + int a3, b3; + + //untainted + f1(&a1, &b1); + a1 = b1; + a_glob = b_glob; + f1(&a1, &b1); + __goblint_check(a1 - b1 == 0); + __goblint_check(a_glob - b_glob == 0); + + //tainted + a2 = b2; + f2(&a2, &b2); + __goblint_check(a2 - b2 == 0); //UNKNOWN! + + //add new + f3(&a3, &b3); + __goblint_check(a3 - b3 == 0); +} diff --git a/tests/regression/46-apron2/32-iset_array_octagon.c b/tests/regression/46-apron2/32-iset_array_octagon.c new file mode 100644 index 0000000000..390ce814f2 --- /dev/null +++ b/tests/regression/46-apron2/32-iset_array_octagon.c @@ -0,0 +1,379 @@ +// SKIP PARAM: --enable ana.int.interval_set --set ana.base.arrays.domain partitioned --set ana.activated[+] apron --set sem.int.signed_overflow assume_none +#include + +void main(void) { + example0(); + example1(); + example2(); + example3(); + example4(); + example4a(); + example4b(); + example4c(); + example5(); + example6(); + example7(); + example8(); + mineEx1(); +} + +void example0(void) { + int a[20]; + int i = 0; + int j = 0; + int top; + int z; + + // Necessary so we can not answer the queries below from the base domain + // and actually test the behavior of the octagons + int between1and8; + if(between1and8 < 1) { + between1and8 = 1; + } + + if(between1and8 > 8) { + between1and8 = 8; + } + + while(i < 20) { + a[i] = 0; + i++; + } + + while(j < between1and8) { + a[j] = 1; + j++; + } + + a[j] = 2; // a -> (j,([1,1],[2,2],[0,0])) + + + z = j; + + // Values that may be read are 1 or 2 + __goblint_check(a[z] == 1); // FAIL + __goblint_check(a[z] == 2); + __goblint_check(z >= 0); + __goblint_check(z <= j); + __goblint_check(a[z] == 0); //FAIL +} + +void example1(void) { + int a[20]; + int i = 0; + int j = 0; + int top; + int z; + + // Necessary so we can not answer the queries below from the base domain + // and actually test the behavior of the octagons + int between1and8; + if(between1and8 < 1) { + between1and8 = 1; + } + + if(between1and8 > 8) { + between1and8 = 8; + } + + while(i < 20) { + a[i] = 0; + i++; + } + + while(j < between1and8) { + a[j] = 1; + j++; + } + + a[j] = 2; // a -> (j,([1,1],[2,2],[0,0])) + + if(top) { + z = j; + } else { + z = j-1; + } + + // Values that may be read are 1 or 2 + __goblint_check(a[z] == 1); //UNKNOWN + __goblint_check(a[z] == 2); //UNKNOWN + __goblint_check(z >= 0); + + // Relies on option sem.int.signed_overflow assume_none + __goblint_check(z <= j); + __goblint_check(a[z] != 0); +} + +void example2(void) { + int a[20]; + int i = 0; + int j = 0; + int top; + int z; + + // Necessary so we can not answer the queries below from the base domain + // and actually test the behavior of the octagons + int between1and8; + if(between1and8 < 1) { + between1and8 = 1; + } + + if(between1and8 > 8) { + between1and8 = 8; + } + + while(i < 20) { + a[i] = 0; + i++; + } + + while(j < between1and8) { + a[j] = 2; + j++; + } + + a[j] = 1; // a -> (j,([2,2],[1,1],[0,0])) + + if(top) { + z = j; + } else { + z = j+1; + } + + // Values that may be read are 1 or 0 + __goblint_check(a[z] == 1); //UNKNOWN + __goblint_check(a[z] == 0); //UNKNOWN + + // Relies on option sem.int.signed_overflow assume_none + __goblint_check(a[z] != 2); +} + +// Simple example (employing MustBeEqual) +void example3(void) { + int a[42]; + int i = 0; + int x; + + while(i < 42) { + a[i] = 0; + int v = i; + x = a[v]; + __goblint_check(x == 0); + i++; + } +} + +// Simple example (employing MayBeEqual / MayBeSmaller) +void example4(void) { + int a[42]; + int i = 0; + + while(i<=9) { + a[i] = 9; + int j = i+5; + a[j] = 42; + + // Here we know a[i] is 9 when we have MayBeEqual + __goblint_check(a[i] == 9); + + // but only about the part to the left of i if we also have MayBeSmaller + if(i>0) { + int k = a[i-1]; + __goblint_check(k == 9); + + int l = a[0]; + __goblint_check(l == 9); + } + + i++; + } +} +// Just like the example before except that it tests correct behavior when variable order is reversed +void example4a(void) { + int a[42]; + int j; + int i = 0; + + while(i<=9) { + a[i] = 9; + j = i+5; + a[j] = 42; + + // Here we know a[i] is 9 when we have MayBeEqual + __goblint_check(a[i] == 9); + + // but only about the part to the left of i if we also have MayBeSmaller + if(i>0) { + __goblint_check(a[i-1] == 9); + } + + i++; + } +} + +// Just like the example before except that it tests correct behavior when operands for + are reversed +void example4b(void) { + int a[42]; + int j; + int i = 0; + + while(i<=9) { + a[i] = 9; + j = 5+i; + a[j] = 42; + + // Here we know a[i] is 9 when we have MayBeEqual + __goblint_check(a[i] == 9); + + // but only about the part to the left of i if we also have MayBeSmaller + if(i>0) { + __goblint_check(a[i-1] == 9); + } + + i++; + } +} + +// Like example before but iterating backwards +void example4c(void) { + int a[42]; + int j; + int i = 41; + + while(i > 8) { + a[i] = 7; + a[i-2] = 31; + + if(i < 41) { + __goblint_check(a[i+1] == 7); + } + + i--; + } +} + +void example5(void) { + int a[40]; + int i = 0; + + // This is a dirty cheat to get the array to be partitioned before entering the loop + // This is needed because the may be less of the octagons is not sophisticated enough yet. + // Once that is fixed this will also work without this line + a[i] = 0; + + while(i < 42) { + int j = i; + a[j] = 0; + i++; + + __goblint_check(a[i] == 0); //UNKNOWN + + __goblint_check(a[i-1] == 0); + __goblint_check(a[j] == 0); + + if (i>1) { + __goblint_check(a[i-2] == 0); + __goblint_check(a[j-1] == 0); + } + } +} + +void example6(void) { + int a[42]; + int i = 0; + int top; + + while(i<30) { + a[i] = 0; + i++; + + __goblint_check(a[top] == 0); //UNKNOWN + + int j=0; + while(j 10) { + if(top) { + j = i-5; + } else { + j = i-7; + } + + __goblint_check(a[j] == 0); + } + } +} + +void example8(void) { + int a[42]; + int i = 0; + int j = i; + + int N; + + if(N < 5) { + N = 5; + } + if(N > 40) { + N = 40; + } + + + while(i < N) { + a[i] = 0; + i++; + j = i; + a[j-1] = 0; + a[j] = 0; + j++; // Octagon knows -1 <= i-j <= -1 + i = j; // Without octagons, we lose partitioning here because we don't know how far the move has been + + __goblint_check(a[i-1] == 0); + __goblint_check(a[i-2] == 0); + } + + j = 0; + while(j < N) { + __goblint_check(a[j] == 0); + j++; + } +} + +// Example from https://www-apr.lip6.fr/~mine/publi/article-mine-HOSC06.pdf +void mineEx1(void) { + int X = 0; + int N = rand(); + if(N < 0) { N = 0; } + + while(X < N) { + X++; + } + + __goblint_check(X-N == 0); + // __goblint_check(X == N); // Currently not able to assert this because octagon doesn't handle it + + if(X == N) { + N = 8; + } else { + // is dead code but if that is detected or not depends on what we do in branch + // currenlty we can't detect this + N = 42; + } +} diff --git a/tests/regression/46-apron2/33-iset_no-context.c b/tests/regression/46-apron2/33-iset_no-context.c new file mode 100644 index 0000000000..f1ef90b9c5 --- /dev/null +++ b/tests/regression/46-apron2/33-iset_no-context.c @@ -0,0 +1,25 @@ +// SKIP PARAM: --set ana.activated[+] apron --set ana.path_sens[+] threadflag --enable ana.int.interval_set --disable ana.relation.context +extern int __VERIFIER_nondet_int(); + +#include + +int oct(int x, int y) { + int s; + if (x <= y) + s = 1; + else + s = 0; + return s; +} + +void main() { + int x = __VERIFIER_nondet_int(); //rand + int y = __VERIFIER_nondet_int(); //rand + int res; + if (x <= y) { + res = oct(x, y); + __goblint_check(res == 1); // UNKNOWN (indended by disabled context) + } + + res = oct(x, y); +} diff --git a/tests/regression/46-apron2/34-iset_previously_problematic_c.c b/tests/regression/46-apron2/34-iset_previously_problematic_c.c new file mode 100644 index 0000000000..3c1847bbcf --- /dev/null +++ b/tests/regression/46-apron2/34-iset_previously_problematic_c.c @@ -0,0 +1,30 @@ +// SKIP PARAM: --enable ana.int.interval_set --set ana.base.arrays.domain partitioned --set ana.activated[+] apron +// These examples were cases were we saw issues of not reaching a fixpoint during development of the octagon domain. Since those issues might +// resurface, these tests without asserts are included +int main(int argc, char const *argv[]) +{ + int i = 0; + int j; + int nGroups; + int bliblablue; + int inUse16[16] ; + + + nGroups = 2; + bliblablue = 4; + + i = 0; + + while (i < 16) { + inUse16[i] = 0; + + j = 0; + while (j < 16) { + j++; + } + + i++; + } + + return 0; +} diff --git a/tests/regression/46-apron2/35-iset_hh.c b/tests/regression/46-apron2/35-iset_hh.c new file mode 100644 index 0000000000..d29c265d5a --- /dev/null +++ b/tests/regression/46-apron2/35-iset_hh.c @@ -0,0 +1,23 @@ +// SKIP PARAM: --enable ana.int.interval_set --set solver td3 --set ana.activated[+] apron --set sem.int.signed_overflow assume_none +// This is part of 34-localization, but also symlinked to 36-apron. + +// ALSO: --enable ana.int.interval_set --set solver slr3 --set ana.activated[+] apron --set sem.int.signed_overflow assume_none +// Example from Halbwachs-Henry, SAS 2012 +// Localized widening or restart policy should be able to prove that i <= j+3 +// if the abstract domain is powerful enough. +#include > + +void main() +{ + int i = 0; + while (i<4) { + int j=0; + while (j<4) { + i=i+1; + j=j+1; + } + i = i-j+1; + __goblint_check(i <= j+3); + } + return ; +} diff --git a/tests/regression/46-apron2/36-iset_previosuly_problematic_g.c b/tests/regression/46-apron2/36-iset_previosuly_problematic_g.c new file mode 100644 index 0000000000..4cbcaef064 --- /dev/null +++ b/tests/regression/46-apron2/36-iset_previosuly_problematic_g.c @@ -0,0 +1,19 @@ +// SKIP PARAM: --enable ana.int.interval_set --set ana.base.arrays.domain partitioned --set ana.activated[+] apron +// These examples were cases were we saw issues of not reaching a fixpoint during development of the octagon domain. Since those issues might +// resurface, these tests without asserts are included +int main(int argc, char const *argv[]) +{ + int i = 0; + int j; + int nGroups = 6; + int inUse16[16]; + + while (i < 16) + { // TO-DO: here + inUse16[i] = 0; + j = 0; + i++; + } + + return 0; +} diff --git a/tests/regression/46-apron2/37-iset_previosuly_problematic_f.c b/tests/regression/46-apron2/37-iset_previosuly_problematic_f.c new file mode 100644 index 0000000000..918e67b0e8 --- /dev/null +++ b/tests/regression/46-apron2/37-iset_previosuly_problematic_f.c @@ -0,0 +1,27 @@ +// SKIP PARAM: --enable ana.int.interval_set --set ana.base.arrays.domain partitioned --set ana.activated[+] apron +// These examples were cases were we saw issues of not reaching a fixpoint during development of the octagon domain. Since those issues might +// resurface, these tests without asserts are included +int main(int argc, char const *argv[]) +{ + int a[256]; + int i = 0; + int zPend = 0; + int ll_i; + int nblock; + + while (i < nblock) + { // TO-DO: Here + if (a[0] == ll_i) // this needs to be var == var for the problem to occur + { + zPend++; + } + else + { + a[0] = 8; + } + + i++; + } + + return 0; +} diff --git a/tests/regression/46-apron2/38-iset_address.c b/tests/regression/46-apron2/38-iset_address.c new file mode 100644 index 0000000000..c75f047b2b --- /dev/null +++ b/tests/regression/46-apron2/38-iset_address.c @@ -0,0 +1,28 @@ +// SKIP PARAM: --enable ana.int.interval_set --set ana.activated[+] apron +// Example from https://www-apr.lip6.fr/~mine/publi/article-mine-HOSC06.pdf +#include + +void main(void) { + int X = 0; + int N = rand(); + if(N < 0) { N = 0; } + + while(X < N) { + X++; + } + + int* ptr = &N; + *ptr = N; + + + __goblint_check(X-N == 0); + __goblint_check(X == N); + + if(X == N) { + N = 8; + } else { + // is dead code but if that is detected or not depends on what we do in branch + // currenlty we can't detect this + N = 42; + } +} diff --git a/tests/regression/46-apron2/39-iset_previosuly_problematic_d.c b/tests/regression/46-apron2/39-iset_previosuly_problematic_d.c new file mode 100644 index 0000000000..18a53d31aa --- /dev/null +++ b/tests/regression/46-apron2/39-iset_previosuly_problematic_d.c @@ -0,0 +1,18 @@ +// SKIP PARAM: --enable ana.int.interval_set --set ana.activated[+] apron +// These examples were cases were we saw issues of not reaching a fixpoint during development of the octagon domain. Since those issues might +// resurface, these tests without asserts are included +int main(int argc, char const *argv[]) +{ + int l; + int r = 42; + + while(1) { + if (l-r <= 0) { + r--; + } else { + break; + } + } + + return 0; +} diff --git a/tests/regression/46-apron2/40-iset_context-attribute.c b/tests/regression/46-apron2/40-iset_context-attribute.c new file mode 100644 index 0000000000..974e00c820 --- /dev/null +++ b/tests/regression/46-apron2/40-iset_context-attribute.c @@ -0,0 +1,26 @@ +// SKIP PARAM: --set ana.activated[+] apron --set ana.path_sens[+] threadflag --enable ana.int.interval_set --disable ana.relation.context +extern int __VERIFIER_nondet_int(); + +#include + +int oct(int x, int y) __attribute__((goblint_context("relation.context"))); // attributes are not permitted in a function definition +int oct(int x, int y) { + int s; + if (x <= y) + s = 1; + else + s = 0; + return s; +} + +void main() { + int x = __VERIFIER_nondet_int(); //rand + int y = __VERIFIER_nondet_int(); //rand + int res; + if (x <= y) { + res = oct(x, y); + __goblint_check(res == 1); + } + + res = oct(x, y); +} diff --git a/tests/regression/46-apron2/41-iset_no-context-attribute.c b/tests/regression/46-apron2/41-iset_no-context-attribute.c new file mode 100644 index 0000000000..dd1da4795d --- /dev/null +++ b/tests/regression/46-apron2/41-iset_no-context-attribute.c @@ -0,0 +1,26 @@ +// SKIP PARAM: --set ana.activated[+] apron --set ana.path_sens[+] threadflag --enable ana.int.interval_set --enable ana.relation.context +extern int __VERIFIER_nondet_int(); + +#include + +int oct(int x, int y) __attribute__((goblint_context("relation.no-context"))); // attributes are not permitted in a function definition +int oct(int x, int y) { + int s; + if (x <= y) + s = 1; + else + s = 0; + return s; +} + +void main() { + int x = __VERIFIER_nondet_int(); //rand + int y = __VERIFIER_nondet_int(); //rand + int res; + if (x <= y) { + res = oct(x, y); + __goblint_check(res == 1); // UNKNOWN (indended by no-context attribute) + } + + res = oct(x, y); +} diff --git a/tests/regression/46-apron2/42-iset_octagon_interprocedural.c b/tests/regression/46-apron2/42-iset_octagon_interprocedural.c new file mode 100644 index 0000000000..a58b04bcf8 --- /dev/null +++ b/tests/regression/46-apron2/42-iset_octagon_interprocedural.c @@ -0,0 +1,26 @@ +// SKIP PARAM: --enable ana.int.interval_set --set ana.activated[+] apron +#include + +int main(void) { + f1(); +} + +int f1() { + int one; + int two; + + int x; + + one = two; + + __goblint_check(one - two == 0); + x = f2(one,two); + __goblint_check(one - two == 0); + __goblint_check(x == 48); +} + +int f2(int a, int b) { + __goblint_check(a-b == 0); + + return 48; +} diff --git a/tests/regression/46-apron2/43-iset_array_octagon_prec.c b/tests/regression/46-apron2/43-iset_array_octagon_prec.c new file mode 100644 index 0000000000..dfa8c6d84d --- /dev/null +++ b/tests/regression/46-apron2/43-iset_array_octagon_prec.c @@ -0,0 +1,350 @@ +// SKIP PARAM: --enable ana.int.interval_set --set ana.base.arrays.domain partitioned --set ana.activated[+] apron --enable annotation.int.enabled --set ana.int.refinement fixpoint --set sem.int.signed_overflow assume_none +#include + +void main(void) __attribute__((goblint_precision("no-interval"))); +void example1(void) __attribute__((goblint_precision("no-def_exc"))); +void example2(void) __attribute__((goblint_precision("no-def_exc"))); +void example3(void) __attribute__((goblint_precision("no-def_exc"))); +void example4(void) __attribute__((goblint_precision("no-def_exc"))); +void example4a(void) __attribute__((goblint_precision("no-def_exc"))); +void example4b(void) __attribute__((goblint_precision("no-def_exc"))); +void example4c(void) __attribute__((goblint_precision("no-def_exc"))); +void example5(void) __attribute__((goblint_precision("no-def_exc"))); +void example6(void) __attribute__((goblint_precision("no-def_exc"))); +void example7(void) __attribute__((goblint_precision("no-def_exc"))); +void example8(void) __attribute__((goblint_precision("no-def_exc"))); +void mineEx1(void) __attribute__((goblint_precision("no-def_exc"))); + + +void main(void) { + example1(); + example2(); + example3(); + example4(); + example4a(); + example4b(); + example4c(); + example5(); + example6(); + example7(); + example8(); + mineEx1(); +} + +void example1(void) { + int a[20]; + int i = 0; + int j = 0; + int top; + int z; + + // Necessary so we can not answer the queries below from the base domain + // and actually test the behavior of the octagons + int between1and8; + if(between1and8 < 1) { + between1and8 = 1; + } + + if(between1and8 > 8) { + between1and8 = 8; + } + + while(i < 20) { + a[i] = 0; + i++; + } + + while(j < between1and8) { + a[j] = 1; + j++; + } + + a[j] = 2; // a -> (j,([1,1],[2,2],[0,0])) + + if(top) { + z = j; + } else { + z = j-1; + } + + // Values that may be read are 1 or 2 + __goblint_check(a[z] == 1); //UNKNOWN + __goblint_check(a[z] == 2); //UNKNOWN + + // Relies on option sem.int.signed_overflow assume_none + __goblint_check(a[z] != 0); +} + +void example2(void) { + int a[20]; + int i = 0; + int j = 0; + int top; + int z; + + // Necessary so we can not answer the queries below from the base domain + // and actually test the behavior of the octagons + int between1and8; + if(between1and8 < 1) { + between1and8 = 1; + } + + if(between1and8 > 8) { + between1and8 = 8; + } + + while(i < 20) { + a[i] = 0; + i++; + } + + while(j < between1and8) { + a[j] = 2; + j++; + } + + a[j] = 1; // a -> (j,([2,2],[1,1],[0,0])) + + if(top) { + z = j; + } else { + z = j+1; + } + + // Values that may be read are 1 or 0 + __goblint_check(a[z] == 1); //UNKNOWN + __goblint_check(a[z] == 0); //UNKNOWN + + // Relies on option sem.int.signed_overflow assume_none + __goblint_check(a[z] != 2); +} + +// Simple example (employing MustBeEqual) +void example3(void) { + int a[42]; + int i = 0; + int x; + + while(i < 42) { + a[i] = 0; + int v = i; + x = a[v]; + __goblint_check(x == 0); + i++; + } +} + +// Simple example (employing MayBeEqual / MayBeSmaller) +void example4(void) { + int a[42]; + int i = 0; + + while(i<=9) { + a[i] = 9; + int j = i+5; + a[j] = 42; + + // Here we know a[i] is 9 when we have MayBeEqual + __goblint_check(a[i] == 9); + + // but only about the part to the left of i if we also have MayBeSmaller + if(i>0) { + int k = a[i-1]; + __goblint_check(k == 9); + + int l = a[0]; + __goblint_check(l == 9); + } + + i++; + } +} +// Just like the example before except that it tests correct behavior when variable order is reversed +void example4a(void) { + int a[42]; + int j; + int i = 0; + + while(i<=9) { + a[i] = 9; + j = i+5; + a[j] = 42; + + // Here we know a[i] is 9 when we have MayBeEqual + __goblint_check(a[i] == 9); + + // but only about the part to the left of i if we also have MayBeSmaller + if(i>0) { + __goblint_check(a[i-1] == 9); + } + + i++; + } +} + +// Just like the example before except that it tests correct behavior when operands for + are reversed +void example4b(void) { + int a[42]; + int j; + int i = 0; + + while(i<=9) { + a[i] = 9; + j = 5+i; + a[j] = 42; + + // Here we know a[i] is 9 when we have MayBeEqual + __goblint_check(a[i] == 9); + + // but only about the part to the left of i if we also have MayBeSmaller + if(i>0) { + __goblint_check(a[i-1] == 9); + } + + i++; + } +} + +// Like example before but iterating backwards +void example4c(void) { + int a[42]; + int j; + int i = 41; + + while(i > 8) { + a[i] = 7; + a[i-2] = 31; + + if(i < 41) { + __goblint_check(a[i+1] == 7); + } + + i--; + } +} + +void example5(void) { + int a[40]; + int i = 0; + + // This is a dirty cheat to get the array to be partitioned before entering the loop + // This is needed because the may be less of the octagons is not sophisticated enough yet. + // Once that is fixed this will also work without this line + a[i] = 0; + + while(i < 42) { + int j = i; + a[j] = 0; + i++; + + __goblint_check(a[i] == 0); //UNKNOWN + + __goblint_check(a[i-1] == 0); + __goblint_check(a[j] == 0); + + if (i>1) { + __goblint_check(a[i-2] == 0); + __goblint_check(a[j-1] == 0); + } + } +} + +void example6(void) { + int a[42]; + int i = 0; + int top; + + while(i<30) { + a[i] = 0; + i++; + + __goblint_check(a[top] == 0); //UNKNOWN + + int j=0; + while(j 10) { + if(top) { + j = i-5; + } else { + j = i-7; + } + + __goblint_check(a[j] == 0); + } + } +} + +void example8(void) { + int a[42]; + int i = 0; + int j = i; + + int N; + + if(N < 5) { + N = 5; + } + if(N > 40) { + N = 40; + } + + + while(i < N) { + a[i] = 0; + i++; + j = i; + a[j-1] = 0; + a[j] = 0; + j++; // Octagon knows -1 <= i-j <= -1 + i = j; // Without octagons, we lose partitioning here because we don't know how far the move has been + + __goblint_check(a[i-1] == 0); + __goblint_check(a[i-2] == 0); + } + + j = 0; + while(j < N) { + __goblint_check(a[j] == 0); + j++; + } +} + +// Example from https://www-apr.lip6.fr/~mine/publi/article-mine-HOSC06.pdf +void mineEx1(void) { + int X = 0; + int N = rand(); + if(N < 0) { N = 0; } + + while(X < N) { + X++; + } + + __goblint_check(X-N == 0); + // __goblint_check(X == N); // Currently not able to assert this because octagon doesn't handle it + + if(X == N) { + N = 8; + } else { + // is dead code but if that is detected or not depends on what we do in branch + // currenlty we can't detect this + N = 42; + } +} diff --git a/tests/regression/46-apron2/44-iset_array_octagon_keep_last.c b/tests/regression/46-apron2/44-iset_array_octagon_keep_last.c new file mode 100644 index 0000000000..813ad88aa4 --- /dev/null +++ b/tests/regression/46-apron2/44-iset_array_octagon_keep_last.c @@ -0,0 +1,335 @@ +// SKIP PARAM: --enable ana.int.interval_set --set ana.base.arrays.domain partitioned --set ana.base.partition-arrays.keep-expr "last" --set ana.activated[+] apron --set sem.int.signed_overflow assume_none +#include + +void main(void) { + example1(); + example2(); + example3(); + example4(); + example4a(); + example4b(); + example4c(); + example5(); + example6(); + example7(); + example8(); + mineEx1(); +} + +void example1(void) { + int a[20]; + int i = 0; + int j = 0; + int top; + int z; + + // Necessary so we can not answer the queries below from the base domain + // and actually test the behavior of the octagons + int between1and8; + if(between1and8 < 1) { + between1and8 = 1; + } + + if(between1and8 > 8) { + between1and8 = 8; + } + + while(i < 20) { + a[i] = 0; + i++; + } + + while(j < between1and8) { + a[j] = 1; + j++; + } + + a[j] = 2; // a -> (j,([1,1],[2,2],[0,0])) + + if(top) { + z = j; + } else { + z = j-1; + } + + // Values that may be read are 1 or 2 + __goblint_check(a[z] == 1); //UNKNOWN + __goblint_check(a[z] == 2); //UNKNOWN + + // Relies on option sem.int.signed_overflow assume_none + __goblint_check(a[z] != 0); +} + +void example2(void) { + int a[20]; + int i = 0; + int j = 0; + int top; + int z; + + // Necessary so we can not answer the queries below from the base domain + // and actually test the behavior of the octagons + int between1and8; + if(between1and8 < 1) { + between1and8 = 1; + } + + if(between1and8 > 8) { + between1and8 = 8; + } + + while(i < 20) { + a[i] = 0; + i++; + } + + while(j < between1and8) { + a[j] = 2; + j++; + } + + a[j] = 1; // a -> (j,([2,2],[1,1],[0,0])) + + if(top) { + z = j; + } else { + z = j+1; + } + + // Values that may be read are 1 or 0 + __goblint_check(a[z] == 1); //UNKNOWN + __goblint_check(a[z] == 0); //UNKNOWN + + // Relies on option sem.int.signed_overflow assume_none + __goblint_check(a[z] != 2); +} + +// Simple example (employing MustBeEqual) +void example3(void) { + int a[42]; + int i = 0; + int x; + + while(i < 42) { + a[i] = 0; + int v = i; + x = a[v]; + __goblint_check(x == 0); + i++; + } +} + +// Simple example (employing MayBeEqual / MayBeSmaller) +void example4(void) { + int a[42]; + int i = 0; + + while(i<=9) { + a[i] = 9; + int j = i+5; + a[j] = 42; + + // Here we know a[i] is 9 when we have MayBeEqual + __goblint_check(a[i] == 9); // UNKNOWN + + // but only about the part to the left of i if we also have MayBeSmaller + if(i>0) { + int k = a[i-1]; + __goblint_check(k == 9); // UNKNOWN + + int l = a[0]; + __goblint_check(l == 9); // UNKNOWN + } + + i++; + } +} +// Just like the example before except that it tests correct behavior when variable order is reversed +void example4a(void) { + int a[42]; + int j; + int i = 0; + + while(i<=9) { + a[i] = 9; + j = i+5; + a[j] = 42; + + // Here we know a[i] is 9 when we have MayBeEqual + __goblint_check(a[i] == 9); //UNKNOWN + + // but only about the part to the left of i if we also have MayBeSmaller + if(i>0) { + __goblint_check(a[i-1] == 9); //UNKNOWN + } + + i++; + } +} + +// Just like the example before except that it tests correct behavior when operands for + are reversed +void example4b(void) { + int a[42]; + int j; + int i = 0; + + while(i<=9) { + a[i] = 9; + j = 5+i; + a[j] = 42; + + // Here we know a[i] is 9 when we have MayBeEqual + __goblint_check(a[i] == 9); //UNKNOWN + + // but only about the part to the left of i if we also have MayBeSmaller + if(i>0) { + __goblint_check(a[i-1] == 9); //UNKNOWN + } + + i++; + } +} + +// Like example before but iterating backwards +void example4c(void) { + int a[42]; + int j; + int i = 41; + + while(i > 8) { + a[i] = 7; + a[i-2] = 31; + + if(i < 41) { + __goblint_check(a[i+1] == 7); //UNKNOWN + } + + i--; + } +} + +void example5(void) { + int a[40]; + int i = 0; + + // This is a dirty cheat to get the array to be partitioned before entering the loop + // This is needed because the may be less of the octagons is not sophisticated enough yet. + // Once that is fixed this will also work without this line + a[i] = 0; + + while(i < 42) { + int j = i; + a[j] = 0; + i++; + + __goblint_check(a[i] == 0); //UNKNOWN + + __goblint_check(a[i-1] == 0); + __goblint_check(a[j] == 0); + + if (i>1) { + __goblint_check(a[i-2] == 0); + __goblint_check(a[j-1] == 0); + } + } +} + +void example6(void) { + int a[42]; + int i = 0; + int top; + + while(i<30) { + a[i] = 0; + i++; + + __goblint_check(a[top] == 0); //UNKNOWN + + int j=0; + while(j 10) { + if(top) { + j = i-5; + } else { + j = i-7; + } + + __goblint_check(a[j] == 0); + } + } +} + +void example8(void) { + int a[42]; + int i = 0; + int j = i; + + int N; + + if(N < 5) { + N = 5; + } + if(N > 40) { + N = 40; + } + + + while(i < N) { + a[i] = 0; + i++; + j = i; + a[j-1] = 0; + a[j] = 0; + j++; // Octagon knows -1 <= i-j <= -1 + i = j; // Without octagons, we lose partitioning here because we don't know how far the move has been + + __goblint_check(a[i-1] == 0); + __goblint_check(a[i-2] == 0); + } + + j = 0; + while(j < N) { + __goblint_check(a[j] == 0); //UNKNOWN + j++; + } +} + +// Example from https://www-apr.lip6.fr/~mine/publi/article-mine-HOSC06.pdf +void mineEx1(void) { + int X = 0; + int N = rand(); + if(N < 0) { N = 0; } + + while(X < N) { + X++; + } + + __goblint_check(X-N == 0); + // __goblint_check(X == N); // Currently not able to assert this because octagon doesn't handle it + + if(X == N) { + N = 8; + } else { + // is dead code but if that is detected or not depends on what we do in branch + // currenlty we can't detect this + N = 42; + } +} diff --git a/tests/regression/46-apron2/45-iset_previosuly_problematic_i.c b/tests/regression/46-apron2/45-iset_previosuly_problematic_i.c new file mode 100644 index 0000000000..dab6078b98 --- /dev/null +++ b/tests/regression/46-apron2/45-iset_previosuly_problematic_i.c @@ -0,0 +1,41 @@ +// SKIP PARAM: --enable ana.int.interval_set --set ana.base.arrays.domain partitioned --set ana.activated[+] apron +// These examples were cases were we saw issues of not reaching a fixpoint during development of the octagon domain. Since those issues might +// resurface, these tests without asserts are included +char buf2[67]; + +int main(int argc, char **argv) +{ + int human_output_opts; + int to_block_size; + char buf1[67]; + char local; + + int from_block_size = 1; + + int exponent; + int exponent_max; + int buflen; + int power; + + memmove((void *)buf2, (void const *)buf1, 67); + + int bla = 18; + + if (human_output_opts & 128) + { + if (exponent < 0) + { + exponent = 0; + power = 1; + while (power < to_block_size) + { + exponent++; + if (exponent == exponent_max) + { + break; + } + } + } + } + return 0; +} diff --git a/tests/regression/46-apron2/46-iset_simple-apron-interval.c b/tests/regression/46-apron2/46-iset_simple-apron-interval.c new file mode 100644 index 0000000000..e6304f9edb --- /dev/null +++ b/tests/regression/46-apron2/46-iset_simple-apron-interval.c @@ -0,0 +1,25 @@ +// SKIP PARAM: --set ana.activated[+] apron --enable ana.int.interval_set --set ana.apron.domain interval +// Example from https://www-apr.lip6.fr/~mine/publi/article-mine-HOSC06.pdf, adapted +#include + +void main(void) { + int X = 0; + int N = rand(); + if(N < 0) { N = 0; } + + while(X < N) { + X++; + } + + __goblint_check(X-N == 0); //UNKNOWN + __goblint_check(X == N); //UNKNOWN + + if(X == N) { + N = 8; + } else { + N = 42; + } + __goblint_check(N == 8); // UNKNOWN + __goblint_check(N >= 8); + __goblint_check(N <= 42); +} diff --git a/tests/regression/46-apron2/47-iset_previously_problematic_a.c b/tests/regression/46-apron2/47-iset_previously_problematic_a.c new file mode 100644 index 0000000000..2bf6044560 --- /dev/null +++ b/tests/regression/46-apron2/47-iset_previously_problematic_a.c @@ -0,0 +1,27 @@ +// SKIP PARAM: --enable ana.int.interval_set --set ana.activated[+] apron +// These examples were cases were we saw issues of not reaching a fixpoint during development of the octagon domain. Since those issues might +// resurface, these tests without asserts are included +int main(int argc, char const *argv[]) +{ + int top; + + int pad = 0; + int to_uppcase; + int change_case = 0; + + while (change_case != 1 && to_uppcase != 0) { + if(top == 1) { + to_uppcase = 1; + continue; + } + + if(top == 2) { + change_case = 1; + continue; + } + + break; + } + + return 0; +} diff --git a/tests/regression/46-apron2/48-iset_simple-polyhedra.c b/tests/regression/46-apron2/48-iset_simple-polyhedra.c new file mode 100644 index 0000000000..384b7b1654 --- /dev/null +++ b/tests/regression/46-apron2/48-iset_simple-polyhedra.c @@ -0,0 +1,21 @@ +// SKIP PARAM: --set ana.activated[+] apron --enable ana.int.interval_set --set ana.apron.domain polyhedra +// Example from https://www-apr.lip6.fr/~mine/publi/article-mine-HOSC06.pdf, adapted +#include + +void main(void) { + int X = 0; + int N = rand(); + if(N < 0 || N > 10000) { N = 0; } + + X = 2 * N; + + __goblint_check(X - 2 * N == 0); + __goblint_check(X == 2 * N); + + if(X == 2 * N) { + N = 8; + } else { + N = 42; //DEAD + } + +} diff --git a/tests/regression/46-apron2/49-iset_previously_problematic_b.c b/tests/regression/46-apron2/49-iset_previously_problematic_b.c new file mode 100644 index 0000000000..19d925013c --- /dev/null +++ b/tests/regression/46-apron2/49-iset_previously_problematic_b.c @@ -0,0 +1,51 @@ +// SKIP PARAM: --enable ana.int.interval_set --set ana.activated[+] apron +// These examples were cases were we saw issues of not reaching a fixpoint during development of the octagon domain. Since those issues might +// resurface, these tests without asserts are included +typedef int wchar_t; +typedef unsigned long size_t; + +char *trim2(char const *s, int how) +{ + char *d; + char *tmp___4; + unsigned int state; + int tmp___9; + + if (tmp___9 == 0) { + tmp___9 = 1; + } + + size_t tmp___18; + + d = tmp___4; + + if (tmp___18 > 1UL) + { + if (how != 1) + { + state = 0U; + + while (1) + { + if (!tmp___9){ + } + else { + break; + } + + + state = 1U; + } + } + } + return (d); + +} + +int main(int argc, char const *argv[]) +{ + char *s; + trim2(s, 4); + + return 0; +} diff --git a/tests/regression/46-apron2/50-iset_branched-not-too-brutal-c.c b/tests/regression/46-apron2/50-iset_branched-not-too-brutal-c.c new file mode 100644 index 0000000000..fe2309a568 --- /dev/null +++ b/tests/regression/46-apron2/50-iset_branched-not-too-brutal-c.c @@ -0,0 +1,33 @@ +// SKIP PARAM: --set ana.activated[+] apron --set ana.path_sens[+] threadflag --enable ana.int.interval_set --enable ana.sv-comp.functions +extern int __VERIFIER_nondet_int(); + +#include +#include +int global = 0; +pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; + +void *t_fun(void *arg) +{ + int top = __VERIFIER_nondet_int(); //rand + pthread_mutex_lock(&mutex); + if(top) { + global = 5; + } else { + global = 12; + } + global = 0; + pthread_mutex_unlock(&mutex); +} + +int main(void) +{ + pthread_t t; + pthread_create(&t, ((void *)0), t_fun, ((void *)0)); + + __goblint_check(global == 0); //UNKNOWN! + + pthread_mutex_lock(&mutex); + __goblint_check(global == 0); + pthread_mutex_unlock(&mutex); + return 0; +} diff --git a/tests/regression/46-apron2/51-iset_previosuly_problematic_e.c b/tests/regression/46-apron2/51-iset_previosuly_problematic_e.c new file mode 100644 index 0000000000..551bb731e9 --- /dev/null +++ b/tests/regression/46-apron2/51-iset_previosuly_problematic_e.c @@ -0,0 +1,25 @@ +// SKIP PARAM: --enable ana.int.interval_set --set ana.activated[+] apron +// These examples were cases were we saw issues of not reaching a fixpoint during development of the octagon domain. Since those issues might +// resurface, these tests without asserts are included +int main(int argc, char const *argv[]) +{ + int j = -1; + int digits = 0; + + int hour12; + int number_value; + + if (hour12 > 12) { + hour12 -= 12; + } + + digits = 0; + + while (j < 9) + { + number_value /= 10; + j++; + } + + return 0; +} diff --git a/tests/regression/46-apron2/52-iset_context.c b/tests/regression/46-apron2/52-iset_context.c new file mode 100644 index 0000000000..aa4b134649 --- /dev/null +++ b/tests/regression/46-apron2/52-iset_context.c @@ -0,0 +1,25 @@ +// SKIP PARAM: --set ana.activated[+] apron --set ana.path_sens[+] threadflag --enable ana.int.interval_set --enable ana.relation.context +extern int __VERIFIER_nondet_int(); + +#include + +int oct(int x, int y) { + int s; + if (x <= y) + s = 1; + else + s = 0; + return s; +} + +void main() { + int x = __VERIFIER_nondet_int(); //rand + int y = __VERIFIER_nondet_int(); //rand + int res; + if (x <= y) { + res = oct(x, y); + __goblint_check(res == 1); + } + + res = oct(x, y); +} diff --git a/tests/regression/46-apron2/53-iset_previously_problematic_h.c b/tests/regression/46-apron2/53-iset_previously_problematic_h.c new file mode 100644 index 0000000000..05489e47dc --- /dev/null +++ b/tests/regression/46-apron2/53-iset_previously_problematic_h.c @@ -0,0 +1,47 @@ +// SKIP PARAM: --enable ana.int.interval_set --set ana.base.arrays.domain partitioned --set ana.activated[+] apron +// These examples were cases were we saw issues of not reaching a fixpoint during development of the octagon domain. Since those issues might +// resurface, these tests without asserts are included +int main(int argc, char const *argv[]) +{ + int iter = 0; + int i = 0; + int j; + int t; + int nGroups; + int inUse16[16]; + int top; + + if (top) { + nGroups = 2; + } else { + nGroups = 6; + } + + while (iter < 4) + { + t = 0; + while (t < nGroups) + { + t++; + } + + iter++; + } + + i = 0; + while (i < 16) + { //TO-DO: here + inUse16[i] = 0; + + j = 0; + while (j < 16) + { + j++; + } + + i++; + } + + /* code */ + return 0; +} diff --git a/tests/regression/46-apron2/54-iset_octagon_simple.c b/tests/regression/46-apron2/54-iset_octagon_simple.c new file mode 100644 index 0000000000..adf07fdf53 --- /dev/null +++ b/tests/regression/46-apron2/54-iset_octagon_simple.c @@ -0,0 +1,24 @@ +// SKIP PARAM: --enable ana.int.interval_set --set ana.activated[+] apron +// Example from https://www-apr.lip6.fr/~mine/publi/article-mine-HOSC06.pdf +#include + +void main(void) { + int X = 0; + int N = rand(); + if(N < 0) { N = 0; } + + while(X < N) { + X++; + } + + __goblint_check(X-N == 0); + __goblint_check(X == N); + + if(X == N) { + N = 8; + } else { + // is dead code but if that is detected or not depends on what we do in branch + // currently we can't detect this + N = 42; + } +} diff --git a/tests/regression/46-apron2/55-iset_array_octagon_keep_last_prec.c b/tests/regression/46-apron2/55-iset_array_octagon_keep_last_prec.c new file mode 100644 index 0000000000..5a318b6e6a --- /dev/null +++ b/tests/regression/46-apron2/55-iset_array_octagon_keep_last_prec.c @@ -0,0 +1,349 @@ +// SKIP PARAM: --enable ana.int.interval_set --set ana.base.arrays.domain partitioned --set ana.base.partition-arrays.keep-expr "last" --set ana.activated[+] apron --enable annotation.int.enabled --set ana.int.refinement fixpoint --set sem.int.signed_overflow assume_none +#include + +void main(void) __attribute__((goblint_precision("no-interval"))); +void example1(void) __attribute__((goblint_precision("no-def_exc"))); +void example2(void) __attribute__((goblint_precision("no-def_exc"))); +void example3(void) __attribute__((goblint_precision("no-def_exc"))); +void example4(void) __attribute__((goblint_precision("no-def_exc"))); +void example4a(void) __attribute__((goblint_precision("no-def_exc"))); +void example4b(void) __attribute__((goblint_precision("no-def_exc"))); +void example4c(void) __attribute__((goblint_precision("no-def_exc"))); +void example5(void) __attribute__((goblint_precision("no-def_exc"))); +void example6(void) __attribute__((goblint_precision("no-def_exc"))); +void example8(void) __attribute__((goblint_precision("no-def_exc"))); +void mineEx1(void) __attribute__((goblint_precision("no-def_exc"))); + + +void main(void) { + example1(); + example2(); + example3(); + example4(); + example4a(); + example4b(); + example4c(); + example5(); + example6(); + example7(); + example8(); + mineEx1(); +} + +void example1(void) { + int a[20]; + int i = 0; + int j = 0; + int top; + int z; + + // Necessary so we can not answer the queries below from the base domain + // and actually test the behavior of the octagons + int between1and8; + if(between1and8 < 1) { + between1and8 = 1; + } + + if(between1and8 > 8) { + between1and8 = 8; + } + + while(i < 20) { + a[i] = 0; + i++; + } + + while(j < between1and8) { + a[j] = 1; + j++; + } + + a[j] = 2; // a -> (j,([1,1],[2,2],[0,0])) + + if(top) { + z = j; + } else { + z = j-1; + } + + // Values that may be read are 1 or 2 + __goblint_check(a[z] == 1); //UNKNOWN + __goblint_check(a[z] == 2); //UNKNOWN + + // Relies on option sem.int.signed_overflow assume_none + __goblint_check(a[z] != 0); +} + +void example2(void) { + int a[20]; + int i = 0; + int j = 0; + int top; + int z; + + // Necessary so we can not answer the queries below from the base domain + // and actually test the behavior of the octagons + int between1and8; + if(between1and8 < 1) { + between1and8 = 1; + } + + if(between1and8 > 8) { + between1and8 = 8; + } + + while(i < 20) { + a[i] = 0; + i++; + } + + while(j < between1and8) { + a[j] = 2; + j++; + } + + a[j] = 1; // a -> (j,([2,2],[1,1],[0,0])) + + if(top) { + z = j; + } else { + z = j+1; + } + + // Values that may be read are 1 or 0 + __goblint_check(a[z] == 1); //UNKNOWN + __goblint_check(a[z] == 0); //UNKNOWN + + // Relies on option sem.int.signed_overflow assume_none + __goblint_check(a[z] != 2); +} + +// Simple example (employing MustBeEqual) +void example3(void) { + int a[42]; + int i = 0; + int x; + + while(i < 42) { + a[i] = 0; + int v = i; + x = a[v]; + __goblint_check(x == 0); + i++; + } +} + +// Simple example (employing MayBeEqual / MayBeSmaller) +void example4(void) { + int a[42]; + int i = 0; + + while(i<=9) { + a[i] = 9; + int j = i+5; + a[j] = 42; + + // Here we know a[i] is 9 when we have MayBeEqual + __goblint_check(a[i] == 9); // UNKNOWN + + // but only about the part to the left of i if we also have MayBeSmaller + if(i>0) { + int k = a[i-1]; + __goblint_check(k == 9); // UNKNOWN + + int l = a[0]; + __goblint_check(l == 9); // UNKNOWN + } + + i++; + } +} +// Just like the example before except that it tests correct behavior when variable order is reversed +void example4a(void) { + int a[42]; + int j; + int i = 0; + + while(i<=9) { + a[i] = 9; + j = i+5; + a[j] = 42; + + // Here we know a[i] is 9 when we have MayBeEqual + __goblint_check(a[i] == 9); //UNKNOWN + + // but only about the part to the left of i if we also have MayBeSmaller + if(i>0) { + __goblint_check(a[i-1] == 9); //UNKNOWN + } + + i++; + } +} + +// Just like the example before except that it tests correct behavior when operands for + are reversed +void example4b(void) { + int a[42]; + int j; + int i = 0; + + while(i<=9) { + a[i] = 9; + j = 5+i; + a[j] = 42; + + // Here we know a[i] is 9 when we have MayBeEqual + __goblint_check(a[i] == 9); //UNKNOWN + + // but only about the part to the left of i if we also have MayBeSmaller + if(i>0) { + __goblint_check(a[i-1] == 9); //UNKNOWN + } + + i++; + } +} + +// Like example before but iterating backwards +void example4c(void) { + int a[42]; + int j; + int i = 41; + + while(i > 8) { + a[i] = 7; + a[i-2] = 31; + + if(i < 41) { + __goblint_check(a[i+1] == 7); //UNKNOWN + } + + i--; + } +} + +void example5(void) { + int a[40]; + int i = 0; + + // This is a dirty cheat to get the array to be partitioned before entering the loop + // This is needed because the may be less of the octagons is not sophisticated enough yet. + // Once that is fixed this will also work without this line + a[i] = 0; + + while(i < 42) { + int j = i; + a[j] = 0; + i++; + + __goblint_check(a[i] == 0); //UNKNOWN + + __goblint_check(a[i-1] == 0); + __goblint_check(a[j] == 0); + + if (i>1) { + __goblint_check(a[i-2] == 0); + __goblint_check(a[j-1] == 0); + } + } +} + +void example6(void) { + int a[42]; + int i = 0; + int top; + + while(i<30) { + a[i] = 0; + i++; + + __goblint_check(a[top] == 0); //UNKNOWN + + int j=0; + while(j 10) { + if(top) { + j = i-5; + } else { + j = i-7; + } + + __goblint_check(a[j] == 0); + } + } +} + +void example8(void) { + int a[42]; + int i = 0; + int j = i; + + int N; + + if(N < 5) { + N = 5; + } + if(N > 40) { + N = 40; + } + + + while(i < N) { + a[i] = 0; + i++; + j = i; + a[j-1] = 0; + a[j] = 0; + j++; // Octagon knows -1 <= i-j <= -1 + i = j; // Without octagons, we lose partitioning here because we don't know how far the move has been + + __goblint_check(a[i-1] == 0); + __goblint_check(a[i-2] == 0); + } + + j = 0; + while(j < N) { + __goblint_check(a[j] == 0); //UNKNOWN + j++; + } +} + +// Example from https://www-apr.lip6.fr/~mine/publi/article-mine-HOSC06.pdf +void mineEx1(void) { + int X = 0; + int N = rand(); + if(N < 0) { N = 0; } + + while(X < N) { + X++; + } + + __goblint_check(X-N == 0); + // __goblint_check(X == N); // Currently not able to assert this because octagon doesn't handle it + + if(X == N) { + N = 8; + } else { + // is dead code but if that is detected or not depends on what we do in branch + // currenlty we can't detect this + N = 42; + } +} diff --git a/tests/regression/46-apron2/56-combine-env-assign.c b/tests/regression/46-apron2/56-combine-env-assign.c new file mode 100644 index 0000000000..04a3d0d0e3 --- /dev/null +++ b/tests/regression/46-apron2/56-combine-env-assign.c @@ -0,0 +1,21 @@ +// SKIP PARAM: --set ana.activated[+] apron --set ana.base.privatization none --set ana.relation.privatization top +#include + +int g = 0; +int h = 0; +int *ptr = &g; + +int change_ptr_and_return_5() { + ptr = &h; + return 5; +} + +int main() { + // ptr points to h here, but apron evaluates on pre-state of function call, updating g instead + *ptr = change_ptr_and_return_5(); + + int x = g + 1; // Currently this fails because of operation on bot + __goblint_check(g == h); //FAIL + __goblint_check(h == 5); + __goblint_check(g == 0); +} diff --git a/tests/regression/46-apron2/57-rand.c b/tests/regression/46-apron2/57-rand.c new file mode 100644 index 0000000000..40a699433e --- /dev/null +++ b/tests/regression/46-apron2/57-rand.c @@ -0,0 +1,11 @@ +// SKIP PARAM: --disable ana.int.def_exc --enable ana.int.congruence --set ana.activated[+] apron --set ana.base.privatization none --set ana.relation.privatization top +// Strange int domains, I know: The ranges of def_exc are already able to prove this assertion, meaning it is no longer about apron also knowing this. +// Disabling all int domains leads to all queries returning top though, meaning the assertion is not proven. +// Congruence offers support for constants, but does not contain any poor man's intervals. => That's why we use it here. +#include +#include + +int main() { + int i = rand(); + __goblint_check(i >= 0); +} diff --git a/tests/regression/56-witness/05-prec-problem.c b/tests/regression/56-witness/05-prec-problem.c index d1255bfc09..132ba6b466 100644 --- a/tests/regression/56-witness/05-prec-problem.c +++ b/tests/regression/56-witness/05-prec-problem.c @@ -1,4 +1,4 @@ -//PARAM: --enable witness.yaml.enabled --enable ana.int.interval +//PARAM: --enable witness.yaml.enabled --enable ana.int.interval --set witness.yaml.entry-types[+] precondition_loop_invariant #include #include 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/44-base-unassume-array.c b/tests/regression/56-witness/44-base-unassume-array.c new file mode 100644 index 0000000000..c3928ae233 --- /dev/null +++ b/tests/regression/56-witness/44-base-unassume-array.c @@ -0,0 +1,16 @@ +// PARAM: --set ana.activated[+] unassume --set witness.yaml.unassume 44-base-unassume-array.yml --enable ana.int.interval +#include + +int main() { + int a[10]; + + for (int i = 0; i < 3; i++) { + a[i] = i; + } + + for (int i = 0; i < 10; i++) { + __goblint_check(a[i] >= 0); + __goblint_check(a[i] < 3); + } + return 0; +} diff --git a/tests/regression/56-witness/44-base-unassume-array.yml b/tests/regression/56-witness/44-base-unassume-array.yml new file mode 100644 index 0000000000..dbf7fb8e54 --- /dev/null +++ b/tests/regression/56-witness/44-base-unassume-array.yml @@ -0,0 +1,58 @@ +- entry_type: loop_invariant + metadata: + format_version: "0.1" + uuid: c03a4c45-567e-4791-ac75-0675f782dc8c + creation_time: 2023-05-10T15:02:06Z + producer: + name: Goblint + version: heads/array-witness-invariant-0-gfb806119b-dirty + command_line: '''/home/simmo/dev/goblint/sv-comp/goblint/goblint'' ''44-base-unassume-array.c'' + ''--enable'' ''ana.int.interval'' ''--enable'' ''witness.yaml.enabled'' ''--set'' + ''dbg.debug'' ''true'' ''--enable'' ''dbg.timing.enabled'' ''--set'' ''goblint-dir'' + ''.goblint-56-44''' + task: + input_files: + - 44-base-unassume-array.c + input_file_hashes: + 44-base-unassume-array.c: 9d9dc013c8d8aee483852aa73d0b4ac48ee7ea0f5433dc86ee28c3fe54c49726 + data_model: LP64 + language: C + location: + file_name: 44-base-unassume-array.c + file_hash: 9d9dc013c8d8aee483852aa73d0b4ac48ee7ea0f5433dc86ee28c3fe54c49726 + line: 7 + column: 6 + function: main + loop_invariant: + string: 0 <= a[(long )"all_index"] + type: assertion + format: C +- entry_type: loop_invariant + metadata: + format_version: "0.1" + uuid: c03a4c45-567e-4791-ac75-0675f782dc8c + creation_time: 2023-05-10T15:02:06Z + producer: + name: Goblint + version: heads/array-witness-invariant-0-gfb806119b-dirty + command_line: '''/home/simmo/dev/goblint/sv-comp/goblint/goblint'' ''44-base-unassume-array.c'' + ''--enable'' ''ana.int.interval'' ''--enable'' ''witness.yaml.enabled'' ''--set'' + ''dbg.debug'' ''true'' ''--enable'' ''dbg.timing.enabled'' ''--set'' ''goblint-dir'' + ''.goblint-56-44''' + task: + input_files: + - 44-base-unassume-array.c + input_file_hashes: + 44-base-unassume-array.c: 9d9dc013c8d8aee483852aa73d0b4ac48ee7ea0f5433dc86ee28c3fe54c49726 + data_model: LP64 + language: C + location: + file_name: 44-base-unassume-array.c + file_hash: 9d9dc013c8d8aee483852aa73d0b4ac48ee7ea0f5433dc86ee28c3fe54c49726 + line: 7 + column: 6 + function: main + loop_invariant: + string: a[(long )"all_index"] < 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/58-base-mm-tid/24-phases-sound.c b/tests/regression/58-base-mm-tid/24-phases-sound.c new file mode 100644 index 0000000000..c1fa5d1aef --- /dev/null +++ b/tests/regression/58-base-mm-tid/24-phases-sound.c @@ -0,0 +1,46 @@ +// PARAM: --set ana.path_sens[+] threadflag --set ana.base.privatization mutex-meet-tid --enable ana.int.interval --set ana.activated[+] threadJoins --set ana.activated[+] thread +// Tests soundness when additionally thread analysis is enabled, that is able to go back to single-threaded mode after all created joins have been joined. +#include +#include + +int g = 10; + +pthread_mutex_t A = PTHREAD_MUTEX_INITIALIZER; + +void *t_benign(void *arg) { + pthread_mutex_lock(&A); + g = 10; + __goblint_check(g == 10); + pthread_mutex_unlock(&A); + return NULL; +} + +void *t_benign2(void *arg) { + pthread_mutex_lock(&A); + __goblint_check(g == 20); + g = 10; + __goblint_check(g == 10); + pthread_mutex_unlock(&A); + return NULL; +} + +int main(void) { + + pthread_t id2; + pthread_create(&id2, NULL, t_benign, NULL); + pthread_join(id2, NULL); + + + g = 20; + __goblint_check(g == 20); + + pthread_t id; + pthread_create(&id, NULL, t_benign2, NULL); + + + pthread_mutex_lock(&A); + __goblint_check(g == 20); //UNKNOWN! + pthread_mutex_unlock(&A); + + return 0; +} diff --git a/tests/regression/58-base-mm-tid/25-phases-intricate-sound.c b/tests/regression/58-base-mm-tid/25-phases-intricate-sound.c new file mode 100644 index 0000000000..cedb1d7c47 --- /dev/null +++ b/tests/regression/58-base-mm-tid/25-phases-intricate-sound.c @@ -0,0 +1,55 @@ +// PARAM: --set ana.path_sens[+] threadflag --set ana.base.privatization mutex-meet-tid --enable ana.int.interval --set ana.activated[+] threadJoins --set ana.activated[+] thread +#include +#include + +int g = 10; + +pthread_mutex_t A = PTHREAD_MUTEX_INITIALIZER; +pthread_mutex_t B = PTHREAD_MUTEX_INITIALIZER; + +void *t_benign(void *arg) { + pthread_mutex_lock(&A); + pthread_mutex_lock(&B); + g = 10; + __goblint_check(g == 10); + pthread_mutex_unlock(&B); + pthread_mutex_unlock(&A); + return NULL; +} + +void *t_benign2(void *arg) { + pthread_mutex_lock(&A); + pthread_mutex_lock(&B); + g = 10; + __goblint_check(g == 10); + pthread_mutex_unlock(&B); + pthread_mutex_unlock(&A); + return NULL; +} + +int main(void) { + + pthread_t id2; + pthread_create(&id2, NULL, t_benign, NULL); + pthread_join(id2, NULL); + + g = 20; + __goblint_check(g == 20); + + // Modified while holding one of the locks that is protecting in MT mode + pthread_mutex_lock(&A); + g = g + 5; + pthread_mutex_unlock(&A); + + pthread_create(&id2, NULL, t_benign2, NULL); + + pthread_mutex_lock(&A); + pthread_mutex_lock(&B); + __goblint_check(g == 25); //UNKNOWN! + __goblint_check(g == 10); //UNKNOWN! + pthread_mutex_unlock(&B); + pthread_mutex_unlock(&A); + + + return 0; +} diff --git a/tests/regression/58-base-mm-tid/26-phases-trivial.c b/tests/regression/58-base-mm-tid/26-phases-trivial.c new file mode 100644 index 0000000000..323c6df251 --- /dev/null +++ b/tests/regression/58-base-mm-tid/26-phases-trivial.c @@ -0,0 +1,28 @@ +// PARAM: --set ana.path_sens[+] threadflag --set ana.base.privatization mutex-meet-tid --enable ana.int.interval --set ana.activated[+] threadJoins --set ana.activated[+] thread +#include +#include + +int g = 10; + +pthread_mutex_t A = PTHREAD_MUTEX_INITIALIZER; + + +void *t_benign(void *arg) { + pthread_mutex_lock(&A); + g = 10; + __goblint_check(g == 10); + pthread_mutex_unlock(&A); + return NULL; +} + + +int main(void) { + pthread_t id2; + pthread_create(&id2, NULL, t_benign, NULL); + pthread_join(id2, NULL); + + g = 20; + __goblint_check(g == 20); + + return 0; +} diff --git a/tests/regression/58-base-mm-tid/27-phases.c b/tests/regression/58-base-mm-tid/27-phases.c new file mode 100644 index 0000000000..eb450d2465 --- /dev/null +++ b/tests/regression/58-base-mm-tid/27-phases.c @@ -0,0 +1,51 @@ +// PARAM: --set ana.path_sens[+] threadflag --set ana.base.privatization mutex-meet-tid --enable ana.int.interval --set ana.activated[+] threadJoins --set ana.activated[+] thread +#include +#include + +int g = 10; + +pthread_mutex_t A = PTHREAD_MUTEX_INITIALIZER; + + +void *t_benign(void *arg) { + pthread_mutex_lock(&A); + g = 10; + __goblint_check(g == 10); + pthread_mutex_unlock(&A); + return NULL; +} + +void *t_benign2(void *arg) { + pthread_mutex_lock(&A); + __goblint_check(g == 20); + g = 10; + __goblint_check(g == 10); + pthread_mutex_unlock(&A); + return NULL; +} + +int main(void) { + + pthread_t id2; + pthread_create(&id2, NULL, t_benign, NULL); + pthread_join(id2, NULL); + + + g = 20; + __goblint_check(g == 20); + + pthread_mutex_lock(&A); + __goblint_check(g == 20); + pthread_mutex_unlock(&A); + + pthread_create(&id2, NULL, t_benign2, NULL); + + + pthread_mutex_lock(&A); + __goblint_check(g == 20); //UNKNOWN! + __goblint_check(g == 10); //UNKNOWN! + pthread_mutex_unlock(&A); + + + return 0; +} diff --git a/tests/regression/58-base-mm-tid/28-phases-prot.c b/tests/regression/58-base-mm-tid/28-phases-prot.c new file mode 100644 index 0000000000..905448c300 --- /dev/null +++ b/tests/regression/58-base-mm-tid/28-phases-prot.c @@ -0,0 +1,48 @@ +// PARAM: --set ana.path_sens[+] threadflag --set ana.base.privatization mutex-meet-tid --enable ana.int.interval --set ana.activated[+] threadJoins --set ana.activated[+] thread +#include +#include + +int g = 10; + +pthread_mutex_t A = PTHREAD_MUTEX_INITIALIZER; + + +void *t_benign(void *arg) { + pthread_mutex_lock(&A); + g = 10; + __goblint_check(g == 10); + pthread_mutex_unlock(&A); + return NULL; +} + +void *t_benign2(void *arg) { + pthread_mutex_lock(&A); + __goblint_check(g == 30); //TODO (does not work as 20 from parent thread is potentially read) + g = 10; + __goblint_check(g == 10); + pthread_mutex_unlock(&A); + return NULL; +} + +int main(void) { + + pthread_t id2; + pthread_create(&id2, NULL, t_benign, NULL); + pthread_join(id2, NULL); + + + g = 20; + __goblint_check(g == 20); + + pthread_mutex_lock(&A); + __goblint_check(g == 20); + pthread_mutex_unlock(&A); + + pthread_mutex_lock(&A); + g = 30; + pthread_create(&id2, NULL, t_benign2, NULL); + __goblint_check(g == 30); + pthread_mutex_unlock(&A); + + return 0; +} diff --git a/tests/regression/58-base-mm-tid/29-phases-prot-prime.c b/tests/regression/58-base-mm-tid/29-phases-prot-prime.c new file mode 100644 index 0000000000..64a50b40aa --- /dev/null +++ b/tests/regression/58-base-mm-tid/29-phases-prot-prime.c @@ -0,0 +1,49 @@ +// PARAM: --set ana.path_sens[+] threadflag --set ana.base.privatization mutex-meet-tid --enable ana.int.interval --set ana.activated[+] threadJoins --set ana.activated[+] thread +#include +#include + +int g = 10; + +pthread_mutex_t A = PTHREAD_MUTEX_INITIALIZER; + + +void *t_benign(void *arg) { + pthread_mutex_lock(&A); + g = 10; + __goblint_check(g == 10); + pthread_mutex_unlock(&A); + return NULL; +} + +void *t_benign2(void *arg) { + pthread_mutex_lock(&A); + __goblint_check(g == 30); //UNKNOWN! + g = 10; + __goblint_check(g == 10); + pthread_mutex_unlock(&A); + return NULL; +} + +int main(void) { + + pthread_t id2; + pthread_create(&id2, NULL, t_benign, NULL); + pthread_join(id2, NULL); + + + g = 20; + __goblint_check(g == 20); + + pthread_mutex_lock(&A); + __goblint_check(g == 20); + pthread_mutex_unlock(&A); + + pthread_mutex_lock(&A); + g = 30; + pthread_create(&id2, NULL, t_benign2, NULL); + __goblint_check(g == 30); + g = 40; + pthread_mutex_unlock(&A); + + return 0; +} diff --git a/tests/regression/58-base-mm-tid/30-create-lock.c b/tests/regression/58-base-mm-tid/30-create-lock.c new file mode 100644 index 0000000000..635298fc71 --- /dev/null +++ b/tests/regression/58-base-mm-tid/30-create-lock.c @@ -0,0 +1,34 @@ +// PARAM: --set ana.path_sens[+] threadflag --set ana.base.privatization mutex-meet-tid --enable ana.int.interval --set ana.activated[+] threadJoins +#include +#include + +int g = 10; + +pthread_mutex_t A = PTHREAD_MUTEX_INITIALIZER; + + +void *t_benign(void *arg) { + return NULL; +} + +void *t_benign2(void *arg) { + pthread_mutex_lock(&A); + int x = g == 40; // For evaluations that happen before the side-effect of the unlock of A, g is bot and the exception is caught by eval_rv + __goblint_check(x); //UNKNOWN! + return NULL; +} + +int main(void) { + + pthread_t id2; + pthread_create(&id2, NULL, t_benign, NULL); + pthread_join(id2, NULL); + + pthread_mutex_lock(&A); + g = 30; + pthread_create(&id2, NULL, t_benign2, NULL); + g = 40; + pthread_mutex_unlock(&A); + + return 0; +} diff --git a/tests/regression/58-base-mm-tid/31-create-lock-assert.c b/tests/regression/58-base-mm-tid/31-create-lock-assert.c new file mode 100644 index 0000000000..02f4a15c6f --- /dev/null +++ b/tests/regression/58-base-mm-tid/31-create-lock-assert.c @@ -0,0 +1,38 @@ +// PARAM: --set ana.path_sens[+] threadflag --set ana.base.privatization mutex-meet-tid --enable ana.int.interval --set ana.activated[+] threadJoins +#include +#include + +int g = 10; + +pthread_mutex_t A = PTHREAD_MUTEX_INITIALIZER; + + +void *t_benign(void *arg) { + return NULL; +} + +void *t_benign2(void *arg) { + pthread_mutex_lock(&A); + // For evaluations that happen before the side-effect of the unlock of A, g is bot. + // This caused an excpetion in query_evalint which we now catch when we are not in verify mode. + __goblint_check(g == 40); //UNKNOWN! + __goblint_check(g == 30); //UNKNOWN! + __goblint_check(g == 10); //FAIL + pthread_mutex_unlock(&A); + return NULL; +} + +int main(void) { + + pthread_t id2; + pthread_create(&id2, NULL, t_benign, NULL); + pthread_join(id2, NULL); + + pthread_mutex_lock(&A); + g = 30; + pthread_create(&id2, NULL, t_benign2, NULL); + g = 40; + pthread_mutex_unlock(&A); + + return 0; +} diff --git a/tests/regression/58-base-mm-tid/32-phases-sound-tid.c b/tests/regression/58-base-mm-tid/32-phases-sound-tid.c new file mode 100644 index 0000000000..81304351bd --- /dev/null +++ b/tests/regression/58-base-mm-tid/32-phases-sound-tid.c @@ -0,0 +1,45 @@ +// PARAM: --set ana.path_sens[+] threadflag --set ana.base.privatization mutex-meet-tid --enable ana.int.interval --set ana.activated[+] threadJoins +#include +#include + +int g = 10; + +pthread_mutex_t A = PTHREAD_MUTEX_INITIALIZER; + +void *t_benign(void *arg) { + pthread_mutex_lock(&A); + g = 10; + __goblint_check(g == 10); + pthread_mutex_unlock(&A); + return NULL; +} + +void *t_benign2(void *arg) { + pthread_mutex_lock(&A); + __goblint_check(g == 20); + g = 10; + __goblint_check(g == 10); + pthread_mutex_unlock(&A); + return NULL; +} + +int main(void) { + + pthread_t id2; + pthread_create(&id2, NULL, t_benign, NULL); + pthread_join(id2, NULL); + + + g = 20; + __goblint_check(g == 20); + + pthread_t id; + pthread_create(&id, NULL, t_benign2, NULL); + + + pthread_mutex_lock(&A); + __goblint_check(g == 20); //UNKNOWN! + pthread_mutex_unlock(&A); + + return 0; +} diff --git a/tests/regression/58-base-mm-tid/33-phases-sound-tid-other.c b/tests/regression/58-base-mm-tid/33-phases-sound-tid-other.c new file mode 100644 index 0000000000..30a0a7003f --- /dev/null +++ b/tests/regression/58-base-mm-tid/33-phases-sound-tid-other.c @@ -0,0 +1,41 @@ +// PARAM: --set ana.path_sens[+] threadflag --set ana.base.privatization mutex-meet-tid --enable ana.int.interval --set ana.activated[+] threadJoins +#include +#include + +int g = 10; + +pthread_mutex_t A = PTHREAD_MUTEX_INITIALIZER; + +void* t_other(void *arg) { + g = 60; +} + +void *t_benign(void *arg) { + pthread_mutex_lock(&A); + g = 10; + __goblint_check(g == 10); //UNKNOWN! + pthread_mutex_unlock(&A); + + + pthread_t id2; + pthread_create(&id2, NULL, t_other, NULL); + + return NULL; +} + +int main(void) { + + pthread_t id2; + pthread_create(&id2, NULL, t_benign, NULL); + pthread_join(id2, NULL); + + + g = 20; + __goblint_check(g == 20); //UNKNOWN! + + pthread_mutex_lock(&A); + __goblint_check(g == 20); //UNKNOWN! + pthread_mutex_unlock(&A); + + return 0; +} diff --git a/tests/regression/62-abortUnless/04-lval.c b/tests/regression/62-abortUnless/04-lval.c new file mode 100644 index 0000000000..bcfab23c91 --- /dev/null +++ b/tests/regression/62-abortUnless/04-lval.c @@ -0,0 +1,18 @@ +// PARAM: --set ana.activated[+] abortUnless +#include + +int assume_abort_if_not(int cond) { + if(!cond) + { + abort(); + } + return 42; +} + +int main(void) +{ + int x, y; + y = assume_abort_if_not(x == 8); + __goblint_check(x==8); + __goblint_check(y==42); +} diff --git a/tests/regression/65-taint/01-simple.c b/tests/regression/65-taint/01-simple.c new file mode 100644 index 0000000000..201c7842f6 --- /dev/null +++ b/tests/regression/65-taint/01-simple.c @@ -0,0 +1,32 @@ +//PARAM --set "ana.activated[+]" taintPartialContexts --disable ana.base.context.int +#include + +int g; + +int identity (int a) { + return a; +} + +void addOnePtr (int *a, int *b) { + *a = *a + 1; +} + +int main() { + int y, z; + + g = 42; + identity(10); + g = -42; + identity(9); + + __goblint_check(g <= 0); + + z = 3; + y = 3; + addOnePtr(&y, &z); + z = -3; + y = -3; + addOnePtr(&y, &z); + + __goblint_check(z == -3); +} diff --git a/tests/regression/65-taint/02-invalidate.c b/tests/regression/65-taint/02-invalidate.c new file mode 100644 index 0000000000..cf4636192f --- /dev/null +++ b/tests/regression/65-taint/02-invalidate.c @@ -0,0 +1,28 @@ +//PARAM --set "ana.activated[+]" taintPartialContexts --set ana.ctx_insens[+] base +#include + +extern void unknown_fun (int *a); + +void mainfunct(int *rptr, int* uptr) { + unknown_fun(rptr); +} + +int g; + +int main() { + int r, u; + + g = 1; + r = 1; + u = 1; + mainfunct(&r, &u); + + g = 2; + r = 2; + u = 2; + mainfunct(&r, &u); + + __goblint_check(g == 2); //UNKNOWN! + __goblint_check(r == 2); //UNKNOWN! + __goblint_check(u == 2); +} diff --git a/tests/regression/65-taint/03-lval.c b/tests/regression/65-taint/03-lval.c new file mode 100644 index 0000000000..467432198b --- /dev/null +++ b/tests/regression/65-taint/03-lval.c @@ -0,0 +1,57 @@ +//PARAM: --set ana.ctx_insens[+] base --set ana.activated[+] taintPartialContexts --set ana.base.arrays.domain unroll --set ana.base.arrays.unrolling-factor 2 +#include + +struct myStruct { + int n; + int q; +}; + +struct myStruct globStrct; + +int globArry[2]; +int globInt; + +void f(int* mem) { + globArry[1] = 70; + globStrct.q = 70; + *(mem + 1) = 70; +} + +int main () { + int *locMem = malloc(sizeof(int)*2); + + // init + globInt = 1; + globStrct.n = 1; + globStrct.q = 1; + globArry[0] = 1; + globArry[1] = 1; + *(locMem) = 1; + *(locMem + 1) = 1; + + f(locMem); + + //change + globInt = 2; + globStrct.n = 2; + globStrct.q = 2; + globArry[0] = 2; + globArry[1] = 2; + *(locMem) = 2; + *(locMem + 1) = 2; + + f(locMem); + + //check untainted + __goblint_check(globInt == 2); + __goblint_check(globStrct.n == 2); + __goblint_check(globArry[0] == 2); + __goblint_check(*(locMem) == 2); //UNKNOWN + + //validate tainted + __goblint_check(globStrct.q == 70); + __goblint_check(globArry[1] == 70); + __goblint_check(*(locMem) == 70); //UNKNOWN + + free(locMem); +} diff --git a/tests/regression/65-taint/04-multithread.c b/tests/regression/65-taint/04-multithread.c new file mode 100644 index 0000000000..8de85fe251 --- /dev/null +++ b/tests/regression/65-taint/04-multithread.c @@ -0,0 +1,50 @@ +// NOMARSHAL PARAM: --set ana.activated[+] taintPartialContexts +#include +#include + +int glob_rel; +int glob_keep; +pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER; +pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER; + +void f(pthread_mutex_t *mutex_ptr) { + pthread_mutex_unlock(mutex_ptr); +} + +void g() { +} + +void *t_fun(void *arg) { + int x_t_fun = -2; + int context = 1; + + // f releases the mutex, so information should be lost after call, even though glob_rel is untainted + pthread_mutex_lock(&mutex1); + glob_rel = 1; + f(&mutex1); + __goblint_check(glob_rel == 1); //UNKNOWN! + + // g does nothing, so glob_keep information can be kept + pthread_mutex_lock(&mutex2); + glob_keep = 1; + g(); + __goblint_check(glob_keep == 1); + pthread_mutex_unlock(&mutex2); + + //sanity + __goblint_check(x_t_fun == -2); + return NULL; +} + +int main() { + int x_main = -1; + int x_arg = -3; + pthread_t id; + + pthread_create(&id, NULL, t_fun, &x_arg); + pthread_join (id, NULL); + + //sanity + __goblint_check(x_main == -1); + __goblint_check(x_arg == -3); +} diff --git a/tests/regression/65-taint/05-partArray.c b/tests/regression/65-taint/05-partArray.c new file mode 100644 index 0000000000..01e3e973b8 --- /dev/null +++ b/tests/regression/65-taint/05-partArray.c @@ -0,0 +1,28 @@ +// PARAM: --enable ana.int.interval --set ana.base.arrays.domain partitioned --set ana.ctx_insens[+] base --set ana.activated[+] taintPartialContexts +// adapted from 22 06 +#include + +void do_first(int* arr) { + int x = arr[0]; + arr[0] = 3; +} + +void init_array(int* arr, int val) { + for(int i = 0; i < 20; i++) { + arr[i] = val; + } + arr[0] = val; + __goblint_check(arr[2] == val); + __goblint_check(arr[10] == val); +} + +int main() { + int a[20]; + + init_array(a, 42); + __goblint_check(a[2] == 42); + __goblint_check(a[10] == 42); + + do_first(a); + __goblint_check(a[0] == 3); +} diff --git a/tests/regression/65-taint/06-condVars.c b/tests/regression/65-taint/06-condVars.c new file mode 100644 index 0000000000..8dcc01b574 --- /dev/null +++ b/tests/regression/65-taint/06-condVars.c @@ -0,0 +1,25 @@ +// PARAM: --set ana.activated[+] condvars --set ana.activated[+] taintPartialContexts +#include + +int glob; + +void f() { +} + +int main() { + int unk; + int tv; + if (unk) + glob = 0; + else + glob = 10; + + tv = (glob == 0); + f(); + + if (tv) + __goblint_assert(glob == 0); + else + __goblint_assert(glob != 0); + +} diff --git a/tests/regression/65-taint/07-varEq.c b/tests/regression/65-taint/07-varEq.c new file mode 100644 index 0000000000..0c2f6ab967 --- /dev/null +++ b/tests/regression/65-taint/07-varEq.c @@ -0,0 +1,17 @@ +// PARAM: --set ana.activated[+] var_eq --set ana.ctx_insens[+] var_eq --set ana.activated[+] taintPartialContexts +#include + +void f(int *zptr){ + +} + +int main() { + int z, x; + + f(&z); + z = x; + f(&z); + + __goblint_check(z == x); + +} diff --git a/tests/regression/65-taint/09-multipleVar.c b/tests/regression/65-taint/09-multipleVar.c new file mode 100644 index 0000000000..e55fc9b3a3 --- /dev/null +++ b/tests/regression/65-taint/09-multipleVar.c @@ -0,0 +1,25 @@ +// PARAM: --set ana.activated[+] taintPartialContexts +#include + +int rec_f(int *ptr) { + // function is called two times: + // 1. from main where *ptr<1> = x_main = 0. x_f<1> = 6 is initialized, + // then rec_f(&x_f<1>) is called and the return value is returned to main + // 2. from rec_f<1> where *ptr<2> = x_f<1> = 6. This is changed to -6, tainting x_f. + // It is important that at this point x_f is not removed from the taint_set (because it is local), + // otherwise in rec_f<1> x_f will be untainted and the old (wrong) value of 6 is kept + int x_f = 6; + if (*ptr == 0){ + rec_f(&x_f); + } else { + *ptr = -6; + } + return x_f; +} + +void main () { + int x_main = 0; + int c; + c = rec_f(&x_main); + __goblint_check(c == 6); //UNKNOWN +} diff --git a/tests/regression/65-taint/10-callToMain.c b/tests/regression/65-taint/10-callToMain.c new file mode 100644 index 0000000000..9f98270465 --- /dev/null +++ b/tests/regression/65-taint/10-callToMain.c @@ -0,0 +1,19 @@ +// PARAM: --disable ana.base.context.non-ptr --set ana.activated[+] taintPartialContexts +#include + +int glob; + +int main(int argc, char **argv) { + char **other= malloc(1); + + if(argc < 0) { + return 0; + } else { + glob = 10; + main(-1, other); + glob = -10; + main(-1, other); + + __goblint_check(glob < 0); + } +} diff --git a/tests/regression/65-taint/11-svcomp-cstrcmp.c b/tests/regression/65-taint/11-svcomp-cstrcmp.c new file mode 100644 index 0000000000..2b5c09d96a --- /dev/null +++ b/tests/regression/65-taint/11-svcomp-cstrcmp.c @@ -0,0 +1,45 @@ +// PARAM: --enable ana.int.interval --set ana.activated[+] taintPartialContexts +// Adapted from sv-benchmarks/termination-crafted-lit/cstrcmp.c +typedef long unsigned int size_t; + +void * __attribute__((__cdecl__)) malloc (size_t __size) ; + +extern int __VERIFIER_nondet_int(void); + +/* Returns some null-terminated string. */ +char* build_nondet_String(void) { + int length = __VERIFIER_nondet_int(); + if (length < 1) { + length = 1; + } + char* nondetString = (char*) malloc(length * sizeof(char)); + nondetString[length-1] = '\0'; + return nondetString; +} + + + + + +int (cstrcmp)(const char *s1, const char *s2) + { + unsigned char uc1, uc2; + /* Move s1 and s2 to the first differing characters + in each string, or the ends of the strings if they + are identical. */ + while (*s1 != '\0' && *s1 == *s2) { + s1++; //NOWARN! + s2++; //NOWARN! + } + /* Compare the characters as unsigned char and + return the difference. */ + uc1 = (*(unsigned char *) s1); + uc2 = (*(unsigned char *) s2); + return ((uc1 < uc2) ? -1 : (uc1 > uc2)); + } + +int main() { + return cstrcmp(build_nondet_String(),build_nondet_String()); +} + + diff --git a/tests/regression/66-interval-set-one/00-was_problematic_2.c b/tests/regression/66-interval-set-one/00-was_problematic_2.c new file mode 100644 index 0000000000..e5b3938a76 --- /dev/null +++ b/tests/regression/66-interval-set-one/00-was_problematic_2.c @@ -0,0 +1,24 @@ +// PARAM: --enable ana.int.interval_set --set ana.base.arrays.domain partitioned +int main(void) +{ + int arr[260]; + int n; + + n = 5; + arr[0] = 0; + + while (n > 1) { //here + arr[1] = 7; + n--; + } + + int arr2[260]; + + n = 5; + arr2[259] = 0; + + while (n > 1) { + arr2[258] = 7; + n--; + } +} diff --git a/tests/regression/66-interval-set-one/01-dynamically-sized-array-oob-access.c b/tests/regression/66-interval-set-one/01-dynamically-sized-array-oob-access.c new file mode 100644 index 0000000000..90cd7727fc --- /dev/null +++ b/tests/regression/66-interval-set-one/01-dynamically-sized-array-oob-access.c @@ -0,0 +1,33 @@ +// PARAM: --enable ana.arrayoob --enable ana.int.interval_set --enable ana.int.interval_set --enable ana.int.enums + +// Variable sized array: oob access + +#include +#include +int main() { + int top; + int N; +// The if statement is needed, so the size is actually dynamic + if (top) { + N = 5; + } else { + N = 10; + } + int arr[N]; + arr[0] = 1; + arr[1] = 2; + arr[2] = 3; + arr[3] = 4; + arr[4] = 5; // NOWARN + arr[-1] = 10; // WARN + for (int i = 0; i < 5; ++i) { + arr[i] = 5; // NOWARN + } + for (int i = 0; i <= 5; ++i) { + arr[i] = 5; // WARN + } + for (int i = -2; i < 5; ++i) { + arr[i] = 5; // WARN + } + return 0; +} diff --git a/tests/regression/66-interval-set-one/02-continue.c b/tests/regression/66-interval-set-one/02-continue.c new file mode 100644 index 0000000000..1439c0a755 --- /dev/null +++ b/tests/regression/66-interval-set-one/02-continue.c @@ -0,0 +1,20 @@ +// PARAM: --enable ana.int.interval_set --set exp.unrolling-factor 5 --set ana.base.arrays.domain unroll --set ana.base.arrays.unrolling-factor 5 +// Simple example +#include + +void main(void) +{ + int j = 0; + + for(int i=0;i < 50;i++) { + if(i < 2) { + continue; + } + if(i>4) { + break; + } + j++; + } + + __goblint_check(j==3); +} diff --git a/tests/regression/66-interval-set-one/03-was_problematic_3.c b/tests/regression/66-interval-set-one/03-was_problematic_3.c new file mode 100644 index 0000000000..59ce6e5cb6 --- /dev/null +++ b/tests/regression/66-interval-set-one/03-was_problematic_3.c @@ -0,0 +1,52 @@ +// PARAM: --enable ana.int.interval_set --set ana.base.arrays.domain partitioned +struct some_struct +{ + int dir[7]; + int length; + double coeff; + double forwback; +}; + +struct some_struct q_paths[200]; +int num_q_paths; + +int add_basic_path(int length, double coeff) +{ + int ir[4]; + int j; + int flag; + + ir[2] = 0; + while (ir[2] < 2) + { + ir[3] = 0; + while (ir[3] < 2) + { + j = 0; + while (j < num_q_paths) + { + if (flag == 1) + { + break; + } + j++; + } + + q_paths[num_q_paths].length = length; + + num_q_paths++; + + (ir[3])++; + } + (ir[2])++; + } + + return 42; +} + +int main(int argc, char **argv) +{ + double this_coeff; + int pl; + add_basic_path(pl, this_coeff); +} diff --git a/tests/regression/66-interval-set-one/04-interval-overflow.c b/tests/regression/66-interval-set-one/04-interval-overflow.c new file mode 100644 index 0000000000..77ca6c5b59 --- /dev/null +++ b/tests/regression/66-interval-set-one/04-interval-overflow.c @@ -0,0 +1,44 @@ +// PARAM: --enable ana.int.interval_set --enable ana.int.congruence --disable ana.int.def_exc +// Overflow information should be passed from the interval domain to congruences +#include +#include + +int main(){ + signed char r; + + if (r) { + r = -68; + } else { + r = -63; + } + + signed char k = r - 80; + __goblint_check(k == 0); //UNKNOWN! + + signed char non_ov = r - 10; + __goblint_check(non_ov == -78); //UNKNOWN! + + signed char m = r * 2; + + __goblint_check(m == 0); //UNKNOWN! + + signed char l = r + (-80); + __goblint_check(l == 0); //UNKNOWN! + + int g; + + if (g) { + g = -126; + } else { + g = -128; + } + + signed char f = g / (-1); + __goblint_check(f == 1); //UNKNOWN! + + signed char d = -g; + __goblint_check(d == 1); //UNKNOWN! + + return 0; + +} diff --git a/tests/regression/66-interval-set-one/05-sync.c b/tests/regression/66-interval-set-one/05-sync.c new file mode 100644 index 0000000000..deea7768ff --- /dev/null +++ b/tests/regression/66-interval-set-one/05-sync.c @@ -0,0 +1,41 @@ +// PARAM: --set ana.path_sens[+] threadflag --set ana.base.privatization mutex-meet-tid --enable ana.int.interval_set --set ana.activated[-] threadJoins +// Inspired by 36/87 +#include +#include +#include + +int g; +int h; +pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; + +void *t_fun(void *arg) { + pthread_mutex_lock(&mutex); + __goblint_check(g==h); + pthread_mutex_unlock(&mutex); + return NULL; +} + + +int main(void) { + int top2; + + + pthread_t id; + pthread_create(&id, NULL, t_fun, NULL); + + pthread_mutex_lock(&mutex); + if(top2) { + g=34; + h=77; + } + + g=0; + h=0; + pthread_mutex_unlock(&mutex); + + pthread_mutex_lock(&mutex); + __goblint_check(g==h); + pthread_mutex_unlock(&mutex); + + return 0; +} diff --git a/tests/regression/66-interval-set-one/06-ints.c b/tests/regression/66-interval-set-one/06-ints.c new file mode 100644 index 0000000000..9c65d45ed7 --- /dev/null +++ b/tests/regression/66-interval-set-one/06-ints.c @@ -0,0 +1,194 @@ +// PARAM: --enable ana.int.interval_set --set sem.int.signed_overflow assume_wraparound +// With sem.int.signed_overflow set to assume_wraparound to assume two's complement representation for signed ints and don't go to top on every signed overflow. +#include + +#define RANGE(x, l, u) x >= l && x <= u + +int main() { + main2(); + + + int x, y; + if (x+1 == 2) { + __goblint_check(x == 1); + } else { + __goblint_check(x != 1); + } + if (5-x == 3) + __goblint_check(x == 2); + else + __goblint_check(x != 2); + if (5-x == 3 && x+y == x*3) + __goblint_check(x == 2 && y == 4); + if (x == 3 && y/x == 2) { + __goblint_check(y == 6); // UNKNOWN! + __goblint_check(RANGE(y, 6, 8)); + } + if (y/3 == -2) + __goblint_check(RANGE(y, -8, -6)); + if (y/-3 == -2) + __goblint_check(RANGE(y, 6, 8)); + if (y/x == 2 && x == 3) + __goblint_check(x == 3); // TO-DO y == [6,8]; this does not work because CIL transforms this into two if-statements + if (2+(3-x)*4/5 == 6 && 2*y >= x+5) + __goblint_check(RANGE(x, -3, -2) && y >= 1); // UNKNOWN + if (x > 1 && x < 5 && x % 2 == 1) // x = [2,4] && x % 2 = 1 => x = 3 + __goblint_check(x == 3); + + + long xl, yl, zl; + if (xl+1 == 2) { + __goblint_check(xl == 1); + } else { + __goblint_check(xl != 1); + } + if (5-xl == 3) + __goblint_check(xl == 2); + if (5-xl == 3 && xl+yl == xl*3) + __goblint_check(xl == 2 && yl == 4); + if (xl == 3 && yl/xl == 2) + // yl could for example also be 7 + __goblint_check(yl == 6); // UNKNOWN! + if (yl/xl == 2 && xl == 3) + __goblint_check(xl == 3); // TO-DO yl == 6 + if (2+(3-xl)*4/5 == 6 && 2*yl >= xl+4) + // xl could also be -3 + __goblint_check(xl == -2 && yl >= 1); //UNKNOWN! + if (xl > 1 && xl < 5 && xl % 2 == 1) { + __goblint_check(xl != 2); // [2,4] -> [3,4] TO-DO x % 2 == 1 + } + + + short xs, ys, zs; + if (xs+1 == 2) { + __goblint_check(xs == 1); + } else { + // Does not survive the casts inserted by CIL + // __goblint_check(xs != 1); + } + if (5-xs == 3) + __goblint_check(xs == 2); + if (5-xs == 3 && xs+ys == xs*3) + __goblint_check(xs == 2 && ys == 4); + if (xs == 3 && ys/xs == 2) { + // ys could for example also be 7 + __goblint_check(ys == 6); // UNKNOWN! + __goblint_check(RANGE(ys, 6, 8)); + } + if (ys/3 == -2) + __goblint_check(RANGE(ys, -8, -6)); + if (ys/-3 == -2) + __goblint_check(RANGE(ys, 6, 8)); + if (ys/xs == 2 && xs == 3) + __goblint_check(xs == 3); // TO-DO yl == 6 + if (2+(3-xs)*4/5 == 6 && 2*ys >= xs+5) { + // xs could also be -3 + __goblint_check(xs == -2 && ys >= 1); //UNKNOWN! + __goblint_check(RANGE(xs, -3, -2) && ys >= 1); // UNKNOWN + } + if (xs > 1 && xs < 5 && xs % 2 == 1) { + __goblint_check(xs != 2); + } + +} + +int main2() { + int one = 1; + int two = 2; + int three = 3; + int four = 4; + int five = 5; + int six = 6; + + int x, y, z; + if (x+one == two) { + __goblint_check(x == one); + } else { + __goblint_check(x != one); + } + if (five-x == three) + __goblint_check(x == two); + if (five-x == three && x+y == x*three) + __goblint_check(x == two && y == four); + if (x == three && y/x == two) { + // y could for example also be 7 + __goblint_check(y == six); // UNKNOWN! + __goblint_check(RANGE(y, 6, 8)); + } + if (y/x == two && x == three) + __goblint_check(x == three); // TO-DO y == six + if (two+(three-x)*four/five == six && two*y >= x+four) + // x could also be -three + __goblint_check(x == -two && y >= one); //UNKNOWN! + if (x > one && x < five && x % two == one) + __goblint_check(x != two); // [two,four] -> [three,four] TO-DO x % two == one + + if (y/three == -two) + __goblint_check(RANGE(y, -8, -6)); + if (y/-three == -two) + __goblint_check(RANGE(y, 6, 8)); + if (y/x == two && x == three) + __goblint_check(x == 3); // TO-DO y == [6,8]; this does not work because CIL transforms this into two if-statements + if (two+(three-x)*four/five == six && two*y >= x+five) + __goblint_check(RANGE(x, -3, -2) && y >= 1); // UNKNOWN + if (x > one && x < five && x % two == one) // x = [2,4] && x % 2 = 1 => x = 3 + __goblint_check(x != 2); // [3,4] TO-DO [3,3] + + + + long xl, yl, zl; + if (xl+one == two) { + __goblint_check(xl == one); + } else { + __goblint_check(xl != one); + } + if (five-xl == three) + __goblint_check(xl == two); + if (five-xl == three && xl+yl == xl*three) + __goblint_check(xl == two && yl == four); + if (xl == three && yl/xl == two) + // yl could for example also be 7 + __goblint_check(yl == six); // UNKNOWN! + if (yl/xl == two && xl == three) + __goblint_check(xl == three); // TO-DO yl == six + if (two+(three-xl)*four/five == six && two*yl >= xl+four) + // xl could also be -three + __goblint_check(xl == -two && yl >= one); //UNKNOWN! + if (xl > one && xl < five && xl % two == one) { + __goblint_check(xl != two); // [two,four] -> [three,four] TO-DO x % two == one + } + + + short xs, ys, zs; + if (xs+one == two) { + __goblint_check(xs == one); + } else { + // Does not survive the casts inserted by CIL + // __goblint_check(xs != one); + } + if (five-xs == three) + __goblint_check(xs == two); + if (five-xs == three && xs+ys == xs*three) + __goblint_check(xs == two && ys == four); + if (xs == three && ys/xs == two) { + // ys could for example also be 7 + __goblint_check(ys == six); // UNKNOWN! + __goblint_check(RANGE(ys, six, 8)); + } + if (ys/xs == two && xs == three) + __goblint_check(xs == three); // TO-DO yl == six + if (two+(three-xs)*four/five == six && two*ys >= xs+five) { + // xs could also be -three + __goblint_check(xs == -two && ys >= one); //UNKNOWN! + __goblint_check(RANGE(xs, -three, -two) && ys >= one); // UNKNOWN + } + if (xs > one && xs < five && xs % two == one) { + __goblint_check(xs != two); + } + if (ys/three == -two) + __goblint_check(RANGE(ys, -8, -6)); + if (ys/-three == -two) + __goblint_check(RANGE(ys, 6, 8)); + if (ys/xs == two && xs == three) + __goblint_check(xs == 3); // TO-DO y == [6,8]; this does not work because CIL transforms this into two if-statements +} diff --git a/tests/regression/66-interval-set-one/07-no-int-context-attribute.c b/tests/regression/66-interval-set-one/07-no-int-context-attribute.c new file mode 100644 index 0000000000..ed8666081c --- /dev/null +++ b/tests/regression/66-interval-set-one/07-no-int-context-attribute.c @@ -0,0 +1,16 @@ +// PARAM: --enable ana.int.interval_set --disable ana.context.widen --enable ana.base.context.int +#include + +int f(int x) __attribute__((goblint_context("base.no-int"))); // attributes are not permitted in a function definition +int f(int x) { + if (x) + return f(x+1); + else + return x; +} + +int main () { + int a = f(1); + __goblint_check(!a); + return 0; +} diff --git a/tests/regression/66-interval-set-one/08-base-priv-sync-prune.c b/tests/regression/66-interval-set-one/08-base-priv-sync-prune.c new file mode 100644 index 0000000000..1687659fd4 --- /dev/null +++ b/tests/regression/66-interval-set-one/08-base-priv-sync-prune.c @@ -0,0 +1,22 @@ +// SKIP PARAM: --set ana.base.privatization write+lock --enable ana.int.interval_set --set witness.yaml.validate 04-base-priv-sync-prune.yml +// TODO: fix unsoundness in base priv syncs +#include + +int g = 0; + +void *t_fun(void *arg) { + ; // FAIL (witness) + return NULL; +} + +int main() { + int r, r2; // rand + + if (r) + g = 1; + + pthread_t id; + for (int i = 0; i < r2; r++) + pthread_create(&id, NULL, t_fun, NULL); + return 0; +} diff --git a/tests/regression/66-interval-set-one/09-intervals-large.c b/tests/regression/66-interval-set-one/09-intervals-large.c new file mode 100644 index 0000000000..0c98cdfef1 --- /dev/null +++ b/tests/regression/66-interval-set-one/09-intervals-large.c @@ -0,0 +1,26 @@ +//PARAM: --enable ana.int.interval_set --disable ana.int.def_exc --disable ana.int.enums +#include + +int main(){ + int a = 0; + + // maximum value for ulonglong + unsigned long long x = 18446744073709551615ull; + if(x > 18446744073709551612ull){ + a = 1; + } + __goblint_check(a); + + unsigned long long y = x + 4; + __goblint_check(y == 3); + + // maximum value for long long + signed long long s = 9223372036854775807; + __goblint_check(s > 9223372036854775806); + + signed long long t = s + 2; + // Signed overflow -- The following assertion must be UNKNOWN! + __goblint_check(t == -9223372036854775807); // UNKNOWN! + + return 0; +} diff --git a/tests/regression/66-interval-set-one/10-calloc_struct.c b/tests/regression/66-interval-set-one/10-calloc_struct.c new file mode 100644 index 0000000000..a4981b7c20 --- /dev/null +++ b/tests/regression/66-interval-set-one/10-calloc_struct.c @@ -0,0 +1,42 @@ +// PARAM: --set ana.int.interval_set true --set ana.base.arrays.domain partitioned + +#include +#include + +typedef struct { + int x; + int y; +} data; + +data *d; + +int main(void) { + d = calloc(1,sizeof(data)); + d -> x = 0; + d -> y = 0; + + data e = {.x = 0, .y = 0}; + + __goblint_check(d->x == e.x); + __goblint_check(d->y == e.y); + + int a = d -> x; + int b = d -> y; + + __goblint_check(a != 3); + __goblint_check(b != 4); + + d -> x = 3; + d -> y = 4; + + data f = {.x = 3, .y = 3}; + + __goblint_check(d->x == f.x); //UNKNOWN + __goblint_check(d->y != f.y); + + a = d -> x; + b = d -> y; + + __goblint_check(a == 3); //UNKNOWN + __goblint_check(b == 4); //UNKNOWN +} diff --git a/tests/regression/66-interval-set-one/11-nesting_arrays.c b/tests/regression/66-interval-set-one/11-nesting_arrays.c new file mode 100644 index 0000000000..37b25dfe8a --- /dev/null +++ b/tests/regression/66-interval-set-one/11-nesting_arrays.c @@ -0,0 +1,211 @@ +// PARAM: --enable ana.int.interval_set --set ana.base.arrays.domain partitioned --enable annotation.int.enabled --set ana.int.refinement fixpoint +#include + +struct kala { + int i; + int a[5]; +}; + +struct kalaw { + int* a; +}; + +struct kass { + int v; +}; + +union uArray { + int a[5]; + int b[5]; +}; + +union uStruct { + int b; + struct kala k; +}; + +int main(void) __attribute__((goblint_precision("no-interval"))); +void example1() __attribute__((goblint_precision("no-def_exc"))); +void example2() __attribute__((goblint_precision("no-def_exc"))); +void example3() __attribute__((goblint_precision("no-def_exc"))); +void example4() __attribute__((goblint_precision("no-def_exc"))); +void example5() __attribute__((goblint_precision("no-def_exc"))); +void example6() __attribute__((goblint_precision("no-def_exc"))); +void example7() __attribute__((goblint_precision("no-def_exc"))); +void example8() __attribute__((goblint_precision("no-def_exc"))); + + +int main(void) { + example1(); + example2(); + example3(); + example4(); + example5(); + example6(); + example7(); + example8(); + return 0; +} + +void example1() { + struct kala l; + int i = 0; + int top; + + while (i < 5) { + l.a[i] = 42; + i++; + + // Check assertion that should only hold later does not already hold here + __goblint_check(l.a[4] == 42); //UNKNOWN + } + + // Check the array is correctly initialized + __goblint_check(l.a[1] == 42); + __goblint_check(l.a[2] == 42); + __goblint_check(l.a[3] == 42); + __goblint_check(l.a[4] == 42); + + // Destructively assign to i + i = top; + + // Check the array is still known to be completly initialized + __goblint_check(l.a[1] == 42); + __goblint_check(l.a[2] == 42); + __goblint_check(l.a[3] == 42); + __goblint_check(l.a[4] == 42); +} + +void example2() { + struct kala kalas[5]; + + int i2 = 0; + + while (i2 < 4) { + int j2 = 0; + while (j2 < 5) { + kalas[i2].a[j2] = 8; + j2++; + } + i2++; + } + + // Initialization has not proceeded this far + __goblint_check(kalas[4].a[0] == 8); //UNKNOWN + __goblint_check(kalas[0].a[0] == 8); +} + +void example3() { + struct kala xnn; + for(int l=0; l < 5; l++) { + xnn.a[l] = 42; + } + + __goblint_check(xnn.a[3] == 42); +} + +void example4() { + struct kala xn; + + struct kala xs[5]; + + for(int j=0; j < 4; j++) { + xs[j] = xn; + for(int k=0; k < 5; k++) { + xs[j].a[k] = 7; + } + } + + __goblint_check(xs[3].a[0] == 7); +} + +void example5() { + // This example is a bit contrived to show that splitting and moving works for + // unions + union uArray ua; + int i3 = 0; + int top; + int *i = ⊤ + + ua.a[*i] = 1; + + while (i3 < 5) { + ua.a[i3] = 42; + i3++; + } + + __goblint_check(ua.a[i3 - 1] == 42); + + ua.b[0] = 3; + __goblint_check(ua.b[0] == 3); + + // ------------------------------- + union uStruct us; + int i4 = 0; + + us.b = 4; + us.k.a[i4] = 0; + __goblint_check(us.b == 4); // UNKNOWN + __goblint_check(us.k.a[0] == 0); + __goblint_check(us.k.a[3] == 0); // UNKNOWN + + while (i4 < 5) { + us.k.a[i4] = 42; + i4++; + } + + __goblint_check(us.k.a[1] == 42); + __goblint_check(us.k.a[0] == 0); // FAIL +} + +void example6() { + int a[42]; + int i = 0; + + struct kass k; + k.v = 7; + + while(i < 42) { + a[i] = 0; + i++; + } + + i = 0; + + a[k.v] = 2; + k.v = k.v+1; + + __goblint_check(a[k.v] != 3); +} + +void example7() { + // Has no asserts, just checks this doesn't cause an infinite loop + int a[42]; + int i = 0; + + while(i < 40) { + a[i] = 0; + i++; + } + + a[a[0]] = 2; +} + +// Test correct behavior with more involved expression in subscript operator +void example8() { + int a[42]; + union uArray ua; + + ua.a[0] = 0; + ua.a[1] = 0; + ua.a[2] = 0; + ua.a[3] = 0; + ua.a[4] = 0; + + int i = 0; + int *ip = &i; + + a[ua.a[*ip]] = 42; + ip++; + __goblint_check(a[ua.a[*ip]] == 42); //UNKNOWN +} diff --git a/tests/regression/66-interval-set-one/12-tid-toy10.c b/tests/regression/66-interval-set-one/12-tid-toy10.c new file mode 100644 index 0000000000..26bfb6e363 --- /dev/null +++ b/tests/regression/66-interval-set-one/12-tid-toy10.c @@ -0,0 +1,48 @@ +// PARAM: --set ana.path_sens[+] threadflag --set ana.base.privatization mutex-meet-tid --enable ana.int.interval_set --set ana.activated[+] threadJoins +// Inspired by 36/80 +#include +#include + +int g = 10; +int h = 10; +pthread_mutex_t A = PTHREAD_MUTEX_INITIALIZER; + +void *t_benign(void *arg) { + pthread_mutex_lock(&A); + g = 10; + h = 10; + pthread_mutex_unlock(&A); + return NULL; +} + +int main(void) { + int t = 10; + + // Force multi-threaded handling + pthread_t id2; + pthread_create(&id2, NULL, t_benign, NULL); + + pthread_mutex_lock(&A); + g = 12; + h = 14; + pthread_mutex_unlock(&A); + + pthread_join(id2, NULL); + + pthread_mutex_lock(&A); + __goblint_check(g == h); //UNKNOWN! + pthread_mutex_unlock(&A); + + pthread_mutex_lock(&A); + g = 12; + h = 14; + pthread_mutex_unlock(&A); + + int t = 9; + + pthread_mutex_lock(&A); + __goblint_check(g == h); //FAIL + pthread_mutex_unlock(&A); + + return 0; +} diff --git a/tests/regression/66-interval-set-one/13-glob_interval.c b/tests/regression/66-interval-set-one/13-glob_interval.c new file mode 100644 index 0000000000..40a23e93de --- /dev/null +++ b/tests/regression/66-interval-set-one/13-glob_interval.c @@ -0,0 +1,36 @@ +// PARAM: --set ana.int.interval_set true +#include +#include + +int glob = 0; +pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER; + +void *t_fun(void *arg) { + pthread_mutex_lock(&mtx); + glob = 999; + pthread_mutex_unlock(&mtx); + return NULL; +} + +int main() { + int i = 3; + pthread_t id; + + __goblint_check(glob == 0); + + // Create the thread + pthread_create(&id, NULL, t_fun, NULL); + + // Simple assignments to only locals + __goblint_check(i == 3); + i = 9; + __goblint_check(i == 9); + + glob = 10; + + i = glob; + __goblint_check(i >= 0); + __goblint_check(i > 100); // UNKNOWN + + return 0; +} diff --git a/tests/regression/66-interval-set-one/14-no-int-context.c b/tests/regression/66-interval-set-one/14-no-int-context.c new file mode 100644 index 0000000000..69d2aa2aa7 --- /dev/null +++ b/tests/regression/66-interval-set-one/14-no-int-context.c @@ -0,0 +1,12 @@ +// PARAM: --enable ana.int.interval_set --set solver slr3t --disable ana.base.context.int + +int f (int i) { // -2 + return i+1; } // -3 +void g(int j) { // -4 + f(j); } // -5 +int main(){ + int x; + x = f(1); // -1 + g(x); // 0 + return 0; +} diff --git a/tests/regression/66-interval-set-one/16-simple.c b/tests/regression/66-interval-set-one/16-simple.c new file mode 100644 index 0000000000..e84e727ba7 --- /dev/null +++ b/tests/regression/66-interval-set-one/16-simple.c @@ -0,0 +1,17 @@ +// PARAM: --enable ana.int.interval_set --set exp.unrolling-factor 5 --set ana.base.arrays.domain unroll --set ana.base.arrays.unrolling-factor 5 +// Simple example +#include + +void main(void) +{ + int a[5]; + int i = 0; + + while (i < 5) { + a[i] = i; + i++; + } + + __goblint_check(a[0] == 0); + __goblint_check(a[3] == 3); +} diff --git a/tests/regression/66-interval-set-one/17-hybrid.c b/tests/regression/66-interval-set-one/17-hybrid.c new file mode 100644 index 0000000000..eaad9c41c1 --- /dev/null +++ b/tests/regression/66-interval-set-one/17-hybrid.c @@ -0,0 +1,20 @@ +// PARAM: --enable ana.int.interval_set --set solver td3 --enable solvers.td3.restart.wpoint.enabled --disable solvers.td3.restart.wpoint.once --set sem.int.signed_overflow assume_none +// ALSO: --enable ana.int.interval_set --set solver sl4 --set sem.int.signed_overflow assume_none +// Example from Amato-Scozzari, SAS 2013, based on Halbwachs-Henry, SAS 2012. +// Localized narrowing with restart policy should be able to prove that +// 0 <= i <= 10 inside the inner loop. +#include + +void main() +{ + int i = 0; + while (1) { + i++; + for (int j=0; j < 10; j++) { + __goblint_check(0 <= i); + __goblint_check(i <= 10); + } + if (i>9) i=0; + } + return; +} diff --git a/tests/regression/66-interval-set-one/19-simple_array.c b/tests/regression/66-interval-set-one/19-simple_array.c new file mode 100644 index 0000000000..d5e1ae785b --- /dev/null +++ b/tests/regression/66-interval-set-one/19-simple_array.c @@ -0,0 +1,188 @@ +// SKIP PARAM: --enable ana.int.interval_set --set ana.base.arrays.domain partitioned --set ana.base.partition-arrays.keep-expr "last" --enable annotation.int.enabled --set ana.int.refinement fixpoint +// skipped because https://github.com/goblint/analyzer/issues/468 +#include + +int global; + +int main(void) __attribute__((goblint_precision("no-interval"))); +void example1(void) __attribute__((goblint_precision("no-def_exc"))); +void example2(void) __attribute__((goblint_precision("no-def_exc"))); +void example3(void) __attribute__((goblint_precision("no-def_exc"))); +void example4(void) __attribute__((goblint_precision("no-def_exc"))); +void example5(void) __attribute__((goblint_precision("no-def_exc"))); +void example6(void) __attribute__((goblint_precision("no-def_exc"))); +void example7(void) __attribute__((goblint_precision("no-def_exc"))); +void example8() __attribute__((goblint_precision("no-def_exc"))); + +int main(void) +{ + example1(); + example2(); + example3(); + example4(); + example5(); + example6(); + example7(); + example8(); + return 0; +} + +// Simple example +void example1(void) +{ + int a[42]; + int i = 0; + int top; + + while (i < 42) { + a[i] = 0; + __goblint_check(a[i] == 0); + __goblint_check(a[0] == 0); + __goblint_check(a[17] == 0); // UNKNOWN + i++; + } + + __goblint_check(a[0] == 0); + __goblint_check(a[7] == 0); + __goblint_check(a[41] == 0); +} + +// More complicated expression to index rather than just a variable +void example2(void) { + int a[42]; + int i = 1; + + while (i < 43) { + a[i - 1] = 0; + __goblint_check(a[i - 1] == 0); + __goblint_check(a[38] == 0); // UNKNOWN + i++; + } + + __goblint_check(a[0] == 0); + __goblint_check(a[7] == 0); + __goblint_check(a[41] == 0); +} + +// Two values initialized in one loop +void example3(void) { + int a[42]; + int i = 0; + + while (i < 42) { + a[i] = 0; + i++; + a[i] = 1; + i++; + } + + __goblint_check(a[0] == 2); // FAIL + __goblint_check(a[41] == 0); // UNKNOWN + __goblint_check(a[41] == 1); // UNKNOWN + __goblint_check(a[41] == -1); // FAIL +} + +// Example where initialization proceeds backwards +void example4(void) { + int a[42]; + int i = 41; + + while(i >= 12) { + a[i] = 0; + i--; + } + + __goblint_check(a[i+2] == 0); + __goblint_check(a[41] == 0); + __goblint_check(a[i] == 0); //UNKNOWN + __goblint_check(a[0] == 0); //UNKNOWN +} + +// Example having two arrays partitioned according to one expression +void example5(void) { + int a[42]; + int b[42]; + int i = 0; + + while(i < 42) { + a[i] = 2; + b[41-i] = 0; + + __goblint_check(b[7] == 0); //UNKNOWN + __goblint_check(a[5] == 2); //UNKNOWN + i++; + } + + __goblint_check(a[0] == 2); + __goblint_check(a[41] == 2); + __goblint_check(b[0] == 0); + __goblint_check(b[41] == 0); +} + +// Example showing array becoming partitioned according to different expressions +void example6(void) { + int a[42]; + int i = 0; + int j = 0; + int top; + + while(i < 42) { + a[i] = 4; + i++; + } + + __goblint_check(a[17] == 4); + __goblint_check(a[9] == 4); + __goblint_check(a[3] == 4); + __goblint_check(a[i-1] == 4); + + while(j<10) { + a[j] = -1; + j++; + } + + __goblint_check(a[3] == -1); + __goblint_check(a[0] == -1); + __goblint_check(a[j-1] == -1); + __goblint_check(a[j] == 4); + __goblint_check(a[17] == 4); + __goblint_check(a[j+5] == 4); +} + +// This was the case where we thought we needed path-splitting +void example7(void) { + int a[42]; + int i = 0; + int top; + + if(top) { + while(i < 41) { + a[i] = 0; + __goblint_check(a[i] == 0); + i++; + } + } + + __goblint_check(a[0] == 0); // UNKNOWN + __goblint_check(a[7] == 0); // UNKNOWN + __goblint_check(a[41] == 0); // UNKNOWN + __goblint_check(a[top] == 0); // UNKNOWN +} + +// Check that the global variable is not used for partitioning +void example8() { + int a[10]; + + a[global] = 4; + __goblint_check(a[global] == 4); // UNKNOWN + + for(int i=0; i <5; i++) { + a[i] = 42; + } + + __goblint_check(a[0] == 42); + __goblint_check(a[1] == 42); + __goblint_check(a[2] == 42); + __goblint_check(a[3] == 42); + __goblint_check(a[global] == 42); +} diff --git a/tests/regression/66-interval-set-one/20-slr-glob_interval.c b/tests/regression/66-interval-set-one/20-slr-glob_interval.c new file mode 100644 index 0000000000..85c90c7dc2 --- /dev/null +++ b/tests/regression/66-interval-set-one/20-slr-glob_interval.c @@ -0,0 +1,37 @@ +// PARAM: --set ana.int.interval_set true --set solver new +// https://github.com/goblint/analyzer/pull/805#discussion_r933232518 +#include +#include + +int glob = 0; +pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER; + +void *t_fun(void *arg) { + pthread_mutex_lock(&mtx); + glob = 999; + pthread_mutex_unlock(&mtx); + return NULL; +} + +int main() { + int i = 3; + pthread_t id; + + __goblint_check(glob == 0); + + // Create the thread + pthread_create(&id, NULL, t_fun, NULL); + + // Simple assignments to only locals + __goblint_check(i == 3); + i = 9; + __goblint_check(i == 9); + + glob = 10; + + i = glob; + __goblint_check(i >= 0); + __goblint_check(i > 100); // UNKNOWN + + return 0; +} diff --git a/tests/regression/66-interval-set-one/21-strange-ulong.c b/tests/regression/66-interval-set-one/21-strange-ulong.c new file mode 100644 index 0000000000..31e561d3f7 --- /dev/null +++ b/tests/regression/66-interval-set-one/21-strange-ulong.c @@ -0,0 +1,88 @@ +// PARAM: --enable ana.int.interval_set --set ana.int.refinement once +#include + +int main(); + +int withint() { + int i = 0; + void* bla; + + while(i < 10000) { + i++; + bla = &main; + } + + __goblint_check(1); // reachable + return 0; +} + +int withuint() { + unsigned int i = 0; + void* bla; + + while(i < 10000) { + i++; + bla = &main; + } + + __goblint_check(1); // reachable + return 0; +} + +int withlong() { + long i = 0; + void* bla; + + while(i < 10000) { + i++; + bla = &main; + } + + __goblint_check(1); // reachable + return 0; +} + +int withlonglong() { + long long i = 0; + void* bla; + + while(i < 10000) { + i++; + bla = &main; + } + + __goblint_check(1); // reachable + return 0; +} + +int withulonglong() { + unsigned long long i = 0; + void* bla; + + while(i < 10000) { + i++; + bla = &main; + } + + __goblint_check(1); // reachable + return 0; +} + +int main() { + withint(); + withuint(); + withlong(); + withlonglong(); + withulonglong(); + + unsigned long i = 0; + void* bla; + + while(i < 10000) { + i++; + bla = &main; + } + + __goblint_check(1); // reachable + return 0; +} diff --git a/tests/regression/66-interval-set-one/22-calloc_globmt.c b/tests/regression/66-interval-set-one/22-calloc_globmt.c new file mode 100644 index 0000000000..a86b04dacd --- /dev/null +++ b/tests/regression/66-interval-set-one/22-calloc_globmt.c @@ -0,0 +1,32 @@ +// PARAM: --set ana.int.interval_set true --set ana.base.arrays.domain partitioned +#include +#include +#include + +int *x; +int *y; + +void *t_fun(void *arg) { + *x = 3; + return NULL; +} + +int main() { + pthread_t id; + + x = calloc(1, sizeof(int)); + y = calloc(1, sizeof(int)); + + *x = 0; + *y = 1; + + __goblint_check(*x == 0); + __goblint_check(*y == 1); // UNKNOWN + + pthread_create(&id, NULL, t_fun, NULL); + + __goblint_check(*x == 0); // UNKNOWN + __goblint_check(*y == 1); // UNKNOWN + + return 0; +} diff --git a/tests/regression/66-interval-set-one/23-publish-regression.c b/tests/regression/66-interval-set-one/23-publish-regression.c new file mode 100644 index 0000000000..914cbf21e0 --- /dev/null +++ b/tests/regression/66-interval-set-one/23-publish-regression.c @@ -0,0 +1,36 @@ +// PARAM: --set ana.int.interval_set true + +#include +#include + +int glob1 = 0; +pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER; +pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER; + +// The question is how to compute these S[g] sets? +// They are given in the paper. Should it be as large as possible? + +void *t_fun(void *arg) { + pthread_mutex_lock(&mutex1); + pthread_mutex_lock(&mutex2); + glob1 = 5; + pthread_mutex_unlock(&mutex2); + // But if s[g] = {mutex1,mutex2}, we publish here. + pthread_mutex_lock(&mutex2); + __goblint_check(glob1 == 5); + glob1 = 0; + pthread_mutex_unlock(&mutex1); + pthread_mutex_unlock(&mutex2); + return NULL; +} + +int main(void) { + pthread_t id; + __goblint_check(glob1 == 0); + pthread_create(&id, NULL, t_fun, NULL); + pthread_mutex_lock(&mutex1); + __goblint_check(glob1 == 0); + pthread_mutex_unlock(&mutex1); + pthread_join (id, NULL); + return 0; +} diff --git a/tests/regression/66-interval-set-one/25-simple_array.c b/tests/regression/66-interval-set-one/25-simple_array.c new file mode 100644 index 0000000000..85933d2310 --- /dev/null +++ b/tests/regression/66-interval-set-one/25-simple_array.c @@ -0,0 +1,209 @@ +// PARAM: --enable ana.int.interval_set --set ana.base.arrays.domain partitioned +#include + +int global; + +int main(void) +{ + example1(); + example2(); + example3(); + example4(); + example5(); + example6(); + example7(); + example8(); + example9(); + example10(); + return 0; +} + +// Simple example +void example1(void) +{ + int a[42]; + int i = 0; + int top; + + while (i < 42) { + a[i] = 0; + __goblint_check(a[i] == 0); + __goblint_check(a[0] == 0); + __goblint_check(a[17] == 0); // UNKNOWN + i++; + } + + __goblint_check(a[0] == 0); + __goblint_check(a[7] == 0); + __goblint_check(a[41] == 0); +} + +// More complicated expression to index rather than just a variable +void example2(void) { + int a[42]; + int i = 1; + + while (i < 43) { + a[i - 1] = 0; + __goblint_check(a[i - 1] == 0); + __goblint_check(a[38] == 0); // UNKNOWN + i++; + } + + __goblint_check(a[0] == 0); + __goblint_check(a[7] == 0); + __goblint_check(a[41] == 0); +} + +// Two values initialized in one loop +void example3(void) { + int a[42]; + int i = 0; + + while (i < 42) { + a[i] = 0; + i++; + a[i] = 1; + i++; + } + + __goblint_check(a[0] == 2); // FAIL + __goblint_check(a[41] == 0); // UNKNOWN + __goblint_check(a[41] == 1); // UNKNOWN + __goblint_check(a[41] == -1); // FAIL +} + +// Example where initialization proceeds backwards +void example4(void) { + int a[42]; + int i = 41; + + while(i >= 12) { + a[i] = 0; + i--; + } + + __goblint_check(a[i+2] == 0); + __goblint_check(a[41] == 0); + __goblint_check(a[i] == 0); //UNKNOWN + __goblint_check(a[0] == 0); //UNKNOWN +} + +// Example having two arrays partitioned according to one expression +void example5(void) { + int a[42]; + int b[42]; + int i = 0; + + while(i < 42) { + a[i] = 2; + b[41-i] = 0; + + __goblint_check(b[7] == 0); //UNKNOWN + __goblint_check(a[5] == 2); //UNKNOWN + i++; + } + + __goblint_check(a[0] == 2); + __goblint_check(a[41] == 2); + __goblint_check(b[0] == 0); + __goblint_check(b[41] == 0); +} + +// Example showing array becoming partitioned according to different expressions +void example6(void) { + int a[42]; + int i = 0; + int j = 0; + int top; + + while(i < 42) { + a[i] = 4; + i++; + } + + __goblint_check(a[17] == 4); + __goblint_check(a[9] == 4); + __goblint_check(a[3] == 4); + __goblint_check(a[i-1] == 4); + + while(j<10) { + a[j] = -1; + j++; + } + + __goblint_check(a[3] == -1); + __goblint_check(a[0] == -1); + __goblint_check(a[j-1] == -1); + __goblint_check(a[j] == 4); + __goblint_check(a[17] == 4); + __goblint_check(a[j+5] == 4); +} + +// This was the case where we thought we needed path-splitting +void example7(void) { + int a[42]; + int i = 0; + int top; + + if(top) { + while(i < 41) { + a[i] = 0; + __goblint_check(a[i] == 0); + i++; + } + } + + __goblint_check(a[0] == 0); // UNKNOWN + __goblint_check(a[7] == 0); // UNKNOWN + __goblint_check(a[41] == 0); // UNKNOWN + __goblint_check(a[top] == 0); // UNKNOWN +} + +// Check that the global variable is not used for partitioning +void example8() { + int a[10]; + + a[global] = 4; + __goblint_check(a[global] == 4); // UNKNOWN + + for(int i=0; i <5; i++) { + a[i] = 42; + } + + __goblint_check(a[0] == 42); + __goblint_check(a[1] == 42); + __goblint_check(a[2] == 42); + __goblint_check(a[3] == 42); + __goblint_check(a[global] == 42); +} + +// Check that arrays of types different from int are handeled correctly +void example9() { + // no char because char has unknown signedness (particularly, unsigned on arm64) + signed char a[10]; + int n; + __goblint_check(a[3] == 800); // FAIL + + for(int i=0;i < 10; i++) { + a[i] = 7; + } + + __goblint_check(a[0] == 7); + __goblint_check(a[3] == 7); + + a[3] = (signed char) n; + __goblint_check(a[3] == 800); //FAIL + __goblint_check(a[3] == 127); //UNKNOWN + __goblint_check(a[3] == -128); //UNKNOWN + __goblint_check(a[3] == -129); //FAIL +} + +void example10() { + int a[20]; + a[5] = 3; + + int i=5; + a[i] = 7; + __goblint_check(a[5] == 7); +} diff --git a/tests/regression/66-interval-set-one/26-float.c b/tests/regression/66-interval-set-one/26-float.c new file mode 100644 index 0000000000..d0c1cac8d7 --- /dev/null +++ b/tests/regression/66-interval-set-one/26-float.c @@ -0,0 +1,26 @@ +// PARAM: --enable ana.int.interval_set --enable ana.int.def_exc --enable ana.sv-comp.functions --set ana.activated[+] var_eq --set ana.activated[+] region +#include + +int isNan(float arg) { + float x; + return arg != arg; +} + +int main(){ + struct blub { float f; } s; + float fs[3]; + + float top; + // float may be NaN here, therefore the comaprison should be unknown + __goblint_check(top == top); //UNKNOWN! + __goblint_check(s.f == s.f); //UNKNOWN! + __goblint_check(fs[1] == fs[1]); //UNKNOWN! + + int r = isNan(top); + + if(r) { + __goblint_check(1); + } else { + __goblint_check(1); + } + } diff --git a/tests/regression/66-interval-set-one/27-calloc_int.c b/tests/regression/66-interval-set-one/27-calloc_int.c new file mode 100644 index 0000000000..826c770bc2 --- /dev/null +++ b/tests/regression/66-interval-set-one/27-calloc_int.c @@ -0,0 +1,19 @@ +// PARAM: --set ana.int.interval_set true --set ana.base.arrays.domain partitioned +#include +#include + +int main(void) { + int *r = calloc(1,sizeof(int)); + + r[0] = 0; + + __goblint_check(r[0] != 5); + __goblint_check(r[0] == 0); + + r[0] = 5; + + __goblint_check(r[0] == 5); //UNKNOWN + __goblint_check(r[0] != 0); //UNKNOWN + __goblint_check(r[0] != -10); + __goblint_check(r[0] != 100); +} diff --git a/tests/regression/66-interval-set-one/28-performance.c b/tests/regression/66-interval-set-one/28-performance.c new file mode 100644 index 0000000000..e1f6312421 --- /dev/null +++ b/tests/regression/66-interval-set-one/28-performance.c @@ -0,0 +1,11 @@ +// PARAM: --enable ana.int.interval_set --set ana.base.arrays.domain partitioned --enable exp.fast_global_inits +// Without fast_global_inits this takes >150s, when it is enabled < 0.1s +#include + +int global_array[50][500][20]; + +int main(void) { + for(int i =0; i < 50; i++) { + __goblint_check(global_array[i][42][7] == 0); + } +} diff --git a/tests/regression/66-interval-set-one/29-global_array.c b/tests/regression/66-interval-set-one/29-global_array.c new file mode 100644 index 0000000000..6c92f6915f --- /dev/null +++ b/tests/regression/66-interval-set-one/29-global_array.c @@ -0,0 +1,24 @@ +// PARAM: --enable ana.int.interval_set --set ana.base.arrays.domain partitioned +#include + +int global_array[50]; + +int main(void) { + some_func(); + + int x = global_array[5]; + __goblint_check(x == 0); //UNKNOWN + __goblint_check(x == 42); //UNKNOWN +} + + +void some_func(void) { + global_array[0] = 5; + + for(int i=1; i < 50; i++) { + global_array[i] = 42; + } + + int x = global_array[0]; + __goblint_check(x == 42); //FAIL +} diff --git a/tests/regression/66-interval-set-one/31-interval-arith.c b/tests/regression/66-interval-set-one/31-interval-arith.c new file mode 100644 index 0000000000..1a3a1dc14a --- /dev/null +++ b/tests/regression/66-interval-set-one/31-interval-arith.c @@ -0,0 +1,17 @@ +// PARAM: --enable ana.int.interval_set --disable ana.int.def_exc --disable ana.int.enums +#include +#include + +int main(){ + unsigned int i = 3; + + // 3 * 2^30 == 3221225472u is outside of the range that Intervall32 can represent + // Therefore, when trying to refine i, Base.invariant meets i -> [3;3] with i -> [(-2^31) / 2^30; ((2^31)-1) / 2^30] = [-2; 1] + // We thus get i -> Bottom, and the code after the condition is considered unreachable + if(i * 1073741824u == 3221225472u){ + printf("%u\n", i); + __goblint_check(i == 3); // SUCCESS + } + __goblint_check(i == 3); // SUCCESS + return 0; +} diff --git a/tests/regression/66-interval-set-one/33-was_problematic.c b/tests/regression/66-interval-set-one/33-was_problematic.c new file mode 100644 index 0000000000..f2c042ff40 --- /dev/null +++ b/tests/regression/66-interval-set-one/33-was_problematic.c @@ -0,0 +1,34 @@ +// PARAM: --enable ana.int.interval_set --set ana.base.arrays.domain partitioned +#include + +int main(int argc, char **argv) +{ + int unLo; + int sp = 1; + int nextD[3]; + + // nextD[0] = 2; // When we have this and the one in line 25 it is fine + + int x; + + while (sp > 0) + { + sp--; + + while (1) + { + if (x+1 <= 100 || x+1 > 100) + { + break; + } + } + + // If we have only this one there is a problem + nextD[0] = 2; + + int y = 27; + } + + __goblint_check(1 == 1); // Was reported as unreachable before + return 0; +} diff --git a/tests/regression/66-interval-set-one/34-calloc_glob.c b/tests/regression/66-interval-set-one/34-calloc_glob.c new file mode 100644 index 0000000000..4f8cb99efb --- /dev/null +++ b/tests/regression/66-interval-set-one/34-calloc_glob.c @@ -0,0 +1,27 @@ +// Made after 02 22 + +// PARAM: --set ana.int.interval_set true --set ana.base.arrays.domain partitioned + +#include +#include + +int *x; +int *y; + +int main() { + int *p; + x = calloc(1, sizeof(int)); + y = calloc(1, sizeof(int)); + + *x = 0; + *y = 1; + + __goblint_check(*x == 0); + __goblint_check(*y == 1); //UNKNOWN + + p = x; x = y; y = p; + __goblint_check(*x == 1); //UNKNOWN + __goblint_check(*y == 0); + + return 0; +} diff --git a/tests/regression/66-interval-set-one/36-one_by_one.c b/tests/regression/66-interval-set-one/36-one_by_one.c new file mode 100644 index 0000000000..78f3ec2fa0 --- /dev/null +++ b/tests/regression/66-interval-set-one/36-one_by_one.c @@ -0,0 +1,28 @@ +// PARAM: --enable ana.int.interval_set --set ana.base.arrays.domain partitioned +#include + +int main(void) { + int a[4]; + int b[4]; + + a[0] = 42; + a[1] = 42; + a[2] = 42; + a[3] = 42; + + __goblint_check(a[0] == 42); + __goblint_check(a[1] == 42); + __goblint_check(a[2] == 42); + __goblint_check(a[3] == 42); + + int *ptr = &b; + *ptr = 1; ptr++; + *ptr = 1; ptr++; + *ptr = 1; ptr++; + *ptr = 1; ptr++; + + __goblint_check(b[0] == 1); + __goblint_check(b[1] == 1); + __goblint_check(b[2] == 1); + __goblint_check(b[3] == 1); +} diff --git a/tests/regression/66-interval-set-one/37-on-attribute.c b/tests/regression/66-interval-set-one/37-on-attribute.c new file mode 100644 index 0000000000..300ed32561 --- /dev/null +++ b/tests/regression/66-interval-set-one/37-on-attribute.c @@ -0,0 +1,16 @@ +// PARAM: --enable ana.int.interval_set --disable ana.context.widen +#include + +int f(int x) __attribute__((goblint_context("widen"))); // attributes are not permitted in a function definition +int f(int x) { + if (x) + return f(x+1); + else + return x; +} + +int main () { + int a = f(1); + __goblint_check(!a); + return 0; +} diff --git a/tests/regression/66-interval-set-one/38-interval-congruence.c b/tests/regression/66-interval-set-one/38-interval-congruence.c new file mode 100644 index 0000000000..da8606dea1 --- /dev/null +++ b/tests/regression/66-interval-set-one/38-interval-congruence.c @@ -0,0 +1,34 @@ +// PARAM: --disable ana.int.def_exc --enable ana.int.interval_set --disable ana.int.congruence --set ana.int.refinement fixpoint +#include + +int main(){ + int r; + r=r; + + if (r) { + r = 2; + } else { + r = 7; + } + + // At this point r in the congr. dom should be 2 + 5Z + int k = r; + if (k >= 3) { + + // After refinement with congruences, the lower bound should be 7 as the numbers 3 - 6 are not in the congr. class + __goblint_check(k < 7); // FAIL + } + + int l; + if (l) { + l = 37; + } else { + l = 42; + } + + if (l <= 41) { + // Similarly to before, the upper bound should be 37 now. + __goblint_check(l > 37); // FAIL + } + return 0; +} diff --git a/tests/regression/66-interval-set-one/39-calls.c b/tests/regression/66-interval-set-one/39-calls.c new file mode 100644 index 0000000000..67ff46ad77 --- /dev/null +++ b/tests/regression/66-interval-set-one/39-calls.c @@ -0,0 +1,55 @@ +// PARAM: --enable ana.int.interval_set --disable ana.int.def_exc --set ana.base.arrays.domain partitioned +// Variable-sized arrays +void foo(int n, int a[n]); +void foo2(int n, int a[30][n]); +void foo3(int n, int a[n][30]); + +int main(void) +{ + int a[40]; + foo(40, a); + + int n = 30; + int b[n][n]; + b[29][0] = 0; + foo2(30, b); + foo3(30, b); +} + +int somefunction() { + return 42; +} + +//Two variable-sized arrays +//In CIL, a is changed to a pointer, and b is left alone +void foo(int n, int a[n]) { + + double b[n]; + a[n-1] = 0; + b[n-1] = 0.0; + printf("sizeof(a) = %d, sizeof(b) = %d\n", sizeof(a), sizeof(b)); + + + int m = 78; + char boom[n][somefunction()]; + char boom2[somefunction()][n]; + char boom3[somefunction()][somefunction()]; + char boom4[somefunction()][17][somefunction()][m]; + + //formals should be promoted to pointers (int*, in this case) + int* p = a; + p++; + if (sizeof(a) != sizeof(p)) E(2); + + //locals should keep their array type. CIL rewrites sizeof(b) + // as (n * sizeof(*b)) + if (sizeof(b) != (n * sizeof(double))) E(3); +} + +void foo2(int n, int a[30][n]) { + if(a[29][0] != 0) E(4); +} + +void foo3(int n, int a[n][30]) { + if(a[29][0] != 0) E(4); +} diff --git a/tests/regression/66-interval-set-one/40-priv_interval.c b/tests/regression/66-interval-set-one/40-priv_interval.c new file mode 100644 index 0000000000..047e04875a --- /dev/null +++ b/tests/regression/66-interval-set-one/40-priv_interval.c @@ -0,0 +1,32 @@ +// PARAM: --set ana.int.interval_set true +#include +#include + +int glob1 = 5; +pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER; +pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER; + +void *t_fun(void *arg) { + int t; + pthread_mutex_lock(&mutex1); + t = glob1; + __goblint_check(t == 5); + glob1 = -10; + __goblint_check(glob1 == -10); + glob1 = t; + pthread_mutex_unlock(&mutex1); + return NULL; +} + +int main(void) { + pthread_t id; + __goblint_check(glob1 == 5); + pthread_create(&id, NULL, t_fun, NULL); + pthread_mutex_lock(&mutex1); + glob1++; + __goblint_check(glob1 == 6); + glob1--; + pthread_mutex_unlock(&mutex1); + pthread_join (id, NULL); + return 0; +} diff --git a/tests/regression/66-interval-set-one/44-calloc_zero_init.c b/tests/regression/66-interval-set-one/44-calloc_zero_init.c new file mode 100644 index 0000000000..004387a7b4 --- /dev/null +++ b/tests/regression/66-interval-set-one/44-calloc_zero_init.c @@ -0,0 +1,13 @@ +// PARAM: --set ana.int.interval_set true --set ana.base.arrays.domain partitioned + +#include +#include + +int main(void) { + int *ro = calloc(2,sizeof(int)); + __goblint_check(ro[0] == 0); + __goblint_check(ro[1] == 0); + + ro[0] = 3; + __goblint_check(ro[1] != 3); //UNKNOWN +} diff --git a/tests/regression/66-interval-set-one/46-calloc_matrix.c b/tests/regression/66-interval-set-one/46-calloc_matrix.c new file mode 100644 index 0000000000..8b94b3116a --- /dev/null +++ b/tests/regression/66-interval-set-one/46-calloc_matrix.c @@ -0,0 +1,12 @@ +// PARAM: --set ana.int.interval_set true --set ana.base.arrays.domain partitioned + +#include +#include + +int main(void) { + int (*r)[5] = calloc(2, sizeof(int[5])); + r[0][1] = 3; + int* z = &r[0][1]; + + __goblint_check(*z == 3); //UNKNOWN +} diff --git a/tests/regression/66-interval-set-one/47-only-intervals.c b/tests/regression/66-interval-set-one/47-only-intervals.c new file mode 100644 index 0000000000..952aa95e89 --- /dev/null +++ b/tests/regression/66-interval-set-one/47-only-intervals.c @@ -0,0 +1,9 @@ +// PARAM: --enable ana.int.interval_set --disable ana.int.def_exc +#include + +int main() { + for(int i=2; i < 42; i++) { + int x = i==2; // NOWARN + __goblint_check(1); + } +} diff --git a/tests/regression/66-interval-set-one/48-tid-toy12.c b/tests/regression/66-interval-set-one/48-tid-toy12.c new file mode 100644 index 0000000000..4da779c541 --- /dev/null +++ b/tests/regression/66-interval-set-one/48-tid-toy12.c @@ -0,0 +1,80 @@ +// PARAM: --set ana.path_sens[+] threadflag --set ana.base.privatization mutex-meet-tid --enable ana.int.interval_set --set ana.activated[+] threadJoins +// Inspired by 36/82 +#include +#include + +int g = 10; +int h = 10; +pthread_mutex_t A = PTHREAD_MUTEX_INITIALIZER; + +pthread_t other_t; + +void *t_fun(void *arg) { + int x = 8; + int y; + + pthread_mutex_lock(&A); + g = x; + h = y; + pthread_mutex_unlock(&A); + + pthread_mutex_lock(&A); + g = x; + h = x; + pthread_mutex_unlock(&A); + return NULL; +} + +void *t_benign(void *arg) { + // Without this, it would even succeed without the must joined analysis. + // With it, that is required! + pthread_mutex_lock(&A); + g = 12; + h = 14; + pthread_mutex_unlock(&A); + + pthread_mutex_lock(&A); + pthread_create(&other_t, NULL, t_fun, NULL); + pthread_mutex_unlock(&A); + + pthread_mutex_lock(&A); + g = 10; + h = 10; + pthread_mutex_unlock(&A); + return NULL; +} + +int main(void) { + int t; + + pthread_mutex_lock(&A); + g = 12; + h = 14; + pthread_mutex_unlock(&A); + + // Force multi-threaded handling + pthread_t id2; + pthread_t id3; + pthread_create(&id2, NULL, t_benign, NULL); + pthread_create(&id3, NULL, t_benign, NULL); + + pthread_mutex_lock(&A); + __goblint_check(g == h); //UNKNOWN! + pthread_mutex_unlock(&A); + + pthread_join(id2, NULL); + + pthread_mutex_lock(&A); + __goblint_check(g == h); // UNKNOWN! + pthread_mutex_unlock(&A); + + pthread_mutex_lock(&A); + pthread_join(other_t, NULL); + pthread_mutex_unlock(&A); + + pthread_mutex_lock(&A); + __goblint_check(g == h); // UNKNOWN! + pthread_mutex_unlock(&A); + + return 0; +} diff --git a/tests/regression/66-interval-set-one/49-simple-cases-unrolled.c b/tests/regression/66-interval-set-one/49-simple-cases-unrolled.c new file mode 100644 index 0000000000..bd4b7c8d06 --- /dev/null +++ b/tests/regression/66-interval-set-one/49-simple-cases-unrolled.c @@ -0,0 +1,196 @@ +// PARAM: --enable ana.int.interval_set --set exp.unrolling-factor 5 --set ana.base.arrays.domain unroll --set ana.base.arrays.unrolling-factor 5 +#include + +int global; + +int main(void) +{ + example1(); + example2(); + example3(); + example4(); + example5(); + example6(); + example7(); + example8(); + example9(); + example10(); + return 0; +} + +// Simple example +void example1(void) +{ + int a[5]; + int i = 0; + + while (i < 5) { + a[i] = i; + i++; + } + + __goblint_check(a[0] == 0); + __goblint_check(a[3] == 3); +} + +// Do-while loop simple example +void example2(void) +{ + int a[5]; + int i = 0; + + do { + a[i] = i; + i++; + } while (i<=5); + + __goblint_check(a[0] == 0); + __goblint_check(a[3] == 3); +} + +// Initialization not completed, yet the array representation is not precise +void example3(void) +{ + int a[10]; + int i = 0; + + while (i < 5) { + a[i] = i; + i++; + } + + __goblint_check(a[0] == 0); + __goblint_check(a[3] == 0); // FAIL + __goblint_check(a[7] == 0); // UNKNOWN +} + +// Example with increased precision. Goblint detects in which iteration it is during the unrolled part. +void example4(void) +{ + int a[10]; + int i = 0; + int first_iteration = 1; + + while (i < 10) { + if (first_iteration == 1) __goblint_check(i==0); + else if (i > 5) __goblint_check(i == 6); // UNKNOWN + first_iteration = 0; + a[i] = 0; + i++; + } + + __goblint_check(a[0] == 0); + __goblint_check(first_iteration == 0); +} + + +// Example where the precision increase can be appreciated by a variable that +// is modified in the loop other than the ones used in the loop head +void example5(void) +{ + int a[4]; + int i = 0; + int top = 0; + + while (i < 4) { + a[i] = 0; + top += i; + if(i==2){ + __goblint_check(top == 3); + } + else{ + __goblint_check(top == 3); // FAIL + } + i++; + } + + __goblint_check(a[0] == 0); + __goblint_check(a[3] == 0); + __goblint_check(top == 6); +} + +// Loop has less iterations than the unrolling factor +void example6(void) +{ + int a[5]; + int i = 0; + int top = 0; + + while (i < 3) { + a[i] = 0; + __goblint_check(a[0]==0); + i++; + } + + __goblint_check(a[0] == 0); + __goblint_check(a[3] == 0); //UNKNOWN! + __goblint_check(top == 6); // FAIL +} + +// There is a call on the loop's condition +int update(int i) { + if (i>5){ + return 0; + } + else{ + return 1; + } +} +void example7(void) +{ + int a[10]; + int i = 0; + while(update(i)){ + a[i] = i; + ++i; + } + __goblint_check(a[0] == 0); //UNKNOWN + __goblint_check(a[6] == 0); //UNKNOWN +} + +// nested loops +void example8(void) +{ + int a[5]; + int b[] = {0,0,0,0,0}; + int i = 0; + while(i < 5){ + a[i] = i; + int j = 0; + while(j < 5){ + b[j] += a[i]; + ++j; + } + ++i; + } + return 0; +} + +// example with loop like the ones CIL does internally (while(1) + break) +void example9(void) +{ + int a[5]; + int i = 0; + while(1){ + a[i] = i; + ++i; + if (i == 5) break; + } + return 0; +} + +// example with loop containing a "continue" instruction +void example10(void) +{ + int a[5]; + int i = 0; + while(i<5){ + if (i == 3) { + i++; + continue; + } + a[i] = i; + ++i; + } + return 0; +} diff --git a/tests/regression/66-interval-set-one/50-interprocedural.c b/tests/regression/66-interval-set-one/50-interprocedural.c new file mode 100644 index 0000000000..a0d9b05597 --- /dev/null +++ b/tests/regression/66-interval-set-one/50-interprocedural.c @@ -0,0 +1,69 @@ +// PARAM: --enable ana.int.interval_set --set ana.base.arrays.domain partitioned +#include + +int main(void) { + example1(); + example2(); +} + +// ----------------------------------- Example 1 ------------------------------------------------------------------------------ +void example1() { + int a[20]; + int b[20]; + + init_array(a, 42); + + __goblint_check(a[2] == 42); + __goblint_check(a[10] == 42); + + do_first(a); + __goblint_check(a[0] == 3); + + init_array(b,12); + __goblint_check(b[2] == 12); + __goblint_check(b[10] == 12); +} + +void do_first(int* arr) { + int x = arr[0]; + arr[0] = 3; +} + +void init_array(int* arr, int val) { + for(int i = 0; i < 20; i++) { + arr[i] = val; + } + arr[0] = val; + + __goblint_check(arr[2] == val); + __goblint_check(arr[10] == val); +} + +// ----------------------------------- Example 2 ------------------------------------------------------------------------------ + +void example2(void) { + int arr[20]; + + for(int i = 0; i < 20; i++) + { + arr[i] = 42; + __goblint_check(arr[i] == 42); + callee(arr); + } + + __goblint_check(arr[0] == 100); //FAIL + __goblint_check(arr[0] == 7); //UNKNOWN + __goblint_check(arr[0] == 42); //UNKNOWN + + __goblint_check(arr[7] == 100); //FAIL + __goblint_check(arr[7] == 7); //UNKNOWN + __goblint_check(arr[7] == 42); //UNKNOWN + + __goblint_check(arr[20] == 100); //FAIL + __goblint_check(arr[20] == 7); //UNKNOWN + __goblint_check(arr[20] == 42); //UNKNOWN +} + +void callee(int* arr) { + arr[0] = 7; +} diff --git a/tests/regression/66-interval-set-one/51-widen-sides.c b/tests/regression/66-interval-set-one/51-widen-sides.c new file mode 100644 index 0000000000..b086baf026 --- /dev/null +++ b/tests/regression/66-interval-set-one/51-widen-sides.c @@ -0,0 +1,30 @@ +// PARAM: --set ana.ctx_insens "['base', 'mallocWrapper']" --enable ana.int.interval_set --sets solvers.td3.side_widen sides-local +#include + +int further(int n) { + // Even sides-local can not save us here :( + __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 <= 2); + further(n); +} + +void doIt(char* const arg) { + // These calls cause side-effects to the start state of [fun] + // As the calls happen after each other, we have two increasing contributions to [fun] from this unknown + // In the setting with solvers.td3.side_widen sides, [fun] thus becomes a wpoint + fun(0, arg); +} + + +int main() { + doIt("one"); + doIt("two"); + + // In the setting with solvers.td3.side_widen sides, widening happens and the bound is lost + fun(2, "org"); +} diff --git a/tests/regression/66-interval-set-one/53-simple_array.c b/tests/regression/66-interval-set-one/53-simple_array.c new file mode 100644 index 0000000000..79fa6a03dd --- /dev/null +++ b/tests/regression/66-interval-set-one/53-simple_array.c @@ -0,0 +1,222 @@ +// SKIP PARAM: --enable ana.int.interval_set --set ana.base.arrays.domain partitioned --enable annotation.int.enabled --set ana.int.refinement fixpoint +// skipped because https://github.com/goblint/analyzer/issues/468 +#include + +int global; + +int main(void) __attribute__((goblint_precision("no-interval"))); +void example1(void) __attribute__((goblint_precision("no-def_exc"))); +void example2(void) __attribute__((goblint_precision("no-def_exc"))); +void example3(void) __attribute__((goblint_precision("no-def_exc"))); +void example4(void) __attribute__((goblint_precision("no-def_exc"))); +void example5(void) __attribute__((goblint_precision("no-def_exc"))); +void example6(void) __attribute__((goblint_precision("no-def_exc"))); +void example7(void) __attribute__((goblint_precision("no-def_exc"))); +void example8() __attribute__((goblint_precision("no-def_exc"))); +void example9() __attribute__((goblint_precision("no-def_exc"))); +void example10() __attribute__((goblint_precision("no-def_exc"))); + + +int main(void) +{ + example1(); + example2(); + example3(); + example4(); + example5(); + example6(); + example7(); + example8(); + example9(); + example10(); + return 0; +} + +// Simple example +void example1(void) +{ + int a[42]; + int i = 0; + int top; + + while (i < 42) { + a[i] = 0; + __goblint_check(a[i] == 0); + __goblint_check(a[0] == 0); + __goblint_check(a[17] == 0); // UNKNOWN + i++; + } + + __goblint_check(a[0] == 0); + __goblint_check(a[7] == 0); + __goblint_check(a[41] == 0); +} + +// More complicated expression to index rather than just a variable +void example2(void) { + int a[42]; + int i = 1; + + while (i < 43) { + a[i - 1] = 0; + __goblint_check(a[i - 1] == 0); + __goblint_check(a[38] == 0); // UNKNOWN + i++; + } + + __goblint_check(a[0] == 0); + __goblint_check(a[7] == 0); + __goblint_check(a[41] == 0); +} + +// Two values initialized in one loop +void example3(void) { + int a[42]; + int i = 0; + + while (i < 42) { + a[i] = 0; + i++; + a[i] = 1; + i++; + } + + __goblint_check(a[0] == 2); // FAIL + __goblint_check(a[41] == 0); // UNKNOWN + __goblint_check(a[41] == 1); // UNKNOWN + __goblint_check(a[41] == -1); // FAIL +} + +// Example where initialization proceeds backwards +void example4(void) { + int a[42]; + int i = 41; + + while(i >= 12) { + a[i] = 0; + i--; + } + + __goblint_check(a[i+2] == 0); + __goblint_check(a[41] == 0); + __goblint_check(a[i] == 0); //UNKNOWN + __goblint_check(a[0] == 0); //UNKNOWN +} + +// Example having two arrays partitioned according to one expression +void example5(void) { + int a[42]; + int b[42]; + int i = 0; + + while(i < 42) { + a[i] = 2; + b[41-i] = 0; + + __goblint_check(b[7] == 0); //UNKNOWN + __goblint_check(a[5] == 2); //UNKNOWN + i++; + } + + __goblint_check(a[0] == 2); + __goblint_check(a[41] == 2); + __goblint_check(b[0] == 0); + __goblint_check(b[41] == 0); +} + +// Example showing array becoming partitioned according to different expressions +void example6(void) { + int a[42]; + int i = 0; + int j = 0; + int top; + + while(i < 42) { + a[i] = 4; + i++; + } + + __goblint_check(a[17] == 4); + __goblint_check(a[9] == 4); + __goblint_check(a[3] == 4); + __goblint_check(a[i-1] == 4); + + while(j<10) { + a[j] = -1; + j++; + } + + __goblint_check(a[3] == -1); + __goblint_check(a[0] == -1); + __goblint_check(a[j-1] == -1); + __goblint_check(a[j] == 4); + __goblint_check(a[17] == 4); + __goblint_check(a[j+5] == 4); +} + +// This was the case where we thought we needed path-splitting +void example7(void) { + int a[42]; + int i = 0; + int top; + + if(top) { + while(i < 41) { + a[i] = 0; + __goblint_check(a[i] == 0); + i++; + } + } + + __goblint_check(a[0] == 0); // UNKNOWN + __goblint_check(a[7] == 0); // UNKNOWN + __goblint_check(a[41] == 0); // UNKNOWN + __goblint_check(a[top] == 0); // UNKNOWN +} + +// Check that the global variable is not used for partitioning +void example8() { + int a[10]; + + a[global] = 4; + __goblint_check(a[global] == 4); // UNKNOWN + + for(int i=0; i <5; i++) { + a[i] = 42; + } + + __goblint_check(a[0] == 42); + __goblint_check(a[1] == 42); + __goblint_check(a[2] == 42); + __goblint_check(a[3] == 42); + __goblint_check(a[global] == 42); +} + +// Check that arrays of types different from int are handeled correctly +void example9() { + char a[10]; + int n; + __goblint_check(a[3] == 800); // FAIL + + for(int i=0;i < 10; i++) { + a[i] = 7; + } + + __goblint_check(a[0] == 7); + __goblint_check(a[3] == 7); + + a[3] = (char) n; + __goblint_check(a[3] == 800); //FAIL + __goblint_check(a[3] == 127); //UNKNOWN + __goblint_check(a[3] == -128); //UNKNOWN + __goblint_check(a[3] == -129); //FAIL +} + +void example10() { + int a[20]; + a[5] = 3; + + int i=5; + a[i] = 7; + __goblint_check(a[5] == 7); +} diff --git a/tests/regression/66-interval-set-one/54-interval-and-enums.c b/tests/regression/66-interval-set-one/54-interval-and-enums.c new file mode 100644 index 0000000000..2ec4687309 --- /dev/null +++ b/tests/regression/66-interval-set-one/54-interval-and-enums.c @@ -0,0 +1,74 @@ +// PARAM: --enable ana.int.interval_set --enable ana.int.def_exc --enable ana.int.enums +#include +#include + + +int main () { + int a = 1,b = 2,c = 3; + int x,y,z; + int w; + int false = 0; + int true = 42; + + if (x){ + __goblint_check(x != 0); + } else { + __goblint_check(x == 0); + } + + __goblint_check(!! true); + __goblint_check(! false); + + if (a){ + a = a; + } else + __goblint_check(0); // NOWARN + + + if (!a) + __goblint_check(0); // NOWARN + else + a = a; + + if (z != 0){ + a = 8; + b = 9; + } else { + a = 9; + b = 8; + } + + __goblint_check(a); + __goblint_check(a!=b); //UNKNOWN + __goblint_check(a<10); + __goblint_check(a<=9); + __goblint_check(!(a<8)); + __goblint_check(a==8); //UNKNOWN + __goblint_check(b>7); + __goblint_check(b>=8); + __goblint_check(!(a>9)); + __goblint_check(b==8); //UNKNOWN + + for(x = 0; x < 10; x++){ + __goblint_check(x >= 0); + // Because the false branch remained unreachable for more iterations, the analysis behaved differently, meaning + // with ana.int.enums enabled, we didn't know (x >= 0) here + __goblint_check(x <= 9); + } + __goblint_check(x == 10); + + if (0 <= w) + { + } + else + { + return 0; + } + + if (w > 0) + { + __goblint_check(1); + } + + return 0; +} diff --git a/tests/regression/66-interval-set-one/55-advantage_for_last.c b/tests/regression/66-interval-set-one/55-advantage_for_last.c new file mode 100644 index 0000000000..e544577fee --- /dev/null +++ b/tests/regression/66-interval-set-one/55-advantage_for_last.c @@ -0,0 +1,20 @@ +// PARAM: --enable ana.int.interval_set --set ana.base.partition-arrays.keep-expr last --set ana.base.arrays.domain partitioned +#include + +void main(void) { + example1(); +} + +void example1(void) { + int a[42]; + a[40] = 2; + int i = 0; + + while(i < 41) { + a[i] = 0; + i++; + } + + __goblint_check(a[2] == 0); + __goblint_check(a[3] == 0); +} diff --git a/tests/regression/66-interval-set-one/56-modulo-interval.c b/tests/regression/66-interval-set-one/56-modulo-interval.c new file mode 100644 index 0000000000..c561892604 --- /dev/null +++ b/tests/regression/66-interval-set-one/56-modulo-interval.c @@ -0,0 +1,21 @@ +// PARAM: --disable ana.int.def_exc --enable ana.int.interval_set +#include +int main() { + int x = -1; + int m = x % 5; + int r = x /5; + __goblint_check(m == -1); + __goblint_check(r == 0); + + x = 1; + m = x%-5; + r = x/-5; + __goblint_check(m == 1); + __goblint_check(r ==0); + + x = -1; + m = x%-5; + r = x/-5; + __goblint_check(m == -1); + __goblint_check(r == 0); +} diff --git a/tests/regression/66-interval-set-one/57-passing_ptr_to_array.c b/tests/regression/66-interval-set-one/57-passing_ptr_to_array.c new file mode 100644 index 0000000000..a84ab962d0 --- /dev/null +++ b/tests/regression/66-interval-set-one/57-passing_ptr_to_array.c @@ -0,0 +1,72 @@ +//PARAM: --enable ana.int.interval_set --disable ana.int.def_exc --set ana.base.arrays.domain partitioned +#include + +void foo(int (*a)[40]){ + int x = (*(a + 29))[7]; + __goblint_check(x == 23); //FAIL + + int y = (*(a + 7))[13]; + __goblint_check(y == 23); + + __goblint_check(a[7][13] == 23); +} + +void foo2(int n,int (*a)[n]){ + int x = (*(a + 29))[7]; + __goblint_check(x == 23); //FAIL + + int y = (*(a + 7))[13]; + __goblint_check(y == 23); + + __goblint_check(a[7][13] == 23); +} + +void foo3(int n,int a[][n]){ + int x = (*(a + 29))[7]; + __goblint_check(x == 23); //FAIL + + int y = (*(a + 7))[13]; + __goblint_check(y == 23); + + __goblint_check(a[7][13] == 23); +} + +void foo4(int n,int a[n][n]){ + int x = (*(a + 29))[7]; + __goblint_check(x == 23); //FAIL + + int y = (*(a + 7))[13]; + __goblint_check(y == 23); + + __goblint_check(a[7][13] == 23); +} + +void foo5(int n, int m, int a[n][m]){ + int x = (*(a + 29))[7]; + __goblint_check(x == 23); //FAIL + + int y = (*(a + 7))[13]; + __goblint_check(y == 23); + + __goblint_check(a[7][13] == 23); +} + +int main(void) +{ + int n =40; + int b[n][n]; + + for(int i=0;i < 40; i++) { + for(int j=0; j<40;j++) { + b[i][j] = 0; + } + } + + b[7][13] = 23; + + foo(b); + foo2(40,b); + foo3(40,b); + foo4(40,b); + foo5(40,40,b); +} diff --git a/tests/regression/66-interval-set-one/59-replace_with_const.c b/tests/regression/66-interval-set-one/59-replace_with_const.c new file mode 100644 index 0000000000..2233920d49 --- /dev/null +++ b/tests/regression/66-interval-set-one/59-replace_with_const.c @@ -0,0 +1,38 @@ +// PARAM: --enable ana.int.interval_set --set ana.base.arrays.domain partitioned --set ana.base.partition-arrays.keep-expr "last" --enable ana.base.partition-arrays.partition-by-const-on-return +#include + +int main(void) { + example1(); +} + +// ----------------------------------- Example 1 ------------------------------------------------------------------------------ +void example1() { + int a[20]; + int b[20]; + + init_array(a, 42); + + __goblint_check(a[2] == 42); + __goblint_check(a[10] == 42); + + do_first(a); + __goblint_check(a[0] == 3); + + init_array(b,12); + __goblint_check(b[2] == 12); + __goblint_check(b[10] == 12); +} + +void do_first(int* arr) { + int x = arr[0]; + arr[0] = 3; +} + +void init_array(int* arr, int val) { + for(int i = 0; i < 15; i++) { + arr[i] = val; + } + + __goblint_check(arr[2] == val); + __goblint_check(arr[10] == val); +} diff --git a/tests/regression/66-interval-set-one/60-intervals-test.c b/tests/regression/66-interval-set-one/60-intervals-test.c new file mode 100644 index 0000000000..37067b03f7 --- /dev/null +++ b/tests/regression/66-interval-set-one/60-intervals-test.c @@ -0,0 +1,14 @@ +// PARAM: --enable ana.int.interval_set --disable ana.int.def_exc --disable ana.int.enums +#include + +void main(){ + int n = 7; + for (; n; n--) { + __goblint_check(n==1); // UNKNOWN! + } + int i; + if(i-1){ + __goblint_check(i==2); // UNKNOWN! + } + return; +} diff --git a/tests/regression/66-interval-set-one/61-arithm.c b/tests/regression/66-interval-set-one/61-arithm.c new file mode 100644 index 0000000000..90b9c82bb3 --- /dev/null +++ b/tests/regression/66-interval-set-one/61-arithm.c @@ -0,0 +1,18 @@ +// PARAM: --enable ana.int.def_exc --disable ana.int.interval_set +#include +#include +// 2 ^ 30 +#define MULT 1073741824 + +int main(){ + unsigned int top; + unsigned int result; + // top = 7; + if(top != 3){ + result = top * MULT; + // if top == 7 then we have (2 + 1) * 2^30 == (4 + 2 + 1) * 2^30 (mod 2^32) + __goblint_check(result != 3221225472); // UNKNOWN! + printf("%u\n", result); + } + return result; +} diff --git a/tests/regression/66-interval-set-one/62-pfscan_widen_dependent_minimal.c b/tests/regression/66-interval-set-one/62-pfscan_widen_dependent_minimal.c new file mode 100644 index 0000000000..792be97752 --- /dev/null +++ b/tests/regression/66-interval-set-one/62-pfscan_widen_dependent_minimal.c @@ -0,0 +1,80 @@ +// PARAM: --enable ana.int.interval_set --enable exp.priv-distr-init +extern int __VERIFIER_nondet_int(); + +#include +#include + +// protection priv succeeds +// write fails due to [1,1] widen [0,1] -> [-inf,1] +// sensitive to eval and widen order! + +struct __anonstruct_PQUEUE_63 { + int qsize ; + int occupied ; + pthread_mutex_t mtx ; +}; +typedef struct __anonstruct_PQUEUE_63 PQUEUE; + +PQUEUE pqb ; + +int pqueue_init(PQUEUE *qp , int qsize ) +{ + qp->qsize = qsize; + qp->occupied = 0; + pthread_mutex_init(& qp->mtx, NULL); + return (0); +} + +int pqueue_put(PQUEUE *qp) +{ + pthread_mutex_lock(& qp->mtx); + while (qp->occupied >= qp->qsize) { + + } + __goblint_check(qp->occupied >= 0); // precise privatization fails + (qp->occupied) ++; + pthread_mutex_unlock(& qp->mtx); + return (1); +} + +int pqueue_get(PQUEUE *qp) +{ + int got = 0; + pthread_mutex_lock(& qp->mtx); + while (qp->occupied <= 0) { + + } + __goblint_check(qp->occupied > 0); // precise privatization fails + if (qp->occupied > 0) { + (qp->occupied) --; + got = 1; + pthread_mutex_unlock(& qp->mtx); + } else { + pthread_mutex_unlock(& qp->mtx); + } + return (got); +} + + +void *worker(void *arg ) +{ + while (1) { + pqueue_get(& pqb); + } + return NULL; +} + +int main(int argc , char **argv ) +{ + pthread_t tid; + int qsize = __VERIFIER_nondet_int(); + + PQUEUE *qp = &pqb; + pqueue_init(& pqb, qsize); + pthread_create(& tid, NULL, & worker, NULL); + + for (int i = 1; i < argc; i++) { + pqueue_put(& pqb); + } + return 0; +} diff --git a/tests/regression/66-interval-set-one/64-loc.c b/tests/regression/66-interval-set-one/64-loc.c new file mode 100644 index 0000000000..9c4a628f41 --- /dev/null +++ b/tests/regression/66-interval-set-one/64-loc.c @@ -0,0 +1,71 @@ +// PARAM: --set ana.path_sens[+] threadflag --set ana.base.privatization mutex-meet-tid --enable ana.int.interval_set --set ana.activated[+] threadJoins --enable ana.thread.include-node +// Inspired by 36/98 +#include +#include + +int g = 10; +int h = 10; +pthread_mutex_t A = PTHREAD_MUTEX_INITIALIZER; + +void *t_benign(void *arg) { + pthread_mutex_lock(&A); + g = 20; + pthread_mutex_unlock(&A); + return NULL; +} + +void *u_benign(void *arg) { + pthread_mutex_lock(&A); + h = 20; + pthread_mutex_unlock(&A); + return NULL; +} + +int main(void) { + int t; + + pthread_t id; + + if(t) { + pthread_create(&id, NULL, t_benign, NULL); + } else { + pthread_create(&id, NULL, t_benign, NULL); + } + + // As these two threads are distinguished, we have a non-unique TID for id + pthread_join(id, NULL); + + + pthread_mutex_lock(&A); + g = 12; + pthread_mutex_unlock(&A); + + pthread_mutex_lock(&A); + __goblint_check(g == 12); //TODO + pthread_mutex_unlock(&A); + +// --------------------------------------------------------------------------- + + pthread_t id2; + pthread_t id3; + + + pthread_create(&id2, NULL, u_benign, NULL); + pthread_create(&id3, NULL, u_benign, NULL); + + pthread_join(id2, NULL); + + // As these two threads are distinguished, id3 is a unique thread + pthread_join(id3, NULL); + + + pthread_mutex_lock(&A); + h = 12; + pthread_mutex_unlock(&A); + + pthread_mutex_lock(&A); + __goblint_check(h == 12); + pthread_mutex_unlock(&A); + + return 0; +} diff --git a/tests/regression/66-interval-set-one/65-multidimensional-array-oob-access.c b/tests/regression/66-interval-set-one/65-multidimensional-array-oob-access.c new file mode 100644 index 0000000000..54f81c15e6 --- /dev/null +++ b/tests/regression/66-interval-set-one/65-multidimensional-array-oob-access.c @@ -0,0 +1,20 @@ +// PARAM: --enable ana.arrayoob --enable ana.int.interval_set --enable ana.int.enums +//Multidimensional array: Out of bounds access +#include +int main( ) +{ + int arr[4][3] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; + arr[3][2] = 3; //NOWARN + arr[6][3] = 10; //WARN + arr[3][6] = 10; //WARN + arr[0][1] = 4; //NOWARN + arr[-6][3] = 10; //WARN + arr[3][-3] = 10; //WARN + + for (int i = 0; i < 10; ++i) + { + arr[i][i] = 5; //WARN + } + return 0; +} + diff --git a/tests/regression/66-interval-set-one/66-large-n-div2.c b/tests/regression/66-interval-set-one/66-large-n-div2.c new file mode 100644 index 0000000000..759b98ce5e --- /dev/null +++ b/tests/regression/66-interval-set-one/66-large-n-div2.c @@ -0,0 +1,23 @@ +//PARAM: --enable ana.int.interval_set --enable ana.int.def_exc +#include + +int main(){ + int top; + // 2^33 + long long x = 8589934592l; + // 2^31 - 1 + long long y = 2147483647; + + if(top) { + x = x - 1; + } + + long long z = x/y; + + if(z == 4){ + // Should be reachable + __goblint_check(1); + } + + __goblint_check(z == 4); +} diff --git a/tests/regression/66-interval-set-one/69-even_more_passing.c b/tests/regression/66-interval-set-one/69-even_more_passing.c new file mode 100644 index 0000000000..5cac0d8d86 --- /dev/null +++ b/tests/regression/66-interval-set-one/69-even_more_passing.c @@ -0,0 +1,42 @@ +//PARAM: --enable ana.int.interval_set --disable ana.int.def_exc --set ana.base.arrays.domain partitioned +#include + +void foo2(int n , int (*a)[n] ) +{ + int x ; + int y ; + + int *ptr = *(a+7); + __goblint_check(ptr[13] == 23); + + x = (*(a + 29))[7]; + __goblint_check(x == 23); //FAIL + + y = (*(a + 7))[13]; + __goblint_check(y == 23); + + return; +} + +int main(void) +{ + int r = 40; + int c[40][40]; + int d[r][r]; + + for(int i = 0; i < 40;i++) { + for(int j=0;j < 40;j++) { + c[i][j] = 0; + d[i][j] = 0; + } + } + + c[7][13] = 23; + d[7][13] = 23; + + + foo2(40, c); + foo2(40, d); + + return (0); +} diff --git a/tests/regression/66-interval-set-one/70-simple-cases.c b/tests/regression/66-interval-set-one/70-simple-cases.c new file mode 100644 index 0000000000..9007e56f5b --- /dev/null +++ b/tests/regression/66-interval-set-one/70-simple-cases.c @@ -0,0 +1,196 @@ +// PARAM: --enable ana.int.interval_set --set exp.unrolling-factor 5 +#include + +int global; + +int main(void) +{ + example1(); + example2(); + example3(); + example4(); + example5(); + example6(); + example7(); + example8(); + example9(); + example10(); + return 0; +} + +// Simple example +void example1(void) +{ + int a[5]; + int i = 0; + + while (i < 5) { + a[i] = i; + i++; + } + + __goblint_check(a[0] == 0); // UNKNOWN + __goblint_check(a[3] == 3); // UNKNOWN +} + +// Do-while loop simple example +void example2(void) +{ + int a[5]; + int i = 0; + + do { + a[i] = i; + i++; + } while (i<=5); + + __goblint_check(a[0] == 0); // UNKNOWN + __goblint_check(a[3] == 3); // UNKNOWN +} + +// Initialization not completed, yet the array representation is not precise +void example3(void) +{ + int a[10]; + int i = 0; + + while (i < 5) { + a[i] = i; + i++; + } + + __goblint_check(a[0] == 0); // UNKNOWN + __goblint_check(a[3] == 0); // UNKNOWN + __goblint_check(a[7] == 0); // UNKNOWN +} + +// Example with increased precision. Goblint detects in which iteration it is during the unrolled part. +void example4(void) +{ + int a[10]; + int i = 0; + int first_iteration = 1; + + while (i < 10) { + if (first_iteration == 1) __goblint_check(i==0); + else if (i > 5) __goblint_check(i == 6); // UNKNOWN + first_iteration = 0; + a[i] = 0; + i++; + } + + __goblint_check(a[0] == 0); + __goblint_check(first_iteration == 0); +} + + +// Example where the precision increase can be appreciated by a variable that +// is modified in the loop other than the ones used in the loop head +void example5(void) +{ + int a[4]; + int i = 0; + int top = 0; + + while (i < 4) { + a[i] = 0; + top += i; + if(i==2){ + __goblint_check(top == 3); + } + else{ + __goblint_check(top == 3); // FAIL + } + i++; + } + + __goblint_check(a[0] == 0); + __goblint_check(a[3] == 0); + __goblint_check(top == 6); +} + +// Loop has less iterations than the unrolling factor +void example6(void) +{ + int a[5]; + int i = 0; + int top = 0; + + while (i < 3) { + a[i] = 0; + __goblint_check(a[0]==0); + i++; + } + + __goblint_check(a[0] == 0); + __goblint_check(a[3] == 0); + __goblint_check(top == 6); // FAIL +} + +// There is a call on the loop's condition +int update(int i) { + if (i>5){ + return 0; + } + else{ + return 1; + } +} +void example7(void) +{ + int a[10]; + int i = 0; + while(update(i)){ + a[i] = i; + ++i; + } + __goblint_check(a[0] == 0); //UNKNOWN + __goblint_check(a[6] == 0); //UNKNOWN +} + +// nested loops +void example8(void) +{ + int a[5]; + int b[] = {0,0,0,0,0}; + int i = 0; + while(i < 5){ + a[i] = i; + int j = 0; + while(j < 5){ + b[j] += a[i]; + ++j; + } + ++i; + } + return 0; +} + +// example with loop like the ones CIL does internally (while(1) + break) +void example9(void) +{ + int a[5]; + int i = 0; + while(1){ + a[i] = i; + ++i; + if (i == 5) break; + } + return 0; +} + +// example with loop containing a "continue" instruction +void example10(void) +{ + int a[5]; + int i = 0; + while(i<5){ + if (i == 3) { + i++; + continue; + } + a[i] = i; + ++i; + } + return 0; +} \ No newline at end of file diff --git a/tests/regression/66-interval-set-one/71-int-context-option.c b/tests/regression/66-interval-set-one/71-int-context-option.c new file mode 100644 index 0000000000..5f31a25a8a --- /dev/null +++ b/tests/regression/66-interval-set-one/71-int-context-option.c @@ -0,0 +1,15 @@ +// PARAM: --enable ana.int.interval_set --disable ana.context.widen --disable ana.base.context.int --set annotation.goblint_context.f[+] base.int +#include + +int f(int x) { + if (x) + return x * f(x - 1); + else + return 1; +} + +int main () { + int a = f(10); + __goblint_check(a == 3628800); + return 0; +} diff --git a/tests/regression/66-interval-set-one/73-intervals.c b/tests/regression/66-interval-set-one/73-intervals.c new file mode 100644 index 0000000000..cfaf9ed3a0 --- /dev/null +++ b/tests/regression/66-interval-set-one/73-intervals.c @@ -0,0 +1,11 @@ +// PARAM: --set sem.int.signed_overflow assume_none --enable ana.int.interval_set --disable ana.int.def_exc +#include + +int main(void) { + int x = 0; + while(x != 42) { + x++; + __goblint_check(x >= 1); + } + +} diff --git a/tests/regression/66-interval-set-one/74-testfive-intervals-protection.c b/tests/regression/66-interval-set-one/74-testfive-intervals-protection.c new file mode 100644 index 0000000000..513707b871 --- /dev/null +++ b/tests/regression/66-interval-set-one/74-testfive-intervals-protection.c @@ -0,0 +1,34 @@ +// PARAM: --enable ana.int.interval_set --set ana.base.privatization protection --set solvers.td3.side_widen sides-pp +// also needs sides-pp now that protected and unprotected use different global constraint variables +#include +#include + +int myglobal; +pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; + +void lock() { + pthread_mutex_lock(&mutex); +} + +void unlock() { + pthread_mutex_unlock(&mutex); +} + + +void *t_fun(void *arg) { + lock(); + myglobal++; // NORACE + unlock(); + return NULL; +} + + +int main(void) { + pthread_t id; + pthread_create(&id, NULL, t_fun, NULL); + lock(); + myglobal++; // NORACE + unlock(); + pthread_join (id, NULL); + return 0; +} diff --git a/tests/regression/66-interval-set-one/75-22_02-pointers_array.c b/tests/regression/66-interval-set-one/75-22_02-pointers_array.c new file mode 100644 index 0000000000..e140dbbeb8 --- /dev/null +++ b/tests/regression/66-interval-set-one/75-22_02-pointers_array.c @@ -0,0 +1,283 @@ +// PARAM: --enable ana.int.interval_set --set ana.base.arrays.domain partitioned --enable annotation.int.enabled --set ana.int.refinement fixpoint +#include + +int main(void) __attribute__((goblint_precision("no-interval"))); +void example1(void) __attribute__((goblint_precision("no-def_exc"))); +void example2() __attribute__((goblint_precision("no-def_exc"))); +void example3(void) __attribute__((goblint_precision("no-def_exc"))); +void example4(void) __attribute__((goblint_precision("no-def_exc"))); +void example5(void) __attribute__((goblint_precision("no-def_exc"))); +void example6(void) __attribute__((goblint_precision("no-def_exc"))); +void example7(void) __attribute__((goblint_precision("no-def_exc"))); +void example8(void) __attribute__((goblint_precision("no-def_exc"))); + +struct a { + int x[42]; + int y; +}; + +void example9() __attribute__((goblint_precision("no-def_exc"))); +int example10() __attribute__((goblint_precision("no-def_exc"))); +void foo(int (*a)[40]) __attribute__((goblint_precision("no-def_exc"))); +void example11() __attribute__((goblint_precision("no-def_exc"))); + + +int main(void) { + example1(); + example2(); + example3(); + example4(); + example5(); + example6(); + example7(); + example8(); + example9(); + example10(); + example11(); + return 0; +} + +// Initializing an array with pointers +void example1(void) { + int top; + + int a[42]; + int *ptr = &a; + + *ptr = 42; + ptr++; + + __goblint_check(a[0] == 42); + __goblint_check(a[1] == 42); // UNKNOWN + + *ptr = 42; + __goblint_check(a[0] == 42); + __goblint_check(a[1] == 42); + ptr++; + + *ptr = 42; + ptr++; + *ptr = 42; + ptr++; + *ptr = 42; + ptr++; + *ptr = 42; + ptr++; + + + int i = 5; + __goblint_check(a[i] == 42); + + if(top) { + i++; + } + + __goblint_check(a[i] == 42); // UNKNOWN +} + +// Tests correct handling when pointers may point to several different things +void example2() { + int array1[10000000]; + int array2[10000000]; + + int* ptr; + + if(rand()) { + ptr = &array1; + *ptr = 5; + + __goblint_check(*ptr == 5); + } + else { + ptr = &array2; + *ptr = 5; + + __goblint_check(*ptr == 5); + } + + // Since ptr could point to different arrays, the update here can not be precise + *ptr = 6; + + __goblint_check(*ptr == 6); // UNKNOWN +} + +void example3(void) { + int array1[5]; + int *ptr = &array1; + + for(int i =0; i <5; i++) { + *ptr = 42; + __goblint_check(*ptr == 42); + ptr++; + } +} + +void example4(void) { + int array1[5]; + int *ptr = &array1; + int *end = &(array1[5]); + + while(ptr <= end) { + *ptr = 42; + __goblint_check(*ptr == 42); + ptr++; + } + + // In an ideal world, I would like to have information about array1[0] and so on. For this the <= would need to improve, so that ptr is known to point to {array1[5,5]} +} + +void example5(void) { + int array1[5]; + int *ptr = &(array1[4]); + + *ptr = 42; + ptr--; + *ptr = 42; + ptr--; + *ptr = 40; + + __goblint_check(*ptr == 40); + __goblint_check(array1[4] == 42); + __goblint_check(array1[3] == 42); + __goblint_check(array1[2] == 40); + __goblint_check(array1[0] == 42); // UNKNOWN +} + +void example6(void) { + int array1[100]; + int* ptr = &array1; + + *ptr = 5; + int v = *ptr; + __goblint_check(v == 5); + + ptr++; + *ptr = 6; + ptr++; + *ptr = 7; + + // This is necessary for the tests that we are doing later + int k = ptr-&array1; + __goblint_check(k == 2); + int m = ptr-array1; + __goblint_check(m == 2); + + int* np = &array1; + np++; + np++; + int x = *np; + __goblint_check(x==7); +} + +void example7(void) { + int top; + + int arr1[42]; + int arr2[42]; + int *ptr; + + for(int i = 0; i < 42; i++) { + arr1[i] = 4; + arr2[i] = 4; + } + + ptr = &arr1[7]; + + if(top) { + ptr = &arr2[7]; + } + + *ptr = 9; + + // Case ptr = &arr1[7] + // arr1 -> (ptr-arr1, ([4,4], [9,9],[4,4])) + // arr2 -> (-,[4,4]) + + // Case ptr = &arr2[7] + // arr1 -> (-, [4,4]) + // arr2 -> (ptr-arr2, ([4,4], [9,9],[4,4])) + + // LUB: + // arr1 -> (-, [4,9]) + // arr2 -> (-, [4,9]) + int x = arr1[7]; + __goblint_check(x == 3); // FAIL + __goblint_check(x == 4); // UNKNOWN + __goblint_check(x == 9); // UNKNOWN + __goblint_check(x == 10); // FAIL +} + +void example8(void) { + int a[42][42]; + + for(int i = 0; i < 42; i++) { + for(int j=0;j < 42; j++) { + a[i][j] = 0; + } + } + + a[14][0] = 3; + + int* ptr = a[7]; + int x = *(ptr+7); + __goblint_check(x == 3); //FAIL + + int (*ptr2)[42]; + ptr2 = a+7; + x = (*ptr2)[6]; + __goblint_check(x == 3); //FAIL + printf("x is %d\n", x); +} + +struct a { + int x[42]; + int y; +}; + +void example9() { + int a[42][42]; + int (*ptr2)[42]; + int *y; + int i, j, x; + + for(i = 0; i < 42; i++) { + for(j=0;j < 42; j++) { + a[i][j] = 0; + } + } + + a[14][0] = 3; + ptr2 = a+7; + y = (ptr2+1)[6]; + __goblint_check(*y == 3); +} + +int example10() { + struct a x[42]; + int i, j, y, *ptr; + + for(i = 0; i < 42; i++) { + for(j=0;j < 42; j++) { + x[i].x[j] = 0; + } + } + x[3].x[3] = 7; + + ptr = x[3].x; + y = *(ptr + 3); + __goblint_check(y == 0); //FAIL + printf("y is %d", y); +} + +void foo(int (*a)[40]) { + int x = (*(a + 29))[7]; + __goblint_check(x == 23); //UNKNOWN +} + +void example11() +{ + int b[40][40]; + b[7][7] = 23; + + foo(b); +} diff --git a/tests/regression/66-interval-set-one/76-calloc_loop.c b/tests/regression/66-interval-set-one/76-calloc_loop.c new file mode 100644 index 0000000000..4b0cfee477 --- /dev/null +++ b/tests/regression/66-interval-set-one/76-calloc_loop.c @@ -0,0 +1,19 @@ +// Made after 02 21 +// PARAM: --set ana.int.interval_set true --set ana.base.arrays.domain partitioned + +#include +#include + +int main() { + int* x[10]; + int i = 0; + + while (i < 10) + x[i++] = calloc(1, sizeof(int)); + + *x[3] = 50; + *x[7] = 100; + __goblint_check(*x[8] == 100); // UNKNOWN + + return 0; +} diff --git a/tests/regression/66-interval-set-one/77-more-problem.c b/tests/regression/66-interval-set-one/77-more-problem.c new file mode 100644 index 0000000000..726baeb999 --- /dev/null +++ b/tests/regression/66-interval-set-one/77-more-problem.c @@ -0,0 +1,18 @@ +//PARAM: --set ana.int.refinement once --enable ana.int.interval_set --enable ana.int.congruence --disable ana.int.def_exc +#include + +int main(void) +{ + int ret = 0; + unsigned int s__version; + if (s__version + 65280 != 768) + { + ret = 0; + } + else + { + ret = 1; + } + + __goblint_check(ret == 0); //UNKNOWN! +} diff --git a/tests/regression/66-interval-set-one/78-pointers_array.c b/tests/regression/66-interval-set-one/78-pointers_array.c new file mode 100644 index 0000000000..7bda3549b4 --- /dev/null +++ b/tests/regression/66-interval-set-one/78-pointers_array.c @@ -0,0 +1,262 @@ +// PARAM: --enable ana.int.interval_set --set ana.base.arrays.domain partitioned +#include + +int main(void) { + example1(); + example2(); + example3(); + example4(); + example5(); + example6(); + example7(); + example8(); + example9(); + example10(); + example11(); + return 0; +} + +// Initializing an array with pointers +void example1(void) { + int top; + + int a[42]; + int *ptr = &a; + + *ptr = 42; + ptr++; + + __goblint_check(a[0] == 42); + __goblint_check(a[1] == 42); // UNKNOWN + + *ptr = 42; + __goblint_check(a[0] == 42); + __goblint_check(a[1] == 42); + ptr++; + + *ptr = 42; + ptr++; + *ptr = 42; + ptr++; + *ptr = 42; + ptr++; + *ptr = 42; + ptr++; + + + int i = 5; + __goblint_check(a[i] == 42); + + if(top) { + i++; + } + + __goblint_check(a[i] == 42); // UNKNOWN +} + +// Tests correct handling when pointers may point to several different things +void example2() { + int array1[10000000]; + int array2[10000000]; + + int* ptr; + + if(rand()) { + ptr = &array1; + *ptr = 5; + + __goblint_check(*ptr == 5); + } + else { + ptr = &array2; + *ptr = 5; + + __goblint_check(*ptr == 5); + } + + // Since ptr could point to different arrays, the update here can not be precise + *ptr = 6; + + __goblint_check(*ptr == 6); // UNKNOWN +} + +void example3(void) { + int array1[5]; + int *ptr = &array1; + + for(int i =0; i <5; i++) { + *ptr = 42; + __goblint_check(*ptr == 42); + ptr++; + } +} + +void example4(void) { + int array1[5]; + int *ptr = &array1; + int *end = &(array1[5]); + + while(ptr <= end) { + *ptr = 42; + __goblint_check(*ptr == 42); + ptr++; + } + + // In an ideal world, I would like to have information about array1[0] and so on. For this the <= would need to improve, so that ptr is known to point to {array1[5,5]} +} + +void example5(void) { + int array1[5]; + int *ptr = &(array1[4]); + + *ptr = 42; + ptr--; + *ptr = 42; + ptr--; + *ptr = 40; + + __goblint_check(*ptr == 40); + __goblint_check(array1[4] == 42); + __goblint_check(array1[3] == 42); + __goblint_check(array1[2] == 40); + __goblint_check(array1[0] == 42); // UNKNOWN +} + +void example6(void) { + int array1[100]; + int* ptr = &array1; + + *ptr = 5; + int v = *ptr; + __goblint_check(v == 5); + + ptr++; + *ptr = 6; + ptr++; + *ptr = 7; + + // This is necessary for the tests that we are doing later + int k = ptr-&array1; + __goblint_check(k == 2); + int m = ptr-array1; + __goblint_check(m == 2); + + int* np = &array1; + np++; + np++; + int x = *np; + __goblint_check(x==7); +} + +void example7(void) { + int top; + + int arr1[42]; + int arr2[42]; + int *ptr; + + for(int i = 0; i < 42; i++) { + arr1[i] = 4; + arr2[i] = 4; + } + + ptr = &arr1[7]; + + if(top) { + ptr = &arr2[7]; + } + + *ptr = 9; + + // Case ptr = &arr1[7] + // arr1 -> (ptr-arr1, ([4,4], [9,9],[4,4])) + // arr2 -> (-,[4,4]) + + // Case ptr = &arr2[7] + // arr1 -> (-, [4,4]) + // arr2 -> (ptr-arr2, ([4,4], [9,9],[4,4])) + + // LUB: + // arr1 -> (-, [4,9]) + // arr2 -> (-, [4,9]) + int x = arr1[7]; + __goblint_check(x == 3); // FAIL + __goblint_check(x == 4); // UNKNOWN + __goblint_check(x == 9); // UNKNOWN + __goblint_check(x == 10); // FAIL +} + +void example8(void) { + int a[42][42]; + + for(int i = 0; i < 42; i++) { + for(int j=0;j < 42; j++) { + a[i][j] = 0; + } + } + + a[14][0] = 3; + + int* ptr = a[7]; + int x = *(ptr+7); + __goblint_check(x == 3); //FAIL + + int (*ptr2)[42]; + ptr2 = a+7; + x = (*ptr2)[6]; + __goblint_check(x == 3); //FAIL + printf("x is %d\n", x); +} + +struct a { + int x[42]; + int y; +}; + +void example9() { + int a[42][42]; + int (*ptr2)[42]; + int *y; + int i, j, x; + + for(i = 0; i < 42; i++) { + for(j=0;j < 42; j++) { + a[i][j] = 0; + } + } + + a[14][0] = 3; + ptr2 = a+7; + y = (ptr2+1)[6]; + __goblint_check(*y == 3); +} + +int example10() { + struct a x[42]; + int i, j, y, *ptr; + + for(i = 0; i < 42; i++) { + for(j=0;j < 42; j++) { + x[i].x[j] = 0; + } + } + x[3].x[3] = 7; + + ptr = x[3].x; + y = *(ptr + 3); + __goblint_check(y == 0); //FAIL + printf("y is %d", y); +} + +void foo(int (*a)[40]){ + int x = (*(a + 29))[7]; + __goblint_check(x == 23); //UNKNOWN +} + +void example11() +{ + int b[40][40]; + b[7][7] = 23; + + foo(b); +} diff --git a/tests/regression/66-interval-set-one/79-tid-toy13.c b/tests/regression/66-interval-set-one/79-tid-toy13.c new file mode 100644 index 0000000000..c52a14bbac --- /dev/null +++ b/tests/regression/66-interval-set-one/79-tid-toy13.c @@ -0,0 +1,80 @@ +// PARAM: --set ana.path_sens[+] threadflag --set ana.base.privatization mutex-meet-tid --enable ana.int.interval_set --set ana.activated[+] threadJoins +// Inspired by 36/83 +#include +#include + +int g = 10; +int h = 10; +pthread_mutex_t A = PTHREAD_MUTEX_INITIALIZER; + +pthread_t other_t; + +void *t_fun(void *arg) { + int x = 10; + int y; + + pthread_mutex_lock(&A); + g = x; + h = y; + pthread_mutex_unlock(&A); + + pthread_mutex_lock(&A); + g = x; + h = x; + pthread_mutex_unlock(&A); + return NULL; +} + +void *t_benign(void *arg) { + // Without this, it would even succeed without the must joined analysis. + // With it, that is required! + pthread_mutex_lock(&A); + g = 12; + h = 14; + pthread_mutex_unlock(&A); + + pthread_mutex_lock(&A); + pthread_create(&other_t, NULL, t_fun, NULL); + pthread_mutex_unlock(&A); + + pthread_mutex_lock(&A); + g = 10; + h = 10; + pthread_mutex_unlock(&A); + return NULL; +} + +int main(void) { + int t; + + pthread_mutex_lock(&A); + g = 12; + h = 14; + pthread_mutex_unlock(&A); + + // Force multi-threaded handling + pthread_t id2; + for(int i = 0; i < 10;i++) { + pthread_create(&id2, NULL, t_benign, NULL); + } + + pthread_mutex_lock(&A); + __goblint_check(g == h); //UNKNOWN! + pthread_mutex_unlock(&A); + + pthread_join(id2, NULL); + + pthread_mutex_lock(&A); + __goblint_check(g == h); // UNKNOWN! + pthread_mutex_unlock(&A); + + pthread_mutex_lock(&A); + pthread_join(other_t, NULL); + pthread_mutex_unlock(&A); + + pthread_mutex_lock(&A); + __goblint_check(g == h); // UNKNOWN! + pthread_mutex_unlock(&A); + + return 0; +} diff --git a/tests/regression/66-interval-set-one/80-lustre-minimal.c b/tests/regression/66-interval-set-one/80-lustre-minimal.c new file mode 100644 index 0000000000..abc33c3031 --- /dev/null +++ b/tests/regression/66-interval-set-one/80-lustre-minimal.c @@ -0,0 +1,11 @@ +// PARAM: --enable ana.int.interval_set --enable ana.int.def_exc +// issue #120 +#include + +int main() { + // should be LP64 + unsigned long n = 16; + unsigned long size = 4912; + + __goblint_check(!(0xffffffffffffffffUL / size < n)); +} diff --git a/tests/regression/66-interval-set-one/82-malloc_array.c b/tests/regression/66-interval-set-one/82-malloc_array.c new file mode 100644 index 0000000000..e64d539e59 --- /dev/null +++ b/tests/regression/66-interval-set-one/82-malloc_array.c @@ -0,0 +1,14 @@ +// PARAM: --set ana.int.interval_set true --set ana.base.arrays.domain partitioned +#include +#include + +int main(void) { + int *r = malloc(5 * sizeof(int)); + + r[3] = 2; + + __goblint_check(r[4] == 2); + /* Here we only test our implementation. Concretely, accessing the uninitialised r[4] is undefined behavior. + In our implementation we keep the whole memory allocated by malloc as one Blob and the whole Blob contains 2 after it was assigned to r[3]. + This is more useful than keeping the Blob unknown. */ +} diff --git a/tests/regression/66-interval-set-one/84-non-zero.c b/tests/regression/66-interval-set-one/84-non-zero.c new file mode 100644 index 0000000000..bf79c3279b --- /dev/null +++ b/tests/regression/66-interval-set-one/84-non-zero.c @@ -0,0 +1,29 @@ +// PARAM: --enable ana.int.interval_set --set ana.base.arrays.domain partitioned --enable exp.fast_global_inits +// This checks that partitioned arrays and fast_global_inits are no longer incompatible +#include + +int global_array[5] = {9, 0, 3, 42, 11}; +int global_array_multi[2][5] = {{9, 0, 3, 42, 11}, {9, 0, 3, 42, 11}}; + +int main(void) { + __goblint_check(global_array[0] == 9); //UNKNOWN + __goblint_check(global_array[1] == 0); //UNKNOWN + __goblint_check(global_array[2] == 3); //UNKNOWN + __goblint_check(global_array[3] == 42); //UNKNOWN + __goblint_check(global_array[4] == 11); //UNKNOWN + __goblint_check(global_array[1] == -1); //FAIL + + __goblint_check(global_array_multi[0][0] == 9); //UNKNOWN + __goblint_check(global_array_multi[0][1] == 0); //UNKNOWN + __goblint_check(global_array_multi[0][2] == 3); //UNKNOWN + __goblint_check(global_array_multi[0][3] == 42); //UNKNOWN + __goblint_check(global_array_multi[0][4] == 11); //UNKNOWN + __goblint_check(global_array_multi[0][1] == -1); //FAIL + + __goblint_check(global_array_multi[1][0] == 9); //UNKNOWN + __goblint_check(global_array_multi[1][1] == 0); //UNKNOWN + __goblint_check(global_array_multi[1][2] == 3); //UNKNOWN + __goblint_check(global_array_multi[1][3] == 42); //UNKNOWN + __goblint_check(global_array_multi[1][4] == 11); //UNKNOWN + __goblint_check(global_array_multi[1][1] == -1); //FAIL +} diff --git a/tests/regression/66-interval-set-one/85-cast-unsigned-to-signed.c b/tests/regression/66-interval-set-one/85-cast-unsigned-to-signed.c new file mode 100644 index 0000000000..e3fbefcc32 --- /dev/null +++ b/tests/regression/66-interval-set-one/85-cast-unsigned-to-signed.c @@ -0,0 +1,9 @@ +// PARAM: --enable ana.int.interval_set --set sem.int.signed_overflow assume_none +#include + +int main(void) { + unsigned long x; + long y = x; + __goblint_check(y >= 0); // UNKNOWN! + return 0; +} diff --git a/tests/regression/66-interval-set-one/86-large-n-div.c b/tests/regression/66-interval-set-one/86-large-n-div.c new file mode 100644 index 0000000000..2ad14368b2 --- /dev/null +++ b/tests/regression/66-interval-set-one/86-large-n-div.c @@ -0,0 +1,18 @@ +//PARAM: --enable ana.int.interval_set --disable ana.int.def_exc +#include + +int main(){ + // 2^33 + long long x = 8589934592l; + // 2^31 - 1 + long long y = 2147483647; + + long long z = x/y; + + if(z == 4){ + // Should be reachable + __goblint_check(1); + } + + __goblint_check(z == 4); +} diff --git a/tests/regression/66-interval-set-one/87-on.c b/tests/regression/66-interval-set-one/87-on.c new file mode 100644 index 0000000000..93e628bbf8 --- /dev/null +++ b/tests/regression/66-interval-set-one/87-on.c @@ -0,0 +1,13 @@ +// PARAM: --enable ana.int.interval_set --set ana.base.arrays.domain partitioned +// This checks that partitioned arrays and fast_global_inits are no longer incompatible +#include + +int global_array[50]; +int global_array_multi[50][2][2]; + +int main(void) { + for(int i =0; i < 50; i++) { + __goblint_check(global_array[i] == 0); + __goblint_check(global_array_multi[i][1][1] == 0); + } +} diff --git a/tests/regression/66-interval-set-one/88-publish-basic.c b/tests/regression/66-interval-set-one/88-publish-basic.c new file mode 100644 index 0000000000..ee7ecd946b --- /dev/null +++ b/tests/regression/66-interval-set-one/88-publish-basic.c @@ -0,0 +1,24 @@ +// PARAM: --set ana.int.interval_set true +#include +#include + +int glob1 = 0; +pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; + +void *t_fun(void *arg) { + pthread_mutex_lock(&mutex); + glob1 = 5; + __goblint_check(glob1 == 5); + pthread_mutex_unlock(&mutex); + return NULL; +} + +int main(void) { + pthread_t id; + __goblint_check(glob1 == 0); + pthread_create(&id, NULL, t_fun, NULL); + __goblint_check(glob1 == 0); // UNKNOWN! + __goblint_check(glob1 == 5); // UNKNOWN! + pthread_join (id, NULL); + return 0; +} diff --git a/tests/regression/66-interval-set-one/89-slr-interval.c b/tests/regression/66-interval-set-one/89-slr-interval.c new file mode 100644 index 0000000000..b2f852cd5a --- /dev/null +++ b/tests/regression/66-interval-set-one/89-slr-interval.c @@ -0,0 +1,73 @@ +// PARAM: --set ana.int.interval_set true --set solver new +// https://github.com/goblint/analyzer/pull/805#discussion_r933230577 +#include +#include + + +int main () { + int a = 1,b = 2,c = 3; + int x,y,z; + int w; + int false = 0; + int true = 42; + + if (x){ + __goblint_check(x != 0); + } else { + __goblint_check(x == 0); + } + + __goblint_check(!! true); + __goblint_check(! false); + + if (a){ + a = a; + } else + __goblint_check(0); // NOWARN + + + if (!a) + __goblint_check(0); // NOWARN + else + a = a; + + if (z != 0){ + a = 8; + b = 9; + } else { + a = 9; + b = 8; + } + + __goblint_check(a); + __goblint_check(a!=b); //UNKNOWN + __goblint_check(a<10); + __goblint_check(a<=9); + __goblint_check(!(a<8)); + __goblint_check(a==8); //UNKNOWN + __goblint_check(b>7); + __goblint_check(b>=8); + __goblint_check(!(a>9)); + __goblint_check(b==8); //UNKNOWN + + for(x = 0; x < 10; x++){ + __goblint_check(x >= 0); + __goblint_check(x <= 9); + } + __goblint_check(x == 10); + + if (0 <= w) + { + } + else + { + return 0; + } + + if (w > 0) + { + __goblint_check(1); + } + + return 0; +} diff --git a/tests/regression/66-interval-set-one/90-nesting_arrays.c b/tests/regression/66-interval-set-one/90-nesting_arrays.c new file mode 100644 index 0000000000..8d5bc8cea5 --- /dev/null +++ b/tests/regression/66-interval-set-one/90-nesting_arrays.c @@ -0,0 +1,211 @@ +// PARAM: --enable ana.int.interval_set --set ana.base.arrays.domain partitioned --set ana.base.partition-arrays.keep-expr "last" --enable annotation.int.enabled --set ana.int.refinement fixpoint +#include + +struct kala { + int i; + int a[5]; +}; + +struct kalaw { + int* a; +}; + +struct kass { + int v; +}; + +union uArray { + int a[5]; + int b[5]; +}; + +union uStruct { + int b; + struct kala k; +}; + +int main(void) __attribute__((goblint_precision("no-interval"))); +void example1() __attribute__((goblint_precision("no-def_exc"))); +void example2() __attribute__((goblint_precision("no-def_exc"))); +void example3() __attribute__((goblint_precision("no-def_exc"))); +void example4() __attribute__((goblint_precision("no-def_exc"))); +void example5() __attribute__((goblint_precision("no-def_exc"))); +void example6() __attribute__((goblint_precision("no-def_exc"))); +void example7() __attribute__((goblint_precision("no-def_exc"))); +void example8() __attribute__((goblint_precision("no-def_exc"))); + + +int main(void) { + example1(); + example2(); + example3(); + example4(); + example5(); + example6(); + example7(); + example8(); + return 0; +} + +void example1() { + struct kala l; + int i = 0; + int top; + + while (i < 5) { + l.a[i] = 42; + i++; + + // Check assertion that should only hold later does not already hold here + __goblint_check(l.a[4] == 42); //UNKNOWN + } + + // Check the array is correctly initialized + __goblint_check(l.a[1] == 42); + __goblint_check(l.a[2] == 42); + __goblint_check(l.a[3] == 42); + __goblint_check(l.a[4] == 42); + + // Destructively assign to i + i = top; + + // Check the array is still known to be completely initialized + __goblint_check(l.a[1] == 42); + __goblint_check(l.a[2] == 42); + __goblint_check(l.a[3] == 42); + __goblint_check(l.a[4] == 42); +} + +void example2() { + struct kala kalas[5]; + + int i2 = 0; + + while (i2 < 4) { + int j2 = 0; + while (j2 < 5) { + kalas[i2].a[j2] = 8; + j2++; + } + i2++; + } + + // Initialization has not proceeded this far + __goblint_check(kalas[4].a[0] == 8); //UNKNOWN + __goblint_check(kalas[0].a[0] == 8); +} + +void example3() { + struct kala xnn; + for(int l=0; l < 5; l++) { + xnn.a[l] = 42; + } + + __goblint_check(xnn.a[3] == 42); +} + +void example4() { + struct kala xn; + + struct kala xs[5]; + + for(int j=0; j < 4; j++) { + xs[j] = xn; + for(int k=0; k < 5; k++) { + xs[j].a[k] = 7; + } + } + + __goblint_check(xs[3].a[0] == 7); +} + +void example5() { + // This example is a bit contrived to show that splitting and moving works for + // unions + union uArray ua; + int i3 = 0; + int top; + int *i = ⊤ + + ua.a[*i] = 1; + + while (i3 < 5) { + ua.a[i3] = 42; + i3++; + } + + __goblint_check(ua.a[i3 - 1] == 42); + + ua.b[0] = 3; + __goblint_check(ua.b[0] == 3); + + // ------------------------------- + union uStruct us; + int i4 = 0; + + us.b = 4; + us.k.a[i4] = 0; + __goblint_check(us.b == 4); // UNKNOWN + __goblint_check(us.k.a[0] == 0); + __goblint_check(us.k.a[3] == 0); // UNKNOWN + + while (i4 < 5) { + us.k.a[i4] = 42; + i4++; + } + + __goblint_check(us.k.a[1] == 42); + __goblint_check(us.k.a[0] == 0); // FAIL +} + +void example6() { + int a[42]; + int i = 0; + + struct kass k; + k.v = 7; + + while(i < 42) { + a[i] = 0; + i++; + } + + i = 0; + + a[k.v] = 2; + k.v = k.v+1; + + __goblint_check(a[k.v] != 3); +} + +void example7() { + // Has no asserts, just checks this doesn't cause an infinite loop + int a[42]; + int i = 0; + + while(i < 40) { + a[i] = 0; + i++; + } + + a[a[0]] = 2; +} + +// Test correct behavior with more involved expression in subscript operator +void example8() { + int a[42]; + union uArray ua; + + ua.a[0] = 0; + ua.a[1] = 0; + ua.a[2] = 0; + ua.a[3] = 0; + ua.a[4] = 0; + + int i = 0; + int *ip = &i; + + a[ua.a[*ip]] = 42; + ip++; + __goblint_check(a[ua.a[*ip]] == 42); //UNKNOWN +} diff --git a/tests/regression/66-interval-set-one/92-assert-infinite-loop.c b/tests/regression/66-interval-set-one/92-assert-infinite-loop.c new file mode 100644 index 0000000000..920acf2dc9 --- /dev/null +++ b/tests/regression/66-interval-set-one/92-assert-infinite-loop.c @@ -0,0 +1,19 @@ +// PARAM: --enable ana.int.interval_set --disable ana.int.def_exc +// This is a pattern we saw in some examples for SVCOMP, where instead of the __goblint_check(0) there was a call to verifier error. +// Because of the demand-driven nature of our solvers, we never looked at the code inside fail since there is no edge from the loop to the endpoint of f. +// However, the __goblint_check(0) (verifier error) is still reachable from main. +#include + +void f(void) { + fail: + __goblint_check(0); //FAIL + goto fail; +} + +int main(void) { + int top; + + if(top) { + f(); + } +} diff --git a/tests/regression/66-interval-set-one/93-enum.c b/tests/regression/66-interval-set-one/93-enum.c new file mode 100644 index 0000000000..5d2b45043d --- /dev/null +++ b/tests/regression/66-interval-set-one/93-enum.c @@ -0,0 +1,7 @@ +// PARAM: --disable ana.int.interval_set --disable ana.int.def_exc --enable ana.int.enums +void main(){ + int n = 1; + for (; n; n++) { // fixed point not reached here + } + return; +} diff --git a/tests/regression/66-interval-set-one/94-widen-dependent.c b/tests/regression/66-interval-set-one/94-widen-dependent.c new file mode 100644 index 0000000000..0bbe2fa8f5 --- /dev/null +++ b/tests/regression/66-interval-set-one/94-widen-dependent.c @@ -0,0 +1,38 @@ +// PARAM: --enable ana.int.interval_set --enable exp.priv-distr-init +#include +#include + +// protection priv succeeds +// write fails due to [1,1] widen [0,1] -> [-inf,1] +// sensitive to eval and widen order! + +int g = 0; + +pthread_mutex_t A = PTHREAD_MUTEX_INITIALIZER; + +void *worker(void *arg ) +{ + pthread_mutex_lock(&A); + while (g <= 0) { + + } + __goblint_check(g > 0); // precise privatization fails + g--; + pthread_mutex_unlock(&A); + return NULL; +} + +int main(int argc , char **argv ) +{ + pthread_t tid; + pthread_create(& tid, NULL, & worker, NULL); + + pthread_mutex_lock(&A); + while (g >= 10) { + + } + __goblint_check(g >= 0); // precise privatization fails + g++; + pthread_mutex_unlock(&A); + return 0; +} diff --git a/tests/regression/66-interval-set-one/95-large_arrays-nocalloc.c b/tests/regression/66-interval-set-one/95-large_arrays-nocalloc.c new file mode 100644 index 0000000000..ad1476a6ba --- /dev/null +++ b/tests/regression/66-interval-set-one/95-large_arrays-nocalloc.c @@ -0,0 +1,41 @@ +// PARAM: --enable ana.int.interval_set --set ana.base.arrays.domain partitioned +#include +#include +#include +#include +#include + +// Test to check whether partitioned arrays can have an index expression evaluating to values largers than the max value of int64 + +#define LENGTH (LONG_MAX - 600) +#define STOP (LENGTH - 1) + +int main(){ + // Check that ptrdiff_t is at least as big as long, so we can index arrays with non-negative longs + __goblint_check(sizeof(ptrdiff_t) >= sizeof(long)); + + char arr[LENGTH]; + + for(unsigned long i = 0; i < STOP; i++){ + arr[i] = 1; + } + + // arr[0] ... arr[STOP - 1] should be 1, the others equal to 0 + __goblint_check(arr[0] == 1); + __goblint_check(arr[INT_MAX + 1l] == 1); + + // j is the smallest index where checking it used to yield an unsound value + // long j = ((long) INT_MAX) * INT_MAX * 2 + INT_MAX - 1; + long j = LONG_MAX - 6442450943; + __goblint_check(0 < j); + __goblint_check(j < STOP); + + __goblint_check(arr[j - 1] == 1); + + __goblint_check(arr[j] == 1); + __goblint_check(arr[STOP - 1] == 1); + + __goblint_check(arr[STOP] == 0); //UNKNOWN! + __goblint_check(arr[LENGTH - 1] == 0); //UNKNOWN! + return 0; +} diff --git a/tests/regression/66-interval-set-one/96-more_passing.c b/tests/regression/66-interval-set-one/96-more_passing.c new file mode 100644 index 0000000000..5dbf6c6d54 --- /dev/null +++ b/tests/regression/66-interval-set-one/96-more_passing.c @@ -0,0 +1,75 @@ +//PARAM: --enable ana.int.interval_set --disable ana.int.def_exc --set ana.base.arrays.domain partitioned +#include +#include + +void foo(int n, int a[n]) { + int x = a[7]; + __goblint_check(x == 42); +} + +void fooo(int n, int a[n][n]) { + __goblint_check(a[29][7] == 42); + int *ptr = a[29]; + int x = *(ptr+7); + printf("x is %d", x); + __goblint_check(x == 42); +} + +void foo2(int n, int a[50][n]) { + __goblint_check(a[29][7] == 42); + __goblint_check(a[29][7] == 0); //FAIL +} + +// This is quite ugly, but valid C99 +void foo3(int n, int b[n], int a[n][b[0]]) { + __goblint_check(a[29][7] == 42); +} + +void foo4(int n, int m, int r, int a[n][m][r]) { + __goblint_check(a[3][3][2] == 42); +} + +int main(void) +{ + // One-dimensional arrays + int a[40]; + a[7] = 42; + foo(40, a); + + int x; + + if(x < 8) { + x = 347; + } + + int b[x]; + b[7] = 42; + + foo(x, b); + + //Two dimensional arrays + int c[50][50]; + + for(int i = 0; i < 50;i++) { + for(int j=0;j < 50;j++) { + c[i][j] = 0; + } + } + + c[29][7] = 42; + + foo2(50,c); + fooo(50, c); + + int x[50]; + b[0] = 50; + foo3(50, x, c); + + int n = 15; + int m = 47; + int r = 11; + + int d[n][m][r]; + d[3][3][2] = 42; + foo4(n,m,r,d); +} diff --git a/tests/regression/66-interval-set-one/97-casts.c b/tests/regression/66-interval-set-one/97-casts.c new file mode 100644 index 0000000000..95b2026abe --- /dev/null +++ b/tests/regression/66-interval-set-one/97-casts.c @@ -0,0 +1,109 @@ +// PARAM: --enable ana.int.interval_set --enable ana.float.interval +#include + +#define RANGE(val, min, max) \ + if (rnd) \ + { \ + val = min; \ + } \ + else \ + { \ + val = max; \ + } + +int main() +{ + int rnd; + + double value; + float value2; + long double value3; + int i; + long l; + unsigned u; + + // Casts from double/float/long double into different variants of ints + __goblint_check((int)0.0); // FAIL + __goblint_check((long)0.0); // FAIL + __goblint_check((unsigned)0.0); // FAIL + __goblint_check((int)0.0f); // FAIL + __goblint_check((long)0.0f); // FAIL + __goblint_check((unsigned)0.0f); // FAIL + __goblint_check((int)0.0l); // FAIL + __goblint_check((long)0.0l); // FAIL + __goblint_check((unsigned)0.0l); // FAIL + + __goblint_check((unsigned)1.0); // SUCCESS + __goblint_check((long)2.0); // SUCCESS + __goblint_check((int)3.0); // SUCCESS + __goblint_check((unsigned)1.0f); // SUCCESS + __goblint_check((long)2.0f); // SUCCESS + __goblint_check((int)3.0f); // SUCCESS + __goblint_check((unsigned)1.0l); // SUCCESS + __goblint_check((long)2.0l); // SUCCESS + __goblint_check((int)3.0l); // SUCCESS + + // Cast from int into double/float/long double + __goblint_check((double)0); // FAIL + __goblint_check((double)0l); // FAIL + __goblint_check((double)0u); // FAIL + + __goblint_check((double)1u); // SUCCESS + __goblint_check((double)2l); // SUCCESS + __goblint_check((double)3); // SUCCESS + + __goblint_check((float)0); // FAIL + __goblint_check((float)0l); // FAIL + __goblint_check((float)0u); // FAIL + + __goblint_check((float)1u); // SUCCESS + __goblint_check((float)2l); // SUCCESS + __goblint_check((float)3); // SUCCESS + + __goblint_check((long double)0); // FAIL + __goblint_check((long double)0l); // FAIL + __goblint_check((long double)0u); // FAIL + + __goblint_check((long double)1u); // SUCCESS + __goblint_check((long double)2l); // SUCCESS + __goblint_check((long double)3); // SUCCESS + + // cast with ranges + RANGE(i, -5, 5); + value = (double)i; + __goblint_check(-5. <= value && value <= 5.f); // SUCCESS + value2 = (float)i; + __goblint_check(-5.f <= value2 && value2 <= 5.); // SUCCESS + value3 = (long double)i; + __goblint_check(-5.f <= value3 && value3 <= 5.l); // SUCCESS + + RANGE(l, 10, 20); + value = l; + __goblint_check(10.f <= value && value <= 20.); // SUCCESS + value2 = l; + __goblint_check(10.l <= value2 && value2 <= 20.f); // SUCCESS + value3 = l; + __goblint_check(10. <= value2 && value2 <= 20.); // SUCCESS + + RANGE(u, 100, 1000); + value = u; + __goblint_check(value > 1.); // SUCCESS + value2 = u; + __goblint_check(value2 > 1.f); // SUCCESS + value3 = u; + __goblint_check(value2 > 1.l); // SUCCESS + + RANGE(value, -10.f, 10.); + i = (int)value; + __goblint_check(-10 <= i && i <= 10); // SUCCESS + + RANGE(value2, -10.f, 10.); + i = (int)value2; + __goblint_check(-10 <= i && i <= 10); // SUCCESS + + RANGE(value3, -10.l, 10.); + i = (int)value3; + __goblint_check(-10 <= i && i <= 10); // SUCCESS + + return 0; +} diff --git a/tests/regression/66-interval-set-one/98-widen-dependent-local.c b/tests/regression/66-interval-set-one/98-widen-dependent-local.c new file mode 100644 index 0000000000..a5661256c0 --- /dev/null +++ b/tests/regression/66-interval-set-one/98-widen-dependent-local.c @@ -0,0 +1,45 @@ +// PARAM: --enable ana.int.interval_set --enable exp.priv-distr-init +extern int __VERIFIER_nondet_int(); + +#include +#include + +// protection priv succeeds +// write fails due to [1,+inf] widen ([1,+inf] join [0,+inf]) -> [-inf,+inf] +// sensitive to eval and widen order! + +int g = 0; +int limit; // unknown + +pthread_mutex_t A = PTHREAD_MUTEX_INITIALIZER; + +void *worker(void *arg ) +{ + // just for going to multithreaded mode + return NULL; +} + +int put() { + pthread_mutex_lock(&A); + while (g >= limit) { // problematic widen + + } + __goblint_check(g >= 0); // precise privatization fails + g++; + pthread_mutex_unlock(&A); +} + +int main(int argc , char **argv ) +{ + pthread_t tid; + pthread_create(& tid, NULL, & worker, NULL); + + int r = __VERIFIER_nondet_int(); + limit = r; // only problematic if limit unknown + + while (1) { + // only problematic if not inlined + put(); + } + return 0; +} diff --git a/tests/regression/66-interval-set-one/99-off.c b/tests/regression/66-interval-set-one/99-off.c new file mode 100644 index 0000000000..337616b9f2 --- /dev/null +++ b/tests/regression/66-interval-set-one/99-off.c @@ -0,0 +1,13 @@ +// PARAM: --enable ana.int.interval_set --set ana.base.arrays.domain partitioned --disable exp.fast_global_inits +// This checks that partitioned arrays and fast_global_inits are no longer incompatible +#include + +int global_array[50]; +int global_array_multi[50][2][2]; + +int main(void) { + for(int i =0; i < 50; i++) { + __goblint_check(global_array[i] == 0); + __goblint_check(global_array_multi[i][1][1] == 0); + } +} diff --git a/tests/regression/67-interval-sets-two/00-large_arrays.c b/tests/regression/67-interval-sets-two/00-large_arrays.c new file mode 100644 index 0000000000..85a1ea59c2 --- /dev/null +++ b/tests/regression/67-interval-sets-two/00-large_arrays.c @@ -0,0 +1,47 @@ +// PARAM: --enable ana.int.interval_set --set ana.base.arrays.domain partitioned +#include +#include +#include +#include +#include + +// Test to check whether partitioned arrays can have an index expression evaluating to values larger than the max value of int64 + +#define LENGTH (LONG_MAX - 600) +#define STOP (LENGTH - 1) + +int main(){ + // Check that ptrdiff_t is at least as big as long, so we can index arrays with non-negative longs + __goblint_check(sizeof(ptrdiff_t) >= sizeof(long)); + + char *arr = calloc(LENGTH, sizeof(char)); + if(arr == NULL){ + printf("Could not allocate array, exiting.\n"); + return 1; + } + + for(unsigned long i = 0; i < STOP; i++){ + arr[i] = 1; + } + + // arr[0] ... arr[STOP - 1] should be 1, the others equal to 0 + __goblint_check(arr[0] == 1); // UNKNOWN + __goblint_check(arr[INT_MAX + 1l] == 1); //UNKNOWN + + // j is the smallest index where checking it used to yield an unsound value + // long j = ((long) INT_MAX) * INT_MAX * 2 + INT_MAX - 1; + long j = LONG_MAX - 6442450943; + __goblint_check(0 < j); + __goblint_check(j < STOP); + + // This check is imprecise, but not unsound + __goblint_check(arr[j - 1] == 1); //UNKNOWN + + // These two asserts used to fail somehow + __goblint_check(arr[j] == 1); //UNKNOWN + __goblint_check(arr[STOP - 1] == 1); //UNKNOWN + + __goblint_check(arr[STOP] == 0); //UNKNOWN + __goblint_check(arr[LENGTH - 1] == 0); //UNKNOWN + return 0; +} diff --git a/tests/regression/67-interval-sets-two/01-array-out-of-bounds.c b/tests/regression/67-interval-sets-two/01-array-out-of-bounds.c new file mode 100644 index 0000000000..5ce4e65da6 --- /dev/null +++ b/tests/regression/67-interval-sets-two/01-array-out-of-bounds.c @@ -0,0 +1,16 @@ +// PARAM: --enable ana.arrayoob --enable ana.int.interval_set --enable ana.int.enums +#include +//This is the most basic case +int main() +{ + int arr[] = {1, 2, 3, 4, 5, 6}; + arr[2] = 0; //NOWARN + arr[6] = 10; //WARN + arr[-1] = 10; //WARN + + for (int i = 0; i < 10; ++i) + { + arr[i] = 5; //WARN + } + return 0; +} diff --git a/tests/regression/67-interval-sets-two/02-pointers_array.c b/tests/regression/67-interval-sets-two/02-pointers_array.c new file mode 100644 index 0000000000..34013210a3 --- /dev/null +++ b/tests/regression/67-interval-sets-two/02-pointers_array.c @@ -0,0 +1,193 @@ +// PARAM: --enable ana.int.interval_set --set ana.base.arrays.domain partitioned --set ana.base.partition-arrays.keep-expr "last" --enable annotation.int.enabled --set ana.int.refinement fixpoint +#include + +int main(void) __attribute__((goblint_precision("no-interval"))); +void example1(void) __attribute__((goblint_precision("no-def_exc"))); +void example2() __attribute__((goblint_precision("no-def_exc"))); +void example3(void) __attribute__((goblint_precision("no-def_exc"))); +void example4(void) __attribute__((goblint_precision("no-def_exc"))); +void example5(void) __attribute__((goblint_precision("no-def_exc"))); +void example6(void) __attribute__((goblint_precision("no-def_exc"))); +void example7(void) __attribute__((goblint_precision("no-def_exc"))); + + +int main(void) { + example1(); + example2(); + example3(); + example4(); + example5(); + example6(); + example7(); + return 0; +} + +// Initializing an array with pointers +void example1(void) { + int top; + + int a[42]; + int *ptr = &a; + + *ptr = 42; + ptr++; + + __goblint_check(a[0] == 42); + __goblint_check(a[1] == 42); // UNKNOWN + + *ptr = 42; + __goblint_check(a[0] == 42); + __goblint_check(a[1] == 42); + ptr++; + + *ptr = 42; + ptr++; + *ptr = 42; + ptr++; + *ptr = 42; + ptr++; + *ptr = 42; + ptr++; + + + int i = 5; + __goblint_check(a[i] == 42); + + if(top) { + i++; + } + + __goblint_check(a[i] == 42); // UNKNOWN +} + +// Tests correct handling when pointers may point to several different things +void example2() { + int array1[10000000]; + int array2[10000000]; + + int* ptr; + + if(rand()) { + ptr = &array1; + *ptr = 5; + + __goblint_check(*ptr == 5); + } + else { + ptr = &array2; + *ptr = 5; + + __goblint_check(*ptr == 5); + } + + // Since ptr could point to different arrays, the update here can not be precise + *ptr = 6; + + __goblint_check(*ptr == 6); // UNKNOWN +} + +void example3(void) { + int array1[5]; + int *ptr = &array1; + + for(int i =0; i <5; i++) { + *ptr = 42; + __goblint_check(*ptr == 42); + ptr++; + } +} + +void example4(void) { + int array1[5]; + int *ptr = &array1; + int *end = &(array1[5]); + + while(ptr <= end) { + *ptr = 42; + __goblint_check(*ptr == 42); + ptr++; + } + + // In an ideal world, I would like to have information about array1[0] and so on. For this the <= would need yo improve +} + +void example5(void) { + int array1[5]; + int *ptr = &(array1[4]); + + *ptr = 42; + ptr--; + *ptr = 42; + ptr--; + *ptr = 40; + + __goblint_check(*ptr == 40); + __goblint_check(array1[4] == 42); + __goblint_check(array1[3] == 42); + __goblint_check(array1[2] == 40); + __goblint_check(array1[0] == 42); // UNKNOWN +} + +void example6(void) { + int array1[100]; + int* ptr = &array1; + + *ptr = 5; + int v = *ptr; + __goblint_check(v == 5); + + ptr++; + *ptr = 6; + ptr++; + *ptr = 7; + + // This is necessary for the tests that we are doing later + int k = ptr-&array1; + __goblint_check(k == 2); + int m = ptr-array1; + __goblint_check(m == 2); + + int* np = &array1; + np++; + np++; + int x = *np; + __goblint_check(x==7); +} + +void example7(void) { + int top; + + int arr1[42]; + int arr2[42]; + int *ptr; + + for(int i = 0; i < 42; i++) { + arr1[i] = 4; + arr2[i] = 4; + } + + ptr = &arr1[7]; + + if(top) { + ptr = &arr2[7]; + } + + *ptr = 9; + + // Case ptr = &arr1[7] + // arr1 -> (ptr-arr1, ([4,4], [9,9],[4,4])) + // arr2 -> (-,[4,4]) + + // Case ptr = &arr2[7] + // arr1 -> (-, [4,4]) + // arr2 -> (ptr-arr2, ([4,4], [9,9],[4,4])) + + // LUB: + // arr1 -> (-, [4,9]) + // arr2 -> (-, [4,9]) + int x = arr1[7]; + __goblint_check(x == 3); // FAIL + __goblint_check(x == 4); // UNKNOWN + __goblint_check(x == 9); // UNKNOWN + __goblint_check(x == 10); // FAIL +} diff --git a/tests/regression/67-interval-sets-two/03-def_exc-interval-inconsistent.c b/tests/regression/67-interval-sets-two/03-def_exc-interval-inconsistent.c new file mode 100644 index 0000000000..8d56fa0e3c --- /dev/null +++ b/tests/regression/67-interval-sets-two/03-def_exc-interval-inconsistent.c @@ -0,0 +1,23 @@ +// PARAM: --enable ana.int.def_exc --enable ana.int.interval_set --enable ana.sv-comp.functions --set sem.int.signed_overflow assume_none --set ana.int.refinement never +// used to crash in branch when is_bool returned true, but to_bool returned None on (0,[1,1]) +// manually minimized from sv-benchmarks/c/recursive/MultCommutative-2.c +extern int __VERIFIER_nondet_int(void); + +void f(int m) { + if (m < 0) { + f(-m); + } + if (m == 0) { + return; + } + f(m - 1); +} + +int main() { + int m = __VERIFIER_nondet_int(); + if (m < 0 || m > 1) { + return 0; + } + f(m); // m=[0,1] + return 0; +} diff --git a/tests/regression/67-interval-sets-two/04-unsupported.c b/tests/regression/67-interval-sets-two/04-unsupported.c new file mode 100644 index 0000000000..97ce11258a --- /dev/null +++ b/tests/regression/67-interval-sets-two/04-unsupported.c @@ -0,0 +1,37 @@ +// PARAM: --enable ana.int.interval_set --disable exp.fast_global_inits --set ana.base.arrays.domain partitioned + +// This is just to test that the analysis does not cause problems for features that are not explicitly dealt with +int main(void) { + callok(); +} + +struct blub +{ + int blubby; +}; + +struct ms +{ + struct blub b; + int x; + int y; + int z; +}; + + +void fun(int *ptr) { + *ptr++; +} + +void callok(void) { + struct ms *ptr = kzalloc(sizeof(struct ms)); + + fun(&ptr->b.blubby); + + ptr->b.blubby = 8; + + ptr->x = 47; + ptr->y = 11; + + return 0; +} diff --git a/tests/regression/67-interval-sets-two/05-tid-toy11.c b/tests/regression/67-interval-sets-two/05-tid-toy11.c new file mode 100644 index 0000000000..3dee221f7d --- /dev/null +++ b/tests/regression/67-interval-sets-two/05-tid-toy11.c @@ -0,0 +1,78 @@ +// PARAM: --set ana.path_sens[+] threadflag --set ana.base.privatization mutex-meet-tid --enable ana.int.interval_set --set ana.activated[+] threadJoins +// Inspired by 36/81 +#include +#include + +int g = 10; +int h = 10; +pthread_mutex_t A = PTHREAD_MUTEX_INITIALIZER; + +pthread_t other_t; + +void *t_fun(void *arg) { + int x = 10; + int y; + + pthread_mutex_lock(&A); + g = x; + h = y; + pthread_mutex_unlock(&A); + + pthread_mutex_lock(&A); + g = x; + h = x; + pthread_mutex_unlock(&A); + return NULL; +} + +void *t_benign(void *arg) { + // Without this, it would even succeed without the must joined analysis. + // With it, that is required! + pthread_mutex_lock(&A); + g = 12; + h = 14; + pthread_mutex_unlock(&A); + + pthread_mutex_lock(&A); + pthread_create(&other_t, NULL, t_fun, NULL); + pthread_mutex_unlock(&A); + + pthread_mutex_lock(&A); + g = 10; + h = 10; + pthread_mutex_unlock(&A); + return NULL; +} + +int main(void) { + int t; + + pthread_mutex_lock(&A); + g = 12; + h = 14; + pthread_mutex_unlock(&A); + + // Force multi-threaded handling + pthread_t id2; + pthread_create(&id2, NULL, t_benign, NULL); + + pthread_mutex_lock(&A); + __goblint_check(g == h); //UNKNOWN! + pthread_mutex_unlock(&A); + + pthread_join(id2, NULL); + + pthread_mutex_lock(&A); + __goblint_check(g == h); // UNKNOWN! + pthread_mutex_unlock(&A); + + pthread_mutex_lock(&A); + pthread_join(other_t, NULL); + pthread_mutex_unlock(&A); + + pthread_mutex_lock(&A); + __goblint_check(g == h); + pthread_mutex_unlock(&A); + + return 0; +} diff --git a/tests/regression/67-interval-sets-two/06-no-int-context.c b/tests/regression/67-interval-sets-two/06-no-int-context.c new file mode 100644 index 0000000000..d4ff46b0b7 --- /dev/null +++ b/tests/regression/67-interval-sets-two/06-no-int-context.c @@ -0,0 +1,15 @@ +// PARAM: --enable ana.int.interval_set --disable ana.context.widen --disable ana.base.context.int +#include + +int f(int x) { + if (x) + return f(x+1); + else + return x; +} + +int main () { + int a = f(1); + __goblint_check(!a); + return 0; +} diff --git a/tests/regression/67-interval-sets-two/07-var_eq.c b/tests/regression/67-interval-sets-two/07-var_eq.c new file mode 100644 index 0000000000..6e7eedc3bb --- /dev/null +++ b/tests/regression/67-interval-sets-two/07-var_eq.c @@ -0,0 +1,31 @@ +// PARAM: --enable ana.int.interval_set --set ana.base.arrays.domain partitioned --set ana.activated[+] var_eq +#include + +int global; + +int main(void) +{ + example1(); + example2(); + example3(); + example4(); + example5(); + example6(); + example7(); + example8(); + example9(); + example10(); + return 0; +} + +// Simple example +void example1(void) +{ + int top; + int top2; + int arr[10]; + + arr[top] = 42; + top2 = top; + __goblint_check(arr[top2] == 42); +} diff --git a/tests/regression/67-interval-sets-two/08-nested-unroll.c b/tests/regression/67-interval-sets-two/08-nested-unroll.c new file mode 100644 index 0000000000..2e5051a652 --- /dev/null +++ b/tests/regression/67-interval-sets-two/08-nested-unroll.c @@ -0,0 +1,17 @@ +// PARAM: --enable ana.int.interval_set --set exp.unrolling-factor 5 --set ana.base.arrays.domain unroll --set ana.base.arrays.unrolling-factor 5 +#include +int main(void) { + int arr[10][10]; + + for(int i=0;i<10; i++) { + for(int j=0;j <10; j++) { + arr[i][j] = i+j; + } + } + + for(int i=0;i<5; i++) { + for(int j=0;j <5; j++) { + __goblint_check(arr[i][j] == i+j); + } + } +} diff --git a/tests/regression/67-interval-sets-two/09-mm-reentrant.c b/tests/regression/67-interval-sets-two/09-mm-reentrant.c new file mode 100644 index 0000000000..87f9419fd5 --- /dev/null +++ b/tests/regression/67-interval-sets-two/09-mm-reentrant.c @@ -0,0 +1,59 @@ +// PARAM: --enable ana.int.interval_set --set ana.base.privatization mutex-meet --disable sem.unknown_function.invalidate.globals --disable sem.unknown_function.spawn +#include +#include +#include +#include +#include +#include + +pthread_mutex_t mt; +int i = 0; + +void* fn1(void* agr) +{ + int top = rand(); + + pthread_mutex_lock(&mt); + if(top) { + i = 5; + } + pthread_mutex_lock(&mt); + __goblint_check(i == 0); //UNKNOWN! + i = 0; + pthread_mutex_unlock(&mt); + pthread_mutex_unlock(&mt); +} + +void* fn2(void* agr) +{ + int top = rand(); + + pthread_mutex_lock(&mt); + if(top) { + i = 5; + } + top = pthread_mutex_lock(&mt); + __goblint_check(i == 0); //UNKNOWN! + i = 0; + pthread_mutex_unlock(&mt); + pthread_mutex_unlock(&mt); +} + + +int main() +{ + pthread_t tid1; + pthread_t tid2; + + pthread_mutexattr_t mat; + pthread_mutexattr_init(&mat); + + //The type of lock set is recursive + pthread_mutexattr_settype(&mat, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&mt, &mat); + + pthread_create(&tid1, NULL, fn1, NULL); + pthread_create(&tid2, NULL, fn2, NULL); + + pthread_join(tid1,NULL); +} diff --git a/tests/regression/67-interval-sets-two/10-cast-return-void-ptr.c b/tests/regression/67-interval-sets-two/10-cast-return-void-ptr.c new file mode 100644 index 0000000000..c93bde44d1 --- /dev/null +++ b/tests/regression/67-interval-sets-two/10-cast-return-void-ptr.c @@ -0,0 +1,15 @@ +// PARAM: --enable ana.int.interval_set --set sem.int.signed_overflow assume_none +#include + +int empty() { + return -1; // return shouldn't cast to void* generally, but just for thread return +} + +int main(void) { + if (!empty()==-1) { // if -1 is cast to void*, it makes both branches dead! + __goblint_check(1); // NOWARN (unreachable) + } + + __goblint_check(1); // reachable + return 0; +} diff --git a/tests/regression/67-interval-sets-two/13-loop.c b/tests/regression/67-interval-sets-two/13-loop.c new file mode 100644 index 0000000000..8df2802574 --- /dev/null +++ b/tests/regression/67-interval-sets-two/13-loop.c @@ -0,0 +1,62 @@ +// PARAM: --enable ana.int.interval_set --disable ana.int.def_exc --set ana.base.arrays.domain partitioned +#include + +int main(void) +{ + example1(); + example2(); + example3(); + example4(); +} + +void example1(void) { + for(int i=1;i<10;i++) { + int a[i]; + a[i-1] = 0; + __goblint_check(a[i-1] == 0); + } +} + +void example2(void) { + for(int i=0; i < 47; i++) { + int a[i+2]; + + for(int j = 0; j < 2; j++) { + a[j] = 0; + } + + __goblint_check(a[0] == 0); + } +} + +void example3(void) { + for(int i = 2; i < 47; i++) { + int n = 1; + int a[1]; + + if(i == 2) { + a[0] = 42; + } + + __goblint_check(a[0] == 42); //UNKNOWN + } +} + +void example4(void) { + int top; + int l = 5; + + if(top) { + l = 6; + } + + int a[l]; + + for(int i=0; i < l-1; i++) { + a[i] = 42; + } + + for(int i=0; i < 4; i++) { + __goblint_check(a[i] == 42); + } +} diff --git a/tests/regression/67-interval-sets-two/14-trylock_rc_slr.c b/tests/regression/67-interval-sets-two/14-trylock_rc_slr.c new file mode 100644 index 0000000000..af46023135 --- /dev/null +++ b/tests/regression/67-interval-sets-two/14-trylock_rc_slr.c @@ -0,0 +1,25 @@ +// PARAM: --enable ana.int.interval_set --set solver slr3t +#include + +pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; +long counter = 0; + +void *counter_thread (void *arg) { + int tmp = counter; + int i = 0; + pthread_mutex_lock (&mutex); + while(i<5){ + tmp = counter; + tmp++; + counter = tmp; + i++; + } + pthread_mutex_unlock (&mutex); + tmp = 1; +} + +int main (int argc, char *argv[]) { + pthread_t counter_thread_id; + pthread_create (&counter_thread_id, NULL, counter_thread, NULL); + return 0; +} diff --git a/tests/regression/67-interval-sets-two/15-interval-bot.c b/tests/regression/67-interval-sets-two/15-interval-bot.c new file mode 100644 index 0000000000..ab7d043b92 --- /dev/null +++ b/tests/regression/67-interval-sets-two/15-interval-bot.c @@ -0,0 +1,11 @@ +// PARAM: --enable ana.int.interval_set --enable ana.int.def_exc + +int main(){ + + unsigned long long a ; + unsigned long long addr; + + if(a + addr > 0x0ffffffffULL){ + return 1; + } +} diff --git a/tests/regression/67-interval-sets-two/16-branched-thread-creation.c b/tests/regression/67-interval-sets-two/16-branched-thread-creation.c new file mode 100644 index 0000000000..c309ec8ffd --- /dev/null +++ b/tests/regression/67-interval-sets-two/16-branched-thread-creation.c @@ -0,0 +1,50 @@ +// PARAM: --set ana.path_sens[+] threadflag --set ana.base.privatization mutex-meet-tid --enable ana.int.interval_set --set ana.activated[-] threadJoins +// Inspired by 36/86 +#include +#include +#include + +int g; +int h; +pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; + +void *t_fun(void *arg) { + int t = 4; + + pthread_mutex_lock(&mutex); + g=t; + h=t; + pthread_mutex_unlock(&mutex); + return NULL; +} + + +int main(void) { + int top; + int mt = 0; + + if(top) { + + g = 8; + h = 7; + + } else { + pthread_t id; + pthread_create(&id, NULL, t_fun, NULL); + + pthread_mutex_lock(&mutex); + g=top; + h=top; + pthread_mutex_unlock(&mutex); + mt=1; + } + + if(!mt) { + pthread_mutex_lock(&mutex); + __goblint_check(g==h); //MAYFAIL + pthread_mutex_unlock(&mutex); + } + + + return 0; +} diff --git a/tests/regression/67-interval-sets-two/17-intervals-branching-meet-keyed.c b/tests/regression/67-interval-sets-two/17-intervals-branching-meet-keyed.c new file mode 100644 index 0000000000..8f37662abf --- /dev/null +++ b/tests/regression/67-interval-sets-two/17-intervals-branching-meet-keyed.c @@ -0,0 +1,42 @@ +// PARAM: --enable ana.int.interval_set --set ana.base.structs.domain "keyed" + +#include + +struct Pair { + int first; + int second; +}; + +void example1() { + int a; + int b; + + struct Pair pair; + + if (a) { + pair.first = 10; + pair.second = 20; + } else { + pair.first = 20; + pair.second = 30; + } + + if (pair.first == 15) { + // This should be unreachable! + b = 0; // This line is not dead if we --disable ana.base.structs.meet-condition + } else if (pair.first == 10) { + __goblint_check(pair.second == 20); + b = 1; + } else if (pair.first == 20) { + __goblint_check(pair.second == 30); + b = 1; + } + __goblint_check(b == 1); +} + + +int main() { + example1(); + + return 0; +} diff --git a/tests/regression/67-interval-sets-two/18-adapted_from_01_09_array.c b/tests/regression/67-interval-sets-two/18-adapted_from_01_09_array.c new file mode 100644 index 0000000000..9e95421320 --- /dev/null +++ b/tests/regression/67-interval-sets-two/18-adapted_from_01_09_array.c @@ -0,0 +1,122 @@ +// PARAM: --enable ana.int.interval_set --set ana.base.arrays.domain partitioned +#include +#include + +int fun_5() { return 5; } +int fun_6() { return 6; } +int fun_5b() { return 5; } + +struct kala { + int a[5]; +}; + +struct kass { + int v; +}; + +int main () { + int i,t, k1,k2,top; + + int a[] = {2,2,2}; + int b[2], c[3]; + int (*f[2])() = {fun_5, fun_6}; + int (*g[2])() = {fun_5, fun_5b}; + int (*fp)(); + int *ip; + int (*iap)[]; + + // really really top + if (i) top = (int) ⊤ + else top = 5; + + __goblint_check(a[0] == 2); + __goblint_check(a[1] == 2); + __goblint_check(a[2] == 2); + + // writing to unknown index: + // NB! We assume the index is in bounds! + if (k1) i=0; else i=1; + a[i] = 0; + __goblint_check(a[0] == 0); // UNKNOWN + __goblint_check(a[1] == 0); // UNKNOWN + __goblint_check(a[2] == 0); // FAIL + + // reading from unknown index: + b[0] = 2; b[1] = 2; + __goblint_check(b[i] == 2); + b[0] = 3; + __goblint_check(b[i] == 2); // UNKNOWN + + // function arrays + t = f[i](); + __goblint_check(t == 5); // UNKNOWN + t = g[i](); + __goblint_check(t == 5); + + // array has set of addresses: + if (k2) f[i] = fun_5b; + t = f[1](); + __goblint_check(t == 5); // UNKNOWN + + // now we collect all the sets: + fp = f[i]; + t = fp(); + __goblint_check(t == 5); // UNKNOWN + fp = g[i]; + t = fp(); + __goblint_check(t == 5); + + // NASTY ARRAY OPS: + c[0] = 5; c[1] = 5; c[2] = 5; + // this is not usual: a pointer to an array (easy!) + iap = &c; + t = (*iap)[2]; + __goblint_check(t == 5); + + // Typical C: a pointer to first element of array (difficult!) + ip = c; // this means &c[0] + + // dereferencing... + __goblint_check(*ip == 5); + + // pointing into the array + ip = &c[1]; + __goblint_check(*ip == 5); + + // and some pointer arithmetic (tests are meaningless) + *ip = 6; + ip++; + __goblint_check(*ip == 5); + + // Now testing arrays inside structs. + struct kala x; + ip = x.a; + x.a[0] = 7; + __goblint_check(*ip == 7); + + // (typeless) Top index + __goblint_check(x.a[top] == 7); // UNKNOWN + + // And finally array of structs + struct kala xs[5]; + xs[0] = x; + ip = &xs[0].a[0]; + + struct kass k[1]; + k[0].v = 42; + __goblint_check(k[0].v == 42); + + // multi-dim arrays + int ma[1][1]; + ma[0][0] = 42; + __goblint_check(ma[0][0] == 42); + + //i = hash("kala"); + //printf("Hash value: %d", i); + + // NB arrays must be in bounds... otherwise everything fails! + // It's not possible to analyze this: + // a[3] = 666; + + return 0; +} diff --git a/tests/regression/67-interval-sets-two/19-arrays-within-structures.c b/tests/regression/67-interval-sets-two/19-arrays-within-structures.c new file mode 100644 index 0000000000..2c6698fc60 --- /dev/null +++ b/tests/regression/67-interval-sets-two/19-arrays-within-structures.c @@ -0,0 +1,28 @@ +// PARAM: --enable ana.arrayoob --enable ana.int.interval_set --enable ana.int.interval_set --enable ana.int.enums +// Arrays within structures. Source of sample struct: +// https://codeforwin.org/2018/07/how-to-declare-initialize-and-access-array-of-structure.html +#include +int main() { + struct student { + char name[100]; + int roll; + float marks; + }; + // Structure array declaration + struct student stu[] = { + {"vandah", 12, 89.5f}, + {"edin", 15, 98.0f}, + {"david", 17, 90.0f}, + }; + stu[0].roll = 2; //NOWARN + stu[-1].roll = 10; // WARN + stu[1].marks = 90.5f; //NOWARN + stu[20].marks = 89.5f; //WARN + for (int i = 0; i < 3; ++i) { + stu[i].roll = 5; // NOWARN + } + for (int i = 0; i < 10; ++i) { + stu[i].roll = 5; // WARN + } + return 0; +} diff --git a/tests/regression/67-interval-sets-two/20-no-loc.c b/tests/regression/67-interval-sets-two/20-no-loc.c new file mode 100644 index 0000000000..cdb21b6424 --- /dev/null +++ b/tests/regression/67-interval-sets-two/20-no-loc.c @@ -0,0 +1,71 @@ +// PARAM: --set ana.path_sens[+] threadflag --set ana.base.privatization mutex-meet-tid --enable ana.int.interval_set --set ana.activated[+] threadJoins --disable ana.thread.include-node +// Inspired by 36/97 +#include +#include + +int g = 10; +int h = 10; +pthread_mutex_t A = PTHREAD_MUTEX_INITIALIZER; + +void *t_benign(void *arg) { + pthread_mutex_lock(&A); + g = 20; + pthread_mutex_unlock(&A); + return NULL; +} + +void *u_benign(void *arg) { + pthread_mutex_lock(&A); + h = 20; + pthread_mutex_unlock(&A); + return NULL; +} + +int main(void) { + int t; + + pthread_t id; + + if(t) { + pthread_create(&id, NULL, t_benign, NULL); + } else { + pthread_create(&id, NULL, t_benign, NULL); + } + + // As these two threads are not distinguished, we have a unique TID for id + pthread_join(id, NULL); + + + pthread_mutex_lock(&A); + g = 12; + pthread_mutex_unlock(&A); + + pthread_mutex_lock(&A); + __goblint_check(g == 12); + pthread_mutex_unlock(&A); + +// --------------------------------------------------------------------------- + + pthread_t id2; + pthread_t id3; + + + pthread_create(&id2, NULL, u_benign, NULL); + pthread_create(&id3, NULL, u_benign, NULL); + + pthread_join(id2, NULL); + + // As these two threads are not distinguished, id3 is a not unique thread + pthread_join(id3, NULL); + + + pthread_mutex_lock(&A); + h = 12; + pthread_mutex_unlock(&A); + + pthread_mutex_lock(&A); + __goblint_check(h == 12); //TODO + pthread_mutex_unlock(&A); + + return 0; +} diff --git a/tests/regression/67-interval-sets-two/21-thread_ret.c b/tests/regression/67-interval-sets-two/21-thread_ret.c new file mode 100644 index 0000000000..2a4583528e --- /dev/null +++ b/tests/regression/67-interval-sets-two/21-thread_ret.c @@ -0,0 +1,34 @@ +//PARAM: --enable ana.int.interval_set --set ana.activated[-] threadreturn + +#include +#include +#include +#include +#include + +int myglobal; +pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER; +pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER; + + +int f() { + return 1; +} + +void *t_fun(void *arg) { + myglobal=f(); + return NULL; +} + +int main(void) { + pthread_t id; + void *ptr; + void **pptr = &ptr; + pthread_create(&id, NULL, t_fun, NULL); + pthread_join (id, pptr); + int v = *((int*) pptr); + // If we don't have the threadreturn analysis running, all returns from all functions called by the t_fun thread, as well as of t_fun itself are joined together + // But we still should get a value better than top! + __goblint_check(v!=2); + return 0; +} diff --git a/tests/regression/67-interval-sets-two/23-testfive-intervals.c b/tests/regression/67-interval-sets-two/23-testfive-intervals.c new file mode 100644 index 0000000000..b5f9f49fc5 --- /dev/null +++ b/tests/regression/67-interval-sets-two/23-testfive-intervals.c @@ -0,0 +1,33 @@ +// PARAM: --enable ana.int.interval_set --set ana.base.privatization mutex-meet --set solvers.td3.side_widen sides-pp +#include +#include + +int myglobal; +pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; + +void lock() { + pthread_mutex_lock(&mutex); +} + +void unlock() { + pthread_mutex_unlock(&mutex); +} + + +void *t_fun(void *arg) { + lock(); + myglobal++; // NORACE + unlock(); + return NULL; +} + + +int main(void) { + pthread_t id; + pthread_create(&id, NULL, t_fun, NULL); + lock(); + myglobal++; // NORACE + unlock(); + pthread_join (id, NULL); + return 0; +} diff --git a/tests/regression/67-interval-sets-two/24-arithmetic-bot.c b/tests/regression/67-interval-sets-two/24-arithmetic-bot.c new file mode 100644 index 0000000000..f238788bb3 --- /dev/null +++ b/tests/regression/67-interval-sets-two/24-arithmetic-bot.c @@ -0,0 +1,40 @@ +// PARAM: --enable ana.int.interval_set --enable ana.int.def_exc +// from: ldv-linux-3.0/usb_urb-drivers-vhost-vhost_net.ko.cil.out.i +typedef unsigned long long u64; + +int main( ) +{ u64 a ; + unsigned long flag ; + unsigned long roksum ; + struct thread_info *tmp___7 ; + int tmp___8 ; + long tmp___9; + void *log_base; + unsigned long sz; + u64 addr; + { + a = (addr / 4096ULL) / 8ULL; + if (a > (u64 )(0x0fffffffffffffffUL - (unsigned long )log_base)) { + return (0); + } else { + if (a + (u64 )((unsigned long )log_base) > 0x0fffffffffffffffULL) { + return (0); + } else { + } + } + { + tmp___7 = current_thread_info(); + // __asm__ ("add %3,%1 ; sbb %0,%0 ; cmp %1,%4 ; sbb $0,%0": "=&r" (flag), "=r" (roksum): "1" (log_base + a), + // "g" ((long )((((sz + 32768UL) - 1UL) / 4096UL) / 8UL)), "rm" (tmp___7->addr_limit.seg)); + } + if (flag == 0UL) { + tmp___8 = 1; + } else { + tmp___8 = 0; + } + { + tmp___9 = __builtin_expect((long )tmp___8, 1L); + } + return ((int )tmp___9); + } +} diff --git a/tests/regression/67-interval-sets-two/25-mine14.c b/tests/regression/67-interval-sets-two/25-mine14.c new file mode 100644 index 0000000000..55596fbb4c --- /dev/null +++ b/tests/regression/67-interval-sets-two/25-mine14.c @@ -0,0 +1,35 @@ +// PARAM: --set ana.path_sens[+] threadflag --set ana.base.privatization mutex-meet-tid --enable ana.int.interval_set --set ana.activated[+] threadJoins --enable ana.int.interval_threshold_widening +// Fig 5a from Miné 2014 +#include +#include +#include + +int x; +pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; + +void *t_fun(void *arg) { + int top; + while(top) { + pthread_mutex_lock(&mutex); + if(x<100) { + x++; + } + pthread_mutex_unlock(&mutex); + } + return NULL; +} + + +int main(void) { + int top, top2; + + + pthread_t id; + pthread_t id2; + pthread_create(&id, NULL, t_fun, NULL); + pthread_create(&id2, NULL, t_fun, NULL); + pthread_mutex_lock(&mutex); + __goblint_check(x <= 100); + pthread_mutex_unlock(&mutex); + return 0; +} diff --git a/tests/regression/67-interval-sets-two/27-nested2.c b/tests/regression/67-interval-sets-two/27-nested2.c new file mode 100644 index 0000000000..516fcdb381 --- /dev/null +++ b/tests/regression/67-interval-sets-two/27-nested2.c @@ -0,0 +1,17 @@ +// PARAM: --enable ana.int.interval_set --set solver td3 --set sem.int.signed_overflow assume_none +// ALSO: --enable ana.int.interval_set --set solver slr3 --set sem.int.signed_overflow assume_none +// Example from Amato-Scozzari, SAS 2013 +// Localized narrowing should be able to prove that i >= 0 in the outer loop. +#include + +void main() +{ + int i = 0; + while (1) { + int j = 0; + for (; j<10; j++) ; + i=i+11-j; + __goblint_check(i >= 0); + } + return; +} diff --git a/tests/regression/67-interval-sets-two/28-multidimensional_arrays.c b/tests/regression/67-interval-sets-two/28-multidimensional_arrays.c new file mode 100644 index 0000000000..06f1614232 --- /dev/null +++ b/tests/regression/67-interval-sets-two/28-multidimensional_arrays.c @@ -0,0 +1,63 @@ +// PARAM: --enable ana.int.interval_set --set ana.base.arrays.domain partitioned +#include + +int main(void) { + example1(); + example2(); + return 0; +} + +// Two-dimensional array +void example1(void) { + int a[10][10]; + int i=0; + int j=0; + + while(i < 9) { + + j = 0; + + while(j < 10) { + a[i][j] = 42; + j++; + } + + __goblint_check(a[i][0] == 42); + __goblint_check(a[i][9] == 42); + __goblint_check(a[3][9] == 42); // UNKNOWN + + i++; + } + + __goblint_check(a[0][0] == 42); + __goblint_check(a[2][5] == 42); + __goblint_check(a[8][9] == 42); + __goblint_check(a[3][7] == 42); + __goblint_check(a[9][9] == 42); // UNKNOWN + __goblint_check(a[9][2] == 42); // UNKNOWN +} + +// Combines backwards- and forwards-iteration +void example2(void) { + int array[10][10]; + int i = 9; + + while(i >= 0) { + int j =0; + + while(j < 10) { + array[i][j] = 4711; + __goblint_check(array[i-1][j+1] == 4711); //UNKNOWN + j++; + } + + i--; + } + + __goblint_check(array[2][3] == 4711); + __goblint_check(array[0][9] == 4711); + __goblint_check(array[8][5] == 4711); + __goblint_check(array[2][1] == 4711); + __goblint_check(array[0][0] == 4711); + __goblint_check(array[7][5] == 4711); +} diff --git a/tests/regression/67-interval-sets-two/29-def-exc.c b/tests/regression/67-interval-sets-two/29-def-exc.c new file mode 100644 index 0000000000..b26a4cf8fe --- /dev/null +++ b/tests/regression/67-interval-sets-two/29-def-exc.c @@ -0,0 +1,198 @@ +// PARAM: --enable ana.int.def_exc --enable ana.int.interval_set +#include +#define LONGS(x) (((x) + sizeof(unsigned long) - 1)/sizeof(unsigned long)) +#include + +union U1 { + unsigned char f0; + int f1; +}; + + +typedef unsigned long custom_t; +void main() +{ + char yy[256]; + char *ryy_j = &yy[1]; + + int i =0; + + ryy_j++; + ryy_j++; + + while(i < 2) { // Here was an issue with a fixpoint not being reached + ryy_j = ryy_j + 1; + i++; + } + + // The type of ! needs to be IInt + long l; + int r = !l + 4; + + // From dtlk driver example, produces + // "warning: pointer targets in assignment differ in signedness [-Wpointer-sign]" + // in GCC + unsigned char *t; + static char buf[100] = "bliblablubapk\r"; + + t = buf; + t += 2; + + *t = '\r'; + while (*t != '\r') { + t++; + } + + unsigned int v; + unsigned short s1, s2; + + v = v & 0xFFF0FFFF; + v = v << 16; + + v = (unsigned int)(((unsigned int) (s1 ^ s2))); + v = (unsigned int)(((unsigned int) (s1 ^ s2)) << (16)); + + v = (v & 0xFFF0FFFF) | + (((unsigned int) s1 ^ s2) << 16); + + int tmp___1; + int *tmp___2; + + _Bool fclose_fail = (_Bool )(tmp___1 != 0); + + if (! fclose_fail) { + *tmp___2 = 0; + } + + hash_initialize(); + + + + custom_t ci; + void const* b = (void const*) ci; + test((void const *)ci); + + for (i = 0; i < (((20) + (4) - 1)/(4)); i++) { + + } + + + for (i = 0; i < (((20) + (8UL) - 1)/(8UL)); i++) { + + } + + + + + unsigned long v = 8UL; + for (i = 0; i < (((20) + (8UL) - 1)/(v)); i++) { + + } + + i = 0; + if(i < 28UL/v) { + i++; + } + + int gef = 75; + + if(i < 28UL/v) { + i++; + } + + gef = 38; + + i =0; + for(; i < 28UL/v; i++) { + + } + + for (i = 0; i < (((20) + sizeof(unsigned long) - 1)/sizeof(unsigned long)); i++) { + + } + + for (i = 0; i < LONGS(20); i++) { + + } + + int top; + + union U1 g_76; + g_76.f0 = 12; // (f0, (`Definite 12, [0,8])) + if (top) { + g_76.f1 = 5; // (f1, (`Definite 5, [-31,31])) + } + + + unsigned char v; + signed char u; + int r2 = ((int )v == (int )u); + + + signed char l_48 = (signed char )58L; + unsigned char l_67 = (unsigned char)48UL; + + signed char *l_247 ; + unsigned char *l_229 ; + + l_247 = &l_48; + + if (top) { + l_229 = &l_67; + l_247 = (signed char *)l_229; + } + + signed char res = *l_247; + + signed short x; + unsigned int y; + unsigned short z = 0x7ED9L; + + if (((((((signed char)y) == x) && z)))) + { + + } + + return; +} + + +struct hash_table { + custom_t n_buckets ; +}; + +typedef struct hash_table Hash_table; + +Hash_table *hash_initialize() +{ Hash_table *table___0 ; + void *tmp ; + _Bool tmp___0 ; + void *tmp___1 ; + + tmp = malloc(sizeof(*table___0)); + + custom_t n; + table___0 = (Hash_table *)tmp; + table___0->n_buckets = n; + + if (! table___0->n_buckets) { + + goto fail; + } + fail: + + free((void *)table___0); + + return ((Hash_table *)((void *)0)); +} + +int test(void const *ptr) { + if(!ptr) { + __goblint_check(ptr == 0); + int f = 7; + } else { + __goblint_check(ptr == 1); //UNKNOWN! + __goblint_check(ptr != 0); + int f= 38; + } +} diff --git a/tests/regression/67-interval-sets-two/30-no-int-context-option.c b/tests/regression/67-interval-sets-two/30-no-int-context-option.c new file mode 100644 index 0000000000..6801cb908e --- /dev/null +++ b/tests/regression/67-interval-sets-two/30-no-int-context-option.c @@ -0,0 +1,15 @@ +// PARAM: --enable ana.int.interval_set --disable ana.context.widen --enable ana.base.context.int --set annotation.goblint_context.f[+] base.no-int +#include + +int f(int x) { + if (x) + return f(x+1); + else + return x; +} + +int main () { + int a = f(1); + __goblint_check(!a); + return 0; +} diff --git a/tests/regression/67-interval-sets-two/31-ptrdiff.c b/tests/regression/67-interval-sets-two/31-ptrdiff.c new file mode 100644 index 0000000000..d7bd4667df --- /dev/null +++ b/tests/regression/67-interval-sets-two/31-ptrdiff.c @@ -0,0 +1,20 @@ +// PARAM: --enable ana.int.interval_set --set ana.base.arrays.domain partitioned --set ana.activated[+] var_eq +int *tmp; + +int main () +{ + int pathbuf[2]; + + int *bound = pathbuf + sizeof(pathbuf)/sizeof(*pathbuf) - 1; + + int *p = pathbuf; + + while (p <= bound) { + + *p = 1; + + p++; + } + + return 0; +} diff --git a/tests/regression/67-interval-sets-two/32-nested.c b/tests/regression/67-interval-sets-two/32-nested.c new file mode 100644 index 0000000000..bf15ab09da --- /dev/null +++ b/tests/regression/67-interval-sets-two/32-nested.c @@ -0,0 +1,17 @@ +// PARAM: --enable ana.int.interval_set --set solver td3 +// ALSO: --enable ana.int.interval_set --set solver slr3 +// Example from Halbwachs-Henry, SAS 2012 +// Localized widening should be able to prove that i=10 at the end +// of the nested loops. +#include + +void main() +{ + int i = 0; + + for (; i<10 ; i++) { + for (int j = 0; j < 10 ; j++) ; + } + + __goblint_check(i == 10); +} diff --git a/tests/regression/67-interval-sets-two/33-calloc_array.c b/tests/regression/67-interval-sets-two/33-calloc_array.c new file mode 100644 index 0000000000..4982fc7f41 --- /dev/null +++ b/tests/regression/67-interval-sets-two/33-calloc_array.c @@ -0,0 +1,19 @@ +// PARAM: --set ana.int.interval_set true --set ana.base.arrays.domain partitioned + +#include +#include + +int main(void) { + int *r = calloc(5,sizeof(int)); + + __goblint_check(r[0] == 0); + + r[0] = 3; + + __goblint_check(r[0] == 3); //UNKNOWN + + int z = r[1]; + + __goblint_check(z == 0); //UNKNOWN + __goblint_check(z != 365); +} diff --git a/tests/regression/67-interval-sets-two/34-publish-precision.c b/tests/regression/67-interval-sets-two/34-publish-precision.c new file mode 100644 index 0000000000..28dfe64af7 --- /dev/null +++ b/tests/regression/67-interval-sets-two/34-publish-precision.c @@ -0,0 +1,35 @@ +// PARAM: --set ana.int.interval_set true +#include +#include + +int glob1 = 0; +pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER; +pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER; + +void *t_fun(void *arg) { + pthread_mutex_lock(&mutex1); + pthread_mutex_lock(&mutex2); + glob1 = 5; + + pthread_mutex_unlock(&mutex2); + pthread_mutex_lock(&mutex2); + + __goblint_check(glob1 == 5); + glob1 = 0; + + pthread_mutex_unlock(&mutex2); + pthread_mutex_unlock(&mutex1); + return NULL; +} + +int main(void) { + pthread_t id; + __goblint_check(glob1 == 0); + pthread_create(&id, NULL, t_fun, NULL); + pthread_mutex_lock(&mutex2); + __goblint_check(glob1 == 0); // UNKNOWN! + __goblint_check(glob1 == 5); // UNKNOWN! + pthread_mutex_unlock(&mutex2); + pthread_join (id, NULL); + return 0; +} diff --git a/tests/regression/67-interval-sets-two/35-strict-loop-enter.c b/tests/regression/67-interval-sets-two/35-strict-loop-enter.c new file mode 100644 index 0000000000..f51035736e --- /dev/null +++ b/tests/regression/67-interval-sets-two/35-strict-loop-enter.c @@ -0,0 +1,21 @@ +//PARAM: --disable ana.int.def_exc --enable ana.int.interval_set +#include + + +int main() { + int g = 0; + int x; + + for(x=0; x < 50; x++){ + g = 1; + } + // x = [50, 50] after narrow + if(x>50){ // live after widen, but dead after narrow + // node after Pos(x>50) is marked dead at the end + // but the loop is not with x = [51,2147483647] + for(int i=0; i<=0; i--){ + g = 57; + } + __goblint_check(1); // NOWARN (unreachable) + } +} \ No newline at end of file diff --git a/tests/regression/67-interval-sets-two/36-no-eval-on-write-multi.c b/tests/regression/67-interval-sets-two/36-no-eval-on-write-multi.c new file mode 100644 index 0000000000..5d1a6c1627 --- /dev/null +++ b/tests/regression/67-interval-sets-two/36-no-eval-on-write-multi.c @@ -0,0 +1,35 @@ +//PARAM: --enable ana.int.interval_set --enable ana.int.enums --ana.base.privatization "write" -v +#include +#include + +// Test case that shows how avoiding reading integral globals can reduce the number of solver evaluations. +// Avoiding to evaluate integral globals when setting them reduced the number of necessary evaluations from 62 to 20 in this test case. + +pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; +int glob = 10; + +void* t_fun(void* ptr) { + pthread_mutex_lock(&mutex); + glob = 3; + glob = 4; + glob = 1; + pthread_mutex_unlock(&mutex); + return NULL; +} + +void bar() { + glob = 2; +} + +int main() { + pthread_t t; + + pthread_create(&t, NULL, t_fun, NULL); + pthread_mutex_lock(&mutex); + bar(); + pthread_mutex_unlock(&mutex); + pthread_join(t, NULL); + __goblint_check(glob >= 1); + __goblint_check(glob <= 10); + return 0; +} diff --git a/tests/regression/67-interval-sets-two/37-int-context-attribute.c b/tests/regression/67-interval-sets-two/37-int-context-attribute.c new file mode 100644 index 0000000000..3f51c839af --- /dev/null +++ b/tests/regression/67-interval-sets-two/37-int-context-attribute.c @@ -0,0 +1,16 @@ +// PARAM: --enable ana.int.interval_set --disable ana.context.widen --disable ana.base.context.int +#include + +int f(int x) __attribute__((goblint_context("base.int"))); // attributes are not permitted in a function definition +int f(int x) { + if (x) + return x * f(x - 1); + else + return 1; +} + +int main () { + int a = f(10); + __goblint_check(a == 3628800); + return 0; +} diff --git a/tests/regression/67-interval-sets-two/38-simple.c b/tests/regression/67-interval-sets-two/38-simple.c new file mode 100644 index 0000000000..3e254841f7 --- /dev/null +++ b/tests/regression/67-interval-sets-two/38-simple.c @@ -0,0 +1,33 @@ +// PARAM: --enable ana.int.interval_set --disable ana.int.def_exc --set ana.base.arrays.domain partitioned +#include + +int main(void) +{ + int a[40]; + int n = 30; + int m = 20; + + // Check one-dimensional first + int b[n]; + b[29] = 5; + __goblint_check(b[29] = 5); + + int c[n+4]; + c[31] = 2; + __goblint_check(c[31] = 2); + + // Two dimensional, one variable, first + int d[n][30]; + d[2][2] = 42; + __goblint_check(d[2][2] == 42); + + // Two dimensional, one variable, last + int e[20][n]; + e[2][2] = 42; + __goblint_check(e[2][2] == 42); + + // Two dimensional, two variable + int f[m][n]; + f[2][2] = 42; + __goblint_check(f[2][2] == 42); +} diff --git a/tests/regression/67-interval-sets-two/39-div.c b/tests/regression/67-interval-sets-two/39-div.c new file mode 100644 index 0000000000..b3b9f23d4a --- /dev/null +++ b/tests/regression/67-interval-sets-two/39-div.c @@ -0,0 +1,50 @@ +// PARAM: --enable ana.int.def_exc --enable ana.int.interval_set +#include + +int main(void) { + int i = 1; + int v = 8; + + int r = 28/v; + + while(1) { + int zgg = 17; + if (! (i < 28 / v)) { + zgg = 5; + goto while_beak; + } + + int z = 3; + i ++; + z = 7; + } + + + + + + // while(i < 28/v) { + // int z = 3; + // i++; + // z = 7; + // } + while_beak: + __goblint_check(i == 3); + + + + int a = 5; + int b = 7; + + int r = 0; + + if (a == b) { + // Dead + r = 1; + } else { + r = 2; + } + + __goblint_check(r == 1); //FAIL + __goblint_check(r == 2); +} diff --git a/tests/regression/67-interval-sets-two/40-off-attribute.c b/tests/regression/67-interval-sets-two/40-off-attribute.c new file mode 100644 index 0000000000..43236bd624 --- /dev/null +++ b/tests/regression/67-interval-sets-two/40-off-attribute.c @@ -0,0 +1,16 @@ +// PARAM: --enable ana.int.interval_set --enable ana.context.widen +#include + +int f(int x) __attribute__((goblint_context("no-widen"))); // attributes are not permitted in a function definition +int f(int x) { + if (x) + return x * f(x - 1); + else + return 1; +} + +int main () { + int a = f(10); + __goblint_check(a == 3628800); + return 0; +} diff --git a/tests/regression/67-interval-sets-two/41-interval-branching.c b/tests/regression/67-interval-sets-two/41-interval-branching.c new file mode 100644 index 0000000000..bcb5e088b1 --- /dev/null +++ b/tests/regression/67-interval-sets-two/41-interval-branching.c @@ -0,0 +1,12 @@ +// PARAM: --enable ana.int.interval_set --disable ana.int.def_exc +#include +#include +int main(){ + int i; + if(i<0){ + __goblint_check(i<0); + } else { + __goblint_check(i>=0); + } + return 0; +} diff --git a/tests/regression/67-interval-sets-two/43-first-reads.c b/tests/regression/67-interval-sets-two/43-first-reads.c new file mode 100644 index 0000000000..852b76a509 --- /dev/null +++ b/tests/regression/67-interval-sets-two/43-first-reads.c @@ -0,0 +1,37 @@ +// PARAM: --set ana.int.interval_set true +extern int __VERIFIER_nondet_int(); + +#include +#include + +int glob1 = 0; +pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER; + +void *t_fun(void *arg) { + int t = __VERIFIER_nondet_int(); + pthread_mutex_lock(&mutex1); + if(t == 42) { + glob1 = 1; + } + t = glob1; + + __goblint_check(t == 0); //UNKNOWN! + + __goblint_check(t == 1); //UNKNOWN! + + glob1 = 0; + + pthread_mutex_unlock(&mutex1); + return NULL; +} + +int main(void) { + pthread_t id; + __goblint_check(glob1 == 0); + pthread_create(&id, NULL, t_fun, NULL); + pthread_mutex_lock(&mutex1); + __goblint_check(glob1 == 0); + pthread_mutex_unlock(&mutex1); + pthread_join (id, NULL); + return 0; +} diff --git a/tests/regression/67-interval-sets-two/44-comparision-bot.c b/tests/regression/67-interval-sets-two/44-comparision-bot.c new file mode 100644 index 0000000000..5b3622828a --- /dev/null +++ b/tests/regression/67-interval-sets-two/44-comparision-bot.c @@ -0,0 +1,10 @@ +// PARAM: --enable ana.int.interval_set --enable ana.int.def_exc +#include +int main(){ + int a = 0; + unsigned int b = (unsigned int) a - 256U; + if ((unsigned int) a - 256U <= 511U) { + a += 4; + } + printf("%u\n", (unsigned int) a - 256U); +} diff --git a/tests/regression/67-interval-sets-two/45-intervals-branching-meet.c b/tests/regression/67-interval-sets-two/45-intervals-branching-meet.c new file mode 100644 index 0000000000..7da8125bb1 --- /dev/null +++ b/tests/regression/67-interval-sets-two/45-intervals-branching-meet.c @@ -0,0 +1,42 @@ +// PARAM: --enable ana.int.interval_set --set ana.base.structs.domain "sets" + +#include + +struct Pair { + int first; + int second; +}; + +void example1() { + int a; + int b; + + struct Pair pair; + + if (a) { + pair.first = 10; + pair.second = 20; + } else { + pair.first = 20; + pair.second = 30; + } + + if (pair.first == 15) { + // This should be unreachable! + b = 0; // This line is not dead if we --disable ana.base.structs.meet-condition + } else if (pair.first == 10) { + __goblint_check(pair.second == 20); + b = 1; + } else if (pair.first == 20) { + __goblint_check(pair.second == 30); + b = 1; + } + __goblint_check(b == 1); +} + + +int main() { + example1(); + + return 0; +} diff --git a/tests/regression/67-interval-sets-two/46-nesting_arrays.c b/tests/regression/67-interval-sets-two/46-nesting_arrays.c new file mode 100644 index 0000000000..be4a3d996e --- /dev/null +++ b/tests/regression/67-interval-sets-two/46-nesting_arrays.c @@ -0,0 +1,200 @@ +// PARAM: --enable ana.int.interval_set --set ana.base.arrays.domain partitioned +#include + +struct kala { + int i; + int a[5]; +}; + +struct kalaw { + int* a; +}; + +struct kass { + int v; +}; + +union uArray { + int a[5]; + int b[5]; +}; + +union uStruct { + int b; + struct kala k; +}; + +int main(void) { + example1(); + example2(); + example3(); + example4(); + example5(); + example6(); + example7(); + example8(); + return 0; +} + +void example1() { + struct kala l; + int i = 0; + int top; + + while (i < 5) { + l.a[i] = 42; + i++; + + // Check assertion that should only hold later does not already hold here + __goblint_check(l.a[4] == 42); //UNKNOWN + } + + // Check the array is correctly initialized + __goblint_check(l.a[1] == 42); + __goblint_check(l.a[2] == 42); + __goblint_check(l.a[3] == 42); + __goblint_check(l.a[4] == 42); + + // Destructively assign to i + i = top; + + // Check the array is still known to be completly initialized + __goblint_check(l.a[1] == 42); + __goblint_check(l.a[2] == 42); + __goblint_check(l.a[3] == 42); + __goblint_check(l.a[4] == 42); +} + +void example2() { + struct kala kalas[5]; + + int i2 = 0; + + while (i2 < 4) { + int j2 = 0; + while (j2 < 5) { + kalas[i2].a[j2] = 8; + j2++; + } + i2++; + } + + // Initialization has not proceeded this far + __goblint_check(kalas[4].a[0] == 8); //UNKNOWN + __goblint_check(kalas[0].a[0] == 8); +} + +void example3() { + struct kala xnn; + for(int l=0; l < 5; l++) { + xnn.a[l] = 42; + } + + __goblint_check(xnn.a[3] == 42); +} + +void example4() { + struct kala xn; + + struct kala xs[5]; + + for(int j=0; j < 4; j++) { + xs[j] = xn; + for(int k=0; k < 5; k++) { + xs[j].a[k] = 7; + } + } + + __goblint_check(xs[3].a[0] == 7); +} + +void example5() { + // This example is a bit contrived to show that splitting and moving works for + // unions + union uArray ua; + int i3 = 0; + int top; + int *i = ⊤ + + ua.a[*i] = 1; + + while (i3 < 5) { + ua.a[i3] = 42; + i3++; + } + + __goblint_check(ua.a[i3 - 1] == 42); + + ua.b[0] = 3; + __goblint_check(ua.b[0] == 3); + + // ------------------------------- + union uStruct us; + int i4 = 0; + + us.b = 4; + us.k.a[i4] = 0; + __goblint_check(us.b == 4); // UNKNOWN + __goblint_check(us.k.a[0] == 0); + __goblint_check(us.k.a[3] == 0); // UNKNOWN + + while (i4 < 5) { + us.k.a[i4] = 42; + i4++; + } + + __goblint_check(us.k.a[1] == 42); + __goblint_check(us.k.a[0] == 0); // FAIL +} + +void example6() { + int a[42]; + int i = 0; + + struct kass k; + k.v = 7; + + while(i < 42) { + a[i] = 0; + i++; + } + + i = 0; + + a[k.v] = 2; + k.v = k.v+1; + + __goblint_check(a[k.v] != 3); +} + +void example7() { + // Has no asserts, just checks this doesn't cause an infinite loop + int a[42]; + int i = 0; + + while(i < 40) { + a[i] = 0; + i++; + } + + a[a[0]] = 2; +} + +// Test correct behavior with more involved expression in subscript operator +void example8() { + int a[42]; + union uArray ua; + + ua.a[0] = 0; + ua.a[1] = 0; + ua.a[2] = 0; + ua.a[3] = 0; + ua.a[4] = 0; + + int i = 0; + int *ip = &i; + + a[ua.a[*ip]] = 42; + ip++; + __goblint_check(a[ua.a[*ip]] == 42); //UNKNOWN +} diff --git a/tests/regression/67-interval-sets-two/47-non-zero-performance.c b/tests/regression/67-interval-sets-two/47-non-zero-performance.c new file mode 100644 index 0000000000..322977461a --- /dev/null +++ b/tests/regression/67-interval-sets-two/47-non-zero-performance.c @@ -0,0 +1,26 @@ +// PARAM: --enable ana.int.interval_set --set ana.base.arrays.domain partitioned --enable exp.fast_global_inits +#include + +int global_array[10000] = {9, 0, 3, 42, 11 }; // All non-specified ones will be zero +int global_array_multi[2][10000] = {{9, 0, 3, 42, 11}, {9, 0, 3, 42, 11}}; // All non-specified ones will be zero + +int main(void) { + __goblint_check(global_array[0] == 9); //UNKNOWN + __goblint_check(global_array[1] == 0); //UNKNOWN + __goblint_check(global_array[2] == 3); //UNKNOWN + __goblint_check(global_array[3] == 42); //UNKNOWN + __goblint_check(global_array[4] == 11); //UNKNOWN + + __goblint_check(global_array_multi[0][0] == 9); //UNKNOWN + __goblint_check(global_array_multi[0][1] == 0); //UNKNOWN + __goblint_check(global_array_multi[0][2] == 3); //UNKNOWN + __goblint_check(global_array_multi[0][3] == 42); //UNKNOWN + __goblint_check(global_array_multi[0][4] == 11); //UNKNOWN + + + __goblint_check(global_array_multi[1][0] == 9); //UNKNOWN + __goblint_check(global_array_multi[1][1] == 0); //UNKNOWN + __goblint_check(global_array_multi[1][2] == 3); //UNKNOWN + __goblint_check(global_array_multi[1][3] == 42); //UNKNOWN + __goblint_check(global_array_multi[1][4] == 11); //UNKNOWN +} diff --git a/tests/regression/67-interval-sets-two/48-calloc_struct_array.c b/tests/regression/67-interval-sets-two/48-calloc_struct_array.c new file mode 100644 index 0000000000..30606fdca5 --- /dev/null +++ b/tests/regression/67-interval-sets-two/48-calloc_struct_array.c @@ -0,0 +1,20 @@ +// PARAM: --set ana.int.interval_set true --set ana.base.arrays.domain partitioned + +#include +#include + +struct h { + int a[2]; + int b; +}; + +int main(void) { + struct h *m = calloc(3,sizeof(int)); + + struct h *a = calloc(10,sizeof(struct h)); + + struct h *b = a+1; + a->a[1] = 3; + int* ptr = &(b->a[1]); + __goblint_check(*ptr == 3); //UNKNOWN +} diff --git a/tests/regression/67-interval-sets-two/49-threshold.c b/tests/regression/67-interval-sets-two/49-threshold.c new file mode 100644 index 0000000000..8ea0730b59 --- /dev/null +++ b/tests/regression/67-interval-sets-two/49-threshold.c @@ -0,0 +1,34 @@ +// PARAM: --enable ana.int.interval_set --enable ana.int.interval_threshold_widening + +#include +#include + +int g; + +pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER; + +void* fun(void* arg) { + pthread_mutex_lock(&m); + if(g < 100) { + g++; + }; + pthread_mutex_unlock(&m); +} + + +int main() { + pthread_t t1; + pthread_create(&t1, 0, fun, 0); + + pthread_mutex_lock(&m); + if(g < 100) { + g++; + }; + pthread_mutex_unlock(&m); + + pthread_join(t1, 0); + + __goblint_check(g <= 100); + + return 0; +} diff --git a/tests/regression/67-interval-sets-two/50-interval.c b/tests/regression/67-interval-sets-two/50-interval.c new file mode 100644 index 0000000000..cef177e1aa --- /dev/null +++ b/tests/regression/67-interval-sets-two/50-interval.c @@ -0,0 +1,72 @@ +// PARAM: --set ana.int.interval_set true +#include +#include + + +int main () { + int a = 1,b = 2,c = 3; + int x,y,z; + int w; + int false = 0; + int true = 42; + + if (x){ + __goblint_check(x != 0); + } else { + __goblint_check(x == 0); + } + + __goblint_check(!! true); + __goblint_check(! false); + + if (a){ + a = a; + } else + __goblint_check(0); // NOWARN + + + if (!a) + __goblint_check(0); // NOWARN + else + a = a; + + if (z != 0){ + a = 8; + b = 9; + } else { + a = 9; + b = 8; + } + + __goblint_check(a); + __goblint_check(a!=b); //UNKNOWN + __goblint_check(a<10); + __goblint_check(a<=9); + __goblint_check(!(a<8)); + __goblint_check(a==8); //UNKNOWN + __goblint_check(b>7); + __goblint_check(b>=8); + __goblint_check(!(a>9)); + __goblint_check(b==8); //UNKNOWN + + for(x = 0; x < 10; x++){ + __goblint_check(x >= 0); + __goblint_check(x <= 9); + } + __goblint_check(x == 10); + + if (0 <= w) + { + } + else + { + return 0; + } + + if (w > 0) + { + __goblint_check(1); + } + + return 0; +} diff --git a/tests/regression/67-interval-sets-two/52-no-eval-on-write.c b/tests/regression/67-interval-sets-two/52-no-eval-on-write.c new file mode 100644 index 0000000000..ad6cb038a4 --- /dev/null +++ b/tests/regression/67-interval-sets-two/52-no-eval-on-write.c @@ -0,0 +1,24 @@ +//PARAM: --enable ana.int.interval_set --enable exp.earlyglobs --enable ana.int.enums -v +#include + +// Test case that shows how avoiding reading integral globals can reduce the number of solver evaluations. +// Avoiding to evaluate integral globals when setting them reduced the number of necessary evaluations from 27 to 14 in this test case. +int glob = 10; + +void foo() { + glob = 3; + glob = 4; + glob = 1; +} + +void bar() { + glob = 2; +} + +int main() { + foo(); + bar(); + __goblint_check(glob >= 1); + __goblint_check(glob <= 10); + return 0; +} diff --git a/tests/regression/67-interval-sets-two/53-pointer-to-arrays-of-different-sizes.c b/tests/regression/67-interval-sets-two/53-pointer-to-arrays-of-different-sizes.c new file mode 100644 index 0000000000..9779efdd52 --- /dev/null +++ b/tests/regression/67-interval-sets-two/53-pointer-to-arrays-of-different-sizes.c @@ -0,0 +1,28 @@ +// PARAM: --enable ana.arrayoob --enable ana.int.interval_set --enable ana.int.enums +//Pointer pointing to arrays of different sizes +#include +int main( ) +{ + int arr[] = {1, 2, 3, 4, 5, 6}; + int arr2[] = {1, 2, 3}; + int * ptr = arr; + + ptr[3] = 4; //NOWARN + ptr[6] = 10; //WARN + ptr[-1] = 10; //WARN + + for (int i = 0; i < 10; ++i) + { + ptr[i] = 5; //WARN + } + int * ptr2 = arr2; + ptr2[1] = 3; //NOWARN + ptr2[3] = 10; //WARN + ptr2[-1] = 10; //WARN + for (int i = 0; i < 5; ++i) + { + ptr2[i] = 5; //WARN + } + return 0; +} + diff --git a/tests/regression/67-interval-sets-two/54-simple_array_in_loops.c b/tests/regression/67-interval-sets-two/54-simple_array_in_loops.c new file mode 100644 index 0000000000..1a52963233 --- /dev/null +++ b/tests/regression/67-interval-sets-two/54-simple_array_in_loops.c @@ -0,0 +1,50 @@ +// PARAM: --enable ana.int.interval_set --set ana.base.arrays.domain unroll --set ana.base.arrays.unrolling-factor 2 +#include +int global; + +int main(void) +{ + example1(); + example2(); + return 0; +} + +// Simple example +void example1(void) +{ + int a[42]; + int i = 0; + int top; + + while (i < 42) { + a[i] = 0; + __goblint_check(a[i] == 0); // UNKNOWN + __goblint_check(a[0] == 0); // UNKNOWN + __goblint_check(a[17] == 0); + i++; + } + + __goblint_check(a[0] == 0); // UNKNOWN + __goblint_check(a[7] == 0); + __goblint_check(a[41] == 0); +} + + +// Check that arrays of types different from int are handeled correctly +void example2() { + // no char because char has unknown signedness (particularly, unsigned on arm64) + signed char a[10]; + int n; + __goblint_check(a[3] == 800); // FAIL (char cannot be 800) + __goblint_check(a[3] == 127); // UNKNOWN! + + for(int i=0;i < 10; i++) { + a[i] = 7; + } + + a[3] = (signed char) n; + __goblint_check(a[3] == 800); //FAIL + __goblint_check(a[3] == 127); //UNKNOWN + __goblint_check(a[3] == -128); //UNKNOWN + __goblint_check(a[3] == -129); //FAIL +} diff --git a/tests/regression/67-interval-sets-two/56-interval-set-dead-code.c b/tests/regression/67-interval-sets-two/56-interval-set-dead-code.c new file mode 100644 index 0000000000..e1345155d9 --- /dev/null +++ b/tests/regression/67-interval-sets-two/56-interval-set-dead-code.c @@ -0,0 +1,18 @@ +// PARAM: --enable ana.int.interval_set +#include +#include + +int main() { + + int i; + + if (i > 5 && i < 10) { + i = 1; + } + if (i == 7) { + i = INT_MAX; + i += 1; + } + + return 0; +} \ No newline at end of file diff --git a/tests/regression/67-interval-sets-two/57-interval-set-wrap-around.c b/tests/regression/67-interval-sets-two/57-interval-set-wrap-around.c new file mode 100644 index 0000000000..e711030f08 --- /dev/null +++ b/tests/regression/67-interval-sets-two/57-interval-set-wrap-around.c @@ -0,0 +1,18 @@ +// PARAM: --enable ana.int.interval_set --set sem.int.signed_overflow assume_wraparound +#include +#include + +int main() { + + char i; + + if (i < 126) { + i = 126; + } + + i++; + + __goblint_check(i != 0); + + return 0; +} \ No newline at end of file diff --git a/tests/regression/67-interval-sets-two/58-interval-set-dead-code-with-fun-call.c b/tests/regression/67-interval-sets-two/58-interval-set-dead-code-with-fun-call.c new file mode 100644 index 0000000000..b63ebd6fab --- /dev/null +++ b/tests/regression/67-interval-sets-two/58-interval-set-dead-code-with-fun-call.c @@ -0,0 +1,27 @@ +// PARAM: --enable ana.int.interval_set +#include +#include + +int sum(int* a, int n) { + int sum = 0; + for (int i = 0; i < n; i++) { + sum += a[i]; + } + return sum + 2 * INT_MAX; +} + +int main() { + int n; + int a[n]; + int x; + + if (x > 1 && x < 11) { + x -= 2; + } + + if (x == 9) { + x = sum(a, n); + } + + return x == 42 ? 1 : 0; +} diff --git a/tests/regression/68-longjmp/01-setjmp-return.c b/tests/regression/68-longjmp/01-setjmp-return.c new file mode 100644 index 0000000000..3a3eb3137c --- /dev/null +++ b/tests/regression/68-longjmp/01-setjmp-return.c @@ -0,0 +1,31 @@ +// PARAM: --enable ana.int.interval --enable ana.int.enums --set solvers.td3.side_widen never +#include +#include +#include + +int main(void) +{ + jmp_buf jmp_buf; + + __goblint_check(1); + switch (setjmp(jmp_buf)) + { + case 0: + __goblint_check(1); + longjmp(jmp_buf, 1); + __goblint_check(0); // NOWARN + break; + case 1: + __goblint_check(1); + break; + case 2: + __goblint_check(0); // NOWARN + break; + default: + __goblint_check(0); // NOWARN + break; + } + __goblint_check(1); + + return 0; +} diff --git a/tests/regression/68-longjmp/02-setjmp-global.c b/tests/regression/68-longjmp/02-setjmp-global.c new file mode 100644 index 0000000000..c5e256df9a --- /dev/null +++ b/tests/regression/68-longjmp/02-setjmp-global.c @@ -0,0 +1,34 @@ +// PARAM: --enable ana.int.interval --enable ana.int.enums --set solvers.td3.side_widen never +#include +#include + +int global = 0; +jmp_buf jmp_buffer; + +int main(void) +{ + + __goblint_check(1); + setjmp(jmp_buffer); + switch (global) + { + case 0: + __goblint_check(1); + global = 1; + longjmp(jmp_buffer, 1); + __goblint_check(0); // NOWARN + break; + case 1: + __goblint_check(1); + break; + case 2: + __goblint_check(0); // NOWARN + break; + default: + __goblint_check(0); // NOWARN + break; + } + __goblint_check(1); + + return 0; +} diff --git a/tests/regression/68-longjmp/03-setjmp-local.c b/tests/regression/68-longjmp/03-setjmp-local.c new file mode 100644 index 0000000000..8cc9676b55 --- /dev/null +++ b/tests/regression/68-longjmp/03-setjmp-local.c @@ -0,0 +1,34 @@ +// PARAM: --enable ana.int.interval --enable ana.int.enums --set solvers.td3.side_widen never --disable exp.volatiles_are_top +#include +#include + + +int main(void) +{ + jmp_buf jmp_buf; + volatile int local = 0; + + __goblint_check(1); + setjmp(jmp_buf); + switch (local) + { + case 0: + __goblint_check(1); + local = 1; + longjmp(jmp_buf, 1); + __goblint_check(0); // NOWARN + break; + case 1: + __goblint_check(1); + break; + case 2: + __goblint_check(0); // NOWARN + break; + default: + __goblint_check(0); // NOWARN + break; + } + __goblint_check(1); + + return 0; +} diff --git a/tests/regression/68-longjmp/04-counting-local.c b/tests/regression/68-longjmp/04-counting-local.c new file mode 100644 index 0000000000..691985a61a --- /dev/null +++ b/tests/regression/68-longjmp/04-counting-local.c @@ -0,0 +1,25 @@ +// PARAM: --enable ana.int.interval --enable ana.int.enums --disable exp.volatiles_are_top --set solvers.td3.side_widen never +#include +#include + +jmp_buf my_jump_buffer; + +void foo(int count) +{ + __goblint_check(count >= 0 && count <= 5); + longjmp(my_jump_buffer, 1); + __goblint_check(0); // NOWARN +} + +int main(void) +{ + volatile int count = 0; + setjmp(my_jump_buffer); + __goblint_check(count == 0); // UNKNOWN! + if (count < 5) { + count++; + foo(count); + __goblint_check(0); // NOWARN + } + __goblint_check(count == 5); +} diff --git a/tests/regression/68-longjmp/05-heap-counting-return-one-method.c b/tests/regression/68-longjmp/05-heap-counting-return-one-method.c new file mode 100644 index 0000000000..1b40c3c31d --- /dev/null +++ b/tests/regression/68-longjmp/05-heap-counting-return-one-method.c @@ -0,0 +1,18 @@ +// PARAM: --enable ana.int.interval --enable ana.int.enums --set solvers.td3.side_widen never --enable exp.earlyglobs --set ana.setjmp.split none --disable exp.volatiles_are_top +#include +#include +#include + +int main(void) +{ + jmp_buf* my_jump_buffer = malloc(sizeof(jmp_buf)); + + volatile int count = setjmp(*my_jump_buffer); + __goblint_check(count == 0); // UNKNOWN! + if (count < 5) { + __goblint_check(count >= 0 & count < 5); + longjmp(*my_jump_buffer, count + 1); + __goblint_check(0); // NOWARN + } + __goblint_check(count == 5); +} diff --git a/tests/regression/68-longjmp/06-sigsetjmp-return.c b/tests/regression/68-longjmp/06-sigsetjmp-return.c new file mode 100644 index 0000000000..33e5253a3a --- /dev/null +++ b/tests/regression/68-longjmp/06-sigsetjmp-return.c @@ -0,0 +1,30 @@ +// PARAM: --enable ana.int.interval --enable ana.int.enums --set solvers.td3.side_widen never +#include +#include + +int main(void) +{ + jmp_buf jmp_buf; + + __goblint_check(1); + switch (sigsetjmp(jmp_buf,0)) + { + case 0: + __goblint_check(1); + longjmp(jmp_buf, 1); + __goblint_check(0); // NOWARN + break; + case 1: + __goblint_check(1); + break; + case 2: + __goblint_check(0); // NOWARN + break; + default: + __goblint_check(0); // NOWARN + break; + } + __goblint_check(1); + + return 0; +} diff --git a/tests/regression/68-longjmp/07-returns.c b/tests/regression/68-longjmp/07-returns.c new file mode 100644 index 0000000000..b962662f34 --- /dev/null +++ b/tests/regression/68-longjmp/07-returns.c @@ -0,0 +1,28 @@ +// PARAM: --enable ana.int.interval --enable ana.int.enums --disable exp.volatiles_are_top --set solvers.td3.side_widen never +#include +#include + +jmp_buf my_jump_buffer; + +int foo(int count) +{ + __goblint_check(count >= 0 && count <= 5); + longjmp(my_jump_buffer, -10); + __goblint_check(0); // NOWARN + return 8; +} + +int main(void) +{ + volatile int count = 0; + int x = setjmp(my_jump_buffer); + if( x == -10) { + __goblint_check(1); + return -1; + } + if( x > 0) { + __goblint_check(0); //NOWARN + } + + foo(count); +} diff --git a/tests/regression/68-longjmp/11-counting-return.c b/tests/regression/68-longjmp/11-counting-return.c new file mode 100644 index 0000000000..682b402e01 --- /dev/null +++ b/tests/regression/68-longjmp/11-counting-return.c @@ -0,0 +1,23 @@ +// PARAM: --enable ana.int.interval --enable ana.int.enums --set solvers.td3.side_widen never --set ana.setjmp.split none +#include +#include + +jmp_buf my_jump_buffer; + +void foo(int count) +{ + __goblint_check(count >= 0 && count < 5); + longjmp(my_jump_buffer, count + 1); + __goblint_check(0); // NOWARN +} + +int main(void) +{ + int count = setjmp(my_jump_buffer); + __goblint_check(count == 0); // UNKNOWN! + if (count < 5) { + foo(count); + __goblint_check(0); // NOWARN + } + __goblint_check(count == 5); +} diff --git a/tests/regression/68-longjmp/12-counting-global.c b/tests/regression/68-longjmp/12-counting-global.c new file mode 100644 index 0000000000..8b8847870f --- /dev/null +++ b/tests/regression/68-longjmp/12-counting-global.c @@ -0,0 +1,25 @@ +//PARAM: --enable ana.int.interval --enable ana.int.enums --set solvers.td3.side_widen never +#include +#include + +jmp_buf my_jump_buffer; +int count = 0; + +void foo() +{ + __goblint_check(count >= 0 && count < 5); + count++; + longjmp(my_jump_buffer, 1); + __goblint_check(0); // NOWARN +} + +int main(void) +{ + setjmp(my_jump_buffer); + __goblint_check(count == 0); // UNKNOWN! + if (count < 5) { + foo(); + __goblint_check(0); // NOWARN + } + __goblint_check(count == 5); +} diff --git a/tests/regression/68-longjmp/13-counting-local.c b/tests/regression/68-longjmp/13-counting-local.c new file mode 100644 index 0000000000..4751138286 --- /dev/null +++ b/tests/regression/68-longjmp/13-counting-local.c @@ -0,0 +1,25 @@ +// PARAM: --enable ana.int.interval --enable ana.int.enums --set solvers.td3.side_widen never --disable exp.volatiles_are_top +#include +#include + +jmp_buf my_jump_buffer; + +void foo(int count) +{ + __goblint_check(count >= 0 && count <= 5); + longjmp(my_jump_buffer, 1); + __goblint_check(0); // NOWARN +} + +int main(void) +{ + volatile int count = 0; + setjmp(my_jump_buffer); + __goblint_check(count == 0); // UNKNOWN! + if (count < 5) { + count++; + foo(count); + __goblint_check(0); // NOWARN + } + __goblint_check(count == 5); +} diff --git a/tests/regression/68-longjmp/14-counting-return-one-method.c b/tests/regression/68-longjmp/14-counting-return-one-method.c new file mode 100644 index 0000000000..651c9f85e1 --- /dev/null +++ b/tests/regression/68-longjmp/14-counting-return-one-method.c @@ -0,0 +1,17 @@ +// PARAM: --enable ana.int.interval --enable ana.int.enums --set solvers.td3.side_widen never --set ana.setjmp.split none +#include +#include + +jmp_buf my_jump_buffer; + +int main(void) +{ + int count = setjmp(my_jump_buffer); + __goblint_check(count == 0); // UNKNOWN! + if (count < 5) { + __goblint_check(count >= 0 && count < 5); + longjmp(my_jump_buffer, count + 1); + __goblint_check(0); // NOWARN + } + __goblint_check(count == 5); +} diff --git a/tests/regression/68-longjmp/15-counting-global-one-method.c b/tests/regression/68-longjmp/15-counting-global-one-method.c new file mode 100644 index 0000000000..7b476afc15 --- /dev/null +++ b/tests/regression/68-longjmp/15-counting-global-one-method.c @@ -0,0 +1,19 @@ +//PARAM: --enable ana.int.interval --enable ana.int.enums --set solvers.td3.side_widen never +#include +#include + +jmp_buf my_jump_buffer; +int count = 0; + +int main(void) +{ + setjmp(my_jump_buffer); + __goblint_check(count == 0); // UNKNOWN! + if (count < 5) { + __goblint_check(count >= 0 && count < 5); + count++; + longjmp(my_jump_buffer, 1); + __goblint_check(0); // NOWARN + } + __goblint_check(count == 5); +} diff --git a/tests/regression/68-longjmp/16-counting-local-one-method.c b/tests/regression/68-longjmp/16-counting-local-one-method.c new file mode 100644 index 0000000000..51ca3b8ab0 --- /dev/null +++ b/tests/regression/68-longjmp/16-counting-local-one-method.c @@ -0,0 +1,19 @@ +//PARAM: --enable ana.int.interval --enable ana.int.enums --set solvers.td3.side_widen never --disable exp.volatiles_are_top +#include +#include + +jmp_buf my_jump_buffer; + +int main(void) +{ + volatile int count = 0; + setjmp(my_jump_buffer); + __goblint_check(count == 0); // UNKNOWN! + if (count < 5) { + count++; + __goblint_check(count >= 0 && count <= 5); + longjmp(my_jump_buffer, 1); + __goblint_check(0); // NOWARN + } + __goblint_check(count == 5); +} diff --git a/tests/regression/68-longjmp/17-loop.c b/tests/regression/68-longjmp/17-loop.c new file mode 100644 index 0000000000..a0d7836f4c --- /dev/null +++ b/tests/regression/68-longjmp/17-loop.c @@ -0,0 +1,30 @@ +//PARAM: --set ana.setjmp.split coarse +#include +#include +#include +#include + +void jmpfunction(jmp_buf env_buf) { + longjmp(env_buf, 2); +} + +int main () { + int val; + jmp_buf env_buffer; + + /* save calling environment for longjmp */ + val = setjmp( env_buffer ); // NOWARN + + if( val != 0 ) { // NOWARN + printf("Returned from a longjmp() with value = %i\n", val); + longjmp(env_buffer, val+1); + exit(0); + } + + printf("Jump function call\n"); + jmpfunction( env_buffer ); + + __goblint_check(0); // NOWARN + + return(0); +} diff --git a/tests/regression/68-longjmp/18-simple-else.c b/tests/regression/68-longjmp/18-simple-else.c new file mode 100644 index 0000000000..af1fdb97ad --- /dev/null +++ b/tests/regression/68-longjmp/18-simple-else.c @@ -0,0 +1,40 @@ +// PARAM: --enable ana.int.interval --enable ana.int.enums --set ana.setjmp.split none +#include +#include +#include +#include + +jmp_buf env_buffer; +int global = 0; + +int fun() { + global = 2; + longjmp(env_buffer, 2); +} + + +int main () { + int val; + jmp_buf env_buffer2; + + __goblint_check(global == 0); + + /* save calling environment for longjmp */ + val = setjmp( env_buffer ); + + __goblint_check(global == 0); //UNKNOWN! + __goblint_check(global == 2); //UNKNOWN! + __goblint_check(global == 8); //FAIL! + + if( val != 0 ) { + printf("Returned from a longjmp() with value = %i\n", val); + __goblint_check(val == 2); + __goblint_check(global == 2); //TODO (requires path-sensitivity distinguishing between returns) -> see test 44 + exit(0); + } + + fun(); + + __goblint_check(0); // NOWARN + return(0); +} diff --git a/tests/regression/68-longjmp/19-simpler.c b/tests/regression/68-longjmp/19-simpler.c new file mode 100644 index 0000000000..b928ce08d0 --- /dev/null +++ b/tests/regression/68-longjmp/19-simpler.c @@ -0,0 +1,24 @@ +// PARAM: --enable ana.int.interval +#include +#include +#include +#include + +jmp_buf env_buffer; +int global = 0; + +int fun() { + longjmp(env_buffer, 2); +} + + +int main () { + int val; + + __goblint_check(global == 0); + setjmp( env_buffer ); + fun(); + + __goblint_check(0); // NOWARN + return(0); +} diff --git a/tests/regression/68-longjmp/20-simpler-multifun.c b/tests/regression/68-longjmp/20-simpler-multifun.c new file mode 100644 index 0000000000..0213cd015d --- /dev/null +++ b/tests/regression/68-longjmp/20-simpler-multifun.c @@ -0,0 +1,30 @@ +// PARAM: --enable ana.int.interval +#include +#include +#include +#include + +jmp_buf env_buffer; +int global = 0; + +void foo() { + int local = 7; + longjmp(env_buffer, 2); +} + + +int fun() { + foo(); +} + + +int main () { + int val; + + __goblint_check(global == 0); + setjmp( env_buffer ); + fun(); + + __goblint_check(0); // NOWARN + return(0); +} diff --git a/tests/regression/68-longjmp/21-multifun.c b/tests/regression/68-longjmp/21-multifun.c new file mode 100644 index 0000000000..3d593c2a8f --- /dev/null +++ b/tests/regression/68-longjmp/21-multifun.c @@ -0,0 +1,33 @@ +// PARAM: --enable ana.int.interval +#include +#include +#include +#include + +jmp_buf env_buffer; +int global = 0; + +void foo() { + int local = 7; + longjmp(env_buffer, 2); +} + + +int fun() { + global = 42; + foo(); +} + + +int main () { + int val; + + __goblint_check(global == 0); + if(0 == setjmp( env_buffer )) { + fun(); + } else { + __goblint_check(global == 42); + } + + return(0); +} diff --git a/tests/regression/68-longjmp/22-multifun-arg.c b/tests/regression/68-longjmp/22-multifun-arg.c new file mode 100644 index 0000000000..a6b4b71c2b --- /dev/null +++ b/tests/regression/68-longjmp/22-multifun-arg.c @@ -0,0 +1,37 @@ +// PARAM: --enable ana.int.interval --disable exp.volatiles_are_top --enable ana.int.enums --set solvers.td3.side_widen never +#include +#include +#include +#include + +jmp_buf env_buffer; +int global = 0; + +void foo() { + int local = 7; + longjmp(env_buffer, 2); +} + + +int fun(int* ptr) { + global = 42; + *ptr = 1; + foo(); +} + + +int main () { + volatile int val = 0; + + __goblint_check(global == 0); + int x = setjmp( env_buffer); + if(0 == x) { + fun(&val); + } else { + __goblint_check(x == 2); + __goblint_check(val == 1); + __goblint_check(global == 42); + } + + return(0); +} diff --git a/tests/regression/68-longjmp/23-arguments.c b/tests/regression/68-longjmp/23-arguments.c new file mode 100644 index 0000000000..f135011c83 --- /dev/null +++ b/tests/regression/68-longjmp/23-arguments.c @@ -0,0 +1,35 @@ +// PARAM: --enable ana.int.interval +#include +#include +#include +#include + +jmp_buf env_buffer; +int global = 0; + +int fun() { + int top; + + if(top == 1) { + longjmp(env_buffer, 2); //NOWARN + } else if (top == 2) { + longjmp(env_buffer, 0); //WARN + } else { + longjmp(env_buffer, top); //WARN + } +} + + +int main () { + int val; + + __goblint_check(global == 0); + if(setjmp( env_buffer )) { + return 8; + } + + fun(); + + __goblint_check(0); // NOWARN + return(0); +} diff --git a/tests/regression/68-longjmp/24-too-late.c b/tests/regression/68-longjmp/24-too-late.c new file mode 100644 index 0000000000..4c5062853f --- /dev/null +++ b/tests/regression/68-longjmp/24-too-late.c @@ -0,0 +1,31 @@ +// PARAM: --enable ana.int.interval +#include +#include +#include +#include + +jmp_buf env_buffer; +int global = 0; + +int fun() { + int top; + longjmp(env_buffer, 2); //WARN +} + +int bar() { + if(setjmp( env_buffer )) { + return 8; + } +} + + +int main () { + int val; + + __goblint_check(global == 0); + bar(); + fun(); + + __goblint_check(0); // NOWARN + return(0); +} diff --git a/tests/regression/68-longjmp/25-rec.c b/tests/regression/68-longjmp/25-rec.c new file mode 100644 index 0000000000..8c1f7b06a8 --- /dev/null +++ b/tests/regression/68-longjmp/25-rec.c @@ -0,0 +1,41 @@ +// PARAM: --enable ana.int.interval --enable exp.earlyglobs +#include +#include +#include +#include + +jmp_buf env_buffer; +int global = 0; + +int fun() { + int top; + longjmp(env_buffer, 2); //WARN +} + +int bar() { + int top,top2; + + if(top) { + if(setjmp( env_buffer )) { + return 8; + } + } + + if(top2) { + fun(); + } +} + + +int main () { + int val; + + __goblint_check(global == 0); + bar(); + + // In this second invocation of bar() the jumpbuffer could still contain the old value set during the first invocation, making the longjmp in fun() + // an illegal longjmp :/ + bar(); + + return(0); +} diff --git a/tests/regression/68-longjmp/26-non-term.c b/tests/regression/68-longjmp/26-non-term.c new file mode 100644 index 0000000000..74cb2df8d9 --- /dev/null +++ b/tests/regression/68-longjmp/26-non-term.c @@ -0,0 +1,29 @@ +// SKIP PARAM: --enable ana.int.interval --enable exp.earlyglobs +#include +#include +#include +#include + +jmp_buf env_buffer; +int global = 0; + +// This will cause the analysis to not terminate as the set of +// active setjumps will grow without bounds as the set of active +// setjumps also forms part of the context. +void bar() { + if(global == 0) { + if(setjmp( env_buffer )) { + return 8; + } + } + else + longjmp(env_buffer, 1); // unreachable longjmp to trick us into activating longjmp analyses + + bar(); +} + + +int main () { + bar(); + return(0); +} diff --git a/tests/regression/68-longjmp/27-other.c b/tests/regression/68-longjmp/27-other.c new file mode 100644 index 0000000000..a19cc7c9b2 --- /dev/null +++ b/tests/regression/68-longjmp/27-other.c @@ -0,0 +1,22 @@ +// PARAM: --enable ana.int.interval --enable exp.earlyglobs +#include +#include +#include +#include + +int main(void) +{ + jmp_buf buf; + int top; + + if(top) { + if(setjmp(buf)) { + __goblint_check(1); + return 8; + } + } + + longjmp(buf, 1); //WARN + + return 0; +} diff --git a/tests/regression/68-longjmp/28-svcomp.c b/tests/regression/68-longjmp/28-svcomp.c new file mode 100644 index 0000000000..16856fb322 --- /dev/null +++ b/tests/regression/68-longjmp/28-svcomp.c @@ -0,0 +1,22 @@ +// PARAM: --enable ana.int.interval --enable exp.earlyglobs --enable ana.sv-comp.enabled --set ana.specification "CHECK( init(main()), LTL(G ! call(reach_error())) )" +#include +#include +#include +#include + +int main(void) +{ + jmp_buf buf; + int top; + + if(top) { + if(setjmp(buf)) { + __goblint_check(1); + return 8; + } + } + + longjmp(buf, 1); //WARN + + return 0; +} diff --git a/tests/regression/68-longjmp/29-rec2.c b/tests/regression/68-longjmp/29-rec2.c new file mode 100644 index 0000000000..1bc9a5a012 --- /dev/null +++ b/tests/regression/68-longjmp/29-rec2.c @@ -0,0 +1,39 @@ +//PARAM: --enable ana.int.interval --enable exp.earlyglobs +#include +#include +#include +#include + +jmp_buf env_buffer; +int global = 0; + + +void handler(int v) { + if(setjmp( env_buffer )) { + return 8; + } + + + if(v == 0) { + handler(1); + } else { + fun(); + } + + // If v == 0 then env_buffer was set in the recursive call to handler + // meaning that jumping here again is not valid. + fun(); +} + + +int fun() { + int top; + longjmp(env_buffer, 2); //WARN +} + +int main () { + int val; + + handler(global); + return(0); +} diff --git a/tests/regression/68-longjmp/30-nonuniquetid.c b/tests/regression/68-longjmp/30-nonuniquetid.c new file mode 100644 index 0000000000..4e586130ff --- /dev/null +++ b/tests/regression/68-longjmp/30-nonuniquetid.c @@ -0,0 +1,42 @@ +#include +#include +#include + +jmp_buf buf; +jmp_buf buf1; +pthread_mutex_t m; + +void *t_benign(void *arg) { + if(setjmp(buf1)) { + return NULL; + } + + longjmp(buf1, 1); //NOWARN +} + +void *t_fun(void *arg) { + pthread_mutex_lock(&m); + if(setjmp(buf)) { + return NULL; + } + pthread_mutex_unlock(&m); + + pthread_mutex_lock(&m); + longjmp(buf, 1); //WARN + pthread_mutex_unlock(&m); +} + +int main(void) { + int t; + + pthread_t id; + pthread_create(&id, NULL, t_benign, NULL); + + pthread_t id2[10]; + for(int i =0; i < 10;i++) { + // As we have both a unique & a non-unique thread here, we automatically warn as appropriate + pthread_create(&id2[i], NULL, t_fun, NULL); + } + + return 0; +} diff --git a/tests/regression/68-longjmp/31-mixedjmpbufs.c b/tests/regression/68-longjmp/31-mixedjmpbufs.c new file mode 100644 index 0000000000..470526fe19 --- /dev/null +++ b/tests/regression/68-longjmp/31-mixedjmpbufs.c @@ -0,0 +1,47 @@ +#include +#include +#include +#include + +jmp_buf error0; +jmp_buf error1; + + +int blorg(int x) { + if(x > 8) { + longjmp(error1, 1); // WARN (modified since setjmp) + } + + return x; +} + +int blub(int x,int y) { + if(x == 0) { + longjmp(error0, 1); // WARN (modified since setjmp) + } + + return blorg(x-27+3); +} + + + +int main(void) { + + if(setjmp(error0)) { + printf("error0 occured"); + return -1; + } + + if(setjmp(error1)) { + printf("error1 occured"); + return -2; + } + + int x, y; + scanf("%d", &x); + scanf("%d", &y); + int x = blub(x, y); // NOWARN + printf("%d", x); + + return 0; +} diff --git a/tests/regression/68-longjmp/32-libpng.c b/tests/regression/68-longjmp/32-libpng.c new file mode 100644 index 0000000000..e2b8e6019c --- /dev/null +++ b/tests/regression/68-longjmp/32-libpng.c @@ -0,0 +1,76 @@ +#include +#include +#include + +typedef void ( *png_longjmp_ptr) (jmp_buf, int); +struct png_struct_def +{ + jmp_buf jmp_buf_local; + png_longjmp_ptr longjmp_fn; + jmp_buf *jmp_buf_ptr; + size_t jmp_buf_size; +}; + +typedef struct png_struct_def png_struct; +typedef png_struct * __restrict png_structrp; + +jmp_buf* png_set_longjmp_fn(png_structrp png_ptr, png_longjmp_ptr longjmp_fn, size_t jmp_buf_size) +{ + + if (png_ptr ==((void *)0) ) + return ((void *)0); + + if (png_ptr->jmp_buf_ptr == ((void *)0)) + { + png_ptr->jmp_buf_size = 0; + + if (jmp_buf_size <= (sizeof png_ptr->jmp_buf_local)) + png_ptr->jmp_buf_ptr = &png_ptr->jmp_buf_local; + + else + { + png_ptr->jmp_buf_ptr = malloc (jmp_buf_size); + + if (png_ptr->jmp_buf_ptr ==((void *)0)) + return ((void *)0); + + png_ptr->jmp_buf_size = jmp_buf_size; + } + } + + else + { + size_t size = png_ptr->jmp_buf_size; + + if (size == 0) + { + size = (sizeof png_ptr->jmp_buf_local); + if (png_ptr->jmp_buf_ptr != &png_ptr->jmp_buf_local) + { + } + } + + if (size != jmp_buf_size) + { + return ((void *)0); + } + } + + png_ptr->longjmp_fn = longjmp_fn; + return png_ptr->jmp_buf_ptr; +} + + +int main(void) { + png_struct *read_ptr = malloc(sizeof(png_struct)); + int local = 5; + + if (setjmp ((*png_set_longjmp_fn((read_ptr), longjmp, (sizeof (jmp_buf)))))) { + int z = local; //WARN + return 48; + + } else { + local = 8; + read_ptr->longjmp_fn(*read_ptr->jmp_buf_ptr, 1); + } +} diff --git a/tests/regression/68-longjmp/33-munge.c b/tests/regression/68-longjmp/33-munge.c new file mode 100644 index 0000000000..65972b4704 --- /dev/null +++ b/tests/regression/68-longjmp/33-munge.c @@ -0,0 +1,55 @@ +// PARAMS: --disable exp.volatiles_are_top +#include +#include +#include + +jmp_buf buf; +jmp_buf buf1; +pthread_mutex_t m; +int g; + +void blub(int *x) { + *x = 17; +} + +void blarg() { + int x = 28; + blub(&x); +} + +void *t_benign(void *arg) { + volatile int vol = 2; + int t = 42, top; + + if(setjmp(buf1)) { + t = t+1; //WARN + return NULL; + } + + t = 19; + + if(setjmp(buf)) { + t = t+1; //NOWARN + return NULL; + } + + blarg(); + vol++; + g = 17; + + if(top) { + longjmp(buf1, 1); //WARN + } else{ + longjmp(buf, 1); //NOWARN + } +} + + +int main(void) { + int t; + + pthread_t id; + pthread_create(&id, NULL, t_benign, NULL); + + return 0; +} diff --git a/tests/regression/68-longjmp/34-wrapper.c b/tests/regression/68-longjmp/34-wrapper.c new file mode 100644 index 0000000000..bf07e1333e --- /dev/null +++ b/tests/regression/68-longjmp/34-wrapper.c @@ -0,0 +1,84 @@ +//PARAM: --set ana.malloc.wrappers[+] my_dirty_little_malloc +#include +#include +#include + +typedef void ( *png_longjmp_ptr) (jmp_buf, int); +struct png_struct_def +{ + jmp_buf jmp_buf_local; + png_longjmp_ptr longjmp_fn; + jmp_buf *jmp_buf_ptr; + size_t jmp_buf_size; +}; + +typedef struct png_struct_def png_struct; +typedef png_struct * __restrict png_structrp; + +void* my_dirty_little_malloc(size_t size) { + return malloc(size); +} + + +jmp_buf* png_set_longjmp_fn(png_structrp png_ptr, png_longjmp_ptr longjmp_fn, size_t jmp_buf_size) +{ + + if (png_ptr ==((void *)0) ) + return ((void *)0); + + if (png_ptr->jmp_buf_ptr == ((void *)0)) + { + png_ptr->jmp_buf_size = 0; + + if (jmp_buf_size <= (sizeof png_ptr->jmp_buf_local)) + png_ptr->jmp_buf_ptr = &png_ptr->jmp_buf_local; + + else + { + png_ptr->jmp_buf_ptr = my_dirty_little_malloc (jmp_buf_size); + + if (png_ptr->jmp_buf_ptr ==((void *)0)) + return ((void *)0); + + png_ptr->jmp_buf_size = jmp_buf_size; + } + } + + else + { + size_t size = png_ptr->jmp_buf_size; + + if (size == 0) + { + size = (sizeof png_ptr->jmp_buf_local); + if (png_ptr->jmp_buf_ptr != &png_ptr->jmp_buf_local) + { + } + } + + if (size != jmp_buf_size) + { + return ((void *)0); + } + } + + png_ptr->longjmp_fn = longjmp_fn; + return png_ptr->jmp_buf_ptr; +} + + +int main(void) { + png_struct *read_ptr = my_dirty_little_malloc(sizeof(png_struct)); + int *soup = my_dirty_little_malloc(sizeof(int)); + *soup = 8; + int local = 5; + + if (setjmp ((*png_set_longjmp_fn((read_ptr), longjmp, (sizeof (jmp_buf)))))) { + int z = local; //WARN + return 48; + + } else { + local = 8; + read_ptr->longjmp_fn(*read_ptr->jmp_buf_ptr, 1); + } +} diff --git a/tests/regression/68-longjmp/35-null.c b/tests/regression/68-longjmp/35-null.c new file mode 100644 index 0000000000..3e4f50d4bd --- /dev/null +++ b/tests/regression/68-longjmp/35-null.c @@ -0,0 +1,93 @@ +//PARAM: --set ana.malloc.wrappers[+] my_dirty_little_malloc --disable sem.unknown_function.spawn +#include +#include +#include + +typedef void ( *png_longjmp_ptr) (jmp_buf, int); +struct png_struct_def +{ + jmp_buf jmp_buf_local; + png_longjmp_ptr longjmp_fn; + jmp_buf *jmp_buf_ptr; + size_t jmp_buf_size; +}; + +typedef struct png_struct_def png_struct; +typedef png_struct * __restrict png_structrp; + +void* my_dirty_little_malloc(size_t size) { + return malloc(size); +} + +extern void* munge(png_structrp png_ptr); + +jmp_buf* png_set_longjmp_fn(png_structrp png_ptr, png_longjmp_ptr longjmp_fn, size_t jmp_buf_size) +{ + + if (png_ptr ==((void *)0) ) + return ((void *)0); + + if (png_ptr->jmp_buf_ptr == ((void *)0)) + { + png_ptr->jmp_buf_size = 0; + + if (jmp_buf_size <= (sizeof png_ptr->jmp_buf_local)) + png_ptr->jmp_buf_ptr = &png_ptr->jmp_buf_local; + + else + { + png_ptr->jmp_buf_ptr = my_dirty_little_malloc (jmp_buf_size); + + if (png_ptr->jmp_buf_ptr ==((void *)0)) + return ((void *)0); + + png_ptr->jmp_buf_size = jmp_buf_size; + } + } + + else + { + size_t size = png_ptr->jmp_buf_size; + + if (size == 0) + { + size = (sizeof png_ptr->jmp_buf_local); + if (png_ptr->jmp_buf_ptr != &png_ptr->jmp_buf_local) + { + } + } + + if (size != jmp_buf_size) + { + return ((void *)0); + } + } + + png_ptr->longjmp_fn = longjmp_fn; + return png_ptr->jmp_buf_ptr; +} + + +int main(void) { + png_struct *read_ptr = my_dirty_little_malloc(sizeof(png_struct)); + int *soup = my_dirty_little_malloc(sizeof(int)); + *soup = 8; + int local = 5; + + struct __jmp_buf_tag *buf[1] = malloc(sizeof(jmp_buf)); + + if (setjmp ((*png_set_longjmp_fn((read_ptr), longjmp, (sizeof (jmp_buf)))))) { + int z = local; //WARN + return 48; + + } else { + local = 8; + + int top; + if(top) { + read_ptr->jmp_buf_ptr = ((void *)0); + } + munge(read_ptr); + read_ptr->longjmp_fn(*read_ptr->jmp_buf_ptr, 1); + } +} diff --git a/tests/regression/68-longjmp/36-poison.c b/tests/regression/68-longjmp/36-poison.c new file mode 100644 index 0000000000..0a2959bb92 --- /dev/null +++ b/tests/regression/68-longjmp/36-poison.c @@ -0,0 +1,51 @@ +// PARAM: --enable ana.int.interval +#include +#include +#include +#include + +jmp_buf env_buffer; +int global = 0; + +int myRandom() { + // Chosen by fair dice roll. + return 42; +} + +int funjmp() { + longjmp(env_buffer, 2); +} + +int fun(int param) { + param = param +1; //NOWARN + if(param == 2) { + funjmp(); + } +} + +int main () { + int val; + int x; + int top; + + __goblint_check(global == 0); + if(setjmp( env_buffer )) { + x = val; //WARN + + if(top) { + val = 8; + } else { + val = myRandom(); + } + + x = val; //NOWARN + fun(5); + return; + }; + + val = 8; + fun(1); + + __goblint_check(0); // NOWARN + return(0); +} diff --git a/tests/regression/68-longjmp/37-more.c b/tests/regression/68-longjmp/37-more.c new file mode 100644 index 0000000000..d82a8908af --- /dev/null +++ b/tests/regression/68-longjmp/37-more.c @@ -0,0 +1,86 @@ +//PARAM: +#include +#include +#include + +typedef void ( *png_longjmp_ptr) (jmp_buf, int); +struct png_struct_def +{ + jmp_buf jmp_buf_local; + png_longjmp_ptr longjmp_fn; + jmp_buf *jmp_buf_ptr; + size_t jmp_buf_size; +}; + +typedef struct png_struct_def png_struct; +typedef png_struct * __restrict png_structrp; + +jmp_buf* png_set_longjmp_fn(png_structrp png_ptr, png_longjmp_ptr longjmp_fn, size_t jmp_buf_size) +{ + + if (png_ptr ==((void *)0)) + return ((void *)0); + + if (png_ptr->jmp_buf_ptr == ((void *)0)) + { + png_ptr->jmp_buf_size = 0; + + if (jmp_buf_size <= (sizeof png_ptr->jmp_buf_local)) + png_ptr->jmp_buf_ptr = &png_ptr->jmp_buf_local; + + else + { + png_ptr->jmp_buf_ptr = malloc(jmp_buf_size); + + if (png_ptr->jmp_buf_ptr ==((void *)0)) + return ((void *)0); + + png_ptr->jmp_buf_size = jmp_buf_size; + } + } + + else + { + size_t size = png_ptr->jmp_buf_size; + + if (size == 0) + { + size = (sizeof png_ptr->jmp_buf_local); + if (png_ptr->jmp_buf_ptr != &png_ptr->jmp_buf_local) + { + } + } + + if (size != jmp_buf_size) + { + return ((void *)0); + } + } + + png_ptr->longjmp_fn = longjmp_fn; + return png_ptr->jmp_buf_ptr; +} + + +int main(void) { + png_struct *read_ptr; + + // They really do this crap! + png_struct create_struct; + memset(&create_struct, 0, (sizeof create_struct)); + + read_ptr = malloc(sizeof(png_struct)); + *read_ptr = create_struct; + + int local = 5; + + if (setjmp ((*png_set_longjmp_fn((read_ptr), longjmp, (sizeof (jmp_buf)))))) { + int z = local; //WARN + return 48; + + } else { + local = 8; + + read_ptr->longjmp_fn(*read_ptr->jmp_buf_ptr, 1); + } +} diff --git a/tests/regression/68-longjmp/38-more-trimmed.c b/tests/regression/68-longjmp/38-more-trimmed.c new file mode 100644 index 0000000000..54805c9edf --- /dev/null +++ b/tests/regression/68-longjmp/38-more-trimmed.c @@ -0,0 +1,48 @@ +// PARAM: --set ana.malloc.unique_address_count 1 +#include +#include +#include + +typedef void ( *png_longjmp_ptr) (jmp_buf, int); +struct png_struct_def +{ + jmp_buf jmp_buf_local; + png_longjmp_ptr longjmp_fn; + jmp_buf *jmp_buf_ptr; + size_t jmp_buf_size; +}; + +typedef struct png_struct_def png_struct; +typedef png_struct * __restrict png_structrp; + +jmp_buf* png_set_longjmp_fn(png_structrp png_ptr, png_longjmp_ptr longjmp_fn, size_t jmp_buf_size) +{ + png_ptr->jmp_buf_size = 0; + png_ptr->jmp_buf_ptr = &png_ptr->jmp_buf_local; + png_ptr->longjmp_fn = longjmp_fn; + return png_ptr->jmp_buf_ptr; +} + + +int main(void) { + png_struct *read_ptr; + + // They really do this crap! + png_struct create_struct; + memset(&create_struct, 0, (sizeof create_struct)); + + read_ptr = malloc(sizeof(png_struct)); + *read_ptr = create_struct; + + int local = 5; + + if (setjmp ((*png_set_longjmp_fn((read_ptr), longjmp, (sizeof (jmp_buf)))))) { + int z = local; //WARN + return 48; + + } else { + local = 8; + + read_ptr->longjmp_fn(*read_ptr->jmp_buf_ptr, 1); + } +} diff --git a/tests/regression/68-longjmp/39-poison-return.c b/tests/regression/68-longjmp/39-poison-return.c new file mode 100644 index 0000000000..09e8883664 --- /dev/null +++ b/tests/regression/68-longjmp/39-poison-return.c @@ -0,0 +1,32 @@ +// PARAM: +#include +#include +#include +#include + +jmp_buf env_buffer; + +int wrap () { + int val; + int x; + int top; + + val = val + 2; //NOWARN + + if(setjmp( env_buffer )) { + x = val; //WARN + return 3; + }; + + val = 8; + longjmp(env_buffer, 2); + + __goblint_check(0); // NOWARN + return(0); +} + +int main() { + wrap(); + int y = 8; + wrap(); +} diff --git a/tests/regression/68-longjmp/40-complicated-poison.c b/tests/regression/68-longjmp/40-complicated-poison.c new file mode 100644 index 0000000000..2b3eb89d96 --- /dev/null +++ b/tests/regression/68-longjmp/40-complicated-poison.c @@ -0,0 +1,65 @@ +// PARAM: --enable ana.int.interval +#include +#include +#include +#include + +jmp_buf env_buffer; +int global = 0; + +int myRandom() { + // Chosen by fair dice roll. + return 42; +} + +int funjmp() { + longjmp(env_buffer, 2); +} + +int fun(int param) { + param = param +1; //NOWARN + if(param == 2) { + funjmp(); + } +} + +int eight() { + return 8; +} + +int main () { + int val; + int x; + int top; + int* ptr = &val; + int* ptr2 = &val; + int** ptrptr = &ptr; + int arr[10] = {0,0,0,0,0,0,0,0,0,0}; + int* ptr3; + + __goblint_check(global == 0); + if(setjmp( env_buffer )) { + x = val; //WARN + x = *ptr; //WARN + ptr3 = ptr2; //WARN + x = *ptr2; //WARN + x = **ptrptr; //WARN + x = arr[**ptrptr]; //WARN + + *ptr = 5; //NOWARN + x = *ptr; //NOWARN + *ptr2 = 9; //WARN (ptr2 still has indeterminate value) + *ptr2 = eight(); //WARN (ptr2 still has indeterminate value) + + x = val; //NOWARN + fun(5); + return 3; + }; + + val = 8; + ptr2 = &x; + fun(1); + + __goblint_check(0); // NOWARN + return(0); +} diff --git a/tests/regression/68-longjmp/41-poison-rec.c b/tests/regression/68-longjmp/41-poison-rec.c new file mode 100644 index 0000000000..c5ff941298 --- /dev/null +++ b/tests/regression/68-longjmp/41-poison-rec.c @@ -0,0 +1,33 @@ +// PARAM: --enable ana.int.interval +#include +#include +#include +#include + +jmp_buf env_buffer; +int global = 0; + +int fun(int param) { + char keyword[81] = "example"; + + if(param != 2) { + char c = keyword[0]; //NOWARN + return 1; + } + + if(setjmp( env_buffer )) { + fun(4); + + char c = keyword[0]; //WARN + return 3; + }; + + keyword[0] = 'a'; + + longjmp(env_buffer, 2); +} + +int main () { + fun(2); + return(0); +} diff --git a/tests/regression/68-longjmp/42-poison-reduced.c b/tests/regression/68-longjmp/42-poison-reduced.c new file mode 100644 index 0000000000..a3ef050b35 --- /dev/null +++ b/tests/regression/68-longjmp/42-poison-reduced.c @@ -0,0 +1,37 @@ +// PARAM: --set ana.activated[+] expsplit --disable sem.unknown_function.spawn --disable sem.unknown_function.invalidate.globals --disable sem.unknown_function.invalidate.args --enable dbg.verbose --disable exp.volatiles_are_top --enable ana.int.interval +#include +jmp_buf env_buffer; +struct c { + char *g; +}; + +int u(struct c * t) { + if (*t->g) { + return 2; + } else { + return 3; + } +} + +void set_g_to_keyword(struct c* t) { + char keyword[20]; + keyword[0] = 'a'; + t->g = keyword; +} + +main() { + struct c* ab = malloc(sizeof(struct c)); + int x; + + if(setjmp(env_buffer)) { + __goblint_check(x == 2); + set_g_to_keyword(ab); + } + else { + set_g_to_keyword(ab); + x = 1; + u(ab); + x = 2; + longjmp(env_buffer, 1); + } +} diff --git a/tests/regression/68-longjmp/43-poison-addr.c b/tests/regression/68-longjmp/43-poison-addr.c new file mode 100644 index 0000000000..6d1c0b455d --- /dev/null +++ b/tests/regression/68-longjmp/43-poison-addr.c @@ -0,0 +1,37 @@ +// PARAM: +#include +#include +#include +#include + +jmp_buf env_buffer; + +void makeFive(int* ptr) { + *ptr = 5; +} + +int wrap () { + int val; + int x; + int top; + + val = val + 2; //NOWARN + + if(setjmp( env_buffer )) { + makeFive(&val); //NOWARN + x = val; //NOWARN + return 3; + }; + + val = 8; + longjmp(env_buffer, 2); + + __goblint_check(0); // NOWARN + return(0); +} + +int main() { + wrap(); + int y = 8; + wrap(); +} diff --git a/tests/regression/68-longjmp/44-simple-else-path.c b/tests/regression/68-longjmp/44-simple-else-path.c new file mode 100644 index 0000000000..9215eb79d8 --- /dev/null +++ b/tests/regression/68-longjmp/44-simple-else-path.c @@ -0,0 +1,38 @@ +// PARAM: --enable ana.int.interval --enable ana.int.enums --set ana.activated[+] expsplit +#include +#include +#include +#include + +jmp_buf env_buffer; +int global = 0; + +int fun() { + global = 2; + longjmp(env_buffer, 2); +} + + +int main () { + int val; + jmp_buf env_buffer2; + + __goblint_check(global == 0); + + /* save calling environment for longjmp */ + val = setjmp( env_buffer ); + + + if( val != 0 ) { + printf("Returned from a longjmp() with value = %i\n", val); + __goblint_check(val == 2); + __goblint_check(global == 2); + exit(0); + } + + __goblint_check(global == 0); + fun(); + + __goblint_check(0); // NOWARN + return(0); +} diff --git a/tests/regression/68-longjmp/45-variably-modified.c b/tests/regression/68-longjmp/45-variably-modified.c new file mode 100644 index 0000000000..5bb2ebea85 --- /dev/null +++ b/tests/regression/68-longjmp/45-variably-modified.c @@ -0,0 +1,54 @@ +// PARAM: --enable ana.int.interval --enable ana.int.enums --set ana.activated[+] expsplit --disable warn.deadcode +#include +#include +#include +#include + +jmp_buf env_buffer; +int global = 0; + +int fun() { + if(setjmp(env_buffer)) { //NOWARN + return 0; + } + + + global = 2; + longjmp(env_buffer, 2); +} + + +int main () { + int val; + jmp_buf env_buffer2; + + __goblint_check(global == 0); + + if(setjmp(env_buffer)) { //NOWARN + return 0; + } + + int n; + + { + // Array of variably modified type + int a[n]; + + if(setjmp(env_buffer)) { // WARN + return 0; + } + } + + { + // Array of variably modified type + int b[2][n]; + + if(setjmp(env_buffer)) { // WARN + return 0; + } + } + + fun(); + + return(0); +} diff --git a/tests/regression/68-longjmp/46-copied.c b/tests/regression/68-longjmp/46-copied.c new file mode 100644 index 0000000000..d7ec9de0da --- /dev/null +++ b/tests/regression/68-longjmp/46-copied.c @@ -0,0 +1,28 @@ +// PARAM: --enable ana.int.interval --enable ana.int.enums --set ana.activated[+] expsplit --disable warn.deadcode +#include +#include +#include +// #include + +struct buf_struct { + jmp_buf buf; +}; + +struct buf_struct env_buffer; +struct buf_struct buffer2; +int global = 0; + +int main () { + int val; + __goblint_check(global == 0); + + if(setjmp(env_buffer.buf)) { //NOWARN + return 0; + } + + buffer2 = env_buffer; + + longjmp(buffer2.buf,42); //WARN + + return(0); +} diff --git a/tests/regression/68-longjmp/47-more-reduced.c b/tests/regression/68-longjmp/47-more-reduced.c new file mode 100644 index 0000000000..ff85a229e5 --- /dev/null +++ b/tests/regression/68-longjmp/47-more-reduced.c @@ -0,0 +1,38 @@ +// PARAM: --set ana.activated[+] expsplit --disable sem.unknown_function.spawn --disable sem.unknown_function.invalidate.globals --disable sem.unknown_function.invalidate.args --enable dbg.verbose --disable exp.volatiles_are_top --enable ana.int.interval +#include +jmp_buf env_buffer; + +struct c { + char *g; +} s; + +int g; + +set_key(struct c* t) { + char keyword[20]; + keyword[0] = 'a'; + t->g = keyword; + + if (*t->g) { g=1; } +} + +main() { + struct c * t = &s; + + switch(setjmp(env_buffer)) { + case 0: break; + case 1: + if (*t->g) { g=1; } + // This refinement somehow adds bottom for keyword as an explicit binding (???) + set_key(t); + longjmp(env_buffer, 2); //NOWARN + break; + case 2: + return; + } + + set_key(t); + g = 4; + + longjmp(env_buffer, 1); +} diff --git a/tests/regression/68-longjmp/48-bot-buff.c b/tests/regression/68-longjmp/48-bot-buff.c new file mode 100644 index 0000000000..eaf92f89af --- /dev/null +++ b/tests/regression/68-longjmp/48-bot-buff.c @@ -0,0 +1,6 @@ +#include +jmp_buf env_buffer; + +int main() { + longjmp(env_buffer, 1); //WARN +} diff --git a/tests/regression/68-longjmp/49-arguments-inline.c b/tests/regression/68-longjmp/49-arguments-inline.c new file mode 100644 index 0000000000..7c8c00b1d0 --- /dev/null +++ b/tests/regression/68-longjmp/49-arguments-inline.c @@ -0,0 +1,15 @@ +#include +#include + +jmp_buf env_buffer; + +int main () { + if (setjmp(env_buffer)) { + __goblint_check(1); // reachable + return 8; + } + + longjmp(env_buffer, 0); // WARN + __goblint_check(0); // NOWARN + return 0; +} diff --git a/tests/regression/68-longjmp/50-arguments-non-top.c b/tests/regression/68-longjmp/50-arguments-non-top.c new file mode 100644 index 0000000000..fd5f30a166 --- /dev/null +++ b/tests/regression/68-longjmp/50-arguments-non-top.c @@ -0,0 +1,31 @@ +// PARAM: --enable ana.int.interval +#include +#include +#include +#include + +jmp_buf env_buffer; + +int fun() { + int r; + __goblint_assume(0 <= r); + __goblint_assume(r <= 10); + longjmp(env_buffer, r); //WARN +} + + +int main () { + int val; + if (val = setjmp( env_buffer )) { + __goblint_check(val == 1); // UNKNOWN! + __goblint_check(val != 1); // UNKNOWN! + __goblint_check(1 <= val); // TODO (better interval exclude) + __goblint_check(val <= 10); + return 8; + } + + fun(); + + __goblint_check(0); // NOWARN + return(0); +} diff --git a/tests/regression/68-longjmp/51-worries.c b/tests/regression/68-longjmp/51-worries.c new file mode 100644 index 0000000000..d25df25712 --- /dev/null +++ b/tests/regression/68-longjmp/51-worries.c @@ -0,0 +1,26 @@ +// PARAM: --enable ana.int.interval +#include +#include +#include +#include + +jmp_buf env_buffer; +int global = 0; + +int bar() { + longjmp(env_buffer, 2); + return 8; +} + +void foo() { + global = bar(); +} + +int main() { + if(setjmp( env_buffer )) { + assert(global == 0); + return 0; + } + + foo(); +} 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/68-longjmp/README.md b/tests/regression/68-longjmp/README.md new file mode 100644 index 0000000000..4e3f00d05b --- /dev/null +++ b/tests/regression/68-longjmp/README.md @@ -0,0 +1,5 @@ +# Tests for the support of setjmp/longjmp by Goblint + +Some of these tests are technically illegal according to the C standard, as `setjmp` may only appear inside a condition. +We still have it pulled out here sometimes to allow conveniently inspecting analysis results without having to deal with +CIL inserted temporaries. GCC also support this without any issues. diff --git a/tests/regression/69-addresses/01-meet.c b/tests/regression/69-addresses/01-meet.c new file mode 100644 index 0000000000..d5ac776249 --- /dev/null +++ b/tests/regression/69-addresses/01-meet.c @@ -0,0 +1,23 @@ +#include +#include + +int main(){ + long long l; + + int* p = malloc(sizeof(int)); + + int i; + int* p2 = (int*) i; // create a top-pointer + + __goblint_check(p == p2); //UNKNOWN! + + if(p == p2){ + __goblint_check(p == p2); //TODO + } + + if(p2 == p){ + __goblint_check(p == p2); //TODO + } + + return 0; +} \ No newline at end of file diff --git a/tests/regression/69-addresses/02-array-cast.c b/tests/regression/69-addresses/02-array-cast.c new file mode 100644 index 0000000000..6a3f123ac7 --- /dev/null +++ b/tests/regression/69-addresses/02-array-cast.c @@ -0,0 +1,26 @@ +// SKIP +#include + +int main() { + int a[10]; + int *b = a; + + __goblint_check(a == b); + __goblint_check(a + 4 == b + 4); + + char *b_char = (char*) a; + __goblint_check((void*) a == (void*) b_char ); + + char* a_intoffset =(char*) (a + 1); + char* b_intoffset = b_char + sizeof(int); + + __goblint_check(a_intoffset == b_intoffset); + __goblint_check((char*) (a + 1) == b_char + sizeof(int)); + + char* a_4intoffset = (char*) (a + 4); + + __goblint_check(a_4intoffset == b_intoffset); // FAIL + __goblint_check((char*) (a + 4) == b_char + sizeof(int)); // FAIL + + return 0; +} diff --git a/tests/regression/69-addresses/03-issue-564.c b/tests/regression/69-addresses/03-issue-564.c new file mode 100644 index 0000000000..5eaa60eb4b --- /dev/null +++ b/tests/regression/69-addresses/03-issue-564.c @@ -0,0 +1,17 @@ +#include + +typedef struct { + int x; +} a; + +int main() { + a z; + a *y = &z; + + int *m = &y->x; // {&z.x} + a *n = &y[0]; // {&z[def_exc:0]} + + int b = m == n; + assert(b); + return 0; +} diff --git a/tests/regression/70-transform/01-empty.c b/tests/regression/70-transform/01-empty.c new file mode 100644 index 0000000000..0bc74b5e60 --- /dev/null +++ b/tests/regression/70-transform/01-empty.c @@ -0,0 +1 @@ +// SKIP: this is an input file for cram tests diff --git a/tests/regression/70-transform/01-ordering.t b/tests/regression/70-transform/01-ordering.t new file mode 100644 index 0000000000..abf499c392 --- /dev/null +++ b/tests/regression/70-transform/01-ordering.t @@ -0,0 +1,5 @@ +Check that assert transform is not allowed to happen after dead code removal + $ ./transform.sh --stderr remove_dead_code assert -- 01-empty.c + Option failure: trans.activated: the 'assert' transform may not occur after the 'remove_dead_code' transform + Fatal error: exception Failure("Option failure: trans.activated: the 'assert' transform may not occur after the 'remove_dead_code' transform") + [2] diff --git a/tests/regression/70-transform/02-deadcode.c b/tests/regression/70-transform/02-deadcode.c new file mode 100644 index 0000000000..b3c01c07be --- /dev/null +++ b/tests/regression/70-transform/02-deadcode.c @@ -0,0 +1,141 @@ +// SKIP: this is an input file for cram tests + +#include + +// requires sem.noreturn.dead_code +extern noreturn void abort(); + + +// only called with positive n +int basic1(int n) { + int a = 0, b = 1, c; + + if (n < 0) + return 0; + + for (int i = 0; i < n; i++) { + c = a + b; + a = b; + b = c; + } + + return a; +} + +// only called with negative n +int basic2(int n) { + int a = 0; + + if (n < 0) + return 0; + + for (int i = 0; i < n; i++) + // Bug in dead code warnings: no dead code warning is emitted, because body is not included + // in the result. Transformation checks all CFG nodes, and therefore works. + a += i + n; + + return a; +} + + +int one_branch_dead(int x) { + if (x > 7) + return x; + else + return 7 - x; +} + + + +int uncalled_but_referenced_function(int x) { + int y = x + 1; + return x * y; +} + + +int uncalled1() { + return 1; +} + +int conditional_call_in_loop(int x) { + for (int i = 0; i < x; i++) { + // don't call this function with x > 7, then this will get removed + if (i > 7) { + uncalled1(); + } + } + return 0; +} + +// called with u > 12 +int loop_continue_break(int u) { + int w = 12; + for (int t = 0; t < u; t++) { + w += t; + if (t > 3) continue; + w += u; + if (t > 7) break; + w += w; + } + return w; +} + +int loop_dead_on_break(int z) { + int s = 5; + for (int i = 0; i < z; i++) { + s += i; + if (i < 5) break; + s += s; // dead + } + return s; +} + +int compound_statement_in_out() { + goto in1; + + // condition is dead, must not remove if statement's body though + if (1) { + in1: + goto in2; + } + + while (1) { + in2: + goto in3; + } + + for (int i = 0; i < 10; i++) { + in3: + goto out; + } + + out: + return 0; +} + + +int main() { + // test calls in multiple contexts + basic1(7); + basic1(3); + basic1(6); + basic2(-3); + basic2(-6); + basic2(-12); + + one_branch_dead(9); + one_branch_dead(12); + int (*f)(int) = &uncalled_but_referenced_function; + + conditional_call_in_loop(5); + loop_continue_break(11); + loop_dead_on_break(3); + + compound_statement_in_out(); + + abort(); + + // calls from dead code don't count + uncalled1(); + uncalled_but_referenced_function(3); +} diff --git a/tests/regression/70-transform/02-deadcode.t b/tests/regression/70-transform/02-deadcode.t new file mode 100644 index 0000000000..03a46b891e --- /dev/null +++ b/tests/regression/70-transform/02-deadcode.t @@ -0,0 +1,243 @@ + $ args='remove_dead_code -- --enable ana.int.interval --enable sem.noreturn.dead_code' + + $ ./transform.sh $args 02-deadcode.c + extern void abort() __attribute__((__noreturn__)) ; + int basic1(int n ) + { + int a ; + int b ; + int c ; + int i ; + + { + { + a = 0; + b = 1; + } + { + i = 0; + } + { + while (1) { + + if (! (i < n)) { + goto while_break; + } + { + c = a + b; + a = b; + b = c; + i ++; + } + } + while_break: /* CIL Label */ ; + } + return (a); + } + } + int basic2(int n ) + { + int a ; + + { + { + a = 0; + } + if (n < 0) { + return (0); + } + } + } + int one_branch_dead(int x ) + { + + + { + if (x > 7) { + return (x); + } + } + } + int uncalled_but_referenced_function(int x ) + { + + + { + + } + } + int conditional_call_in_loop(int x ) + { + int i ; + + { + { + i = 0; + } + { + while (1) { + + if (! (i < x)) { + goto while_break; + } + { + i ++; + } + } + while_break: /* CIL Label */ ; + } + return (0); + } + } + int loop_continue_break(int u ) + { + int w ; + int t ; + + { + { + w = 12; + t = 0; + } + { + while (1) { + + if (! (t < u)) { + goto while_break; + } + { + w += t; + } + if (t > 3) { + goto __Cont; + } + { + w += u; + } + if (t > 7) { + goto while_break; + } + { + w += w; + } + __Cont: /* CIL Label */ + { + t ++; + } + } + while_break: /* CIL Label */ ; + } + return (w); + } + } + int loop_dead_on_break(int z ) + { + int s ; + int i ; + + { + { + s = 5; + i = 0; + } + { + while (1) { + + if (! (i < z)) { + goto while_break; + } + { + s += i; + } + if (i < 5) { + goto while_break; + } + } + while_break: /* CIL Label */ ; + } + return (s); + } + } + int compound_statement_in_out(void) + { + + + { + goto in1; + { + in1: + goto in2; + } + { + { + in2: + goto in3; + } + } + { + { + in3: + goto out; + } + } + out: + return (0); + } + } + int main(void) + { + int (*f)(int ) ; + + { + { + basic1(7); + basic1(3); + basic1(6); + basic2(-3); + basic2(-6); + basic2(-12); + one_branch_dead(9); + one_branch_dead(12); + f = & uncalled_but_referenced_function; + conditional_call_in_loop(5); + loop_continue_break(11); + loop_dead_on_break(3); + compound_statement_in_out(); + abort(); + } + } + } + +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 -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 @@ + + } else { + + return (7 - x); + @@ -65,0 +71,8 @@ + +int uncalled1(void) + +{ + + + + + + { + + + +} + +} + @@ -79,0 +93,5 @@ + + if (i > 7) { + + { + + uncalled1(); + + } + + } + @@ -151,0 +170,4 @@ + + { + + s += s; + + i ++; + + } + @@ -203,0 +226,2 @@ + + uncalled1(); + + uncalled_but_referenced_function(3); diff --git a/tests/regression/70-transform/03-deadcode-globals.c b/tests/regression/70-transform/03-deadcode-globals.c new file mode 100644 index 0000000000..4ae084fbca --- /dev/null +++ b/tests/regression/70-transform/03-deadcode-globals.c @@ -0,0 +1,50 @@ +// SKIP: this is an input file for cram tests + +// structs + +struct struct1 { + const int field1; + const char field2; +}; + +typedef struct struct1 struct1_named1; // used, thus kept +typedef struct struct1 struct1_named2; // unused, removed +typedef struct struct1 struct1_named3; // only used as return type, kept +typedef struct struct1 struct1_named4; // only used as an argument type, kept + +struct struct2 { + int field3; + struct1_named1 *field4; // use of struct1_named1 and struct struct1 +}; + +const struct struct1 struct1_value1 = { + .field1 = 0, + .field2 = 'a' +}; + +struct1_named3 const struct_f() { + return struct1_value1; +} + +int struct_pointer_f(struct1_named4 *x) { + return x->field1 + 1; +} + +// globals referencing each other + +const int global1 = 1; // referenced (indirectly) +const int global2 = global1 + 7; // referenced (directly in main) +const int global3 = 2; // referenced by global4 only +const int global4 = global3 + 9; + +// reference each other, but otherwise unreferenced +int mutually_recursive2(void); +int mutually_recursive1() { return mutually_recursive2(); } +int mutually_recursive2() { return mutually_recursive1(); } + + +int main() { + struct1_named1 x = struct_f(); + int y = global2; + struct_pointer_f(&x); +} diff --git a/tests/regression/70-transform/03-deadcode-globals.t b/tests/regression/70-transform/03-deadcode-globals.t new file mode 100644 index 0000000000..ee3fae2c80 --- /dev/null +++ b/tests/regression/70-transform/03-deadcode-globals.t @@ -0,0 +1,43 @@ + $ ./transform.sh remove_dead_code -- 03-deadcode-globals.c + struct struct1 { + int field1 ; + char field2 ; + }; + typedef struct struct1 struct1_named1; + typedef struct struct1 struct1_named3; + typedef struct struct1 struct1_named4; + struct struct1 const struct1_value1 = {(int const )0, (char const )'a'}; + struct1_named3 const struct_f(void) + { + + + { + return (struct1_value1); + } + } + int struct_pointer_f(struct1_named4 *x ) + { + + + { + return ((int )(x->field1 + 1)); + } + } + int const global1 = (int const )1; + int const global2 = global1 + 7; + int main(void) + { + struct1_named1 x ; + struct1_named3 tmp ; + int y ; + + { + { + tmp = (struct1_named3 )struct_f(); + x = tmp; + y = (int )global2; + struct_pointer_f(& x); + } + return (0); + } + } diff --git a/tests/regression/70-transform/04-unchecked-condition.c b/tests/regression/70-transform/04-unchecked-condition.c new file mode 100644 index 0000000000..6a3e33c28e --- /dev/null +++ b/tests/regression/70-transform/04-unchecked-condition.c @@ -0,0 +1,69 @@ +// SKIP: this is an input file for cram tests + +int f_both(int x) { + int result; + + if (x > 7) + goto true_block; + else + goto false_block; + + // condition never checked, but true and false blocks live + if (x * 12 > 3) { + true_block: + result = 2; + } else { + false_block: + result = 12; + } + + return result; +} + +int f_true(int x) { + int result; + + if (x > 7) + goto true_block; + else + goto false_block; + + // condition never checked, and only true block live + if (x * 12 > 3) { + true_block: + result = 2; + } else { + false_block: + result = 12; + } + + return result; +} + +int f_false(int x) { + int result; + + if (x > 7) + goto true_block; + else + goto false_block; + + // condition never checked, and only false block live + if (x * 12 > 3) { + true_block: + result = 2; + } else { + false_block: + result = 12; + } + + return result; +} + +int main() { + f_both(3); + f_both(9); + + f_true(12); + f_false(-3); +} diff --git a/tests/regression/70-transform/04-unchecked-condition.t b/tests/regression/70-transform/04-unchecked-condition.t new file mode 100644 index 0000000000..4c88a058d9 --- /dev/null +++ b/tests/regression/70-transform/04-unchecked-condition.t @@ -0,0 +1,73 @@ + $ ./transform.sh remove_dead_code -- 04-unchecked-condition.c + int f_both(int x ) + { + int result ; + + { + if (x > 7) { + goto true_block; + } else { + goto false_block; + } + if ("UNCHECKED CONDITION") { + true_block: + { + result = 2; + } + } else { + false_block: + { + result = 12; + } + } + return (result); + } + } + int f_true(int x ) + { + int result ; + + { + if (x > 7) { + goto true_block; + } + { + true_block: + { + result = 2; + } + } + return (result); + } + } + int f_false(int x ) + { + int result ; + + { + if (! (x > 7)) { + goto false_block; + } + { + false_block: + { + result = 12; + } + } + return (result); + } + } + int main(void) + { + + + { + { + f_both(3); + f_both(9); + f_true(12); + f_false(-3); + } + return (0); + } + } diff --git a/tests/regression/70-transform/dune b/tests/regression/70-transform/dune new file mode 100644 index 0000000000..54742abdb8 --- /dev/null +++ b/tests/regression/70-transform/dune @@ -0,0 +1,2 @@ +(cram + (deps transform.sh (glob_files *.c) (glob_files *.json))) diff --git a/tests/regression/70-transform/transform.sh b/tests/regression/70-transform/transform.sh new file mode 100755 index 0000000000..1079e8a204 --- /dev/null +++ b/tests/regression/70-transform/transform.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +# usage: transform.sh [--stdout] [--stderr] [--file] transform1 transform2 ... -- [goblint args] file.c +# runs goblint with the given transformations active and outputs the transformed file to stdout +# - unless --stdout/--stderr is passed, supress those streams +# - if --file is passed, output to a temporary file and print its path to stdout + +set -eu -o pipefail + +function main() { + local -a trans_args=() + local stdout=0 stderr=0 file=0 output_file + + while [ $# -gt 0 ]; local arg="$1"; shift; do + case $arg in + --) break ;; + --stdout) stdout=1 ;; + --stderr) stderr=1 ;; + --file) file=1 ;; + *) trans_args+=( "--set" "trans.activated[+]" "$arg" ) ;; + esac + done + + if (( file == 1 && ( stdout == 1 || stderr == 1 ) )); then + printf '%s\n' '--file and --stdout/--stderr are mutually exclusive'; exit 1; fi + + output_file="$(mktemp ./transformed.c.XXXXXX)" + + # save stdout to FD 3 (automatic FD allocation not availble on Macintosh's bash) + exec 3>&1 + if (( stdout != 1 )); then exec 1>/dev/null; fi + if (( stderr != 1 )); then exec 2>/dev/null; fi + + # turn off backtraces + OCAMLRUNPARAM="${OCAMLRUNPARAM:-},b=0" \ + goblint "${trans_args[@]}" --set trans.output "$output_file" "$@" || result=$? + + # remove the 'Generated by CIL v. X.X.X' header, use -i'.tmp' for Macintosh systems + sed -i'.tmp' '1,3d' "$output_file" + if [ $file = 0 ]; then + cat "$output_file" 1>&3 + rm "$output_file" + else + printf '%s\n' "$output_file" 1>&3 + fi + + return "${result-0}" +} + +main "$@" diff --git a/tests/regression/71-doublelocking/01-simple.c b/tests/regression/71-doublelocking/01-simple.c new file mode 100644 index 0000000000..f2c961cb63 --- /dev/null +++ b/tests/regression/71-doublelocking/01-simple.c @@ -0,0 +1,36 @@ +// PARAM: --set ana.activated[+] 'maylocks' +#include +#include +#include +#include + +int g; + +pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER; + +void* f1(void* ptr) { + int top; + + g = 1; + if(top) { + pthread_mutex_lock(&mut); + } + pthread_mutex_lock(&mut); //WARN + pthread_mutex_unlock(&mut); + return NULL; +} + + +int main(int argc, char const *argv[]) +{ + pthread_t t1; + pthread_t t2; + + pthread_create(&t1,NULL,f1,NULL); + pthread_join(t1, NULL); + + pthread_mutex_lock(&mut); //NOWARN + pthread_mutex_unlock(&mut); + + return 0; +} diff --git a/tests/regression/71-doublelocking/02-unknown.c b/tests/regression/71-doublelocking/02-unknown.c new file mode 100644 index 0000000000..9ad70a03b3 --- /dev/null +++ b/tests/regression/71-doublelocking/02-unknown.c @@ -0,0 +1,36 @@ +// PARAM: --set ana.activated[+] 'maylocks' +#include +#include +#include +#include + +pthread_mutex_t mut[8]; + +void* f1(void* ptr) { + int top; + int x = 2; + if(top) { + x = 3; + } + + + pthread_mutex_lock(&mut[x]); + pthread_mutex_lock(&mut[3]); //WARN + pthread_mutex_unlock(&mut[3]); + return NULL; +} + + +int main(int argc, char const *argv[]) +{ + pthread_t t1; + pthread_t t2; + + pthread_create(&t1,NULL,f1,NULL); + pthread_join(t1, NULL); + + pthread_mutex_lock(&mut[0]); //NOWARN + pthread_mutex_unlock(&mut[0]); + + return 0; +} diff --git a/tests/regression/71-doublelocking/03-thread-exit-with-mutex.c b/tests/regression/71-doublelocking/03-thread-exit-with-mutex.c new file mode 100644 index 0000000000..d71f3fb616 --- /dev/null +++ b/tests/regression/71-doublelocking/03-thread-exit-with-mutex.c @@ -0,0 +1,39 @@ +// PARAM: --set ana.activated[+] 'maylocks' --set ana.activated[+] 'pthreadMutexType' +#include +#include +#include +#include +#include + +pthread_mutex_t mut[8]; + +void* f1(void* ptr) { + int top; + int x = 2; + if(top) { + x = 3; + } + + pthread_mutex_lock(&mut[x]); + + if(top) { + pthread_exit(NULL); //WARN + } + + return NULL; //WARN +} + + +int main(int argc, char const *argv[]) +{ + pthread_t t1; + pthread_t t2; + + pthread_create(&t1,NULL,f1,NULL); + pthread_join(t1, NULL); + + pthread_mutex_lock(&mut[0]); //NOWARN + pthread_mutex_unlock(&mut[0]); + + return 0; // We would actually want to not warn here, but the mutex type analysis is currently too imprecise +} diff --git a/tests/regression/71-doublelocking/04-unlock.c b/tests/regression/71-doublelocking/04-unlock.c new file mode 100644 index 0000000000..1cc5f26654 --- /dev/null +++ b/tests/regression/71-doublelocking/04-unlock.c @@ -0,0 +1,35 @@ +#include +#include +#include +#include + +int g; + +pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER; +pthread_mutex_t mut2 = PTHREAD_MUTEX_INITIALIZER; +pthread_cond_t cond = PTHREAD_COND_INITIALIZER; + +void* f1(void* ptr) { + int top; + + pthread_mutex_lock(&mut); + pthread_mutex_unlock(&mut2); //WARN + return NULL; +} + + +int main(int argc, char const *argv[]) +{ + pthread_t t1; + pthread_t t2; + + pthread_create(&t1,NULL,f1,NULL); + pthread_join(t1, NULL); + + pthread_mutex_lock(&mut); + pthread_mutex_unlock(&mut); //NOWARN + + pthread_cond_wait(&cond,&mut); //WARN + + return 0; +} diff --git a/tests/regression/71-doublelocking/05-rec.c b/tests/regression/71-doublelocking/05-rec.c new file mode 100644 index 0000000000..5bc94dbeda --- /dev/null +++ b/tests/regression/71-doublelocking/05-rec.c @@ -0,0 +1,47 @@ +// PARAM: --set ana.activated[+] 'maylocks' --set ana.activated[+] 'pthreadMutexType' +#define _GNU_SOURCE +#include +#include +#include +#include + + +int g; + +pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER; + +#ifndef __APPLE__ +pthread_mutex_t mut2 = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP; +#endif + + +void* f1(void* ptr) { + int top; + + g = 1; + if(top) { + pthread_mutex_lock(&mut); + } + pthread_mutex_lock(&mut); //WARN + pthread_mutex_unlock(&mut); + return NULL; +} + + +int main(int argc, char const *argv[]) +{ + pthread_t t1; + pthread_t t2; + + pthread_create(&t1,NULL,f1,NULL); + pthread_join(t1, NULL); + +#ifndef __APPLE__ + pthread_mutex_lock(&mut2); //NOWARN + pthread_mutex_lock(&mut2); //NOWARN + pthread_mutex_unlock(&mut2); + pthread_mutex_unlock(&mut2); +#endif + + return 0; +} diff --git a/tests/regression/71-doublelocking/06-rec-dyn.c b/tests/regression/71-doublelocking/06-rec-dyn.c new file mode 100644 index 0000000000..aed19210c5 --- /dev/null +++ b/tests/regression/71-doublelocking/06-rec-dyn.c @@ -0,0 +1,43 @@ +// PARAM: --set ana.activated[+] 'maylocks' --set ana.activated[+] 'pthreadMutexType' +#define _GNU_SOURCE +#include +#include +#include +#include + +int g; + +void* f1(void* ptr) { + pthread_mutex_t* mut = (pthread_mutex_t*) ptr; + + pthread_mutex_lock(mut); //NOWARN + pthread_mutex_lock(mut); //NOWARN + pthread_mutex_unlock(mut); + pthread_mutex_unlock(mut); + return NULL; +} + + +int main(int argc, char const *argv[]) +{ + pthread_t t1; + pthread_mutex_t mut; + + pthread_mutexattr_t attr; + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&mut, &attr); + + + pthread_create(&t1,NULL,f1,&mut); + + + pthread_mutex_lock(&mut); //NOWARN + pthread_mutex_lock(&mut); //NOWARN + pthread_mutex_unlock(&mut); + pthread_mutex_unlock(&mut); + + pthread_join(t1, NULL); + + + return 0; +} diff --git a/tests/regression/71-doublelocking/07-rec-dyn-osx.c b/tests/regression/71-doublelocking/07-rec-dyn-osx.c new file mode 100644 index 0000000000..bb3cf65657 --- /dev/null +++ b/tests/regression/71-doublelocking/07-rec-dyn-osx.c @@ -0,0 +1,103 @@ +// PARAM: --set ana.activated[+] 'maylocks' --set ana.activated[+] 'pthreadMutexType' --set pre.cppflags[+] "-DGOBLINT_NO_PTHREAD_ONCE" +// Here, we do not include pthread.h, so MutexAttr.recursive_int remains at `2`, emulating the behavior of OS X. +#define GOBLINT_NO_PTHREAD_ONCE 1 +typedef signed char __int8_t; +typedef unsigned char __uint8_t; +typedef short __int16_t; +typedef unsigned short __uint16_t; +typedef int __int32_t; +typedef unsigned int __uint32_t; +typedef long long __int64_t; +typedef unsigned long long __uint64_t; +typedef long __darwin_intptr_t; +typedef unsigned int __darwin_natural_t; +typedef int __darwin_ct_rune_t; + + +struct __darwin_pthread_handler_rec { + void (*__routine)(void *); + void *__arg; + struct __darwin_pthread_handler_rec *__next; +}; + +struct _opaque_pthread_attr_t { + long __sig; + char __opaque[56]; +}; + +struct _opaque_pthread_mutex_t { + long __sig; + char __opaque[56]; +}; + +struct _opaque_pthread_mutexattr_t { + long __sig; + char __opaque[8]; +}; + +struct _opaque_pthread_t { + long __sig; + struct __darwin_pthread_handler_rec *__cleanup_stack; + char __opaque[8176]; +}; + +typedef struct _opaque_pthread_attr_t __darwin_pthread_attr_t; +typedef struct _opaque_pthread_mutex_t __darwin_pthread_mutex_t; +typedef struct _opaque_pthread_mutexattr_t __darwin_pthread_mutexattr_t; +typedef struct _opaque_pthread_t *__darwin_pthread_t; + +typedef __darwin_pthread_attr_t pthread_attr_t; +typedef __darwin_pthread_mutex_t pthread_mutex_t; +typedef __darwin_pthread_mutexattr_t pthread_mutexattr_t; +typedef __darwin_pthread_t pthread_t; + +int pthread_create(pthread_t _Nullable restrict, + const pthread_attr_t * _Nullable restrict, + void * , + void *); + +int pthread_join(pthread_t , void *); +int pthread_mutex_init(pthread_mutex_t * restrict, const pthread_mutexattr_t * _Nullable restrict); +int pthread_mutex_lock(pthread_mutex_t *); +int pthread_mutex_unlock(pthread_mutex_t *); +int pthread_mutexattr_destroy(pthread_mutexattr_t *); +int pthread_mutexattr_init(pthread_mutexattr_t *); +int pthread_mutexattr_settype(pthread_mutexattr_t *, int); + + +int g; + +void* f1(void* ptr) { + pthread_mutex_t* mut = (pthread_mutex_t*) ptr; + + pthread_mutex_lock(mut); + pthread_mutex_lock(mut); //NOWARN + pthread_mutex_unlock(mut); + pthread_mutex_unlock(mut); + return ((void *)0); +} + + +int main(int argc, char const *argv[]) +{ + pthread_t t1; + pthread_mutex_t mut; + + pthread_mutexattr_t attr; + pthread_mutexattr_settype(&attr, 2); + pthread_mutex_init(&mut, &attr); + + + pthread_create(&t1,((void *)0),f1,&mut); + + + pthread_mutex_lock(&mut); + pthread_mutex_lock(&mut); //NOWARN + pthread_mutex_unlock(&mut); + pthread_mutex_unlock(&mut); + + pthread_join(t1, ((void *)0)); + + + return 0; +} diff --git a/tests/regression/71-doublelocking/08-other-type.c b/tests/regression/71-doublelocking/08-other-type.c new file mode 100644 index 0000000000..0f4a2f7887 --- /dev/null +++ b/tests/regression/71-doublelocking/08-other-type.c @@ -0,0 +1,66 @@ +// PARAM: --set ana.activated[+] 'maylocks' --set ana.activated[+] 'pthreadMutexType' +#define _GNU_SOURCE +#include +#include +#include +#include + + +int g; + +pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER; + +#ifndef __APPLE__ +pthread_mutex_t mut2 = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP; +pthread_mutex_t mut3 = PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP; +#else +// OS X does not define PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP +// we thus use the default one there, which should also create warnings +pthread_mutex_t mut3; +#endif + + +void* f1(void* ptr) { + int top; + + g = 1; + if(top) { + pthread_mutex_lock(&mut); + } + pthread_mutex_lock(&mut); //WARN + pthread_mutex_unlock(&mut); + return NULL; +} + +void* f2(void* ptr) { + int top; + + g = 1; + if(top) { + pthread_mutex_lock(&mut3); + } + pthread_mutex_lock(&mut3); //WARN + pthread_mutex_unlock(&mut3); + return NULL; +} + + + +int main(int argc, char const *argv[]) +{ + pthread_t t1; + pthread_t t2; + + pthread_create(&t1,NULL,f1,NULL); + pthread_create(&t2,NULL,f2,NULL); + pthread_join(t1, NULL); + +#ifndef __APPLE__ + pthread_mutex_lock(&mut2); //NOWARN + pthread_mutex_lock(&mut2); //NOWARN + pthread_mutex_unlock(&mut2); //NOWARN + pthread_mutex_unlock(&mut2); +#endif + + return 0; +} diff --git a/tests/regression/71-doublelocking/09-other-dyn.c b/tests/regression/71-doublelocking/09-other-dyn.c new file mode 100644 index 0000000000..cb4ef064f9 --- /dev/null +++ b/tests/regression/71-doublelocking/09-other-dyn.c @@ -0,0 +1,55 @@ +// PARAM: --set ana.activated[+] 'maylocks' --set ana.activated[+] 'pthreadMutexType' +#define _GNU_SOURCE +#include +#include +#include +#include + +int g; + +void* f1(void* ptr) { + pthread_mutex_t* mut = (pthread_mutex_t*) ptr; + + pthread_mutex_lock(mut); + pthread_mutex_lock(mut); //WARN + + return NULL; +} + + +void* f2(void* ptr) { + pthread_mutex_t* mut = (pthread_mutex_t*) ptr; + + pthread_mutex_lock(mut); + pthread_mutex_unlock(mut); + + // To check that this is now actually removed from the may lockset + return NULL; //NOWARN +} + + + +int main(int argc, char const *argv[]) +{ + pthread_t t1; + pthread_t t2; + pthread_mutex_t mut; + + pthread_mutexattr_t attr; + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL); + pthread_mutex_init(&mut, &attr); + + + pthread_create(&t1,NULL,f1,&mut); + + + pthread_mutex_lock(&mut); + pthread_mutex_lock(&mut); //WARN + + + pthread_join(t1, NULL); + + pthread_create(&t2,NULL,f2,&mut); + + return 0; +} diff --git a/tests/regression/71-doublelocking/10-thread-exit-recursive.c b/tests/regression/71-doublelocking/10-thread-exit-recursive.c new file mode 100644 index 0000000000..f360a98e48 --- /dev/null +++ b/tests/regression/71-doublelocking/10-thread-exit-recursive.c @@ -0,0 +1,33 @@ +// PARAM: --set ana.activated[+] 'maylocks' --set ana.activated[+] 'pthreadMutexType' +#define _GNU_SOURCE +#include +#include +#include +#include + +int g; + +void* f1(void* ptr) { + pthread_mutex_t* mut = (pthread_mutex_t*) ptr; + + pthread_mutex_lock(mut); //NOWARN + pthread_mutex_lock(mut); //NOWARN + pthread_mutex_unlock(mut); + return NULL; //WARN +} + + +int main(int argc, char const *argv[]) +{ + pthread_t t1; + pthread_mutex_t mut; + + pthread_mutexattr_t attr; + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&mut, &attr); + + + pthread_create(&t1,NULL,f1,&mut); + + return 0; +} diff --git a/tests/regression/71-doublelocking/11-rec-dyn-branch.c b/tests/regression/71-doublelocking/11-rec-dyn-branch.c new file mode 100644 index 0000000000..4fee99a765 --- /dev/null +++ b/tests/regression/71-doublelocking/11-rec-dyn-branch.c @@ -0,0 +1,50 @@ +// PARAM: --set ana.activated[+] 'maylocks' --set ana.activated[+] 'pthreadMutexType' +// Like 06, but tests mutexattr survives joins +#define _GNU_SOURCE +#include +#include +#include +#include + +int g; + +void* f1(void* ptr) { + pthread_mutex_t* mut = (pthread_mutex_t*) ptr; + + pthread_mutex_lock(mut); //NOWARN + pthread_mutex_lock(mut); //NOWARN + pthread_mutex_unlock(mut); + pthread_mutex_unlock(mut); + return NULL; +} + + +int main(int argc, char const *argv[]) +{ + pthread_t t1; + pthread_mutex_t mut; + + pthread_mutexattr_t attr; + + if(argc == 2) { + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + } else { + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + } + + pthread_mutex_init(&mut, &attr); + + + pthread_create(&t1,NULL,f1,&mut); + + + pthread_mutex_lock(&mut); //NOWARN + pthread_mutex_lock(&mut); //NOWARN + pthread_mutex_unlock(&mut); + pthread_mutex_unlock(&mut); + + pthread_join(t1, NULL); + + + return 0; +} diff --git a/tests/regression/71-doublelocking/12-rec-dyn-struct.c b/tests/regression/71-doublelocking/12-rec-dyn-struct.c new file mode 100644 index 0000000000..bf30db6898 --- /dev/null +++ b/tests/regression/71-doublelocking/12-rec-dyn-struct.c @@ -0,0 +1,56 @@ +// PARAM: --set ana.activated[+] 'maylocks' --set ana.activated[+] 'pthreadMutexType' +// Like 06, but tests mutexattr survives joins +#define _GNU_SOURCE +#include +#include +#include +#include + +int g; +struct s { + pthread_mutex_t mut; +}; + +typedef struct s s_t; + + +void* f1(void* ptr) { + pthread_mutex_t* mut = &(((s_t*) ptr)->mut); + + pthread_mutex_lock(mut); //NOWARN + pthread_mutex_lock(mut); //NOWARN + pthread_mutex_unlock(mut); + pthread_mutex_unlock(mut); + return NULL; +} + + +int main(int argc, char const *argv[]) +{ + pthread_t t1; + s_t mut_str; + + pthread_mutexattr_t attr; + + if(argc == 2) { + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + } else { + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + } + + pthread_mutex_init(&mut_str.mut, &attr); + + + pthread_create(&t1,NULL,f1,&mut_str); + + + pthread_mutex_lock(&mut_str.mut); //NOWARN + pthread_mutex_lock(&mut_str.mut); //NOWARN + pthread_mutex_unlock(&mut_str.mut); + pthread_mutex_unlock(&mut_str.mut); + + pthread_join(t1, NULL); + + + return 0; +} diff --git a/tests/regression/71-doublelocking/13-rec-struct.c b/tests/regression/71-doublelocking/13-rec-struct.c new file mode 100644 index 0000000000..272353e33b --- /dev/null +++ b/tests/regression/71-doublelocking/13-rec-struct.c @@ -0,0 +1,53 @@ +// PARAM: --set ana.activated[+] 'maylocks' --set ana.activated[+] 'pthreadMutexType' +#define _GNU_SOURCE +#include +#include +#include +#include + + +int g; + +struct s { + pthread_mutex_t m; +}; + +typedef struct s s_t; + +s_t mut = { PTHREAD_MUTEX_INITIALIZER }; + +#ifndef __APPLE__ +s_t mut2 = { PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP }; +#endif + + +void* f1(void* ptr) { + int top; + + g = 1; + if(top) { + pthread_mutex_lock(&mut.m); + } + pthread_mutex_lock(&mut.m); //WARN + pthread_mutex_unlock(&mut.m); + return NULL; +} + + +int main(int argc, char const *argv[]) +{ + pthread_t t1; + pthread_t t2; + + pthread_create(&t1,NULL,f1,NULL); + pthread_join(t1, NULL); + +#ifndef __APPLE__ + pthread_mutex_lock(&mut2.m); //NOWARN + pthread_mutex_lock(&mut2.m); //NOWARN + pthread_mutex_unlock(&mut2.m); + pthread_mutex_unlock(&mut2.m); +#endif + + return 0; +} diff --git a/tests/regression/71-doublelocking/14-rec-dyn-no-race.c b/tests/regression/71-doublelocking/14-rec-dyn-no-race.c new file mode 100644 index 0000000000..b49f481eee --- /dev/null +++ b/tests/regression/71-doublelocking/14-rec-dyn-no-race.c @@ -0,0 +1,45 @@ +// PARAM: --set ana.activated[+] 'pthreadMutexType' +#define _GNU_SOURCE +#include +#include +#include +#include + +int g; + +void* f1(void* ptr) { + pthread_mutex_t* mut = (pthread_mutex_t*) ptr; + + pthread_mutex_lock(mut); //NOWARN + pthread_mutex_lock(mut); //NOWARN + pthread_mutex_unlock(mut); + g = 8; //NORACE + pthread_mutex_unlock(mut); + return NULL; +} + + +int main(int argc, char const *argv[]) +{ + pthread_t t1; + pthread_mutex_t mut; + + pthread_mutexattr_t attr; + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&mut, &attr); + + + pthread_create(&t1,NULL,f1,&mut); + + + pthread_mutex_lock(&mut); //NOWARN + pthread_mutex_lock(&mut); //NOWARN + pthread_mutex_unlock(&mut); + g = 9; //NORACE + pthread_mutex_unlock(&mut); + + pthread_join(t1, NULL); + + + return 0; +} diff --git a/tests/regression/71-doublelocking/15-rec-dyn-nested.c b/tests/regression/71-doublelocking/15-rec-dyn-nested.c new file mode 100644 index 0000000000..d5dac9cd81 --- /dev/null +++ b/tests/regression/71-doublelocking/15-rec-dyn-nested.c @@ -0,0 +1,41 @@ +// PARAM: --set ana.activated[+] 'pthreadMutexType' +// Check we don't have a stack overflow because of tracking multiplicities +#define _GNU_SOURCE +#include +#include +#include +#include + +int g; + +void f2(pthread_mutex_t* mut) { + int top1, top2; + pthread_mutex_lock(mut); + if(top1 == top2) { + // This would cause the number of contexts to explode + f2(mut); + } + pthread_mutex_unlock(mut); +} + +void* f1(void* ptr) { + pthread_mutex_t* mut = (pthread_mutex_t*) ptr; + f2(mut); + return NULL; +} + + +int main(int argc, char const *argv[]) +{ + pthread_t t1; + pthread_mutex_t mut; + + pthread_mutexattr_t attr; + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&mut, &attr); + + + pthread_create(&t1,NULL,f1,&mut); + pthread_join(t1, NULL); + return 0; +} diff --git a/tests/regression/71-doublelocking/16-rec-dyn-no-path-sense.c b/tests/regression/71-doublelocking/16-rec-dyn-no-path-sense.c new file mode 100644 index 0000000000..2457ed3f62 --- /dev/null +++ b/tests/regression/71-doublelocking/16-rec-dyn-no-path-sense.c @@ -0,0 +1,51 @@ +// PARAM: --set ana.activated[+] 'pthreadMutexType' --set ana.path_sens[-] 'mutex' +// Test that multiplicity also works when path-sensitivity is disabled. +#define _GNU_SOURCE +#include +#include +#include +#include + +int g; + +void* f1(void* ptr) { + pthread_mutex_t* mut = (pthread_mutex_t*) ptr; + int top; + + + pthread_mutex_lock(mut); + + if(top) { + pthread_mutex_lock(mut); + } + + pthread_mutex_unlock(mut); + g = 8; //RACE! + + + return NULL; +} + + +int main(int argc, char const *argv[]) +{ + pthread_t t1; + pthread_mutex_t mut; + + pthread_mutexattr_t attr; + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&mut, &attr); + + + pthread_create(&t1,NULL,f1,&mut); + + + pthread_mutex_lock(&mut); + g = 9; // RACE! + pthread_mutex_unlock(&mut); + + pthread_join(t1, NULL); + + + return 0; +} diff --git a/tests/regression/72-thread_create_wrapper/01-wrapper.c b/tests/regression/72-thread_create_wrapper/01-wrapper.c new file mode 100644 index 0000000000..89cddd87bb --- /dev/null +++ b/tests/regression/72-thread_create_wrapper/01-wrapper.c @@ -0,0 +1,39 @@ +// PARAM: --set ana.activated[+] threadJoins --set ana.activated[+] threadCreateWrapper --set ana.thread.wrappers[+] my_pthread_create +#include +#include + +int my_pthread_create( + pthread_t *restrict thread, + const pthread_attr_t *restrict attr, + void *(*start_routine)(void *), + void *restrict arg +) { + return pthread_create(thread, attr, start_routine, arg); +} + +// uncomment to remove the wrapper +// #define my_pthread_create pthread_create + +int g = 0; +pthread_mutex_t A = PTHREAD_MUTEX_INITIALIZER; + +void *t_fun(void *arg) { + pthread_mutex_lock(&A); + g = 1; + pthread_mutex_unlock(&A); + return NULL; +} + +int main() { + pthread_t id1; + my_pthread_create(&id1, NULL, t_fun, NULL); + pthread_t id2; + my_pthread_create(&id2, NULL, t_fun, NULL); + + pthread_join(id1, NULL); + pthread_join(id2, NULL); + + g = 2; // NORACE + + return 0; +} diff --git a/tests/regression/72-thread_create_wrapper/02-unique-counter.c b/tests/regression/72-thread_create_wrapper/02-unique-counter.c new file mode 100644 index 0000000000..081d4dd49d --- /dev/null +++ b/tests/regression/72-thread_create_wrapper/02-unique-counter.c @@ -0,0 +1,40 @@ +// PARAM: --set ana.activated[+] threadJoins --set ana.activated[+] threadCreateWrapper --set ana.thread.unique_thread_id_count 1 +#include +#include + +// not marked as a wrapper this time: instead, the two calls are given unique IDs +int my_pthread_create( + pthread_t *restrict thread, + const pthread_attr_t *restrict attr, + void *(*start_routine)(void *), + void *restrict arg +) { + return pthread_create(thread, attr, start_routine, arg); +} + +// uncomment to remove the wrapper +// #define my_pthread_create pthread_create + +int g = 0; +pthread_mutex_t A = PTHREAD_MUTEX_INITIALIZER; + +void *t_fun(void *arg) { + pthread_mutex_lock(&A); + g = 1; + pthread_mutex_unlock(&A); + return NULL; +} + +int main() { + pthread_t id1; + my_pthread_create(&id1, NULL, t_fun, NULL); + pthread_t id2; + my_pthread_create(&id2, NULL, t_fun, NULL); + + pthread_join(id1, NULL); + pthread_join(id2, NULL); + + g = 2; // NORACE + + return 0; +} diff --git a/tests/regression/72-thread_create_wrapper/03-wrapper-unique-counter.c b/tests/regression/72-thread_create_wrapper/03-wrapper-unique-counter.c new file mode 100644 index 0000000000..2a6fcbd066 --- /dev/null +++ b/tests/regression/72-thread_create_wrapper/03-wrapper-unique-counter.c @@ -0,0 +1,50 @@ +// PARAM: --set ana.activated[+] threadJoins --set ana.activated[+] threadCreateWrapper --set ana.thread.wrappers[+] my_pthread_create --set ana.thread.unique_thread_id_count 1 +#include +#include + +// mark this as a wrapper, which is called multiple times in the same place +int my_pthread_create( + pthread_t *restrict thread, + const pthread_attr_t *restrict attr, + void *(*start_routine)(void *), + void *restrict arg +) { + return pthread_create(thread, attr, start_routine, arg); +} + +// this is not marked as a wrapper; instead each call to my_pthread_create is given a unique ID +int my_other_pthread_create( + pthread_t *restrict thread, + const pthread_attr_t *restrict attr, + void *(*start_routine)(void *), + void *restrict arg +) { + return my_pthread_create(thread, attr, start_routine, arg); +} + +// uncomment to remove the wrapper +// #define my_other_pthread_create pthread_create + +int g = 0; +pthread_mutex_t A = PTHREAD_MUTEX_INITIALIZER; + +void *t_fun(void *arg) { + pthread_mutex_lock(&A); + g = 1; + pthread_mutex_unlock(&A); + return NULL; +} + +int main() { + pthread_t id1; + my_other_pthread_create(&id1, NULL, t_fun, NULL); + pthread_t id2; + my_other_pthread_create(&id2, NULL, t_fun, NULL); + + pthread_join(id1, NULL); + pthread_join(id2, NULL); + + g = 2; // NORACE + + return 0; +} diff --git a/tests/regression/72-thread_create_wrapper/04-unique-counter-id-count-0.c b/tests/regression/72-thread_create_wrapper/04-unique-counter-id-count-0.c new file mode 100644 index 0000000000..0c79b04a0d --- /dev/null +++ b/tests/regression/72-thread_create_wrapper/04-unique-counter-id-count-0.c @@ -0,0 +1,43 @@ +// PARAM: --set ana.activated[+] threadJoins --set ana.activated[+] threadCreateWrapper --set ana.thread.unique_thread_id_count 0 +// Adapted from test `unique-counter`, but with `unique_thread_id_count` set to 0. +// We expect Goblint to find a race condition. + +#include +#include + +// not marked as a wrapper this time: instead, the two calls are given unique IDs +int my_pthread_create( + pthread_t *restrict thread, + const pthread_attr_t *restrict attr, + void *(*start_routine)(void *), + void *restrict arg +) { + return pthread_create(thread, attr, start_routine, arg); +} + +// uncomment to remove the wrapper +// #define my_pthread_create pthread_create + +int g = 0; +pthread_mutex_t A = PTHREAD_MUTEX_INITIALIZER; + +void *t_fun(void *arg) { + pthread_mutex_lock(&A); + g = 1; + pthread_mutex_unlock(&A); + return NULL; +} + +int main() { + pthread_t id1; + my_pthread_create(&id1, NULL, t_fun, NULL); + pthread_t id2; + my_pthread_create(&id2, NULL, t_fun, NULL); + + pthread_join(id1, NULL); + pthread_join(id2, NULL); + + g = 2; // RACE + + return 0; +} diff --git a/tests/regression/72-thread_create_wrapper/05-wrapper-unique-counter-id-count-0.c b/tests/regression/72-thread_create_wrapper/05-wrapper-unique-counter-id-count-0.c new file mode 100644 index 0000000000..eb4df18d5d --- /dev/null +++ b/tests/regression/72-thread_create_wrapper/05-wrapper-unique-counter-id-count-0.c @@ -0,0 +1,53 @@ +// PARAM: --set ana.activated[+] threadJoins --set ana.activated[+] threadCreateWrapper --set ana.thread.wrappers[+] my_pthread_create --set ana.thread.unique_thread_id_count 0 +// Adapted from test `wrapper-unique-counter`, but with `unique_thread_id_count` set to 0. +// We expect Goblint to find a race condition. + +#include +#include + +// mark this as a wrapper, which is called multiple times in the same place +int my_pthread_create( + pthread_t *restrict thread, + const pthread_attr_t *restrict attr, + void *(*start_routine)(void *), + void *restrict arg +) { + return pthread_create(thread, attr, start_routine, arg); +} + +// this is not marked as a wrapper; instead each call to my_pthread_create is given a unique ID +int my_other_pthread_create( + pthread_t *restrict thread, + const pthread_attr_t *restrict attr, + void *(*start_routine)(void *), + void *restrict arg +) { + return my_pthread_create(thread, attr, start_routine, arg); +} + +// uncomment to remove the wrapper +// #define my_other_pthread_create pthread_create + +int g = 0; +pthread_mutex_t A = PTHREAD_MUTEX_INITIALIZER; + +void *t_fun(void *arg) { + pthread_mutex_lock(&A); + g = 1; + pthread_mutex_unlock(&A); + return NULL; +} + +int main() { + pthread_t id1; + my_other_pthread_create(&id1, NULL, t_fun, NULL); + pthread_t id2; + my_other_pthread_create(&id2, NULL, t_fun, NULL); + + pthread_join(id1, NULL); + pthread_join(id2, NULL); + + g = 2; // RACE + + return 0; +} diff --git a/tests/regression/73-strings/01-string_literals.c b/tests/regression/73-strings/01-string_literals.c new file mode 100644 index 0000000000..36e4ed121c --- /dev/null +++ b/tests/regression/73-strings/01-string_literals.c @@ -0,0 +1,113 @@ +// PARAM: --disable ana.base.limit-string-addresses --enable ana.int.interval + +#include +#include + +char* hello_world() { + return "Hello world!"; +} + +void id(char* s) { + char* ptr = NULL; // future usage of cmp should warn: workaround for macOS test + #ifdef __APPLE__ + #define ID int i = strcmp(ptr, "trigger warning") + #else + #define ID strcpy(s, s) + #endif + ID; // WARN +} + +int main() { + char* s1 = "abcde"; + char* s2 = "abcdfg"; + char* s3 = hello_world(); + + int i = strlen(s1); + __goblint_check(i == 5); + + i = strlen(s2); + __goblint_check(i == 6); + + i = strlen(s3); + __goblint_check(i == 12); + + i = strcmp(s1, s2); + __goblint_check(i < 0); + + i = strcmp(s2, "abcdfg"); + __goblint_check(i == 0); + + char* cmp = strstr(s1, "bcd"); + i = strcmp(cmp, "bcde"); + __goblint_check(i == 0); + + cmp = strstr(s1, "bcdf"); + __goblint_check(cmp == NULL); + + if (rand() == 42) + s3 = "hello"; + else + s3 = "world"; + + cmp = strstr(s3, "l"); + __goblint_check(cmp != NULL); + + cmp = strstr(s3, "he"); + __goblint_check(cmp != NULL); // UNKNOWN + + i = strncmp(s1, s2, 4); + __goblint_check(i == 0); + + i = strncmp(s1, s2, 5); + __goblint_check(i != 0); + + /* the following portion fails on macOS because of a spurious warning: + * see issue goblint/cil#143 + * + * remove #ifdef's as soon as issue fixed */ + id(s2); + + cmp = NULL; // future usage of cmp should warn: workaround for macOS test + + #ifdef __APPLE__ + #define STRCPY i = strcmp(cmp, "trigger warning") + #else + #define STRCPY strcpy(s1, "hi"); + #endif + STRCPY; // WARN + + #ifdef __APPLE__ + #define STRNCPY i = strcmp(cmp, "trigger warning") + #else + # define STRNCPY strncpy(s1, "hi", 1) + #endif + STRNCPY; // WARN + + #ifdef __APPLE__ + #define STRCAT i = strcmp(cmp, "trigger warning") + #else + #define STRCAT strcat(s1, "hi") + #endif + STRCAT; // WARN + + #ifdef __APPLE__ + #define STRNCAT i = strcmp(cmp, "trigger warning") + #else + #define STRNCAT strncat(s1, "hi", 1) + #endif + STRNCAT; // WARN + + #ifdef __APPLE__ + // do nothing => no warning + #else + char s4[] = "hello"; + strcpy(s4, s2); // NOWARN + strncpy(s4, s3, 2); // NOWARN + + char s5[13] = "hello"; + strcat(s5, " world"); // NOWARN + strncat(s5, "! some further text", 1); // NOWARN + #endif + + return 0; +} diff --git a/tests/regression/73-strings/02-string_literals_with_null.c b/tests/regression/73-strings/02-string_literals_with_null.c new file mode 100644 index 0000000000..75d000bbb8 --- /dev/null +++ b/tests/regression/73-strings/02-string_literals_with_null.c @@ -0,0 +1,44 @@ +// PARAM: --disable ana.base.limit-string-addresses --enable ana.int.interval + +#include +#include + +int main() { + char* s1 = "hello\0 world\0!"; + char* s2 = "hello"; + char* s3 = "hello world!"; + char* s4 = "\0 i am the empty string"; + + int i = strlen(s1); + __goblint_check(i == 5); + + i = strcmp(s1, s2); + __goblint_check(i == 0); + + i = strcmp(s3, s1); + __goblint_check(i > 0); + + i = strcmp(s4, ""); + __goblint_check(i == 0); + + i = strncmp(s1, s3, 5); + __goblint_check(i == 0); + + i = strncmp(s2, s1, 7); + __goblint_check(i == 0); + + char* cmp = strstr(s3, s1); + i = strcmp(cmp, "hello world!"); + __goblint_check(i == 0); + + cmp = strstr(s1, "world"); + __goblint_check(cmp == NULL); + + cmp = strstr(s1, s4); + i = strcmp(cmp, s1); + __goblint_check(i == 0); + i = strcmp(cmp, "hello"); + __goblint_check(i == 0); + + return 0; +} \ No newline at end of file diff --git a/tests/regression/73-strings/03-string_basics.c b/tests/regression/73-strings/03-string_basics.c new file mode 100644 index 0000000000..db196c64b4 --- /dev/null +++ b/tests/regression/73-strings/03-string_basics.c @@ -0,0 +1,64 @@ +// PARAM: --disable ana.base.limit-string-addresses --enable ana.int.interval + +#include +#include +#include + +void concat_1(char* s, int i) { + if (i <= 0) + return; + else + strncat(s, "10", 1); + concat_1(s, i - 1); +} + +int main() { + char* s1 = malloc(40); + strcpy(s1, "hello "); + char s2[] = "world!"; + char s3[10] = "abcd"; + char s4[20] = "abcdf"; + + int i = strlen(s1); + __goblint_check(i == 6); // UNKNOWN + + i = strlen(s2); + __goblint_check(i == 6); // UNKNOWN + + i = strlen(s3); + __goblint_check(i == 4); // UNKNOWN + + strcat(s1, s2); + i = strcmp(s1, "hello world!"); + __goblint_check(i == 0); // UNKNOWN + + strcpy(s1, "hi "); + strncpy(s1, s3, 3); + i = strlen(s1); + __goblint_check(i == 3); // UNKNOWN + + strcat(s1, "ababcd"); + char* cmp = strstr(s1, "bab"); + __goblint_check(cmp != NULL); // UNKNOWN + + i = strcmp(cmp, "babcd"); // WARN: no check if cmp != NULL (even if it obviously is != NULL) + __goblint_check(i == 0); // UNKNOWN + + i = strncmp(s4, s3, 4); + __goblint_check(i == 0); // UNKNOWN + + i = strncmp(s4, s3, 5); + __goblint_check(i > 0); // UNKNOWN + + strncpy(s1, "", 20); + concat_1(s1, 30); + i = strlen(s1); + __goblint_check(i == 30); // UNKNOWN + + cmp = strstr(s1, "0"); + __goblint_check(cmp == NULL); // UNKNOWN + + free(s1); + + return 0; +} \ No newline at end of file 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/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-invalid_deref/02-conditional-uaf.c b/tests/regression/74-invalid_deref/02-conditional-uaf.c new file mode 100644 index 0000000000..f9873815fb --- /dev/null +++ b/tests/regression/74-invalid_deref/02-conditional-uaf.c @@ -0,0 +1,19 @@ +//PARAM: --set ana.activated[+] useAfterFree +#include +#include + +int main() { + int *ptr = malloc(sizeof(int)); + *ptr = 42; + + int input1; + + if (input1) { + free(ptr); + } + + *ptr = 43; //WARN + free(ptr); //WARN + + return 0; +} \ No newline at end of file diff --git a/tests/regression/74-invalid_deref/03-nested-ptr-uaf.c b/tests/regression/74-invalid_deref/03-nested-ptr-uaf.c new file mode 100644 index 0000000000..244a643706 --- /dev/null +++ b/tests/regression/74-invalid_deref/03-nested-ptr-uaf.c @@ -0,0 +1,19 @@ +//PARAM: --set ana.activated[+] useAfterFree +#include +#include + +int main() { + int *ptr = malloc(sizeof(int)); + *ptr = 1; + + free(ptr); + + int a[2] = {0, 1}; + a[*ptr] = 5; //WARN + + if (a[*ptr] != 5) { //WARN + free(ptr); //WARN + } + + return 0; +} \ No newline at end of file diff --git a/tests/regression/74-invalid_deref/04-function-call-uaf.c b/tests/regression/74-invalid_deref/04-function-call-uaf.c new file mode 100644 index 0000000000..d110db9edc --- /dev/null +++ b/tests/regression/74-invalid_deref/04-function-call-uaf.c @@ -0,0 +1,33 @@ +//PARAM: --set ana.activated[+] useAfterFree +#include +#include + +int *ptr1; + +int main() { + ptr1 = malloc(sizeof(int)); + *ptr1 = 100; + + int *ptr2 = malloc(sizeof(int)); + *ptr2 = 1; + + int *ptr3 = malloc(sizeof(int)); + *ptr3 = 10; + + free(ptr1); + free(ptr2); + + // No deref happening in the function call, hence nothing to warn about + f(ptr1, ptr2, ptr3); //NOWARN + + free(ptr3); //WARN + + return 0; +} + +void f(int *p1, int *p2, int *p3) { + *p1 = 5000; //WARN + free(p1); //WARN + free(p2); //WARN + free(p3); +} \ No newline at end of file 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-invalid_deref/09-juliet-uaf.c b/tests/regression/74-invalid_deref/09-juliet-uaf.c new file mode 100644 index 0000000000..e1a88508a6 --- /dev/null +++ b/tests/regression/74-invalid_deref/09-juliet-uaf.c @@ -0,0 +1,119 @@ +//PARAM: --set ana.activated[+] useAfterFree +#include +#include +#include +#include + +static char * helperBad(char * aString) +{ + size_t i = 0; + size_t j; + char * reversedString = NULL; + if (aString != NULL) + { + i = strlen(aString); + reversedString = (char *) malloc(i+1); + if (reversedString == NULL) {exit(-1);} + for (j = 0; j < i; j++) + { + reversedString[j] = aString[i-j-1]; + } + reversedString[i] = '\0'; + + free(reversedString); + // No need to warn in the line below, as there's no dereferencing happening + return reversedString; // NOWARN + } + else + { + return NULL; + } +} + +static char * helperGood(char * aString) +{ + size_t i = 0; + size_t j; + char * reversedString = NULL; + if (aString != NULL) + { + i = strlen(aString); + reversedString = (char *) malloc(i+1); + if (reversedString == NULL) {exit(-1);} + for (j = 0; j < i; j++) + { + reversedString[j] = aString[i-j-1]; + } + reversedString[i] = '\0'; + return reversedString; //NOWARN + } + else + { + return NULL; + } +} + +static int staticReturnsTrue() +{ + return 1; +} + +static int staticReturnsFalse() +{ + return 0; +} + +void CWE416_Use_After_Free__return_freed_ptr_08_bad() +{ + if(staticReturnsTrue()) + { + { + // 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 + } + } +} + +static void good1() +{ + if(staticReturnsFalse()) + { + /* INCIDENTAL: CWE 561 Dead Code, the code below will never run */ + printf("%s\n", "Benign, fixed string"); + } + else + { + { + char * reversedString = helperGood("GoodSink"); + printf("%s\n", reversedString); + } + } +} + +static void good2() +{ + if(staticReturnsTrue()) + { + { + char * reversedString = helperGood("GoodSink"); + printf("%s\n", reversedString); + } + } +} + +void CWE416_Use_After_Free__return_freed_ptr_08_good() +{ + good1(); + good2(); +} + + +int main(int argc, char * argv[]) +{ + CWE416_Use_After_Free__return_freed_ptr_08_good(); + CWE416_Use_After_Free__return_freed_ptr_08_bad(); + + return 0; +} \ No newline at end of file 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-invalid_deref/18-simple-uaf.c b/tests/regression/74-invalid_deref/18-simple-uaf.c new file mode 100644 index 0000000000..5a12179a24 --- /dev/null +++ b/tests/regression/74-invalid_deref/18-simple-uaf.c @@ -0,0 +1,15 @@ +//PARAM: --set ana.activated[+] useAfterFree +#include +#include + +int main() { + int *ptr = malloc(sizeof(int)); + *ptr = 42; + + free(ptr); + + *ptr = 43; //WARN + free(ptr); //WARN + + return 0; +} \ No newline at end of file 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-invalid_deref/24-uaf-free-in-wrapper-fun.c b/tests/regression/74-invalid_deref/24-uaf-free-in-wrapper-fun.c new file mode 100644 index 0000000000..e0eed9e800 --- /dev/null +++ b/tests/regression/74-invalid_deref/24-uaf-free-in-wrapper-fun.c @@ -0,0 +1,29 @@ +//PARAM: --set ana.activated[+] useAfterFree +# include +# include +# include +# include +# include + +int *p, *p_alias; +char buf[10]; + +void bad_func() { + free(p); // exit() is missing +} + +int main (int argc, char *argv[]) { + int f = open(argv[1], O_RDONLY); + read(f, buf, 10); + p = malloc(sizeof(int)); + + if (buf[0] == 'A') { + p_alias = malloc(sizeof(int)); + p = p_alias; + } + if (buf[1] == 'F') + bad_func(); + if (buf[2] == 'U') + *p = 1; //WARN + return 0; +} \ No newline at end of file diff --git a/tests/regression/74-invalid_deref/25-uaf-struct.c b/tests/regression/74-invalid_deref/25-uaf-struct.c new file mode 100644 index 0000000000..fa3ffc7b56 --- /dev/null +++ b/tests/regression/74-invalid_deref/25-uaf-struct.c @@ -0,0 +1,48 @@ +//PARAM: --set ana.activated[+] useAfterFree +#include +#include +#include +#include +#include + +struct auth { + char name[32]; + int auth; +}; + +struct auth *auth; +char *service; + +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) { + // 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 + } + } + if (strncmp(line, "reset", 5) == 0) { + free(auth); //WARN + } + if (strncmp(line, "service", 6) == 0) { + service = strdup(line + 7); + } + if (strncmp(line, "login", 5) == 0) { + if (auth->auth) { //WARN + printf("you have logged in already!\n"); + } else { + printf("please enter your password\n"); + } + } + } +} \ No newline at end of file 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-invalid_deref/27-wrapper-funs-uaf.c b/tests/regression/74-invalid_deref/27-wrapper-funs-uaf.c new file mode 100644 index 0000000000..cc6539eff2 --- /dev/null +++ b/tests/regression/74-invalid_deref/27-wrapper-funs-uaf.c @@ -0,0 +1,40 @@ +//PARAM: --set ana.activated[+] useAfterFree +#include +#include + +void *my_malloc(size_t size) { + return malloc(size); +} + +void my_free(void *ptr) { + free(ptr); +} + +void *my_malloc2(size_t size) { + return my_malloc(size); +} + +void my_free2(void *ptr) { + my_free(ptr); +} + +int main(int argc, char const *argv[]) { + char *p = my_malloc2(50 * sizeof(char)); + + *(p + 42) = 'c'; //NOWARN + printf("%s", p); //NOWARN + + my_free2(p); + + *(p + 42) = 'c'; //WARN + // printf() is considered an implicit deref => need to warn + printf("%s", p); //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-invalid_deref/28-multi-threaded-uaf.c b/tests/regression/74-invalid_deref/28-multi-threaded-uaf.c new file mode 100644 index 0000000000..f6d11ae098 --- /dev/null +++ b/tests/regression/74-invalid_deref/28-multi-threaded-uaf.c @@ -0,0 +1,30 @@ +//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_other(void* p) { + pthread_mutex_lock(&mtx); + free(gptr); //WARN + pthread_mutex_unlock(&mtx); +} + +int main() { + gptr = malloc(sizeof(int)); + *gptr = 42; + + pthread_t thread; + pthread_create(&thread, NULL, t_other, NULL); + + pthread_mutex_lock(&mtx); + *gptr = 43; //WARN + free(gptr); //WARN + pthread_mutex_unlock(&mtx); + + return 0; +} \ No newline at end of file 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/75-invalid_free/01-invalid-dealloc-simple.c b/tests/regression/75-invalid_free/01-invalid-dealloc-simple.c new file mode 100644 index 0000000000..16fbd593f4 --- /dev/null +++ b/tests/regression/75-invalid_free/01-invalid-dealloc-simple.c @@ -0,0 +1,14 @@ +#include + +int main(int argc, char const *argv[]) +{ + int a; + int *p = &a; + free(p); //WARN + + char b = 'b'; + char *p2 = &b; + free(p2); //WARN + + return 0; +} diff --git a/tests/regression/75-invalid_free/02-invalid-dealloc-struct.c b/tests/regression/75-invalid_free/02-invalid-dealloc-struct.c new file mode 100644 index 0000000000..6768103976 --- /dev/null +++ b/tests/regression/75-invalid_free/02-invalid-dealloc-struct.c @@ -0,0 +1,14 @@ +#include + +typedef struct custom_t { + int x; + int y; +} custom_t; + +int main(int argc, char const *argv[]) +{ + custom_t *var; + free(var); //WARN + + return 0; +} diff --git a/tests/regression/75-invalid_free/03-invalid-dealloc-array.c b/tests/regression/75-invalid_free/03-invalid-dealloc-array.c new file mode 100644 index 0000000000..c023b5fc53 --- /dev/null +++ b/tests/regression/75-invalid_free/03-invalid-dealloc-array.c @@ -0,0 +1,25 @@ +#include + +typedef struct custom_t { + int x; + int y; +} custom_t; + +#define MAX_SIZE 5000 + +int main(int argc, char const *argv[]) +{ + custom_t custom_arr[MAX_SIZE]; + free(custom_arr); //WARN + + int int_arr[MAX_SIZE]; + free(int_arr); //WARN + + char char_arr[MAX_SIZE]; + free(char_arr); //WARN + + char char_arr2[1]; + free(char_arr2); //WARN + + return 0; +} diff --git a/tests/regression/75-invalid_free/04-invalid-realloc.c b/tests/regression/75-invalid_free/04-invalid-realloc.c new file mode 100644 index 0000000000..94cbf031c2 --- /dev/null +++ b/tests/regression/75-invalid_free/04-invalid-realloc.c @@ -0,0 +1,25 @@ +#include + +typedef struct custom_t { + int x; + int y; +} custom_t; + +#define MAX_SIZE 5000 + +int main(int argc, char const *argv[]) +{ + custom_t custom_arr[10]; + realloc(custom_arr, MAX_SIZE); //WARN + + int int_arr[100]; + realloc(int_arr, MAX_SIZE); //WARN + + char char_arr[1000]; + realloc(char_arr, MAX_SIZE); //WARN + + char char_arr2[1]; + realloc(char_arr2, MAX_SIZE); //WARN + + return 0; +} 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/75-invalid_free/08-itc-no-double-free.c b/tests/regression/75-invalid_free/08-itc-no-double-free.c new file mode 100644 index 0000000000..f2f501f1bc --- /dev/null +++ b/tests/regression/75-invalid_free/08-itc-no-double-free.c @@ -0,0 +1,209 @@ +//PARAM: --set ana.activated[+] useAfterFree --enable ana.int.interval +#include + +void double_free_001() +{ + char* ptr= (char*) malloc(sizeof(char)); + + free(ptr); //NOWARN (Double Free (CWE-415)) +} + +void double_free_002() +{ + char* ptr= (char*) malloc(10*sizeof(char)); + int i; + + for(i=0;i<10;i++) + { + ptr[i]='a'; + if(i==10) + free(ptr); + } + free(ptr); //NOWARN (Double Free (CWE-415)) +} + +void double_free_003() +{ + char* ptr= (char*) malloc(10*sizeof(char)); + int i; + + for(i=0;i<10;i++) + { + *(ptr+i)='a'; + + } + + free(ptr); //NOWARN (Double Free (CWE-415)) +} + +void double_free_004() +{ + char* ptr= (char*) malloc(10*sizeof(char)); + int i; + for(i=0;i<10;i++) + { + *(ptr+i)='a'; + + } + free(ptr); //NOWARN (Double Free (CWE-415)) +} + +void double_free_005() +{ + char* ptr= (char*) malloc(sizeof(char)); + + if(ptr) + free(ptr); //NOWARN (Double Free (CWE-415)) +} + +void double_free_006() +{ + char* ptr= (char*) malloc(sizeof(char)); + if(0) + free(ptr); + + free(ptr); //NOWARN (Double Free (CWE-415)) +} + +void double_free_007() +{ + char* ptr= (char*) malloc(sizeof(char)); + int flag=0; + + if(flag<0) + free(ptr); + + free(ptr); //NOWARN (Double Free (CWE-415)) +} + +char *double_free_function_008_gbl_ptr; + +void double_free_function_008() +{ + free (double_free_function_008_gbl_ptr); //NOWARN (Double Free (CWE-415)) +} + +void double_free_008() +{ + double_free_function_008_gbl_ptr= (char*) malloc(sizeof(char)); + + double_free_function_008(); +} + +void double_free_009() +{ + char* ptr= (char*) malloc(sizeof(char)); + int flag=0; + + while(flag==1) + { + free(ptr); + flag++; + } + free(ptr); //NOWARN (Double Free (CWE-415)) +} + +void double_free_010() +{ + char* ptr= (char*) malloc(sizeof(char)); + int flag=1; + + while(flag) + { + // We're currently too unprecise to properly detect this below (due to the loop) + free(ptr); // (Double Free (CWE-415)) + flag--; + } +} + +void double_free_011() +{ + char* ptr= (char*) malloc(sizeof(char)); + int flag=1,a=0,b=1; + + while(a +#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/75-invalid_free/11-itc-double-free.c b/tests/regression/75-invalid_free/11-itc-double-free.c new file mode 100644 index 0000000000..d0d35eccee --- /dev/null +++ b/tests/regression/75-invalid_free/11-itc-double-free.c @@ -0,0 +1,222 @@ +//PARAM: --set ana.activated[+] useAfterFree +#include + +void double_free_001() +{ + char* ptr= (char*) malloc(sizeof(char)); + free(ptr); + + free(ptr); //WARN (Double Free (CWE-415)) +} + +void double_free_002() +{ + char* ptr= (char*) malloc(10*sizeof(char)); + int i; + + for(i=0;i<10;i++) + { + ptr[i]='a'; + if(i==9) + { + free(ptr); + } + } + free(ptr); //WARN (Double Free (CWE-415)) +} + +void double_free_003() +{ + char* ptr= (char*) malloc(10*sizeof(char)); + int i; + + for(i=0;i<10;i++) + { + *(ptr+i)='a'; + if(i==9) + { + free(ptr); + } + } + free(ptr); //WARN (Double Free (CWE-415)) +} + +void double_free_004() +{ + char* ptr= (char*) malloc(10*sizeof(char)); + int i; + for(i=0;i<10;i++) + { + *(ptr+i)='a'; + } + + if (rand() % 2==0) + { + free(ptr); + } + + if(rand() % 3==0) + free(ptr); //WARN (Double Free (CWE-415)) +} + +void double_free_005() +{ + char* ptr= (char*) malloc(sizeof(char)); + free(ptr); + + if(ptr) + free(ptr); //WARN (Double Free (CWE-415)) +} + +void double_free_006() +{ + char* ptr= (char*) malloc(sizeof(char)); + if(1) + free(ptr); + + free(ptr); //WARN (Double Free (CWE-415)) +} + +void double_free_007() +{ + char* ptr= (char*) malloc(sizeof(char)); + int flag=0; + + if(flag>=0) + free(ptr); + + free(ptr); //WARN (Double Free (CWE-415)) +} + +char *double_free_function_008_gbl_ptr; + +void double_free_function_008() +{ + free (double_free_function_008_gbl_ptr); +} + +void double_free_008() +{ + double_free_function_008_gbl_ptr= (char*) malloc(sizeof(char)); + + double_free_function_008(); + free(double_free_function_008_gbl_ptr); //WARN (Double Free (CWE-415)) +} + +void double_free_009() +{ + char* ptr= (char*) malloc(sizeof(char)); + int flag=0; + + while(flag==0) + { + free(ptr); + flag++; + } + free(ptr); //WARN (Double Free (CWE-415)) +} + +void double_free_010() +{ + char* ptr= (char*) malloc(sizeof(char)); + int flag=1; + + while(flag) + { + free(ptr); + flag--; + } + free(ptr); //WARN (Double Free (CWE-415)) +} + +void double_free_011() +{ + char* ptr= (char*) malloc(sizeof(char)); + int flag=1,a=0,b=2; + + while(a + +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/75-invalid_free/13-juliet-double-free.c b/tests/regression/75-invalid_free/13-juliet-double-free.c new file mode 100644 index 0000000000..c1ab5310c9 --- /dev/null +++ b/tests/regression/75-invalid_free/13-juliet-double-free.c @@ -0,0 +1,105 @@ +//PARAM: --set ana.activated[+] useAfterFree +#include +#include +#include +#include +#include + +typedef struct _twoIntsStruct { + int intOne; + int intTwo; +} twoIntsStruct; + +static int staticTrue = 1; /* true */ +static int staticFalse = 0; /* false */ + +void CWE415_Double_Free__malloc_free_struct_05_bad() +{ + twoIntsStruct * data; + data = NULL; + if(staticTrue) + { + data = (twoIntsStruct *)malloc(100*sizeof(twoIntsStruct)); + if (data == NULL) {exit(-1);} + free(data); + } + if(staticTrue) + { + free(data); //WARN (Double Free (CWE-415)) + } +} + +static void goodB2G1() +{ + twoIntsStruct * data; + data = NULL; + if(staticTrue) + { + data = (twoIntsStruct *)malloc(100*sizeof(twoIntsStruct)); + if (data == NULL) {exit(-1);} + free(data); + } +} + +static void goodB2G2() +{ + twoIntsStruct * data; + data = NULL; + if(staticTrue) + { + data = (twoIntsStruct *)malloc(100*sizeof(twoIntsStruct)); + if (data == NULL) {exit(-1);} + free(data); + } +} + +static void goodG2B1() +{ + twoIntsStruct * data; + data = NULL; + if(staticFalse) + { + /* INCIDENTAL: CWE 561 Dead Code, the code below will never run */ + printf("%s\n", "Benign, fixed string"); + } + else + { + data = (twoIntsStruct *)malloc(100*sizeof(twoIntsStruct)); + if (data == NULL) {exit(-1);} + } + if(staticTrue) + { + free(data); + } +} + +static void goodG2B2() +{ + twoIntsStruct * data; + data = NULL; + if(staticTrue) + { + data = (twoIntsStruct *)malloc(100*sizeof(twoIntsStruct)); + if (data == NULL) {exit(-1);} + } + if(staticTrue) + { + free(data); + } +} + +void CWE415_Double_Free__malloc_free_struct_05_good() +{ + goodB2G1(); + goodB2G2(); + goodG2B1(); + goodG2B2(); +} + +int main(int argc, char * argv[]) +{ + CWE415_Double_Free__malloc_free_struct_05_good(); + CWE415_Double_Free__malloc_free_struct_05_bad(); + + return 0; +} \ No newline at end of file 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/99-tutorials/03-taint_simple.c b/tests/regression/99-tutorials/03-taint_simple.c index d9d00351c1..4cc206d949 100644 --- a/tests/regression/99-tutorials/03-taint_simple.c +++ b/tests/regression/99-tutorials/03-taint_simple.c @@ -31,7 +31,7 @@ int main(void) { // Trivial example showing how the analysis you just wrote benefits from other analyses - // If we wanted to write a real analysis, we would also aks other analyses questions, to e.g. handle pointers + // If we wanted to write a real analysis, we would also ask other analyses questions, to e.g. handle pointers int z; if(z == 0) { z = 5; 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/cdomains/floatDomainTest.ml b/unittest/cdomains/floatDomainTest.ml index 48edd47a12..da7aae7d1c 100644 --- a/unittest/cdomains/floatDomainTest.ml +++ b/unittest/cdomains/floatDomainTest.ml @@ -1,350 +1,354 @@ -open Goblint_lib -open OUnit2 -open FloatOps - -module FloatInterval(Float_t: CFloatType)(Domain_t: FloatDomain.FloatDomainBase) = -struct - module FI = Domain_t - module IT = IntDomain.IntDomTuple - - let to_float = Float_t.to_float - let of_float = Float_t.of_float - let add = Float_t.add - let sub = Float_t.sub - let mul = Float_t.mul - let div = Float_t.div - - let pred x = Option.get (to_float (Float_t.pred (of_float Nearest x))) - let succ x = Option.get (to_float (Float_t.succ (of_float Nearest x))) - - let fmax = Option.get (to_float Float_t.upper_bound) - let fmin = Option.get (to_float Float_t.lower_bound) - let fsmall = Option.get (to_float Float_t.smallest) - - let fi_zero = FI.of_const 0. - let fi_one = FI.of_const 1. - let fi_neg_one = FI.of_const (-.1.) - let itb_true = IT.of_int IBool (Big_int_Z.big_int_of_int 1) - let itb_false = IT.of_int IBool (Big_int_Z.big_int_of_int 0) - let itb_unknown = IT.of_interval IBool (Big_int_Z.big_int_of_int 0, Big_int_Z.big_int_of_int 1) - - let assert_equal v1 v2 = - assert_equal ~cmp:FI.equal ~printer:FI.show v1 v2 - - let itb_xor v1 v2 = (**returns true, if both IntDomainTuple bool results are either unknown or different *) - ((IT.equal v1 itb_unknown) && (IT.equal v2 itb_unknown)) || ((IT.equal v1 itb_true) && (IT.equal v2 itb_false)) || ((IT.equal v1 itb_false) && (IT.equal v2 itb_true)) - - (**interval tests *) - let test_FI_nan _ = - assert_equal (FI.top ()) (FI.of_const Float.nan) - - - let test_FI_add_specific _ = - let (+) = FI.add in - let (=) a b = assert_equal b a in - begin - (FI.of_const (-. 0.)) = fi_zero; - fi_zero + fi_one = fi_one; - fi_neg_one + fi_one = fi_zero; - fi_one + (FI.of_const fmax) = FI.top (); - fi_neg_one + (FI.of_const fmin) = FI.top (); - fi_neg_one + (FI.of_const fmax) = (FI.of_interval ((pred fmax), fmax)); - fi_one + (FI.of_const fmin) = (FI.of_interval (fmin, succ fmin)); - FI.top () + FI.top () = FI.top (); - (FI.of_const fmin) + (FI.of_const fmax) = fi_zero; - (FI.of_const fsmall) + (FI.of_const fsmall) = FI.of_const (fsmall +. fsmall); - let one_plus_fsmall = Option.get (to_float (Float_t.add Up (Float_t.of_float Up 1.) Float_t.smallest)) in - (FI.of_const fsmall) + (FI.of_const 1.) = FI.of_interval (1., one_plus_fsmall); - (FI.of_interval (1., 2.)) + (FI.of_interval (2., 3.)) = FI.of_interval (3., 5.); - (FI.of_interval (-. 2., 3.)) + (FI.of_interval (-. 100., 20.)) = FI.of_interval (-. 102., 23.); - end - - let test_FI_sub_specific _ = - let (-) = FI.sub in - let (=) a b = assert_equal b a in - begin - fi_zero - fi_one = fi_neg_one; - fi_neg_one - fi_one = FI.of_const (-. 2.); - fi_one - (FI.of_const fmin) = FI.top (); - fi_neg_one - (FI.of_const fmax) = FI.top (); - (FI.of_const fmax) - fi_one = (FI.of_interval ((pred fmax), fmax)); - (FI.of_const fmin) - fi_neg_one = (FI.of_interval (fmin, succ fmin)); - FI.top () - FI.top () = FI.top (); - (FI.of_const fmax) - (FI.of_const fmax) = fi_zero; - (FI.of_const fsmall) - (FI.of_const fsmall) = fi_zero; - (FI.of_const fsmall) - (FI.of_const 1.) = FI.of_interval (-. 1., succ (-. 1.)); - (FI.of_interval (-. 2., 3.)) - (FI.of_interval (-. 100., 20.)) = FI.of_interval (-. 22., 103.); - (FI.of_const (-. 0.)) - fi_zero = fi_zero - end - - let test_FI_mul_specific _ = - let ( * ) = FI.mul in - let (=) a b = assert_equal b a in - begin - fi_zero * fi_one = fi_zero; - (FI.of_const 2.) * (FI.of_const fmin) = FI.top (); - (FI.of_const 2.) * (FI.of_const fmax) = FI.top (); - (FI.of_const fsmall) * (FI.of_const fmax) = FI.of_const (fsmall *. fmax); - FI.top () * FI.top () = FI.top (); - (FI.of_const fmax) * fi_zero = fi_zero; - (FI.of_const fsmall) * fi_zero = fi_zero; - (FI.of_const fsmall) * fi_one = FI.of_const fsmall; - (FI.of_const fmax) * fi_one = FI.of_const fmax; - (FI.of_const 2.) * (FI.of_const 0.5) = fi_one; - (FI.of_interval (-. 2., 3.)) * (FI.of_interval (-. 100., 20.)) = FI.of_interval (-. 300., 200.); - - let up = if Float_t.name <> "float" then succ 1.00000000000000222 else succ (succ 1.00000000000000111 *. succ 1.00000000000000111) in - begin - (FI.of_const 1.00000000000000111) * (FI.of_const 1.00000000000000111) = FI.of_interval (1.00000000000000222 , up); - (FI.of_const (-. 1.00000000000000111)) * (FI.of_const 1.00000000000000111) = FI.of_interval (-. up, -. 1.00000000000000222) - end - end - - let test_FI_div_specific _ = - let (/) = FI.div in - let (=) a b = assert_equal b a in - begin - fi_zero / fi_one = fi_zero; - (FI.of_const 2.) / fi_zero = FI.top (); - fi_zero / fi_zero = FI.nan (); - (FI.of_const fmax) / (FI.of_const fsmall) = FI.top (); - (FI.of_const fmin) / (FI.of_const fsmall) = FI.top (); - FI.top () / FI.top () = FI.top (); - fi_zero / fi_one = fi_zero; - (FI.of_const fsmall) / fi_one = FI.of_const fsmall; - (FI.of_const fsmall) / (FI.of_const fsmall) = fi_one; - (FI.of_const fmax) / (FI.of_const fmax) = fi_one; - (FI.of_const fmax) / fi_one = FI.of_const fmax; - (FI.of_const 2.) / (FI.of_const 0.5) = (FI.of_const 4.); - (FI.of_const 4.) / (FI.of_const 2.) = (FI.of_const 2.); - (FI.of_interval (-. 2., 3.)) / (FI.of_interval (-. 100., 20.)) = FI.top (); - (FI.of_interval (6., 10.)) / (FI.of_interval (2., 3.)) = (FI.of_interval (2., 5.)); - - (FI.of_const 1.) / (FI.of_const 3.) = (FI.of_interval (pred 0.333333333333333370340767487505, 0.333333333333333370340767487505)); - (FI.of_const (-. 1.)) / (FI.of_const 3.) = (FI.of_interval (-. 0.333333333333333370340767487505, succ (-. 0.333333333333333370340767487505))) - end - - let test_FI_casti2f_specific _ = - let cast_bool a b = - assert_equal b (FI.of_int (IT.of_int IBool (Big_int_Z.big_int_of_int a))) in - begin - cast_bool 0 fi_zero; - cast_bool 1 fi_one - end; - let cast a b = assert_equal b (FI.of_int a) in - begin - GobConfig.set_bool "ana.int.interval" true; - cast (IT.top_of IInt) (FI.of_interval (-2147483648.,2147483647.)); - cast (IT.top_of IBool) (FI.of_interval (0., 1.)); - cast (IT.of_int IInt Big_int_Z.zero_big_int) fi_zero; - cast (IT.of_int IInt Big_int_Z.unit_big_int) fi_one; - (* no IChar because char has unknown signedness (particularly, unsigned on arm64) *) - cast (IT.of_interval IUChar (Big_int_Z.big_int_of_int 0, Big_int_Z.big_int_of_int 128)) (FI.of_interval (0., 128.)); - cast (IT.of_interval ISChar (Big_int_Z.big_int_of_int (-8), Big_int_Z.big_int_of_int (-1))) (FI.of_interval (-. 8., - 1.)); - cast (IT.of_interval IUInt (Big_int_Z.big_int_of_int 2, Big_int_Z.big_int_of_int 100)) (FI.of_interval (2., 100.)); - cast (IT.of_interval IInt (Big_int_Z.big_int_of_int (- 100), Big_int_Z.big_int_of_int 100)) (FI.of_interval (-. 100., 100.)); - cast (IT.of_interval IUShort (Big_int_Z.big_int_of_int 2, Big_int_Z.big_int_of_int 100)) (FI.of_interval (2., 100.)); - cast (IT.of_interval IShort (Big_int_Z.big_int_of_int (- 100), Big_int_Z.big_int_of_int 100)) (FI.of_interval (-. 100., 100.)); - - cast (IT.of_interval IULong (Big_int_Z.zero_big_int, Big_int_Z.big_int_of_int64 Int64.max_int)) (FI.of_interval (0., 9223372036854775807.)); - cast (IT.of_interval IULong (Big_int_Z.zero_big_int, Big_int_Z.big_int_of_int64 (9223372036854775806L))) (FI.of_interval (0., 9223372036854775807.)); - cast (IT.of_interval ILong (Big_int_Z.big_int_of_int64 Int64.min_int, Big_int_Z.zero_big_int)) (FI.of_interval (-. 9223372036854775808., 0.)); - cast (IT.of_interval ILong (Big_int_Z.big_int_of_int (- 100), Big_int_Z.big_int_of_int 100)) (FI.of_interval (-. 100., 100.)); - cast (IT.of_interval IULongLong (Big_int_Z.zero_big_int, Big_int_Z.big_int_of_int64 Int64.max_int)) (FI.of_interval (0., 9223372036854775807.)); - cast (IT.of_interval IULongLong (Big_int_Z.zero_big_int, Big_int_Z.big_int_of_int64 (9223372036854775806L))) (FI.of_interval (0., 9223372036854775807.)); - cast (IT.of_interval ILongLong (Big_int_Z.big_int_of_int64 Int64.min_int, Big_int_Z.zero_big_int)) (FI.of_interval (-. 9223372036854775808., 0.)); - cast (IT.of_interval ILongLong (Big_int_Z.big_int_of_int (- 100), Big_int_Z.big_int_of_int 100)) (FI.of_interval (-. 100., 100.)); - GobConfig.set_bool "ana.int.interval" false; - end - - let test_FI_castf2i_specific _ = - let cast ikind a b = - OUnit2.assert_equal ~cmp:IT.equal ~printer:IT.show b (FI.to_int ikind a) in - begin - GobConfig.set_bool "ana.int.interval" true; - cast IInt (FI.of_interval (-2147483648.,2147483647.)) (IT.top_of IInt); - cast IInt (FI.of_interval (-9999999999.,9999999999.)) (IT.top_of IInt); - cast IInt (FI.of_interval (-10.1,20.9)) (IT.of_interval IInt ( Big_int_Z.big_int_of_int (-10), Big_int_Z.big_int_of_int 20)); - cast IBool (FI.of_interval (0.,1.)) (IT.top_of IBool); - cast IBool (FI.of_interval (-9999999999.,9999999999.)) (IT.top_of IBool); - cast IBool fi_one (IT.of_bool IBool true); - cast IBool fi_zero (IT.of_bool IBool false); - - (* no IChar because char has unknown signedness (particularly, unsigned on arm64) *) - cast IUChar (FI.of_interval (0.123, 128.999)) (IT.of_interval IUChar (Big_int_Z.big_int_of_int 0, Big_int_Z.big_int_of_int 128)); - cast ISChar (FI.of_interval (-. 8.0000000, 127.)) (IT.of_interval ISChar (Big_int_Z.big_int_of_int (-8), Big_int_Z.big_int_of_int 127)); - cast IUInt (FI.of_interval (2., 100.)) (IT.of_interval IUInt (Big_int_Z.big_int_of_int 2, Big_int_Z.big_int_of_int 100)); - cast IInt (FI.of_interval (-. 100.2, 100.1)) (IT.of_interval IInt (Big_int_Z.big_int_of_int (- 100), Big_int_Z.big_int_of_int 100)); - cast IUShort (FI.of_interval (2., 100.)) (IT.of_interval IUShort (Big_int_Z.big_int_of_int 2, Big_int_Z.big_int_of_int 100)); - cast IShort (FI.of_interval (-. 100., 100.)) (IT.of_interval IShort (Big_int_Z.big_int_of_int (- 100), Big_int_Z.big_int_of_int 100)); - - cast IULong (FI.of_interval (0., 9223372036854775808.)) (IT.of_interval IULong (Big_int_Z.zero_big_int, Big_int_Z.big_int_of_string "9223372036854775808")); - cast ILong (FI.of_interval (-. 9223372036854775808., 0.)) (IT.of_interval ILong (Big_int_Z.big_int_of_string "-9223372036854775808", Big_int_Z.zero_big_int)); - cast ILong (FI.of_interval (-. 100.99999, 100.99999)) (IT.of_interval ILong (Big_int_Z.big_int_of_int (- 100), Big_int_Z.big_int_of_int 100)); - cast IULongLong (FI.of_interval (0., 9223372036854775808.)) (IT.of_interval IULongLong (Big_int_Z.zero_big_int, Big_int_Z.big_int_of_string "9223372036854775808")); - cast ILongLong (FI.of_interval (-. 9223372036854775808., 0.)) (IT.of_interval ILongLong ((Big_int_Z.big_int_of_string "-9223372036854775808"), Big_int_Z.zero_big_int)); - cast ILongLong (FI.of_interval (-. 100., 100.)) (IT.of_interval ILongLong (Big_int_Z.big_int_of_int (- 100), Big_int_Z.big_int_of_int 100)); - GobConfig.set_bool "ana.int.interval" false; - end - - let test_FI_meet_specific _ = - let check_meet a b c = - assert_equal c (FI.meet a b) in - begin - check_meet (FI.top ()) (FI.top ()) (FI.top ()); - check_meet (FI.top ()) fi_one fi_one; - check_meet fi_zero fi_one (FI.bot ()); - check_meet (FI.of_interval (0., 10.)) (FI.of_interval (5., 20.)) (FI.of_interval (5., 10.)); - end - - let test_FI_join_specific _ = - let check_join a b c = - assert_equal c (FI.join a b) in - begin - check_join (FI.top ()) (FI.top ()) (FI.top ()); - check_join (FI.top ()) fi_one (FI.top ()); - check_join (FI.of_interval (0., 10.)) (FI.of_interval (5., 20.)) (FI.of_interval (0., 20.)); - end - - let test_FI_leq_specific _ = - let check_leq flag a b = - OUnit2.assert_equal flag (FI.leq a b) in - begin - check_leq true (FI.top ()) (FI.top ()); - check_leq true fi_one fi_one; - check_leq false fi_one fi_zero; - check_leq true (FI.of_interval (5., 20.)) (FI.of_interval (0., 20.)); - check_leq false (FI.of_interval (0., 20.)) (FI.of_interval (5., 20.)); - check_leq true (FI.of_interval (1., 19.)) (FI.of_interval (0., 20.)); - check_leq false (FI.of_interval (0., 20.)) (FI.of_interval (20.0001, 20.0002)); - end - - let test_FI_widen_specific _ = - let check_widen a b c = - assert_equal c (FI.widen a b) in - begin - check_widen (FI.top ()) (FI.top ()) (FI.top ()); - check_widen fi_zero (FI.top ()) (FI.top ()); - check_widen (FI.top ()) fi_one (FI.top ()); - check_widen fi_zero fi_one (FI.of_interval (0., fmax)); - check_widen fi_one fi_zero (FI.of_interval (fmin, 1.)); - check_widen fi_one (FI.of_interval (0., 2.)) (FI.of_interval (fmin, fmax)); - end - - let test_FI_narrow_specific _ = - let check_narrow a b c = - assert_equal c (FI.narrow a b) in - begin - check_narrow (FI.top ()) (FI.top ()) (FI.top ()); - check_narrow fi_zero (FI.top ()) fi_zero; - check_narrow (FI.top ()) fi_zero fi_zero; - check_narrow fi_zero fi_one fi_zero; - end - - let test_FI_ArithmeticOnFloatBot _ = - begin - assert_raises (FloatDomain.ArithmeticOnFloatBot ("minimal "^(FI.show (FI.bot ())))) (fun() -> (FI.minimal (FI.bot ()))); - assert_raises (FloatDomain.ArithmeticOnFloatBot ("to_int "^(FI.show (FI.bot ())))) (fun() -> (FI.to_int IInt (FI.bot ()))); - assert_raises (FloatDomain.ArithmeticOnFloatBot ((FI.show (FI.bot ()))^" op "^(FI.show fi_zero))) (fun() -> (FI.add (FI.bot ()) fi_zero)); - assert_raises (FloatDomain.ArithmeticOnFloatBot ((FI.show (FI.bot ()))^" op "^(FI.show fi_zero))) (fun() -> (FI.lt (FI.bot ()) fi_zero)); - assert_raises (FloatDomain.ArithmeticOnFloatBot ("unop "^(FI.show (FI.bot ())))) (fun() -> (FI.acos (FI.bot ()))); - end - - (**interval tests using QCheck arbitraries *) - let test_FI_not_bot = - QCheck.Test.make ~name:"test_FI_not_bot" (FI.arbitrary ()) (fun arg -> - not (FI.is_bot arg)) - - let test_FI_of_const_not_bot = - QCheck.Test.make ~name:"test_FI_of_const_not_bot" QCheck.float (fun arg -> - not (FI.is_bot (FI.of_const arg))) - - let test_FI_div_zero_result_top = - QCheck.Test.make ~name:"test_FI_div_zero_result_top" (FI.arbitrary ()) (fun arg -> - FI.is_top (FI.div arg (FI.of_const 0.))) - - let test_FI_accurate_neg = - QCheck.Test.make ~name:"test_FI_accurate_neg" QCheck.float (fun arg -> - FI.equal (FI.of_const (-.arg)) (FI.neg (FI.of_const arg))) - - let test_FI_lt_xor_ge = - QCheck.Test.make ~name:"test_FI_lt_xor_ge" (QCheck.pair (FI.arbitrary ()) (FI.arbitrary ())) (fun (arg1, arg2) -> - itb_xor (FI.lt arg1 arg2) (FI.ge arg1 arg2)) - - let test_FI_gt_xor_le = - QCheck.Test.make ~name:"test_FI_gt_xor_le" (QCheck.pair (FI.arbitrary ()) (FI.arbitrary ())) (fun (arg1, arg2) -> - itb_xor (FI.gt arg1 arg2) (FI.le arg1 arg2)) - - let test_FI_eq_xor_ne = - QCheck.Test.make ~name:"test_FI_eq_xor_ne" (QCheck.pair (FI.arbitrary ()) (FI.arbitrary ())) (fun (arg1, arg2) -> - itb_xor (FI.eq arg1 arg2) (FI.ne arg1 arg2)) - - let test_FI_add = - QCheck.Test.make ~name:"test_FI_add" (QCheck.pair QCheck.float QCheck.float) (fun (arg1, arg2) -> - let result = FI.add (FI.of_const arg1) (FI.of_const arg2) in - (FI.leq (FI.of_const (Option.get (to_float (add Up (of_float Nearest arg1) (of_float Nearest arg2))))) result) && - (FI.leq (FI.of_const (Option.get (to_float (add Down (of_float Nearest arg1) (of_float Nearest arg2))))) result)) - - let test_FI_sub = - QCheck.Test.make ~name:"test_FI_sub" (QCheck.pair QCheck.float QCheck.float) (fun (arg1, arg2) -> - let result = FI.sub (FI.of_const arg1) (FI.of_const arg2) in - (FI.leq (FI.of_const (Option.get (to_float (sub Up (of_float Nearest arg1) (of_float Nearest arg2))))) result) && - (FI.leq (FI.of_const (Option.get (to_float (sub Down (of_float Nearest arg1) (of_float Nearest arg2))))) result)) - - let test_FI_mul = - QCheck.Test.make ~name:"test_FI_mul" (QCheck.pair QCheck.float QCheck.float) (fun (arg1, arg2) -> - let result = FI.mul (FI.of_const arg1) (FI.of_const arg2) in - (FI.leq (FI.of_const (Option.get (to_float (mul Up (of_float Nearest arg1) (of_float Nearest arg2))))) result) && - (FI.leq (FI.of_const (Option.get (to_float (mul Down (of_float Nearest arg1) (of_float Nearest arg2))))) result)) - - - let test_FI_div = - QCheck.Test.make ~name:"test_FI_div" (QCheck.pair QCheck.float QCheck.float) (fun (arg1, arg2) -> - let result = FI.div (FI.of_const arg1) (FI.of_const arg2) in - (FI.leq (FI.of_const (Option.get (to_float (div Up (of_float Nearest arg1) (of_float Nearest arg2))))) result) && - (FI.leq (FI.of_const (Option.get (to_float (div Down (of_float Nearest arg1) (of_float Nearest arg2))))) result)) - - - let test () = [ - "test_FI_nan" >:: test_FI_nan; - "test_FI_add_specific" >:: test_FI_add_specific; - "test_FI_sub_specific" >:: test_FI_sub_specific; - "test_FI_mul_specific" >:: test_FI_mul_specific; - "test_FI_div_specific" >:: test_FI_div_specific; - "test_FI_casti2f_specific" >:: test_FI_casti2f_specific; - "test_FI_castf2i_specific" >:: test_FI_castf2i_specific; - (* "test_FI_castf2f_specific" >:: *) - "test_FI_join_specific" >:: test_FI_meet_specific; - "test_FI_meet_specific" >:: test_FI_join_specific; - "test_FI_meet_specific" >:: test_FI_leq_specific; - "test_FI_widen_specific" >:: test_FI_widen_specific; - "test_FI_narrow_specific" >:: test_FI_narrow_specific; - "test_FI_ArithmeticOnFloatBot" >:: test_FI_ArithmeticOnFloatBot; - ] - - let test_qcheck () = QCheck_ounit.to_ounit2_test_list [ - test_FI_not_bot; - test_FI_of_const_not_bot; - test_FI_div_zero_result_top; - test_FI_accurate_neg; - test_FI_lt_xor_ge; - test_FI_gt_xor_le; - test_FI_eq_xor_ne; - test_FI_add; - test_FI_sub; - test_FI_mul; - test_FI_div; - ] -end - -module FloatIntervalTest32 = FloatInterval(CFloat)(FloatDomain.F32Interval) -module FloatIntervalTest64 = FloatInterval(CDouble)(FloatDomain.F64Interval) - -let test () = - "floatDomainTest" >::: - [ - "float_interval32" >::: FloatIntervalTest32.test (); - "float_interval_qcheck32" >::: FloatIntervalTest32.test_qcheck (); - "float_interval64" >::: FloatIntervalTest64.test (); - "float_interval_qcheck64" >::: FloatIntervalTest64.test_qcheck (); - ] +open Goblint_lib +open OUnit2 +open FloatOps + +module FloatInterval(Float_t: CFloatType)(Domain_t: FloatDomain.FloatDomainBase) = +struct + module FI = Domain_t + module IT = IntDomain.IntDomTuple + + let to_float = Float_t.to_float + let of_float = Float_t.of_float + let add = Float_t.add + let sub = Float_t.sub + let mul = Float_t.mul + let div = Float_t.div + + let pred x = Option.get (to_float (Float_t.pred (of_float Nearest x))) + let succ x = Option.get (to_float (Float_t.succ (of_float Nearest x))) + + let fmax = Option.get (to_float Float_t.upper_bound) + let fmin = Option.get (to_float Float_t.lower_bound) + let fsmall = Option.get (to_float Float_t.smallest) + + let fi_zero = FI.of_const 0. + let fi_one = FI.of_const 1. + let fi_neg_one = FI.of_const (-.1.) + let itb_true = IT.of_int IBool Z.one + let itb_false = IT.of_int IBool Z.zero + let itb_unknown = IT.of_interval IBool (Z.zero, Z.one) + + let assert_equal v1 v2 = + assert_equal ~cmp:FI.equal ~printer:FI.show v1 v2 + + let itb_xor v1 v2 = (**returns true, if both IntDomainTuple bool results are either unknown or different *) + ((IT.equal v1 itb_unknown) && (IT.equal v2 itb_unknown)) || ((IT.equal v1 itb_true) && (IT.equal v2 itb_false)) || ((IT.equal v1 itb_false) && (IT.equal v2 itb_true)) + + (**interval tests *) + let test_FI_nan _ = + assert_equal (FI.top ()) (FI.of_const Float.nan) + + + let test_FI_add_specific _ = + let (+) = FI.add in + let (=) a b = assert_equal b a in + begin + (FI.of_const (-. 0.)) = fi_zero; + fi_zero + fi_one = fi_one; + fi_neg_one + fi_one = fi_zero; + fi_one + (FI.of_const fmax) = FI.top (); + fi_neg_one + (FI.of_const fmin) = FI.top (); + fi_neg_one + (FI.of_const fmax) = (FI.of_interval ((pred fmax), fmax)); + fi_one + (FI.of_const fmin) = (FI.of_interval (fmin, succ fmin)); + FI.top () + FI.top () = FI.top (); + (FI.of_const fmin) + (FI.of_const fmax) = fi_zero; + (FI.of_const fsmall) + (FI.of_const fsmall) = FI.of_const (fsmall +. fsmall); + let one_plus_fsmall = Option.get (to_float (Float_t.add Up (Float_t.of_float Up 1.) Float_t.smallest)) in + (FI.of_const fsmall) + (FI.of_const 1.) = FI.of_interval (1., one_plus_fsmall); + (FI.of_interval (1., 2.)) + (FI.of_interval (2., 3.)) = FI.of_interval (3., 5.); + (FI.of_interval (-. 2., 3.)) + (FI.of_interval (-. 100., 20.)) = FI.of_interval (-. 102., 23.); + end + + let test_FI_sub_specific _ = + let (-) = FI.sub in + let (=) a b = assert_equal b a in + begin + fi_zero - fi_one = fi_neg_one; + fi_neg_one - fi_one = FI.of_const (-. 2.); + fi_one - (FI.of_const fmin) = FI.top (); + fi_neg_one - (FI.of_const fmax) = FI.top (); + (FI.of_const fmax) - fi_one = (FI.of_interval ((pred fmax), fmax)); + (FI.of_const fmin) - fi_neg_one = (FI.of_interval (fmin, succ fmin)); + FI.top () - FI.top () = FI.top (); + (FI.of_const fmax) - (FI.of_const fmax) = fi_zero; + (FI.of_const fsmall) - (FI.of_const fsmall) = fi_zero; + (FI.of_const fsmall) - (FI.of_const 1.) = FI.of_interval (-. 1., succ (-. 1.)); + (FI.of_interval (-. 2., 3.)) - (FI.of_interval (-. 100., 20.)) = FI.of_interval (-. 22., 103.); + (FI.of_const (-. 0.)) - fi_zero = fi_zero + end + + let test_FI_mul_specific _ = + let ( * ) = FI.mul in + let (=) a b = assert_equal b a in + begin + fi_zero * fi_one = fi_zero; + (FI.of_const 2.) * (FI.of_const fmin) = FI.top (); + (FI.of_const 2.) * (FI.of_const fmax) = FI.top (); + (FI.of_const fsmall) * (FI.of_const fmax) = FI.of_const (fsmall *. fmax); + FI.top () * FI.top () = FI.top (); + (FI.of_const fmax) * fi_zero = fi_zero; + (FI.of_const fsmall) * fi_zero = fi_zero; + (FI.of_const fsmall) * fi_one = FI.of_const fsmall; + (FI.of_const fmax) * fi_one = FI.of_const fmax; + (FI.of_const 2.) * (FI.of_const 0.5) = fi_one; + (FI.of_interval (-. 2., 3.)) * (FI.of_interval (-. 100., 20.)) = FI.of_interval (-. 300., 200.); + + let up = if Float_t.name <> "float" then succ 1.00000000000000222 else succ (succ 1.00000000000000111 *. succ 1.00000000000000111) in + begin + (FI.of_const 1.00000000000000111) * (FI.of_const 1.00000000000000111) = FI.of_interval (1.00000000000000222 , up); + (FI.of_const (-. 1.00000000000000111)) * (FI.of_const 1.00000000000000111) = FI.of_interval (-. up, -. 1.00000000000000222) + end + end + + let test_FI_div_specific _ = + let (/) = FI.div in + let (=) a b = assert_equal b a in + begin + fi_zero / fi_one = fi_zero; + (FI.of_const 2.) / fi_zero = FI.top (); + fi_zero / fi_zero = FI.nan (); + (FI.of_const fmax) / (FI.of_const fsmall) = FI.top (); + (FI.of_const fmin) / (FI.of_const fsmall) = FI.top (); + FI.top () / FI.top () = FI.top (); + fi_zero / fi_one = fi_zero; + (FI.of_const fsmall) / fi_one = FI.of_const fsmall; + (FI.of_const fsmall) / (FI.of_const fsmall) = fi_one; + (FI.of_const fmax) / (FI.of_const fmax) = fi_one; + (FI.of_const fmax) / fi_one = FI.of_const fmax; + (FI.of_const 2.) / (FI.of_const 0.5) = (FI.of_const 4.); + (FI.of_const 4.) / (FI.of_const 2.) = (FI.of_const 2.); + (FI.of_interval (-. 2., 3.)) / (FI.of_interval (-. 100., 20.)) = FI.top (); + (FI.of_interval (6., 10.)) / (FI.of_interval (2., 3.)) = (FI.of_interval (2., 5.)); + + (FI.of_const 1.) / (FI.of_const 3.) = (FI.of_interval (pred 0.333333333333333370340767487505, 0.333333333333333370340767487505)); + (FI.of_const (-. 1.)) / (FI.of_const 3.) = (FI.of_interval (-. 0.333333333333333370340767487505, succ (-. 0.333333333333333370340767487505))) + end + + let test_FI_casti2f_specific _ = + let cast_bool a b = + assert_equal b (FI.of_int (IT.of_int IBool (Z.of_int a))) in + begin + cast_bool 0 fi_zero; + cast_bool 1 fi_one + end; + let cast a b = assert_equal b (FI.of_int a) in + begin + GobConfig.set_bool "ana.int.interval" true; + PrecisionUtil.reset_lazy (); + cast (IT.top_of IInt) (FI.of_interval (-2147483648.,2147483647.)); + cast (IT.top_of IBool) (FI.of_interval (0., 1.)); + cast (IT.of_int IInt Z.zero) fi_zero; + cast (IT.of_int IInt Z.one) fi_one; + (* no IChar because char has unknown signedness (particularly, unsigned on arm64) *) + cast (IT.of_interval IUChar (Z.zero, Z.of_int 128)) (FI.of_interval (0., 128.)); + cast (IT.of_interval ISChar (Z.of_int (-8), Z.of_int (-1))) (FI.of_interval (-. 8., - 1.)); + cast (IT.of_interval IUInt (Z.of_int 2, Z.of_int 100)) (FI.of_interval (2., 100.)); + cast (IT.of_interval IInt (Z.of_int (- 100), Z.of_int 100)) (FI.of_interval (-. 100., 100.)); + cast (IT.of_interval IUShort (Z.of_int 2, Z.of_int 100)) (FI.of_interval (2., 100.)); + cast (IT.of_interval IShort (Z.of_int (- 100), Z.of_int 100)) (FI.of_interval (-. 100., 100.)); + + cast (IT.of_interval IULong (Z.zero, Z.of_int64 Int64.max_int)) (FI.of_interval (0., 9223372036854775807.)); + cast (IT.of_interval IULong (Z.zero, Z.of_int64 (9223372036854775806L))) (FI.of_interval (0., 9223372036854775807.)); + cast (IT.of_interval ILong (Z.of_int64 Int64.min_int, Z.zero)) (FI.of_interval (-. 9223372036854775808., 0.)); + cast (IT.of_interval ILong (Z.of_int (- 100), Z.of_int 100)) (FI.of_interval (-. 100., 100.)); + cast (IT.of_interval IULongLong (Z.zero, Z.of_int64 Int64.max_int)) (FI.of_interval (0., 9223372036854775807.)); + cast (IT.of_interval IULongLong (Z.zero, Z.of_int64 (9223372036854775806L))) (FI.of_interval (0., 9223372036854775807.)); + cast (IT.of_interval ILongLong (Z.of_int64 Int64.min_int, Z.zero)) (FI.of_interval (-. 9223372036854775808., 0.)); + cast (IT.of_interval ILongLong (Z.of_int (- 100), Z.of_int 100)) (FI.of_interval (-. 100., 100.)); + GobConfig.set_bool "ana.int.interval" false; + PrecisionUtil.reset_lazy (); + end + + let test_FI_castf2i_specific _ = + let cast ikind a b = + OUnit2.assert_equal ~cmp:IT.equal ~printer:IT.show b (FI.to_int ikind a) in + begin + GobConfig.set_bool "ana.int.interval" true; + PrecisionUtil.reset_lazy (); + cast IInt (FI.of_interval (-2147483648.,2147483647.)) (IT.top_of IInt); + cast IInt (FI.of_interval (-9999999999.,9999999999.)) (IT.top_of IInt); + cast IInt (FI.of_interval (-10.1,20.9)) (IT.of_interval IInt ( Z.of_int (-10), Z.of_int 20)); + cast IBool (FI.of_interval (0.,1.)) (IT.top_of IBool); + cast IBool (FI.of_interval (-9999999999.,9999999999.)) (IT.top_of IBool); + cast IBool fi_one (IT.of_bool IBool true); + cast IBool fi_zero (IT.of_bool IBool false); + + (* no IChar because char has unknown signedness (particularly, unsigned on arm64) *) + cast IUChar (FI.of_interval (0.123, 128.999)) (IT.of_interval IUChar (Z.zero, Z.of_int 128)); + cast ISChar (FI.of_interval (-. 8.0000000, 127.)) (IT.of_interval ISChar (Z.of_int (-8), Z.of_int 127)); + cast IUInt (FI.of_interval (2., 100.)) (IT.of_interval IUInt (Z.of_int 2, Z.of_int 100)); + cast IInt (FI.of_interval (-. 100.2, 100.1)) (IT.of_interval IInt (Z.of_int (- 100), Z.of_int 100)); + cast IUShort (FI.of_interval (2., 100.)) (IT.of_interval IUShort (Z.of_int 2, Z.of_int 100)); + cast IShort (FI.of_interval (-. 100., 100.)) (IT.of_interval IShort (Z.of_int (- 100), Z.of_int 100)); + + cast IULong (FI.of_interval (0., 9223372036854775808.)) (IT.of_interval IULong (Z.zero, Z.of_string "9223372036854775808")); + cast ILong (FI.of_interval (-. 9223372036854775808., 0.)) (IT.of_interval ILong (Z.of_string "-9223372036854775808", Z.zero)); + cast ILong (FI.of_interval (-. 100.99999, 100.99999)) (IT.of_interval ILong (Z.of_int (- 100), Z.of_int 100)); + cast IULongLong (FI.of_interval (0., 9223372036854775808.)) (IT.of_interval IULongLong (Z.zero, Z.of_string "9223372036854775808")); + cast ILongLong (FI.of_interval (-. 9223372036854775808., 0.)) (IT.of_interval ILongLong ((Z.of_string "-9223372036854775808"), Z.zero)); + cast ILongLong (FI.of_interval (-. 100., 100.)) (IT.of_interval ILongLong (Z.of_int (- 100), Z.of_int 100)); + GobConfig.set_bool "ana.int.interval" false; + PrecisionUtil.reset_lazy (); + end + + let test_FI_meet_specific _ = + let check_meet a b c = + assert_equal c (FI.meet a b) in + begin + check_meet (FI.top ()) (FI.top ()) (FI.top ()); + check_meet (FI.top ()) fi_one fi_one; + check_meet fi_zero fi_one (FI.bot ()); + check_meet (FI.of_interval (0., 10.)) (FI.of_interval (5., 20.)) (FI.of_interval (5., 10.)); + end + + let test_FI_join_specific _ = + let check_join a b c = + assert_equal c (FI.join a b) in + begin + check_join (FI.top ()) (FI.top ()) (FI.top ()); + check_join (FI.top ()) fi_one (FI.top ()); + check_join (FI.of_interval (0., 10.)) (FI.of_interval (5., 20.)) (FI.of_interval (0., 20.)); + end + + let test_FI_leq_specific _ = + let check_leq flag a b = + OUnit2.assert_equal flag (FI.leq a b) in + begin + check_leq true (FI.top ()) (FI.top ()); + check_leq true fi_one fi_one; + check_leq false fi_one fi_zero; + check_leq true (FI.of_interval (5., 20.)) (FI.of_interval (0., 20.)); + check_leq false (FI.of_interval (0., 20.)) (FI.of_interval (5., 20.)); + check_leq true (FI.of_interval (1., 19.)) (FI.of_interval (0., 20.)); + check_leq false (FI.of_interval (0., 20.)) (FI.of_interval (20.0001, 20.0002)); + end + + let test_FI_widen_specific _ = + let check_widen a b c = + assert_equal c (FI.widen a b) in + begin + check_widen (FI.top ()) (FI.top ()) (FI.top ()); + check_widen fi_zero (FI.top ()) (FI.top ()); + check_widen (FI.top ()) fi_one (FI.top ()); + check_widen fi_zero fi_one (FI.of_interval (0., fmax)); + check_widen fi_one fi_zero (FI.of_interval (fmin, 1.)); + check_widen fi_one (FI.of_interval (0., 2.)) (FI.of_interval (fmin, fmax)); + end + + let test_FI_narrow_specific _ = + let check_narrow a b c = + assert_equal c (FI.narrow a b) in + begin + check_narrow (FI.top ()) (FI.top ()) (FI.top ()); + check_narrow fi_zero (FI.top ()) fi_zero; + check_narrow (FI.top ()) fi_zero fi_zero; + check_narrow fi_zero fi_one fi_zero; + end + + let test_FI_ArithmeticOnFloatBot _ = + begin + assert_raises (FloatDomain.ArithmeticOnFloatBot ("minimal "^(FI.show (FI.bot ())))) (fun() -> (FI.minimal (FI.bot ()))); + assert_raises (FloatDomain.ArithmeticOnFloatBot ("to_int "^(FI.show (FI.bot ())))) (fun() -> (FI.to_int IInt (FI.bot ()))); + assert_raises (FloatDomain.ArithmeticOnFloatBot ((FI.show (FI.bot ()))^" op "^(FI.show fi_zero))) (fun() -> (FI.add (FI.bot ()) fi_zero)); + assert_raises (FloatDomain.ArithmeticOnFloatBot ((FI.show (FI.bot ()))^" op "^(FI.show fi_zero))) (fun() -> (FI.lt (FI.bot ()) fi_zero)); + assert_raises (FloatDomain.ArithmeticOnFloatBot ("unop "^(FI.show (FI.bot ())))) (fun() -> (FI.acos (FI.bot ()))); + end + + (**interval tests using QCheck arbitraries *) + let test_FI_not_bot = + QCheck.Test.make ~name:"test_FI_not_bot" (FI.arbitrary ()) (fun arg -> + not (FI.is_bot arg)) + + let test_FI_of_const_not_bot = + QCheck.Test.make ~name:"test_FI_of_const_not_bot" QCheck.float (fun arg -> + not (FI.is_bot (FI.of_const arg))) + + let test_FI_div_zero_result_top = + QCheck.Test.make ~name:"test_FI_div_zero_result_top" (FI.arbitrary ()) (fun arg -> + FI.is_top (FI.div arg (FI.of_const 0.))) + + let test_FI_accurate_neg = + QCheck.Test.make ~name:"test_FI_accurate_neg" QCheck.float (fun arg -> + FI.equal (FI.of_const (-.arg)) (FI.neg (FI.of_const arg))) + + let test_FI_lt_xor_ge = + QCheck.Test.make ~name:"test_FI_lt_xor_ge" (QCheck.pair (FI.arbitrary ()) (FI.arbitrary ())) (fun (arg1, arg2) -> + itb_xor (FI.lt arg1 arg2) (FI.ge arg1 arg2)) + + let test_FI_gt_xor_le = + QCheck.Test.make ~name:"test_FI_gt_xor_le" (QCheck.pair (FI.arbitrary ()) (FI.arbitrary ())) (fun (arg1, arg2) -> + itb_xor (FI.gt arg1 arg2) (FI.le arg1 arg2)) + + let test_FI_eq_xor_ne = + QCheck.Test.make ~name:"test_FI_eq_xor_ne" (QCheck.pair (FI.arbitrary ()) (FI.arbitrary ())) (fun (arg1, arg2) -> + itb_xor (FI.eq arg1 arg2) (FI.ne arg1 arg2)) + + let test_FI_add = + QCheck.Test.make ~name:"test_FI_add" (QCheck.pair QCheck.float QCheck.float) (fun (arg1, arg2) -> + let result = FI.add (FI.of_const arg1) (FI.of_const arg2) in + (FI.leq (FI.of_const (Option.get (to_float (add Up (of_float Nearest arg1) (of_float Nearest arg2))))) result) && + (FI.leq (FI.of_const (Option.get (to_float (add Down (of_float Nearest arg1) (of_float Nearest arg2))))) result)) + + let test_FI_sub = + QCheck.Test.make ~name:"test_FI_sub" (QCheck.pair QCheck.float QCheck.float) (fun (arg1, arg2) -> + let result = FI.sub (FI.of_const arg1) (FI.of_const arg2) in + (FI.leq (FI.of_const (Option.get (to_float (sub Up (of_float Nearest arg1) (of_float Nearest arg2))))) result) && + (FI.leq (FI.of_const (Option.get (to_float (sub Down (of_float Nearest arg1) (of_float Nearest arg2))))) result)) + + let test_FI_mul = + QCheck.Test.make ~name:"test_FI_mul" (QCheck.pair QCheck.float QCheck.float) (fun (arg1, arg2) -> + let result = FI.mul (FI.of_const arg1) (FI.of_const arg2) in + (FI.leq (FI.of_const (Option.get (to_float (mul Up (of_float Nearest arg1) (of_float Nearest arg2))))) result) && + (FI.leq (FI.of_const (Option.get (to_float (mul Down (of_float Nearest arg1) (of_float Nearest arg2))))) result)) + + + let test_FI_div = + QCheck.Test.make ~name:"test_FI_div" (QCheck.pair QCheck.float QCheck.float) (fun (arg1, arg2) -> + let result = FI.div (FI.of_const arg1) (FI.of_const arg2) in + (FI.leq (FI.of_const (Option.get (to_float (div Up (of_float Nearest arg1) (of_float Nearest arg2))))) result) && + (FI.leq (FI.of_const (Option.get (to_float (div Down (of_float Nearest arg1) (of_float Nearest arg2))))) result)) + + + let test () = [ + "test_FI_nan" >:: test_FI_nan; + "test_FI_add_specific" >:: test_FI_add_specific; + "test_FI_sub_specific" >:: test_FI_sub_specific; + "test_FI_mul_specific" >:: test_FI_mul_specific; + "test_FI_div_specific" >:: test_FI_div_specific; + "test_FI_casti2f_specific" >:: test_FI_casti2f_specific; + "test_FI_castf2i_specific" >:: test_FI_castf2i_specific; + (* "test_FI_castf2f_specific" >:: *) + "test_FI_join_specific" >:: test_FI_meet_specific; + "test_FI_meet_specific" >:: test_FI_join_specific; + "test_FI_meet_specific" >:: test_FI_leq_specific; + "test_FI_widen_specific" >:: test_FI_widen_specific; + "test_FI_narrow_specific" >:: test_FI_narrow_specific; + "test_FI_ArithmeticOnFloatBot" >:: test_FI_ArithmeticOnFloatBot; + ] + + let test_qcheck () = QCheck_ounit.to_ounit2_test_list [ + test_FI_not_bot; + test_FI_of_const_not_bot; + test_FI_div_zero_result_top; + test_FI_accurate_neg; + test_FI_lt_xor_ge; + test_FI_gt_xor_le; + test_FI_eq_xor_ne; + test_FI_add; + test_FI_sub; + test_FI_mul; + test_FI_div; + ] +end + +module FloatIntervalTest32 = FloatInterval(CFloat)(FloatDomain.F32Interval) +module FloatIntervalTest64 = FloatInterval(CDouble)(FloatDomain.F64Interval) + +let test () = + "floatDomainTest" >::: + [ + "float_interval32" >::: FloatIntervalTest32.test (); + "float_interval_qcheck32" >::: FloatIntervalTest32.test_qcheck (); + "float_interval64" >::: FloatIntervalTest64.test (); + "float_interval_qcheck64" >::: FloatIntervalTest64.test_qcheck (); + ] diff --git a/unittest/cdomains/intDomainTest.ml b/unittest/cdomains/intDomainTest.ml index 858b179135..f803ef1c35 100644 --- a/unittest/cdomains/intDomainTest.ml +++ b/unittest/cdomains/intDomainTest.ml @@ -200,7 +200,7 @@ let test_ex_set _ = module Interval = struct - module I = IntDomain.Interval + module I = IntDomain.SOverflowUnlifter(IntDomain.Interval) let assert_equal x y = assert_equal ~cmp:I.equal ~printer:I.show x y diff --git a/unittest/cdomains/lvalTest.ml b/unittest/cdomains/lvalTest.ml index 701477ee6e..387e3bde3e 100644 --- a/unittest/cdomains/lvalTest.ml +++ b/unittest/cdomains/lvalTest.ml @@ -3,20 +3,21 @@ open OUnit2 open GoblintCil module ID = IntDomain.IntDomWithDefaultIkind (IntDomain.IntDomLifter (IntDomain.DefExc)) (IntDomain.PtrDiffIkind) -module LV = Lval.NormalLat (ID) +module Offs = Offset.MakeLattice (ID) +module LV = AddressDomain.AddressLattice (Mval.MakeLattice (Offs)) let ikind = IntDomain.PtrDiffIkind.ikind () let a_var = Cil.makeGlobalVar "a" Cil.intPtrType -let a_lv = LV.from_var a_var -let i_0 = ID.of_int ikind (Z.of_int 0) -let a_lv_0 = LV.from_var_offset (a_var, `Index (i_0, `NoOffset)) -let i_1 = ID.of_int ikind (Z.of_int 1) -let a_lv_1 = LV.from_var_offset (a_var, `Index (i_1, `NoOffset)) +let a_lv = LV.of_var a_var +let i_0 = ID.of_int ikind Z.zero +let a_lv_0 = LV.of_mval (a_var, `Index (i_0, `NoOffset)) +let i_1 = ID.of_int ikind Z.one +let a_lv_1 = LV.of_mval (a_var, `Index (i_1, `NoOffset)) let i_top = ID.join i_0 i_1 -let a_lv_top = LV.from_var_offset (a_var, `Index (i_top, `NoOffset)) +let a_lv_top = LV.of_mval (a_var, `Index (i_top, `NoOffset)) let i_not_0 = ID.join i_1 (ID.of_int ikind (Z.of_int 2)) -let a_lv_not_0 = LV.from_var_offset (a_var, `Index (i_not_0, `NoOffset)) +let a_lv_not_0 = LV.of_mval (a_var, `Index (i_not_0, `NoOffset)) let assert_leq x y = @@ -27,38 +28,18 @@ let assert_not_leq x y = let assert_equal x y = assert_equal ~cmp:LV.equal ~printer:LV.show x y - -let test_equal_0 _ = - assert_equal a_lv a_lv_0 - -let test_compare_0 _ = - assert_bool "test_compare_0" @@ (LV.compare a_lv a_lv_0 = 0) - -let test_hash_0 _ = - assert_bool "test_hash_0" @@ (LV.hash a_lv = LV.hash a_lv_0) - -let test_leq_0 _ = - assert_leq a_lv a_lv_0; - assert_leq a_lv_0 a_lv - let test_join_0 _ = - assert_equal a_lv_top (LV.join a_lv_0 a_lv_1); - skip_if true "TODO"; - assert_equal a_lv_top (LV.join a_lv a_lv_1) (* TODO *) + assert_equal a_lv_top (LV.join a_lv_0 a_lv_1) let test_leq_not_0 _ = assert_leq a_lv_1 a_lv_not_0; - OUnit.assert_equal ~printer:[%show: [`Eq | `Neq | `Top]] `Neq (ID.equal_to (Z.of_int 0) i_not_0); - OUnit.assert_equal ~printer:[%show: [`MustZero | `MustNonzero | `MayZero]] `MustNonzero (LV.Offs.cmp_zero_offset (`Index (i_not_0, `NoOffset))); + OUnit.assert_equal ~printer:[%show: [`Eq | `Neq | `Top]] `Neq (ID.equal_to Z.zero i_not_0); + OUnit.assert_equal ~printer:[%show: [`MustZero | `MustNonzero | `MayZero]] `MustNonzero (Offs.cmp_zero_offset (`Index (i_not_0, `NoOffset))); assert_not_leq a_lv a_lv_not_0; assert_not_leq a_lv_0 a_lv_not_0 let test () = "lvalTest" >::: [ - "test_equal_0" >:: test_equal_0; - "test_compare_0" >:: test_compare_0; - "test_hash_0" >:: test_hash_0; "test_join_0" >:: test_join_0; - "test_leq_0" >:: test_leq_0; "test_leq_not_0" >:: test_leq_not_0; ] diff --git a/unittest/domains/mapDomainTest.ml b/unittest/domains/mapDomainTest.ml index 8947ea3926..ff64a75f92 100644 --- a/unittest/domains/mapDomainTest.ml +++ b/unittest/domains/mapDomainTest.ml @@ -3,12 +3,12 @@ open OUnit2 module Pretty = GoblintCil.Pretty -module GroupableDriver : MapDomain.Groupable with type t = string = +module PrintableDriver : Printable.S with type t = string = struct include Printable.Strings end -module LatticeDriver = Lattice.Fake (GroupableDriver) +module LatticeDriver = Lattice.Fake (PrintableDriver) module TestMap (M:MapDomain.S with type key = string and type value = string) = @@ -162,8 +162,8 @@ struct end -module Mbot = MapDomain.MapBot (GroupableDriver) (LatticeDriver) -module Mtop = MapDomain.MapTop (GroupableDriver) (LatticeDriver) +module Mbot = MapDomain.MapBot (PrintableDriver) (LatticeDriver) +module Mtop = MapDomain.MapTop (PrintableDriver) (LatticeDriver) module Tbot = TestMap (Mbot) module Ttop = TestMap (Mtop) @@ -171,7 +171,7 @@ module Ttop = TestMap (Mtop) let test_Mbot_join_meet _ = let assert_eq = - let printer a = Pretty.sprint ~width:80 (Mbot.pretty () a) in + let printer a = Pretty.sprint ~width:max_int (Mbot.pretty () a) in let cmp = Mbot.equal in assert_equal ~cmp:(cmp) ~printer:(printer) in @@ -207,7 +207,7 @@ let test_Mbot_join_meet _ = let test_Mtop_join_meet _ = let assert_eq = - let printer a = Pretty.sprint ~width:80 (Mtop.pretty () a) in + let printer a = Pretty.sprint ~width:max_int (Mtop.pretty () a) in let cmp = Mtop.equal in assert_equal ~cmp:(cmp) ~printer:(printer) in diff --git a/unittest/mainTest.ml b/unittest/mainTest.ml index d2a559a5f1..642e495d50 100644 --- a/unittest/mainTest.ml +++ b/unittest/mainTest.ml @@ -8,8 +8,10 @@ let all_tests = ("" >::: LvalTest.test (); CompilationDatabaseTest.tests; LibraryDslTest.tests; + CilfacadeTest.tests; (* etc *) - "domaintest" >::: QCheck_ounit.to_ounit2_test_list Maindomaintest.all_testsuite + "domaintest" >::: QCheck_ounit.to_ounit2_test_list Maindomaintest.all_testsuite; + IntOpsTest.tests; ]) let () = run_test_tt_main all_tests diff --git a/unittest/maindomaintest.ml b/unittest/maindomaintest.ml index d17d2bf891..8e6a7f5a3a 100644 --- a/unittest/maindomaintest.ml +++ b/unittest/maindomaintest.ml @@ -4,7 +4,7 @@ open GoblintCil module PrintableChar = struct - include Printable.Std + include Printable.StdLeaf type t = char [@@deriving eq, ord, hash, to_yojson] let name () = "char" let show x = String.make 1 x @@ -41,16 +41,17 @@ let domains: (module Lattice.S) list = [ (module ArbitraryLattice); (module HoareArbitrary); (module HoareArbitrary_NoTop); - (module HoareDomain.MapBot (ArbitraryLattice) (HoareArbitrary)); - (module HoareDomain.MapBot (ArbitraryLattice) (HoareArbitrary_NoTop)); + (module HoareDomain.MapBot[@alert "-deprecated"] (ArbitraryLattice) (HoareArbitrary)); + (module HoareDomain.MapBot[@alert "-deprecated"] (ArbitraryLattice) (HoareArbitrary_NoTop)); ] let nonAssocDomains: (module Lattice.S) list = [] let intDomains: (module IntDomainProperties.S) list = [ - (module IntDomain.Interval); + (module IntDomain.SOverflowUnlifter(IntDomain.Interval)); (module IntDomain.Enums); (module IntDomain.Congruence); + (module IntDomain.SOverflowUnlifter(IntDomain.IntervalSet)); (* (module IntDomain.Flattened); *) (* (module IntDomain.Interval32); *) (* (module IntDomain.Booleans); *) @@ -66,7 +67,7 @@ let ikinds: Cil.ikind list = [ IChar; ISChar; IUChar; - IBool; + (* IBool; *) (* see https://github.com/goblint/analyzer/pull/1111 *) IInt; IUInt; IShort; 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 new file mode 100644 index 0000000000..611f2f546f --- /dev/null +++ b/unittest/util/intOpsTest.ml @@ -0,0 +1,30 @@ +open OUnit2 +open Goblint_lib + +(* If the first operand of a div is negative, Zarith rounds the result away from zero. + We thus always transform this into a division with a non-negative first operand. *) +let old_div a b = if Z.lt a Z.zero then Z.neg (Z.ediv (Z.neg a) b) else Z.ediv a b + +(* Z.erem computes the Euclidian Modulus, but what we want here is the remainder, as returned by mod on ints + -1 rem 5 == -1, whereas -1 Euclid-Mod 5 == 4 *) +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) -> + 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) -> + assume (Z.compare y Z.zero <> 0); + Z.equal (Z.rem x y) (old_rem x y) + )) + +let tests = + "intOpsTest" >::: [ + "bigint" >::: QCheck_ounit.to_ounit2_test_list [ + test_bigint_div; + test_bigint_rem; + ] + ]