diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 138dd913..c8419ef2 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -3,6 +3,12 @@ name: Java CI with Gradle on: push: pull_request: + workflow_dispatch: + +env: + # Python of the SV-COMP execution environment (SV-COMP 2027: Ubuntu 26.04). + SVCOMP_PYTHON_VERSION: '3.14' + SVCOMP_BASE_IMAGE: docker.io/ubuntu:26.04 jobs: build: @@ -10,16 +16,190 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v7 - name: Set up JDK 17 - uses: actions/setup-java@v4 + uses: actions/setup-java@v6 with: java-version: '17' distribution: 'temurin' - name: Setup Gradle - uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4 + uses: gradle/actions/setup-gradle@3f5f9adaf7d9fecd50b5935e54106014257a94e6 # v6.4.0 + - name: Prepare Solver Dependencies + run: ./gradlew copyNativeLibs - name: Build - run: ./gradlew build -x test + run: ./gradlew --no-daemon build -x test + # The property pre-check (svcomp/PropertyPreCheck.py) and the static pre-analysis + # (svcomp/StaticPreAnalysis.py) come from cfg-extraction; SWAT pins the cfg-extraction ref it + # ships in cfg-extraction.ref. + - name: Read pinned cfg-extraction ref + id: cfg + run: echo "ref=$(tr -d '[:space:]' < cfg-extraction.ref)" >> "${GITHUB_OUTPUT}" + - name: Check out cfg-extraction + uses: actions/checkout@v7 + with: + repository: SWAT-project/cfg-extraction + ref: ${{ steps.cfg.outputs.ref }} + path: cfg-extraction + - name: Build the property pre-check and the static pre-analysis + working-directory: cfg-extraction + run: ./gradlew --no-daemon precheckJar fatJar + - name: Upload JARs + uses: actions/upload-artifact@v7 + with: + name: release-jars-${{ github.sha }} + path: | + symbolic-executor/lib/symbolic-executor.jar + cfg-extraction/build/libs/cfg-extractor-precheck.jar + cfg-extraction/build/libs/cfg-extractor-1.0-SNAPSHOT-all.jar + if-no-files-found: error + retention-days: 1 + + package: + needs: build + if: >- + github.event_name == 'pull_request' || + github.event_name == 'workflow_dispatch' || + (github.event_name == 'push' && ( + github.ref == 'refs/heads/main' || + github.ref == 'refs/heads/dev' || + startsWith(github.ref, 'refs/tags/') + )) + runs-on: ubuntu-24.04 + permissions: + contents: read + outputs: + channel: ${{ steps.meta.outputs.channel }} + zip-name: ${{ steps.meta.outputs.zip-name }} + rolling: ${{ steps.meta.outputs.rolling }} + prerelease: ${{ steps.meta.outputs.prerelease }} + artifact-name: ${{ steps.meta.outputs.artifact-name }} + steps: + - uses: actions/checkout@v7 + - name: Download JARs + uses: actions/download-artifact@v8 + with: + name: release-jars-${{ github.sha }} + path: ${{ runner.temp }}/release-jars + - name: Determine release channel + id: meta + run: | + ref_type="${GITHUB_REF_TYPE}" + ref_name="${GITHUB_REF_NAME}" + sha_short="${GITHUB_SHA::7}" + + channel="" # release/tag name; empty -> build artifact only, no release + release="false" + rolling="false" # rolling channels move their tag to the current commit + prerelease="false" + + if [[ "$ref_type" == "tag" ]]; then + channel="$ref_name" + release="true" + elif [[ "$ref_name" == "main" ]]; then + channel="latest" + release="true" + rolling="true" + elif [[ "$ref_name" == "dev" ]]; then + channel="latest-dev" + release="true" + rolling="true" + prerelease="true" + fi + + # Channel-stable asset name gives the nightly cron a fixed download URL; + # the exact commit is recorded in the release notes and BUILD_INFO.txt. + if [[ -n "$channel" ]]; then + version="$channel" + else + version="$sha_short" + fi + zip_name="swat-svcomp-${version}.zip" + + { + echo "channel=${channel}" + echo "version=${version}" + echo "zip-name=${zip_name}" + echo "artifact-name=swat-svcomp-${version}" + echo "release=${release}" + echo "rolling=${rolling}" + echo "prerelease=${prerelease}" + } >> "${GITHUB_OUTPUT}" + - name: Build SV-COMP Python runtime + env: + SWAT_SVCOMP_PYTHON_VERSION: ${{ env.SVCOMP_PYTHON_VERSION }} + run: scripts/build-svcomp-python-runtime.sh "${RUNNER_TEMP}/svcomp-python" + - name: Pack SV-COMP ZIP + env: + SWAT_SVCOMP_VERSION: ${{ steps.meta.outputs.version }} + SWAT_SVCOMP_ARTIFACT_DIR: ${{ runner.temp }}/release-jars + SWAT_SVCOMP_PYTHON_DIR: ${{ runner.temp }}/svcomp-python + SWAT_SVCOMP_COMMIT: ${{ github.sha }} + SWAT_SVCOMP_REF: ${{ github.ref_name }} + SWAT_SVCOMP_CHANNEL: ${{ steps.meta.outputs.channel }} + run: scripts/package-svcomp.sh + - name: Check SV-COMP ZIP in competition base image + run: scripts/check-svcomp-package.sh "build/distributions/${{ steps.meta.outputs.zip-name }}" "${SVCOMP_BASE_IMAGE}" + - name: Upload SV-COMP ZIP artifact + uses: actions/upload-artifact@v7 + with: + name: ${{ steps.meta.outputs.artifact-name }} + path: build/distributions/${{ steps.meta.outputs.zip-name }} + if-no-files-found: error + + publish-release: + needs: package + if: >- + github.event_name == 'push' && ( + github.ref == 'refs/heads/main' || + github.ref == 'refs/heads/dev' || + startsWith(github.ref, 'refs/tags/') + ) + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Download SV-COMP ZIP artifact + uses: actions/download-artifact@v8 + with: + name: ${{ needs.package.outputs.artifact-name }} + path: ${{ runner.temp }}/svcomp-package + - name: Publish release + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + CHANNEL: ${{ needs.package.outputs.channel }} + ZIP_NAME: ${{ needs.package.outputs.zip-name }} + ROLLING: ${{ needs.package.outputs.rolling }} + PRERELEASE: ${{ needs.package.outputs.prerelease }} + run: | + zip_path="${RUNNER_TEMP}/svcomp-package/${ZIP_NAME}" + notes="$(printf 'Automated SWAT SV-COMP package.\n\nRef: %s\nCommit: %s\nRun: %s/%s/actions/runs/%s\n' \ + "$GITHUB_REF_NAME" "$GITHUB_SHA" "$GITHUB_SERVER_URL" "$GITHUB_REPOSITORY" "$GITHUB_RUN_ID")" + + prerelease_flag=() + [[ "$PRERELEASE" == "true" ]] && prerelease_flag=(--prerelease) + + if [[ "$ROLLING" == "true" ]]; then + # Refresh the rolling channel: drop the old release + tag, then recreate + # it at the current commit so the tag always tracks the channel head. + # Uses the default GITHUB_TOKEN, so the new tag does not retrigger CI. + gh release delete "$CHANNEL" --yes --cleanup-tag 2>/dev/null || true + gh release create "$CHANNEL" "$zip_path" \ + --target "$GITHUB_SHA" \ + --title "SWAT ${CHANNEL} (${GITHUB_SHA::7})" \ + --notes "$notes" \ + "${prerelease_flag[@]}" + else + # Permanent, version-tagged release (the tag already points at this commit). + if gh release view "$CHANNEL" >/dev/null 2>&1; then + gh release upload "$CHANNEL" "$zip_path" --clobber + else + gh release create "$CHANNEL" "$zip_path" \ + --title "SWAT ${CHANNEL}" \ + --notes "$notes" \ + "${prerelease_flag[@]}" + fi + fi test: needs: build @@ -27,21 +207,21 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v7 - name: Set up JDK 17 - uses: actions/setup-java@v4 + uses: actions/setup-java@v6 with: java-version: '17' distribution: 'temurin' - name: Setup Gradle - uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4 + uses: gradle/actions/setup-gradle@3f5f9adaf7d9fecd50b5935e54106014257a94e6 # v6.4.0 - name: Copy Z3 run: ./gradlew copyNativeLibs - name: Test run: ./gradlew test - name: Upload test reports if: failure() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: test-reports path: symbolic-executor/build/reports/tests @@ -53,18 +233,18 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v7 - name: Set up JDK 17 - uses: actions/setup-java@v4 + uses: actions/setup-java@v6 with: java-version: '17' distribution: 'temurin' - name: Setup Gradle - uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4 + uses: gradle/actions/setup-gradle@3f5f9adaf7d9fecd50b5935e54106014257a94e6 # v6.4.0 - name: Generate Javadoc run: ./gradlew :symbolic-executor:javadoc - name: Upload Javadoc - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: javadoc path: symbolic-executor/build/docs/javadoc @@ -75,11 +255,11 @@ jobs: permissions: contents: write steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v7 - name: Set up JDK 17 - uses: actions/setup-java@v4 + uses: actions/setup-java@v6 with: java-version: '17' distribution: 'temurin' - name: Generate and submit dependency graph - uses: gradle/actions/dependency-submission@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4 + uses: gradle/actions/dependency-submission@3f5f9adaf7d9fecd50b5935e54106014257a94e6 # v6.4.0 diff --git a/.gitignore b/.gitignore index 761ca1c8..3a446e27 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,8 @@ targets/applications/openolat/libs/z3/ **/logs/ **/logs-debug/ **/results/ +**/runs/ +**/runs-debug/ **/*.class /build/ **/build/ @@ -21,13 +23,18 @@ libs/jacoco/report targets/applications/openhospital/oh_build/restler_working_dir/Compile targets/applications/openhospital/oh_build/restler_working_dir/RestlerResults targets/sv-comp/sv-benchmarks/ +targets/sv-comp/sv-benchmarks-*/ targets/sv-comp/wit4java/ **/__pycache__ **/.venv/* /.vscode/ **/smt_files/ **/witness/ +# ... but not the witness packages of the executor sources +!symbolic-executor/src/**/witness/ **/sv-comp/results/ **/analysis/ !targets/sv-comp/scripts/lib/analysis -libs/cfg-extractor/cfg-extractor-1.0-SNAPSHOT.jar \ No newline at end of file +libs/cfg-extractor/cfg-extractor-1.0-SNAPSHOT.jar +# Default output of targets/sv-comp/scripts/plot_timing_comparison.py +timing_comparison.png diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..e86e7feb --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,288 @@ +# SWAT + +Dynamic symbolic execution for Java: + +- `symbolic-executor/` is the Java agent that instruments bytecode and records execution traces. +- `symbolic-explorer/` is the Python driver that builds the execution tree, picks branches to + flip and calls the solver. +- `targets/sv-comp/` holds the SV-COMP harness and benchmarks. + +## Static pre-analysis (SA) + +The explorer can prune its search with a control-flow graph from the extractor in +`../cfg-extraction`. That graph is loaded by `symbolic-explorer/data/StaticAnalysisGraph/SAGraph.py` +and walked in `strategy/DFS.py`; the module comments of both define the terms below. + +**Terms** (used in the code, logs and stats): + +- **Target point** (`SANode.isTarget`): where the property may be violated, i.e. an assertion point + (valid-assert) or a site that may raise a RuntimeException (no-runtime-exception). Later targets + include shell calls and SQL statement building, so prefer changes that work for any kind. +- **Interesting:** a target point is reachable, given the walk's call stack (`is_interesting`). + `dfs` prunes the sides of branches that are not. +- **Pairing** (`DFS._pair`): how a tree branch was matched with a graph branch. **Verified** (key + and call stack checked), **resynced** (found again by key), **unverified** (followed, but a key + or a call site is missing to check it), **lost** (no graph node), **masked** (a branch the graph + has no node for: the walk holds its place). Only verified and resynced pairings prune or slice. +- **Desync:** a key or call-stack mismatch: logged as `[DFS] SA desync`, recorded as `sa_desync` + (turns SAFE into UNKNOWN), and the walk is lost from there. +- **Sliced / merged / decision** (`DFS._classify`): a sliced branch is irrelevant to the targets + (relevance, below), a merged one was merged by a loop summary; neither is flipped nor keeps its + constraint. **Poisoned:** a sliced branch that turned out relevant after all. + +**The walk:** + +- **Lockstep by position:** `dfs` walks the execution tree and the SA graph together, pairing + branch for branch. If the two disagree on how many branches a path has, pruning uses the wrong + nodes and can produce false SAFE verdicts. Any change to what the executor reports as a branch, + including the transformers under `symbolic-executor/.../instrument/`, needs a matching change in + the extractor. Concrete branches count too; filtering to symbolic branches happens after `dfs`. +- **Keys check the pairing:** every trace branch carries a key (`owner.name(desc)|line|kind|ordinal`, + `instrument/BranchKeys.java`, named in `InstructionMethodAdapter.nameInstruction`) and the frame + stack change since the previous branch (`ShadowContext.takeStackDelta`). `SAGraph.check_branch` + compares them with the graph node's key and the walk's call stack; when the walk is lost, + `SAGraph.resync` finds the branch by key, with the call stack rebuilt from the trace. The + extractor computes the same keys; change both together. +- **Losing the walk is safe:** a lost or unverified walk prunes and slices nothing. The walk is + lost at incomplete graph nodes, at call sites with several callees (exact dispatch to known + overrides: it resyncs inside whichever callee ran; the summaries take the union), and after an + exception the graph does not model (below). +- **Masked branches:** branches in `` (not in the graph), in application code that library + code calls back (flagged `callback` by the executor, `ShadowContext.inCallback`), in SWAT's own + helpers (the reference-comparison rewrite `UtilInstrumented.refEquals`, flagged like callbacks, + `ShadowContext.inSwatHelper`, since how many there are depends on runtime types), and choice + branches (the executor's search for the element a symbolic index selects). + `Verifier.nondetObject(type, factory)`, which the executor runs as `factory.createObject()`, is + expanded and keyed as that call by the extractor. +- **Exceptions the graph does not model** (null dereferences, casts, library calls, Errors): the + extractor gives them pseudo types (`?Exception`, `?Error`) whose escapes SAGraph treats as + caught by any handler listed for them (`unknown_handlers`), so paths into such handlers stay + interesting. When one actually happens, the executor records an `UNMODELLED_EXCEPTION` marker, + which `Tree.structural` keeps as `Node.unmodelled_exception` on the next node; the walk is lost + there until it resyncs. +- **Guards that cannot raise:** a phantom guard the extractor marks `cannotRaise` (the property + pre-check proves its implicit exception cannot happen, cfg-extraction `RaiseSites`) gets a dead + node as its exception side in `SAGraph.load_json_graph`, so that side is never interesting. +- **Observability:** each selection logs one line, e.g. `[DFS] Walk: 34 branches (33 verified, + 0 resynced, 0 unverified, 0 lost, 1 masked), 12 sides pruned; 32 sliced, 0 merged, 0 covered, + 0 poisoned` (`DFS.describe_walk`; "covered": candidates left out as covered by a path with the + same relevant decisions), and `stats.json` has the same counts in `static_analysis.last_walk`. + The per-branch trace of the walk is at level `DFS.TRACE`, below DEBUG. + +**The graph and its targets:** + +- **Targets per property** (`StaticPreAnalysis.SA_TARGETS`): `assertionPointIds` under + valid-assert; under no-runtime-exception the sites that may raise a RuntimeException plus the + `uncaught[T]` nodes of RuntimeException types (`runtimeExceptionPointIds`, from the property + pre-check's value analysis, cfg-extraction `RaiseSites`). Incomplete nodes are target points too. + - **No-runtime-exception is opt-in in the harness** (`svcomp test run --sa-no-runtime-exception`; + the explorer prunes whenever it gets `--sa-path`). On the 736 tasks (300 s, 2026-10-24) it is + sound (no false SAFE) and cuts queries on the solved SAFE tasks by a third, but solves nothing + new: the pre-check already proves 426 tasks and the extractor costs 3 to 6 s per task. +- **Library callbacks that may reach an assert** make the extractor count every library call as + an assertion point; `--sa-no-callback-targets` (and `svcomp test run --sa-no-callback-targets`) + turns that off, which is unsound. The extractor gets the target's classpath without Z3's jar + (`StaticPreAnalysis._is_swat_library`): its classes would count as application code. +- **Code outside main's call tree** (threads, shutdown hooks, executors: `SAGraph.async_entry_sites`) + makes the graph unusable: it is rejected and the run explores unpruned. +- **SA runs before exploring by default:** in sv-comp mode exploration waits for + `svcomp/StaticPreAnalysis.py`, so the graph is used from round 0 and runs are deterministic. + `--parallel-sa` instead runs the extractor in a thread while exploration starts unpruned; + `SVCompDriver.adopt_sa_graph` switches to the graph once it is loaded, and it is cancelled + (extractor killed) if exploration finishes first. That hides SA bugs whenever the graph loads + too late, and makes results depend on load. In parallel runs the `static_pre_analysis` timing + stage is 0 and its duration is `static_pre_analysis_wall`; `run_info.json` in the run dir records + the SA mode. + +**Relevance (slicing)** (needs cfg-extraction `v0.2.0` or later for the data-flow facts): a branch +whose region (up to its immediate post-dominator) cannot affect whether a target is reached, nor +any value a target or a relevant branch reads, is irrelevant. `dfs` slices it: it is not flipped, +`collect_path_constrains` leaves its constraint out, and candidates are identified by the sequence +of relevant decisions before them (their sig), so paths that differ only in irrelevant decisions +count as one. Computed in `data/StaticAnalysisGraph/Relevance.py` from the extractor's per-node +data-flow facts; off with `--no-sa-slicing` (explorer and harness). Results and where it helps: +`swat-benchmarks/RESULTS.md`, section "Relevance (slicing)". + +- **Poisoning:** each path carries the regions of its sliced branches; a branch inside one that + the graph cannot account for (masked, unverified, lost, or relevant) or a run that ends there + with an uncaught exception (`Leaf.abnormal`, ancestors `abnormal_below`) poisons those branches + (`Node.poisoned`: relevant for good), and `select_branch` walks again. +- **Statements that may throw** to a local handler or a harmful escape are implicit branches in + the analysis: what they read becomes relevant with them (else a null receiver's origin gets + sliced: four sa-soundness false SAFEs before this). +- **Queries:** a sliced query's full query is solved first; the sliced one only decides where the + full one is UNSAT. A run of a sliced solution may reach its branch on another path with the + same relevant decisions: `check_projected_divergence` checks that after the next walk. +- **Known limitations** (fixes sketched in RESULTS.md, relevance "Open"): all library state is + one location `g:lib` (fix: type families, then allocation sites with an escape check), and the + slice is context-insensitive (fix: a "return value relevant / not" variant per method, chosen + per call site and looked up by the walk's stack). + +**Loop summaries** (executor `symbolic/summary`): diamonds (an int `if` whose sides are +straight-line int arithmetic on locals, also nested, also `/`/`%` by constants) and counting loops +(`i < b` or `i <= b`, step one) are merged into formulas; their branches are flagged `merged` and +`dfs` treats them as no decision, like sliced ones. A counting loop whose body does other things +too (calls, heap) is flagged `mergedConditional`, honored only if `Relevance.summary_ok` (the loop +changes nothing relevant but its locals), and then it opens a region like a sliced branch; its +branches are flagged only when it leaves through its head (`BackEdge` plans tell iterations from +re-entries). `TaintCheck` drops a summary whose merged values could reach a call, a heap or array +store, an array index or size, or a return, since a dropped constraint must not be needed where the +executor concretizes (13 false SAFE before, via `Verifier.assume`). Ignoring a `merged` flag is +always sound. Config `loopSummaries` (on in `sv-comp.cfg`), `--no-loop-summaries`. Results, limits, +next steps: swat-benchmarks/RESULTS.md, "Loop summarization". Pre-pass tests: `./gradlew +:symbolic-executor:test --tests 'de.uzl.its.swat.symbolic.summary.*'`. + +**Other exploration features:** + +- **Small models**: a solution with a numeric input (integral or floating-point, also infinite) + beyond 65536 is re-solved with the inputs bounded first (`SVCompDriver.prefer_small`, + `--no-prefer-small-models`); a loop bound of 2^30 otherwise makes the next run never end (+4 + valid-assert, +1 no-runtime-exception), and an approximate Math model is only right near its + samples. Bounded queries get at most twice the units of the branch's unbounded query, at least + 10M (`BOUNDED_RLIMIT`): they can be far harder than the unbounded one. +- **Approximate models** (`SampledMath`, executor): Math functions are exact at sample inputs + (nice values, and since 2026-11-03 the edges of the double and integral ranges, so overflows to + infinity and NaN are reachable) and the run's own result elsewhere; floating-point `%` + (`JavaRemainder`) is exact where the quotient is at most 1 in magnitude. Both are declared + approximate, so a branch over them is a precision loss and rules out SAFE. Before 2026-11-03 + `%` had no formula at all and silently gave false SAFE (PieSegment). +- **Loop-head state matching** (`loopStateMatching`, executor `LoopStateMatcher`; explorer + `--no-loop-state-matching`): a branch below a state already reached at a smaller position is not + flipped. It is what makes nondet-guarded loops with a repeating concrete state SAFE (28 tasks of + sv-benchmarks valid-assert, 27 of them in `float_unboundedloop`, +56 points, with dfs and + coverage alike; 2 in swat-benchmarks `dispatcher`). A run stops reporting states after any call + into untracked code other than a whitelist (`isPureCall`): from 2026-10-19 to 2026-11-03 + `Class.desiredAssertionStatus`, which every class with an `assert` calls, was missing, and the + feature did nothing in any valid-assert task. +- **Hangs**: the executor's `DeadlockWatchdog` (config `deadlockWatchdog`) ends a run whose + application threads all wait forever, and `ResourceBudget` has a wall-clock deadline (default: + the CPU limit), so a hang ends UNKNOWN instead of being killed by the harness. + +Tests for the walk, runnable without a SWAT run: + +```bash +cd symbolic-explorer && python3 -m unittest tests.test_sa_walk -v +python3 tests/check_marking_on_graphs.py ... # checks marking against an explicit search +../targets/sv-comp/scripts/.venv/bin/python3 -m unittest tests.test_divergence -v # needs the venv +../targets/sv-comp/scripts/.venv/bin/python3 -m unittest tests.test_relevance -v # relevance and slicing +../targets/sv-comp/scripts/.venv/bin/python3 -m unittest tests.test_prefer_small -v # small models +``` + +The SA modules are fully annotated and pass mypy (not in the venvs; install it into a scratch +venv), run from `symbolic-explorer/`: + +```bash +mypy --explicit-package-bases --ignore-missing-imports --follow-imports=silent --check-untyped-defs \ + --disallow-untyped-defs --disallow-any-generics strategy/DFS.py strategy/StrategyService.py \ + data/StaticAnalysisGraph/*.py data/BinaryExecutionTree/*.py svcomp/StaticPreAnalysis.py +``` + +`dfs` only descends into real `data.BinaryExecutionTree.Node` instances. In tests, build them with +`Node.__new__(Node)` and set the fields directly (the per-node flags have class defaults). `pytest` +is not installed; use `unittest`. + +## Decoding executor iids + +The branch ids in the explorer log, e.g. `[DFS] @/`, are packed as +`[loop count:6][switch case:4][cid:19][mid:11][inst:24]`. See `GlobalStateForInstrumentation`. + +- `cid` is the class index, in load order. +- `mid` is the method index, in declaration order (as `javap -p` lists methods). + +Decoding the iids tells you which method each event really came from. That is the quickest way to +spot a walk that has fallen out of step with the execution. + +## SV-COMP runs + +```bash +cd targets/sv-comp && ./scripts/svcomp test run --mode parallel --no-witness --categories valid-assert.prp +./scripts/svcomp test run --help # --suite, --limit-nr-tests, --no-sa, --parallel-sa, --sa-rank-distance, --search-order, --solver-rlimit, --testcase-timeout-s ... +``` + +- **Duration:** a full valid-assert run takes about 40 min with the default 900 s timeout, and + much longer with `--testcase-timeout-s 3600`. Only compare runs made with the same timeout. +- **Resource budget:** as in SV-COMP, the explorer and all executor JVMs share one memory and CPU + budget (`--memory-limit` MB, default 15000; `--cpu-limit` s, default 900; the harness passes + `--testcase-timeout-s` as the CPU limit). The executor heap is derived from the memory limit (a + lower cgroup limit wins). Close to a limit the explorer kills the executor run or interrupts the + Z3 query in progress; 20 s before the CPU limit it ends with UNKNOWN. Both rule out SAFE, see + `symbolic-explorer/svcomp/ResourceBudget.py` and `resources` in `stats.json`. +- **Output:** results go to `targets/sv-comp/runs/run_/`. Per-task results are in + `results/results__*.json`, and per-task logs (`explorer.log`, + `Main_main_interprocedural.json`, `stats.json`) are in `logs//_/`. +- **One run at a time:** runs use this working tree live. Every task re-imports + `symbolic-explorer/` and uses `../cfg-extraction/build/libs/`. Ports are pre-assigned upward + from 9000 when a run starts. So don't start a run while another still has live tasks (check + `ps --ppid `), and remember that editing either repo affects a run in progress. +- **Solver limits:** `--solver-rlimit N` gives each branch's first query N Z3 resource units + (deterministic, unlike a timeout; slow queries run at about 2.5M units/s, so 25000000 is about + 10 s). A branch that runs out is retried later with 5x the limit, after the cheaper queries; a + lone retry runs unlimited. Off by default. Each query's time and rlimit use are in the + `[SOLVER]` debug lines of `explorer.log`. +- **Property pre-check:** before round 0, `svcomp/PropertyPreCheck.py` runs cfg-extraction's + `cfg-extractor-precheck.jar` (`de.uzl.its.cfg.staticcheck.ExceptionFreedomCheck`, an ASM pass, + about 0.2 s). A proof of no-runtime-exception or valid-assert gives SAFE without exploring + (round 0 still runs as a cross-check). The jar comes from `--precheck-jar`, else from next to + `symbolic-explorer/` (the SV-COMP archive), else `../cfg-extraction/build/libs/`; without it the + check is skipped (`missing_jar`). CI builds it at the cfg-extraction tag in `cfg-extraction.ref` (cfg-extraction is public, MIT) + and the archive ships it as `cfg-extraction/cfg-extractor-precheck.jar`. Its soundness gate is + `cfg-extraction/scripts/precheck_gate.py` (0 contradictions over sv-benchmarks). +- **SV-COMP archive** (`scripts/package-svcomp.sh`, run by CI; `run-swat.sh` → `run_swat.py`): + ships the extractor's fat jar as `cfg-extraction/cfg-extractor.jar`, built by CI at the same + ref as the pre-check, and `run_swat.py` passes it as `--sa-jar` (instead of `--sa-path`, a + checkout) under valid-assert only, like the harness. Without `--target` the graph starts at + `Main.main`, the class the executor runs. `scripts/check-svcomp-package.sh` fails unless the + smoke test's `stats.json` has `static_analysis.status` `loaded`. Every jar in the archive + carries `META-INF/swat-third-party.json` (the fat jar's from cfg-extraction's + `fatJarComponentList`), which `third_party_licenses.py` needs for each bundled component. +- **Search order:** `--search-order` picks which open branch is flipped next (`StrategyService`): + `coverage` (default since 2026-11-03: sides no trace took yet, then the shallowest), `dfs`, + `shallow`, `coverage-dfs`, `generational`. The harness always passes it explicitly. Plain DFS + keeps unrolling input-parsing loops one char per round with ever larger queries. The order changes + only how fast violations are found, not SAFE. Coverage against dfs (300 s, 32 workers, with SA, + 2026-11-03, runs `run_20261103_103945` to `_115003`): swat-benchmarks 694 vs 688 (+6 violations, + none lost), sv-benchmarks valid-assert 1034 vs 1031 (+4 that dfs never found in five runs, -1 a + flaky one), no-runtime-exception the same but for `WeibullGenerator_exceptionless`, a real bug dfs + finds and coverage did not in 300 s (label fixed below). SAFE verdicts were identical; violations + both find took fewer rounds with coverage. Without SA (978 valid-assert tasks, 300 s), coverage + found 6 more violations and lost none. +- **Int/string queries:** `solver/Int2BvLifting.py` rewrites the executor's bit-vector ints where + they meet string lengths, indices and char codes into Integer arithmetic before solving, and + solves bit-vector inputs used only as numbers over Integer stand-ins (`lift_inputs`, mapped back + to the inputs by `Z3Handler.map_lifted_inputs`). Mixed `int_to_bv`/`ubv_to_int` queries are + what makes Z3 slow on string code. +- **Benchmarks:** `targets/sv-comp/sv-benchmarks/` is a sparse checkout of `java/` from + SWAT-project/SV-Benchmarks, which mirrors upstream sv-benchmarks with precompiled `.class` files; + `.last_sv_commit` names the upstream commit. Older suites are kept next to it as + `sv-benchmarks-/` (currently `sv-benchmarks-2025-11-03`, which all runs before the switch on 2026-09-30 + used); select one with `--benchmark-dir`, and only compare scores of runs on the same + suite. To update: move the current suite aside, then `./scripts/svcomp setup checkout-benchmarks` + (`--source ` if GitHub is not reachable over SSH). +- **SA benchmarks:** `targets/sv-comp/swat-benchmarks/` holds generated tasks that measure SA + pruning, and `sa-soundness`, SAFE/VIOLATION pairs where pruning can go wrong (run with + `--benchmark-dir swat-benchmarks --suite sa-soundness`). See its README and RESULTS.md, which + also lists the known SA and executor false SAFEs. +- **Local label fixes:** tasks whose upstream label is wrong are fixed in the suite checkout + itself, as commits there (`git -C targets/sv-comp/sv-benchmarks log`) with a comment in the + task's `.yml`. A fresh checkout drops them, so re-apply any that upstream has not fixed yet: + - `argv-tasks/ReverseInterpolator_true`: valid-assert is false (float rounding, e.g. input + 16.84626f); upstream MR 1730 is still open. + - `argv-tasks/PieSegment_true`: valid-assert is false (`angle % 360` is negative for negative + angles); upstream MR 1675 is still open. + - `argv-tasks/AlbersProjection_true`: valid-assert is false (the early return for small `Te` + skips the finite check, e.g. qs = 0, Te = 0 returns 0); no upstream MR yet (MR 1703 was + undone by commit b4857700). + - `argv-tasks/WeibullGenerator_exceptionless`: no-runtime-exception is false (a tiny negative + `beta` makes `1 / beta` -Infinity, `(int)` of it Integer.MIN_VALUE and `Math.pow` +Infinity, + which times `lamda = 0` is NaN and throws, e.g. beta's nondet = -2.0722615e-317); not reported + upstream yet. +- **Verdicts:** false SAFE (`violation -> safe`) is the outcome that matters most, and a pruning + bug causes exactly that. Check any new one before trusting a run. +- **String inputs:** the explorer passes solved Strings as `-Dswat.input...` escaped by + `escape_java_string` (`driver/SVCompDriver.py`), and the executor's `Verifier.nondetString` + undoes it (`unescape`). Change both together. Solved Strings are read from Z3 by code point + (`SolverHandler.encode_value`); `as_string()` output is ambiguous. +- **Soundness flags that downgrade SAFE to UNKNOWN:** symbolic context loss is also recorded when + instrumented code runs untracked ("unknown space", e.g. a callback from library code), and + precision loss when an object array or an untracked array is indexed symbolically (both only + track the concrete element). diff --git a/Third-Party-Licenses.html b/Third-Party-Licenses.html deleted file mode 100644 index f8bde4d4..00000000 --- a/Third-Party-Licenses.html +++ /dev/null @@ -1,3018 +0,0 @@ - - - - - - Dependency License Report for SWAT - - - - -

- Dependency License Report for SWAT -

- -
-

- 1. - Group: com.fasterxml.jackson - Name: jackson-bom - Version: 2.14.1 -

-

- Project URL: - - - https://github.com/FasterXML/jackson - - -

-

- POM License: The Apache Software License, Version 2.0 - - - - https://www.apache.org/licenses/LICENSE-2.0.txt - -

-
-

- 2. - Group: com.fasterxml.jackson.core - Name: jackson-annotations - Version: 2.14.1 -

-

- Project URL: - - - https://github.com/FasterXML/jackson - - -

-

- POM License: The Apache Software License, Version 2.0 - - - - https://www.apache.org/licenses/LICENSE-2.0.txt - -

- -
-

- 3. - Group: com.fasterxml.jackson.core - Name: jackson-core - Version: 2.14.1 -

-

- Project URL: - - - https://github.com/FasterXML/jackson-core - - -

-

- POM License: Apache License, Version 2.0 - - - - http://www.apache.org/licenses/LICENSE-2.0.txt - -

-

- POM License: The Apache Software License, Version 2.0 - - - - https://www.apache.org/licenses/LICENSE-2.0.txt - -

- -
-

- 4. - Group: com.fasterxml.jackson.core - Name: jackson-databind - Version: 2.14.1 -

-

- Project URL: - - - https://github.com/FasterXML/jackson - - -

-

- POM License: Apache License, Version 2.0 - - - - http://www.apache.org/licenses/LICENSE-2.0.txt - -

-

- POM License: The Apache Software License, Version 2.0 - - - - https://www.apache.org/licenses/LICENSE-2.0.txt - -

- -
-

- 5. - Group: com.github.spotbugs - Name: spotbugs-annotations - Version: 4.7.3 -

-

- POM Project URL: - - - https://spotbugs.github.io/ - - -

-

- POM License: GNU LESSER GENERAL PUBLIC LICENSE, Version 2.1 - - - - https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html - -

- -
-

- 6. - Group: com.google.code.findbugs - Name: jsr305 - Version: 3.0.2 -

-

- POM Project URL: - - - http://findbugs.sourceforge.net/ - - -

-

- POM License: The Apache Software License, Version 2.0 - - - - http://www.apache.org/licenses/LICENSE-2.0.txt - -

- -
-

- 7. - Group: com.google.code.gson - Name: gson - Version: 2.9.0 -

-

- Manifest Project URL: - - - https://github.com/google/gson/gson - - -

-

- Manifest License: "Apache-2.0";link="https://www.apache.org/licenses/LICENSE-2.0.txt" (Not packaged) -

-

- POM License: Apache-2.0 - - - - https://www.apache.org/licenses/LICENSE-2.0.txt - -

- -
-

- 8. - Group: com.google.errorprone - Name: error_prone_annotations - Version: 2.18.0 -

-

- POM License: Apache 2.0 - - - - http://www.apache.org/licenses/LICENSE-2.0.txt - -

- -
-

- 9. - Group: com.google.guava - Name: failureaccess - Version: 1.0.1 -

-

- Manifest Project URL: - - - https://github.com/google/guava/ - - -

-

- POM License: The Apache Software License, Version 2.0 - - - - http://www.apache.org/licenses/LICENSE-2.0.txt - -

- -
-

- 10. - Group: com.google.guava - Name: guava - Version: 32.1.2-jre -

-

- Manifest Project URL: - - - https://github.com/google/guava/ - - -

-

- POM Project URL: - - - https://github.com/google/guava - - -

-

- POM License: Apache License, Version 2.0 - - - - http://www.apache.org/licenses/LICENSE-2.0.txt - -

- -
-

- 11. - Group: com.google.guava - Name: guava-parent - Version: 32.1.2-jre -

-

- No license information found -

-
-

- 12. - Group: com.google.guava - Name: listenablefuture - Version: 9999.0-empty-to-avoid-conflict-with-guava -

-

- POM License: The Apache Software License, Version 2.0 - - - - http://www.apache.org/licenses/LICENSE-2.0.txt - -

- -
-

- 13. - Group: commons-codec - Name: commons-codec - Version: 1.11 -

-

- Project URL: - - - http://commons.apache.org/proper/commons-codec/ - - -

-

- POM License: Apache License, Version 2.0 - - - - https://www.apache.org/licenses/LICENSE-2.0.txt - -

- -
-

- 14. - Group: commons-logging - Name: commons-logging - Version: 1.2 -

-

- Project URL: - - - http://commons.apache.org/proper/commons-logging/ - - -

-

- POM License: The Apache Software License, Version 2.0 - - - - http://www.apache.org/licenses/LICENSE-2.0.txt - -

- -
-

- 15. - Group: de.uni-freiburg.informatik.ultimate - Name: smtinterpol - Version: 2.5-1242-g5c50fb6d -

-

- POM Project URL: - - - https://ultimate.informatik.uni-freiburg.de/smtinterpol/ - - -

-

- POM License: GNU LESSER GENERAL PUBLIC LICENSE - - - - http://www.gnu.org/licenses/lgpl-3.0.en.html - -

- -
-

- 16. - Group: io.github.uuverifiers - Name: princess-parser_2.13 - Version: 2023-06-19 -

-

- POM Project URL: - - - https://philipp.ruemmer.org/princess.shtml - - -

-

- POM License: BSD-3-Clause - - - - https://opensource.org/licenses/BSD-3-Clause - -

- -
-

- 17. - Group: io.github.uuverifiers - Name: princess-smt-parser_2.13 - Version: 2023-06-19 -

-

- POM Project URL: - - - https://philipp.ruemmer.org/princess.shtml - - -

-

- POM License: BSD-3-Clause - - - - https://opensource.org/licenses/BSD-3-Clause - -

- -
-

- 18. - Group: io.github.uuverifiers - Name: princess_2.13 - Version: 2023-06-19 -

-

- POM Project URL: - - - https://philipp.ruemmer.org/princess.shtml - - -

-

- POM License: BSD-3-Clause - - - - https://opensource.org/licenses/BSD-3-Clause - -

- -
-

- 19. - Group: net.sf.squirrel-sql.thirdparty-non-maven - Name: java-cup - Version: 0.11a -

-

- POM Project URL: - - - http://www2.cs.tum.edu/projects/cup/ - - -

-

- POM License: CUP Parser Generator Copyright Notice, License, and Disclaimer - - - - http://www.smlnj.org//license.html - -

- -
-

- 20. - Group: org.apache.httpcomponents - Name: httpasyncclient - Version: 4.1.5 -

-

- POM Project URL: - - - http://hc.apache.org/httpcomponents-asyncclient - - -

-

- POM License: Apache License, Version 2.0 - - - - http://www.apache.org/licenses/LICENSE-2.0.txt - -

- -
-

- 21. - Group: org.apache.httpcomponents - Name: httpclient - Version: 4.5.14 -

-

- POM Project URL: - - - http://hc.apache.org/httpcomponents-client-ga - - -

-

- POM License: Apache License, Version 2.0 - - - - http://www.apache.org/licenses/LICENSE-2.0.txt - -

- -
-

- 22. - Group: org.apache.httpcomponents - Name: httpcore - Version: 4.4.16 -

-

- POM Project URL: - - - http://hc.apache.org/httpcomponents-core-ga - - -

-

- POM License: Apache License, Version 2.0 - - - - http://www.apache.org/licenses/LICENSE-2.0.txt - -

- -
-

- 23. - Group: org.apache.httpcomponents - Name: httpcore-nio - Version: 4.4.15 -

-

- POM Project URL: - - - http://hc.apache.org/httpcomponents-core-ga - - -

-

- POM License: Apache License, Version 2.0 - - - - http://www.apache.org/licenses/LICENSE-2.0.txt - -

- -
-

- 24. - Group: org.checkerframework - Name: checker-qual - Version: 3.37.0 -

-

- Manifest License: MIT (Not packaged) -

-

- POM Project URL: - - - https://checkerframework.org/ - - -

-

- POM License: The MIT License - - - - http://opensource.org/licenses/MIT - -

- -
-

- 25. - Group: org.codehaus.mojo - Name: animal-sniffer-annotations - Version: 1.18 -

-

- POM License: MIT license - - - - http://www.opensource.org/licenses/mit-license.php - -

-

- POM License: The Apache Software License, Version 2.0 - - - - http://www.apache.org/licenses/LICENSE-2.0.txt - -

- -
-

- 26. - Group: org.junit - Name: junit-bom - Version: 5.9.1 -

-

- No license information found -

-
-

- 27. - Group: org.ow2.asm - Name: asm - Version: 9.4 -

-

- Manifest Project URL: - - - http://asm.ow2.org - - -

-

- Manifest License: BSD-3-Clause;link=https://asm.ow2.io/LICENSE.txt (Not packaged) -

-

- POM Project URL: - - - http://asm.ow2.io/ - - -

-

- POM License: BSD-3-Clause - - - - https://asm.ow2.io/license.html - -

-

- POM License: The Apache Software License, Version 2.0 - - - - http://www.apache.org/licenses/LICENSE-2.0.txt - -

- -
-

- 28. - Group: org.ow2.asm - Name: asm-analysis - Version: 9.4 -

-

- Manifest Project URL: - - - http://asm.ow2.org - - -

-

- Manifest License: BSD-3-Clause;link=https://asm.ow2.io/LICENSE.txt (Not packaged) -

-

- POM Project URL: - - - http://asm.ow2.io/ - - -

-

- POM License: BSD-3-Clause - - - - https://asm.ow2.io/license.html - -

-

- POM License: The Apache Software License, Version 2.0 - - - - http://www.apache.org/licenses/LICENSE-2.0.txt - -

- -
-

- 29. - Group: org.ow2.asm - Name: asm-commons - Version: 9.4 -

-

- Manifest Project URL: - - - http://asm.ow2.org - - -

-

- Manifest License: BSD-3-Clause;link=https://asm.ow2.io/LICENSE.txt (Not packaged) -

-

- POM Project URL: - - - http://asm.ow2.io/ - - -

-

- POM License: BSD-3-Clause - - - - https://asm.ow2.io/license.html - -

-

- POM License: The Apache Software License, Version 2.0 - - - - http://www.apache.org/licenses/LICENSE-2.0.txt - -

- -
-

- 30. - Group: org.ow2.asm - Name: asm-tree - Version: 9.4 -

-

- Manifest Project URL: - - - http://asm.ow2.org - - -

-

- Manifest License: BSD-3-Clause;link=https://asm.ow2.io/LICENSE.txt (Not packaged) -

-

- POM Project URL: - - - http://asm.ow2.io/ - - -

-

- POM License: BSD-3-Clause - - - - https://asm.ow2.io/license.html - -

-

- POM License: The Apache Software License, Version 2.0 - - - - http://www.apache.org/licenses/LICENSE-2.0.txt - -

- -
-

- 31. - Group: org.ow2.asm - Name: asm-util - Version: 9.4 -

-

- Manifest Project URL: - - - http://asm.ow2.org - - -

-

- Manifest License: BSD-3-Clause;link=https://asm.ow2.io/LICENSE.txt (Not packaged) -

-

- POM Project URL: - - - http://asm.ow2.io/ - - -

-

- POM License: BSD-3-Clause - - - - https://asm.ow2.io/license.html - -

-

- POM License: The Apache Software License, Version 2.0 - - - - http://www.apache.org/licenses/LICENSE-2.0.txt - -

- -
-

- 32. - Group: org.scala-lang - Name: scala-library - Version: 2.13.10 -

-

- POM Project URL: - - - https://www.scala-lang.org/ - - -

-

- POM License: Apache-2.0 - - - - https://www.apache.org/licenses/LICENSE-2.0 - -

- -
-

- 33. - Group: org.scala-lang.modules - Name: scala-parser-combinators_2.13 - Version: 1.1.2 -

-

- Manifest license URL: - - Apache-2.0 - -

-

- POM Project URL: - - - http://www.scala-lang.org/ - - -

-

- POM License: Apache-2.0 - - - - https://www.apache.org/licenses/LICENSE-2.0 - -

- -
-

- 34. - Group: org.sosy-lab - Name: common - Version: 0.3000-569-g89796f98 -

-

- POM Project URL: - - - https://github.com/sosy-lab/java-common-lib - - -

-

- POM License: Apache License, Version 2.0 - - - - http://www.apache.org/licenses/LICENSE-2.0.txt - -

- -
-

- 35. - Group: org.sosy-lab - Name: java-smt - Version: 4.0.3 -

-

- POM Project URL: - - - https://github.com/sosy-lab/java-smt - - -

-

- POM License: Apache License, Version 2.0 - - - - http://www.apache.org/licenses/LICENSE-2.0.txt - -

- -
-

- 36. - Group: org.antlr - Name: ST4 - Version: 4.3.4 -

-

- POM License: The BSD License - - - - http://www.antlr.org/license.html - -

- -
-

- 37. - Group: org.antlr - Name: antlr-runtime - Version: 3.5.3 -

-

- POM Project URL: - - - http://www.antlr.org - - -

-

- POM License: BSD licence - - - - http://antlr.org/license.html - -

- -
-

- 38. - Name: Z3 - Version: 4.12.2.0 -

-

- Project URL: - - - https://github.com/Z3Prover/z3 - - -

-

- License: MIT licence - - - - https://github.com/Z3Prover/z3/blob/master/LICENSE.txt - -

- - - -
-

- 39 - Name: aioboto3 - Version: 11.2.0 -

-

- Project URL: - - - https://github.com/terrycain/aioboto3 - - -

-

- License: Apache-2.0 -

- - -
-

- 40 - Name: aiobotocore - Version: 2.5.0 -

-

- Project URL: - - - https://github.com/aio-libs/aiobotocore - - -

-

- License: Apache License 2.0 -

- - -
-

- 41 - Name: aiohttp - Version: 3.8.5 -

-

- Project URL: - - - https://github.com/aio-libs/aiohttp - - -

-

- License: Apache 2 -

- - -
-

- 43 - Name: aiosignal - Version: 1.3.1 -

-

- Project URL: - - - https://github.com/aio-libs/aiosignal - - -

-

- License: Apache 2.0 -

- - -
-

- 44 - Name: altgraph - Version: 0.17.2 -

-

- Project URL: - - - https://altgraph.readthedocs.io - - -

-

- License: MIT -

- - -
-

- 45 - Name: aniso8601 - Version: 9.0.1 -

-

- Project URL: - - - https://bitbucket.org/nielsenb/aniso8601 - - -

-

- License: BSD-3-Clause -

- - -
-

- 46 - Name: anyio - Version: 3.7.1 -

-

- Project URL: - - - https://github.com/agronholm/anyio/ - - -

-

- License: MIT -

- - -
-

- 47 - Name: appdirs - Version: 1.4.4 -

-

- Project URL: - - - http://github.com/ActiveState/appdirs - - -

-

- License: MIT -

- - -
-

- 48 - Name: argparse - Version: 1.4.0 -

-

- Project URL: - - - https://github.com/ThomasWaldmann/argparse/ - - -

-

- License: Python Software Foundation License -

- - -
-

- 49 - Name: async-timeout - Version: 4.0.2 -

-

- Project URL: - - - https://github.com/aio-libs/async-timeout - - -

-

- License: Apache 2 -

- - -
-

- 50 - Name: Automat - Version: 22.10.0 -

-

- Project URL: - - - https://github.com/glyph/Automat - - -

-

- License: MIT -

- - -
-

- 51 - Name: boto - Version: 2.49.0 -

-

- Project URL: - - - https://github.com/boto/boto/ - - -

-

- License: MIT -

- - -
-

- 52 - Name: boto3 - Version: 1.26.76 -

-

- Project URL: - - - https://github.com/boto/boto3 - - -

-

- License: Apache License 2.0 -

- - -
-

- 53 - Name: botocore - Version: 1.29.76 -

-

- Project URL: - - - https://github.com/boto/botocore - - -

-

- License: Apache License 2.0 -

- - -
-

- 54 - Name: certifi - Version: 2023.5.7 -

-

- Project URL: - - - https://github.com/certifi/python-certifi - - -

-

- License: MPL-2.0 -

- - -
-

- 55 - Name: cffi - Version: 1.15.1 -

-

- Project URL: - - - http://cffi.readthedocs.org - - -

-

- License: MIT -

- - -
-

- 56 - Name: charset-normalizer - Version: 3.1.0 -

-

- Project URL: - - - https://github.com/Ousret/charset_normalizer - - -

-

- License: MIT -

- - -
-

- 57 - Name: click - Version: 8.1.7 -

-

- Project URL: - - - https://palletsprojects.com/p/click/ - - -

-

- License: BSD-3-Clause -

- - -
-

- 58 - Name: constantly - Version: 15.1.0 -

-

- Project URL: - - - https://github.com/twisted/constantly - - -

-

- License: MIT -

- - -
-

- 59 - Name: contourpy - Version: 1.2.0 -

-

- Project URL: - - - https://github.com/contourpy/contourpy - - -

-

- License: BSD 3-Clause License -

- - -
-

- 60 - Name: corscanner - Version: 0.9.7 -

-

- Project URL: - - - http://github.com/chenjj/CORScanner - - -

-

- License: MIT -

- - -
-

- 61 - Name: cryptography - Version: 41.0.3 -

-

- Project URL: - - - https://github.com/pyca/cryptography - - -

-

- License: Apache-2.0 OR BSD-3-Clause -

- - -
-

- 62 - Name: cssselect - Version: 1.2.0 -

-

- Project URL: - - - https://github.com/scrapy/cssselect - - -

-

- License: BSD -

- - -
-

- 63 - Name: cycler - Version: 0.12.1 -

-

- Project URL: - - - https://github.com/matplotlib/cycler - - -

-

- License: Copyright (c) 2015, matplotlib project -

- - -
-

- 64 - Name: docker - Version: 6.1.3 -

-

- Project URL: - - - https://github.com/docker/docker-py - - -

-

- License: Apache License 2.0 -

- - -
-

- 65 - Name: docker-pycreds - Version: 0.4.0 -

-

- Project URL: - - - https://github.com/shin-/dockerpy-creds - - -

-

- License: Apache License 2.0 -

- - -
-

- 66 - Name: Flask-RESTful - Version: 0.3.10 -

-

- Project URL: - - - https://www.github.com/flask-restful/flask-restful/ - - -

-

- License: BSD -

- - -
-

- 67 - Name: fonttools - Version: 4.44.1 -

-

- Project URL: - - - http://github.com/fonttools/fonttools - - -

-

- License: MIT -

- - -
-

- 68 - Name: frida - Version: 16.1.3 -

-

- Project URL: - - - https://frida.re - - -

-

- License: wxWindows Library Licence, Version 3.1 -

- - -
-

- 69 - Name: frida-tools - Version: 12.2.1 -

-

- Project URL: - - - https://frida.re - - -

-

- License: wxWindows Library Licence, Version 3.1 -

- - -
-

- 70 - Name: frozenlist - Version: 1.4.0 -

-

- Project URL: - - - https://github.com/aio-libs/frozenlist - - -

-

- License: Apache 2 -

- - -
-

- 71 - Name: fsspec - Version: 2023.6.0 -

-

- Project URL: - - - http://github.com/fsspec/filesystem_spec - - -

-

- License: BSD -

- - -
-

- 72 - Name: future - Version: 0.18.2 -

-

- Project URL: - - - https://python-future.org - - -

-

- License: MIT -

- - -
-

- 73 - Name: gevent - Version: 23.9.1 -

-

- Project URL: - - - http://www.gevent.org/ - - -

-

- License: MIT -

- - -
-

- 74 - Name: gitdb - Version: 4.0.10 -

-

- Project URL: - - - https://github.com/gitpython-developers/gitdb - - -

-

- License: BSD License -

- - -
-

- 75 - Name: GitPython - Version: 3.1.31 -

-

- Project URL: - - - https://github.com/gitpython-developers/GitPython - - -

-

- License: BSD -

- - -
-

- 76 - Name: greenlet - Version: 3.0.1 -

-

- Project URL: - - - https://greenlet.readthedocs.io/ - - -

-

- License: MIT License -

- - -
-

- 77 - Name: h11 - Version: 0.14.0 -

-

- Project URL: - - - https://github.com/python-hyper/h11 - - -

-

- License: MIT -

- - -
-

- 78 - Name: huggingface-hub - Version: 0.16.2 -

-

- Project URL: - - - https://github.com/huggingface/huggingface_hub - - -

-

- License: Apache -

- - -
-

- 79 - Name: hyperlink - Version: 21.0.0 -

-

- Project URL: - - - https://github.com/python-hyper/hyperlink - - -

-

- License: MIT -

- - -
-

- 80 - Name: incremental - Version: 22.10.0 -

-

- Project URL: - - - https://github.com/twisted/incremental - - -

-

- License: MIT -

- - -
-

- 81 - Name: itemadapter - Version: 0.8.0 -

-

- Project URL: - - - https://github.com/scrapy/itemadapter - - -

-

- License: BSD -

- - -
-

- 82 - Name: itemloaders - Version: 1.1.0 -

-

- Project URL: - - - https://github.com/scrapy/itemloaders - - -

-

- License: BSD -

- - -
-

- 83 - Name: itsdangerous - Version: 2.1.2 -

-

- Project URL: - - - https://palletsprojects.com/p/itsdangerous/ - - -

-

- License: BSD-3-Clause -

- - -
-

- 84 - Name: Jinja2 - Version: 3.1.2 -

-

- Project URL: - - - https://palletsprojects.com/p/jinja/ - - -

-

- License: BSD-3-Clause -

- - -
-

- 85 - Name: jmespath - Version: 1.0.1 -

-

- Project URL: - - - https://github.com/jmespath/jmespath.py - - -

-

- License: MIT -

- - -
-

- 86 - Name: joblib - Version: 1.3.1 -

-

- Project URL: - - - https://github.com/joblib/joblib - - -

-

- License: BSD 3-Clause -

- - -
-

- 87 - Name: kiwisolver - Version: 1.4.5 -

-

- Project URL: - - - https://github.com/nucleic/kiwi - - -

-

- License: Revised BSD -

- - -
-

- 88 - Name: lxml - Version: 4.9.3 -

-

- Project URL: - - - https://lxml.de/ - - -

-

- License: BSD-3-Clause -

- - -
-

- 89 - Name: macholib - Version: 1.15.2 -

-

- Project URL: - - - http://github.com/ronaldoussoren/macholib - - -

-

- License: MIT -

- - -
-

- 90 - Name: MarkupSafe - Version: 2.1.3 -

-

- Project URL: - - - https://palletsprojects.com/p/markupsafe/ - - -

-

- License: BSD-3-Clause -

- - -
-

- 91 - Name: matplotlib - Version: 3.8.1 -

-

- Project URL: - - - https://matplotlib.org - - -

-

- License: PSF -

- - -
-

- 92 - Name: mpmath - Version: 1.3.0 -

-

- Project URL: - - - http://mpmath.org/ - - -

-

- License: BSD -

- - -
-

- 93 - Name: multidict - Version: 6.0.4 -

-

- Project URL: - - - https://github.com/aio-libs/multidict - - -

-

- License: Apache 2 -

- - -
-

- 94 - Name: numpy - Version: 1.25.0 -

-

- Project URL: - - - https://www.numpy.org - - -

-

- License: BSD-3-Clause -

- - -
-

- 95 - Name: parsel - Version: 1.8.1 -

-

- Project URL: - - - https://github.com/scrapy/parsel - - -

-

- License: BSD -

- - -
-

- 96 - Name: pathtools - Version: 0.1.2 -

-

- Project URL: - - - http://github.com/gorakhargosh/pathtools - - -

-

- License: MIT License -

- - -
-

- 97 - Name: Pillow - Version: 10.1.0 -

-

- Project URL: - - - https://python-pillow.org - - -

-

- License: HPND -

- - -
-

- 98 - Name: pip - Version: 23.2.1 -

-

- Project URL: - - - https://pip.pypa.io/ - - -

-

- License: MIT -

- - -
-

- 99 - Name: prettytable - Version: 3.9.0 -

-

- Project URL: - - - https://github.com/jazzband/prettytable - - -

-

- License: BSD (3 clause) -

- - -
-

- 100 - Name: Protego - Version: 0.2.1 -

-

- Project URL: - - - https://github.com/scrapy/protego - - -

-

- License: BSD -

- - -
-

- 101 - Name: protobuf - Version: 4.23.3 -

-

- Project URL: - - - https://developers.google.com/protocol-buffers/ - - -

-

- License: 3-Clause BSD License -

- - -
-

- 102 - Name: psutil - Version: 5.9.5 -

-

- Project URL: - - - https://github.com/giampaolo/psutil - - -

-

- License: BSD-3-Clause -

- - -
-

- 103 - Name: pyasn1 - Version: 0.5.0 -

-

- Project URL: - - - https://github.com/pyasn1/pyasn1 - - -

-

- License: BSD-2-Clause -

- - -
-

- 104 - Name: pyasn1-modules - Version: 0.3.0 -

-

- Project URL: - - - https://github.com/pyasn1/pyasn1-modules - - -

-

- License: BSD -

- - -
-

- 105 - Name: pycparser - Version: 2.21 -

-

- Project URL: - - - https://github.com/eliben/pycparser - - -

-

- License: BSD -

- - -
-

- 106 - Name: pydantic_core - Version: 2.14.5 -

-

- Project URL: - - - https://github.com/pydantic/pydantic-core - - -

-

- License: MIT -

- - -
-

- 107 - Name: PyDispatcher - Version: 2.0.7 -

-

- Project URL: - - - https://github.com/mcfletch/pydispatcher - - -

-

- License: BSD -

- - -
-

- 108 - Name: Pygments - Version: 2.16.1 -

-

- Project URL: - - - https://pygments.org/ - - -

-

- License: BSD-2-Clause -

- - -
-

- 109 - Name: pyOpenSSL - Version: 23.2.0 -

-

- Project URL: - - - https://pyopenssl.org/ - - -

-

- License: Apache License, Version 2.0 -

- - -
-

- 110 - Name: python-dateutil - Version: 2.8.2 -

-

- Project URL: - - - https://github.com/dateutil/dateutil - - -

-

- License: Dual License -

- - -
-

- 111 - Name: pytz - Version: 2023.3.post1 -

-

- Project URL: - - - http://pythonhosted.org/pytz - - -

-

- License: MIT -

- - -
-

- 112 - Name: PyYAML - Version: 6.0 -

-

- Project URL: - - - https://pyyaml.org/ - - -

-

- License: MIT -

- - -
-

- 113 - Name: queuelib - Version: 1.6.2 -

-

- Project URL: - - - https://github.com/scrapy/queuelib - - -

-

- License: BSD -

- - -
-

- 114 - Name: regex - Version: 2023.6.3 -

-

- Project URL: - - - https://github.com/mrabarnett/mrab-regex - - -

-

- License: Apache Software License -

- - -
-

- 115 - Name: requests - Version: 2.31.0 -

-

- Project URL: - - - https://requests.readthedocs.io - - -

-

- License: Apache 2.0 -

- - -
-

- 116 - Name: requests-file - Version: 1.5.1 -

-

- Project URL: - - - http://github.com/dashea/requests-file - - -

-

- License: Apache 2.0 -

- - - -
-

- 118 - Name: s3transfer - Version: 0.6.1 -

-

- Project URL: - - - https://github.com/boto/s3transfer - - -

-

- License: Apache License 2.0 -

- - -
-

- 119 - Name: safetensors - Version: 0.3.1 -

-

- Project URL: - - - https://github.com/huggingface/safetensors - - -

-

- License: Apache License 2.0 -

- - -
-

- 120 - Name: scikit-learn - Version: 1.3.0 -

-

- Project URL: - - - http://scikit-learn.org - - -

-

- License: new BSD -

- - -
-

- 121 - Name: GCC runtime library - Version: 1.11.1 -

-

- Project URL: - - - https://scipy.org/ - - -

-

- License: GPLv3 + runtime exception -

- - -
-

- 122 - Name: Scrapy - Version: 2.9.0 -

-

- Project URL: - - - https://scrapy.org - - -

-

- License: BSD -

- - -
-

- 123 - Name: sentry-sdk - Version: 1.27.0 -

-

- Project URL: - - - https://github.com/getsentry/sentry-python - - -

-

- License: MIT -

- - -
-

- 124 - Name: setproctitle - Version: 1.3.2 -

-

- Project URL: - - - https://github.com/dvarrazzo/py-setproctitle - - -

-

- License: BSD -

- - -
-

- 125 - Name: setuptools - Version: 58.0.4 -

-

- Project URL: - - - https://github.com/pypa/setuptools - - -

-

- License: MIT License -

- - -
-

- 126 - Name: six - Version: 1.15.0 -

-

- Project URL: - - - https://github.com/benjaminp/six - - -

-

- License: MIT -

- - -
- -

- 128 - Name: smmap - Version: 5.0.0 -

-

- Project URL: - - - https://github.com/gitpython-developers/smmap - - -

-

- License: BSD -

- - -
-

- 129 - Name: sniffio - Version: 1.3.0 -

-

- Project URL: - - - https://github.com/python-trio/sniffio - - -

-

- License: MIT OR Apache-2.0 -

- - -
-

- 130 - Name: sympy - Version: 1.12 -

-

- Project URL: - - - https://sympy.org - - -

-

- License: New BSD License -

- - -
-

- 131 - Name: threadpoolctl - Version: 3.1.0 -

-

- Project URL: - - - https://github.com/joblib/threadpoolctl - - -

-

- License: BSD-3-Clause -

- - -
-

- 132 - Name: tldextract - Version: 3.4.4 -

-

- Project URL: - - - https://github.com/john-kurkowski/tldextract - - -

-

- License: BSD-3-Clause -

- - -
-

- 133 - Name: tokenizers - Version: 0.13.3 -

-

- Project URL: - - - https://github.com/huggingface/tokenizers - - -

-

- License: Apache License 2.0 -

- - -
-

- 134 - Name: torch - Version: 2.0.1 -

-

- Project URL: - - - https://pytorch.org/ - - -

-

- License: BSD-3 -

- - -
-

- 135 - Name: tqdm - Version: 4.65.0 -

-

- Project URL: - - - https://tqdm.github.io - - -

-

- License: MPLv2.0, MIT Licences -

- - -
-

- 136 - Name: transformers - Version: 4.30.2 -

-

- Project URL: - - - https://github.com/huggingface/transformers - - -

-

- License: Apache 2.0 License -

- - -
-

- 137 - Name: tree-sitter - Version: 0.20.1 -

-

- Project URL: - - - https://github.com/tree-sitter/py-tree-sitter - - -

-

- License: MIT -

- - -
-

- 138 - Name: Twisted - Version: 22.10.0 -

-

- Project URL: - - - https://twistedmatrix.com/ - - -

-

- License: MIT -

- - -
-

- 139 - Name: urllib3 - Version: 1.26.16 -

-

- Project URL: - - - https://urllib3.readthedocs.io/ - - -

-

- License: MIT -

- - -
-

- 140 - Name: w3lib - Version: 2.1.1 -

-

- Project URL: - - - https://github.com/scrapy/w3lib - - -

-

- License: BSD -

- - -
-

- 141 - Name: wandb - Version: 0.15.4 -

-

- Project URL: - - - https://github.com/wandb/wandb - - -

-

- License: MIT license -

- - -
-

- 142 - Name: wcwidth - Version: 0.2.6 -

-

- Project URL: - - - https://github.com/jquast/wcwidth - - -

-

- License: MIT -

- - -
-

- 143 - Name: websocket-client - Version: 1.6.1 -

-

- Project URL: - - - https://github.com/websocket-client/websocket-client.git - - -

-

- License: Apache-2.0 -

- - -
-

- 144 - Name: wheel - Version: 0.37.0 -

-

- Project URL: - - - https://github.com/pypa/wheel - - -

-

- License: MIT -

- - -
-

- 145 - Name: wrapt - Version: 1.15.0 -

-

- Project URL: - - - https://github.com/GrahamDumpleton/wrapt - - -

-

- License: BSD -

- - -
-

- 146 - Name: yarl - Version: 1.9.2 -

-

- Project URL: - - - https://github.com/aio-libs/yarl/ - - -

-

- License: Apache-2.0 -

- - -
-

- 147 - Name: z3-solver - Version: 4.12.2.0 -

-

- Project URL: - - - https://github.com/Z3Prover/z3 - - -

-

- License: MIT License -

- - -
-

- 148 - Name: zope.event - Version: 5.0 -

-

- Project URL: - - - https://github.com/zopefoundation/zope.event - - -

-

- License: ZPL-2.1 -

- - -
-

- 149 - Name: zope.interface - Version: 6.0 -

-

- Project URL: - - - https://github.com/zopefoundation/zope.interface - - -

-

- License: ZPL 2.1 -

- - - - - diff --git a/build.gradle b/build.gradle index 9dfa1969..1d866353 100644 --- a/build.gradle +++ b/build.gradle @@ -1,17 +1,17 @@ import org.gradle.internal.os.OperatingSystem plugins { - id 'com.diffplug.spotless' version '6.9.0' + id 'com.diffplug.spotless' version '8.10.4' } tasks.register('copyNativeLibs', Copy) { // copies correct native z3 files to libs/ def osArchMap = [ - 'windows-x64': 'z3-4.15.4-x64-win', - 'windows-x86': 'z3-4.15.4-x86-win', - 'osx-x64' : 'z3-4.15.4-x64-osx-13.7.6', - 'osx-arm64' : 'z3-4.15.4-arm64-osx-13.7.6', - 'linux-x64' : 'z3-4.15.4-x64-glibc-2.39', - 'linux-arm64': 'z3-4.15.4-arm64-glibc-2.34' + 'windows-x64': 'z3-5.3.1-x64-win', + 'windows-x86': 'z3-5.3.1-x86-win', + 'osx-x64' : 'z3-5.3.1-x64-osx-13.3', + 'osx-arm64' : 'z3-5.3.1-arm64-osx-13.3', + 'linux-x64' : 'z3-5.3.1-x64-glibc-2.39', + 'linux-arm64': 'z3-5.3.1-arm64-glibc-2.38' ] def os = OperatingSystem.current() @@ -47,6 +47,9 @@ tasks.register('copyNativeLibs', Copy) { // copies correct native z3 files to li include "${osArchMap[osKey]}/bin/z3*" include "${osArchMap[osKey]}/bin/libz3.*" include "${osArchMap[osKey]}/bin/libz3java.*" + include "${osArchMap[osKey]}/bin/z3java.*" // Windows + // Z3 5.x ships the Java bindings in java/, older releases in bin/ + include "${osArchMap[osKey]}/java/com.microsoft.z3.jar" include "${osArchMap[osKey]}/bin/com.microsoft.z3.jar" eachFile { FileCopyDetails fcp -> fcp.relativePath = new RelativePath(true, fcp.name) diff --git a/cfg-extraction.ref b/cfg-extraction.ref new file mode 100644 index 00000000..22c08f72 --- /dev/null +++ b/cfg-extraction.ref @@ -0,0 +1 @@ +v0.2.1 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7f93135c..02f06ab2 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 3fa8f862..d3f02198 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.6-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 0adc8e1a..23d15a93 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -145,7 +147,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -153,7 +155,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -202,16 +204,16 @@ fi # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/gradlew.bat b/gradlew.bat index 93e3f59f..db3a6ac2 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/libs/maven/org/sosy-lab/java-smt/6.0.0-1-g1493c4018/java-smt-6.0.0-1-g1493c4018.jar b/libs/maven/org/sosy-lab/java-smt/6.0.0-1-g1493c4018/java-smt-6.0.0-1-g1493c4018.jar new file mode 100644 index 00000000..e24e7828 Binary files /dev/null and b/libs/maven/org/sosy-lab/java-smt/6.0.0-1-g1493c4018/java-smt-6.0.0-1-g1493c4018.jar differ diff --git a/libs/maven/org/sosy-lab/java-smt/6.0.0-1-g1493c4018/java-smt-6.0.0-1-g1493c4018.pom b/libs/maven/org/sosy-lab/java-smt/6.0.0-1-g1493c4018/java-smt-6.0.0-1-g1493c4018.pom new file mode 100644 index 00000000..e5034fe8 --- /dev/null +++ b/libs/maven/org/sosy-lab/java-smt/6.0.0-1-g1493c4018/java-smt-6.0.0-1-g1493c4018.pom @@ -0,0 +1,247 @@ + + + + + 4.0.0 + org.sosy-lab + java-smt + jar + 6.0.0-1-g1493c4018 + java-smt + Unified acccess layer to SMT solvers + https://github.com/sosy-lab/java-smt + + + + Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + + + + + Software Systems Lab + https://www.sosy-lab.org/ + + + + https://github.com/sosy-lab/java-smt/ + scm:git:git://github.com/sosy-lab/java-smt.git + scm:git:git@github.com:sosy-lab/java-smt.git + + + + + Karlheinz Friedberger + kfriedberger@gmail.com + Software Systems Lab + https://www.sosy-lab.org/people/friedberger/ + + project maintainer + + + + Dirk Beyer + dirk.beyer@sosy-lab.org + https://www.sosy-lab.org/people/beyer/ + Software Systems Lab + http://www.sosy-lab.org/ + + project manager + + + + + + + + + org.sosy-lab + common + 0.3000-609-g90a352c + compile + + + com.google.guava + guava + 33.4.8-jre + compile + + + com.google.guava + guava-testlib + 33.4.8-jre + true + + + com.google.auto.value + auto-value-annotations + 1.11.0 + true + + + org.checkerframework + checker-qual + 3.49.5 + compile + + + junit + junit + 4.13.2 + true + + + com.google.truth + truth + 1.4.5 + true + + + com.google.truth.extensions + truth-java8-extension + 1.4.5 + true + + + com.google.errorprone + error_prone_annotations + 2.41.0 + true + + + com.github.spotbugs + spotbugs-annotations + 4.9.4 + compile + + + com.google.code.findbugs + jsr305 + 3.0.2 + compile + + + de.uni-freiburg.informatik.ultimate + smtinterpol + 2.5-1242-g5c50fb6d + true + + + io.github.uuverifiers + princess_2.13 + 2025-11-17 + true + + + io.github.uuverifiers + ostrich_2.13 + 2.0 + true + + + io.github.uuverifiers + princess_2.13 + 2025-11-17 + true + + + net.sf.squirrel-sql.thirdparty-non-maven + * + + + + + io.github.uuverifiers + ostrich_2.13 + 2.0 + true + + + net.sf.squirrel-sql.thirdparty-non-maven + * + + + + + edu.tum.cs + java-cup + 11b-20160615 + true + + + org.sosy-lab + javasmt-solver-mathsat + 5.6.15 + true + + + org.sosy-lab + javasmt-solver-z3 + 4.15.4 + true + + + org.sosy-lab + javasmt-solver-opensmt + 2.9.0-gef441e1c + true + + + org.sosy-lab + javasmt-solver-optimathsat + 1.7.3-sosy1 + true + + + org.sosy-lab + javasmt-solver-cvc4 + 1.8-prerelease-2020-06-24-g7825d8f28 + true + + + org.sosy-lab + javasmt-solver-cvc5 + 2026-02-26-d22638a + true + + + org.sosy-lab + javasmt-solver-boolector + 3.2.2-g1a89c229 + true + + + org.sosy-lab + javasmt-solver-bitwuzla + 0.8.2-339-g550ed911 + true + + + org.sosy-lab + javasmt-solver-z3-legacy + 4.5.0-gd57a2a6dc + true + + + org.sosy-lab + javasmt-yices2 + 5.0.1-1258-gdad634a69 + true + + + org.codehaus.mojo + animal-sniffer-annotations + 1.18 + compile + + + diff --git a/libs/z3-4.15.4-x64-glibc-2.39.zip b/libs/z3-4.15.4-x64-glibc-2.39.zip deleted file mode 100644 index f5031b51..00000000 Binary files a/libs/z3-4.15.4-x64-glibc-2.39.zip and /dev/null differ diff --git a/libs/z3-4.15.4-x86-win.zip b/libs/z3-4.15.4-x86-win.zip deleted file mode 100644 index 38419c52..00000000 Binary files a/libs/z3-4.15.4-x86-win.zip and /dev/null differ diff --git a/libs/z3-4.15.4-arm64-glibc-2.34.zip b/libs/z3-5.3.1-arm64-glibc-2.38.zip similarity index 73% rename from libs/z3-4.15.4-arm64-glibc-2.34.zip rename to libs/z3-5.3.1-arm64-glibc-2.38.zip index 1155d780..512d124f 100644 Binary files a/libs/z3-4.15.4-arm64-glibc-2.34.zip and b/libs/z3-5.3.1-arm64-glibc-2.38.zip differ diff --git a/libs/z3-4.15.4-arm64-osx-13.7.6.zip b/libs/z3-5.3.1-arm64-osx-13.3.zip similarity index 74% rename from libs/z3-4.15.4-arm64-osx-13.7.6.zip rename to libs/z3-5.3.1-arm64-osx-13.3.zip index a2b3c985..3fc56508 100644 Binary files a/libs/z3-4.15.4-arm64-osx-13.7.6.zip and b/libs/z3-5.3.1-arm64-osx-13.3.zip differ diff --git a/libs/z3-4.15.4-x64-win.zip b/libs/z3-5.3.1-x64-glibc-2.39.zip similarity index 76% rename from libs/z3-4.15.4-x64-win.zip rename to libs/z3-5.3.1-x64-glibc-2.39.zip index 98b7e3a0..768ce303 100644 Binary files a/libs/z3-4.15.4-x64-win.zip and b/libs/z3-5.3.1-x64-glibc-2.39.zip differ diff --git a/libs/z3-4.15.4-x64-osx-13.7.6.zip b/libs/z3-5.3.1-x64-osx-13.3.zip similarity index 76% rename from libs/z3-4.15.4-x64-osx-13.7.6.zip rename to libs/z3-5.3.1-x64-osx-13.3.zip index 4043cf30..3f1f99e2 100644 Binary files a/libs/z3-4.15.4-x64-osx-13.7.6.zip and b/libs/z3-5.3.1-x64-osx-13.3.zip differ diff --git a/libs/z3-5.3.1-x64-win.zip b/libs/z3-5.3.1-x64-win.zip new file mode 100644 index 00000000..0096a966 Binary files /dev/null and b/libs/z3-5.3.1-x64-win.zip differ diff --git a/libs/z3-5.3.1-x86-win.zip b/libs/z3-5.3.1-x86-win.zip new file mode 100644 index 00000000..d9d05b66 Binary files /dev/null and b/libs/z3-5.3.1-x86-win.zip differ diff --git a/scripts/build-javasmt.sh b/scripts/build-javasmt.sh index 5c660dfc..85f2e31f 100755 --- a/scripts/build-javasmt.sh +++ b/scripts/build-javasmt.sh @@ -1,21 +1,29 @@ #!/bin/bash -# Script to build JavaSMT from source and copy to libs directory +# Builds JavaSMT from source and installs it into the local Maven repository +# libs/maven, from which symbolic-executor resolves org.sosy-lab:java-smt +# (version in settings.gradle). +# +# The branch is the JavaSMT release SWAT uses plus the changes needed for the +# vendored Z3 version. It is compiled against the Java bindings of that Z3 +# (com.microsoft.z3.jar from the vendored Linux Z3 distribution in libs/), so +# the bytecode matches the libz3java the executor loads at run time. set -e # Exit on any error # Get the directory where this script is located SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT_DIR="$SCRIPT_DIR/.." -# Configuration -REPO_URL="https://github.com/SWAT-project/java-smt.git" -BRANCH_NAME="feature/z3-native-options" +# Configuration (JAVASMT_REPO_URL: build from another clone, e.g. a local one) +REPO_URL="${JAVASMT_REPO_URL:-https://github.com/SWAT-project/java-smt.git}" +BRANCH_NAME="swat/6.0.0-z3-5" +BASE_RELEASE="6.0.0" # the JavaSMT release the branch is based on; its POM lists the dependencies +Z3_DIST="z3-5.3.1-x64-glibc-2.39" TEMP_DIR="$SCRIPT_DIR/temp-javasmt-build" -TARGET_DIR="$SCRIPT_DIR/../libs/java-library-path" -JAR_NAME="java-smt-latest.jar" +MAVEN_DIR="$ROOT_DIR/libs/maven/org/sosy-lab/java-smt" echo "🔨 Building JavaSMT from source..." - # Check if Ant is installed if ! command -v ant &> /dev/null then @@ -23,8 +31,11 @@ then exit 1 fi -# Create target directory if it doesn't exist -mkdir -p "$TARGET_DIR" +Z3_ZIP="$ROOT_DIR/libs/${Z3_DIST}.zip" +if [ ! -f "$Z3_ZIP" ]; then + echo "❌ Error: Z3 distribution not found at $Z3_ZIP" + exit 1 +fi # Clean up any existing temp directory if [ -d "$TEMP_DIR" ]; then @@ -42,24 +53,30 @@ cd "$TEMP_DIR" # Checkout the correct branch echo "Checking out $BRANCH_NAME..." git checkout "$BRANCH_NAME" - -# Build the project -echo "🔧 Building JavaSMT..." -ant jar - -# Find the generated JAR file (it has a version-specific name) -JAR_FILE=$(find . -name "java-smt-*.jar" -maxdepth 1 | head -n 1) - -if [ -z "$JAR_FILE" ]; then - echo "❌ Error: No java-smt JAR file found after build!" +VERSION="$(git describe --tags)" + +# Fetch the dependencies, then compile against the vendored Z3 Java bindings +# instead of the ones Ivy fetched (ivy.disable keeps Ivy from restoring them) +echo "🔧 Building JavaSMT $VERSION..." +ant resolve-dependencies +unzip -p "$Z3_ZIP" "${Z3_DIST}/java/com.microsoft.z3.jar" > lib/java/runtime-z3/com.microsoft.z3.jar +ant -Divy.disable=true -Divy.module=java-smt jar + +JAR_FILE="java-smt-${VERSION}.jar" +if [ ! -f "$JAR_FILE" ]; then + echo "❌ Error: $JAR_FILE not found after build!" cd "$SCRIPT_DIR" rm -rf "$TEMP_DIR" exit 1 fi -echo "📦 Found JAR: $JAR_FILE" -echo "📦 Copying to $TARGET_DIR/$JAR_NAME..." -cp "$JAR_FILE" "$TARGET_DIR/$JAR_NAME" +TARGET_DIR="$MAVEN_DIR/$VERSION" +echo "📦 Installing $JAR_FILE into $TARGET_DIR..." +mkdir -p "$TARGET_DIR" +cp "$JAR_FILE" "$TARGET_DIR/" +curl -fsSL "https://repo1.maven.org/maven2/org/sosy-lab/java-smt/${BASE_RELEASE}/java-smt-${BASE_RELEASE}.pom" \ + | sed "0,/${BASE_RELEASE}<\/version>/s//${VERSION}<\/version>/" \ + > "$TARGET_DIR/java-smt-${VERSION}.pom" # Navigate back to script directory cd "$SCRIPT_DIR" @@ -68,8 +85,6 @@ cd "$SCRIPT_DIR" echo "🧹 Cleaning up temporary build directory..." rm -rf "$TEMP_DIR" -echo "✅ JavaSMT build complete! JAR available at: $TARGET_DIR/$JAR_NAME" +echo "✅ JavaSMT build complete: $TARGET_DIR" echo "" -echo "💡 To use this JAR, update your build.gradle dependencies:" -echo " implementation files('$TARGET_DIR/$JAR_NAME')" -echo " // and remove or comment out: implementation libs.java.smt" \ No newline at end of file +echo "💡 To use it, set the java-smt version in settings.gradle to $VERSION" diff --git a/scripts/build-svcomp-python-runtime.sh b/scripts/build-svcomp-python-runtime.sh new file mode 100755 index 00000000..29bec7b0 --- /dev/null +++ b/scripts/build-svcomp-python-runtime.sh @@ -0,0 +1,87 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Builds the Python dependency directory that ships in the SV-COMP archive as +# libs/python/. The archive runs SymbolicExplorer with the competition +# machine's system python3 and puts this directory first on PYTHONPATH, so no +# virtual environment (and no absolute interpreter path) is baked into the ZIP. +# +# Wheels are resolved for the *target* interpreter, not the one running pip, +# so this works on any build host (e.g. an ubuntu-24.04 CI runner building for +# the Ubuntu 26.04 / Python 3.14 competition image). +# +# Usage: scripts/build-svcomp-python-runtime.sh +# +# Environment: +# SWAT_SVCOMP_PYTHON_VERSION target CPython "major.minor" (default: 3.14, +# the python3 of Ubuntu 26.04 / SV-COMP 2027) +# SWAT_SVCOMP_PIP pip command to use (default: "python3 -m pip") +# SWAT_SVCOMP_WITNESSCREATOR replaces the pinned witnesscreator requirement +# (git tag) with a local source directory or +# wheel, for builds against an unreleased version + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +REQUIREMENTS="${ROOT_DIR}/scripts/svcomp-package/requirements.txt" +PY_VERSION="${SWAT_SVCOMP_PYTHON_VERSION:-3.14}" +PIP_CMD="${SWAT_SVCOMP_PIP:-python3 -m pip}" +OUT_DIR="${1:?usage: $0 }" + +[[ "$PY_VERSION" =~ ^3\.[0-9]+$ ]] || { echo "error: invalid SWAT_SVCOMP_PYTHON_VERSION: ${PY_VERSION}" >&2; exit 1; } +PY_TAG="cp${PY_VERSION/./}" + +if [[ -e "$OUT_DIR" ]] && [[ -n "$(ls -A "$OUT_DIR")" ]]; then + echo "error: output directory is not empty: ${OUT_DIR}" >&2 + exit 1 +fi +mkdir -p "$OUT_DIR" + +# witnesscreator (pure Python, stdlib only) is pinned to a git tag and built from +# source below; everything else comes as wheels for the target interpreter. +WHEEL_REQUIREMENTS="$(mktemp "${TMPDIR:-/tmp}/swat-svcomp-requirements.XXXXXX")" +trap 'rm -f "$WHEEL_REQUIREMENTS"' EXIT +grep -v '^witnesscreator' "$REQUIREMENTS" > "$WHEEL_REQUIREMENTS" +WITNESSCREATOR_REQ="${SWAT_SVCOMP_WITNESSCREATOR:-$(grep '^witnesscreator' "$REQUIREMENTS")}" +[[ -n "$WITNESSCREATOR_REQ" ]] || { echo "error: no witnesscreator requirement in ${REQUIREMENTS}" >&2; exit 1; } + +# --no-deps: requirements.txt is a complete, pinned closure. +# --only-binary: never compile on the build host (would target the wrong ABI). +# shellcheck disable=SC2086 +$PIP_CMD install \ + --disable-pip-version-check \ + --no-cache-dir \ + --no-compile \ + --no-deps \ + --only-binary=:all: \ + --implementation cp \ + --python-version "$PY_VERSION" \ + --abi "$PY_TAG" \ + --abi abi3 \ + --abi none \ + --platform manylinux_2_17_x86_64 \ + --platform manylinux2014_x86_64 \ + --platform manylinux_2_27_x86_64 \ + --platform manylinux_2_28_x86_64 \ + --platform manylinux_2_34_x86_64 \ + --platform any \ + --target "$OUT_DIR" \ + -r "$WHEEL_REQUIREMENTS" + +# Pure-Python package: built on the host, valid for any CPython >= 3.10. +# shellcheck disable=SC2086 +$PIP_CMD install \ + --disable-pip-version-check \ + --no-cache-dir \ + --no-compile \ + --no-deps \ + --target "$OUT_DIR" \ + "$WITNESSCREATOR_REQ" + +# Console-script wrappers carry the build host's interpreter path; unused. +rm -rf "${OUT_DIR}/bin" +find "$OUT_DIR" -name '__pycache__' -type d -prune -exec rm -rf {} + + +# Non-hidden marker (BenchExec skips hidden files when collecting tool files). +echo "$PY_VERSION" > "${OUT_DIR}/PYTHON_VERSION" +cp "$REQUIREMENTS" "${OUT_DIR}/requirements.txt" + +echo "Python runtime for CPython ${PY_VERSION} written to: ${OUT_DIR}" diff --git a/scripts/check-svcomp-package.sh b/scripts/check-svcomp-package.sh new file mode 100755 index 00000000..ec7ce60f --- /dev/null +++ b/scripts/check-svcomp-package.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Checks an SV-COMP ZIP built by scripts/package-svcomp.sh against the archive +# requirements (single top-level directory, LICENSE, THIRD-PARTY-LICENSES.md, +# README, smoketest.sh, one-line --version) and runs it in a bare competition base image with only +# the fm-tools required_ubuntu_packages installed, like `fm-weck smoke-test`. +# +# Usage: scripts/check-svcomp-package.sh [base-image] +# Requires docker. Default base image: docker.io/ubuntu:26.04 (SV-COMP 2027). + +ZIP_PATH="$(realpath "${1:?usage: $0 [base-image]}")" +BASE_IMAGE="${2:-docker.io/ubuntu:26.04}" +# Keep in sync with required_ubuntu_packages in fm-tools data/swat.yml. +UBUNTU_PACKAGES="python3 openjdk-17-jdk-headless" + +fail() { + echo "error: $*" >&2 + exit 1 +} + +echo "ZIP: ${ZIP_PATH} ($(stat -c %s "$ZIP_PATH") bytes)" + +top_dirs="$(unzip -Z1 "$ZIP_PATH" | cut -d/ -f1 | sort -u)" +[[ "$(wc -l <<< "$top_dirs")" -eq 1 ]] || fail "ZIP must have a single top-level directory, found: ${top_dirs//$'\n'/ }" + +check_dir="$(mktemp -d "${TMPDIR:-/tmp}/swat-svcomp-check.XXXXXX")" +unzip -q "$ZIP_PATH" -d "$check_dir" +tool_dir="${check_dir}/${top_dirs}" +for f in LICENSE README.md smoketest.sh run-swat.sh libs/python/PYTHON_VERSION cfg-extraction/cfg-extractor-precheck.jar cfg-extraction/cfg-extractor.jar; do + [[ -f "${tool_dir}/${f}" ]] || fail "missing ${f} in archive root ${top_dirs}/" +done +[[ -s "${tool_dir}/THIRD-PARTY-LICENSES.md" ]] || fail "missing or empty THIRD-PARTY-LICENSES.md in archive root ${top_dirs}/" +[[ -x "${tool_dir}/run-swat.sh" && -x "${tool_dir}/smoketest.sh" ]] || fail "run-swat.sh and smoketest.sh must be executable" + +echo "Running checks in ${BASE_IMAGE} (packages: ${UBUNTU_PACKAGES})" +docker run --rm -v "${tool_dir}:/tool" -w /tool -e UBUNTU_PACKAGES="$UBUNTU_PACKAGES" "$BASE_IMAGE" bash -euc ' + apt-get update -qq + DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends $UBUNTU_PACKAGES > /dev/null + echo "python3: $(python3 --version)" + echo "java: $(java -version 2>&1 | head -n 1)" + PYTHONPATH=libs/python python3 -c "import fastapi, pydantic, uvicorn, requests, yaml, z3, witnesscreator; print(\"z3:\", z3.get_version_string())" + version="$(./run-swat.sh --version)" + echo "version: ${version}" + [ -n "$version" ] && [ "$(printf "%s\n" "$version" | wc -l)" -eq 1 ] || { echo "error: --version must print one non-empty line" >&2; exit 1; } + [ "$(./run-swat.sh -v)" = "$version" ] || { echo "error: -v and --version differ" >&2; exit 1; } + precheck="$(java -cp cfg-extraction/cfg-extractor-precheck.jar de.uzl.its.cfg.staticcheck.ExceptionFreedomCheck smoketest/common smoketest | tail -n 1)" + echo "property pre-check: ${precheck}" + case "$precheck" in *"\"tool\": \"cfg-extraction\""*) ;; *) echo "error: the property pre-check did not run" >&2; exit 1;; esac + ./smoketest.sh > smoketest.out 2>&1 || { cat smoketest.out; exit 1; } + echo "smoketest stdout: $(wc -c < smoketest.out) bytes" + [ -f witness/witness.yml ] && [ -f witness/witness.graphml ] || { echo "error: smoketest left no witness/witness.yml + witness.graphml" >&2; exit 1; } + tail -n 1 smoketest.out + # The smoke test is a valid-assert task, so the static pre-analysis must have run and its graph + # been used: a failing extractor only shows as an unpruned exploration, not as a wrong verdict. + sa_status="$(python3 -c "import json; print(json.load(open(\"logs/stats.json\"))[\"static_analysis\"][\"status\"])")" + echo "static pre-analysis: ${sa_status}" + [ "$sa_status" = "loaded" ] || { echo "error: the static pre-analysis did not load its graph (status ${sa_status}), see out.log" >&2; exit 1; } +' +echo "SV-COMP package check passed." diff --git a/scripts/package-svcomp.sh b/scripts/package-svcomp.sh new file mode 100755 index 00000000..cb5436cd --- /dev/null +++ b/scripts/package-svcomp.sh @@ -0,0 +1,188 @@ +#!/usr/bin/env bash +set -euo pipefail + +# This script packages artifacts that were already produced by CI. It must not +# invoke Gradle, create virtual environments, or download dependencies. +# +# Generated files are expected in the repository checkout after the CI build. +# Set SWAT_SVCOMP_ARTIFACT_DIR only if those generated files live somewhere +# else. The artifact root must contain: +# +# - symbolic-executor/lib/symbolic-executor.jar, or symbolic-executor.jar at the +# root (actions/upload-artifact drops the directories of a single-file upload) +# - cfg-extraction/build/libs/cfg-extractor-precheck.jar, or cfg-extractor-precheck.jar +# at the root: the property pre-check, built from cfg-extraction at the ref pinned in +# cfg-extraction.ref (./gradlew precheckJar) +# - cfg-extraction/build/libs/cfg-extractor-1.0-SNAPSHOT-all.jar, or cfg-extractor-1.0-SNAPSHOT-all.jar +# at the root: the static pre-analysis (the extractor's fat jar, ./gradlew fatJar, same ref), which +# run_swat.py runs under valid-assert +# +# Set SWAT_SVCOMP_PYTHON_DIR to the Python dependency directory produced by +# scripts/build-svcomp-python-runtime.sh (it includes the witnesscreator +# package that writes the witnesses). It is shipped as libs/python/ and used +# with the competition machine's system python3 (no bundled venv). +# +# Z3 and JavaSMT are taken from the vendored files in this repository (libs/). +# +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$ROOT_DIR" + +VERSION="${SWAT_SVCOMP_VERSION:-$(git rev-parse --short HEAD)}" +COMMIT="${SWAT_SVCOMP_COMMIT:-$(git rev-parse HEAD 2>/dev/null || echo unknown)}" +REF="${SWAT_SVCOMP_REF:-$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo unknown)}" +CHANNEL="${SWAT_SVCOMP_CHANNEL:-}" +PACKAGE_NAME="swat-svcomp-${VERSION}" +WORK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/swat-svcomp-package.XXXXXX")" +PACKAGE_DIR="${WORK_DIR}/${PACKAGE_NAME}" +DIST_DIR="${ROOT_DIR}/build/distributions" +SUPPORT_DIR="${ROOT_DIR}/scripts/svcomp-package" +ARTIFACT_DIR="$(cd "${SWAT_SVCOMP_ARTIFACT_DIR:-$ROOT_DIR}" && pwd)" +PYTHON_DIR="${SWAT_SVCOMP_PYTHON_DIR:-}" +LINUX_Z3_DIST="z3-5.3.1-x64-glibc-2.39" +LINUX_Z3_ZIP="${ROOT_DIR}/libs/${LINUX_Z3_DIST}.zip" +JAVA_SMT_DIR="${ROOT_DIR}/libs/maven/org/sosy-lab/java-smt" + +if [[ -n "$PYTHON_DIR" ]]; then + PYTHON_DIR="$(cd "$PYTHON_DIR" && pwd)" +fi + +fail() { + echo "error: $*" >&2 + exit 1 +} + +first_artifact_file() { + local path + for rel in "$@"; do + path="${ARTIFACT_DIR}/${rel}" + if [[ -f "$path" ]]; then + printf '%s\n' "$path" + return 0 + fi + done + fail "missing built artifact file; checked: $*" +} + +copy_tree_files() { + local src="$1" + local dest="$2" + mkdir -p "$dest" + find "$src" -type f \ + ! -path '*/__pycache__/*' \ + ! -path '*/.venv/*' \ + ! -name '*.pyc' \ + ! -name '*.pyo' \ + ! -name '*.iml' \ + -print0 | while IFS= read -r -d '' file; do + local rel="${file#"$src"/}" + mkdir -p "$dest/$(dirname "$rel")" + cp "$file" "$dest/$rel" + done +} + +copy_artifact_file() { + local src="$1" + local dest="$2" + local mode="${3:-0644}" + mkdir -p "$(dirname "$dest")" + install -m "$mode" "$src" "$dest" +} + +copy_artifact_tree() { + local src="$1" + local dest="$2" + [[ -d "$src" ]] || fail "missing artifact directory: ${src}" + mkdir -p "$(dirname "$dest")" + cp -a "$src" "$dest" +} + +# Installs of the vendored Linux Z3 distribution (relative to its root) into +# libs/java-library-path/ under its file name, or under [name] if given. +install_z3_runtime_file() { + local path="$1" + local mode="${2:-0644}" + local dest="${PACKAGE_DIR}/libs/java-library-path/${3:-$(basename "$path")}" + + [[ -f "$LINUX_Z3_ZIP" ]] || fail "missing vendored Linux Z3 distribution: ${LINUX_Z3_ZIP}" + mkdir -p "$(dirname "$dest")" + unzip -p "$LINUX_Z3_ZIP" "${LINUX_Z3_DIST}/${path}" > "$dest" + [[ -s "$dest" ]] || fail "missing ${path} in ${LINUX_Z3_ZIP}" + chmod "$mode" "$dest" +} + +echo "Packaging repository files from: ${ROOT_DIR}" +echo "Packaging built artifacts from: ${ARTIFACT_DIR}" + +echo "Creating package staging directory: ${PACKAGE_DIR}" +mkdir -p "$PACKAGE_DIR" +{ + echo "version=${VERSION}" + echo "channel=${CHANNEL}" + echo "ref=${REF}" + echo "commit=${COMMIT}" +} > "$PACKAGE_DIR/BUILD_INFO.txt" + +install -m 0644 LICENSE "$PACKAGE_DIR/LICENSE" +install -m 0644 "$SUPPORT_DIR/README.md" "$PACKAGE_DIR/README.md" +install -m 0755 "$SUPPORT_DIR/run-swat.sh" "$PACKAGE_DIR/run-swat.sh" +install -m 0755 "$SUPPORT_DIR/compile-target.sh" "$PACKAGE_DIR/compile-target.sh" +install -m 0755 "$SUPPORT_DIR/smoketest.sh" "$PACKAGE_DIR/smoketest.sh" +install -m 0755 "$SUPPORT_DIR/run_swat.py" "$PACKAGE_DIR/run_swat.py" +install -m 0644 "$SUPPORT_DIR/requirements.txt" "$PACKAGE_DIR/requirements.txt" +install -m 0644 targets/sv-comp/sv-comp.cfg "$PACKAGE_DIR/sv-comp.cfg" + +EXECUTOR_JAR="$(first_artifact_file \ + symbolic-executor/lib/symbolic-executor.jar \ + symbolic-executor.jar \ + symbolic-executor/build/libs/symbolic-executor-all.jar)" +copy_artifact_file "$EXECUTOR_JAR" "$PACKAGE_DIR/symbolic-executor/lib/symbolic-executor.jar" + +# The property pre-check from cfg-extraction (svcomp/PropertyPreCheck.py, --precheck-jar). +PRECHECK_JAR="$(first_artifact_file \ + cfg-extraction/build/libs/cfg-extractor-precheck.jar \ + cfg-extractor-precheck.jar)" +copy_artifact_file "$PRECHECK_JAR" "$PACKAGE_DIR/cfg-extraction/cfg-extractor-precheck.jar" + +# The static pre-analysis from cfg-extraction (svcomp/StaticPreAnalysis.py, --sa-jar). +EXTRACTOR_JAR="$(first_artifact_file \ + cfg-extraction/build/libs/cfg-extractor-1.0-SNAPSHOT-all.jar \ + cfg-extractor-1.0-SNAPSHOT-all.jar)" +copy_artifact_file "$EXTRACTOR_JAR" "$PACKAGE_DIR/cfg-extraction/cfg-extractor.jar" + +copy_tree_files symbolic-explorer "$PACKAGE_DIR/symbolic-explorer" + +install_z3_runtime_file bin/z3 0755 +install_z3_runtime_file bin/libz3.so +install_z3_runtime_file bin/libz3java.so +install_z3_runtime_file java/com.microsoft.z3.jar +install_z3_runtime_file LICENSE.txt 0644 z3-LICENSE.txt +# The JavaSMT build the executor uses (scripts/build-javasmt.sh); it is shaded into the executor JAR +# and shipped next to it for reference. +JAVA_SMT_JARS=("$JAVA_SMT_DIR"/*/java-smt-*.jar) +[[ ${#JAVA_SMT_JARS[@]} -eq 1 && -f "${JAVA_SMT_JARS[0]}" ]] || fail "expected one JavaSMT JAR in ${JAVA_SMT_DIR}, found: ${JAVA_SMT_JARS[*]}" +JAVA_SMT_JAR="${JAVA_SMT_JARS[0]}" +copy_artifact_file "$JAVA_SMT_JAR" "$PACKAGE_DIR/libs/java-library-path/java-smt-latest.jar" + +copy_tree_files "$SUPPORT_DIR/smoketest" "$PACKAGE_DIR/smoketest" + +[[ -n "$PYTHON_DIR" ]] || fail "SWAT_SVCOMP_PYTHON_DIR must point to the output of scripts/build-svcomp-python-runtime.sh" +[[ -f "$PYTHON_DIR/PYTHON_VERSION" ]] || fail "missing ${PYTHON_DIR}/PYTHON_VERSION; build it with scripts/build-svcomp-python-runtime.sh" +[[ -d "$PYTHON_DIR/witnesscreator" ]] || fail "missing witnesscreator package in ${PYTHON_DIR}; build it with scripts/build-svcomp-python-runtime.sh" +copy_artifact_tree "$PYTHON_DIR" "${PACKAGE_DIR}/libs/python" + +# Fails if a bundled component has no determinable license or license text. +python3 "$SUPPORT_DIR/third_party_licenses.py" "$PACKAGE_DIR" + +mkdir -p "$DIST_DIR" +ZIP_PATH="${DIST_DIR}/${PACKAGE_NAME}.zip" +ZIP_TMP="${WORK_DIR}/${PACKAGE_NAME}.zip" + +echo "Creating ZIP: ${ZIP_PATH}" +( + cd "$WORK_DIR" + zip -qr "$ZIP_TMP" "$PACKAGE_NAME" +) +cp "$ZIP_TMP" "$ZIP_PATH" + +echo "Package created at: ${ZIP_PATH}" +echo "Staging directory left at: ${PACKAGE_DIR}" diff --git a/scripts/svcomp-package/README.md b/scripts/svcomp-package/README.md new file mode 100644 index 00000000..d65e2864 --- /dev/null +++ b/scripts/svcomp-package/README.md @@ -0,0 +1,32 @@ +# SWAT SV-COMP Package + +This archive contains a runnable SWAT package for SV-COMP style Java verification tasks. + +Important entry points: + +- `run-swat.sh`: verifier entry point used by BenchExec (`run-swat.sh --version` + or `-v` prints the version). +- `compile-target.sh`: helper used by `run_swat.py` to compile Java benchmark sources. +- `smoketest.sh`: runs SWAT on the bundled smoke-test target. +- `sv-comp.cfg`: SWAT configuration for SV-COMP mode. +- `cfg-extraction/cfg-extractor-precheck.jar`: the static property pre-check (from + cfg-extraction), which can prove no-runtime-exception or valid-assert before exploring. +- `cfg-extraction/cfg-extractor.jar`: the static pre-analysis (from cfg-extraction), whose + control-flow graph of the task prunes and slices the exploration under valid-assert. +- `THIRD-PARTY-LICENSES.md`: bundled third-party components and their licenses. + +Requirements (SV-COMP 2027 environment, Ubuntu 26.04): `python3` (3.14) and +Java 17 (`openjdk-17-jdk-headless`, which also provides `javac`). + +All Python dependencies are bundled in `libs/python/` and are used with the +system `python3`; no virtual environment is needed. They are built for the +Python version recorded in `libs/python/PYTHON_VERSION`, and `run-swat.sh` +refuses to start with a different one. Set `SWAT_PYTHON` to use another +interpreter of that version. The full tool log of the last run is in `out.log`; +stdout is truncated if it would exceed SV-COMP's output limit. + +Example: + +```sh +./run-swat.sh ../../sv-benchmarks/java/properties/valid-assert.prp smoketest/common smoketest +``` diff --git a/scripts/svcomp-package/compile-target.sh b/scripts/svcomp-package/compile-target.sh new file mode 100755 index 00000000..5d2e5ff5 --- /dev/null +++ b/scripts/svcomp-package/compile-target.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +# Check if at least two arguments are provided +if [ "$#" -lt 2 ]; then + echo "Usage: $0 ... " + exit 1 +fi + +# Check if the fourth argument ends with 'rbtree' and swap if true +if [[ "${4}" == *rbtree ]]; then + temp=$3 + set -- "${@:1:2}" "$4" "$temp" "${@:5}" +fi + +# print all params +shift # Remove the first argument (property file) + +# Extract the target file (last argument) +target_directory="${!#}" # Get the last argument + +# Construct the classpath using all remaining arguments except the last one +classpath=$(IFS=:; echo "${*:1:$#-1}/") + +build_directory="$target_directory/build" + +#echo $target_directory $classpath $build_directory +# Create the build directory if it doesn't exist +mkdir -p "$build_directory" + +# Use find to get all .java files recursively from the target directory +java_files=$(find "$target_directory" -name "*.java") + +echo "javac -source 1.8 -target 1.8 -cp \"$classpath\" -d \"$build_directory\" $java_files" +# Compile all the found .java files +javac -source 1.8 -target 1.8 -cp "$classpath" -d "$build_directory" $java_files \ No newline at end of file diff --git a/scripts/svcomp-package/licenses/Apache-2.0.txt b/scripts/svcomp-package/licenses/Apache-2.0.txt new file mode 100644 index 00000000..137069b8 --- /dev/null +++ b/scripts/svcomp-package/licenses/Apache-2.0.txt @@ -0,0 +1,73 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/scripts/svcomp-package/licenses/BSD-3-Clause.txt b/scripts/svcomp-package/licenses/BSD-3-Clause.txt new file mode 100644 index 00000000..ea890afb --- /dev/null +++ b/scripts/svcomp-package/licenses/BSD-3-Clause.txt @@ -0,0 +1,11 @@ +Copyright (c) . + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/scripts/svcomp-package/licenses/EPL-2.0.txt b/scripts/svcomp-package/licenses/EPL-2.0.txt new file mode 100644 index 00000000..78756b5e --- /dev/null +++ b/scripts/svcomp-package/licenses/EPL-2.0.txt @@ -0,0 +1,80 @@ +Eclipse Public License - v 2.0 +THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE (“AGREEMENT”). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +1. DEFINITIONS +“Contribution” means: + +a) in the case of the initial Contributor, the initial content Distributed under this Agreement, and +b) in the case of each subsequent Contributor: +i) changes to the Program, and +ii) additions to the Program; +where such changes and/or additions to the Program originate from and are Distributed by that particular Contributor. A Contribution “originates” from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include changes or additions to the Program that are not Modified Works. +“Contributor” means any person or entity that Distributes the Program. + +“Licensed Patents” mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program. + +“Program” means the Contributions Distributed in accordance with this Agreement. + +“Recipient” means anyone who receives the Program under this Agreement or any Secondary License (as applicable), including Contributors. + +“Derivative Works” shall mean any work, whether in Source Code or other form, that is based on (or derived from) the Program and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. + +“Modified Works” shall mean any work in Source Code or other form that results from an addition to, deletion from, or modification of the contents of the Program, including, for purposes of clarity any new file in Source Code form that contains any contents of the Program. Modified Works shall not include works that contain only declarations, interfaces, types, classes, structures, or files of the Program solely in each case in order to link to, bind by name, or subclass the Program or Modified Works thereof. + +“Distribute” means the acts of a) distributing or b) making available in any manner that enables the transfer of a copy. + +“Source Code” means the form of a Program preferred for making modifications, including but not limited to software source code, documentation source, and configuration files. + +“Secondary License” means either the GNU General Public License, Version 2.0, or any later versions of that license, including any exceptions or additional permissions as identified by the initial Contributor. + +2. GRANT OF RIGHTS +a) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, Distribute and sublicense the Contribution of such Contributor, if any, and such Derivative Works. +b) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in Source Code or other form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder. +c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to Distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program. +d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement. +e) Notwithstanding the terms of any Secondary License, no Contributor makes additional grants to any Recipient (other than those set forth in this Agreement) as a result of such Recipient's receipt of the Program under the terms of a Secondary License (if permitted under the terms of Section 3). +3. REQUIREMENTS +3.1 If a Contributor Distributes the Program in any form, then: + +a) the Program must also be made available as Source Code, in accordance with section 3.2, and the Contributor must accompany the Program with a statement that the Source Code for the Program is available under this Agreement, and informs Recipients how to obtain it in a reasonable manner on or through a medium customarily used for software exchange; and +b) the Contributor may Distribute the Program under a license different than this Agreement, provided that such license: +i) effectively disclaims on behalf of all other Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose; +ii) effectively excludes on behalf of all other Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits; +iii) does not attempt to limit or alter the recipients' rights in the Source Code under section 3.2; and +iv) requires any subsequent distribution of the Program by any party to be under a license that satisfies the requirements of this section 3. +3.2 When the Program is Distributed as Source Code: + +a) it must be made available under this Agreement, or if the Program (i) is combined with other material in a separate file or files made available under a Secondary License, and (ii) the initial Contributor attached to the Source Code the notice described in Exhibit A of this Agreement, then the Program may be made available under the terms of such Secondary Licenses, and +b) a copy of this Agreement must be included with each copy of the Program. +3.3 Contributors may not remove or alter any copyright, patent, trademark, attribution notices, disclaimers of warranty, or limitations of liability (‘notices’) contained within the Program from any copy of the Program which they Distribute, provided that Contributors may add their own appropriate notices. + +4. COMMERCIAL DISTRIBUTION +Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor (“Commercial Contributor”) hereby agrees to defend and indemnify every other Contributor (“Indemnified Contributor”) against any losses, damages and costs (collectively “Losses”) arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: a) promptly notify the Commercial Contributor in writing of such claim, and b) allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense. + +For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages. + +5. NO WARRANTY +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement, including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations. + +6. DISCLAIMER OF LIABILITY +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +7. GENERAL +If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. + +If Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed. + +All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive. + +Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be Distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to Distribute the Program (including its Contributions) under the new version. + +Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved. Nothing in this Agreement is intended to be enforceable by any entity that is not a Contributor or Recipient. No third-party beneficiary rights are created under this Agreement. + +Exhibit A – Form of Secondary Licenses Notice +“This Source Code may also be made available under the following Secondary Licenses when the conditions for such availability set forth in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), version(s), and exceptions or additional permissions here}.” + +Simply including a copy of this Agreement, including this Exhibit A is not sufficient to license the Source Code under Secondary Licenses. + +If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + +You may add additional accurate notices of copyright ownership. diff --git a/scripts/svcomp-package/licenses/LGPL-2.1-only.txt b/scripts/svcomp-package/licenses/LGPL-2.1-only.txt new file mode 100644 index 00000000..c6487f4f --- /dev/null +++ b/scripts/svcomp-package/licenses/LGPL-2.1-only.txt @@ -0,0 +1,176 @@ +GNU LESSER GENERAL PUBLIC LICENSE + +Version 2.1, February 1999 + +Copyright (C) 1991, 1999 Free Software Foundation, Inc. +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] + +Preamble + +The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. + +This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. + +When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. + +To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. + +For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. + +We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. + +To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. + +Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. + +Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. + +When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. + +We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. + +For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. + +In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. + +Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. + +The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. + +GNU LESSER GENERAL PUBLIC LICENSE +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". + +A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. + +The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) + +"Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. + +Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. + +1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. + +You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. + +(For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. + +3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. + +Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. + +This option is useful when you wish to copy part of the code of the Library into a program that is not a library. + +4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. + +If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. + +5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. + +However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. + +When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. + +If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) + +Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. + +6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. + +You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: + + a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. + + e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. + +For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. + +It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. + +7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. + + b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. + +8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. + +9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. + +10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. + +11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. + +This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. + +12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. + +13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. + +14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. + +NO WARRANTY + +15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +END OF TERMS AND CONDITIONS + +How to Apply These Terms to Your New Libraries + +If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). + +To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. + + one line to give the library's name and an idea of what it does. + Copyright (C) year name of author + + This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: + +Yoyodyne, Inc., hereby disclaims all copyright interest in +the library `Frob' (a library for tweaking knobs) written +by James Random Hacker. + +signature of Ty Coon, 1 April 1990 +Ty Coon, President of Vice +That's all there is to it! diff --git a/scripts/svcomp-package/licenses/MIT.txt b/scripts/svcomp-package/licenses/MIT.txt new file mode 100644 index 00000000..d817195d --- /dev/null +++ b/scripts/svcomp-package/licenses/MIT.txt @@ -0,0 +1,18 @@ +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT +LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO +EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/scripts/svcomp-package/requirements.txt b/scripts/svcomp-package/requirements.txt new file mode 100644 index 00000000..ff3568e3 --- /dev/null +++ b/scripts/svcomp-package/requirements.txt @@ -0,0 +1,21 @@ +annotated-doc==0.0.5 +annotated-types==0.8.0 +anyio==4.15.1 +certifi==2026.10.19 +charset-normalizer==3.5.2 +click==8.5.0 +fastapi==0.151.0 +h11==0.16.0 +idna==3.20 +opentelemetry-api==1.45.1 +pydantic==2.14.1 +pydantic_core==2.50.1 +PyYAML==6.0.3 +requests==2.34.2 +starlette==1.9.0 +typing-inspection==0.4.4 +typing_extensions==4.16.0 +urllib3==2.8.0 +uvicorn==0.54.0 +witnesscreator @ git+https://github.com/SWAT-project/WitnessCreator@v1.0.0 +z3-solver==5.3.1.0 diff --git a/scripts/svcomp-package/run-swat.sh b/scripts/svcomp-package/run-swat.sh new file mode 100755 index 00000000..8a454e19 --- /dev/null +++ b/scripts/svcomp-package/run-swat.sh @@ -0,0 +1,70 @@ +#!/bin/bash + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# '-v' is what the BenchExec tool-info module passes; '--version' is the +# SV-COMP archive convention. Both print a single version line. +for arg in "$@" +do + if [ "$arg" == "-v" ] || [ "$arg" == "--version" ]; then + # The package version (e.g. svcomp27-rc1) from BUILD_INFO.txt, written by + # scripts/package-svcomp.sh. + version="$(sed -n 's/^version=//p' "$SCRIPT_DIR/BUILD_INFO.txt" 2>/dev/null)" + if [ -n "$version" ]; then + echo "$version" + exit 0 + fi + exec java -cp "$SCRIPT_DIR/symbolic-executor/lib/symbolic-executor.jar" de.uzl.its.swat.Version + fi +done + +# get last argument +last_path="${@: -1}" + +# get second last argument +second_last_path="${*: -2:1}" + +# check if there is a Main.java in last_path +if [ -f "$second_last_path/Main.java" ]; then + # switch the last two arguments + set -- "${@:1:$(($#-2))}" "$last_path" "$second_last_path" +fi + +# The violation witness is written by the explorer to witness/ from the given input paths. + +# Python dependencies are shipped in libs/python (built for one CPython +# minor version, recorded in libs/python/PYTHON_VERSION) and used with the +# system python3. Override the interpreter with SWAT_PYTHON if needed. +PYTHON="${SWAT_PYTHON:-python3}" +PYTHON_LIBS="$SCRIPT_DIR/libs/python" +expected_py="$(cat "$PYTHON_LIBS/PYTHON_VERSION" 2>/dev/null)" +actual_py="$("$PYTHON" -c 'import sys; print("%d.%d" % sys.version_info[:2])' 2>/dev/null)" +if [ -z "$expected_py" ]; then + echo "Error: Python dependencies not found at $PYTHON_LIBS" + exit 1 +fi +if [ "$actual_py" != "$expected_py" ]; then + echo "Error: SWAT's Python dependencies were built for Python $expected_py, but '$PYTHON' is '${actual_py:-not runnable}'" + exit 1 +fi +export PYTHONPATH="$PYTHON_LIBS${PYTHONPATH:+:$PYTHONPATH}" +export PYTHONNOUSERSITE=1 + +echo "Running SWAT with arguments: $@" +"$PYTHON" -u "$SCRIPT_DIR/run_swat.py" "$@" > out.log 2>&1 + +# SV-COMP requires tool output below 2 MB. Long explorations can log far more, +# so past the cap print the head, every verdict line, and the tail (which holds +# the final verdict and witness). The full log stays in out.log. +MAX_STDOUT_BYTES=$((1536 * 1024)) +log_size="$(stat -c %s out.log)" +if [ "$log_size" -le "$MAX_STDOUT_BYTES" ]; then + cat out.log +else + head -c $((256 * 1024)) out.log + echo + echo "[run-swat.sh] output truncated: ${log_size} bytes total; verdict lines and tail follow" + grep -F '[VERDICT ' out.log | head -n 200 | cut -c1-500 + echo "[run-swat.sh] ... tail ..." + tail -c $((1024 * 1024)) out.log +fi diff --git a/scripts/svcomp-package/run_swat.py b/scripts/svcomp-package/run_swat.py new file mode 100755 index 00000000..e72130f2 --- /dev/null +++ b/scripts/svcomp-package/run_swat.py @@ -0,0 +1,257 @@ +from contextlib import contextmanager +import logging, os, enum, shutil, subprocess, sys, uuid + +import concurrent.futures, json, datetime +from typing import List, Optional + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) +SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) +ORIGINAL_CWD = os.getcwd() +# The explorer writes the violation witness (witness.yml, witness.graphml) here; BenchExec collects +# it via the `**/witness.*` result-files pattern of the benchmark definition. +WITNESS_DIR = os.path.join(SCRIPT_DIR, 'witness') +# The explorer's logs and testcase statistics (stats.json) +LOG_DIR = os.path.join(SCRIPT_DIR, 'logs') +# The static pre-analysis (cfg-extraction's extractor), which prunes and slices the exploration with a +# control-flow graph of the task. As in SWAT's harness (targets/sv-comp/scripts/lib/command_gen.py) +# it runs under valid-assert only: under no-runtime-exception it is sound but costs more than it saves. +SA_JAR = os.path.join(SCRIPT_DIR, 'cfg-extraction', 'cfg-extractor.jar') +SA_PROPERTIES = ('valid-assert.prp',) + + +class ExecutionStatus(enum.Enum): + SUCCESS = "success" + ERROR = "error" + TIMEOUT = "timeout" + +class Verdict(enum.Enum): + VIOLATION = "== ERROR" + SAFE = "== OK" + UNKNOWN = "== DONT-KNOW" + NO_SYMBOLIC_VARS = "== NON-SYMBOLIC" + +@contextmanager +def pushd(dirname): + """Context manager to temporarily change the working directory.""" + original_dir = os.getcwd() + logger.info(f"Changing directory to: {dirname}") + os.chdir(dirname) + try: + yield + finally: + os.chdir(original_dir) + +def determine_result(output: List[str], property_file: str) -> Verdict: + """Parse the verdict for a specific property from the output.""" + verdict = None + for line in output: + if f"[VERDICT {property_file}]" in line: + if "== OK" in line: + verdict = Verdict.SAFE + break + elif "== ERROR" in line: + verdict = Verdict.VIOLATION + break + elif "== DONT-KNOW" in line: + verdict = Verdict.UNKNOWN + break + return verdict + + + + + + +def run_command_with_timeout(cmd: list[str], timeout: int | None = None) -> tuple[ExecutionStatus, list[str]]: + """Executes the given command and returns output from both STDOUT and STDERR. + + timeout=None (the default) imposes no internal cutoff so SWAT can use the full + SV-COMP time budget; benchexec enforces the wall/CPU limit externally. + """ + + logger.info(f'[TARGET EXECUTION]: Running command: {cmd}') + output = [] + with subprocess.Popen( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + universal_newlines=True, + bufsize=1 + ) as proc: + try: + # Read output and wait for process to finish, with a timeout + stdout, _ = proc.communicate(timeout=timeout) + output = stdout.splitlines() + + return ExecutionStatus.SUCCESS, output + + + except subprocess.TimeoutExpired: + proc.kill() + stdout, _ = proc.communicate() + output = stdout.splitlines() + return ExecutionStatus.TIMEOUT, output + + except Exception as e: + logger.critical(f'[SVCOMP] Exception: {e}') + proc.kill() + output = [str(e)] + return ExecutionStatus.ERROR, output + [str(e)] + + + + + +def generate_command(args, property_file: str, property_path: str, input_paths: List[str]): + """The explorer command; `property_path` and `input_paths` are the verifier's arguments as given + by BenchExec (relative to the original working directory) and end up in the witness.""" + config_file = 'sv-comp.cfg' + + agent_path = os.path.join(SCRIPT_DIR, 'symbolic-executor', 'lib', 'symbolic-executor.jar') + config_path = os.path.join(SCRIPT_DIR, config_file) + library_path = os.path.join(SCRIPT_DIR, 'libs', 'java-library-path') + + # Same interpreter (and PYTHONPATH, via the inherited environment) as run-swat.sh set up. + base_command = [ sys.executable, "-u", os.path.join(SCRIPT_DIR, 'symbolic-explorer', 'SymbolicExplorer.py'), + "-prp", property_file, + "--agent", agent_path, + "--precheck-jar", os.path.join(SCRIPT_DIR, 'cfg-extraction', 'cfg-extractor-precheck.jar'), + "--config", config_path, + "-z3", library_path, + "--logdir", LOG_DIR, + "--mode", "sv-comp", + '--port', "8000", + # The class the executor runs; the static pre-analysis' graph starts at its main. + "--target", "Main", + *(["--sa-jar", SA_JAR] if property_file in SA_PROPERTIES else []), + "--witness-dir", WITNESS_DIR, + "--witness-property", property_path, + "--witness-base-dir", ORIGINAL_CWD, + "--producer-version", swat_version(), + "--witness-input", *input_paths, + "--classpath"] + print("CP args: " + str(args)) + for arg in args[1:]: + print("CP arg: " + arg) + print("CP arg path: " + os.path.join(SCRIPT_DIR, arg)) + base_command.append(os.path.join(SCRIPT_DIR, arg)) + return base_command + + +def copy_target(target_dir): + # generate random target tmp name + local_target_dir = f"target_{uuid.uuid4()}" + + os.system(f'rm -rf {local_target_dir} && cp -r {target_dir} {local_target_dir}') + print(f"Target copied from {target_dir} to: {local_target_dir}") + return local_target_dir + +def compile_target(args, local_target_dir): + cmd = [ + 'bash', + f'compile-target.sh', + ] + args[-1] = local_target_dir # overwrite target dir + cmd = cmd + args + print(cmd) + _, output = run_command_with_timeout(cmd) + log_output(output) + + +def swat_version(): + """The package version from BUILD_INFO.txt, written by scripts/package-svcomp.sh.""" + try: + with open(os.path.join(SCRIPT_DIR, 'BUILD_INFO.txt')) as f: + for line in f: + if line.startswith('version='): + return line[len('version='):].strip() + except OSError: + pass + return 'unknown' + +def print_witness(): + """Print the witness file contents for validation on the benchmark server.""" + witness_file_path = os.path.join(WITNESS_DIR, 'witness.yml') + if os.path.exists(witness_file_path): + print("\n" + "="*80) + print("WITNESS FILE CONTENTS:") + print("="*80) + with open(witness_file_path, 'r') as f: + print(f.read()) + print("="*80) + print("END OF WITNESS FILE") + print("="*80 + "\n") + else: + print(f"WARNING: Witness file not found at {witness_file_path}") + +def log_output(output: List[str]): + for line in output: + logger.info(line.strip()) + +if __name__ == "__main__": + logger.info("Running SWAT Wrapper...") + + # Look for property file in arguments + # SV-COMP framework passes the .prp file directly as first argument + property_file = None + property_path = None + for arg in sys.argv[1:]: + if arg.endswith('.prp'): + property_file = os.path.basename(arg) + property_path = arg + logger.info(f"Found property file in arguments: {property_file}") + break + + if property_file is None: + logger.error(f"ERROR: No .prp file found in arguments.") + logger.error(f"Arguments: {sys.argv}") + exit(1) + + logger.info(f"Using property file: {property_file}") + + # The task's input files/directories, as given (everything but the property file) + input_paths = [arg for arg in sys.argv[1:] if arg != property_path] + + target_dir = sys.argv[-1] + cmd = generate_command(sys.argv[1:], property_file, property_path, input_paths) + # Exactly one witness per run: drop whatever an earlier run left behind + shutil.rmtree(WITNESS_DIR, ignore_errors=True) + if os.path.exists(os.path.join(LOG_DIR, 'stats.json')): + os.remove(os.path.join(LOG_DIR, 'stats.json')) + local_target_dir = copy_target(target_dir) + compile_target(sys.argv[1:], local_target_dir) + + path = local_target_dir + log_dir = f'{path}/logs' + + cmd[-1] = os.path.join(SCRIPT_DIR, local_target_dir, 'build') #f"{local_target_dir}/build" + # The CPU time limit covers the whole run, so the explorer's budget starts with what copying + # and compiling the task took (the memory and CPU limits are the explorer's defaults). + t = os.times() + cmd[cmd.index('--classpath'):cmd.index('--classpath')] = [ + '--cpu-used', f'{t.user + t.system + t.children_user + t.children_system:.1f}'] + #delete the dir if it exists + if os.path.exists(log_dir): + os.system(f'rm -rf {log_dir}') + os.makedirs(log_dir) + with pushd(path): + # The explorer's output goes straight to ours: on long runs it is hundreds of MB, and + # collecting and re-logging it here took tens of seconds of CPU time and as much memory, + # after the explorer had already stopped in time for the limits. + logger.info(f'[TARGET EXECUTION]: Running command: {cmd}') + sys.stdout.flush() + sys.stderr.flush() + returncode = subprocess.run(cmd, stderr=subprocess.STDOUT).returncode + print(f"Execution status: {returncode}") + + # The explorer records its verdict in the testcase statistics + try: + with open(os.path.join(LOG_DIR, 'stats.json')) as f: + verdict = json.load(f)['verdict_raw'] + except (OSError, ValueError, KeyError): + verdict = None + if verdict == Verdict.VIOLATION.value: + # The explorer wrote the witness from the trace of the violating execution + print_witness() + print(f'Verdict:\n {verdict}') \ No newline at end of file diff --git a/scripts/svcomp-package/smoketest.sh b/scripts/svcomp-package/smoketest.sh new file mode 100755 index 00000000..22228fae --- /dev/null +++ b/scripts/svcomp-package/smoketest.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +cd "$(dirname "$0")" +rm -rf witness +output="$("bash" "run-swat.sh" "../../sv-benchmarks/java/properties/valid-assert.prp" "smoketest/common" "smoketest")" +if ! echo "$output" | grep -q "\[VERDICT valid-assert.prp\] == ERROR"; then + echo "Smoketest failed: no violation verdict!" + exit 1 +fi +# The violation must come with a YAML witness (format 2.3) with a target waypoint, written by +# the explorer to witness/witness.yml +if ! grep -q 'entry_type: "violation_sequence"' witness/witness.yml 2>/dev/null \ + || ! grep -q 'type: "target"' witness/witness.yml; then + echo "Smoketest failed: no violation witness with a target waypoint in witness/witness.yml!" + exit 1 +fi +echo "Smoketest passed!" +exit 0 + diff --git a/scripts/svcomp-package/smoketest/Main.java b/scripts/svcomp-package/smoketest/Main.java new file mode 100644 index 00000000..7362ba89 --- /dev/null +++ b/scripts/svcomp-package/smoketest/Main.java @@ -0,0 +1,10 @@ +import org.sosy_lab.sv_benchmarks.Verifier; + +public class Main { + public static void main(String[] args) { + // Fetch inputs using Verifier.nondet* methods + int i = Verifier.nondetInt(); + + assert i != 42; + } +} diff --git a/scripts/svcomp-package/smoketest/common/Verifier.java b/scripts/svcomp-package/smoketest/common/Verifier.java new file mode 100644 index 00000000..7fd23997 --- /dev/null +++ b/scripts/svcomp-package/smoketest/common/Verifier.java @@ -0,0 +1,60 @@ +// This file is part of the SV-Benchmarks collection of verification tasks: +// https://github.com/sosy-lab/sv-benchmarks +// +// SPDX-FileCopyrightText: Contributed by Peter Schrammel +// SPDX-FileCopyrightText: 2011-2020 The SV-Benchmarks Community +// +// SPDX-License-Identifier: Apache-2.0 + +package org.sosy_lab.sv_benchmarks; + +import java.util.Random; + +public final class Verifier { + public static void assume(boolean condition) { + if (!condition) { + Runtime.getRuntime().halt(1); + } + } + + public static boolean nondetBoolean() { + return new Random().nextBoolean(); + } + + public static byte nondetByte() { + return (byte) (new Random().nextInt()); + } + + public static char nondetChar() { + return (char) (new Random().nextInt()); + } + + public static short nondetShort() { + return (short) (new Random().nextInt()); + } + + public static int nondetInt() { + return new Random().nextInt(); + } + + public static long nondetLong() { + return new Random().nextLong(); + } + + public static float nondetFloat() { + return new Random().nextFloat(); + } + + public static double nondetDouble() { + return new Random().nextDouble(); + } + + public static String nondetString() { + Random random = new Random(); + int size = random.nextInt(); + assume(size >= 0); + byte[] bytes = new byte[size]; + random.nextBytes(bytes); + return new String(bytes); + } +} diff --git a/scripts/svcomp-package/third_party_licenses.py b/scripts/svcomp-package/third_party_licenses.py new file mode 100755 index 00000000..872e90f7 --- /dev/null +++ b/scripts/svcomp-package/third_party_licenses.py @@ -0,0 +1,239 @@ +#!/usr/bin/env python3 +"""Writes THIRD-PARTY-LICENSES.md into a staged SV-COMP package. + +Usage: third_party_licenses.py + +Inputs, all read from the package itself: +- META-INF/swat-third-party.json inside symbolic-executor/lib/symbolic-executor.jar + (written by the thirdPartyList Gradle task: the Java components shaded into the jar), and the + same file inside cfg-extraction/cfg-extractor-precheck.jar (cfg-extraction's precheckJar task) + and cfg-extraction/cfg-extractor.jar (its fatJar task: the static pre-analysis); +- libs/java-library-path/z3-LICENSE.txt (LICENSE.txt of the bundled Z3 release); +- every libs/python/*.dist-info (bundled Python packages and the license files they ship). + +Java license texts come from scripts/svcomp-package/licenses/.txt. +Exits non-zero, naming the component, if a bundled component has no determinable +license or no license text. +""" + +import json +import re +import sys +import zipfile +from email.parser import Parser +from pathlib import Path + +LICENSE_DIR = Path(__file__).resolve().parent / "licenses" +JAR = "symbolic-executor/lib/symbolic-executor.jar" +PRECHECK_JAR = "cfg-extraction/cfg-extractor-precheck.jar" +EXTRACTOR_JAR = "cfg-extraction/cfg-extractor.jar" +JAR_LIST = "META-INF/swat-third-party.json" +Z3_LICENSE = "libs/java-library-path/z3-LICENSE.txt" +PYTHON_DIR = "libs/python" + +# POM license names -> SPDX ids. A name missing here fails the build (add it after checking). +JAVA_LICENSES = { + "apache 2.0": "Apache-2.0", + "apache license, version 2.0": "Apache-2.0", + "the apache license, version 2.0": "Apache-2.0", + "the apache software license, version 2.0": "Apache-2.0", + "gnu lesser general public license, version 2.1": "LGPL-2.1-only", + "gnu lesser general public license 2.1": "LGPL-2.1-only", # SootUp + "gnu lesser general public license version 2.1, february 1999": "LGPL-2.1-only", # JGraphT + "eclipse public license (epl) 2.0": "EPL-2.0", # JGraphT + "the mit license": "MIT", + "mit license": "MIT", +} +# Python trove classifiers (after "License :: OSI Approved :: ") -> SPDX ids. +PY_CLASSIFIERS = { + "MIT License": "MIT", + "Apache Software License": "Apache-2.0", + "Mozilla Public License 2.0 (MPL 2.0)": "MPL-2.0", +} +# Packages whose metadata does not name an SPDX license (checked by hand). +PY_LICENSE_OVERRIDES = { + "witnesscreator": "BSD-2-Clause", # License field holds the copyright line; text is BSD-2-Clause +} +# Packages that ship no license file in their dist-info: package-relative text to use instead. +PY_TEXT_FALLBACKS = { + "z3-solver": Z3_LICENSE, # same project and license as the bundled Z3 release +} +SPDX_ID = re.compile(r"^[A-Za-z0-9.+-]+$") + + +def fail(msg): + sys.exit(f"third_party_licenses.py: error: {msg}") + + +def spdx_of(name): + key = name.strip() + if SPDX_ID.match(key) and (LICENSE_DIR / f"{key}.txt").is_file(): + return key + return JAVA_LICENSES.get(key.lower()) + + +def java_components(pkg, jar_path=JAR): + jar = pkg / jar_path + if not jar.is_file(): + fail(f"missing {jar_path}") + with zipfile.ZipFile(jar) as zf: + try: + entries = json.loads(zf.read(JAR_LIST)) + except KeyError: + fail(f"{jar_path} has no {JAR_LIST}; build it with the thirdPartyList (or precheckJar, fatJar) Gradle task") + comps = [] + for e in entries: + coord = f"{e['group']}:{e['name']}" + ids = [] + for lic in e["licenses"]: + spdx = spdx_of(lic["name"]) + if spdx is None: + fail(f"Java component {coord}: unmapped license {lic['name']!r} (add it to JAVA_LICENSES)") + ids.append(spdx) + if not ids: + fail(f"Java component {coord}: no license in its POM") + for spdx in ids: + if not (LICENSE_DIR / f"{spdx}.txt").is_file(): + fail(f"Java component {coord}: no license text {LICENSE_DIR / (spdx + '.txt')}") + comps.append({"name": coord, "version": e["version"], "license": " OR ".join(ids), + "ids": ids, "url": e["url"]}) + return comps + + +def python_license(name, meta): + if name in PY_LICENSE_OVERRIDES: + return PY_LICENSE_OVERRIDES[name] + if meta.get("License-Expression"): + return meta["License-Expression"].strip() + lic = (meta.get("License") or "").strip() + if lic and SPDX_ID.match(lic): + return lic + for c in meta.get_all("Classifier") or []: + if c.startswith("License :: OSI Approved :: ") and c[27:] in PY_CLASSIFIERS: + return PY_CLASSIFIERS[c[27:]] + return PY_CLASSIFIERS.get(lic) # e.g. z3-solver: "License: MIT License" + + +def python_url(meta): + if meta.get("Home-page"): + return meta["Home-page"].strip() + urls = {} + for entry in meta.get_all("Project-URL") or []: + label, _, url = entry.partition(",") + urls[label.strip().lower()] = url.strip() + for label in ("source", "source code", "code", "repository", "homepage", "home"): + if label in urls: + return urls[label] + return next(iter(urls.values()), "") + + +def python_components(pkg): + dists = sorted((pkg / PYTHON_DIR).glob("*.dist-info")) + if not dists: + fail(f"no *.dist-info in {PYTHON_DIR}") + comps = [] + for d in dists: + meta = Parser().parsestr((d / "METADATA").read_text(encoding="utf-8"), headersonly=True) + name, version = meta["Name"], meta["Version"] + license_id = python_license(name, meta) + if not license_id: + fail(f"Python package {name} {version}: no determinable license") + files = sorted(p for p in (d / "licenses").rglob("*") if p.is_file()) if (d / "licenses").is_dir() else [] + if not files: + files = [d / f for f in meta.get_all("License-File") or [] if (d / f).is_file()] + if not files and name in PY_TEXT_FALLBACKS: + files = [pkg / PY_TEXT_FALLBACKS[name]] + if not files or not all(f.is_file() for f in files): + fail(f"Python package {name} {version}: no license text") + texts = [(f.name, f.read_text(encoding="utf-8", errors="replace")) for f in files] + comps.append({"name": name, "version": version, "license": license_id, + "url": python_url(meta), "texts": texts}) + return comps + + +def cell(s): + return (s or "").replace("|", "\\|") + + +def table(comps): + rows = ["| Component | Version | License | Source |", "|---|---|---|---|"] + rows += [f"| {cell(c['name'])} | {cell(c['version'])} | {cell(c['license'])} | {cell(c['url'])} |" + for c in comps] + return "\n".join(rows) + + +def block(text): + fence = "```" + while fence in text: + fence += "`" + return f"{fence}text\n{text.rstrip()}\n{fence}" + + +def main(): + if len(sys.argv) != 2: + sys.exit(__doc__) + pkg = Path(sys.argv[1]) + java = java_components(pkg) + precheck = java_components(pkg, PRECHECK_JAR) + extractor = java_components(pkg, EXTRACTOR_JAR) + z3_license = pkg / Z3_LICENSE + if not z3_license.is_file(): + fail(f"native Z3: missing license text {Z3_LICENSE}") + z3_jar = next((c for c in java if c["name"] == "com.microsoft:z3"), None) + if z3_jar is None: + fail("native Z3: no com.microsoft:z3 entry in the jar's component list") + native = [{"name": "Z3 (z3, libz3.so, libz3java.so)", "version": z3_jar["version"], + "license": "MIT", "url": "https://github.com/Z3Prover/z3"}] + python = python_components(pkg) + + out = [ + "# Third-Party Licenses", + "", + "This file lists the third-party components bundled in this SWAT package and their licenses.", + "It is generated by `scripts/svcomp-package/third_party_licenses.py` while packaging; do not edit it.", + "SWAT itself is licensed under the terms in `LICENSE`.", + "", + "## Java (shaded into `symbolic-executor/lib/symbolic-executor.jar`)", + "", + "JavaSMT is also shipped unshaded as `libs/java-library-path/java-smt-latest.jar`, and the Z3 Java", + "bindings as `libs/java-library-path/com.microsoft.z3.jar`.", + "", + table(java), + "", + f"## Java (bundled in `{PRECHECK_JAR}`, the property pre-check from cfg-extraction)", + "", + table(precheck), + "", + f"## Java (bundled in `{EXTRACTOR_JAR}`, the static pre-analysis from cfg-extraction)", + "", + table(extractor), + "", + "## Native (`libs/java-library-path/`)", + "", + table(native), + "", + "## Python (`libs/python/`)", + "", + table(python), + "", + "## License texts", + "", + "### Java", + "", + ] + for spdx in sorted({i for c in java + precheck + extractor for i in c["ids"]}): + users = ", ".join(dict.fromkeys(c["name"] for c in java + precheck + extractor if spdx in c["ids"])) + out += [f"#### {spdx}", "", f"Used by: {users}", "", + block((LICENSE_DIR / f"{spdx}.txt").read_text(encoding="utf-8")), ""] + out += ["### Native", "", "#### Z3", "", block(z3_license.read_text(encoding="utf-8")), "", + "### Python", ""] + for c in python: + for fname, text in c["texts"]: + out += [f"#### {c['name']} {c['version']}: {fname}", "", block(text), ""] + target = pkg / "THIRD-PARTY-LICENSES.md" + target.write_text("\n".join(out), encoding="utf-8") + print(f"Wrote {target}: {len(java) + len(precheck) + len(extractor)} Java, {len(native)} native, {len(python)} Python components") + + +if __name__ == "__main__": + main() diff --git a/settings.gradle b/settings.gradle index defcd23f..8db92855 100644 --- a/settings.gradle +++ b/settings.gradle @@ -6,7 +6,6 @@ rootProject.name = "SWAT" include 'symbolic-executor' include 'annotations' -include 'targets:sv-comp:WitnessCreator' include 'targets:instruction-tests:IFXX' include 'targets:instruction-tests:I2X' @@ -61,18 +60,20 @@ include 'targets:applications:oss-fuzz:spatial4j' dependencyResolutionManagement { versionCatalogs { libs { - version('asm','9.6') + version('asm','9.11.1') library('asm-core', 'org.ow2.asm','asm').versionRef('asm') library('asm-commons', 'org.ow2.asm','asm-commons').versionRef('asm') library('asm-util', 'org.ow2.asm','asm-util').versionRef('asm') library('asm-tree', 'org.ow2.asm','asm-tree').versionRef('asm') - library('jackson-databind', 'com.fasterxml.jackson.core:jackson-databind:2.14.1') - library('java-smt', 'org.sosy-lab:java-smt:6.0.0') - library('spock-core', 'org.spockframework:spock-core:2.2-M1-groovy-4.0') - library('mockito-core', 'org.mockito:mockito-core:3.12.4') - library('logback-classic', 'ch.qos.logback:logback-classic:1.5.3') - library('logback-core', 'ch.qos.logback:logback-core:1.5.3') - library('slf4j-api', 'org.slf4j:slf4j-api:2.0.13') + library('jackson-databind', 'com.fasterxml.jackson.core:jackson-databind:2.22.4') + // JavaSMT 6.0.0 built against the Z3 5.3.1 Java bindings (scripts/build-javasmt.sh), + // served from libs/maven + library('java-smt', 'org.sosy-lab:java-smt:6.0.0-1-g1493c4018') + library('spock-core', 'org.spockframework:spock-core:2.5-groovy-4.0') + library('mockito-core', 'org.mockito:mockito-core:5.24.0') + library('logback-classic', 'ch.qos.logback:logback-classic:1.6.5') + library('logback-core', 'ch.qos.logback:logback-core:1.6.5') + library('slf4j-api', 'org.slf4j:slf4j-api:2.0.20') library('cfg-extractor', 'de.uzl.its:cfg-extractor:1.0-SNAPSHOT') bundle('asm', ['asm-core', 'asm-commons', 'asm-util', 'asm-tree']) @@ -83,4 +84,4 @@ dependencyResolutionManagement { include 'targets:applications:modelmapper-test-app' findProject(':targets:applications:modelmapper-test-app')?.name = 'modelmapper-test-app' include 'targets:applications:reflection-test-app' -findProject(':targets:applications:reflection-test-app')?.name = 'reflection-test-app' \ No newline at end of file +findProject(':targets:applications:reflection-test-app')?.name = 'reflection-test-app' diff --git a/symbolic-executor/build.gradle b/symbolic-executor/build.gradle index 0b7932b3..7efbd330 100644 --- a/symbolic-executor/build.gradle +++ b/symbolic-executor/build.gradle @@ -1,6 +1,6 @@ buildscript { ext { - asmAsmVersion = "9.6" // https://gitlab.ow2.org/asm/asm/-/tags + asmAsmVersion = "9.11.1" // https://gitlab.ow2.org/asm/asm/-/tags } configurations { classpath { @@ -18,8 +18,8 @@ plugins { id 'groovy' id 'eclipse' id 'application' - id 'io.freefair.lombok' version '8.4' - id 'com.github.johnrengelman.shadow' version '8.1.1' + id 'io.freefair.lombok' version '8.14.4' + id 'com.gradleup.shadow' version '8.3.11' } tasks.withType(JavaCompile).configureEach { @@ -28,6 +28,14 @@ tasks.withType(JavaCompile).configureEach { repositories { + // JavaSMT built by scripts/build-javasmt.sh, only from there; its dependencies come from + // Maven Central + exclusiveContent { + forRepository { + maven { url = rootProject.file('libs/maven') } + } + filter { includeModule('org.sosy-lab', 'java-smt') } + } mavenCentral() } @@ -51,11 +59,48 @@ test { // Surface SWAT errors as exceptions instead of halting the test JVM systemProperty "exitOnError", "false" + useJUnitPlatform { + excludeTags "slow" // skip @Tag("slow") tests + } + // Forked-agent specs need the built agent jar; they run via agentTest + exclude '**/*AgentSpec*' + testLogging { // Show events for passed, skipped, and failed tests events "passed", "failed", "skipped" // Enable standard output/error to be shown - showStandardStreams = true + showStandardStreams = false + } +} + +// Forked-agent tests: run a small target with the real agent (solver.mode=PRINT) and check the +// TraceDTO printed to stdout. Needs the agent jar in lib/, hence the copyJar dependency. +tasks.register('agentTest', Test) { + dependsOn copyJar + description = 'Runs the *AgentSpec tests against the built agent jar' + group = 'verification' + useJUnitPlatform() + filter { + includeTestsMatching '*AgentSpec' + } + testLogging { + events "passed", "failed", "skipped" + showStandardStreams = false + } +} + +tasks.register('testFull', Test) { + systemProperty "java.library.path", "../libs/java-library-path" + // Surface SWAT errors as exceptions instead of halting the test JVM + systemProperty "exitOnError", "false" + + useJUnitPlatform() + + testLogging { + // Show events for passed, skipped, and failed tests + events "passed", "failed", "skipped" + // Enable standard output/error to be shown + showStandardStreams = false } } @@ -75,7 +120,69 @@ jar { } +// Lists the third-party components bundled into the shadow JAR as META-INF/swat-third-party.json, +// with license and project URL from each module's POM (or the nearest parent POM declaring them). +// scripts/svcomp-package/third_party_licenses.py turns it into THIRD-PARTY-LICENSES.md. +def thirdPartyDir = layout.buildDirectory.dir('generated/third-party') +tasks.register('thirdPartyList') { + description = 'Writes META-INF/swat-third-party.json for the shadow JAR' + def classpath = configurations.runtimeClasspath + inputs.files(classpath) + outputs.dir(thirdPartyDir) + notCompatibleWithConfigurationCache('resolves POMs at execution time') + doLast { + def pomOf = { String g, String a, String v -> + def result = project.dependencies.createArtifactResolutionQuery() + .forModule(g, a, v) + .withArtifacts(MavenModule, MavenPomArtifact) + .execute() + def artifacts = result.resolvedComponents*.getArtifacts(MavenPomArtifact).flatten() + .findAll { it instanceof ResolvedArtifactResult } + if (artifacts.isEmpty()) throw new GradleException("No POM found for ${g}:${a}:${v}") + new groovy.xml.XmlSlurper().parse(artifacts[0].file) + } + def entries = [] + classpath.incoming.artifacts.each { ResolvedArtifactResult art -> + def id = art.id.componentIdentifier + if (id instanceof ProjectComponentIdentifier) return // first-party code + def name = art.file.name + boolean hasClasses = name.endsWith('.jar') && + project.zipTree(art.file).matching { include '**/*.class' }.files.size() > 0 + if (!hasClasses) return // e.g. guava's empty listenablefuture placeholder + if (id instanceof ModuleComponentIdentifier) { + def license = null, url = null + def pom = pomOf(id.group, id.module, id.version) + while (pom != null && (license == null || url == null)) { + if (license == null && pom.licenses.license.size() > 0) { + license = pom.licenses.license.collect { + [name: it.name.text().trim(), url: it.url.text().trim()] + } + } + if (url == null && pom.url.text().trim()) url = pom.url.text().trim() + def parent = pom.parent + pom = parent.size() > 0 ? + pomOf(parent.groupId.text(), parent.artifactId.text(), parent.version.text()) : null + } + entries << [group: id.group, name: id.module, version: id.version, + licenses: license ?: [], url: url ?: ''] + } else if (name == 'com.microsoft.z3.jar') { + // File dependency from libs/java-library-path (copyNativeLibs in the root build) + entries << [group: 'com.microsoft', name: 'z3', version: '5.3.1', + licenses: [[name: 'MIT', url: 'https://github.com/Z3Prover/z3/blob/master/LICENSE.txt']], + url: 'https://github.com/Z3Prover/z3'] + } else { + throw new GradleException("Unknown bundled file dependency ${name}: add it to thirdPartyList") + } + } + def out = thirdPartyDir.get().file('META-INF/swat-third-party.json').asFile + out.parentFile.mkdirs() + out.text = groovy.json.JsonOutput.prettyPrint(groovy.json.JsonOutput.toJson( + entries.sort { "${it.group}:${it.name}" })) + } +} + shadowJar { + from(tasks.named('thirdPartyList')) mainClassName = "" relocate 'ch.qos.logback', 'shadow.swat.ch.qos.logback' relocate 'org.slf4j', 'shadow.swat.org.slf4j' @@ -105,10 +212,6 @@ tasks.register('copyJar', Copy) { } -test { - useJUnitPlatform() -} - // Set duplicate strategy to remove a warning // distTar.setDuplicatesStrategy(DuplicatesStrategy.INCLUDE) // distZip.setDuplicatesStrategy(DuplicatesStrategy.INCLUDE) diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/common/ErrorHandler.java b/symbolic-executor/src/main/java/de/uzl/its/swat/common/ErrorHandler.java index 7f894936..95bb5338 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/common/ErrorHandler.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/common/ErrorHandler.java @@ -5,12 +5,9 @@ import ch.qos.logback.core.FileAppender; import de.uzl.its.swat.common.exceptions.SWATException; import de.uzl.its.swat.common.logging.GlobalLogger; -import de.uzl.its.swat.common.logging.records.ErrorRecord; import de.uzl.its.swat.config.Config; import de.uzl.its.swat.thread.ThreadHandler; -import java.util.Arrays; - import static java.lang.Thread.currentThread; /** * ErrorHandler is responsible for handling exceptions throughout the application. It decides @@ -53,6 +50,14 @@ public void raiseException(String msg) { * @param t the throwable to handle */ public void handleException(String msg, Throwable t) { + // A stack overflow is not a SWAT failure: stop the symbolic tracking (the trace becomes + // context loss) and let the error take its course in the target, as without SWAT. Nothing + // is logged, the stack is exhausted. + Error stackOverflow = StackOverflowGuard.findStackOverflow(t); + if (stackOverflow != null) { + StackOverflowGuard.trackingLost = true; + throw stackOverflow; + } long instCnt = -1; try { instCnt = ThreadHandler.getInstructionCount(currentThread().getId()); @@ -73,14 +78,6 @@ public void handleException(String msg, Throwable t) { throw new RuntimeException(msg, t); } logger.error("Preparing to halt execution due to error..."); - try { - if (ThreadHandler.hasThreadContext(currentThread().getId())) { - ThreadHandler.logStats(currentThread().getId()); - } - ThreadHandler.logStats(-1); - } catch (Exception e) { - logger.error("Error while logging stats"); - } logger.error("Halting..."); if (!config.isLogShadowStateToConsole()) { logger.info("Flushing loggers..."); @@ -100,28 +97,6 @@ public void handleException(String msg, Throwable t) { public void logException(String msg, Throwable t){ logger.error(msg, t); - long threadId = currentThread().getId(); - String executionStage; - if(ThreadHandler.hasThreadContext(threadId)){ - try { - executionStage = ThreadHandler.getCurrentInstruction(threadId).toString(); - } catch (Exception e) { - logger.error("Error while getting current instruction", e); - executionStage = "Unknown"; - } - } else { - executionStage = "Main Thread"; - threadId = -1; - } - - // Record the error - ErrorRecord errorRecord = new ErrorRecord(msg, t.getClass().getSimpleName(), - Arrays.toString(t.getStackTrace()), t.getMessage(), executionStage); - try{ - ThreadHandler.recordException(threadId, errorRecord); - } catch (Exception e) { - logger.error("Error while recording exception", e); - } } } diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/common/FreshCopy.java b/symbolic-executor/src/main/java/de/uzl/its/swat/common/FreshCopy.java new file mode 100644 index 00000000..a375f628 --- /dev/null +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/common/FreshCopy.java @@ -0,0 +1,48 @@ +package de.uzl.its.swat.common; + +/** + * Fresh copies of the String and boxed parameters of an entry method, so that symbolic execution + * never works on interned or cached instances (see AbstractMethodAdapter.instrumentEntryPointParameters). + * + *

A parameter may be null (an optional request parameter or header). The null check is here + * rather than inlined into the entry method: SWAT does not instrument this class, so it adds no + * branch to the trace, which the static pre-analysis graph (built from the original bytecode) + * would not have. + */ +public final class FreshCopy { + private FreshCopy() {} + + public static String ofString(String s) { + return s == null ? null : new String(s); + } + + @SuppressWarnings("removal") + public static Integer ofInteger(Integer v) { + return v == null ? null : new Integer(v.intValue()); + } + + @SuppressWarnings("removal") + public static Long ofLong(Long v) { + return v == null ? null : new Long(v.longValue()); + } + + @SuppressWarnings("removal") + public static Short ofShort(Short v) { + return v == null ? null : new Short(v.shortValue()); + } + + @SuppressWarnings("removal") + public static Byte ofByte(Byte v) { + return v == null ? null : new Byte(v.byteValue()); + } + + @SuppressWarnings("removal") + public static Character ofCharacter(Character v) { + return v == null ? null : new Character(v.charValue()); + } + + @SuppressWarnings("removal") + public static Boolean ofBoolean(Boolean v) { + return v == null ? null : new Boolean(v.booleanValue()); + } +} diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/common/Profile.java b/symbolic-executor/src/main/java/de/uzl/its/swat/common/Profile.java new file mode 100644 index 00000000..d0803cd2 --- /dev/null +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/common/Profile.java @@ -0,0 +1,145 @@ +package de.uzl.its.swat.common; + +import java.io.FileWriter; +import java.io.IOException; +import java.lang.instrument.ClassFileTransformer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.ProtectionDomain; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +/** + * Optional phase profile of one executor run, for measuring where the CPU time of a round goes. + * Off unless {@code -Dswat.profile=} is set; then each {@link #mark} records the wall clock, + * the CPU time of the calling thread and of the whole process (Linux /proc, ns), and {@link #dump} + * appends the marks, the class-load/instrumentation counters and the CPU time per JVM thread to + * the file. Disabled, every call is one static boolean check. + */ +public final class Profile { + public static final String FILE = System.getProperty("swat.profile"); + public static final boolean ON = FILE != null; + + private static final List marks = new ArrayList<>(); + private static final long T0 = System.nanoTime(); + + // Class loads seen by the transformer chain (outermost transform calls only) + private static int classLoads, transformedClasses; + private static long transformNs, bytesIn, bytesOut; + private static final ThreadLocal current = new ThreadLocal<>(); + private static boolean dumped; + + private Profile() {} + + public static synchronized void mark(String label) { + if (!ON) return; + marks.add(String.format("%-22s wall_ms %9.3f thread_cpu_ms %9.3f proc_cpu_ms %9.3f proc_ticks_ms %7d", + label, (System.nanoTime() - T0) / 1e6, threadCpuNs() / 1e6, processCpuNs() / 1e6, processTicksMs())); + } + + /** First transformer of the chain: starts timing an outermost class load. */ + public static ClassFileTransformer begin() { + return new ClassFileTransformer() { + @Override + public byte[] transform(ClassLoader l, String n, Class r, ProtectionDomain d, byte[] b) { + if (r == null && current.get() == null) { + current.set(new long[] {System.nanoTime(), 0, b.length, System.identityHashCode(b)}); + } + return null; + } + }; + } + + /** Last transformer of the chain: ends timing of the class load begin() started. */ + public static ClassFileTransformer end() { + return new ClassFileTransformer() { + @Override + public byte[] transform(ClassLoader l, String n, Class r, ProtectionDomain d, byte[] b) { + long[] s = current.get(); + if (r == null && s != null) { + current.remove(); + synchronized (Profile.class) { + classLoads++; + transformNs += System.nanoTime() - s[0]; + + if (System.identityHashCode(b) != (int) s[3] || b.length != s[2]) { + transformedClasses++; + bytesIn += s[2]; + bytesOut += b.length; + } + } + } + return null; + } + }; + } + + public static synchronized void dump(String label) { + if (!ON || dumped) return; + mark(label); + StringBuilder sb = new StringBuilder(); + sb.append("=== swat.profile pid ").append(ProcessHandle.current().pid()).append('\n'); + for (String m : marks) sb.append(m).append('\n'); + sb.append(String.format("transform: loads %d transformed %d bytes_in %d bytes_out %d wall_ms %.3f%n", + classLoads, transformedClasses, bytesIn, bytesOut, transformNs / 1e6)); + Map byName = new TreeMap<>(); + try (var tasks = Files.list(Path.of("/proc/self/task"))) { + for (Path t : (Iterable) tasks::iterator) { + try { + String comm = Files.readString(t.resolve("comm")).trim().replaceAll("[0-9]+$", "#"); + long ns = Long.parseLong(Files.readString(t.resolve("schedstat")).split(" ")[0]); + long[] v = byName.computeIfAbsent(comm, k -> new long[2]); + v[0] += ns; + v[1]++; + } catch (IOException | RuntimeException ignored) { + } + } + } catch (IOException ignored) { + } + for (Map.Entry e : byName.entrySet()) { + sb.append(String.format("thread %-20s n %3d cpu_ms %9.3f%n", e.getKey(), e.getValue()[1], e.getValue()[0] / 1e6)); + } + try (FileWriter w = new FileWriter(FILE, true)) { + w.write(sb.toString()); + } catch (IOException ignored) { + } + dumped = true; + } + + private static long threadCpuNs() { + try { + return Long.parseLong(Files.readString(Path.of("/proc/thread-self/schedstat")).split(" ")[0]); + } catch (IOException | RuntimeException e) { + return -1; + } + } + + /** CPU time of the live threads of the process (threads that ended are not included). */ + private static long processCpuNs() { + long sum = 0; + try (var tasks = Files.list(Path.of("/proc/self/task"))) { + for (Path t : (Iterable) tasks::iterator) { + try { + sum += Long.parseLong(Files.readString(t.resolve("schedstat")).split(" ")[0]); + } catch (IOException | RuntimeException ignored) { + } + } + } catch (IOException e) { + return -1; + } + return sum; + } + + /** user+sys CPU of the whole process including ended threads, in clock ticks (10 ms). */ + private static long processTicksMs() { + try { + String s = Files.readString(Path.of("/proc/self/stat")); + String[] f = s.substring(s.lastIndexOf(')') + 2).split(" "); + return (Long.parseLong(f[11]) + Long.parseLong(f[12])) * 10; + } catch (IOException | RuntimeException e) { + return -1; + } + } +} diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/common/RefIdentity.java b/symbolic-executor/src/main/java/de/uzl/its/swat/common/RefIdentity.java new file mode 100644 index 00000000..d51af9a0 --- /dev/null +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/common/RefIdentity.java @@ -0,0 +1,123 @@ +package de.uzl.its.swat.common; + +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.util.HashSet; + +/** + * Bookkeeping that lets {@link UtilInstrumented#refEquals} reproduce Java's reference identity + * although the NoCache transformer de-interns string literals and the boxing caches. + * + *

Every object that SWAT creates in place of a shared instance (a string literal, a {@code + * valueOf} result, an {@code intern()} result) is registered here. Two such objects are one + * instance in the original program exactly when their values are equal (and, for boxes, the value + * is in the cache range). Objects that are not registered keep their own identity. + * + *

This class is not instrumented and its methods are ignored invocations: their results only + * depend on where an object was created, which the control flow already determines, so the + * symbolic state receives them as concrete values. + */ +@SuppressWarnings("unused") +public final class RefIdentity { + + private static final ReferenceQueue queue = new ReferenceQueue<>(); + private static final HashSet deInterned = new HashSet<>(); + + private RefIdentity() {} + + /** Registers {@code copy} as standing for the shared instance of its value. */ + public static void markDeInterned(Object copy) { + if (copy == null) return; + synchronized (deInterned) { + expunge(); + deInterned.add(new Key(copy, queue)); + } + } + + public static boolean isDeInterned(Object o) { + if (o == null) return false; + synchronized (deInterned) { + return deInterned.contains(new Key(o, null)); + } + } + + /** + * Whether {@code s} is, in the original program, the canonical (interned) instance of its value: + * a de-interned copy, or a pooled string that reached the program without passing the NoCache + * transformer (e.g. a literal returned by the JDK). The pool lookup interns a fresh copy rather + * than {@code s}, so it never turns {@code s} itself into the pooled instance. + */ + public static boolean isCanonicalString(String s) { + return isDeInterned(s) || isPooledOnly(s); + } + + /** Whether {@code s} is an unregistered string that is the pooled instance of its value. */ + public static boolean isPooledOnly(String s) { + return !isDeInterned(s) && new String(s).intern() == s; + } + + /** + * Whether {@code o} is, in the original program, a cached box: a de-interned {@code valueOf} + * copy (cached if its value is in the cache range, which the caller checks), or the cached + * instance itself. + */ + public static boolean isCanonicalBox(Object o) { + if (isDeInterned(o)) return true; + if (o instanceof Integer i) return i == Integer.valueOf(i.intValue()); + if (o instanceof Long l) return l == Long.valueOf(l.longValue()); + if (o instanceof Short s) return s == Short.valueOf(s.shortValue()); + if (o instanceof Byte b) return b == Byte.valueOf(b.byteValue()); + if (o instanceof Character c) return c == Character.valueOf(c.charValue()); + if (o instanceof Boolean b) return b == Boolean.valueOf(b.booleanValue()); + return false; + } + + /** + * {@code s.intern() == s} with the JVM's own side effect: an unregistered string whose value is + * not pooled yet becomes the pooled instance. A registered copy never does, its value is pooled. + */ + public static boolean internsToItself(String s) { + return !isDeInterned(s) && s.intern() == s; + } + + /** Plain reference identity, kept out of the symbolic state's view of {@code IF_ACMP}. */ + public static boolean same(Object a, Object b) { + return a == b; + } + + /** + * Marks a reference comparison whose outcome depends on the values of {@code a} and {@code b} + * in a way SWAT does not encode. The symbolic state records a reference semantic change when an + * argument is symbolic. + */ + public static void valueDependentIdentity(Object a, Object b) {} + + private static void expunge() { + Object k; + while ((k = queue.poll()) != null) { + deInterned.remove(k); + } + } + + private static final class Key extends WeakReference { + private final int hash; + + Key(Object referent, ReferenceQueue q) { + super(referent, q); + this.hash = System.identityHashCode(referent); + } + + @Override + public int hashCode() { + return hash; + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + if (!(other instanceof Key k)) return false; + Object referent = get(); + return referent != null && referent == k.get(); + } + } +} diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/common/StackOverflowGuard.java b/symbolic-executor/src/main/java/de/uzl/its/swat/common/StackOverflowGuard.java new file mode 100644 index 00000000..df54fa86 --- /dev/null +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/common/StackOverflowGuard.java @@ -0,0 +1,60 @@ +package de.uzl.its.swat.common; + +/** + * Records that the JVM stack overflowed during the execution. Deep recursion in the target plus the + * frames of the symbolic tracking can exhaust the stack, and a {@link StackOverflowError} can then + * strike anywhere: in the target's own frames or inside SWAT while an instruction is processed. + * + *
    + *
  • {@link #trackingLost}: a StackOverflowError was raised inside SWAT. The shadow state may be + * half updated, so symbolic tracking stops for the rest of the execution; the error is + * swallowed, so the target continues as it would without SWAT. + *
  • {@link #targetOverflowed}: a StackOverflowError reached the target's code. It is set by the + * instrumented exception handler of every call site (a PUTSTATIC, no call) before the + * exception is reported to SWAT. The path then depends on the stack size of the JVM, which + * the task semantics do not fix, so neither a SAFE nor a VIOLATION found on it can be trusted. + *
+ * + * Both end the symbolic tracking and mark the trace as context loss. The fields are written + * without any call, so that they can be set on an exhausted stack; the class is initialised + * eagerly by the agent for the same reason. They are global, like the instruction processor whose + * state they guard. + */ +public final class StackOverflowGuard { + + public static volatile boolean trackingLost = false; + public static volatile boolean targetOverflowed = false; + + private StackOverflowGuard() {} + + /** Forces the initialisation of the class (see the class comment). */ + public static void init() {} + + /** Whether symbolic tracking has stopped because of a stack overflow. */ + public static boolean isOverflowed() { + return trackingLost | targetOverflowed; + } + + /** + * Returns the StackOverflowError behind {@code t}, or the initialiser error of a class whose + * static initialiser it broke, or null if {@code t} is not caused by a stack overflow. + */ + public static Error findStackOverflow(Throwable t) { + for (int depth = 0; t != null && depth < 16; depth++, t = t.getCause()) { + if (t instanceof StackOverflowError e) { + return e; + } + if (t instanceof ExceptionInInitializerError e + && String.valueOf(e.getMessage()).contains("java.lang.StackOverflowError")) { + return e; + } + } + return null; + } + + /** Clears the state; only for tests that run several executions in one JVM. */ + public static void reset() { + trackingLost = false; + targetOverflowed = false; + } +} diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/common/Util.java b/symbolic-executor/src/main/java/de/uzl/its/swat/common/Util.java index 4c2e490a..856fb41d 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/common/Util.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/common/Util.java @@ -19,7 +19,9 @@ import java.util.regex.Pattern; import de.uzl.its.swat.thread.ThreadHandler; +import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.Type; +import org.objectweb.asm.util.CheckClassAdapter; import javax.annotation.Nullable; @@ -42,10 +44,6 @@ public class Util { private static final HashMap shouldInstrumentCache = new HashMap<>(); - // Maintain a set of class names that have been de-interned - private static final Set deInternedClasses = new HashSet<>(Arrays.asList( - String.class.getName(), Boolean.class.getName(), Byte.class.getName(), Short.class.getName(), - Character.class.getName(), Integer.class.getName(), Long.class.getName())); /** * Just a method for validating the engine! @@ -193,6 +191,22 @@ public static boolean isInSymbolicScope(String cname) { return verdict; } + /** + * The visitor a transformer writes the instrumented class through: {@code cv} wrapped in ASM's + * {@link CheckClassAdapter} (which checks every visit and runs a data-flow analysis of each + * method) if {@code instrumentation.checkClassAdapter} is set for the class, {@code cv} itself + * otherwise. The checks only find instrumentation bugs and cost about a tenth of an executor + * run's CPU time, so they are off unless configured. + */ + public static ClassVisitor withCheckClassAdapter(ClassVisitor cv, String cname) { + return withCheckClassAdapter(cv, cname, config.isUseCheckClassAdapter(), config.getCheckClassAdapterClass()); + } + + static ClassVisitor withCheckClassAdapter(ClassVisitor cv, String cname, boolean enabled, String classFilter) { + boolean forClass = "*".equals(classFilter) || formatClassName(cname).equals(classFilter); + return enabled && forClass ? new CheckClassAdapter(cv, true) : cv; + } + public static boolean useCheckClassAdapterForClass(String cname) { if (Config.instance().getCheckClassAdapterClass().equals("*")) { @@ -233,14 +247,18 @@ public static Class[] getInterfaces(Class clazz) return interfaces; } + /** + * Asserts that a class name is not a type descriptor. A reference descriptor has the form + * {@code Lpkg/Name;}, so the trailing ';' identifies it; a leading 'L' alone does not, as + * class names such as {@code LinkedList} or {@code LinkedListEntry} start with it too. + */ private static void checkClassName(String className) { if (className.startsWith("[")) { return; // array class, skip check for now } SWATAssert.check( - !className.contains(";") && !className.contains("(") && !className.contains(")") - && !className.startsWith("L"), - "Class name '{}' should not contain ';' or brackets", className); + !className.contains(";") && !className.contains("(") && !className.contains(")"), + "Class name '{}' should not be a descriptor (contains ';', '(' or ')')", className); } /** @@ -414,19 +432,4 @@ public static boolean isSymbolicMethod(String cname, String name) { return Pattern.matches(symbolicMethodPattern, name); } - static boolean shouldUseValueEquality(Object a, Object b) { - // If either is null, Objects.equals handles it correctly anyway - if (a == null || b == null) { - return true; - } - - // Check if either object's class has been de-interned - return isDeInternedClass(a.getClass()) || isDeInternedClass(b.getClass()); - } - - private static boolean isDeInternedClass(Class clazz) { - // Check the exact class and optionally superclasses if inheritance matters - return deInternedClasses.contains(clazz.getName()); - } - } diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/common/UtilInstrumented.java b/symbolic-executor/src/main/java/de/uzl/its/swat/common/UtilInstrumented.java index e288bec7..8282618f 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/common/UtilInstrumented.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/common/UtilInstrumented.java @@ -5,7 +5,6 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.Objects; @SuppressWarnings("unused") public class UtilInstrumented { @@ -24,14 +23,69 @@ public static void liftClass(Object param, String paramCnameDot, String methodNa } } /** - * Conditionally uses Objects.equals() for de-interned classes, - * otherwise uses reference equality. + * Replaces {@code IF_ACMPEQ}/{@code IF_ACMPNE}: Java reference identity, computed on objects of + * which SWAT de-interned the literals and boxing caches (see {@link RefIdentity}). + * + *

Two canonical instances (literals, interned strings, cached boxes) are identical exactly + * when their values are equal, and that comparison goes through the modelled {@code equals} + * and unboxing calls so the symbolic state sees its dependence on the values. Every other + * comparison is plain identity and concrete. The comparisons themselves must not be + * {@code IF_ACMP}s here: the symbolic state encodes those on strings as value equality. */ @SuppressWarnings("unused") public static boolean refEquals(Object a, Object b) { - if (Util.shouldUseValueEquality(a, b)) { - return Objects.equals(a, b); + if (a instanceof String sa && b instanceof String sb) { + if (RefIdentity.isCanonicalString(sa) && RefIdentity.isCanonicalString(sb)) { + if (RefIdentity.isPooledOnly(sa) || RefIdentity.isPooledOnly(sb)) { + // Whether a string from uninstrumented code is pooled may depend on its value. + RefIdentity.valueDependentIdentity(sa, sb); + } + return sa.equals(sb); + } + return RefIdentity.same(a, b); } - return a == b; + if (RefIdentity.same(a, b)) { + return true; + } + if (RefIdentity.isCanonicalBox(a) && RefIdentity.isCanonicalBox(b)) { + // Distinct cached boxes: the same instance iff the values are equal and in the cache range. + if (a instanceof Integer x && b instanceof Integer y) { + int v = x.intValue(); + return v == y.intValue() && v >= -128 && v <= 127; + } + if (a instanceof Long x && b instanceof Long y) { + long v = x.longValue(); + return v == y.longValue() && v >= -128 && v <= 127; + } + if (a instanceof Short x && b instanceof Short y) { + short v = x.shortValue(); + return v == y.shortValue() && v >= -128 && v <= 127; + } + if (a instanceof Character x && b instanceof Character y) { + char v = x.charValue(); + return v == y.charValue() && v <= 127; + } + if (a instanceof Byte x && b instanceof Byte y) { + return x.byteValue() == y.byteValue(); + } + if (a instanceof Boolean x && b instanceof Boolean y) { + return x.booleanValue() == y.booleanValue(); + } + } + return RefIdentity.same(a, b); + } + + /** + * Replaces {@code String.intern()}: a de-interned copy standing for the pooled instance, or + * {@code s} itself when {@code s} is (or, by this call, becomes) the pooled instance. + */ + @SuppressWarnings("unused") + public static String intern(String s) { + if (RefIdentity.internsToItself(s)) { + return s; + } + String copy = new String(s); + RefIdentity.markDeInterned(copy); + return copy; } } diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/common/exceptions/SWATAssert.java b/symbolic-executor/src/main/java/de/uzl/its/swat/common/exceptions/SWATAssert.java index ee4c3418..e8859b9f 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/common/exceptions/SWATAssert.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/common/exceptions/SWATAssert.java @@ -3,7 +3,6 @@ import ch.qos.logback.classic.Logger; import de.uzl.its.swat.common.ErrorHandler; import de.uzl.its.swat.common.logging.GlobalLogger; -import de.uzl.its.swat.common.logging.records.ErrorRecord; import de.uzl.its.swat.config.Config; import de.uzl.its.swat.symbolic.instruction.Instruction; import de.uzl.its.swat.thread.ThreadContext; diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/common/logging/StatsStorage.java b/symbolic-executor/src/main/java/de/uzl/its/swat/common/logging/StatsStorage.java index 6c6cfd3e..cbf961c9 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/common/logging/StatsStorage.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/common/logging/StatsStorage.java @@ -1,55 +1,32 @@ package de.uzl.its.swat.common.logging; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import de.uzl.its.swat.common.logging.records.ErrorRecord; import de.uzl.its.swat.common.logging.records.InvocationEntry; import lombok.Getter; -import java.util.ArrayList; import java.util.HashMap; -import java.util.List; -import java.util.Map; - +import java.util.HashSet; +import java.util.Set; + +/** + * In-memory accumulator for per-execution statistics: the methods that could not be modelled + * symbolically ({@link #invocations}) and the subset of those that caused symbolic context loss + * ({@link #contextLossInvocations}). This data is attached to the trace and sent to the Symbolic + * Explorer (see {@code DTOBuilder}); the explorer owns the consolidated per-testcase output. The + * executor itself no longer writes any stats file. + */ @Getter public class StatsStorage { - private String entrypoint; private final HashMap invocations; - private final ArrayList errors; + // Subset of invocations that received symbolic arguments and therefore caused context loss. + private final Set contextLossInvocations; public StatsStorage() { this.invocations = new HashMap<>(); - this.errors = new ArrayList<>(); + this.contextLossInvocations = new HashSet<>(); } - public String convertToJson() throws JsonProcessingException { - ObjectMapper mapper = new ObjectMapper(); - List> serializedList = new ArrayList<>(); - - invocations.forEach((entry, count) -> { - Map entryMap = new HashMap<>(); - entryMap.put("owner", entry.owner()); - entryMap.put("name", entry.name()); - entryMap.put("desc", entry.desc()); - entryMap.put("isInstance", entry.isInstance()); - entryMap.put("invokeId", entry.invokeId()); - entryMap.put("isSymbolic", entry.isSymbolic()); - entryMap.put("count", count); // Store the count as well - - serializedList.add(entryMap); - }); - errors.forEach(error -> { - Map errorMap = new HashMap<>(); - errorMap.put("message", error.message()); - errorMap.put("type", error.type()); - errorMap.put("stackTrace", error.stackTrace()); - errorMap.put("exceptionMessage", error.exceptionMessage()); - errorMap.put("executionStage", error.executionStage()); - - serializedList.add(errorMap); - }); - - return mapper.writeValueAsString(serializedList); // Convert the list of maps to JSON + /** Marks an already-recorded missing invocation as a context-loss culprit. */ + public void recordContextLossInvocation(InvocationEntry entry) { + contextLossInvocations.add(entry); } } - diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/common/logging/records/ErrorRecord.java b/symbolic-executor/src/main/java/de/uzl/its/swat/common/logging/records/ErrorRecord.java deleted file mode 100644 index 0322708d..00000000 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/common/logging/records/ErrorRecord.java +++ /dev/null @@ -1,4 +0,0 @@ -package de.uzl.its.swat.common.logging.records; - -public record ErrorRecord(String message, String type, String stackTrace, String exceptionMessage, String executionStage) { -} diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/config/Config.java b/symbolic-executor/src/main/java/de/uzl/its/swat/config/Config.java index 2f9a014e..a05871db 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/config/Config.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/config/Config.java @@ -229,6 +229,15 @@ public class Config { // Fontus options // ------------------------------------ @Getter private boolean useStringInterning; + /** Probe loop heads and report their concrete states with the trace (see symbolic/loopstate). */ + @Getter private boolean loopStateMatching; + private static final boolean DEFAULT_LOOP_STATE_MATCHING = false; + /** End a run whose application threads are deadlocked (see thread/DeadlockWatchdog). */ + @Getter private boolean deadlockWatchdog; + private static final boolean DEFAULT_DEADLOCK_WATCHDOG = false; + /** Merge diamonds and counting loops into formulas (see symbolic/summary). */ + @Getter private boolean loopSummaries; + private static final boolean DEFAULT_LOOP_SUMMARIES = false; private static final boolean DEFAULT_STRING_INTERNING = true; @Getter private String stringProxy; private static final String DEFAULT_STRING_PROXY = "Ljava/lang/String;"; @@ -446,6 +455,9 @@ private void readProperties() { // Fontus options // ------------------------------------ useStringInterning = readBoolean("useStringInterning", DEFAULT_STRING_INTERNING); + loopStateMatching = readBoolean("loopStateMatching", DEFAULT_LOOP_STATE_MATCHING); + loopSummaries = readBoolean("loopSummaries", DEFAULT_LOOP_SUMMARIES); + deadlockWatchdog = readBoolean("deadlockWatchdog", DEFAULT_DEADLOCK_WATCHDOG); stringProxy = readString("stringProxy", DEFAULT_STRING_PROXY); } diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/AbstractMethodAdapter.java b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/AbstractMethodAdapter.java index 5d4920cd..3c8719a7 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/AbstractMethodAdapter.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/AbstractMethodAdapter.java @@ -287,8 +287,12 @@ private void handleListType(String param, int paramIdx, boolean addAssignment) { // Create instrumentation for the list itself InsnList listInstrumentation = Utils.liftListVariable(paramIdx, addAssignment, false); - // Apply the instrumentation + // Apply the instrumentation. Its loop over the elements has branches the original method + // (and so the SA graph) does not have: they are reported like those of SWAT's helpers. + String intrinsics = Type.getInternalName(Intrinsics.class); + visitMethodInsn(Opcodes.INVOKESTATIC, intrinsics, "enterInlinedSwatCode", "()V", false); listInstrumentation.accept(this); + visitMethodInsn(Opcodes.INVOKESTATIC, intrinsics, "leaveInlinedSwatCode", "()V", false); } private void instrumentEntryPointParameters(int access, String descriptor) { @@ -317,77 +321,12 @@ private boolean needsFreshCopy(Type type) { private void createFreshParameterCopy(Type type, int localIndex) { - String typeName = type.getInternalName(); - - if (type.getClassName().equals("java.lang.String")) { - // Create fresh copy: new String(original) - mv.visitTypeInsn(Opcodes.NEW, "java/lang/String"); - mv.visitInsn(Opcodes.DUP); - // Load original parameter - mv.visitVarInsn(Opcodes.ALOAD, localIndex); - // Stack: new_string, new_string, original - mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/String", "", - "(Ljava/lang/String;)V", false); - // Store back to parameter slot - mv.visitVarInsn(Opcodes.ASTORE, localIndex); - } else { - // For wrapper types, create fresh instances - createFreshWrapperCopy(type, localIndex); - } - } - - private void createFreshWrapperCopy(Type type, int localIndex) { - String className = type.getInternalName(); - String primitiveType = getPrimitiveTypeDescriptor(type); - - // Create new wrapper instance first - mv.visitTypeInsn(Opcodes.NEW, className); - mv.visitInsn(Opcodes.DUP); - // Stack: [new_wrapper, new_wrapper] - - // Load original wrapper and extract primitive value + // param = FreshCopy.of(param), which keeps null (see FreshCopy for why the check is + // not inlined here) + String simpleName = type.getClassName().substring("java.lang.".length()); mv.visitVarInsn(Opcodes.ALOAD, localIndex); - String valueMethod = getPrimitiveValueMethod(type); - String valueDescriptor = "()" + primitiveType; - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, className, valueMethod, valueDescriptor, false); - // Stack: [new_wrapper, new_wrapper, primitive_value] - - // Call constructor - String constructorDesc = "(" + primitiveType + ")V"; - mv.visitMethodInsn(Opcodes.INVOKESPECIAL, className, "", constructorDesc, false); - // Stack: [new_wrapper] (initialized) - - // Store back to parameter + mv.visitMethodInsn(Opcodes.INVOKESTATIC, "de/uzl/its/swat/common/FreshCopy", "of" + simpleName, + "(" + type.getDescriptor() + ")" + type.getDescriptor(), false); mv.visitVarInsn(Opcodes.ASTORE, localIndex); } - - private String getPrimitiveTypeDescriptor(Type type) { - String className = type.getClassName(); - switch (className) { - case "java.lang.Integer": return "I"; - case "java.lang.Long": return "J"; - case "java.lang.Short": return "S"; - case "java.lang.Byte": return "B"; - case "java.lang.Character": return "C"; - case "java.lang.Boolean": return "Z"; - case "java.lang.Float": return "F"; - case "java.lang.Double": return "D"; - default: throw new IllegalArgumentException("Unsupported type: " + className); - } - } - - private String getPrimitiveValueMethod(Type type) { - String className = type.getClassName(); - switch (className) { - case "java.lang.Integer": return "intValue"; - case "java.lang.Long": return "longValue"; - case "java.lang.Short": return "shortValue"; - case "java.lang.Byte": return "byteValue"; - case "java.lang.Character": return "charValue"; - case "java.lang.Boolean": return "booleanValue"; - case "java.lang.Float": return "floatValue"; - case "java.lang.Double": return "doubleValue"; - default: throw new IllegalArgumentException("Unsupported type: " + className); - } - } } diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/BranchKeys.java b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/BranchKeys.java new file mode 100644 index 00000000..d6e8ca50 --- /dev/null +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/BranchKeys.java @@ -0,0 +1,36 @@ +package de.uzl.its.swat.instrument; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Names the instructions that report a branch or push a frame, so that the explorer can check its + * walk of the static pre-analysis graph against the trace, and find its place again. + * + *

A key is {@code owner.name(desc)|line|kind|ordinal}: the method, the source line, the kind + * ({@code if}, {@code switch}, {@code arr}, {@code newarr}, {@code div}, or {@code call:name(desc)} + * for a call), and the instruction's position among those of the same kind on that line, in + * bytecode order. Bytecode offsets would not do: the graph is built by SootUp, which keeps none. + * Within a line, though, SootUp's statement order is the bytecode order. The extractor builds the + * same key from SootUp's statements (and leaves out lines where its view of them differs from the + * bytecode); change both together. + * + *

Keys are registered while a method is instrumented, for the iid of the instruction (see + * {@link GlobalStateForInstrumentation#setPendingKey}). + */ +public final class BranchKeys { + + private static final Map keys = new ConcurrentHashMap<>(); + + private BranchKeys() { + } + + static void register(long iid, String key) { + keys.put(iid, key); + } + + /** The key of the instruction that recorded {@code iid} (loop and switch bits are ignored), or null. */ + public static String keyOf(long iid) { + return keys.get(GlobalStateForInstrumentation.baseIid(iid)); + } +} diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/ClassSavingTransformer.java b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/ClassSavingTransformer.java index 9a506a1e..2ad9141f 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/ClassSavingTransformer.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/ClassSavingTransformer.java @@ -49,7 +49,7 @@ public byte[] transform( try { // ToDo (Flo): checking for classBeingRedefined != null probably does not make sense if (!Transformer.isInstrumented(cname)) { - return cbuf; + return null; } ClassReader cr = new ClassReader(cbuf); @@ -90,7 +90,7 @@ public byte[] transform( // The ErrorHandler has to be here as this method transfers control to ASM new ErrorHandler().handleException("Error while instrumenting class: " + cname, e); } - return cbuf; + return null; } public void saveClass(byte[] transformedClass, String cname) throws IOException { diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/GlobalStateForInstrumentation.java b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/GlobalStateForInstrumentation.java index d6d38e99..f03d6fd3 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/GlobalStateForInstrumentation.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/GlobalStateForInstrumentation.java @@ -36,11 +36,27 @@ public class GlobalStateForInstrumentation { private static final int MAX_SWITCH_CASES = (1 << SWITCH_BITS) - 1; private static final int MAX_FOR_ITERATIONS = (1 << FOR_BITS) - 1; + /** The key for the next iid, see {@link BranchKeys}. */ + private String pendingKey = null; + + /** + * Names the instruction being instrumented: the next iid handed out gets {@code key} (see + * {@link BranchKeys}). That is the instruction's own iid, since each instruction's + * instrumentation allocates it first. + */ + public void setPendingKey(String key) { + pendingKey = key; + } + /** Increment iid and get the complete id */ public long incAndGetId() { iid++; long id = getId(); registerInstructionId(id); + if (pendingKey != null) { + BranchKeys.register(id, pendingKey); + pendingKey = null; + } InstrCoverage.numInstructions += 1; return id; } @@ -77,6 +93,11 @@ public long getId() { } + /** {@code iid} without the loop counter and switch case bits, as the instruction was given it. */ + public static long baseIid(long iid) { + return iid & ((1L << (BIT_LEN_LONG - FOR_BITS - SWITCH_BITS)) - 1); + } + public static long extractCid(long iid) { long mask = (1L << CBITS) - 1; long shift = (IBITS + MBITS); diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/Intrinsics.java b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/Intrinsics.java index 0561ae14..40c6c713 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/Intrinsics.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/Intrinsics.java @@ -4,13 +4,16 @@ import com.sap.fontus.taintaware.unified.IASString; import de.uzl.its.swat.common.ErrorHandler; import de.uzl.its.swat.common.PrintBox; +import com.fasterxml.jackson.core.JsonProcessingException; import de.uzl.its.swat.common.exceptions.NoThreadContextException; +import de.uzl.its.swat.common.exceptions.NotImplementedException; import de.uzl.its.swat.common.exceptions.SWATAssert; import de.uzl.its.swat.common.exceptions.SWATException; import de.uzl.its.swat.common.exceptions.ThreadAlreadyDisabledException; import de.uzl.its.swat.common.logging.GlobalLogger; import de.uzl.its.swat.config.Config; import de.uzl.its.swat.solver.LocalSolver; +import de.uzl.its.swat.common.Profile; import de.uzl.its.swat.symbolic.dispatcher.SymbolicInstructionDispatcher; import de.uzl.its.swat.symbolic.value.primitive.numeric.integral.BooleanValue; import de.uzl.its.swat.symbolic.value.primitive.numeric.integral.IntValue; @@ -22,6 +25,7 @@ import de.uzl.its.swat.symbolic.value.primitive.numeric.floatingpoint.DoubleValue; import de.uzl.its.swat.symbolic.value.reference.lang.StringValue; import de.uzl.its.swat.thread.ThreadHandler; +import de.uzl.its.swat.witness.Witness; import lombok.Getter; import lombok.Setter; import org.objectweb.asm.Type; @@ -64,52 +68,109 @@ public static void unsupported() { */ public static void init(String endpoint) { try { - int endpointID; - if (config.getInstrumentationTransformer().equals(TransformerType.SV_COMP)) { - endpointID = 0; - } else { - endpointID = endpoint.hashCode(); + long threadId = currentThread().getId(); + if (ThreadHandler.hasThreadContext(threadId)) { + if (ThreadHandler.enterStartedEndpoint(threadId)) { + // The entry class's static initializer started the execution (initBeforeEntry). + // Its locals live in the initial frame that the endpoint now runs in. + ThreadHandler.getSymbolicVisitor(threadId).getStack().getActiveFrame().clearLocals(); + logger.info("Entering endpoint {} started by its static initializer (Thread: {})", + endpoint, threadId); + return; + } + // An endpoint called from within a running one (e.g. a harness main calling + // another main) continues the running symbolic execution. + ThreadHandler.enterNestedEndpoint(threadId); + logger.info("Entering nested endpoint {} (Thread: {})", endpoint, threadId); + return; } - ThreadHandler.addThreadContext(currentThread().getId(), endpoint, endpointID); + startExecution(endpoint); + } catch (Throwable t) { + // The ErrorHandler has to be here as this method transfers control to the target domain + new ErrorHandler().handleException("Error during initialization of symbolic engine: ", t); + } + } - logger.info( - new PrintBox( - 60, - "Symbolic execution started (Thread: " - + Thread.currentThread().getId() - + ")", - new ArrayList<>( - List.of( - new String[]{ - "Execution started (Thread: " - + Thread.currentThread().getId() - + ")", - "Threads (tracked / active): (" - + ThreadHandler.getThreadCount() - + " / " - + Thread.activeCount() - + ")", - "Method: " + endpoint, - "ID: " + endpointID - }))) - .toString()); + /** + * Starts symbolic tracking in the static initializer of the entry class, which the JVM runs + * before the entry method, so that inputs read there (e.g. a static field initialised with a + * Verifier.nondet* call) are tracked. The entry method's init call then continues this + * execution. Does nothing if the thread's execution is already running. The method call is + * added during instrumentation. + * + * @param endpoint The name of the endpoint whose class is being initialised. + */ + public static void initBeforeEntry(String endpoint) { + try { + long threadId = currentThread().getId(); + if (ThreadHandler.hasThreadContext(threadId)) { + return; + } + startExecution(endpoint); + ThreadHandler.setStartedBeforeEndpoint(threadId); } catch (Throwable t) { // The ErrorHandler has to be here as this method transfers control to the target domain new ErrorHandler().handleException("Error during initialization of symbolic engine: ", t); } } + private static void startExecution(String endpoint) throws Exception { + Profile.mark("exec.start"); + int endpointID; + if (config.getInstrumentationTransformer().equals(TransformerType.SV_COMP)) { + endpointID = 0; + } else { + endpointID = endpoint.hashCode(); + } + ThreadHandler.addThreadContext(currentThread().getId(), endpoint, endpointID); + Profile.mark("exec.context"); + + logger.info( + new PrintBox( + 60, + "Symbolic execution started (Thread: " + + Thread.currentThread().getId() + + ")", + new ArrayList<>( + List.of( + new String[]{ + "Execution started (Thread: " + + Thread.currentThread().getId() + + ")", + "Threads (tracked / active): (" + + ThreadHandler.getThreadCount() + + " / " + + Thread.activeCount() + + ")", + "Method: " + endpoint, + "ID: " + endpointID + }))) + .toString()); + } + /** * Finishes symbolic tracking and initializes constrain transfer to coordinator. Method call * added during instrumentation. */ + /** + * Marks the start of code SWAT inlined into a target method. Only the symbolic side acts on it + * (InternalInvocation, ShadowContext.enterInlinedSwatCode). + */ + public static void enterInlinedSwatCode() {} + + /** Marks the end of code SWAT inlined into a target method, see {@link #enterInlinedSwatCode}. */ + public static void leaveInlinedSwatCode() {} + public static void terminate() { + Profile.mark("terminate.start"); try { + if (ThreadHandler.leaveNestedEndpoint(currentThread().getId())) { + logger.info("Leaving nested endpoint (Thread: {})", currentThread().getId()); + return; + } Transformer.logMissedClasses(); ThreadHandler.disableThreadContext(currentThread().getId()); - ThreadHandler.logStats(currentThread().getId()); - ThreadHandler.logStats(-1); logger.info( new PrintBox( 60, @@ -140,7 +201,7 @@ public static void terminate() { break; case PRINT: if (!ThreadHandler.isThreadContextAborted(currentThread().getId())) { - System.out.println(ThreadHandler.getSymbolicVisitor(currentThread().getId()).getSymbolicTraceHandler().getTraceDTO()); + printTrace(currentThread().getId()); } break; case NONE: @@ -150,8 +211,17 @@ public static void terminate() { } SymbolicInstructionDispatcher.flush(); // TODO WTF does that do? calls log(null) <- I think what this does is removing that last symbolic instruction from the pipeline as symbolic execution is always one behind. - ThreadHandler.removeSolverContext(currentThread().getId()); + // An SV-COMP run is one execution per JVM, which exits right after it. Closing the + // solver context there only costs time: Z3 frees the terms of the trace one by one, + // which takes minutes after a long trace. + if (!config.getInstrumentationTransformer().equals(TransformerType.SV_COMP)) { + ThreadHandler.removeSolverContext(currentThread().getId()); + // A long-running target (an HTTP endpoint) executes many times in one JVM: the + // witness data (the violation) belongs to the trace just sent, not to the next. + Witness.reset(); + } ThreadHandler.removeThreadContext(currentThread().getId()); + Profile.mark("terminate.end"); } catch (NoThreadContextException | ThreadAlreadyDisabledException e) { logger.warn(e.toString()); logger.warn( @@ -164,6 +234,46 @@ public static void terminate() { } } + private static void printTrace(long threadId) throws NoThreadContextException, NotImplementedException, JsonProcessingException { + System.out.println(ThreadHandler.getSymbolicVisitor(threadId).getSymbolicTraceHandler().getTraceDTO(ThreadHandler.getStatsStorage(threadId), threadId)); + } + + /** + * Sends (HTTP) or prints (PRINT) the traces of target threads whose symbolic execution is still + * active when the JVM shuts down, i.e. the target ended the run abruptly (System.exit) before + * reaching the instrumented end of its entry method. Executions that terminated normally have + * removed their context already, so this is a no-op for them. Runs in the shutdown hook + * registered by the Transformer, hence on another thread than the target; errors are logged + * only. Instrumented exit calls end the execution before the JVM shuts down ({@link + * de.uzl.its.swat.instrument.svcomp.SystemExit}); this hook is the fallback for the exits that + * bypass them (uninstrumented callers, reflection). + */ + public static void terminateOnShutdown() { + for (long threadId : ThreadHandler.getActiveThreadContextIds()) { + try { + logger.warn( + "JVM shutdown with an active symbolic execution (thread {}), e.g. System.exit in the target: sending its trace", + threadId); + ThreadHandler.disableThreadContext(threadId); + if (ThreadHandler.isThreadContextAborted(threadId)) { + continue; + } + switch (config.getSolverMode()) { + case HTTP: + ThreadHandler.sendData(threadId); + break; + case PRINT: + printTrace(threadId); + break; + default: + break; + } + } catch (Throwable t) { + logger.error("Error sending the trace of thread {} on shutdown", threadId, t); + } + } + } + public static void retrieveAssignments() { PrintBox printBox = new PrintBox(60, "Retrieving assignments"); System.getProperties().forEach((key, value) -> { diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/Transformer.java b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/Transformer.java index 149e4855..36fdfbc0 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/Transformer.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/Transformer.java @@ -3,13 +3,16 @@ import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.LoggerContext; import de.uzl.its.swat.common.ErrorHandler; +import de.uzl.its.swat.common.StackOverflowGuard; import de.uzl.its.swat.common.PrintBox; +import de.uzl.its.swat.common.Profile; import de.uzl.its.swat.common.Util; import de.uzl.its.swat.common.exceptions.InstrumentationException; import de.uzl.its.swat.common.exceptions.NotImplementedException; import de.uzl.its.swat.common.logging.GlobalLogger; import de.uzl.its.swat.config.Config; import de.uzl.its.swat.instrument.annotation.AnnotationTransformer; +import de.uzl.its.swat.instrument.assertion.AssertionThrowTransformer; import de.uzl.its.swat.instrument.classinfo.ClassInfoTransformer; import de.uzl.its.swat.instrument.classvariables.ClassVariablesTransformer; import de.uzl.its.swat.instrument.dataendpoint.SpringRepositoryTransformer; @@ -35,8 +38,12 @@ public abstract class Transformer implements ClassFileTransformer { static { + Profile.mark("agent.clinit"); // register the hook as early as possible Runtime.getRuntime().addShutdownHook(new Thread(() -> { + // A target that ends the JVM abruptly (System.exit) must not lose its trace + Intrinsics.terminateOnShutdown(); + Profile.dump("shutdown.hook"); LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory(); // this will flush and close all appenders (including your buffered FileAppender) lc.stop(); @@ -131,9 +138,18 @@ public static void logMissedClasses(){ @SuppressWarnings("unused") public static void premain(String agentArgs, Instrumentation inst) { + Profile.mark("premain.start"); try { - ThreadHandler.init(); + // Initialised here, not on first use: that can be on an exhausted stack, where a + // failing static initialiser would break the class for the rest of the run + StackOverflowGuard.init(); + new ErrorHandler(); + Profile.mark("premain.handlers"); ThreadHandler.addThreadContext(-1, "SymbolicThread", -1); // Main thread used during initialization and instrumentation + Profile.mark("premain.context"); + if (config.isDeadlockWatchdog()) { + de.uzl.its.swat.thread.DeadlockWatchdog.start(); + } if (config.getInstrumentationTransformer().equals(TransformerType.SV_COMP)) { Verifier.retrieveInputs(); // ToDo only conditionally and at a petter place @@ -142,6 +158,7 @@ public static void premain(String agentArgs, Instrumentation inst) { } + Profile.mark("premain.inputs"); instrumentation = inst; logMissedClasses(); if (config.getInstrumentationTransformer().equals(TransformerType.NONE)) { @@ -167,6 +184,7 @@ public static void premain(String agentArgs, Instrumentation inst) { printBox.addMsg("Working Directory: " + System.getProperty("user.dir")); printBox.addMsg(""); + if (Profile.ON) inst.addTransformer(Profile.begin()); inst.addTransformer(new ClassInfoTransformer()); inst.addTransformer(new NoCacheTransformer()); inst.addTransformer(new RefEqualityTransformer()); @@ -177,6 +195,7 @@ public static void premain(String agentArgs, Instrumentation inst) { } case SPRING_ENDPOINT -> { inst.addTransformer(new SpringEndpointTransformer()); + inst.addTransformer(new AssertionThrowTransformer()); // inst.addTransformer(new SpringExceptionTransformer()); if (Config.instance().isUseDataEndpointAdapter()) { inst.addTransformer(new SpringRepositoryTransformer()); @@ -209,7 +228,9 @@ public static void premain(String agentArgs, Instrumentation inst) { if (config.isLoggingClasses()) { inst.addTransformer(new ClassSavingTransformer()); } + if (Profile.ON) inst.addTransformer(Profile.end()); logger.info(printBox.toString()); + Profile.mark("premain.end"); } catch (Throwable t) { // The ErrorHandler has to be here as this method transfers control to ASM new ErrorHandler().handleException(new InstrumentationException(t)); diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/annotation/AnnotationTransformer.java b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/annotation/AnnotationTransformer.java index 4b07930f..ab27d976 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/annotation/AnnotationTransformer.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/annotation/AnnotationTransformer.java @@ -68,7 +68,7 @@ public byte[] transform(ClassLoader loader, String cname, Class classBeingRed try { // Skip redefinitions and classes that should not be instrumented. if (classBeingRedefined != null || cname == null || !Util.shouldInstrument(cname)) { - return cbuf; + return null; } getPrintBox().addMsg("Class: " + cname); ClassReader cr = new ClassReader(cbuf); @@ -93,6 +93,6 @@ public byte[] transform(ClassLoader loader, String cname, Class classBeingRed new ErrorHandler().handleException("Error while instrumenting class: " + cname, e); } // Return the original bytecode (this transformer does not modify the class). - return cbuf; + return null; } } diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/assertion/AssertionThrowTransformer.java b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/assertion/AssertionThrowTransformer.java new file mode 100644 index 00000000..e0059b8b --- /dev/null +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/assertion/AssertionThrowTransformer.java @@ -0,0 +1,70 @@ +package de.uzl.its.swat.instrument.assertion; + +import de.uzl.its.swat.common.ErrorHandler; +import de.uzl.its.swat.common.Util; +import de.uzl.its.swat.instrument.Transformer; +import java.lang.instrument.ClassFileTransformer; +import java.security.ProtectionDomain; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +/** + * Reports every {@code AssertionError} where it is thrown ({@code Witness.recordThrow}), as the + * SV-COMP transformer does: an assertion fails where it is thrown, even if the target catches the + * error afterwards (spatial4j's endpoint catches AssertionError, for one). For targets that are + * not run as SV-COMP tasks, e.g. Spring endpoints, where the explorer reads the violation from the + * trace's witness data. + */ +public class AssertionThrowTransformer implements ClassFileTransformer { + + public AssertionThrowTransformer() { + Transformer.getPrintBox() + .addMsg("Initializing Transformer: " + this.getClass().getSimpleName()); + } + + @Override + public byte[] transform( + ClassLoader loader, + String cname, + Class classBeingRedefined, + ProtectionDomain d, + byte[] cbuf) { + if (classBeingRedefined != null || cname == null || !Util.shouldInstrument(cname)) return cbuf; + try { + ClassReader cr = new ClassReader(cbuf); + // The hook keeps the stack the same at every frame, so only the max stack changes. + ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS); + cr.accept( + new ClassVisitor(Opcodes.ASM9, cw) { + @Override + public MethodVisitor visitMethod( + int access, String name, String desc, String signature, String[] exceptions) { + MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); + return mv == null || Util.ignoreMethod(name) ? mv : new MethodVisitor(Opcodes.ASM9, mv) { + @Override + public void visitInsn(int opcode) { + if (opcode == Opcodes.ATHROW) { + super.visitInsn(Opcodes.DUP); + super.visitMethodInsn( + Opcodes.INVOKESTATIC, + "de/uzl/its/swat/witness/Witness", + "recordThrow", + "(Ljava/lang/Throwable;)V", + false); + } + super.visitInsn(opcode); + } + }; + } + }, + 0); + return cw.toByteArray(); + } catch (Exception e) { + new ErrorHandler().handleException("Error while instrumenting class: " + cname, e); + } + return cbuf; + } +} diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/classinfo/ClassInfoTransformer.java b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/classinfo/ClassInfoTransformer.java index 7799073b..7066821e 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/classinfo/ClassInfoTransformer.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/classinfo/ClassInfoTransformer.java @@ -36,7 +36,7 @@ public byte[] transform(ClassLoader loader, try { // Only process new classes that should be instrumented. if (classBeingRedefined != null || cname == null || !Util.shouldInstrument(cname)) { - return cbuf; + return null; } getPrintBox().addMsg("Processing class: " + cname); @@ -71,7 +71,7 @@ public byte[] transform(ClassLoader loader, new ErrorHandler().handleException(new InstrumentationException(cname, t)); } // Return the original bytecode (this transformer does not modify the class). - return cbuf; + return null; } /** diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/classvariables/ClassVariablesTransformer.java b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/classvariables/ClassVariablesTransformer.java index cc106658..0042f09c 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/classvariables/ClassVariablesTransformer.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/classvariables/ClassVariablesTransformer.java @@ -70,11 +70,11 @@ public byte[] transform( try { if (classBeingRedefined != null || cname == null || !Util.shouldInstrument(cname)) { - return cbuf; + return null; } if (!Transformer.containsRequestBodyClass(cname)) { - return cbuf; + return null; } getPrintBox().addMsg("Class: " + cname); @@ -83,7 +83,7 @@ public byte[] transform( cr.accept(cn, 0); // if (Transformer.getRequestBodyClassTransformed().contains(cname)) { - // return cbuf; + // return null; // } ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES); @@ -106,6 +106,6 @@ public byte[] transform( errorHandler.handleException("Error while instrumenting class: " + cname, e); } Transformer.addInstrumentedClass(cname, InternalTransformerType.CLASS_VARIABLES); - return cbuf; + return null; } } diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/dataendpoint/SpringRepositoryTransformer.java b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/dataendpoint/SpringRepositoryTransformer.java index 8f41f713..e53a2427 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/dataendpoint/SpringRepositoryTransformer.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/dataendpoint/SpringRepositoryTransformer.java @@ -65,7 +65,7 @@ public byte[] transform( byte[] cbuf) { if (classBeingRedefined != null || cname == null || !Util.shouldInstrument(cname)) { - return cbuf; + return null; } printBox.addMsg("Class: " + cname); @@ -105,6 +105,6 @@ public byte[] transform( logger.debug(printBox.toString()); } - return cbuf; + return null; } } diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/instruction/InstructionClassAdapter.java b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/instruction/InstructionClassAdapter.java index 60bba95d..c95d6a4c 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/instruction/InstructionClassAdapter.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/instruction/InstructionClassAdapter.java @@ -8,6 +8,9 @@ import org.objectweb.asm.Opcodes; import org.objectweb.asm.commons.JSRInlinerAdapter; import org.objectweb.asm.commons.TryCatchBlockSorter; +import org.objectweb.asm.tree.MethodNode; +import de.uzl.its.swat.config.Config; +import de.uzl.its.swat.symbolic.loopstate.LoopHeads; /** * A visitor to visit a Java class. The methods of this class must be called in the following order: @@ -17,6 +20,8 @@ */ public class InstructionClassAdapter extends ClassVisitor { private final String cname; + /** Whether the class has a static initializer (see visitEnd). */ + private boolean hasClinit = false; /** * Constructor that calls the super from the default ClassVisitor * @@ -61,6 +66,9 @@ public void visit( public MethodVisitor visitMethod( int access, String name, String desc, String signature, String[] exceptions) { MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions); + if (name.equals("")) { + hasClinit = true; + } if (cname.equals("de/uzl/its/swat/instrument/svcomp/Verifier") && !name.equals("assume")) { return mv; } @@ -82,9 +90,46 @@ public MethodVisitor visitMethod( name, desc, cname); + boolean loopHeads = Config.instance().isLoopStateMatching(); + boolean summaries = Config.instance().isLoopSummaries(); + if ((loopHeads || summaries) && !cname.startsWith("de/uzl/its/swat/")) { + // Collect the method to find its loop heads and loop summaries, then hand it on + return new MethodNode(Opcodes.ASM9, access, name, desc, signature, exceptions) { + @Override + public void visitEnd() { + super.visitEnd(); + if (loopHeads) { + ima.setLoopHeads(LoopHeads.analyze(cname, this)); + } + if (summaries) { + ima.setLoopSummaries(de.uzl.its.swat.symbolic.summary.LoopSummaries.analyze(cname, this)); + } + accept(ima); + } + }; + } return ima; } return null; } + + /** + * With loop-state matching, gives a class without a static initializer one that records its + * initialization (see ClassInits), as the existing ones do. + */ + @Override + public void visitEnd() { + if (Config.instance().isLoopStateMatching() && !hasClinit && !cname.startsWith("de/uzl/its/swat/")) { + MethodVisitor mv = cv.visitMethod(Opcodes.ACC_STATIC, "", "()V", null, null); + if (mv != null) { + mv.visitCode(); + LoopHeads.emitClassInit(mv, cname); + mv.visitInsn(Opcodes.RETURN); + mv.visitMaxs(1, 0); + mv.visitEnd(); + } + } + super.visitEnd(); + } } diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/instruction/InstructionMethodAdapter.java b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/instruction/InstructionMethodAdapter.java index fa432935..6dc5693e 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/instruction/InstructionMethodAdapter.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/instruction/InstructionMethodAdapter.java @@ -1,6 +1,7 @@ package de.uzl.its.swat.instrument.instruction; import de.uzl.its.swat.common.ErrorHandler; +import de.uzl.its.swat.common.StackOverflowGuard; import de.uzl.its.swat.common.exceptions.InstrumentationException; import de.uzl.its.swat.common.exceptions.SWATAssert; import de.uzl.its.swat.config.Config; @@ -9,6 +10,7 @@ import de.uzl.its.swat.instrument.TryCatchBlock; import de.uzl.its.swat.instrument.Utils; import de.uzl.its.swat.metadata.ClassDepot; +import de.uzl.its.swat.symbolic.loopstate.LoopHeads; import java.lang.reflect.Field; import java.lang.reflect.Method; @@ -69,6 +71,34 @@ public InstructionMethodAdapter( this.desc = desc; } + /** The current source line, and how many instructions of each kind each line has had (see BranchKeys). */ + private int line = -1; + private final java.util.Map keyOrdinals = new java.util.HashMap<>(); + + /** + * Names the instruction being visited for the trace (see {@link de.uzl.its.swat.instrument.BranchKeys}): + * every instruction that reports a branch or pushes a frame. Called before its instrumentation + * allocates its iid. + */ + private void nameInstruction(String kind) { + if (owner.startsWith("de/uzl/its/swat/")) { + return; // SWAT's helpers: the graph has their branches inline in the caller, without keys + } + String group = line + "|" + kind; + int ordinal = keyOrdinals.merge(group, 1, Integer::sum) - 1; + instrumentationState.setPendingKey(owner + "." + methodName + desc + "|" + group + "|" + ordinal); + } + + private static String branchKindOf(int opcode) { + if ((opcode >= IALOAD && opcode <= SALOAD) || (opcode >= IASTORE && opcode <= SASTORE)) { + return "arr"; + } + if (opcode == IDIV || opcode == IREM || opcode == LDIV || opcode == LREM) { + return "div"; + } + return null; + } + /** Starts the visit of the method's code, if any (i.e. non abstract method). */ @Override public void visitCode() { @@ -82,6 +112,9 @@ public void visitCode() { instrumentationState.setMid(mIdx); if (methodName.equals("")) { + if (config.isLoopStateMatching() && !owner.startsWith("de/uzl/its/swat/")) { + LoopHeads.emitClassInit(mv, owner); + } int cIdx = classDepot.getClassIndex(this.owner); String clinit_desc = "(JIJ)V"; // (long iid, int cIdx, long invokeId) addJpushInsn(mv, instrumentationState.incAndGetId()); @@ -103,6 +136,41 @@ public void visitCode() { private static void addJpushInsn(MethodVisitor mv, long val) { Utils.addJpushInsn(mv, val); } + + /** The method's loop heads (see LoopHeads), by the label that starts each. */ + private java.util.Map loopHeads = java.util.Map.of(); + + public void setLoopHeads(java.util.Map loopHeads) { + this.loopHeads = loopHeads; + } + + /** The method's loop summaries (see symbolic/summary/LoopSummaries), and the jumps seen so far. */ + private de.uzl.its.swat.symbolic.summary.LoopSummaries.MethodPlans loopSummaries = + de.uzl.its.swat.symbolic.summary.LoopSummaries.MethodPlans.NONE; + private int jumpOrdinal = 0; + + public void setLoopSummaries(de.uzl.its.swat.symbolic.summary.LoopSummaries.MethodPlans plans) { + this.loopSummaries = plans; + } + + /** + * At a loop head, probes the state. The probe goes through the processor like an instruction, + * so the symbolic execution, which lags one instruction behind, handles it once the jump that + * led here has been recorded. + */ + @Override + public void visitLabel(Label label) { + super.visitLabel(label); + LoopHeads.Head head = loopHeads.get(label); + if (head != null) { + LoopHeads.emitProbe(mv, config.getInstrumentationDispatcher(), instrumentationState.incAndGetId(), head); + } + Integer join = loopSummaries.joins().get(label); + if (join != null) { + de.uzl.its.swat.symbolic.summary.LoopSummaries.emitJoinProbe( + mv, config.getInstrumentationDispatcher(), instrumentationState.incAndGetId(), join); + } + } /** Push a value onto the stack. */ private static void addBipushInsn(MethodVisitor mv, int val) { Utils.addBipushInsn(mv, val); @@ -154,6 +222,7 @@ private void addTypeInsn(MethodVisitor mv, String type, int opcode, String name) */ @Override public void visitLineNumber(int lineNumber, Label label) { + line = lineNumber; mv.visitLineNumber(lineNumber, label); } @@ -174,6 +243,10 @@ public void visitLineNumber(int lineNumber, Label label) { */ @Override public void visitInsn(int opcode) { + String kind = branchKindOf(opcode); + if (kind != null) { + nameInstruction(kind); + } boolean ex; switch (opcode) { case NOP: @@ -733,6 +806,9 @@ public void visitVarInsn(int opcode, int var) { */ @Override public void visitIntInsn(int opcode, int operand) { + if (opcode == NEWARRAY) { + nameInstruction("newarr"); + } String desc = "(JI)V"; addJpushInsn(mv, instrumentationState.incAndGetId()); @@ -791,6 +867,7 @@ public void visitTypeInsn(int opcode, String type) { calledNew = true; break; case ANEWARRAY: + nameInstruction("newarr"); addTypeInsn(mv, type, opcode, "ANEWARRAY"); addSpecialInsn(mv, 0); // for non-exceptional path break; @@ -1484,6 +1561,23 @@ private boolean isFieldGetFieldMethod(String method) { return getFieldMethods.contains(method); } + /** + * Records a StackOverflowError that reaches the exception handler of a call site in + * StackOverflowGuard.targetOverflowed, before the exception is reported to SWAT. Expects the + * throwable on top of the operand stack and leaves it there. No call and no branch: on an + * exhausted stack a call could replace the exception in flight. + * + *

targetOverflowed |= (throwable instanceof StackOverflowError)
+ */ + private void addStackOverflowCheck(MethodVisitor mv) { + String guard = Type.getInternalName(StackOverflowGuard.class); + mv.visitInsn(DUP); + mv.visitTypeInsn(INSTANCEOF, "java/lang/StackOverflowError"); + mv.visitFieldInsn(GETSTATIC, guard, "targetOverflowed", "Z"); + mv.visitInsn(IOR); + mv.visitFieldInsn(PUTSTATIC, guard, "targetOverflowed", "Z"); + } + private void addMethodWithTryCatch( int opcode, String owner, String name, String desc, boolean itf) { @@ -1543,6 +1637,7 @@ private void addMethodWithTryCatch( mv.visitJumpInsn(GOTO, jumpTarget); mv.visitLabel(handler); + addStackOverflowCheck(mv); addJpushInsn(mv, instrumentationState.incAndGetId()); addJpushInsn(mv, invokeId); @@ -1581,6 +1676,7 @@ private void addMethodWithTryCatch( */ @Override public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) { + nameInstruction("call:" + name + desc); String d = "(JJLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"; // increment and pushes the instruction id to the stack using the mv addJpushInsn(mv, instrumentationState.incAndGetId()); @@ -1618,6 +1714,7 @@ public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object. mv.visitJumpInsn(GOTO, jumpTarget); // still try catch mv.visitLabel(handler); + addStackOverflowCheck(mv); addJpushInsn(mv, instrumentationState.incAndGetId()); addJpushInsn(mv, invokeId); mv.visitMethodInsn( @@ -1654,6 +1751,7 @@ public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object. */ @Override public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { + nameInstruction("call:" + name + desc); if (opcode == INVOKESPECIAL && name.equals("")) { if (isInit) { // This code is already inside an init method. @@ -1717,9 +1815,16 @@ public void visitMethodInsn(int opcode, String owner, String name, String desc, @Override public void visitJumpInsn(int opcode, Label label) { + if (opcode != GOTO && opcode != JSR) { + nameInstruction("if"); + } long iid3; String desc = "(JI)V"; addJpushInsn(mv, iid3 = instrumentationState.incAndGetId()); + de.uzl.its.swat.symbolic.summary.LoopSummaries.Plan summary = loopSummaries.byJump().get(jumpOrdinal++); + if (summary != null) { + de.uzl.its.swat.symbolic.summary.LoopSummaries.register(iid3, summary); + } addBipushInsn(mv, System.identityHashCode(label)); // label.getOffset() switch (opcode) { @@ -1954,6 +2059,7 @@ public void visitIincInsn(int var, int increment) { @Override public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) { + nameInstruction("switch"); long iid3; String desc = "(JIII[I[I)V"; // Add an extra array for case values @@ -1990,6 +2096,7 @@ public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) @Override public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) { + nameInstruction("switch"); long iid3; String desc = "(JI[I[I)V"; @@ -2026,6 +2133,7 @@ public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) { @Override public void visitMultiANewArrayInsn(String desc, int dims) { + nameInstruction("newarr"); String d = "(JLjava/lang/String;I)V"; addJpushInsn(mv, instrumentationState.incAndGetId()); diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/instruction/InstructionTransformer.java b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/instruction/InstructionTransformer.java index 31ac2187..e456c7ad 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/instruction/InstructionTransformer.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/instruction/InstructionTransformer.java @@ -65,7 +65,7 @@ public byte[] transform( byte[] cbuf) { try { - if (classBeingRedefined != null || cname == null || !Util.shouldInstrument(cname)) return cbuf; + if (classBeingRedefined != null || cname == null || !Util.shouldInstrument(cname)) return null; getPrintBox().addMsg("Class: " + cname); getPrintBox().setContentPresent(true); @@ -73,7 +73,7 @@ public byte[] transform( ClassWriter cw = new SafeClassWriter( cr, loader, ClassWriter.COMPUTE_FRAMES); - ClassVisitor cv = new InstructionClassAdapter(new CheckClassAdapter(cw, true), cname); + ClassVisitor cv = new InstructionClassAdapter(Util.withCheckClassAdapter(cw, cname), cname); try { cr.accept(cv, ClassReader.SKIP_FRAMES); @@ -110,7 +110,7 @@ public byte[] transform( Transformer.addInstrumentedClass(cname, InternalTransformerType.INSTRUCTION); if (getPrintBox().isContentPresent()) logger.debug(getPrintBox().toString()); - return cbuf; + return null; } public void saveClass(byte[] transformedClass, String cname) throws Exception { diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/nocache/NoCacheMethodAdapter.java b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/nocache/NoCacheMethodAdapter.java index aba06b80..b301652e 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/nocache/NoCacheMethodAdapter.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/nocache/NoCacheMethodAdapter.java @@ -23,6 +23,7 @@ public void visitLdcInsn(Object value) { mv.visitLdcInsn(value); mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/String", "", "(Ljava/lang/String;)V", false); + markDeInterned(); //NoCacheTransformer.getPrintBox() // .addMsg("Replacing LDC with new String"); } else { @@ -33,14 +34,16 @@ public void visitLdcInsn(Object value) { // Intercept method calls to disable interning and caching. @Override public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) { - // Skip String.intern() calls. + // Replace String.intern() with a de-interned copy that stands for the pooled instance. if (opcode == Opcodes.INVOKEVIRTUAL && owner.equals("java/lang/String") && name.equals("intern") && descriptor.equals("()Ljava/lang/String;") && Config.instance().isUseStringInterning()) { + mv.visitMethodInsn(Opcodes.INVOKESTATIC, "de/uzl/its/swat/common/UtilInstrumented", "intern", + "(Ljava/lang/String;)Ljava/lang/String;", false); NoCacheTransformer.getPrintBox() - .addMsg("Removing String.intern() call"); + .addMsg("Replacing String.intern() with UtilInstrumented.intern"); return; } // Replace Integer.valueOf(int) with new Integer(int) @@ -54,6 +57,7 @@ public void visitMethodInsn(int opcode, String owner, String name, String descri mv.visitInsn(Opcodes.DUP); mv.visitVarInsn(Opcodes.ILOAD, localVarIndex); mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Integer", "", "(I)V", false); + markDeInterned(); NoCacheTransformer.getPrintBox() .addMsg("Replacing Integer.valueOf with new Integer"); return; @@ -69,6 +73,7 @@ public void visitMethodInsn(int opcode, String owner, String name, String descri mv.visitInsn(Opcodes.DUP); mv.visitVarInsn(Opcodes.LLOAD, localVarIndex); mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Long", "", "(J)V", false); + markDeInterned(); NoCacheTransformer.getPrintBox() .addMsg("Replacing Long.valueOf with new Long"); return; @@ -84,6 +89,7 @@ public void visitMethodInsn(int opcode, String owner, String name, String descri mv.visitInsn(Opcodes.DUP); mv.visitVarInsn(Opcodes.ILOAD, localVarIndex); mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Short", "", "(S)V", false); + markDeInterned(); NoCacheTransformer.getPrintBox() .addMsg("Replacing Short.valueOf with new Short"); return; @@ -99,6 +105,7 @@ public void visitMethodInsn(int opcode, String owner, String name, String descri mv.visitInsn(Opcodes.DUP); mv.visitVarInsn(Opcodes.ILOAD, localVarIndex); mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Byte", "", "(B)V", false); + markDeInterned(); NoCacheTransformer.getPrintBox() .addMsg("Replacing Byte.valueOf with new Byte"); return; @@ -114,6 +121,7 @@ public void visitMethodInsn(int opcode, String owner, String name, String descri mv.visitInsn(Opcodes.DUP); mv.visitVarInsn(Opcodes.ILOAD, localVarIndex); mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Character", "", "(C)V", false); + markDeInterned(); NoCacheTransformer.getPrintBox() .addMsg("Replacing Character.valueOf with new Character"); return; @@ -129,6 +137,7 @@ public void visitMethodInsn(int opcode, String owner, String name, String descri mv.visitInsn(Opcodes.DUP); mv.visitVarInsn(Opcodes.ILOAD, localVarIndex); mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Boolean", "", "(Z)V", false); + markDeInterned(); NoCacheTransformer.getPrintBox() .addMsg("Replacing Boolean.valueOf with new Boolean"); return; @@ -137,6 +146,16 @@ public void visitMethodInsn(int opcode, String owner, String name, String descri mv.visitMethodInsn(opcode, owner, name, descriptor, isInterface); } + /** + * Registers the copy on top of the stack as standing for a shared instance, so that + * {@code UtilInstrumented.refEquals} can reproduce the identity of the original program. + */ + private void markDeInterned() { + mv.visitInsn(Opcodes.DUP); + mv.visitMethodInsn(Opcodes.INVOKESTATIC, "de/uzl/its/swat/common/RefIdentity", "markDeInterned", + "(Ljava/lang/Object;)V", false); + } + /* // Instrument object reference comparisons (IF_ACMPEQ and IF_ACMPNE) to print a warning. @Override diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/nocache/NoCacheTransformer.java b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/nocache/NoCacheTransformer.java index 77264403..246cae10 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/nocache/NoCacheTransformer.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/nocache/NoCacheTransformer.java @@ -41,13 +41,13 @@ public byte[] transform(ClassLoader loader, String cname, Class classBeingRed ProtectionDomain d, byte[] cbuf) { try { - if (classBeingRedefined != null || cname == null || !Util.shouldInstrument(cname)) return cbuf; + if (classBeingRedefined != null || cname == null || !Util.shouldInstrument(cname)) return null; getPrintBox().addMsg("Class: " + cname); getPrintBox().setContentPresent(true); ClassReader cr = new ClassReader(cbuf); ClassWriter cw = new SafeClassWriter(cr, loader, ClassWriter.COMPUTE_FRAMES); - ClassVisitor cv = new NoCacheClassAdapter(new CheckClassAdapter(cw, true), cname); + ClassVisitor cv = new NoCacheClassAdapter(Util.withCheckClassAdapter(cw, cname), cname); try { cr.accept(cv, ClassReader.SKIP_FRAMES); if (Config.instance().isUseCheckClassAdapter() && Util.useCheckClassAdapterForClass(cname)) { @@ -81,7 +81,7 @@ public byte[] transform(ClassLoader loader, String cname, Class classBeingRed Transformer.addInstrumentedClass(cname, InternalTransformerType.REF_EQUALITY); if (getPrintBox().isContentPresent()) logger.debug(getPrintBox().toString()); - return cbuf; + return null; } } diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/parameter/ParameterTransformer.java b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/parameter/ParameterTransformer.java index f49aa244..afb18fb0 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/parameter/ParameterTransformer.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/parameter/ParameterTransformer.java @@ -75,7 +75,7 @@ public byte[] transform( if (classBeingRedefined != null || cname == null || !Util.isSymbolicClass(cname)) { - return cbuf; + return null; } getPrintBox().addMsg("Class: " + cname); try { @@ -104,6 +104,6 @@ public byte[] transform( } Transformer.addInstrumentedClass(cname, InternalTransformerType.PARAMETER); if (getPrintBox().isContentPresent()) logger.debug(getPrintBox().toString()); - return cbuf; + return null; } } diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/refequality/RefEqualityTransformer.java b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/refequality/RefEqualityTransformer.java index c749857b..dd77d255 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/refequality/RefEqualityTransformer.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/refequality/RefEqualityTransformer.java @@ -39,18 +39,18 @@ public byte[] transform(ClassLoader loader, String cname, Class classBeingRed ProtectionDomain d, byte[] cbuf) { try { - if (classBeingRedefined != null || cname == null || !Util.shouldInstrument(cname)) return cbuf; + if (classBeingRedefined != null || cname == null || !Util.shouldInstrument(cname)) return null; // Don't touch UtilInstrumented because it needs to contain reference equality checks if(Util.isInstrumentedUtilClass(cname)) { - return cbuf; + return null; } getPrintBox().addMsg("Class: " + cname); getPrintBox().setContentPresent(true); ClassReader cr = new ClassReader(cbuf); ClassWriter cw = new SafeClassWriter(cr, loader, ClassWriter.COMPUTE_FRAMES); - ClassVisitor cv = new RefEqualityClassAdapter(new CheckClassAdapter(cw, true), cname); + ClassVisitor cv = new RefEqualityClassAdapter(Util.withCheckClassAdapter(cw, cname), cname); try { cr.accept(cv, ClassReader.SKIP_FRAMES); if (Config.instance().isUseCheckClassAdapter() && Util.useCheckClassAdapterForClass(cname)) { @@ -81,6 +81,6 @@ public byte[] transform(ClassLoader loader, String cname, Class classBeingRed Transformer.addInstrumentedClass(cname, InternalTransformerType.INSTRUCTION); if (getPrintBox().isContentPresent()) logger.debug(getPrintBox().toString()); - return cbuf; + return null; } } \ No newline at end of file diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/springendpoint/SpringEndpointTransformer.java b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/springendpoint/SpringEndpointTransformer.java index 8113e538..c8c363c7 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/springendpoint/SpringEndpointTransformer.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/springendpoint/SpringEndpointTransformer.java @@ -87,7 +87,7 @@ public byte[] transform( byte[] cbuf) { if (classBeingRedefined != null || cname == null || !Util.shouldInstrument(cname)) { - return cbuf; + return null; } getPrintBox().addMsg("Class: " + cname); @@ -108,7 +108,7 @@ public byte[] transform( } } if (!restController) { - return cbuf; + return null; } @@ -163,7 +163,7 @@ public byte[] transform( Transformer.addInstrumentedClass(cname, InternalTransformerType.SPRING_ENDPOINT); if (getPrintBox().isContentPresent()) logger.debug(getPrintBox().toString()); - return cbuf; + return null; } public static void addInstrumentedEndpoint(String name) { diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/springexception/SpringExceptionTransformer.java b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/springexception/SpringExceptionTransformer.java index 32e0cd92..e6e1c78b 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/springexception/SpringExceptionTransformer.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/springexception/SpringExceptionTransformer.java @@ -85,7 +85,7 @@ public byte[] transform( // System.out.println("!!!!!!!!!!!!! " + cname); if (classBeingRedefined != null || cname == null || !shouldInstrument(cname)) { // TODO only temporary - return cbuf; + return null; } getPrintBox().addMsg("Class: " + cname); @@ -101,7 +101,7 @@ public byte[] transform( // } // } // if (!restController) { - // return cbuf; + // return null; // } ClassWriter cw = new ClassWriter(cr, 0); // For some reason changing this to 0 from // ClassWriter.COMPUTE_FRAMES helps?? @@ -126,6 +126,6 @@ public byte[] transform( Transformer.addInstrumentedClass(cname, InternalTransformerType.SPRING_EXCEPTION); if (getPrintBox().isContentPresent()) logger.debug(getPrintBox().toString()); - return cbuf; + return null; } } diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/svcomp/SVCompClassAdapter.java b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/svcomp/SVCompClassAdapter.java index 8ed69119..798cac83 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/svcomp/SVCompClassAdapter.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/svcomp/SVCompClassAdapter.java @@ -1,6 +1,8 @@ package de.uzl.its.swat.instrument.svcomp; import de.uzl.its.swat.common.Util; +import java.util.HashMap; +import java.util.Map; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; @@ -15,6 +17,15 @@ public class SVCompClassAdapter extends ClassVisitor { private final String cname; + /** + * Number of nondet calls seen so far per source line of this class. Shared by all method + * adapters so that the witness can tell apart several nondet calls on one line. + */ + private final Map nondetCallsPerLine = new HashMap<>(); + + /** The source file name of the class (SourceFile attribute), empty if unknown. */ + private String sourceFile = ""; + /** * Constructor that calls the super from the default ClassVisitor * @@ -25,6 +36,14 @@ public SVCompClassAdapter(ClassVisitor cv, String cname) { this.cname = cname; } + @Override + public void visitSource(String source, String debug) { + if (source != null) { + sourceFile = source; + } + super.visitSource(source, debug); + } + /** * Visits a method of the class. This method must return a new MethodVisitor instance (or null) * each time it is called, i.e., it should not return a previously returned visitor. Addition: @@ -55,7 +74,8 @@ public MethodVisitor visitMethod( if (!cname.equals("org/sosy_lab/sv_benchmarks/Verifier")) { SVCompTransformer.getPrintBox().addMsg("Method: " + name); - return new SVCompMethodAdapter(access, mv, cname, name, desc); + return new SVCompMethodAdapter( + access, mv, cname, name, desc, sourceFile, nondetCallsPerLine); } return mv; } diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/svcomp/SVCompMethodAdapter.java b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/svcomp/SVCompMethodAdapter.java index df2a3877..82db4adf 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/svcomp/SVCompMethodAdapter.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/svcomp/SVCompMethodAdapter.java @@ -3,6 +3,8 @@ import de.uzl.its.swat.instrument.InternalTransformerType; import de.uzl.its.swat.instrument.Intrinsics; import de.uzl.its.swat.instrument.Transformer; +import java.util.Map; +import java.util.Set; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; @@ -12,10 +14,18 @@ /** A visitor to visit a Java method */ public class SVCompMethodAdapter extends LocalVariablesSorter { + private static final String SYSTEM_EXIT = "de/uzl/its/swat/instrument/svcomp/SystemExit"; + private final String identifier; private final String cname; private final String name; private final String desc; + private final String sourceFile; + private final Map nondetCallsPerLine; + + /** Return type descriptors of the nondet methods whose values are recorded for the witness. */ + private static final Set WITNESS_VALUE_TYPES = + Set.of("Z", "B", "S", "C", "I", "J", "F", "D", "Ljava/lang/String;"); int lineNumber = 0; /** @@ -24,14 +34,25 @@ public class SVCompMethodAdapter extends LocalVariablesSorter { * @param mv Parent MethodVisitor * @param name The method name * @param desc A string description of the parameters of the method + * @param sourceFile The source file name of the class (empty if unknown) + * @param nondetCallsPerLine Per-class counter of nondet calls per source line (shared between + * the methods of the class) */ public SVCompMethodAdapter( - int access, MethodVisitor mv, String cname, String name, String desc) { + int access, + MethodVisitor mv, + String cname, + String name, + String desc, + String sourceFile, + Map nondetCallsPerLine) { super(Opcodes.ASM9, access, desc, mv); identifier = cname + "/" + name; this.desc = desc; this.cname = cname; this.name = name; + this.sourceFile = sourceFile; + this.nondetCallsPerLine = nondetCallsPerLine; } @Override @@ -49,7 +70,27 @@ public void visitMethodInsn( final boolean isInterface) { // The weird concatenation is needed to avoid the relocation from the ShadowJar plugin to move this owner String targetOwner = "org/".concat("sosy_lab/sv_benchmarks/Verifier"); - if (name.startsWith("nondet") && owner.equals(targetOwner)) { + String objectFactory = "org/".concat("sosy_lab/sv_benchmarks/ObjectFactory"); + if (name.equals("nondetObject") + && owner.equals(targetOwner) + && descriptor.equals("(Ljava/lang/Class;L" + objectFactory + ";)Ljava/lang/Object;")) { + // nondetObject(type, factory) is defined as factory.createObject(); the factory makes its + // nondeterministic choices with the primitive nondet methods, which are redirected when + // the factory class is instrumented. Calling the factory from here (instead of from the + // uninstrumented benchmark Verifier) keeps the call and the returned object tracked, and + // every object the factory can return is covered by exploring its nondet values. + // The object itself is not recorded for the witness (the factory's nondet values are). + Transformer.addInstrumentedClass(cname, InternalTransformerType.SV_COMP); + SVCompTransformer.getPrintBox().setContentPresent(true); + SVCompTransformer.getPrintBox() + .addMsg(" => Found " + name + " in line " + lineNumber); + SVCompTransformer.getPrintBox().addMsg(" => Calling the factory directly"); + // Stack: type, factory -> factory + mv.visitInsn(Opcodes.SWAP); + mv.visitInsn(Opcodes.POP); + mv.visitMethodInsn( + Opcodes.INVOKEINTERFACE, objectFactory, "createObject", "()Ljava/lang/Object;", true); + } else if (name.startsWith("nondet") && owner.equals(targetOwner)) { Transformer.addInstrumentedClass(cname, InternalTransformerType.SV_COMP); String newOwner = "de/uzl/its/swat/instrument/svcomp/Verifier"; // Add one long argument into the desc (will be the only argument) old : ()X where x is @@ -58,28 +99,39 @@ public void visitMethodInsn( String retType = descriptor.split("\\)")[1]; long nextId = Verifier.getNextId(); + // Index of this call among the nondet calls on the same source line (bytecode order + // follows the left-to-right evaluation order); lets the witness locate the call. + int callIndex = nondetCallsPerLine.merge(lineNumber, 1, Integer::sum) - 1; SVCompTransformer.getPrintBox().setContentPresent(true); SVCompTransformer.getPrintBox() .addMsg(" => Found " + name + " in line " + lineNumber); SVCompTransformer.getPrintBox().addMsg(" => Assigning ID: " + nextId); mv.visitLdcInsn(nextId); mv.visitMethodInsn(opcode, newOwner, name, newDescriptor, isInterface); - if (retType.equals("D") || retType.equals("J")) { - visitInsn(Opcodes.DUP2); - } else { - visitInsn(Opcodes.DUP); + if (WITNESS_VALUE_TYPES.contains(retType)) { + // Record the returned value with the call location for the witness (trace data) + if (retType.equals("D") || retType.equals("J")) { + visitInsn(Opcodes.DUP2); + } else { + visitInsn(Opcodes.DUP); + } + mv.visitLdcInsn(name); + mv.visitLdcInsn(cname); + mv.visitLdcInsn(this.name + this.desc); + mv.visitLdcInsn(sourceFile); + mv.visitLdcInsn(lineNumber); + mv.visitLdcInsn(callIndex); + mv.visitMethodInsn( + Opcodes.INVOKESTATIC, + "de/uzl/its/swat/witness/Witness", + "recordValue", + "(" + + retType + + "Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;II)V", + false); + SVCompTransformer.getPrintBox().addMsg(" => Adding Witness recording"); } - mv.visitLdcInsn(lineNumber); - mv.visitLdcInsn(cname); - mv.visitLdcInsn(this.name + this.desc); - mv.visitMethodInsn( - Opcodes.INVOKESTATIC, - "de/uzl/its/swat/witness/Witness", - "recordValue", - "(" + descriptor.substring(2) + "ILjava/lang/String;Ljava/lang/String;)V", - false); mv.visitLdcInsn(nextId); - SVCompTransformer.getPrintBox().addMsg(" => Adding Witness recording"); /* if (descriptor.equals("()D") || descriptor.equals("()J")) { visitInsn(Opcodes.DUP2); @@ -98,6 +150,13 @@ public void visitMethodInsn( } else if (name.equals("assume")) { String newOwner = "de/uzl/its/swat/instrument/svcomp/Verifier"; mv.visitMethodInsn(opcode, newOwner, name, descriptor, isInterface); + } else if (owner.equals("java/lang/System") && name.equals("exit") && descriptor.equals("(I)V")) { + // Send the trace before the program ends (see SystemExit) + mv.visitMethodInsn(Opcodes.INVOKESTATIC, SYSTEM_EXIT, "exit", descriptor, false); + } else if (owner.equals("java/lang/Runtime") && (name.equals("exit") || name.equals("halt")) + && descriptor.equals("(I)V")) { + String newName = name.equals("exit") ? "runtimeExit" : "runtimeHalt"; + mv.visitMethodInsn(Opcodes.INVOKESTATIC, SYSTEM_EXIT, newName, "(Ljava/lang/Runtime;I)V", false); } else if (owner.equals("java/net/Socket")) { String newOwner = "de/uzl/its/swat/instrument/svcomp/Socket"; mv.visitMethodInsn(opcode, newOwner, name, descriptor, isInterface); @@ -106,6 +165,21 @@ public void visitMethodInsn( } } + @Override + public void visitInsn(int opcode) { + if (opcode == Opcodes.ATHROW) { + // An assertion fails where its AssertionError is thrown (see Witness.recordThrow) + mv.visitInsn(Opcodes.DUP); + mv.visitMethodInsn( + Opcodes.INVOKESTATIC, + "de/uzl/its/swat/witness/Witness", + "recordThrow", + "(Ljava/lang/Throwable;)V", + false); + } + super.visitInsn(opcode); + } + @Override public void visitTypeInsn(int opcode, String type) { // Replace the type for `new java.net.Socket` with the mock type diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/svcomp/SVCompTransformer.java b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/svcomp/SVCompTransformer.java index c7392422..39335774 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/svcomp/SVCompTransformer.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/svcomp/SVCompTransformer.java @@ -66,7 +66,7 @@ public byte[] transform( ProtectionDomain d, byte[] cbuf) { - if (classBeingRedefined != null || cname == null || !Util.shouldInstrument(cname)) return cbuf; + if (classBeingRedefined != null || cname == null || !Util.shouldInstrument(cname)) return null; getPrintBox().addMsg("Class: " + cname); getPrintBox().setContentPresent(true); try { @@ -74,7 +74,7 @@ public byte[] transform( ClassWriter cw = new SafeClassWriter( cr, loader, ClassWriter.COMPUTE_FRAMES); - ClassVisitor cv = new SVCompClassAdapter(new CheckClassAdapter(cw, true), cname); + ClassVisitor cv = new SVCompClassAdapter(Util.withCheckClassAdapter(cw, cname), cname); cr.accept(cv, ClassReader.SKIP_FRAMES); if (Config.instance().isUseCheckClassAdapter() && Util.useCheckClassAdapterForClass(cname)) { @@ -94,6 +94,6 @@ public byte[] transform( Transformer.addInstrumentedClass(cname, InternalTransformerType.SV_COMP); if (getPrintBox().isContentPresent()) logger.info(getPrintBox().toString()); - return cbuf; + return null; } } diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/svcomp/SystemExit.java b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/svcomp/SystemExit.java new file mode 100644 index 00000000..ab2dd813 --- /dev/null +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/svcomp/SystemExit.java @@ -0,0 +1,38 @@ +package de.uzl.its.swat.instrument.svcomp; + +import de.uzl.its.swat.instrument.Intrinsics; + +/** + * Replaces {@code System.exit}, {@code Runtime.exit} and {@code Runtime.halt} in instrumented + * code. A program that ends this way never returns from main, so without this the trace of the run + * would never be sent, and the explorer would see a run without any branches (and report SAFE). + * Like {@link Verifier#assume}, the trace is sent first, then the program ends as it asked to. + * + *

This is the primary exit path. It runs on the target thread and terminates the execution + * like the end of main does (incl. the flush of the last instruction), and it is the only path for + * {@code Runtime.halt}, which skips shutdown hooks. {@link Intrinsics#terminateOnShutdown} (the + * shutdown hook of the agent) remains as the fallback for exits that do not pass through an + * instrumented call site, e.g. {@code System.exit} called from uninstrumented library code or by + * reflection; after this path it finds no active execution and does nothing. + * + *

An assertion that fails before the exit, e.g. in the {@code try} of a {@code finally} that + * exits, has already been reported when its error was thrown ({@link + * de.uzl.its.swat.witness.Witness#recordThrow}). + */ +public class SystemExit { + + public static void exit(int status) { + Intrinsics.terminate(); + System.exit(status); + } + + public static void runtimeExit(Runtime runtime, int status) { + Intrinsics.terminate(); + runtime.exit(status); + } + + public static void runtimeHalt(Runtime runtime, int status) { + Intrinsics.terminate(); + runtime.halt(status); + } +} diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/svcomp/Verifier.java b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/svcomp/Verifier.java index 56c7ac4f..798776e9 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/svcomp/Verifier.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/svcomp/Verifier.java @@ -1,5 +1,6 @@ package de.uzl.its.swat.instrument.svcomp; +import de.uzl.its.swat.common.Profile; import ch.qos.logback.classic.Logger; import de.uzl.its.swat.common.ErrorHandler; import de.uzl.its.swat.common.PrintBox; @@ -69,6 +70,7 @@ public static void assume(boolean condition) { .toString()); if (!condition) { Intrinsics.terminate(); + Profile.dump("assume.halt"); Runtime.getRuntime().halt(0); } } @@ -300,8 +302,10 @@ public static long nondetLong(long id) { printBox.addMsg("Requested ID: " + newId); if (inputs.containsKey(prefix + newId) && !inputs.get(prefix + newId).isEmpty()) { printBox.addMsg("Predetermined value available!"); - // Parse as unsigned to handle values from SMT solver bitvector representation - long l = Long.parseUnsignedLong(inputs.get(prefix + newId).remove()); + // The explorer sends solver values signed (negative longs with a minus sign); an + // unsigned form is accepted too. + String encoded = inputs.get(prefix + newId).remove(); + long l = encoded.startsWith("-") ? Long.parseLong(encoded) : Long.parseUnsignedLong(encoded); printBox.addMsg("Returning: " + l); logger.info(printBox.toString()); return l; @@ -399,6 +403,28 @@ public static double nondetDouble(long id) { } } + /** + * Undoes the explorer's escaping of String inputs (escape_java_string in SVCompDriver): a + * backslash comes as two, and any character outside printable ASCII as backslash-u and four + * hex digits. + */ + static String unescape(String s) { + StringBuilder sb = new StringBuilder(s.length()); + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + if (c == '\\' && i + 1 < s.length() && s.charAt(i + 1) == '\\') { + sb.append('\\'); + i++; + } else if (c == '\\' && i + 5 < s.length() && s.charAt(i + 1) == 'u') { + sb.append((char) Integer.parseInt(s.substring(i + 2, i + 6), 16)); + i += 5; + } else { + sb.append(c); + } + } + return sb.toString(); + } + public static String nondetString(long id) { try{ PrintBox printBox = new PrintBox(60, "SV-Comp Verifier: nondetString"); @@ -414,7 +440,9 @@ public static String nondetString(long id) { printBox.addMsg("Requested ID: " + newId); if (inputs.containsKey(prefix + newId) && !inputs.get(prefix + newId).isEmpty()) { printBox.addMsg("Predetermined value available!"); - var val = inputs.get(prefix + newId).peek(); + // A fresh object: SWAT tracks Strings by identity, and unescape("") is the interned + // "", so two empty inputs would otherwise be one object and share one variable. + var val = new String(unescape(inputs.get(prefix + newId).peek())); printBox.addMsg("Returning: " + val); logger.info(printBox.toString()); return val; diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/symbolicwrapper/StaticInitializerEntryMethodAdapter.java b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/symbolicwrapper/StaticInitializerEntryMethodAdapter.java new file mode 100644 index 00000000..a9d237ee --- /dev/null +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/symbolicwrapper/StaticInitializerEntryMethodAdapter.java @@ -0,0 +1,41 @@ +package de.uzl.its.swat.instrument.symbolicwrapper; + +import de.uzl.its.swat.instrument.Intrinsics; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; + +/** + * Starts the symbolic execution at the beginning of the static initializer of a class that + * declares the entry method. The JVM initialises that class before it calls the entry method, so + * without this, inputs read by the initializer would run without a thread context. + */ +public class StaticInitializerEntryMethodAdapter extends MethodVisitor { + + private final String endpointID; + + /** + * @param mv Parent MethodVisitor + * @param endpointID The name of the endpoint whose class is initialised, as passed to + * Intrinsics.init by the endpoint itself + */ + public StaticInitializerEntryMethodAdapter(MethodVisitor mv, String endpointID) { + super(Opcodes.ASM9, mv); + this.endpointID = endpointID; + } + + @Override + public void visitCode() { + super.visitCode(); + SymbolicWrapperTransformer.getPrintBox() + .addMsg(" => Adding initializer: Intrinsics.initBeforeEntry(endpointID)"); + SymbolicWrapperTransformer.getPrintBox().setContentPresent(true); + visitLdcInsn(endpointID); + visitMethodInsn( + Opcodes.INVOKESTATIC, + Type.getInternalName(Intrinsics.class), + "initBeforeEntry", + "(Ljava/lang/String;)V", + false); + } +} diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/symbolicwrapper/SurroundingTryCatchMethodAdapter.java b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/symbolicwrapper/SurroundingTryCatchMethodAdapter.java index bc59a3f2..5d74772a 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/symbolicwrapper/SurroundingTryCatchMethodAdapter.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/symbolicwrapper/SurroundingTryCatchMethodAdapter.java @@ -67,8 +67,13 @@ private void addEndOfSurrounding() { visitInsn(Opcodes.DUP); + // Prints the stack trace and records the violating location for the witness visitMethodInsn( - Opcodes.INVOKEVIRTUAL, "java/lang/Throwable", "printStackTrace", "()V", false); + Opcodes.INVOKESTATIC, + "de/uzl/its/swat/witness/Witness", + "recordTarget", + "(Ljava/lang/Throwable;)V", + false); // Duplicate exception for instanceof check visitInsn(Opcodes.DUP); diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/symbolicwrapper/SymbolicWrapperClassAdapter.java b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/symbolicwrapper/SymbolicWrapperClassAdapter.java index 2540fbd4..61bebd14 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/symbolicwrapper/SymbolicWrapperClassAdapter.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/symbolicwrapper/SymbolicWrapperClassAdapter.java @@ -5,6 +5,7 @@ import de.uzl.its.swat.config.Config; import de.uzl.its.swat.instrument.springendpoint.SpringEndpointTransformer; import java.util.regex.Pattern; +import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; @@ -18,16 +19,48 @@ */ public class SymbolicWrapperClassAdapter extends ClassVisitor { + static final String ENTRY_METHOD_NAME = "main"; + static final String ENTRY_METHOD_DESC = "([Ljava/lang/String;)V"; + private String cname; private final Config config = Config.instance(); + private final boolean declaresEntryMethod; + /** * Constructor that calls the super from the default ClassVisitor * * @param cv Parent ClassVisitor + * @param declaresEntryMethod whether the class declares a static main(String[]) method */ - public SymbolicWrapperClassAdapter(ClassVisitor cv, String cname) { + public SymbolicWrapperClassAdapter(ClassVisitor cv, String cname, boolean declaresEntryMethod) { super(Opcodes.ASM9, cv); this.cname = cname; + this.declaresEntryMethod = declaresEntryMethod; + } + + /** + * Checks whether a class declares a static main(String[]) method. + * + * @param cr The reader of the class + * @return true if the class declares the method + */ + public static boolean declaresEntryMethod(ClassReader cr) { + boolean[] found = {false}; + cr.accept( + new ClassVisitor(Opcodes.ASM9) { + @Override + public MethodVisitor visitMethod( + int access, String name, String desc, String signature, String[] exceptions) { + if ((access & Opcodes.ACC_STATIC) != 0 + && name.equals(ENTRY_METHOD_NAME) + && desc.equals(ENTRY_METHOD_DESC)) { + found[0] = true; + } + return null; + } + }, + ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); + return found[0]; } /** @@ -62,6 +95,11 @@ public MethodVisitor visitMethod( if (mv != null) { switch (config.getInstrumentationTransformer()) { case SV_COMP -> { + if (name.equals("") && declaresEntryMethod) { + SymbolicWrapperTransformer.getPrintBox().addMsg("Method: " + name); + return new StaticInitializerEntryMethodAdapter( + mv, cname + "/" + ENTRY_METHOD_NAME + ENTRY_METHOD_DESC); + } if (!name.equals("main")) { return mv; } diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/symbolicwrapper/SymbolicWrapperTransformer.java b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/symbolicwrapper/SymbolicWrapperTransformer.java index 05eeed58..ab34fcea 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/symbolicwrapper/SymbolicWrapperTransformer.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/instrument/symbolicwrapper/SymbolicWrapperTransformer.java @@ -10,6 +10,7 @@ import de.uzl.its.swat.instrument.InternalTransformerType; import de.uzl.its.swat.instrument.SafeClassWriter; import de.uzl.its.swat.instrument.Transformer; +import de.uzl.its.swat.instrument.TransformerType; import de.uzl.its.swat.instrument.springendpoint.SpringEndpointTransformer; import java.io.PrintWriter; import java.io.StringWriter; @@ -70,13 +71,13 @@ public byte[] transform( throws IllegalClassFormatException { try { if (classBeingRedefined != null || cname == null || !Util.shouldInstrument(cname)) { - return cbuf; + return null; } switch (config.getInstrumentationTransformer()) { case SPRING_ENDPOINT -> { if (!SpringEndpointTransformer.getInstrumentedClasses().contains(cname)) { - return cbuf; + return null; } } case WEB_SERVLET -> new ErrorHandler() @@ -88,18 +89,21 @@ public byte[] transform( case ANNOTATION -> { if (!cname.equals(config.getInstrumentationAnnotationSymbolicClassName()) && config.getInstrumentationAnnotationSymbolicClassName() != null) - return cbuf; + return null; } case PARAMETER -> { if (!Util.isSymbolicClass(cname)) - return cbuf; + return null; } } getPrintBox().addMsg("Class: " + cname); ClassReader cr = new ClassReader(cbuf); ClassWriter cw = new SafeClassWriter(cr, loader, ClassWriter.COMPUTE_FRAMES); - ClassVisitor cv = new SymbolicWrapperClassAdapter(cw, cname); + boolean declaresEntryMethod = + config.getInstrumentationTransformer() == TransformerType.SV_COMP + && SymbolicWrapperClassAdapter.declaresEntryMethod(cr); + ClassVisitor cv = new SymbolicWrapperClassAdapter(cw, cname, declaresEntryMethod); cr.accept(cv, 0); if (Config.instance().isUseCheckClassAdapter() && Util.useCheckClassAdapterForClass(cname)) { @@ -117,6 +121,6 @@ public byte[] transform( new ErrorHandler().handleException("Error while instrumenting class: " + cname, t); } - return cbuf; + return null; } } diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/metadata/ClassDepot.java b/symbolic-executor/src/main/java/de/uzl/its/swat/metadata/ClassDepot.java index ee0bee37..07708f1d 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/metadata/ClassDepot.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/metadata/ClassDepot.java @@ -82,12 +82,16 @@ public synchronized void registerTypeInfoForClass(String className, List classToInterfaces.put(normalized, interfaces); } + /** Returns the registered parents of a class; the list is shared and must not be modified. */ public synchronized List getParentsForClass(String className) { - return classToParents.getOrDefault(Util.formatClassName(className), Collections.emptyList()); + return Collections.unmodifiableList( + classToParents.getOrDefault(Util.formatClassName(className), Collections.emptyList())); } + /** Returns the registered interfaces of a class; the list is shared and must not be modified. */ public synchronized List getInterfacesForClass(String className) { - return classToInterfaces.getOrDefault(Util.formatClassName(className), Collections.emptyList()); + return Collections.unmodifiableList( + classToInterfaces.getOrDefault(Util.formatClassName(className), Collections.emptyList())); } /** diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/request/Request.java b/symbolic-executor/src/main/java/de/uzl/its/swat/request/Request.java index e778ee01..359b8ee2 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/request/Request.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/request/Request.java @@ -2,17 +2,24 @@ import ch.qos.logback.classic.Logger; import de.uzl.its.swat.common.logging.GlobalLogger; -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.net.http.HttpResponse.BodyHandlers; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; import java.nio.charset.StandardCharsets; -import java.util.concurrent.CompletableFuture; public abstract class Request { private static final Logger logger = GlobalLogger.getSymbolicExecutionLogger(); + /** + * POSTs the JSON body to the explorer and waits for its response. + * + *

Uses {@link HttpURLConnection}, not {@code java.net.http.HttpClient}: every executor run + * sends one request and then exits, and creating an HttpClient (its SSL context with the CA + * certificates, selector thread, executor and some 200 classes) cost about a fifth of a short + * run's CPU time, although the request is plain HTTP to localhost. + */ public static void send( String host, int port, @@ -20,37 +27,37 @@ public static void send( int endpointID, int traceID, String requestBodyJson) { - HttpClient httpClient = HttpClient.newHttpClient(); - String url = String.format( "http://%s:%d/%s?endpointID=%d&traceID=%d", host, port, path, endpointID, traceID); logger.info("Sending request to: {}", url); - HttpRequest request = - HttpRequest.newBuilder() - .uri(URI.create(url)) - .header("Content-Type", "application/json") - .version(HttpClient.Version.HTTP_1_1) - .POST( - HttpRequest.BodyPublishers.ofString( - requestBodyJson, StandardCharsets.UTF_8)) - .build(); logger.trace("Request body: {}", requestBodyJson); - CompletableFuture> responseFuture = - httpClient.sendAsync(request, BodyHandlers.ofString()); - - responseFuture - .thenAccept( - response -> - System.out.println( - "Response status code: " + response.statusCode())) - .exceptionally( - e -> { - System.out.println("Request failed: " + e.getMessage()); - return null; - }); - // Block and wait for the future to complete - responseFuture.join(); + byte[] body = requestBodyJson.getBytes(StandardCharsets.UTF_8); + HttpURLConnection connection = null; + try { + connection = (HttpURLConnection) new URL(url).openConnection(); + connection.setRequestMethod("POST"); + connection.setRequestProperty("Content-Type", "application/json"); + connection.setDoOutput(true); + connection.setFixedLengthStreamingMode(body.length); + try (OutputStream out = connection.getOutputStream()) { + out.write(body); + } + int status = connection.getResponseCode(); + // Read the response to its end (as the previous HttpClient implementation did) + try (InputStream in = status >= 400 ? connection.getErrorStream() : connection.getInputStream()) { + if (in != null) { + in.readAllBytes(); + } + } + System.out.println("Response status code: " + status); + } catch (IOException e) { + System.out.println("Request failed: " + e); + } finally { + if (connection != null) { + connection.disconnect(); + } + } } } diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/SymbolicInstructionVisitor.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/SymbolicInstructionVisitor.java index 7ab9a023..a4b4322b 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/SymbolicInstructionVisitor.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/SymbolicInstructionVisitor.java @@ -6,6 +6,7 @@ import ch.qos.logback.classic.Logger; import de.uzl.its.swat.common.ErrorHandler; +import de.uzl.its.swat.common.Util; import de.uzl.its.swat.common.exceptions.*; import de.uzl.its.swat.common.logging.GlobalLogger; import de.uzl.its.swat.coverage.BranchCoverage; @@ -14,12 +15,17 @@ import de.uzl.its.swat.metadata.ClassDepot; import de.uzl.its.swat.metadata.ClassDepotRuntime; import de.uzl.its.swat.symbolic.instruction.*; +import de.uzl.its.swat.symbolic.loopstate.LoopStateMatcher; import de.uzl.its.swat.symbolic.invoke.InvocationHandler; +import de.uzl.its.swat.symbolic.invoke.java.lang.NumberParsing; +import de.uzl.its.swat.symbolic.invoke.java.lang.StringCallBranches; import de.uzl.its.swat.symbolic.processor.InstructionProcessor; import de.uzl.its.swat.symbolic.processor.SymbolicInstructionProcessor; import de.uzl.its.swat.symbolic.shadow.Frame; import de.uzl.its.swat.symbolic.shadow.ShadowContext; import de.uzl.its.swat.symbolic.trace.SymbolicTraceHandler; +import de.uzl.its.swat.symbolic.value.ContainerPlaceHolder; +import de.uzl.its.swat.symbolic.invoke.java.util.CollectionInvocation; import de.uzl.its.swat.symbolic.value.PlaceHolder; import de.uzl.its.swat.symbolic.value.Value; import de.uzl.its.swat.symbolic.value.ValueFactory; @@ -31,6 +37,7 @@ import de.uzl.its.swat.symbolic.value.reference.StringBuilderValue; import de.uzl.its.swat.symbolic.value.reference.array.*; import de.uzl.its.swat.symbolic.value.reference.lang.*; +import de.uzl.its.swat.common.logging.records.InvocationEntry; import de.uzl.its.swat.thread.ThreadHandler; import java.lang.reflect.Modifier; @@ -41,12 +48,17 @@ public class SymbolicInstructionVisitor implements IVisitor { // The stack of stack frames (method stacks) - @Getter private final ShadowContext stack; //Todo rename? + @Getter + private final ShadowContext stack; //Todo rename? + + /** Applies loop summaries (see symbolic/summary). */ + private final de.uzl.its.swat.symbolic.summary.Merger merger; // Counter for the number of times an instruction ID (IID) was seen, used for creating trackable // unique ids in loops etc. private final HashMap iidCounter = new HashMap<>(); // The handler for the symbolic trace that is sent to the symbolic explorer - @Getter private final SymbolicTraceHandler symbolicTraceHandler; + @Getter + private final SymbolicTraceHandler symbolicTraceHandler; private static final Logger logger = GlobalLogger.getSymbolicExecutionLogger(); @@ -54,6 +66,7 @@ public class SymbolicInstructionVisitor implements IVisitor { public SymbolicInstructionVisitor() throws NoThreadContextException { stack = new ShadowContext(); + merger = new de.uzl.its.swat.symbolic.summary.Merger(stack); symbolicTraceHandler = ThreadHandler.getSymbolicTraceHandler(currentThread().getId()); } @@ -84,11 +97,37 @@ public Instruction getNextInst() { * @param inst The (next) instruction/ special marker after the instruction that could have * caused an exception */ + /** + * Marks, in the trace, an exception the static pre-analysis graph does not model precisely: one + * SWAT reports no branch for (a null dereference, a cast, a library call that throws, an + * Error). The explorer stops trusting the graph for the rest of such a path, because the graph + * cannot say where the execution continues. + */ + public static final String UNMODELLED_EXCEPTION = "UNMODELLED_EXCEPTION"; + + /** A frame of an uninstrumented (library) method, not one of SWAT's own helpers. */ + private static boolean isLibraryFrame(Frame frame) { + String cname = frame.getClassName(); + return cname != null && !cname.startsWith("de/uzl/its/swat/") && !cname.equals("LambdaFrame") + && !cname.equals(ShadowContext.getINITIAL_FRAME_NAME()) + && !Util.shouldInstrument(Util.formatClassName(cname)); + } + + private void recordUnmodelledException(long iid) { + symbolicTraceHandler.addSpecialElement(determineIid(iid), UNMODELLED_EXCEPTION); + } + private void checkAndSetException(Instruction inst) throws NoThreadContextException { symbolicTraceHandler.addSpecialElement(determineIid(inst.iid), inst.getClass().getName()); + boolean arrayAccess = untrackedAccessModelled; + untrackedAccessModelled = false; if (!((getNextInst() instanceof SPECIAL) && ((SPECIAL) getNextInst()).i == 0) && !(getNextInst() instanceof CLINIT)) { logger.info("Registered exception: {}", inst.getClass().getName()); + // An explicit throw and a failed access to a non-null array are modelled; the rest not. + if (!(inst instanceof ATHROW) && !arrayAccess) { + recordUnmodelledException(inst.iid); + } stack.clearOperandStack(); stack.pushOperand(PlaceHolder.instance); // ToDo: add more specific placeholder } @@ -118,6 +157,31 @@ private boolean isBranchTaken() { return true; // false; } + /** + * Records the bounds check of an access to an array SWAT does not track, e.g. one returned by an + * uninstrumented call. The static pre-analysis graph has a guard branch for every array access, + * and the explorer walks it in step with the trace by position, so the check has to be in the + * trace even without a symbolic array: as a concrete-only branch, taken iff the access completed + * normally. (Any exception it throws counts as out of bounds, which is all the graph models.) + * + * @param inst The array load or store instruction + */ + /** Whether a failed access to an untracked array is the one the graph models (see checkAndSetException). */ + private boolean untrackedAccessModelled = false; + + private void recordUntrackedBoundsCheck(Instruction inst, IntValue idx, ObjectValue array) throws NoThreadContextException, NotImplementedException { + // If it fails, the graph has it as the bounds guard's failure, an + // ArrayIndexOutOfBoundsException. That is right unless the array is null (a + // NullPointerException) or an AASTORE stores a value of the wrong type (an + // ArrayStoreException). + untrackedAccessModelled = array.getAddress() != ObjectValue.ADDRESS_NULL && !(inst instanceof AASTORE); + boolean completed = (getNextInst() instanceof SPECIAL special && special.i == 0) || getNextInst() instanceof CLINIT; + symbolicTraceHandler.addConcreteBranch(completed, determineIid(inst.iid)); + // Neither the bounds nor the element of an untracked array depend on the index here, so + // with a symbolic index other indices are never tried. + recordConcreteObjectIndex(idx); + } + public boolean checkArrayBounds(Value ref, IntValue idx, long iid) throws NoThreadContextException { SWATAssert.check(ref instanceof AbstractArrayValue || ref instanceof ObjectArrayValue, "Unknown array type!"); @@ -134,6 +198,7 @@ public boolean checkArrayBounds(Value ref, IntValue idx, long iid) throws return result; } } + /** * Loads a reference from an array and stores the reference on the symbolic stack ToDo (Nils): * Reference arrays are not symbolically tracked currently See: Where the length is symbolic (the steps would differ between executions on the same path) + * or too large to search, only the concrete element is tracked and precision loss recorded. + * + * @param arr The accessed array + * @param idx The index, within bounds + * @param iid The iid of the array instruction + */ + private void selectObjectIndex(ObjectArrayValue arr, IntValue idx, long iid) + throws NoThreadContextException, NotImplementedException { + if (!idx.isSymbolic()) { + return; + } + SolverContext context = ThreadHandler.getSolverContext(currentThread().getId()); + FormulaManager fmgr = context.getFormulaManager(); + try { + // e.g. an element of a primitive array that only ever held concrete values + if (fmgr.extractVariables(fmgr.simplify(idx.formula)).isEmpty()) { + return; + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + if (arr.size.isSymbolic() || arr.size.concrete > MAX_INDEX_CHOICE_LENGTH) { + logger.warn("Object array of symbolic or large length accessed at a symbolic index; only the concrete element is tracked"); + symbolicTraceHandler.recordSymbolicPrecisionLoss(); + return; + } + BitvectorFormulaManager bvmgr = fmgr.getBitvectorFormulaManager(); + long branchIid = determineIid(iid); + int lo = 0; + int hi = arr.size.concrete; + int step = 0; + while (hi - lo > 1) { + int mid = lo + (hi - lo) / 2; + boolean below = idx.concrete < mid; + BooleanFormula constraint = bvmgr.lessThan(idx.formula, bvmgr.makeBitvector(32, mid), true); + symbolicTraceHandler.addChoiceBranch(below, constraint, branchIid, "#idx" + step++); + if (below) { + hi = mid; + } else { + lo = mid; + } + } + } + /** * Stores a reference value into an array * * @param inst The ASTORE instruction */ - public void visitAASTORE(AASTORE inst) throws SymbolicInstructionException{ + public void visitAASTORE(AASTORE inst) throws SymbolicInstructionException { try { ObjectValue val = stack.popOperand().asObjectValue(); IntValue idx = stack.popOperand().asIntValue(); @@ -192,6 +327,7 @@ public void visitAASTORE(AASTORE inst) throws SymbolicInstructionException{ if (ref instanceof ObjectArrayValue arr) { boolean result = checkArrayBounds(arr, idx, inst.iid); if (result) { + selectObjectIndex(arr, idx, inst.iid); arr.storeElement(idx, val); } else { enforceException(); @@ -204,6 +340,23 @@ public void visitAASTORE(AASTORE inst) throws SymbolicInstructionException{ } else { enforceException(); } + } else if (ref instanceof ArrayArrayValue arr + && val instanceof ObjectArrayValue row + && "[[Ljava/lang/String;".equals(arr.desc)) { + // A String[] row created by ANEWARRAY stored into a MULTIANEWARRAY: the + // multi-dimensional array holds its rows as StringArrayValues, so it stores a copy. + // Writes through other references to the row do not reach the copy. + StringArrayValue copy = toStringArrayValue(row); + if (copy == null) { + recordUntrackedBoundsCheck(inst, idx, ref); + logger.warn("[AASTORE]: Unsupported row {} for multi-dimensional array", row); + } else if (checkArrayBounds(arr, idx, inst.iid)) { + arr.storeElement(idx, copy); + logger.warn("[AASTORE]: Storing a copy of row {} in a multi-dimensional array causes context loss", row); + symbolicTraceHandler.recordSymbolicContextLoss(); + } else { + enforceException(); + } } else if (ref instanceof StringArrayValue arr) { boolean result = checkArrayBounds(arr, idx, inst.iid); if (result) { @@ -212,6 +365,7 @@ public void visitAASTORE(AASTORE inst) throws SymbolicInstructionException{ enforceException(); } } else { + recordUntrackedBoundsCheck(inst, idx, ref); logger.warn("[AASTORE]: Unknown array type: {}", ref.getClass().getSimpleName()); } } catch (Throwable t) { @@ -219,13 +373,31 @@ public void visitAASTORE(AASTORE inst) throws SymbolicInstructionException{ } } + /** + * Copies a String[] row into a StringArrayValue. + * + * @param row The row + * @return The copy, or null if an element is not a String value (e.g. null) + */ + private StringArrayValue toStringArrayValue(ObjectArrayValue row) throws NoThreadContextException { + SolverContext context = ThreadHandler.getSolverContext(currentThread().getId()); + StringArrayValue copy = new StringArrayValue(context, row.size, row.getAddress()); + for (int i = 0; i < row.size.concrete; i++) { + if (!(row.getField(i) instanceof StringValue element)) { + return null; + } + copy.storeElement(new IntValue(context, i), element); + } + return copy; + } + /** * Pushes NULL onto the symbolic stack * * @param inst The ACONST_NULL instruction */ - public void visitACONST_NULL(ACONST_NULL inst) throws SymbolicInstructionException{ - try{ + public void visitACONST_NULL(ACONST_NULL inst) throws SymbolicInstructionException { + try { stack.pushOperand(ValueFactory.createNULLValue()); } catch (Throwable t) { throw new SymbolicInstructionException(inst, t); @@ -237,8 +409,8 @@ public void visitACONST_NULL(ACONST_NULL inst) throws SymbolicInstructionExcepti * * @param inst One on the ALOAD (ALOAD, ALOAD_0 - ALOAD_3) instructions */ - public void visitALOAD(ALOAD inst) throws SymbolicInstructionException{ - try{ + public void visitALOAD(ALOAD inst) throws SymbolicInstructionException { + try { stack.pushOperand(stack.getLocal(inst.var)); } catch (Throwable t) { throw new SymbolicInstructionException(inst, t); @@ -250,7 +422,7 @@ public void visitALOAD(ALOAD inst) throws SymbolicInstructionException{ * * @param inst The ANEWARRAY instruction. */ - public void visitANEWARRAY(ANEWARRAY inst) throws SymbolicInstructionException{ + public void visitANEWARRAY(ANEWARRAY inst) throws SymbolicInstructionException { try { IntValue size = stack.popOperand().asIntValue(); BooleanFormula constraint = size.checkPositive(); @@ -269,8 +441,8 @@ public void visitANEWARRAY(ANEWARRAY inst) throws SymbolicInstructionException{ * * @param inst The ARETURN instruction */ - public void visitARETURN(ARETURN inst) throws SymbolicInstructionException{ - try{ + public void visitARETURN(ARETURN inst) throws SymbolicInstructionException { + try { symbolicTraceHandler.addSpecialElement(determineIid(inst.iid), "ARETURN"); stack.setReturnValue(stack.popOperand()); // checkAndSetException(inst); @@ -287,7 +459,7 @@ public void visitARETURN(ARETURN inst) throws SymbolicInstructionException{ * @param inst The ARRAYLENGTH instruction */ public void visitARRAYLENGTH(ARRAYLENGTH inst) throws SymbolicInstructionException { - try{ + try { Value v = stack.popOperand(); if (v instanceof AbstractArrayValue arr) { stack.pushOperand(arr.size); @@ -297,7 +469,7 @@ public void visitARRAYLENGTH(ARRAYLENGTH inst) throws SymbolicInstructionExcepti // if (ref.getFields() != null) { // stack.pushOperand(ref.getNFields()); <---- This is currently removed to ensure no wrong value ends up on the stack see Issue #99 // } else { - stack.pushOperand(PlaceHolder.instance); + stack.pushOperand(PlaceHolder.instance); // } } else { @@ -317,8 +489,8 @@ public void visitARRAYLENGTH(ARRAYLENGTH inst) throws SymbolicInstructionExcepti * @param inst One of the ASTORE (ASTORE, ASTORE_0 - ASTORE_3) instructions. inst.var is the * locals index */ - public void visitASTORE(ASTORE inst) throws SymbolicInstructionException{ - try{ + public void visitASTORE(ASTORE inst) throws SymbolicInstructionException { + try { stack.setLocal(inst.var, stack.popOperand()); } catch (Throwable t) { throw new SymbolicInstructionException(inst, t); @@ -333,8 +505,8 @@ public void visitASTORE(ASTORE inst) throws SymbolicInstructionException{ * * @param inst The ATHROW instruction */ - public void visitATHROW(ATHROW inst) throws SymbolicInstructionException{ - try{ + public void visitATHROW(ATHROW inst) throws SymbolicInstructionException { + try { Value top = stack.peekOperand(); stack.clearOperandStack(); stack.pushOperand(top); @@ -349,7 +521,7 @@ public void visitATHROW(ATHROW inst) throws SymbolicInstructionException{ * * @param inst The BALOAD instruction. */ - public void visitBALOAD(BALOAD inst) throws SymbolicInstructionException{ + public void visitBALOAD(BALOAD inst) throws SymbolicInstructionException { try { IntValue idx = stack.popOperand().asIntValue(); ObjectValue arr = (ObjectValue) stack.popOperand(); @@ -360,14 +532,15 @@ public void visitBALOAD(BALOAD inst) throws SymbolicInstructionException{ } else { enforceException(); } - } else if(arr instanceof ByteArrayValue barr) { + } else if (arr instanceof ByteArrayValue barr) { boolean result = checkArrayBounds(barr, idx, inst.iid); if (result) { stack.pushOperand(barr.getElement(idx)); } else { enforceException(); } - } else { + } else { + recordUntrackedBoundsCheck(inst, idx, arr); logger.warn("[BALOAD]: Unknown array type"); stack.pushOperand( arr.getName() != null @@ -385,7 +558,7 @@ public void visitBALOAD(BALOAD inst) throws SymbolicInstructionException{ * * @param inst The BASTORE instruction. */ - public void visitBASTORE(BASTORE inst) throws SymbolicInstructionException{ + public void visitBASTORE(BASTORE inst) throws SymbolicInstructionException { try { Value val = stack.popOperand(); IntValue idx = stack.popOperand().asIntValue(); @@ -405,6 +578,7 @@ public void visitBASTORE(BASTORE inst) throws SymbolicInstructionException{ enforceException(); } } else { + recordUntrackedBoundsCheck(inst, idx, ref); logger.warn("[BASTORE]: Unknown array type"); } } catch (Throwable t) { @@ -417,9 +591,9 @@ public void visitBASTORE(BASTORE inst) throws SymbolicInstructionException{ * * @param inst The BIPUSH instruction */ - public void visitBIPUSH(BIPUSH inst) throws SymbolicInstructionException{ + public void visitBIPUSH(BIPUSH inst) throws SymbolicInstructionException { // ToDo (Nils): Some unforseen concequences because the byte is handled as an int? - try{ + try { stack.pushOperand(ValueFactory.createNumericalValue(ValueType.intValue, inst.value)); } catch (Throwable t) { throw new SymbolicInstructionException(inst, t); @@ -431,7 +605,7 @@ public void visitBIPUSH(BIPUSH inst) throws SymbolicInstructionException{ * * @param inst The CALOAD instruction. */ - public void visitCALOAD(CALOAD inst) throws SymbolicInstructionException{ + public void visitCALOAD(CALOAD inst) throws SymbolicInstructionException { try { IntValue idx = stack.popOperand().asIntValue(); ObjectValue arr = (ObjectValue) stack.popOperand(); @@ -443,6 +617,7 @@ public void visitCALOAD(CALOAD inst) throws SymbolicInstructionException{ enforceException(); } } else { + recordUntrackedBoundsCheck(inst, idx, arr); logger.warn("[CALOAD]: Unknown array type"); stack.pushOperand( arr.getName() != null @@ -460,7 +635,7 @@ public void visitCALOAD(CALOAD inst) throws SymbolicInstructionException{ * * @param inst The CASTORE instruction. */ - public void visitCASTORE(CASTORE inst) throws SymbolicInstructionException{ + public void visitCASTORE(CASTORE inst) throws SymbolicInstructionException { try { CharValue val = stack.popOperand().asCharValue(); IntValue idx = stack.popOperand().asIntValue(); @@ -473,6 +648,7 @@ public void visitCASTORE(CASTORE inst) throws SymbolicInstructionException{ enforceException(); } } else { + recordUntrackedBoundsCheck(inst, idx, ref); logger.warn("[CASTORE]: Unknown array type"); } } catch (Throwable t) { @@ -486,8 +662,8 @@ public void visitCASTORE(CASTORE inst) throws SymbolicInstructionException{ * * @param inst The CHECKCAST instruction */ - public void visitCHECKCAST(CHECKCAST inst) throws SymbolicInstructionException{ - try{ + public void visitCHECKCAST(CHECKCAST inst) throws SymbolicInstructionException { + try { checkAndSetException(inst); } catch (Throwable t) { throw new SymbolicInstructionException(inst, t); @@ -499,8 +675,8 @@ public void visitCHECKCAST(CHECKCAST inst) throws SymbolicInstructionException{ * * @param inst The D2F instruction */ - public void visitD2F(D2F inst) throws SymbolicInstructionException{ - try{ + public void visitD2F(D2F inst) throws SymbolicInstructionException { + try { DoubleValue d1 = (DoubleValue) stack.popWideOperand(); stack.pushOperand(d1.D2F()); } catch (Throwable t) { @@ -513,8 +689,8 @@ public void visitD2F(D2F inst) throws SymbolicInstructionException{ * * @param inst The D2I instruction */ - public void visitD2I(D2I inst) throws SymbolicInstructionException{ - try{ + public void visitD2I(D2I inst) throws SymbolicInstructionException { + try { DoubleValue d1 = (DoubleValue) stack.popWideOperand(); stack.pushOperand(d1.D2I()); } catch (Throwable t) { @@ -527,8 +703,8 @@ public void visitD2I(D2I inst) throws SymbolicInstructionException{ * * @param inst The D2L instruction */ - public void visitD2L(D2L inst) throws SymbolicInstructionException{ - try{ + public void visitD2L(D2L inst) throws SymbolicInstructionException { + try { DoubleValue d1 = (DoubleValue) stack.popWideOperand(); stack.pushWideOperand(d1.D2L()); } catch (Throwable t) { @@ -541,8 +717,8 @@ public void visitD2L(D2L inst) throws SymbolicInstructionException{ * * @param inst The DADD instruction */ - public void visitDADD(DADD inst) throws SymbolicInstructionException{ - try{ + public void visitDADD(DADD inst) throws SymbolicInstructionException { + try { DoubleValue d2 = (DoubleValue) stack.popWideOperand(); DoubleValue d1 = (DoubleValue) stack.popWideOperand(); stack.pushWideOperand(d1.DADD(d2)); @@ -556,7 +732,7 @@ public void visitDADD(DADD inst) throws SymbolicInstructionException{ * * @param inst The DALOAD instruction. */ - public void visitDALOAD(DALOAD inst) throws SymbolicInstructionException{ + public void visitDALOAD(DALOAD inst) throws SymbolicInstructionException { try { IntValue idx = stack.popOperand().asIntValue(); ObjectValue arr = (ObjectValue) stack.popOperand(); @@ -568,6 +744,7 @@ public void visitDALOAD(DALOAD inst) throws SymbolicInstructionException{ enforceException(); } } else { + recordUntrackedBoundsCheck(inst, idx, arr); logger.warn("[DALOAD]: Unknown array type"); stack.pushWideOperand( arr.getName() != null @@ -585,7 +762,7 @@ public void visitDALOAD(DALOAD inst) throws SymbolicInstructionException{ * * @param inst The DASTORE instruction. */ - public void visitDASTORE(DASTORE inst) throws SymbolicInstructionException{ + public void visitDASTORE(DASTORE inst) throws SymbolicInstructionException { try { DoubleValue val = stack.popWideOperand().asDoubleValue(); IntValue idx = stack.popOperand().asIntValue(); @@ -598,6 +775,7 @@ public void visitDASTORE(DASTORE inst) throws SymbolicInstructionException{ enforceException(); } } else { + recordUntrackedBoundsCheck(inst, idx, ref); logger.warn("[DASTORE]: Unknown array type"); } } catch (Throwable t) { @@ -610,8 +788,8 @@ public void visitDASTORE(DASTORE inst) throws SymbolicInstructionException{ * * @param inst The DCMPG instruction */ - public void visitDCMPG(DCMPG inst) throws SymbolicInstructionException{ - try{ + public void visitDCMPG(DCMPG inst) throws SymbolicInstructionException { + try { DoubleValue d2 = stack.popWideOperand().asDoubleValue(); DoubleValue d1 = stack.popWideOperand().asDoubleValue(); stack.pushOperand(d1.DCMPG(d2)); @@ -625,8 +803,8 @@ public void visitDCMPG(DCMPG inst) throws SymbolicInstructionException{ * * @param inst The DCMPL instruction */ - public void visitDCMPL(DCMPL inst) throws SymbolicInstructionException{ - try{ + public void visitDCMPL(DCMPL inst) throws SymbolicInstructionException { + try { DoubleValue d2 = stack.popWideOperand().asDoubleValue(); DoubleValue d1 = stack.popWideOperand().asDoubleValue(); stack.pushOperand(d1.DCMPL(d2)); @@ -640,8 +818,8 @@ public void visitDCMPL(DCMPL inst) throws SymbolicInstructionException{ * * @param inst The DCONST_0 instruction */ - public void visitDCONST_0(DCONST_0 inst) throws SymbolicInstructionException{ - try{ + public void visitDCONST_0(DCONST_0 inst) throws SymbolicInstructionException { + try { stack.pushWideOperand(ValueFactory.createNumericalValue(ValueType.doubleValue, 0.0)); } catch (Throwable t) { throw new SymbolicInstructionException(inst, t); @@ -653,8 +831,8 @@ public void visitDCONST_0(DCONST_0 inst) throws SymbolicInstructionException{ * * @param inst The DCONST_1 instruction */ - public void visitDCONST_1(DCONST_1 inst) throws SymbolicInstructionException{ - try{ + public void visitDCONST_1(DCONST_1 inst) throws SymbolicInstructionException { + try { stack.pushWideOperand(ValueFactory.createNumericalValue(ValueType.doubleValue, 1.0)); } catch (Throwable t) { throw new SymbolicInstructionException(inst, t); @@ -666,9 +844,9 @@ public void visitDCONST_1(DCONST_1 inst) throws SymbolicInstructionException{ * * @param inst The DDIV instruction */ - public void visitDDIV(DDIV inst) throws SymbolicInstructionException{ + public void visitDDIV(DDIV inst) throws SymbolicInstructionException { // ToDo (Nils): What if one of the values is zero? - try{ + try { DoubleValue d2 = (DoubleValue) stack.popWideOperand(); DoubleValue d1 = (DoubleValue) stack.popWideOperand(); stack.pushWideOperand(d1.DDIV(d2)); @@ -682,8 +860,8 @@ public void visitDDIV(DDIV inst) throws SymbolicInstructionException{ * * @param inst One of the DLOAD instructions (DLOAD, DLOAD_0 - DLOAD_3) */ - public void visitDLOAD(DLOAD inst) throws SymbolicInstructionException{ - try{ + public void visitDLOAD(DLOAD inst) throws SymbolicInstructionException { + try { stack.pushWideOperand(stack.getWideLocal(inst.var)); } catch (Throwable t) { throw new SymbolicInstructionException(inst, t); @@ -695,8 +873,8 @@ public void visitDLOAD(DLOAD inst) throws SymbolicInstructionException{ * * @param inst The DMUL instruction */ - public void visitDMUL(DMUL inst) throws SymbolicInstructionException{ - try{ + public void visitDMUL(DMUL inst) throws SymbolicInstructionException { + try { DoubleValue d2 = (DoubleValue) stack.popWideOperand(); DoubleValue d1 = (DoubleValue) stack.popWideOperand(); stack.pushWideOperand(d1.DMUL(d2)); @@ -710,8 +888,8 @@ public void visitDMUL(DMUL inst) throws SymbolicInstructionException{ * * @param inst The DNEG instruction */ - public void visitDNEG(DNEG inst) throws SymbolicInstructionException{ - try{ + public void visitDNEG(DNEG inst) throws SymbolicInstructionException { + try { DoubleValue d1 = (DoubleValue) stack.popWideOperand(); stack.pushWideOperand(d1.DNEG()); } catch (Throwable t) { @@ -719,11 +897,11 @@ public void visitDNEG(DNEG inst) throws SymbolicInstructionException{ } } - public void visitDREM(DREM inst) throws SymbolicInstructionException{ - try{ + public void visitDREM(DREM inst) throws SymbolicInstructionException { + try { DoubleValue d2 = (DoubleValue) stack.popWideOperand(); DoubleValue d1 = (DoubleValue) stack.popWideOperand(); - stack.pushWideOperand(d1.DREM(d2)); + stack.pushWideOperand(d1.DREM(d2, symbolicTraceHandler)); } catch (Throwable t) { throw new SymbolicInstructionException(inst, t); } @@ -735,8 +913,8 @@ public void visitDREM(DREM inst) throws SymbolicInstructionException{ * * @param inst The DRETURN instruction */ - public void visitDRETURN(DRETURN inst) throws SymbolicInstructionException{ - try{ + public void visitDRETURN(DRETURN inst) throws SymbolicInstructionException { + try { symbolicTraceHandler.addSpecialElement(determineIid(inst.iid), "DRETURN"); stack.setReturnValue(stack.popWideOperand()); // checkAndSetException(inst); @@ -750,8 +928,8 @@ public void visitDRETURN(DRETURN inst) throws SymbolicInstructionException{ * * @param inst One of the DSTORE instructions (DSTORE, DSTORE_0 - DSTORE_3) */ - public void visitDSTORE(DSTORE inst) throws SymbolicInstructionException{ - try{ + public void visitDSTORE(DSTORE inst) throws SymbolicInstructionException { + try { stack.setWideLocal(inst.var, stack.popWideOperand()); } catch (Throwable t) { throw new SymbolicInstructionException(inst, t); @@ -763,8 +941,8 @@ public void visitDSTORE(DSTORE inst) throws SymbolicInstructionException{ * * @param inst The DSUB instruction */ - public void visitDSUB(DSUB inst) throws SymbolicInstructionException{ - try{ + public void visitDSUB(DSUB inst) throws SymbolicInstructionException { + try { DoubleValue d2 = (DoubleValue) stack.popWideOperand(); DoubleValue d1 = (DoubleValue) stack.popWideOperand(); stack.pushWideOperand(d1.DSUB(d2)); @@ -779,9 +957,10 @@ public void visitDSUB(DSUB inst) throws SymbolicInstructionException{ * * @param inst The DUP instruction */ - public void visitDUP(DUP inst) throws SymbolicInstructionException{ - try{ - stack.pushOperand(stack.peekOperand());} catch (Throwable t) { + public void visitDUP(DUP inst) throws SymbolicInstructionException { + try { + stack.pushOperand(stack.peekOperand()); + } catch (Throwable t) { throw new SymbolicInstructionException(inst, t); } } @@ -792,8 +971,8 @@ public void visitDUP(DUP inst) throws SymbolicInstructionException{ * * @param inst The DUP2 instruction */ - public void visitDUP2(DUP2 inst) throws SymbolicInstructionException{ - try{ + public void visitDUP2(DUP2 inst) throws SymbolicInstructionException { + try { stack.pushOperand(stack.peekWideOperand()); stack.pushOperand(stack.peekWideOperand()); } catch (Throwable t) { @@ -807,8 +986,8 @@ public void visitDUP2(DUP2 inst) throws SymbolicInstructionException{ * * @param inst The DUP2_X1 instruction */ - public void visitDUP2_X1(DUP2_X1 inst) throws SymbolicInstructionException{ - try{ + public void visitDUP2_X1(DUP2_X1 inst) throws SymbolicInstructionException { + try { Value word1 = stack.popOperand(); Value word2 = stack.popOperand(); Value word3 = stack.popOperand(); @@ -828,8 +1007,8 @@ public void visitDUP2_X1(DUP2_X1 inst) throws SymbolicInstructionException{ * * @param inst The DUP2_X2 instruction */ - public void visitDUP2_X2(DUP2_X2 inst) throws SymbolicInstructionException{ - try{ + public void visitDUP2_X2(DUP2_X2 inst) throws SymbolicInstructionException { + try { Value word1 = stack.popOperand(); Value word2 = stack.popOperand(); Value word3 = stack.popOperand(); @@ -850,8 +1029,8 @@ public void visitDUP2_X2(DUP2_X2 inst) throws SymbolicInstructionException{ * * @param inst The DUP2_X1 instruction */ - public void visitDUP_X1(DUP_X1 inst) throws SymbolicInstructionException{ - try{ + public void visitDUP_X1(DUP_X1 inst) throws SymbolicInstructionException { + try { Value top = stack.popOperand(); Value top2 = stack.popOperand(); stack.pushOperand(top); @@ -869,8 +1048,8 @@ public void visitDUP_X1(DUP_X1 inst) throws SymbolicInstructionException{ * * @param inst The DUP2_X1 instruction */ - public void visitDUP_X2(DUP_X2 inst) throws SymbolicInstructionException{ - try{ + public void visitDUP_X2(DUP_X2 inst) throws SymbolicInstructionException { + try { Value word1 = stack.popOperand(); Value word2 = stack.popOperand(); Value word3 = stack.popOperand(); @@ -888,8 +1067,8 @@ public void visitDUP_X2(DUP_X2 inst) throws SymbolicInstructionException{ * * @param inst The F2D instruction */ - public void visitF2D(F2D inst) throws SymbolicInstructionException{ - try{ + public void visitF2D(F2D inst) throws SymbolicInstructionException { + try { FloatValue f1 = stack.popOperand().asFloatValue(); stack.pushWideOperand(f1.F2D()); } catch (Throwable t) { @@ -902,8 +1081,8 @@ public void visitF2D(F2D inst) throws SymbolicInstructionException{ * * @param inst The F2I instruction */ - public void visitF2I(F2I inst) throws SymbolicInstructionException{ - try{ + public void visitF2I(F2I inst) throws SymbolicInstructionException { + try { FloatValue f1 = stack.popOperand().asFloatValue(); stack.pushOperand(f1.F2I()); } catch (Throwable t) { @@ -911,8 +1090,8 @@ public void visitF2I(F2I inst) throws SymbolicInstructionException{ } } - public void visitF2L(F2L inst) throws SymbolicInstructionException{ - try{ + public void visitF2L(F2L inst) throws SymbolicInstructionException { + try { FloatValue f1 = stack.popOperand().asFloatValue(); stack.pushWideOperand(f1.F2L()); } catch (Throwable t) { @@ -925,8 +1104,8 @@ public void visitF2L(F2L inst) throws SymbolicInstructionException{ * * @param inst The FADD instruction */ - public void visitFADD(FADD inst) throws SymbolicInstructionException{ - try{ + public void visitFADD(FADD inst) throws SymbolicInstructionException { + try { FloatValue f2 = stack.popOperand().asFloatValue(); FloatValue f1 = stack.popOperand().asFloatValue(); stack.pushOperand(f1.FADD(f2)); @@ -940,7 +1119,7 @@ public void visitFADD(FADD inst) throws SymbolicInstructionException{ * * @param inst The FALOAD instruction */ - public void visitFALOAD(FALOAD inst) throws SymbolicInstructionException{ + public void visitFALOAD(FALOAD inst) throws SymbolicInstructionException { try { IntValue idx = stack.popOperand().asIntValue(); ObjectValue arr = (ObjectValue) stack.popOperand(); @@ -952,6 +1131,7 @@ public void visitFALOAD(FALOAD inst) throws SymbolicInstructionException{ enforceException(); } } else { + recordUntrackedBoundsCheck(inst, idx, arr); logger.warn("[FALOAD]: Unknown array type"); stack.pushOperand( arr.getName() != null @@ -969,7 +1149,7 @@ public void visitFALOAD(FALOAD inst) throws SymbolicInstructionException{ * * @param inst The FASTORE instruction */ - public void visitFASTORE(FASTORE inst) throws SymbolicInstructionException{ + public void visitFASTORE(FASTORE inst) throws SymbolicInstructionException { try { FloatValue val = stack.popOperand().asFloatValue(); IntValue idx = stack.popOperand().asIntValue(); @@ -982,6 +1162,7 @@ public void visitFASTORE(FASTORE inst) throws SymbolicInstructionException{ enforceException(); } } else { + recordUntrackedBoundsCheck(inst, idx, ref); logger.warn("[FASTORE]: Unknown array type"); } } catch (Throwable t) { @@ -994,8 +1175,8 @@ public void visitFASTORE(FASTORE inst) throws SymbolicInstructionException{ * * @param inst The FCMPG instruction */ - public void visitFCMPG(FCMPG inst) throws SymbolicInstructionException{ - try{ + public void visitFCMPG(FCMPG inst) throws SymbolicInstructionException { + try { FloatValue f2 = stack.popOperand().asFloatValue(); FloatValue f1 = stack.popOperand().asFloatValue(); stack.pushOperand(f1.FCMPG(f2)); @@ -1009,8 +1190,8 @@ public void visitFCMPG(FCMPG inst) throws SymbolicInstructionException{ * * @param inst The FCMPL instruction */ - public void visitFCMPL(FCMPL inst) throws SymbolicInstructionException{ - try{ + public void visitFCMPL(FCMPL inst) throws SymbolicInstructionException { + try { FloatValue f2 = stack.popOperand().asFloatValue(); FloatValue f1 = stack.popOperand().asFloatValue(); stack.pushOperand(f1.FCMPL(f2)); @@ -1024,8 +1205,8 @@ public void visitFCMPL(FCMPL inst) throws SymbolicInstructionException{ * * @param inst The FCONST_0 instruction */ - public void visitFCONST_0(FCONST_0 inst) throws SymbolicInstructionException{ - try{ + public void visitFCONST_0(FCONST_0 inst) throws SymbolicInstructionException { + try { stack.pushOperand(ValueFactory.createNumericalValue(ValueType.floatValue, 0.0f)); } catch (Throwable t) { throw new SymbolicInstructionException(inst, t); @@ -1037,8 +1218,8 @@ public void visitFCONST_0(FCONST_0 inst) throws SymbolicInstructionException{ * * @param inst The FCONST_1 instruction */ - public void visitFCONST_1(FCONST_1 inst) throws SymbolicInstructionException{ - try{ + public void visitFCONST_1(FCONST_1 inst) throws SymbolicInstructionException { + try { stack.pushOperand(ValueFactory.createNumericalValue(ValueType.floatValue, 1.0f)); } catch (Throwable t) { throw new SymbolicInstructionException(inst, t); @@ -1050,8 +1231,8 @@ public void visitFCONST_1(FCONST_1 inst) throws SymbolicInstructionException{ * * @param inst The FCONST_2 instruction */ - public void visitFCONST_2(FCONST_2 inst) throws SymbolicInstructionException{ - try{ + public void visitFCONST_2(FCONST_2 inst) throws SymbolicInstructionException { + try { stack.pushOperand(ValueFactory.createNumericalValue(ValueType.floatValue, 2.0f)); } catch (Throwable t) { throw new SymbolicInstructionException(inst, t); @@ -1063,8 +1244,8 @@ public void visitFCONST_2(FCONST_2 inst) throws SymbolicInstructionException{ * * @param inst The FDIV instruction */ - public void visitFDIV(FDIV inst) throws SymbolicInstructionException{ - try{ + public void visitFDIV(FDIV inst) throws SymbolicInstructionException { + try { FloatValue f2 = stack.popOperand().asFloatValue(); FloatValue f1 = stack.popOperand().asFloatValue(); stack.pushOperand(f1.FDIV(f2)); @@ -1078,8 +1259,8 @@ public void visitFDIV(FDIV inst) throws SymbolicInstructionException{ * * @param inst One of the FLOAD instructions (FLOAD, FLOAD_0 - FLOAD_3) */ - public void visitFLOAD(FLOAD inst) throws SymbolicInstructionException{ - try{ + public void visitFLOAD(FLOAD inst) throws SymbolicInstructionException { + try { stack.pushOperand(stack.getLocal(inst.var)); } catch (Throwable t) { throw new SymbolicInstructionException(inst, t); @@ -1091,8 +1272,8 @@ public void visitFLOAD(FLOAD inst) throws SymbolicInstructionException{ * * @param inst The FMUL instruction */ - public void visitFMUL(FMUL inst) throws SymbolicInstructionException{ - try{ + public void visitFMUL(FMUL inst) throws SymbolicInstructionException { + try { FloatValue f2 = stack.popOperand().asFloatValue(); FloatValue f1 = stack.popOperand().asFloatValue(); stack.pushOperand(f1.FMUL(f2)); @@ -1106,8 +1287,8 @@ public void visitFMUL(FMUL inst) throws SymbolicInstructionException{ * * @param inst The FNEG instruction */ - public void visitFNEG(FNEG inst) throws SymbolicInstructionException{ - try{ + public void visitFNEG(FNEG inst) throws SymbolicInstructionException { + try { FloatValue f1 = stack.popOperand().asFloatValue(); stack.pushOperand(f1.FNEG()); } catch (Throwable t) { @@ -1121,11 +1302,11 @@ public void visitFNEG(FNEG inst) throws SymbolicInstructionException{ * * @param inst The FREM instruction */ - public void visitFREM(FREM inst) throws SymbolicInstructionException{ - try{ + public void visitFREM(FREM inst) throws SymbolicInstructionException { + try { FloatValue f2 = stack.popOperand().asFloatValue(); FloatValue f1 = stack.popOperand().asFloatValue(); - stack.pushOperand(f1.FREM(f2)); + stack.pushOperand(f1.FREM(f2, symbolicTraceHandler)); } catch (Throwable t) { throw new SymbolicInstructionException(inst, t); } @@ -1137,8 +1318,8 @@ public void visitFREM(FREM inst) throws SymbolicInstructionException{ * * @param inst The FRETURN instruction */ - public void visitFRETURN(FRETURN inst) throws SymbolicInstructionException{ - try{ + public void visitFRETURN(FRETURN inst) throws SymbolicInstructionException { + try { symbolicTraceHandler.addSpecialElement(determineIid(inst.iid), "FRETURN"); stack.setReturnValue(stack.popOperand()); // checkAndSetException(inst); @@ -1153,8 +1334,8 @@ public void visitFRETURN(FRETURN inst) throws SymbolicInstructionException{ * * @param inst One of the FSTORE instructions (FSTORE, FSTORE_0 - FSTORE_3) */ - public void visitFSTORE(FSTORE inst) throws SymbolicInstructionException{ - try{ + public void visitFSTORE(FSTORE inst) throws SymbolicInstructionException { + try { stack.setLocal(inst.var, stack.popOperand()); } catch (Throwable t) { throw new SymbolicInstructionException(inst, t); @@ -1166,8 +1347,8 @@ public void visitFSTORE(FSTORE inst) throws SymbolicInstructionException{ * * @param inst The FSUB instruction. */ - public void visitFSUB(FSUB inst) throws SymbolicInstructionException{ - try{ + public void visitFSUB(FSUB inst) throws SymbolicInstructionException { + try { FloatValue f2 = stack.popOperand().asFloatValue(); FloatValue f1 = stack.popOperand().asFloatValue(); stack.pushOperand(f1.FSUB(f2)); @@ -1181,9 +1362,16 @@ public void visitFSUB(FSUB inst) throws SymbolicInstructionException{ * * @param inst The GETFIELD instruction */ - public void visitGETFIELD(GETFIELD inst) throws SymbolicInstructionException{ + public void visitGETFIELD(GETFIELD inst) throws SymbolicInstructionException { try { ObjectValue ref = stack.popOperand().asObjectValue(); + if (ref.getAddress() == ObjectValue.ADDRESS_NULL) { + // The JVM throws a NullPointerException here: follow it like any other throwing + // instruction instead of reading a field of null. + stack.pushOperand(PlaceHolder.instance); + checkAndSetException(inst); + return; + } int fieldIndex = classDepot.getFieldIndex(inst.cIdx, inst.name, false); @@ -1203,11 +1391,12 @@ public void visitGETFIELD(GETFIELD inst) throws SymbolicInstructionException{ } } - private Value fetchStaticField (GETSTATIC inst) - throws ThreadAlreadyDisabledException, NoThreadContextException, ThreadAlreadyEnabledException, ClassNotFoundException { + private Value fetchStaticField(GETSTATIC inst) + throws ThreadAlreadyDisabledException, NoThreadContextException, ThreadAlreadyEnabledException, + ClassNotFoundException { int runtimeFieldIndex = classDepot.getFieldIndex(inst.cIdx, inst.name, true); return ThreadHandler.getStaticField( - currentThread().getId(), inst.cIdx, runtimeFieldIndex); + currentThread().getId(), inst.cIdx, runtimeFieldIndex); } /** @@ -1215,8 +1404,9 @@ public void visitGETFIELD(GETFIELD inst) throws SymbolicInstructionException{ * * @param inst The GETSTATIC instruction */ - public void visitGETSTATIC(GETSTATIC inst) throws SymbolicInstructionException{ + public void visitGETSTATIC(GETSTATIC inst) throws SymbolicInstructionException { try { + LoopStateMatcher.staticField(classDepot.getClassName(inst.cIdx)); Value val; val = fetchStaticField(inst); if (val == PlaceHolder.instance) { @@ -1224,7 +1414,6 @@ public void visitGETSTATIC(GETSTATIC inst) throws SymbolicInstructionException{ val = new PlaceHolder(PlaceHolder.ValueOrigin.GETSTATIC, inst, null); } - if (inst.desc.startsWith("D") || inst.desc.startsWith("J")) { stack.pushWideOperand(val); } else { @@ -1239,8 +1428,8 @@ public void visitGETSTATIC(GETSTATIC inst) throws SymbolicInstructionException{ } } - public void visitGETVALUE_Object(GETVALUE_Object inst) throws SymbolicInstructionException{ - try{ + public void visitGETVALUE_Object(GETVALUE_Object inst) throws SymbolicInstructionException { + try { Value peek = stack.peekOperand(); Value tmp; boolean isSymbolic = false; @@ -1262,7 +1451,11 @@ public void visitGETVALUE_Object(GETVALUE_Object inst) throws SymbolicInstruc stack.pushOperand(ValueFactory.getLambdaObjectValue(inst.v, parentAddress, key)); } else */ - if (peek instanceof PlaceHolder placeHolder) { + if (peek instanceof ContainerPlaceHolder containerPlaceHolder) { + stack.popOperand(); + stack.pushOperand(CollectionInvocation.resolve( + containerPlaceHolder, inst.val, inst.address, stack, symbolicTraceHandler)); + } else if (peek instanceof PlaceHolder placeHolder) { // remove the placeholder value stack.popOperand(); @@ -1275,11 +1468,16 @@ public void visitGETVALUE_Object(GETVALUE_Object inst) throws SymbolicInstruc } Logger shadowStateLogger = ThreadHandler.getShadowStateLogger(currentThread().getId()); shadowStateLogger.info("Recovered object from heap: {}", tmp); - if(tmp instanceof StringBuilderValue sbv){ - StringBuilder sb = (StringBuilder) inst.val; - SWATAssert.check(sbv.getStringValue().concrete.equals(inst.val), - "Concrete value of the StringBuilder does not match the value in the stack: {} | {}", - sbv.getStringValue().concrete, sb.toString()); + if (tmp instanceof StringBuilderValue sbv && inst.val != null) { + String chars = inst.val.toString(); + if (sbv.isUnknown() || sbv.getStringValue() == null) { + // A change SWAT does not model left its chars unknown: take the concrete ones. + sbv.resync(chars); + } else { + SWATAssert.check(sbv.getStringValue().concrete.equals(chars), + "Concrete value of the StringBuilder does not match the value in the stack: {} | {}", + sbv.getStringValue().concrete, chars); + } } stack.pushOperand(tmp); } else if (inst.address == 0) { @@ -1305,7 +1503,7 @@ public void visitGETVALUE_Object(GETVALUE_Object inst) throws SymbolicInstruc GETSTATIC gsInst = (GETSTATIC) placeHolder.inst; setStaticField(tmp, gsInst.cIdx, gsInst.name); Logger shadowStateLogger = ThreadHandler.getShadowStateLogger(currentThread().getId()); - shadowStateLogger.debug("Storing retrieved value in field {}",gsInst.name); + shadowStateLogger.debug("Storing retrieved value in field {}", gsInst.name); } } } else if ((peek.asObjectValue()).getAddress() == ADDRESS_UNKNOWN) { @@ -1315,10 +1513,18 @@ public void visitGETVALUE_Object(GETVALUE_Object inst) throws SymbolicInstruc stack.pushOperand(ValueFactory.createNULLValue()); } else if (inst.val != null) { if (inst.val instanceof String s) { - SWATAssert.check(inst.val.equals(peek.concrete), - "Concrete value of the object does not match the value in the stack: {} | {}", - inst.val, peek.concrete); - if(peek.formula == null) { + if (!inst.val.equals(peek.concrete)) { + // The shadow String diverged from the JVM (e.g. after a Reader call SWAT + // does not model filled a buffer). As for primitives, continue with the + // concrete value and record the context loss. + logger.warn("[GETVALUE_Object]: String on stack does not match expected value, causes context loss! Expected: {}, Actual: {}", + inst.val, peek.concrete); + symbolicTraceHandler.recordSymbolicContextLoss(); + stack.popOperand(); + StringValue val = ValueFactory.createStringValue(s, inst.address); + stack.pushOperand(val); + stack.putToHeap(inst.address, val); + } else if (peek.formula == null) { // TODO This needs to be cleaned up! stack.popOperand(); StringValue val = ValueFactory.createStringValue(s, inst.address); @@ -1363,7 +1569,8 @@ public void visitGETVALUE_Object(GETVALUE_Object inst) throws SymbolicInstruc } else { // In delegation context, fetch the actual delegated object and replace it on stack if (peek.asObjectValue().getAddress() != inst.address) { - logger.debug("Delegation detected: stack has @{}, instruction expects @{}. Fetching delegated object.", + logger.debug( + "Delegation detected: stack has @{}, instruction expects @{}. Fetching delegated object.", String.format("%08x", peek.asObjectValue().getAddress()), String.format("%08x", inst.address)); @@ -1380,7 +1587,8 @@ public void visitGETVALUE_Object(GETVALUE_Object inst) throws SymbolicInstruc } else if (inst.val != null) { // If not in heap yet, create it from the instruction using ValueFactory try { - Value delegatedObject = de.uzl.its.swat.symbolic.value.ValueFactory.createObjectValue(inst.val, inst.address); + Value delegatedObject = de.uzl.its.swat.symbolic.value.ValueFactory + .createObjectValue(inst.val, inst.address); stack.pushOperand(delegatedObject); stack.putToHeap(inst.address, delegatedObject); logger.debug("Created new delegated object from instruction: {}", delegatedObject); @@ -1411,7 +1619,7 @@ public void visitGETVALUE_Object(GETVALUE_Object inst) throws SymbolicInstruc * * @param inst The (artificial) GETVALUE_boolean instruction */ - public void visitGETVALUE_boolean(GETVALUE_boolean inst) throws SymbolicInstructionException{ + public void visitGETVALUE_boolean(GETVALUE_boolean inst) throws SymbolicInstructionException { visitGETVALUE_primitive(inst, ValueType.booleanValue); } @@ -1420,7 +1628,7 @@ public void visitGETVALUE_boolean(GETVALUE_boolean inst) throws SymbolicInstruct * * @param inst The (artificial) GETVALUE_byte instruction */ - public void visitGETVALUE_byte(GETVALUE_byte inst) throws SymbolicInstructionException{ + public void visitGETVALUE_byte(GETVALUE_byte inst) throws SymbolicInstructionException { visitGETVALUE_primitive(inst, ValueType.byteValue); } @@ -1429,7 +1637,7 @@ public void visitGETVALUE_byte(GETVALUE_byte inst) throws SymbolicInstructionExc * * @param inst The (artificial) GETVALUE_char instruction */ - public void visitGETVALUE_char(GETVALUE_char inst) throws SymbolicInstructionException{ + public void visitGETVALUE_char(GETVALUE_char inst) throws SymbolicInstructionException { visitGETVALUE_primitive(inst, ValueType.charValue); } @@ -1438,7 +1646,7 @@ public void visitGETVALUE_char(GETVALUE_char inst) throws SymbolicInstructionExc * * @param inst The (artificial) GETVALUE_double instruction */ - public void visitGETVALUE_double(GETVALUE_double inst) throws SymbolicInstructionException{ + public void visitGETVALUE_double(GETVALUE_double inst) throws SymbolicInstructionException { visitGETVALUE_primitive(inst, ValueType.doubleValue); } @@ -1447,7 +1655,7 @@ public void visitGETVALUE_double(GETVALUE_double inst) throws SymbolicInstructio * * @param inst The (artificial) GETVALUE_float instruction */ - public void visitGETVALUE_float(GETVALUE_float inst) throws SymbolicInstructionException{ + public void visitGETVALUE_float(GETVALUE_float inst) throws SymbolicInstructionException { visitGETVALUE_primitive(inst, ValueType.floatValue); } @@ -1456,7 +1664,7 @@ public void visitGETVALUE_float(GETVALUE_float inst) throws SymbolicInstructionE * * @param inst The (artificial) GETVALUE_int instruction */ - public void visitGETVALUE_int(GETVALUE_int inst) throws SymbolicInstructionException{ + public void visitGETVALUE_int(GETVALUE_int inst) throws SymbolicInstructionException { visitGETVALUE_primitive(inst, ValueType.intValue); } @@ -1465,7 +1673,7 @@ public void visitGETVALUE_int(GETVALUE_int inst) throws SymbolicInstructionExcep * * @param inst The (artificial) GETVALUE_long instruction */ - public void visitGETVALUE_long(GETVALUE_long inst) throws SymbolicInstructionException{ + public void visitGETVALUE_long(GETVALUE_long inst) throws SymbolicInstructionException { visitGETVALUE_primitive(inst, ValueType.longValue); } @@ -1474,11 +1682,11 @@ public void visitGETVALUE_long(GETVALUE_long inst) throws SymbolicInstructionExc * * @param inst The (artificial) GETVALUE_short instruction */ - public void visitGETVALUE_short(GETVALUE_short inst) throws SymbolicInstructionException{ + public void visitGETVALUE_short(GETVALUE_short inst) throws SymbolicInstructionException { visitGETVALUE_primitive(inst, ValueType.shortValue); } - public void visitGETVALUE_void(GETVALUE_void inst) throws SymbolicInstructionException{ + public void visitGETVALUE_void(GETVALUE_void inst) throws SymbolicInstructionException { // TODO: Why does the case exist if it does nothing? } @@ -1487,15 +1695,21 @@ public void visitGETVALUE_void(GETVALUE_void inst) throws SymbolicInstructionExc * * @param inst Either the GOTO or GOTO_w instruction */ - public void visitGOTO(GOTO inst) throws SymbolicInstructionException{} + public void visitGOTO(GOTO inst) throws SymbolicInstructionException { + try { + merger.atGoto(inst.iid); + } catch (Throwable t) { + throw new SymbolicInstructionException(inst, t); + } + } /** * Converts an integer to a byte and puts the result onto the symbolic stack * * @param inst The I2B instruction */ - public void visitI2B(I2B inst) throws SymbolicInstructionException{ - try{ + public void visitI2B(I2B inst) throws SymbolicInstructionException { + try { IntValue i1 = stack.popOperand().asIntValue(); stack.pushOperand(i1.I2B()); } catch (Throwable t) { @@ -1511,8 +1725,8 @@ public void visitI2B(I2B inst) throws SymbolicInstructionException{ * * @param inst The I2C instruction */ - public void visitI2C(I2C inst) throws SymbolicInstructionException{ - try{ + public void visitI2C(I2C inst) throws SymbolicInstructionException { + try { IntValue i1 = stack.popOperand().asIntValue(); stack.pushOperand(i1.I2C()); } catch (Throwable t) { @@ -1525,8 +1739,8 @@ public void visitI2C(I2C inst) throws SymbolicInstructionException{ * * @param inst The I2D instruction */ - public void visitI2D(I2D inst) throws SymbolicInstructionException{ - try{ + public void visitI2D(I2D inst) throws SymbolicInstructionException { + try { IntValue i1 = stack.popOperand().asIntValue(); stack.pushWideOperand(i1.I2D()); } catch (Throwable t) { @@ -1539,8 +1753,8 @@ public void visitI2D(I2D inst) throws SymbolicInstructionException{ * * @param inst The I2F instruction */ - public void visitI2F(I2F inst) throws SymbolicInstructionException{ - try{ + public void visitI2F(I2F inst) throws SymbolicInstructionException { + try { IntValue i1 = stack.popOperand().asIntValue(); stack.pushOperand(i1.I2F()); } catch (Throwable t) { @@ -1553,8 +1767,8 @@ public void visitI2F(I2F inst) throws SymbolicInstructionException{ * * @param inst The I2L instruction */ - public void visitI2L(I2L inst) throws SymbolicInstructionException{ - try{ + public void visitI2L(I2L inst) throws SymbolicInstructionException { + try { IntValue i1 = stack.popOperand().asIntValue(); stack.pushWideOperand(i1.I2L()); } catch (Throwable t) { @@ -1567,8 +1781,8 @@ public void visitI2L(I2L inst) throws SymbolicInstructionException{ * * @param inst The I2S instruction */ - public void visitI2S(I2S inst) throws SymbolicInstructionException{ - try{ + public void visitI2S(I2S inst) throws SymbolicInstructionException { + try { IntValue i1 = stack.popOperand().asIntValue(); stack.pushOperand(i1.I2S()); } catch (Throwable t) { @@ -1581,8 +1795,8 @@ public void visitI2S(I2S inst) throws SymbolicInstructionException{ * * @param inst The IADD instruction */ - public void visitIADD(IADD inst) throws SymbolicInstructionException{ - try{ + public void visitIADD(IADD inst) throws SymbolicInstructionException { + try { IntValue i2 = stack.popOperand().asIntValue(); IntValue i1 = stack.popOperand().asIntValue(); stack.pushOperand(i1.IADD(i2)); @@ -1596,7 +1810,7 @@ public void visitIADD(IADD inst) throws SymbolicInstructionException{ * * @param inst The IALOAD instruction */ - public void visitIALOAD(IALOAD inst) throws SymbolicInstructionException{ + public void visitIALOAD(IALOAD inst) throws SymbolicInstructionException { try { IntValue idx = stack.popOperand().asIntValue(); ObjectValue ref = (ObjectValue) stack.popOperand(); @@ -1608,6 +1822,7 @@ public void visitIALOAD(IALOAD inst) throws SymbolicInstructionException{ enforceException(); } } else { + recordUntrackedBoundsCheck(inst, idx, ref); logger.warn("[IALOAD]: Unknown array type"); stack.pushOperand( ref.getName() != null @@ -1626,8 +1841,8 @@ public void visitIALOAD(IALOAD inst) throws SymbolicInstructionException{ * * @param inst The IAND instruction */ - public void visitIAND(IAND inst) throws SymbolicInstructionException{ - try{ + public void visitIAND(IAND inst) throws SymbolicInstructionException { + try { IntValue i2 = stack.popOperand().asIntValue(); IntValue i1 = stack.popOperand().asIntValue(); stack.pushOperand(i1.IAND(i2)); @@ -1641,7 +1856,7 @@ public void visitIAND(IAND inst) throws SymbolicInstructionException{ * * @param inst The IASTORE instruction */ - public void visitIASTORE(IASTORE inst) throws SymbolicInstructionException{ + public void visitIASTORE(IASTORE inst) throws SymbolicInstructionException { try { IntValue val = stack.popOperand().asIntValue(); IntValue idx = stack.popOperand().asIntValue(); @@ -1654,6 +1869,7 @@ public void visitIASTORE(IASTORE inst) throws SymbolicInstructionException{ enforceException(); } } else { + recordUntrackedBoundsCheck(inst, idx, ref); logger.warn("[IASTORE]: Unknown array type"); } } catch (Throwable t) { @@ -1666,8 +1882,8 @@ public void visitIASTORE(IASTORE inst) throws SymbolicInstructionException{ * * @param inst Current instruction */ - public void visitICONST_0(ICONST_0 inst) throws SymbolicInstructionException{ - try{ + public void visitICONST_0(ICONST_0 inst) throws SymbolicInstructionException { + try { stack.pushOperand(ValueFactory.createNumericalValue(ValueType.intValue, 0)); } catch (Throwable t) { throw new SymbolicInstructionException(inst, t); @@ -1679,8 +1895,8 @@ public void visitICONST_0(ICONST_0 inst) throws SymbolicInstructionException{ * * @param inst Current instruction */ - public void visitICONST_1(ICONST_1 inst) throws SymbolicInstructionException{ - try{ + public void visitICONST_1(ICONST_1 inst) throws SymbolicInstructionException { + try { stack.pushOperand(ValueFactory.createNumericalValue(ValueType.intValue, 1)); } catch (Throwable t) { throw new SymbolicInstructionException(inst, t); @@ -1692,8 +1908,8 @@ public void visitICONST_1(ICONST_1 inst) throws SymbolicInstructionException{ * * @param inst Current instruction */ - public void visitICONST_2(ICONST_2 inst) throws SymbolicInstructionException{ - try{ + public void visitICONST_2(ICONST_2 inst) throws SymbolicInstructionException { + try { stack.pushOperand(ValueFactory.createNumericalValue(ValueType.intValue, 2)); } catch (Throwable t) { throw new SymbolicInstructionException(inst, t); @@ -1705,8 +1921,8 @@ public void visitICONST_2(ICONST_2 inst) throws SymbolicInstructionException{ * * @param inst Current instruction */ - public void visitICONST_3(ICONST_3 inst) throws SymbolicInstructionException{ - try{ + public void visitICONST_3(ICONST_3 inst) throws SymbolicInstructionException { + try { stack.pushOperand(ValueFactory.createNumericalValue(ValueType.intValue, 3)); } catch (Throwable t) { throw new SymbolicInstructionException(inst, t); @@ -1718,8 +1934,8 @@ public void visitICONST_3(ICONST_3 inst) throws SymbolicInstructionException{ * * @param inst Current instruction */ - public void visitICONST_4(ICONST_4 inst) throws SymbolicInstructionException{ - try{ + public void visitICONST_4(ICONST_4 inst) throws SymbolicInstructionException { + try { stack.pushOperand(ValueFactory.createNumericalValue(ValueType.intValue, 4)); } catch (Throwable t) { throw new SymbolicInstructionException(inst, t); @@ -1731,8 +1947,8 @@ public void visitICONST_4(ICONST_4 inst) throws SymbolicInstructionException{ * * @param inst Current instruction */ - public void visitICONST_5(ICONST_5 inst) throws SymbolicInstructionException{ - try{ + public void visitICONST_5(ICONST_5 inst) throws SymbolicInstructionException { + try { stack.pushOperand(ValueFactory.createNumericalValue(ValueType.intValue, 5)); } catch (Throwable t) { throw new SymbolicInstructionException(inst, t); @@ -1744,8 +1960,8 @@ public void visitICONST_5(ICONST_5 inst) throws SymbolicInstructionException{ * * @param inst Current ICONST_M1 instruction */ - public void visitICONST_M1(ICONST_M1 inst) throws SymbolicInstructionException{ - try{ + public void visitICONST_M1(ICONST_M1 inst) throws SymbolicInstructionException { + try { stack.pushOperand(ValueFactory.createNumericalValue(ValueType.intValue, -1)); } catch (Throwable t) { throw new SymbolicInstructionException(inst, t); @@ -1757,7 +1973,7 @@ public void visitICONST_M1(ICONST_M1 inst) throws SymbolicInstructionException{ * * @param inst Current IDIV instruction */ - public void visitIDIV(IDIV inst) throws SymbolicInstructionException{ + public void visitIDIV(IDIV inst) throws SymbolicInstructionException { try { IntValue i2 = stack.popOperand().asIntValue(); IntValue i1 = stack.popOperand().asIntValue(); @@ -1780,8 +1996,8 @@ public void visitIDIV(IDIV inst) throws SymbolicInstructionException{ * * @param inst The IFEQ instruction */ - public void visitIFEQ(IFEQ inst) throws SymbolicInstructionException{ - try{ + public void visitIFEQ(IFEQ inst) throws SymbolicInstructionException { + try { IntValue i1 = stack.popOperand().asIntValue(); // IntValue i1 = stack.popOperand().asIntValue(); BooleanFormula constraint = i1.IFEQ(); @@ -1790,7 +2006,9 @@ public void visitIFEQ(IFEQ inst) throws SymbolicInstructionException{ BranchCoverage.addVisitedBranch(inst.iid); - symbolicTraceHandler.checkAndSetBranch(isBranchTaken, constraint, iid); + merger.atBranch(inst.iid, isBranchTaken, constraint, + symbolicTraceHandler.checkAndSetBranch(isBranchTaken, constraint, iid), + ThreadHandler.getSolverContext(currentThread().getId())); } catch (Throwable t) { throw new SymbolicInstructionException(inst, t); } @@ -1801,8 +2019,8 @@ public void visitIFEQ(IFEQ inst) throws SymbolicInstructionException{ * * @param inst The IFGE instruction */ - public void visitIFGE(IFGE inst) throws SymbolicInstructionException{ - try{ + public void visitIFGE(IFGE inst) throws SymbolicInstructionException { + try { IntValue i1 = stack.popOperand().asIntValue(); BooleanFormula constraint = i1.IFGE(); boolean isBranchTaken = isBranchTaken(); @@ -1810,7 +2028,9 @@ public void visitIFGE(IFGE inst) throws SymbolicInstructionException{ BranchCoverage.addVisitedBranch(inst.iid); - symbolicTraceHandler.checkAndSetBranch(isBranchTaken, constraint, iid); + merger.atBranch(inst.iid, isBranchTaken, constraint, + symbolicTraceHandler.checkAndSetBranch(isBranchTaken, constraint, iid), + ThreadHandler.getSolverContext(currentThread().getId())); } catch (Throwable t) { throw new SymbolicInstructionException(inst, t); } @@ -1821,8 +2041,8 @@ public void visitIFGE(IFGE inst) throws SymbolicInstructionException{ * * @param inst The IFGT instruction */ - public void visitIFGT(IFGT inst) throws SymbolicInstructionException{ - try{ + public void visitIFGT(IFGT inst) throws SymbolicInstructionException { + try { IntValue i1 = stack.popOperand().asIntValue(); BooleanFormula constraint = i1.IFGT(); boolean isBranchTaken = isBranchTaken(); @@ -1830,7 +2050,9 @@ public void visitIFGT(IFGT inst) throws SymbolicInstructionException{ BranchCoverage.addVisitedBranch(inst.iid); - symbolicTraceHandler.checkAndSetBranch(isBranchTaken, constraint, iid); + merger.atBranch(inst.iid, isBranchTaken, constraint, + symbolicTraceHandler.checkAndSetBranch(isBranchTaken, constraint, iid), + ThreadHandler.getSolverContext(currentThread().getId())); } catch (Throwable t) { throw new SymbolicInstructionException(inst, t); } @@ -1841,8 +2063,8 @@ public void visitIFGT(IFGT inst) throws SymbolicInstructionException{ * * @param inst The IFLE instruction */ - public void visitIFLE(IFLE inst) throws SymbolicInstructionException{ - try{ + public void visitIFLE(IFLE inst) throws SymbolicInstructionException { + try { IntValue i1 = stack.popOperand().asIntValue(); BooleanFormula constraint = i1.IFLE(); boolean isBranchTaken = isBranchTaken(); @@ -1850,7 +2072,9 @@ public void visitIFLE(IFLE inst) throws SymbolicInstructionException{ BranchCoverage.addVisitedBranch(inst.iid); - symbolicTraceHandler.checkAndSetBranch(isBranchTaken, constraint, iid); + merger.atBranch(inst.iid, isBranchTaken, constraint, + symbolicTraceHandler.checkAndSetBranch(isBranchTaken, constraint, iid), + ThreadHandler.getSolverContext(currentThread().getId())); } catch (Throwable t) { throw new SymbolicInstructionException(inst, t); } @@ -1861,8 +2085,8 @@ public void visitIFLE(IFLE inst) throws SymbolicInstructionException{ * * @param inst The IFLT instruction */ - public void visitIFLT(IFLT inst) throws SymbolicInstructionException{ - try{ + public void visitIFLT(IFLT inst) throws SymbolicInstructionException { + try { IntValue i1 = stack.popOperand().asIntValue(); BooleanFormula constraint = i1.IFLT(); boolean isBranchTaken = isBranchTaken(); @@ -1870,7 +2094,9 @@ public void visitIFLT(IFLT inst) throws SymbolicInstructionException{ BranchCoverage.addVisitedBranch(inst.iid); - symbolicTraceHandler.checkAndSetBranch(isBranchTaken, constraint, iid); + merger.atBranch(inst.iid, isBranchTaken, constraint, + symbolicTraceHandler.checkAndSetBranch(isBranchTaken, constraint, iid), + ThreadHandler.getSolverContext(currentThread().getId())); } catch (Throwable t) { throw new SymbolicInstructionException(inst, t); } @@ -1881,8 +2107,8 @@ public void visitIFLT(IFLT inst) throws SymbolicInstructionException{ * * @param inst The IFNE instruction */ - public void visitIFNE(IFNE inst) throws SymbolicInstructionException{ - try{ + public void visitIFNE(IFNE inst) throws SymbolicInstructionException { + try { IntValue i1 = stack.popOperand().asIntValue(); BooleanFormula constraint = i1.IFNE(); boolean isBranchTaken = isBranchTaken(); @@ -1890,7 +2116,9 @@ public void visitIFNE(IFNE inst) throws SymbolicInstructionException{ BranchCoverage.addVisitedBranch(inst.iid); - symbolicTraceHandler.checkAndSetBranch(isBranchTaken, constraint, iid); + merger.atBranch(inst.iid, isBranchTaken, constraint, + symbolicTraceHandler.checkAndSetBranch(isBranchTaken, constraint, iid), + ThreadHandler.getSolverContext(currentThread().getId())); } catch (Throwable t) { throw new SymbolicInstructionException(inst, t); } @@ -1901,8 +2129,8 @@ public void visitIFNE(IFNE inst) throws SymbolicInstructionException{ * * @param inst The IFNONNULL instruction. */ - public void visitIFNONNULL(IFNONNULL inst) throws SymbolicInstructionException{ - try{ + public void visitIFNONNULL(IFNONNULL inst) throws SymbolicInstructionException { + try { ObjectValue o1 = stack.popOperand().asObjectValue(); BooleanFormula constraint = o1.IFNONNULL(); boolean isBranchTaken = isBranchTaken(); @@ -1921,8 +2149,8 @@ public void visitIFNONNULL(IFNONNULL inst) throws SymbolicInstructionException{ * * @param inst The IFNULL instruction. */ - public void visitIFNULL(IFNULL inst) throws SymbolicInstructionException{ - try{ + public void visitIFNULL(IFNULL inst) throws SymbolicInstructionException { + try { ObjectValue o1 = stack.popOperand().asObjectValue(); BooleanFormula constraint = o1.IFNULL(); boolean isBranchTaken = isBranchTaken(); @@ -1942,8 +2170,8 @@ public void visitIFNULL(IFNULL inst) throws SymbolicInstructionException{ * * @param inst The IF_ACMPEQ instruction. */ - public void visitIF_ACMPEQ(IF_ACMPEQ inst) throws SymbolicInstructionException{ - try{ + public void visitIF_ACMPEQ(IF_ACMPEQ inst) throws SymbolicInstructionException { + try { Value v2 = stack.popOperand(); Value v1 = stack.popOperand(); BooleanFormula constraint; @@ -1956,7 +2184,6 @@ public void visitIF_ACMPEQ(IF_ACMPEQ inst) throws SymbolicInstructionException{ } boolean isBranchTaken = isBranchTaken(); long iid = determineIid(inst.iid); - symbolicTraceHandler.checkAndSetBranch(isBranchTaken, constraint, iid); BranchCoverage.addVisitedBranch(inst.iid); @@ -1972,8 +2199,8 @@ public void visitIF_ACMPEQ(IF_ACMPEQ inst) throws SymbolicInstructionException{ * * @param inst The IF_ACMPNE instruction. */ - public void visitIF_ACMPNE(IF_ACMPNE inst) throws SymbolicInstructionException{ - try{ + public void visitIF_ACMPNE(IF_ACMPNE inst) throws SymbolicInstructionException { + try { Value v2 = stack.popOperand(); Value v1 = stack.popOperand(); BooleanFormula constraint; @@ -2000,8 +2227,8 @@ public void visitIF_ACMPNE(IF_ACMPNE inst) throws SymbolicInstructionException{ * * @param inst The IF_ICMPEQ instruction */ - public void visitIF_ICMPEQ(IF_ICMPEQ inst) throws SymbolicInstructionException{ - try{ + public void visitIF_ICMPEQ(IF_ICMPEQ inst) throws SymbolicInstructionException { + try { IntValue i2 = stack.popOperand().asIntValue(); IntValue i1 = stack.popOperand().asIntValue(); BooleanFormula constraint = i1.IF_ICMPEQ(i2); @@ -2010,7 +2237,9 @@ public void visitIF_ICMPEQ(IF_ICMPEQ inst) throws SymbolicInstructionException{ BranchCoverage.addVisitedBranch(inst.iid); - symbolicTraceHandler.checkAndSetBranch(isBranchTaken, constraint, iid); + merger.atBranch(inst.iid, isBranchTaken, constraint, + symbolicTraceHandler.checkAndSetBranch(isBranchTaken, constraint, iid), + ThreadHandler.getSolverContext(currentThread().getId())); } catch (Throwable t) { throw new SymbolicInstructionException(inst, t); } @@ -2028,8 +2257,8 @@ public long determineIid(long iid) { * * @param inst The IF_ICMPGE instruction */ - public void visitIF_ICMPGE(IF_ICMPGE inst) throws SymbolicInstructionException{ - try{ + public void visitIF_ICMPGE(IF_ICMPGE inst) throws SymbolicInstructionException { + try { IntValue i2 = stack.popOperand().asIntValue(); IntValue i1 = stack.popOperand().asIntValue(); BooleanFormula constraint = i1.IF_ICMPGE(i2); @@ -2038,7 +2267,9 @@ public void visitIF_ICMPGE(IF_ICMPGE inst) throws SymbolicInstructionException{ BranchCoverage.addVisitedBranch(inst.iid); - symbolicTraceHandler.checkAndSetBranch(isBranchTaken, constraint, iid); + merger.atBranch(inst.iid, isBranchTaken, constraint, + symbolicTraceHandler.checkAndSetBranch(isBranchTaken, constraint, iid), + ThreadHandler.getSolverContext(currentThread().getId())); } catch (Throwable t) { throw new SymbolicInstructionException(inst, t); } @@ -2049,8 +2280,8 @@ public void visitIF_ICMPGE(IF_ICMPGE inst) throws SymbolicInstructionException{ * * @param inst The IF_ICMPGT instruction */ - public void visitIF_ICMPGT(IF_ICMPGT inst) throws SymbolicInstructionException{ - try{ + public void visitIF_ICMPGT(IF_ICMPGT inst) throws SymbolicInstructionException { + try { IntValue i2 = stack.popOperand().asIntValue(); IntValue i1 = stack.popOperand().asIntValue(); BooleanFormula constraint = i1.IF_ICMPGT(i2); @@ -2059,7 +2290,9 @@ public void visitIF_ICMPGT(IF_ICMPGT inst) throws SymbolicInstructionException{ BranchCoverage.addVisitedBranch(inst.iid); - symbolicTraceHandler.checkAndSetBranch(isBranchTaken, constraint, iid); + merger.atBranch(inst.iid, isBranchTaken, constraint, + symbolicTraceHandler.checkAndSetBranch(isBranchTaken, constraint, iid), + ThreadHandler.getSolverContext(currentThread().getId())); } catch (Throwable t) { throw new SymbolicInstructionException(inst, t); } @@ -2070,8 +2303,8 @@ public void visitIF_ICMPGT(IF_ICMPGT inst) throws SymbolicInstructionException{ * * @param inst The IF_ICMPLE instruction */ - public void visitIF_ICMPLE(IF_ICMPLE inst) throws SymbolicInstructionException{ - try{ + public void visitIF_ICMPLE(IF_ICMPLE inst) throws SymbolicInstructionException { + try { IntValue i2 = stack.popOperand().asIntValue(); IntValue i1 = stack.popOperand().asIntValue(); BooleanFormula constraint = i1.IF_ICMPLE(i2); @@ -2080,7 +2313,9 @@ public void visitIF_ICMPLE(IF_ICMPLE inst) throws SymbolicInstructionException{ BranchCoverage.addVisitedBranch(inst.iid); - symbolicTraceHandler.checkAndSetBranch(isBranchTaken, constraint, iid); + merger.atBranch(inst.iid, isBranchTaken, constraint, + symbolicTraceHandler.checkAndSetBranch(isBranchTaken, constraint, iid), + ThreadHandler.getSolverContext(currentThread().getId())); } catch (Throwable t) { throw new SymbolicInstructionException(inst, t); } @@ -2091,8 +2326,8 @@ public void visitIF_ICMPLE(IF_ICMPLE inst) throws SymbolicInstructionException{ * * @param inst The IF_ICMPLT instruction */ - public void visitIF_ICMPLT(IF_ICMPLT inst) throws SymbolicInstructionException{ - try{ + public void visitIF_ICMPLT(IF_ICMPLT inst) throws SymbolicInstructionException { + try { IntValue i2 = stack.popOperand().asIntValue(); IntValue i1 = stack.popOperand().asIntValue(); BooleanFormula constraint = i1.IF_ICMPLT(i2); @@ -2101,7 +2336,9 @@ public void visitIF_ICMPLT(IF_ICMPLT inst) throws SymbolicInstructionException{ BranchCoverage.addVisitedBranch(inst.iid); - symbolicTraceHandler.checkAndSetBranch(isBranchTaken, constraint, iid); + merger.atBranch(inst.iid, isBranchTaken, constraint, + symbolicTraceHandler.checkAndSetBranch(isBranchTaken, constraint, iid), + ThreadHandler.getSolverContext(currentThread().getId())); } catch (Throwable t) { throw new SymbolicInstructionException(inst, t); } @@ -2112,8 +2349,8 @@ public void visitIF_ICMPLT(IF_ICMPLT inst) throws SymbolicInstructionException{ * * @param inst The IF_ICMPNE instruction */ - public void visitIF_ICMPNE(IF_ICMPNE inst) throws SymbolicInstructionException{ - try{ + public void visitIF_ICMPNE(IF_ICMPNE inst) throws SymbolicInstructionException { + try { IntValue i2 = stack.popOperand().asIntValue(); IntValue i1 = stack.popOperand().asIntValue(); BooleanFormula constraint = i1.IF_ICMPNE(i2); @@ -2122,7 +2359,9 @@ public void visitIF_ICMPNE(IF_ICMPNE inst) throws SymbolicInstructionException{ BranchCoverage.addVisitedBranch(inst.iid); - symbolicTraceHandler.checkAndSetBranch(isBranchTaken, constraint, iid); + merger.atBranch(inst.iid, isBranchTaken, constraint, + symbolicTraceHandler.checkAndSetBranch(isBranchTaken, constraint, iid), + ThreadHandler.getSolverContext(currentThread().getId())); } catch (Throwable t) { throw new SymbolicInstructionException(inst, t); } @@ -2133,8 +2372,8 @@ public void visitIF_ICMPNE(IF_ICMPNE inst) throws SymbolicInstructionException{ * * @param inst The IINC instruction */ - public void visitIINC(IINC inst) throws SymbolicInstructionException{ - try{ + public void visitIINC(IINC inst) throws SymbolicInstructionException { + try { // ToDo (Nils): What happens if this is the first time the local is referenced? The next // line should return a Placeholder then IntValue i1 = stack.getLocal(inst.var).asIntValue(); @@ -2151,8 +2390,8 @@ public void visitIINC(IINC inst) throws SymbolicInstructionException{ * * @param inst The ILOAD instruction (including ILOAD_0 - ILOAD_3) */ - public void visitILOAD(ILOAD inst) throws SymbolicInstructionException{ - try{ + public void visitILOAD(ILOAD inst) throws SymbolicInstructionException { + try { // ToDO (Nils): Why are ILOAD_0 etc not present? Where did they catch them and used this // case? stack.pushOperand(stack.getLocal(inst.var)); @@ -2166,8 +2405,8 @@ public void visitILOAD(ILOAD inst) throws SymbolicInstructionException{ * * @param inst The IMUL instruction */ - public void visitIMUL(IMUL inst) throws SymbolicInstructionException{ - try{ + public void visitIMUL(IMUL inst) throws SymbolicInstructionException { + try { IntValue i2 = stack.popOperand().asIntValue(); IntValue i1 = stack.popOperand().asIntValue(); stack.pushOperand(i1.IMUL(i2)); @@ -2181,8 +2420,8 @@ public void visitIMUL(IMUL inst) throws SymbolicInstructionException{ * * @param inst The INEG instruction */ - public void visitINEG(INEG inst) throws SymbolicInstructionException{ - try{ + public void visitINEG(INEG inst) throws SymbolicInstructionException { + try { IntValue i1 = stack.popOperand().asIntValue(); stack.pushOperand(i1.INEG()); } catch (Throwable t) { @@ -2198,7 +2437,7 @@ public void visitINEG(INEG inst) throws SymbolicInstructionException{ * * @param inst The INSTANCEOF instruction. */ - public void visitINSTANCEOF(INSTANCEOF inst) throws SymbolicInstructionException{ + public void visitINSTANCEOF(INSTANCEOF inst) throws SymbolicInstructionException { try { stack.popOperand(); stack.pushOperand(PlaceHolder.instance); @@ -2208,37 +2447,127 @@ public void visitINSTANCEOF(INSTANCEOF inst) throws SymbolicInstructionException } } + /** + * Records the branches a String or StringBuilder call takes inside the library, which their + * models rely on (see {@link StringCallBranches}): its index checks, and pins of the concrete + * values a model uses. On a normal return all of them held; a call that threw failed one of the + * index checks, if the receiver's chars say so, and that exception is then fully explained by + * the branches. + * + * @param returned Whether the call returned normally + * @return Whether the call threw at one of its index checks + */ + private boolean recordStringCallBranches(long iid, String name, String desc, ObjectValue instance, Value[] arguments, boolean returned) throws NoThreadContextException { + return recordCallBranches(iid, name, StringCallBranches.of(instance, name, desc, arguments), returned); + } + + /** + * Records the branches of a library call (see {@link #recordStringCallBranches}): on a normal + * return all of them held, a call that threw failed the first one that does not hold. + * + * @return Whether the call threw at one of its branches + */ + private boolean recordCallBranches(long iid, String name, List branches, boolean returned) throws NoThreadContextException { + int failed = -1; + for (int i = 0; i < branches.size() && failed < 0; i++) { + if (branches.get(i).check() && !branches.get(i).holds()) failed = i; + } + // A normal return where an index check failed (or a throw where all held): the shadow + // does not match the call; that is left to the model and to recordThrowingCall. + if (branches.isEmpty() || returned == (failed >= 0)) return false; + int last = returned ? branches.size() - 1 : failed; + FormulaManager fmgr = ThreadHandler.getSolverContext(Thread.currentThread().getId()).getFormulaManager(); + for (int i = 0; i <= last; i++) { + StringCallBranches.Branch branch = branches.get(i); + if (!fmgr.extractVariables(branch.condition()).isEmpty()) { + symbolicTraceHandler.checkAndSetLibraryBranch(branch.holds(), branch.condition(), determineIid(iid), "#" + name); + } + } + return !returned; + } + + /** + * As {@link #recordThrowingCall}, unless the call threw at an index check of a String or + * StringBuilder method (see {@link #recordStringCallBranches}), which the trace then explains. + */ + private void recordThrowingInstanceCall(long iid, String owner, String name, String desc, long invokeId) throws NoThreadContextException { + boolean explained; + try { + ObjectValue instance = stack.getInstance(); + Value[] arguments = stack.fetchArgumentsFromLocals(Type.getArgumentTypes(desc), true); + explained = recordStringCallBranches(iid, name, desc, instance, arguments, false); + } catch (Exception e) { + explained = false; + } + if (explained) { + // Not an exception the static pre-analysis graph models (it walks over library calls). + recordUnmodelledException(iid); + } else { + recordThrowingCall(iid, owner, name, desc, invokeId, true); + } + } + + /** + * Handles a call to an uninstrumented method that threw instead of returning: its model, if it + * has one, is not consulted (that happens on a normal return), and whether it throws may depend + * on symbolic arguments, a branch nothing in the trace captures. With symbolic arguments the + * search past this call is therefore incomplete, which is recorded as a context loss. + */ + private void recordThrowingCall(long iid, String owner, String name, String desc, long invokeId, boolean isInstance) throws NoThreadContextException { + // Thrown by uninstrumented code, or before the callee ran (a null receiver, an Error): + // nothing the graph models. + recordUnmodelledException(iid); + boolean symbolic; + try { + Value[] arguments = stack.fetchArgumentsFromLocals(Type.getArgumentTypes(desc), isInstance); + symbolic = isInstance && stack.getInstance().isSymbolic(); + for (Value argument : arguments) symbolic = symbolic || argument.isSymbolic(); + } catch (Exception e) { + symbolic = true; // cannot tell, so assume the worst + } + if (!symbolic && !InvocationHandler.isUninstrumentedObjectFactory(owner, name)) return; + logger.warn("Invocation of method {} in class {} with arguments {} threw, causes context loss", name, owner, desc); + symbolicTraceHandler.recordSymbolicContextLoss(); + long threadId = Thread.currentThread().getId(); + InvocationEntry entry = new InvocationEntry(owner, name, desc, isInstance, invokeId, true); + ThreadHandler.recordMissingInvocation(threadId, entry); + ThreadHandler.recordContextLossInvocation(threadId, entry); + } + /** * Invokes an interface method on an object reference from the symbolic stack * * @param inst The INVOKEINTERFACE instruction */ - public void visitINVOKEINTERFACE(INVOKEINTERFACE inst) throws SymbolicInstructionException{ - try{ - stack.newStackFrame(inst.desc, inst.owner, inst.name, true); + public void visitINVOKEINTERFACE(INVOKEINTERFACE inst) throws SymbolicInstructionException { + try { + LoopStateMatcher.call(inst.owner, inst.name, false); + stack.newStackFrame(inst.desc, inst.owner, inst.name, true, inst.iid); if (stack.getNextInst() instanceof INVOKEMETHOD_END) { ObjectValue instance = stack.getInstance(); Value[] arguments = stack.fetchArgumentsFromLocals(Type.getArgumentTypes(inst.desc), true); - Value retVal = - InvocationHandler.invoke( - symbolicTraceHandler, - inst.desc, - inst.owner, - inst.name, - inst.invokeId, - new ArrayList<>(Arrays.asList(arguments)), - true, - instance); + recordStringCallBranches(inst.iid, inst.name, inst.desc, instance, arguments, true); + Value retVal = InvocationHandler.invoke( + symbolicTraceHandler, + inst.desc, + inst.owner, + inst.name, + inst.invokeId, + new ArrayList<>(Arrays.asList(arguments)), + true, + instance); stack.setReturnValue(retVal); - } + } else if (stack.getNextInst() instanceof INVOKEMETHOD_EXCEPTION) { + recordThrowingInstanceCall(inst.iid, inst.owner, inst.name, inst.desc, inst.invokeId); + } - if (!inst.owner.equals("de/uzl/its/swat/Main")) { - symbolicTraceHandler.recordInvocation( - determineIid(inst.iid), inst.getClass().getCanonicalName()); - } + if (!inst.owner.equals("de/uzl/its/swat/Main")) { + symbolicTraceHandler.recordInvocation( + determineIid(inst.iid), inst.getClass().getCanonicalName()); + } } catch (Throwable t) { throw new SymbolicInstructionException(inst, t); } @@ -2249,24 +2578,27 @@ public void visitINVOKEINTERFACE(INVOKEINTERFACE inst) throws SymbolicInstructio * * @param inst The INVOKESPECIAL instruction */ - public void visitINVOKESPECIAL(INVOKESPECIAL inst) throws SymbolicInstructionException{ - try{ - stack.newStackFrame(inst.desc, inst.owner, inst.name, true); + public void visitINVOKESPECIAL(INVOKESPECIAL inst) throws SymbolicInstructionException { + try { + LoopStateMatcher.call(inst.owner, inst.name, false); + stack.newStackFrame(inst.desc, inst.owner, inst.name, true, inst.iid); if (stack.getNextInst() instanceof INVOKEMETHOD_END) { ObjectValue instance = stack.getInstance(); Value[] arguments = stack.fetchArgumentsFromLocals(Type.getArgumentTypes(inst.desc), true); - Value retVal = - InvocationHandler.invoke( - symbolicTraceHandler, - inst.desc, - inst.owner, - inst.name, - inst.invokeId, - new ArrayList<>(Arrays.asList(arguments)), - true, - instance); + recordStringCallBranches(inst.iid, inst.name, inst.desc, instance, arguments, true); + Value retVal = InvocationHandler.invoke( + symbolicTraceHandler, + inst.desc, + inst.owner, + inst.name, + inst.invokeId, + new ArrayList<>(Arrays.asList(arguments)), + true, + instance); stack.setReturnValue(retVal); + } else if (stack.getNextInst() instanceof INVOKEMETHOD_EXCEPTION) { + recordThrowingInstanceCall(inst.iid, inst.owner, inst.name, inst.desc, inst.invokeId); } if (!inst.owner.equals("de/uzl/its/swat/Main")) { @@ -2283,23 +2615,26 @@ public void visitINVOKESPECIAL(INVOKESPECIAL inst) throws SymbolicInstructionExc * * @param inst The INVOKESTATIC instruction */ - public void visitINVOKESTATIC(INVOKESTATIC inst) throws SymbolicInstructionException{ - try{ - stack.newStackFrame(inst.desc, inst.owner, inst.name, false); + public void visitINVOKESTATIC(INVOKESTATIC inst) throws SymbolicInstructionException { + try { + LoopStateMatcher.call(inst.owner, inst.name, true); + stack.newStackFrame(inst.desc, inst.owner, inst.name, false, inst.iid); if (stack.getNextInst() instanceof INVOKEMETHOD_END) { - Value[] arguments = stack.fetchArgumentsFromLocals(Type.getArgumentTypes(inst.desc), false); - Value retVal = - InvocationHandler.invoke( - symbolicTraceHandler, - inst.desc, - inst.owner, - inst.name, - inst.invokeId, - new ArrayList<>(Arrays.asList(arguments)), - false, - null); + Value[] arguments = stack.fetchArgumentsFromLocals(Type.getArgumentTypes(inst.desc), false); + recordParseCallBranches(inst.iid, inst.owner, inst.name, inst.desc, arguments); + Value retVal = InvocationHandler.invoke( + symbolicTraceHandler, + inst.desc, + inst.owner, + inst.name, + inst.invokeId, + new ArrayList<>(Arrays.asList(arguments)), + false, + null); stack.setReturnValue(retVal); + } else if (stack.getNextInst() instanceof INVOKEMETHOD_EXCEPTION) { + recordThrowingStaticCall(inst.iid, inst.owner, inst.name, inst.desc, inst.invokeId); } if (!inst.owner.equals("de/uzl/its/swat/Main")) { @@ -2311,29 +2646,81 @@ public void visitINVOKESTATIC(INVOKESTATIC inst) throws SymbolicInstructionExcep } } + /** + * Records the branches of a parse call that returned normally (see {@link NumberParsing}): its + * checks, which all held, then those that tell how its value is modelled. + */ + private void recordParseCallBranches(long iid, String owner, String name, String desc, Value[] arguments) throws NoThreadContextException { + List checks = NumberParsing.branches(owner, name, desc, arguments); + if (checks.isEmpty()) return; + recordCallBranches(iid, name, checks, true); + FormulaManager fmgr = ThreadHandler.getSolverContext(Thread.currentThread().getId()).getFormulaManager(); + for (StringCallBranches.Branch branch : NumberParsing.returnBranches(owner, name, desc, arguments)) { + if (!fmgr.extractVariables(branch.condition()).isEmpty()) { + symbolicTraceHandler.checkAndSetLibraryBranch(branch.holds(), branch.condition(), determineIid(iid), "#" + name); + } + } + } + + /** + * As {@link #recordThrowingCall}, unless the call is a parse call that threw a + * NumberFormatException at one of its branches (see {@link NumberParsing#branches}), which the + * trace then explains. + */ + private void recordThrowingStaticCall(long iid, String owner, String name, String desc, long invokeId) throws NoThreadContextException { + boolean explained; + try { + Value[] arguments = stack.fetchArgumentsFromLocals(Type.getArgumentTypes(desc), false); + explained = recordCallBranches(iid, name, NumberParsing.branches(owner, name, desc, arguments), false); + } catch (Exception e) { + explained = false; + } + if (explained) { + recordUnmodelledException(iid); + } else { + recordThrowingCall(iid, owner, name, desc, invokeId, false); + } + } + /** * Invokes a virtual method on an object reference from the symbolic stack * * @param inst The INVOKEVIRTUAL instruction */ - public void visitINVOKEVIRTUAL(INVOKEVIRTUAL inst) throws SymbolicInstructionException{ - try{ - stack.newStackFrame(inst.desc, inst.owner, inst.name, true); + public void visitINVOKEVIRTUAL(INVOKEVIRTUAL inst) throws SymbolicInstructionException { + try { + LoopStateMatcher.call(inst.owner, inst.name, false); + if (previousWasUnpack && isStaticMethod(inst.owner, inst.name, inst.desc)) { + // A static method called through Method.invoke: the instrumentation invokes it like + // an instance method, with Method.invoke's (null) receiver below the arguments. + // The callee has no receiver, so the frame must not have one either. + previousWasUnpack = false; + stack.newStackFrame(inst.desc, inst.owner, inst.name, false, inst.iid); + Frame callee = stack.popFrame(); + stack.popOperand(); // the null receiver + stack.pushFrame(callee); + symbolicTraceHandler.recordInvocation(determineIid(inst.iid), inst.getClass().getCanonicalName()); + return; + } + previousWasUnpack = false; + stack.newStackFrame(inst.desc, inst.owner, inst.name, true, inst.iid); if (stack.getNextInst() instanceof INVOKEMETHOD_END) { ObjectValue instance = stack.getInstance(); Value[] arguments = stack.fetchArgumentsFromLocals(Type.getArgumentTypes(inst.desc), true); - Value retVal = - InvocationHandler.invoke( - symbolicTraceHandler, - inst.desc, - inst.owner, - inst.name, - inst.invokeId, - new ArrayList<>(Arrays.asList(arguments)), - true, - instance); + recordStringCallBranches(inst.iid, inst.name, inst.desc, instance, arguments, true); + Value retVal = InvocationHandler.invoke( + symbolicTraceHandler, + inst.desc, + inst.owner, + inst.name, + inst.invokeId, + new ArrayList<>(Arrays.asList(arguments)), + true, + instance); stack.setReturnValue(retVal); + } else if (stack.getNextInst() instanceof INVOKEMETHOD_EXCEPTION) { + recordThrowingInstanceCall(inst.iid, inst.owner, inst.name, inst.desc, inst.invokeId); } if (!inst.owner.equals("de/uzl/its/swat/Main")) { @@ -2350,23 +2737,25 @@ public void visitINVOKEVIRTUAL(INVOKEVIRTUAL inst) throws SymbolicInstructionExc * * @param inst The INVOKEDYNAMIC instruction */ - public void visitINVOKEDYNAMIC(INVOKEDYNAMIC inst) throws SymbolicInstructionException{ - try{ - stack.newStackFrame(inst.desc, inst.owner, inst.name, false); + public void visitINVOKEDYNAMIC(INVOKEDYNAMIC inst) throws SymbolicInstructionException { + try { + LoopStateMatcher.invokeDynamic(); + stack.newStackFrame(inst.desc, inst.owner, inst.name, false, inst.iid); if (stack.getNextInst() instanceof INVOKEMETHOD_END) { Value[] arguments = stack.fetchArgumentsFromLocals(Type.getArgumentTypes(inst.desc), false); - Value retVal = - InvocationHandler.invoke( - symbolicTraceHandler, - inst.desc, - inst.owner, - inst.name, - inst.invokeId, - new ArrayList<>(Arrays.asList(arguments)), - false, - null); + Value retVal = InvocationHandler.invoke( + symbolicTraceHandler, + inst.desc, + inst.owner, + inst.name, + inst.invokeId, + new ArrayList<>(Arrays.asList(arguments)), + false, + null); stack.setReturnValue(retVal); + } else if (stack.getNextInst() instanceof INVOKEMETHOD_EXCEPTION) { + recordThrowingCall(inst.iid, inst.owner, inst.name, inst.desc, inst.invokeId, false); } if (!inst.owner.equals("de/uzl/its/swat/Main")) { @@ -2383,8 +2772,8 @@ public void visitINVOKEDYNAMIC(INVOKEDYNAMIC inst) throws SymbolicInstructionExc * * @param inst The IOR instruction */ - public void visitIOR(IOR inst) throws SymbolicInstructionException{ - try{ + public void visitIOR(IOR inst) throws SymbolicInstructionException { + try { IntValue i2 = stack.popOperand().asIntValue(); IntValue i1 = stack.popOperand().asIntValue(); stack.pushOperand(i1.IOR(i2)); @@ -2398,7 +2787,7 @@ public void visitIOR(IOR inst) throws SymbolicInstructionException{ * * @param inst The IREM instruction */ - public void visitIREM(IREM inst) throws SymbolicInstructionException{ + public void visitIREM(IREM inst) throws SymbolicInstructionException { try { IntValue i2 = stack.popOperand().asIntValue(); IntValue i1 = stack.popOperand().asIntValue(); @@ -2422,8 +2811,8 @@ public void visitIREM(IREM inst) throws SymbolicInstructionException{ * * @param inst The IRETURN instruction */ - public void visitIRETURN(IRETURN inst) throws SymbolicInstructionException{ - try{ + public void visitIRETURN(IRETURN inst) throws SymbolicInstructionException { + try { symbolicTraceHandler.addSpecialElement(determineIid(inst.iid), "IRETURN"); stack.setReturnValue(stack.popOperand()); // checkAndSetException(inst); @@ -2437,8 +2826,8 @@ public void visitIRETURN(IRETURN inst) throws SymbolicInstructionException{ * * @param inst The ISHL instruction */ - public void visitISHL(ISHL inst) throws SymbolicInstructionException{ - try{ + public void visitISHL(ISHL inst) throws SymbolicInstructionException { + try { IntValue i2 = stack.popOperand().asIntValue(); IntValue i1 = stack.popOperand().asIntValue(); stack.pushOperand(i1.ISHL(i2)); @@ -2453,8 +2842,8 @@ public void visitISHL(ISHL inst) throws SymbolicInstructionException{ * * @param inst The ISHR instruction */ - public void visitISHR(ISHR inst) throws SymbolicInstructionException{ - try{ + public void visitISHR(ISHR inst) throws SymbolicInstructionException { + try { IntValue i2 = stack.popOperand().asIntValue(); IntValue i1 = stack.popOperand().asIntValue(); stack.pushOperand(i1.ISHR(i2)); @@ -2468,8 +2857,8 @@ public void visitISHR(ISHR inst) throws SymbolicInstructionException{ * * @param inst The ISTORE instruction */ - public void visitISTORE(ISTORE inst) throws SymbolicInstructionException{ - try{ + public void visitISTORE(ISTORE inst) throws SymbolicInstructionException { + try { stack.setLocal(inst.var, stack.popOperand()); } catch (Throwable t) { throw new SymbolicInstructionException(inst, t); @@ -2481,8 +2870,8 @@ public void visitISTORE(ISTORE inst) throws SymbolicInstructionException{ * * @param inst The ISUB instruction */ - public void visitISUB(ISUB inst) throws SymbolicInstructionException{ - try{ + public void visitISUB(ISUB inst) throws SymbolicInstructionException { + try { IntValue i2 = stack.popOperand().asIntValue(); IntValue i1 = stack.popOperand().asIntValue(); stack.pushOperand(i1.ISUB(i2)); @@ -2496,8 +2885,8 @@ public void visitISUB(ISUB inst) throws SymbolicInstructionException{ * * @param inst The IUSHR instruction */ - public void visitIUSHR(IUSHR inst) throws SymbolicInstructionException{ - try{ + public void visitIUSHR(IUSHR inst) throws SymbolicInstructionException { + try { IntValue i2 = stack.popOperand().asIntValue(); IntValue i1 = stack.popOperand().asIntValue(); stack.pushOperand(i1.IUSHR(i2)); @@ -2512,8 +2901,8 @@ public void visitIUSHR(IUSHR inst) throws SymbolicInstructionException{ * * @param inst The IXOR instruction */ - public void visitIXOR(IXOR inst) throws SymbolicInstructionException{ - try{ + public void visitIXOR(IXOR inst) throws SymbolicInstructionException { + try { IntValue i2 = stack.popOperand().asIntValue(); IntValue i1 = stack.popOperand().asIntValue(); stack.pushOperand(i1.IXOR(i2)); @@ -2530,10 +2919,11 @@ public void visitIXOR(IXOR inst) throws SymbolicInstructionException{ * * @param inst Either the JSR or JSR_W instruction */ - public void visitJSR(JSR inst) throws SymbolicInstructionException{} + public void visitJSR(JSR inst) throws SymbolicInstructionException { + } - public void visitL2D(L2D inst) throws SymbolicInstructionException{ - try{ + public void visitL2D(L2D inst) throws SymbolicInstructionException { + try { LongValue i1 = stack.popWideOperand().asLongValue(); stack.pushWideOperand(i1.L2D()); } catch (Throwable t) { @@ -2541,8 +2931,8 @@ public void visitL2D(L2D inst) throws SymbolicInstructionException{ } } - public void visitL2F(L2F inst) throws SymbolicInstructionException{ - try{ + public void visitL2F(L2F inst) throws SymbolicInstructionException { + try { LongValue i1 = stack.popWideOperand().asLongValue(); stack.pushOperand(i1.L2F()); } catch (Throwable t) { @@ -2550,8 +2940,8 @@ public void visitL2F(L2F inst) throws SymbolicInstructionException{ } } - public void visitL2I(L2I inst) throws SymbolicInstructionException{ - try{ + public void visitL2I(L2I inst) throws SymbolicInstructionException { + try { LongValue i1 = stack.popWideOperand().asLongValue(); stack.pushOperand(i1.L2I()); } catch (Throwable t) { @@ -2564,8 +2954,8 @@ public void visitL2I(L2I inst) throws SymbolicInstructionException{ * * @param inst The LADD instruction */ - public void visitLADD(LADD inst) throws SymbolicInstructionException{ - try{ + public void visitLADD(LADD inst) throws SymbolicInstructionException { + try { LongValue i2 = stack.popWideOperand().asLongValue(); LongValue i1 = stack.popWideOperand().asLongValue(); stack.pushWideOperand(i1.LADD(i2)); @@ -2579,7 +2969,7 @@ public void visitLADD(LADD inst) throws SymbolicInstructionException{ * * @param inst The LALOAD instruction */ - public void visitLALOAD(LALOAD inst) throws SymbolicInstructionException{ + public void visitLALOAD(LALOAD inst) throws SymbolicInstructionException { try { IntValue idx = stack.popOperand().asIntValue(); ObjectValue arr = (ObjectValue) stack.popOperand(); @@ -2591,6 +2981,7 @@ public void visitLALOAD(LALOAD inst) throws SymbolicInstructionException{ enforceException(); } } else { + recordUntrackedBoundsCheck(inst, idx, arr); logger.warn("[LALOAD]: Unknown array type"); stack.pushWideOperand( arr.getName() != null @@ -2609,8 +3000,8 @@ public void visitLALOAD(LALOAD inst) throws SymbolicInstructionException{ * * @param inst The LAND instruction */ - public void visitLAND(LAND inst) throws SymbolicInstructionException{ - try{ + public void visitLAND(LAND inst) throws SymbolicInstructionException { + try { LongValue i2 = stack.popWideOperand().asLongValue(); LongValue i1 = stack.popWideOperand().asLongValue(); stack.pushWideOperand(i1.LAND(i2)); @@ -2624,7 +3015,7 @@ public void visitLAND(LAND inst) throws SymbolicInstructionException{ * * @param inst The LASTORE instruction */ - public void visitLASTORE(LASTORE inst) throws SymbolicInstructionException{ + public void visitLASTORE(LASTORE inst) throws SymbolicInstructionException { try { LongValue val = stack.popWideOperand().asLongValue(); IntValue idx = stack.popOperand().asIntValue(); @@ -2637,6 +3028,7 @@ public void visitLASTORE(LASTORE inst) throws SymbolicInstructionException{ enforceException(); } } else { + recordUntrackedBoundsCheck(inst, idx, ref); logger.warn("[LASTORE]: Unknown array type"); } } catch (Throwable t) { @@ -2649,8 +3041,8 @@ public void visitLASTORE(LASTORE inst) throws SymbolicInstructionException{ * * @param inst The LCMP instruction */ - public void visitLCMP(LCMP inst) throws SymbolicInstructionException{ - try{ + public void visitLCMP(LCMP inst) throws SymbolicInstructionException { + try { LongValue i2 = stack.popWideOperand().asLongValue(); LongValue i1 = stack.popWideOperand().asLongValue(); stack.pushOperand(i1.LCMP(i2)); @@ -2664,8 +3056,8 @@ public void visitLCMP(LCMP inst) throws SymbolicInstructionException{ * * @param inst The LCONST_0 instruction */ - public void visitLCONST_0(LCONST_0 inst) throws SymbolicInstructionException{ - try{ + public void visitLCONST_0(LCONST_0 inst) throws SymbolicInstructionException { + try { stack.pushWideOperand(ValueFactory.createNumericalValue(ValueType.longValue, 0L)); } catch (Throwable t) { throw new SymbolicInstructionException(inst, t); @@ -2677,8 +3069,8 @@ public void visitLCONST_0(LCONST_0 inst) throws SymbolicInstructionException{ * * @param inst The LCONST_1 instruction */ - public void visitLCONST_1(LCONST_1 inst) throws SymbolicInstructionException{ - try{ + public void visitLCONST_1(LCONST_1 inst) throws SymbolicInstructionException { + try { stack.pushWideOperand(ValueFactory.createNumericalValue(ValueType.longValue, 1L)); } catch (Throwable t) { throw new SymbolicInstructionException(inst, t); @@ -2690,8 +3082,8 @@ public void visitLCONST_1(LCONST_1 inst) throws SymbolicInstructionException{ * * @param inst The (artificial) LDC_String instruction */ - public void visitLDC_String(LDC_String inst) throws SymbolicInstructionException{ - try{ + public void visitLDC_String(LDC_String inst) throws SymbolicInstructionException { + try { stack.pushOperand(ValueFactory.createStringValue(inst.c, inst.address)); checkAndSetException(inst); } catch (Throwable t) { @@ -2704,8 +3096,8 @@ public void visitLDC_String(LDC_String inst) throws SymbolicInstructionException * * @param inst The (artificial) LDC_Object instruction */ - public void visitLDC_Object(LDC_Object inst) throws SymbolicInstructionException{ - try{ + public void visitLDC_Object(LDC_Object inst) throws SymbolicInstructionException { + try { Value tmp = stack.getFromHeap(inst.c); if (tmp != null) { stack.pushOperand(tmp); @@ -2726,8 +3118,8 @@ public void visitLDC_Object(LDC_Object inst) throws SymbolicInstructionException * * @param inst The (artificial) LDC_double instruction */ - public void visitLDC_double(LDC_double inst) throws SymbolicInstructionException{ - try{ + public void visitLDC_double(LDC_double inst) throws SymbolicInstructionException { + try { stack.pushWideOperand(ValueFactory.createNumericalValue(ValueType.doubleValue, inst.c)); checkAndSetException(inst); } catch (Throwable t) { @@ -2740,8 +3132,8 @@ public void visitLDC_double(LDC_double inst) throws SymbolicInstructionException * * @param inst The (artificial) LDC_float instruction */ - public void visitLDC_float(LDC_float inst) throws SymbolicInstructionException{ - try{ + public void visitLDC_float(LDC_float inst) throws SymbolicInstructionException { + try { stack.pushOperand(ValueFactory.createNumericalValue(ValueType.floatValue, inst.c)); checkAndSetException(inst); } catch (Throwable t) { @@ -2754,8 +3146,8 @@ public void visitLDC_float(LDC_float inst) throws SymbolicInstructionException{ * * @param inst The (artificial) LDC_int instruction */ - public void visitLDC_int(LDC_int inst) throws SymbolicInstructionException{ - try{ + public void visitLDC_int(LDC_int inst) throws SymbolicInstructionException { + try { stack.pushOperand(ValueFactory.createNumericalValue(ValueType.intValue, inst.c)); checkAndSetException(inst); } catch (Throwable t) { @@ -2768,8 +3160,8 @@ public void visitLDC_int(LDC_int inst) throws SymbolicInstructionException{ * * @param inst The (artificial) LDC_long instruction */ - public void visitLDC_long(LDC_long inst) throws SymbolicInstructionException{ - try{ + public void visitLDC_long(LDC_long inst) throws SymbolicInstructionException { + try { stack.pushWideOperand(ValueFactory.createNumericalValue(ValueType.longValue, inst.c)); checkAndSetException(inst); } catch (Throwable t) { @@ -2782,7 +3174,7 @@ public void visitLDC_long(LDC_long inst) throws SymbolicInstructionException{ * * @param inst The LDIV instruction */ - public void visitLDIV(LDIV inst) throws SymbolicInstructionException{ + public void visitLDIV(LDIV inst) throws SymbolicInstructionException { try { LongValue l2 = stack.popWideOperand().asLongValue(); LongValue l1 = stack.popWideOperand().asLongValue(); @@ -2806,8 +3198,8 @@ public void visitLDIV(LDIV inst) throws SymbolicInstructionException{ * * @param inst One of the LLOAD isntructions (LLOAD, LLOAD_0 - LLOAD_3) */ - public void visitLLOAD(LLOAD inst) throws SymbolicInstructionException{ - try{ + public void visitLLOAD(LLOAD inst) throws SymbolicInstructionException { + try { stack.pushWideOperand(stack.getWideLocal(inst.var)); } catch (Throwable t) { throw new SymbolicInstructionException(inst, t); @@ -2819,8 +3211,8 @@ public void visitLLOAD(LLOAD inst) throws SymbolicInstructionException{ * * @param inst The LMUL instruction */ - public void visitLMUL(LMUL inst) throws SymbolicInstructionException{ - try{ + public void visitLMUL(LMUL inst) throws SymbolicInstructionException { + try { LongValue i2 = stack.popWideOperand().asLongValue(); LongValue i1 = stack.popWideOperand().asLongValue(); stack.pushWideOperand(i1.LMUL(i2)); @@ -2834,8 +3226,8 @@ public void visitLMUL(LMUL inst) throws SymbolicInstructionException{ * * @param inst The LNEG isntruction */ - public void visitLNEG(LNEG inst) throws SymbolicInstructionException{ - try{ + public void visitLNEG(LNEG inst) throws SymbolicInstructionException { + try { LongValue i1 = stack.popWideOperand().asLongValue(); stack.pushWideOperand(i1.LNEG()); } catch (Throwable t) { @@ -2848,8 +3240,8 @@ public void visitLNEG(LNEG inst) throws SymbolicInstructionException{ * * @param inst The LOR instruction */ - public void visitLOR(LOR inst) throws SymbolicInstructionException{ - try{ + public void visitLOR(LOR inst) throws SymbolicInstructionException { + try { LongValue i2 = stack.popWideOperand().asLongValue(); LongValue i1 = stack.popWideOperand().asLongValue(); stack.pushWideOperand(i1.LOR(i2)); @@ -2863,7 +3255,7 @@ public void visitLOR(LOR inst) throws SymbolicInstructionException{ * * @param inst The LREM instruction */ - public void visitLREM(LREM inst) throws SymbolicInstructionException{ + public void visitLREM(LREM inst) throws SymbolicInstructionException { try { LongValue i2 = stack.popWideOperand().asLongValue(); LongValue i1 = stack.popWideOperand().asLongValue(); @@ -2887,8 +3279,8 @@ public void visitLREM(LREM inst) throws SymbolicInstructionException{ * * @param inst The LRETURN instruction */ - public void visitLRETURN(LRETURN inst) throws SymbolicInstructionException{ - try{ + public void visitLRETURN(LRETURN inst) throws SymbolicInstructionException { + try { symbolicTraceHandler.addSpecialElement(determineIid(inst.iid), "lRETURN"); stack.setReturnValue(stack.popWideOperand()); // checkAndSetException(inst); @@ -2902,8 +3294,8 @@ public void visitLRETURN(LRETURN inst) throws SymbolicInstructionException{ * * @param inst The LSHL instruction */ - public void visitLSHL(LSHL inst) throws SymbolicInstructionException{ - try{ + public void visitLSHL(LSHL inst) throws SymbolicInstructionException { + try { IntValue i2 = stack.popOperand().asIntValue(); LongValue i1 = stack.popWideOperand().asLongValue(); stack.pushWideOperand(i1.LSHL(i2)); @@ -2917,8 +3309,8 @@ public void visitLSHL(LSHL inst) throws SymbolicInstructionException{ * * @param inst The LSHR instruction */ - public void visitLSHR(LSHR inst) throws SymbolicInstructionException{ - try{ + public void visitLSHR(LSHR inst) throws SymbolicInstructionException { + try { IntValue i2 = stack.popOperand().asIntValue(); LongValue i1 = stack.popWideOperand().asLongValue(); stack.pushWideOperand(i1.LSHR(i2)); @@ -2932,8 +3324,8 @@ public void visitLSHR(LSHR inst) throws SymbolicInstructionException{ * * @param inst One of the LSTORE instructions (LSTORE, LSTORE_0 - LSTORE_3) */ - public void visitLSTORE(LSTORE inst) throws SymbolicInstructionException{ - try{ + public void visitLSTORE(LSTORE inst) throws SymbolicInstructionException { + try { stack.setWideLocal(inst.var, stack.popWideOperand()); } catch (Throwable t) { throw new SymbolicInstructionException(inst, t); @@ -2945,8 +3337,8 @@ public void visitLSTORE(LSTORE inst) throws SymbolicInstructionException{ * * @param inst The LSUB instruction */ - public void visitLSUB(LSUB inst) throws SymbolicInstructionException{ - try{ + public void visitLSUB(LSUB inst) throws SymbolicInstructionException { + try { LongValue i2 = stack.popWideOperand().asLongValue(); LongValue i1 = stack.popWideOperand().asLongValue(); stack.pushWideOperand(i1.LSUB(i2)); @@ -2961,8 +3353,8 @@ public void visitLSUB(LSUB inst) throws SymbolicInstructionException{ * * @param inst The LUSHR instruction */ - public void visitLUSHR(LUSHR inst) throws SymbolicInstructionException{ - try{ + public void visitLUSHR(LUSHR inst) throws SymbolicInstructionException { + try { IntValue i2 = stack.popOperand().asIntValue(); LongValue i1 = stack.popWideOperand().asLongValue(); stack.pushWideOperand(i1.LUSHR(i2)); @@ -2976,8 +3368,8 @@ public void visitLUSHR(LUSHR inst) throws SymbolicInstructionException{ * * @param inst The LXOR instruction */ - public void visitLXOR(LXOR inst) throws SymbolicInstructionException{ - try{ + public void visitLXOR(LXOR inst) throws SymbolicInstructionException { + try { LongValue i2 = stack.popWideOperand().asLongValue(); LongValue i1 = stack.popWideOperand().asLongValue(); stack.pushWideOperand(i1.LXOR(i2)); @@ -2991,8 +3383,8 @@ public void visitLXOR(LXOR inst) throws SymbolicInstructionException{ * * @param inst The MONITORENTER instruction. */ - public void visitMONITORENTER(MONITORENTER inst) throws SymbolicInstructionException{ - try{ + public void visitMONITORENTER(MONITORENTER inst) throws SymbolicInstructionException { + try { stack.popOperand(); checkAndSetException(inst); } catch (Throwable t) { @@ -3005,8 +3397,8 @@ public void visitMONITORENTER(MONITORENTER inst) throws SymbolicInstructionExcep * * @param inst The MONITOREXIT instruction. */ - public void visitMONITOREXIT(MONITOREXIT inst) throws SymbolicInstructionException{ - try{ + public void visitMONITOREXIT(MONITOREXIT inst) throws SymbolicInstructionException { + try { stack.popOperand(); checkAndSetException(inst); } catch (Throwable t) { @@ -3014,10 +3406,10 @@ public void visitMONITOREXIT(MONITOREXIT inst) throws SymbolicInstructionExcepti } } - public void visitCLINIT(CLINIT inst) throws SymbolicInstructionException{ + public void visitCLINIT(CLINIT inst) throws SymbolicInstructionException { try { String owner = classDepot.getClassName(inst.cIdx); - stack.newStackFrame(inst.desc, owner, inst.name, false); + stack.newStackFrame(inst.desc, owner, inst.name, false, inst.iid); symbolicTraceHandler.recordInvocation( determineIid(inst.iid), inst.getClass().getCanonicalName()); } catch (Throwable t) { @@ -3026,19 +3418,44 @@ public void visitCLINIT(CLINIT inst) throws SymbolicInstructionException{ // checkAndSetException(inst); } - public void visitUNPACK_INVOKE_PARAMETER(UNPACK_INVOKE_PARAMETER inst) throws SymbolicInstructionException{ - try{ + /** Whether the last instruction unpacked Method.invoke's arguments for the invoke that follows. */ + private boolean previousWasUnpack = false; + + private static boolean isStaticMethod(String owner, String name, String desc) { + try { + Class cls = Class.forName(owner.replace('/', '.'), false, ClassLoader.getSystemClassLoader()); + for (java.lang.reflect.Method m : cls.getDeclaredMethods()) { + if (m.getName().equals(name) && Type.getMethodDescriptor(m).equals(desc)) { + return java.lang.reflect.Modifier.isStatic(m.getModifiers()); + } + } + } catch (Throwable t) { + // Not resolvable: treat as an instance method, as before. + } + return false; + } + + public void visitUNPACK_INVOKE_PARAMETER(UNPACK_INVOKE_PARAMETER inst) throws SymbolicInstructionException { + try { Value top = stack.popOperand(); if (top instanceof ObjectArrayValue param) { SolverContext context = ThreadHandler.getSolverContext(currentThread().getId()); + // Method.invoke passes primitives boxed; the invoke that follows takes them unboxed. + Type[] paramTypes = stack.getNextInst() instanceof INVOKEVIRTUAL next + ? Type.getArgumentTypes(next.desc) : new Type[0]; for (int i = 0; i < param.getSize().concrete; i++) { Value v = param.getElement(new IntValue(context, i)); + if (i < paramTypes.length && paramTypes[i].getSort() <= Type.DOUBLE + && v instanceof BoxedValue boxed && boxed.getVal() != null) { + v = boxed.getVal(); + } if (v instanceof LongValue || v instanceof DoubleValue) { stack.pushWideOperand(v); } else { stack.pushOperand(v); } } + previousWasUnpack = true; } else { throw new SymbolicInstructionException(inst, "Unpacking of ObjectArray requires an Array of Objects."); } @@ -3047,10 +3464,10 @@ public void visitUNPACK_INVOKE_PARAMETER(UNPACK_INVOKE_PARAMETER inst) throws S } } - public void visitSET_FIELD_REFLECTION(SET_FIELD_REFLECTION inst) throws SymbolicInstructionException{ - try{ + public void visitSET_FIELD_REFLECTION(SET_FIELD_REFLECTION inst) throws SymbolicInstructionException { + try { - stack.newStackFrame(inst.desc, inst.owner, inst.name, true); + stack.newStackFrame(inst.desc, inst.owner, inst.name, true, inst.iid); if (stack.getNextInst() instanceof INVOKEMETHOD_END) { String reflectFieldName = inst.reflectFieldName; @@ -3065,7 +3482,6 @@ public void visitSET_FIELD_REFLECTION(SET_FIELD_REFLECTION inst) throws Symbolic valueToSet = stack.getLocal(2); } - // Value fieldObject = stack.getLocal(0); // ToDo: Any easier way? And does it always work? // ToDo: Is it possible, that the field is static? @@ -3078,7 +3494,6 @@ public void visitSET_FIELD_REFLECTION(SET_FIELD_REFLECTION inst) throws Symbolic targetObject.setField(fieldIndex, valueToSet); } - stack.setReturnValue(PlaceHolder.instance); } @@ -3097,9 +3512,9 @@ public void visitSET_FIELD_REFLECTION(SET_FIELD_REFLECTION inst) throws Symbolic } } - public void visitGET_FIELD_REFLECTION(GET_FIELD_REFLECTION inst) throws SymbolicInstructionException{ - try{ - stack.newStackFrame(inst.desc, inst.owner, inst.name, true); + public void visitGET_FIELD_REFLECTION(GET_FIELD_REFLECTION inst) throws SymbolicInstructionException { + try { + stack.newStackFrame(inst.desc, inst.owner, inst.name, true, inst.iid); if (stack.getNextInst() instanceof INVOKEMETHOD_END) { @@ -3118,9 +3533,15 @@ public void visitGET_FIELD_REFLECTION(GET_FIELD_REFLECTION inst) throws Symbolic } else { ObjectValue targetObject = (ObjectValue) stack.getLocal(1); - int fieldIndex = classDepot.getFieldIndex(cIdx, inst.name, false); + int fieldIndex = classDepot.getFieldIndex(cIdx, reflectFieldName, false); getObject = targetObject.getField(fieldIndex); } + if (inst.name.equals("get") + && !(getObject instanceof ObjectValue) + && !(getObject instanceof PlaceHolder)) { + // Field.get boxes the value of a primitive field + getObject = getObject.asObjectValue(); + } // if (getObject instanceof DoubleValue || getObject instanceof LongValue) { // stack.pushWideOperand(getObject); // } else { @@ -3135,7 +3556,7 @@ public void visitGET_FIELD_REFLECTION(GET_FIELD_REFLECTION inst) throws Symbolic } } - public void visitNEW(NEW inst) throws SymbolicInstructionException{ + public void visitNEW(NEW inst) throws SymbolicInstructionException { try { String className = classDepot.getClassName(inst.cIdx); int nFields = classDepot.getFieldCountWithReflection(className, null, false); @@ -3151,32 +3572,33 @@ public void visitNEW(NEW inst) throws SymbolicInstructionException{ } } - public void visitNEWARRAY(NEWARRAY inst) throws SymbolicInstructionException{ + public void visitNEWARRAY(NEWARRAY inst) throws SymbolicInstructionException { try { IntValue size = stack.popOperand().asIntValue(); BooleanFormula constraint = size.checkPositive(); boolean result = size.concrete >= 0; symbolicTraceHandler.checkAndSetBranch(result, constraint, determineIid(inst.iid)); - if(result) { + if (result) { switch (inst.atype) { case 4 -> // T_BOOLEAN - stack.pushOperand(ValueFactory.createArrayValue(ValueType.booleanValue, size, -1)); + stack.pushOperand(ValueFactory.createArrayValue(ValueType.booleanValue, size, -1)); case 5 -> // T_CHAR - stack.pushOperand(ValueFactory.createArrayValue(ValueType.charValue, size, -1)); + stack.pushOperand(ValueFactory.createArrayValue(ValueType.charValue, size, -1)); case 6 -> // T_FLOAT - stack.pushOperand(ValueFactory.createArrayValue(ValueType.floatValue, size, -1)); + stack.pushOperand(ValueFactory.createArrayValue(ValueType.floatValue, size, -1)); case 7 -> // T_DOUBLE - stack.pushOperand(ValueFactory.createArrayValue(ValueType.doubleValue, size, -1)); + stack.pushOperand(ValueFactory.createArrayValue(ValueType.doubleValue, size, -1)); case 8 -> // T_BYTE - stack.pushOperand(ValueFactory.createArrayValue(ValueType.byteValue, size, -1)); + stack.pushOperand(ValueFactory.createArrayValue(ValueType.byteValue, size, -1)); case 9 -> // T_SHORT - stack.pushOperand(ValueFactory.createArrayValue(ValueType.shortValue, size, -1)); + stack.pushOperand(ValueFactory.createArrayValue(ValueType.shortValue, size, -1)); case 10 -> // T_INT - stack.pushOperand(ValueFactory.createArrayValue(ValueType.intValue, size, -1)); + stack.pushOperand(ValueFactory.createArrayValue(ValueType.intValue, size, -1)); case 11 -> // T_LONG - stack.pushOperand(ValueFactory.createArrayValue(ValueType.longValue, size, -1)); - default -> throw new SymbolicInstructionException(inst, "Unknown primitive type: " + inst.atype + "!"); + stack.pushOperand(ValueFactory.createArrayValue(ValueType.longValue, size, -1)); + default -> + throw new SymbolicInstructionException(inst, "Unknown primitive type: " + inst.atype + "!"); } } else { enforceException(); @@ -3191,15 +3613,16 @@ public void visitNEWARRAY(NEWARRAY inst) throws SymbolicInstructionException{ * * @param inst The NOP instruction. */ - public void visitNOP(NOP inst) throws SymbolicInstructionException{} + public void visitNOP(NOP inst) throws SymbolicInstructionException { + } /** * Pops the top value from the symbolic stack. * * @param inst The POP instruction */ - public void visitPOP(POP inst) throws SymbolicInstructionException{ - try{ + public void visitPOP(POP inst) throws SymbolicInstructionException { + try { stack.popOperand(); } catch (Throwable t) { throw new SymbolicInstructionException(inst, t); @@ -3211,8 +3634,8 @@ public void visitPOP(POP inst) throws SymbolicInstructionException{ * * @param inst The POP2 instruction */ - public void visitPOP2(POP2 inst) throws SymbolicInstructionException{ - try{ + public void visitPOP2(POP2 inst) throws SymbolicInstructionException { + try { stack.popWideOperand(); } catch (Throwable t) { throw new SymbolicInstructionException(inst, t); @@ -3228,8 +3651,8 @@ public void visitPOP2(POP2 inst) throws SymbolicInstructionException{ * * @param inst The PUTFIELD instruction */ - public void visitPUTFIELD(PUTFIELD inst) throws SymbolicInstructionException{ - try{ + public void visitPUTFIELD(PUTFIELD inst) throws SymbolicInstructionException { + try { Value value; if (inst.desc.startsWith("D") || inst.desc.startsWith("J")) { value = stack.popWideOperand(); @@ -3237,6 +3660,11 @@ public void visitPUTFIELD(PUTFIELD inst) throws SymbolicInstructionException{ value = stack.popOperand(); } ObjectValue ref = stack.popOperand().asObjectValue(); + if (ref.getAddress() == ObjectValue.ADDRESS_NULL) { + // NullPointerException, see visitGETFIELD + checkAndSetException(inst); + return; + } int fieldIndex = classDepot.getFieldIndex(inst.cIdx, inst.name, false); try { ref.setField(fieldIndex, value); @@ -3250,7 +3678,8 @@ public void visitPUTFIELD(PUTFIELD inst) throws SymbolicInstructionException{ } private void setStaticField(Value value, int cIdx, String name) - throws ThreadAlreadyEnabledException, ThreadAlreadyDisabledException, NoThreadContextException, ClassNotFoundException { + throws ThreadAlreadyEnabledException, ThreadAlreadyDisabledException, NoThreadContextException, + ClassNotFoundException { int fieldIndex = classDepot.getFieldIndex(cIdx, name, true); ThreadHandler.setStaticField( Thread.currentThread().getId(), cIdx, fieldIndex, value); @@ -3261,8 +3690,9 @@ private void setStaticField(Value value, int cIdx, String name) * * @param inst The PUTSTATIC instruction */ - public void visitPUTSTATIC(PUTSTATIC inst) throws SymbolicInstructionException{ + public void visitPUTSTATIC(PUTSTATIC inst) throws SymbolicInstructionException { try { + LoopStateMatcher.staticField(classDepot.getClassName(inst.cIdx)); Value value; if (inst.desc.startsWith("D") || inst.desc.startsWith("J")) { value = stack.popWideOperand(); @@ -3285,14 +3715,15 @@ public void visitPUTSTATIC(PUTSTATIC inst) throws SymbolicInstructionException{ * * @param inst The RET instruction */ - public void visitRET(RET inst) throws SymbolicInstructionException{} + public void visitRET(RET inst) throws SymbolicInstructionException { + } /** * Returns void from method, no handling needed (here) * * @param inst The RETURN instruction */ - public void visitRETURN(RETURN inst) throws SymbolicInstructionException{ + public void visitRETURN(RETURN inst) throws SymbolicInstructionException { // checkAndSetException(inst); } @@ -3301,7 +3732,7 @@ public void visitRETURN(RETURN inst) throws SymbolicInstructionException{ * * @param inst The SALOAD instruction. */ - public void visitSALOAD(SALOAD inst) throws SymbolicInstructionException{ + public void visitSALOAD(SALOAD inst) throws SymbolicInstructionException { try { IntValue idx = stack.popOperand().asIntValue(); ObjectValue arr = (ObjectValue) stack.popOperand(); @@ -3313,6 +3744,7 @@ public void visitSALOAD(SALOAD inst) throws SymbolicInstructionException{ enforceException(); } } else { + recordUntrackedBoundsCheck(inst, idx, arr); logger.warn("[SALOAD]: Unknown array type"); stack.pushOperand( arr.getName() != null @@ -3330,7 +3762,7 @@ public void visitSALOAD(SALOAD inst) throws SymbolicInstructionException{ * * @param inst The SASTORE instruction. */ - public void visitSASTORE(SASTORE inst) throws SymbolicInstructionException{ + public void visitSASTORE(SASTORE inst) throws SymbolicInstructionException { try { ShortValue val = stack.popOperand().asShortValue(); IntValue idx = stack.popOperand().asIntValue(); @@ -3343,6 +3775,7 @@ public void visitSASTORE(SASTORE inst) throws SymbolicInstructionException{ enforceException(); } } else { + recordUntrackedBoundsCheck(inst, idx, ref); logger.warn("[SASTORE]: Unknown array type"); } } catch (Throwable t) { @@ -3355,8 +3788,8 @@ public void visitSASTORE(SASTORE inst) throws SymbolicInstructionException{ * * @param inst The SIPUSH instruction */ - public void visitSIPUSH(SIPUSH inst) throws SymbolicInstructionException{ - try{ + public void visitSIPUSH(SIPUSH inst) throws SymbolicInstructionException { + try { stack.pushOperand(ValueFactory.createNumericalValue(ValueType.intValue, inst.value)); } catch (Throwable t) { throw new SymbolicInstructionException(inst, t); @@ -3368,8 +3801,8 @@ public void visitSIPUSH(SIPUSH inst) throws SymbolicInstructionException{ * * @param inst The SWAP instruction */ - public void visitSWAP(SWAP inst) throws SymbolicInstructionException{ - try{ + public void visitSWAP(SWAP inst) throws SymbolicInstructionException { + try { Value v1 = stack.popOperand(); Value v2 = stack.popOperand(); stack.pushOperand(v1); @@ -3379,8 +3812,8 @@ public void visitSWAP(SWAP inst) throws SymbolicInstructionException{ } } - public void visitINVOKEMETHOD_EXCEPTION(INVOKEMETHOD_EXCEPTION inst) throws SymbolicInstructionException{ - try{ + public void visitINVOKEMETHOD_EXCEPTION(INVOKEMETHOD_EXCEPTION inst) throws SymbolicInstructionException { + try { if (stack.getFrameStack().size() == 1) { logger.warn( "[INVOKEMETHOD_EXCEPTION]: Terminating symbolic execution due to stack size" @@ -3388,7 +3821,14 @@ public void visitINVOKEMETHOD_EXCEPTION(INVOKEMETHOD_EXCEPTION inst) throws Symb Intrinsics.terminate(); return; } - stack.popFrame(); + Frame callee = stack.popFrame(); + if (isLibraryFrame(callee)) { + // The exception comes out of library code that ran application code first (a + // callback): the graph has the call as a plain library call, so it cannot say + // where this goes. (One thrown before any application code ran is recorded at the + // call, see recordThrowingCall.) + recordUnmodelledException(inst.iid); + } stack.pushOperand( PlaceHolder.instance); // placeholder for the exception object ToDo: More specific // placeholder @@ -3403,8 +3843,8 @@ public void visitINVOKEMETHOD_EXCEPTION(INVOKEMETHOD_EXCEPTION inst) throws Symb * * @param inst INVOKEMETHOD_END */ - public void visitINVOKEMETHOD_END(INVOKEMETHOD_END inst) throws SymbolicInstructionException{ - try{ + public void visitINVOKEMETHOD_END(INVOKEMETHOD_END inst) throws SymbolicInstructionException { + try { Frame old = stack.popFrame(); if (old.nReturnWords == 2) { stack.pushWideOperand(old.getRet()); @@ -3422,8 +3862,10 @@ public void visitINVOKEMETHOD_END(INVOKEMETHOD_END inst) throws SymbolicInstruct * * @param inst INVOKEMETHOD_END */ - public void visitINVOKECLINIT_END(INVOKECLINIT_END inst) throws SymbolicInstructionException{ - try{ + public void visitINVOKECLINIT_END(INVOKECLINIT_END inst) throws SymbolicInstructionException { + try { + symbolicTraceHandler.recordInvocation( + determineIid(inst.iid), inst.getClass().getCanonicalName()); if (stack.getActiveFrame().getMethodName().equals(inst.name)) { stack.popFrame(); } else { @@ -3434,18 +3876,53 @@ public void visitINVOKECLINIT_END(INVOKECLINIT_END inst) throws SymbolicInstruct } } - public void visitMAKE_SYMBOLIC(MAKE_SYMBOLIC inst) throws SymbolicInstructionException{} + public void visitMAKE_SYMBOLIC(MAKE_SYMBOLIC inst) throws SymbolicInstructionException { + } - public void visitLOOP_BEGIN(LOOP_BEGIN inst) throws SymbolicInstructionException{ + public void visitLOOP_BEGIN(LOOP_BEGIN inst) throws SymbolicInstructionException { /*int loopCnt = loopCounter.getOrDefault(inst.iid, 0); loopCounter.put(inst.iid, loopCnt + 1); loops.push(inst.iid); */ } - public void visitLOOP_END(LOOP_END inst) throws SymbolicInstructionException{} + public void visitLOOP_END(LOOP_END inst) throws SymbolicInstructionException { + } + + public void visitSPECIAL(SPECIAL inst) throws SymbolicInstructionException { + } + + /** + * Where the sides of a merged diamond meet again: gives the locals their merged formulas (see + * symbolic/summary/Merger). + * + * @param inst The SUMMARY_JOIN probe + */ + public void visitSUMMARY_JOIN(SUMMARY_JOIN inst) throws SymbolicInstructionException { + try { + merger.atJoin(inst.join); + } catch (Throwable t) { + throw new SymbolicInstructionException(inst, t); + } + } - public void visitSPECIAL(SPECIAL inst) throws SymbolicInstructionException{} + /** + * Reports the state at a loop head with the next branch, if it has one (see LoopStateMatcher). + * + * @param inst The LOOP_HEAD probe + */ + public void visitLOOP_HEAD(LOOP_HEAD inst) throws SymbolicInstructionException { + try { + long id = currentThread().getId(); + String state = LoopStateMatcher.stateOf(inst, stack, ThreadHandler.getStaticFields(id), + ThreadHandler.getActiveThreadContextIds().size()); + if (state != null) { + symbolicTraceHandler.addLoopState(state); + } + } catch (Throwable t) { + throw new SymbolicInstructionException(inst, t); + } + } /** * Creates a multidimensional array of references. Currently, no symbolic handling here because @@ -3455,7 +3932,7 @@ public void visitSPECIAL(SPECIAL inst) throws SymbolicInstructionException{} * * @param inst The MULTIANEWARRAY instruction */ - public void visitMULTIANEWARRAY(MULTIANEWARRAY inst) throws SymbolicInstructionException{ + public void visitMULTIANEWARRAY(MULTIANEWARRAY inst) throws SymbolicInstructionException { try { IntValue[] dims = new IntValue[inst.dims]; @@ -3463,7 +3940,7 @@ public void visitMULTIANEWARRAY(MULTIANEWARRAY inst) throws SymbolicInstructionE IntValue i1 = stack.popOperand().asIntValue(); BooleanFormula constraint = i1.checkPositive(); boolean result = i1.concrete >= 0; - symbolicTraceHandler.checkAndSetBranch(result, constraint, determineIid(inst.iid)); + symbolicTraceHandler.checkAndSetBranch(result, constraint, determineIid(inst.iid), "#" + i); dims[dims.length - i - 1] = i1; } @@ -3482,20 +3959,20 @@ public void visitMULTIANEWARRAY(MULTIANEWARRAY inst) throws SymbolicInstructionE * * @param inst The LOOKUPSWITCH instruction. */ - public void visitLOOKUPSWITCH(LOOKUPSWITCH inst) throws SymbolicInstructionException{ - try{ + public void visitLOOKUPSWITCH(LOOKUPSWITCH inst) throws SymbolicInstructionException { + try { int[] keys = inst.keys; IntValue i1 = stack.popOperand().asIntValue(); for (int key : keys) { - BooleanFormula result = - i1.IF_ICMPEQ( - ValueFactory.createNumericalValue(ValueType.intValue, key) - .asIntValue()); + BooleanFormula result = i1.IF_ICMPEQ( + ValueFactory.createNumericalValue(ValueType.intValue, key) + .asIntValue()); symbolicTraceHandler.checkAndSetBranch( - i1.concrete == key, result, determineIid(inst.iid)); + i1.concrete == key, result, determineIid(inst.iid), "#" + key); - if (i1.concrete == key) return; + if (i1.concrete == key) + return; } } catch (Throwable t) { throw new SymbolicInstructionException(inst, t); @@ -3508,18 +3985,18 @@ public void visitLOOKUPSWITCH(LOOKUPSWITCH inst) throws SymbolicInstructionExcep * * @param inst The TABLESWITCH instruction. */ - public void visitTABLESWITCH(TABLESWITCH inst) throws SymbolicInstructionException{ - try{ + public void visitTABLESWITCH(TABLESWITCH inst) throws SymbolicInstructionException { + try { IntValue i1 = stack.popOperand().asIntValue(); for (int i = inst.min; i <= inst.max; i++) { - BooleanFormula result = - i1.IF_ICMPEQ( - ValueFactory.createNumericalValue(ValueType.intValue, i).asIntValue()); + BooleanFormula result = i1.IF_ICMPEQ( + ValueFactory.createNumericalValue(ValueType.intValue, i).asIntValue()); symbolicTraceHandler.checkAndSetBranch( - i1.concrete == i, result, - GlobalStateForInstrumentation.createSwitchCaseIid(inst.iid, i, inst.min)); + i1.concrete == i, result, + GlobalStateForInstrumentation.createSwitchCaseIid(inst.iid, i, inst.min), "#" + i); - if (i1.concrete == i) return; + if (i1.concrete == i) + return; } BranchCoverage.addVisitedBranch(inst.iid); @@ -3536,7 +4013,7 @@ private boolean isIntegral(Object o) { || o instanceof Byte; } - private int toInteger(Object o) throws ValueConversionException{ + private int toInteger(Object o) throws ValueConversionException { if (o instanceof Integer i) { return i; } else if (o instanceof Short s) { @@ -3601,7 +4078,7 @@ private void visitGETVALUE_primitive(GETVALUE_primitive inst, ValueType type) th GETSTATIC gsInst = (GETSTATIC) placeHolder.inst; setStaticField(v, gsInst.cIdx, gsInst.name); Logger shadowStateLogger = ThreadHandler.getShadowStateLogger(currentThread().getId()); - shadowStateLogger.debug("Storing retrieved value in field {}",gsInst.name); + shadowStateLogger.debug("Storing retrieved value in field {}", gsInst.name); } } else if ((!(peek instanceof BoxedValue) && peek.concrete == null) || (peek instanceof BoxedValue && ((BoxedValue) peek).getVal().concrete == null)) { @@ -3612,10 +4089,15 @@ private void visitGETVALUE_primitive(GETVALUE_primitive inst, ValueType type) th stack.popOperand(); stack.pushOperand(ValueFactory.createNumericalValue(type, inst.v)); } - } else if ((peek instanceof BoxedValue && !checkEquality(((BoxedValue)peek).getVal().concrete, inst.v)) + } else if ((peek instanceof BoxedValue + && !checkEquality(((BoxedValue) peek).getVal().concrete, inst.v)) || (!(peek instanceof BoxedValue) && !checkEquality(peek.concrete, inst.v))) { - SWATAssert.check(false, "[GETVALUE_primitive]: Value on stack does not match expected value! Expected: {}, Actual: {}", + // The shadow value diverged from the JVM (a model bug). Continue with the concrete + // value: what the trace derived from the shadow value cannot be trusted, which is + // recorded as a context loss. + logger.warn("[GETVALUE_primitive]: Value on stack does not match expected value, causes context loss! Expected: {}, Actual: {}", inst.v, peek.concrete); + symbolicTraceHandler.recordSymbolicContextLoss(); if (cat2) { stack.popWideOperand(); stack.pushWideOperand(ValueFactory.createNumericalValue(type, inst.v)); diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/UFs/EqualsIgnoreCaseUF.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/UFs/EqualsIgnoreCaseUF.java deleted file mode 100644 index a49cb167..00000000 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/UFs/EqualsIgnoreCaseUF.java +++ /dev/null @@ -1,125 +0,0 @@ -package de.uzl.its.swat.symbolic.UFs; - -import de.uzl.its.swat.common.exceptions.NoThreadContextException; -import org.sosy_lab.java_smt.api.*; - -import java.util.ArrayList; -import java.util.List; - -public class EqualsIgnoreCaseUF { - private final FunctionDeclaration equalsIgnoreCaseUF; - private final SolverContext ctx; - private final UFManager ufmgr; - private final StringFormulaManager smgr; - private final BooleanFormulaManager bmgr; - private final IntegerFormulaManager imgr; - private final ToLowerCaseUF toLowerUF; - - // Global cap for unrolling; tune as needed. - private static final int MAX_K = 50; - - public EqualsIgnoreCaseUF(SolverContext ctx) throws NoThreadContextException { - this.ctx = ctx; - FormulaManager fmgr = ctx.getFormulaManager(); - this.ufmgr = fmgr.getUFManager(); - this.smgr = fmgr.getStringFormulaManager(); - this.bmgr = fmgr.getBooleanFormulaManager(); - this.imgr = fmgr.getIntegerFormulaManager(); - this.toLowerUF = new ToLowerCaseUF(ctx); - - this.equalsIgnoreCaseUF = ufmgr.declareUF( - "equalsIgnoreCase", - FormulaType.BooleanType, - FormulaType.StringType, - FormulaType.StringType); - } - - public BooleanFormula getUFCall(StringFormula s1, StringFormula s2) { - return ufmgr.callUF(equalsIgnoreCaseUF, s1, s2); - } - - /** - * Create the (always-added) constraints that define equalsIgnoreCase for this call. - * No branching on the concrete result is needed anymore. - * - * @param s1 symbolic term of the receiver - * @param s2 symbolic term of the argument - * @param concrete1 runtime concrete value of s1 (used to choose tighter guards) - * @param concrete2 runtime concrete value of s2 - * @param s1Symbolic whether s1 carries variables (your isSymbolic()) - * @param s2Symbolic whether s2 carries variables - */ - public BooleanFormula createEqualsIgnoreCaseConstraints( - StringFormula s1, StringFormula s2, - String concrete1, String concrete2, - boolean s1Symbolic, boolean s2Symbolic) throws NoThreadContextException { - // UF call and lengths - BooleanFormula uf = getUFCall(s1, s2); - NumeralFormula.IntegerFormula len1 = smgr.length(s1); - NumeralFormula.IntegerFormula len2 = smgr.length(s2); - - // Per-index guarded equalities up to K - final int K; - if (!s1Symbolic && !s2Symbolic) { - // Both concrete → caller can short-circuit; keep small K just in case - K = Math.min(MAX_K, Math.max(concrete1.length(), concrete2.length())); - } else if (!s1Symbolic) { // s1 concrete - K = Math.min(MAX_K, concrete1.length()); - } else if (!s2Symbolic) { // s2 concrete - K = Math.min(MAX_K, concrete2.length()); - } else { - K = MAX_K; - } - - List perIndex = new ArrayList<>(K); - for (int i = 0; i < K; i++) { - NumeralFormula.IntegerFormula iF = imgr.makeNumber(i); - BooleanFormula inBoth = bmgr.and( - imgr.lessThan(iF, len1), - imgr.lessThan(iF, len2)); - StringFormula c1 = smgr.charAt(s1, iF); - StringFormula c2 = smgr.charAt(s2, iF); - NumeralFormula.IntegerFormula code1 = smgr.toCodePoint(c1); // -1 if out-of-range - NumeralFormula.IntegerFormula code2 = smgr.toCodePoint(c2); - NumeralFormula.IntegerFormula low1 = toLowerUF.applyToLower(code1, inBoth); - NumeralFormula.IntegerFormula low2 = toLowerUF.applyToLower(code2, inBoth); - perIndex.add(bmgr.implication(inBoth, imgr.equal(low1, low2))); - } - - BooleanFormula body = bmgr.and( - imgr.equal(len1, len2), - bmgr.and(perIndex)); - - // Algebraic axioms (safe & useful) - BooleanFormula reflexive = getUFCall(s1, s1); // equalsIgnoreCase(s,s) - BooleanFormula symmetric = bmgr.equivalence(uf, getUFCall(s2, s1)); // symmetry - BooleanFormula lenMismatchImpliesFalse = bmgr.implication(bmgr.not(imgr.equal(len1, len2)), bmgr.not(uf)); // different lengths → false - - // Always safe direction: if equal ignoring case, then the first K positions must match - BooleanFormula ufImpliesBody = bmgr.implication(uf, body); - - // Reverse direction guarded by a length regime where unrolling is complete - BooleanFormula reverseGuard; - if (!s1Symbolic && s2Symbolic) { - // s1 is concrete of length L → activate equivalence exactly at L - int L = concrete1.length(); - reverseGuard = bmgr.and( - imgr.equal(len1, imgr.makeNumber(L)), - imgr.equal(len2, imgr.makeNumber(L))); - } else if (s1Symbolic && !s2Symbolic) { - int L = concrete2.length(); - reverseGuard = bmgr.and( - imgr.equal(len1, imgr.makeNumber(L)), - imgr.equal(len2, imgr.makeNumber(L))); - } else { - // both symbolic (or both concrete path kept) → guard by len≤K - reverseGuard = bmgr.and( - imgr.equal(len1, len2), - imgr.lessOrEquals(len1, imgr.makeNumber(K))); - } - BooleanFormula bodyImpliesUf = bmgr.implication(bmgr.and(reverseGuard, body), uf); - - // Bundle all constraints to add to your "UF" trace channel - return bmgr.and(reflexive, symmetric, lenMismatchImpliesFalse, ufImpliesBody, bodyImpliesUf); - } -} diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/UFs/SinCosUF.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/UFs/SinCosUF.java deleted file mode 100644 index 5422a5a2..00000000 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/UFs/SinCosUF.java +++ /dev/null @@ -1,94 +0,0 @@ -package de.uzl.its.swat.symbolic.UFs; - -import de.uzl.its.swat.common.exceptions.NoThreadContextException; -import de.uzl.its.swat.symbolic.trace.SymbolicTraceHandler; -import de.uzl.its.swat.thread.ThreadHandler; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.stream.Collectors; - -import org.sosy_lab.java_smt.api.*; - -public class SinCosUF { - private final FormulaType.FloatingPointType DoubleType = FormulaType.getDoublePrecisionFloatingPointType(); - private final FunctionDeclaration sinUF; - private final FunctionDeclaration cosUF; - private final UFManager ufmgr; - private final BooleanFormulaManager bmgr; - private final FloatingPointFormulaManager fmgr; - - private final ArrayList inputValues; - - public SinCosUF(SolverContext ctx) throws NoThreadContextException { - SymbolicTraceHandler sth = ThreadHandler.getSymbolicTraceHandler(Thread.currentThread().getId()); - FormulaManager frml_mgr = ctx.getFormulaManager(); - ufmgr = frml_mgr.getUFManager(); - bmgr = frml_mgr.getBooleanFormulaManager(); - fmgr = frml_mgr.getFloatingPointFormulaManager(); - - sinUF = ufmgr.declareUF("sin", DoubleType, DoubleType); - cosUF = ufmgr.declareUF("cos", DoubleType, DoubleType); - - final double PI = Math.PI; - inputValues = new ArrayList(Arrays.asList( - 0.0, 0.5 * PI, 1.0 * PI, 1.5 * PI, 2.0 * PI, - -0.0, -0.5 * PI, -1.0 * PI, -1.5 * PI, -2.0 * PI, - 1.0, 2.0, 3.0, 4.0)); - int initialSize = inputValues.size(); - for (int i = 0; i < initialSize; i++) { - var out = Math.sin(inputValues.get(i)); - if (!inputValues.contains(out)) - inputValues.add(out); - - out = Math.cos(inputValues.get(i)); - if (!inputValues.contains(out)) - inputValues.add(out); - } - - for (double concreteIn : inputValues) { - var symIn = fmgr.makeNumber(concreteIn, DoubleType); - var symOut = fmgr.makeNumber(Math.sin(concreteIn), DoubleType); - sth.addConstraint(fmgr.equalWithFPSemantics(ufmgr.callUF(sinUF, symIn), symOut)); - - symIn = fmgr.makeNumber(concreteIn, DoubleType); - symOut = fmgr.makeNumber(Math.cos(concreteIn), DoubleType); - sth.addConstraint(fmgr.equalWithFPSemantics(ufmgr.callUF(cosUF, symIn), symOut)); - } - } - - public FloatingPointFormula applySin(FloatingPointFormula symIn) throws NoThreadContextException { - var symOut = ufmgr.callUF(sinUF, symIn); - - SymbolicTraceHandler sth = ThreadHandler.getSymbolicTraceHandler(Thread.currentThread().getId()); - sth.addConstraint(bmgr.and( // Output is between -1 and 1. - fmgr.greaterOrEquals(symOut, fmgr.makeNumber(-1.0, DoubleType)), - fmgr.lessOrEquals(symOut, fmgr.makeNumber(1.0, DoubleType)))); - - // Input must be one of the known inputValues, otherwise the solver can just "make up" any result. - // This loses soundness, but that's okay, because UFs cause precision loss anyway and thus we will never - // give a SAFE verdict. We can however find some VIOLATIONs this way. - sth.addConstraint( - bmgr.or(inputValues.stream().map(v -> fmgr.equalWithFPSemantics(symIn, fmgr.makeNumber(v, DoubleType))) - .collect(Collectors.toList()))); - - return symOut; - } - - public FloatingPointFormula applyCos(FloatingPointFormula symIn) throws NoThreadContextException { - var symOut = ufmgr.callUF(cosUF, symIn); - - SymbolicTraceHandler sth = ThreadHandler.getSymbolicTraceHandler(Thread.currentThread().getId()); - sth.addConstraint(bmgr.and( // Output is between -1 and 1. - fmgr.greaterOrEquals(symOut, fmgr.makeNumber(-1.0, DoubleType)), - fmgr.lessOrEquals(symOut, fmgr.makeNumber(1.0, DoubleType)))); - - // Input must be one of the known inputValues, otherwise the solver can just "make up" any result. - // This loses soundness, but that's okay, because UFs cause precision loss anyway and thus we will never - // give a SAFE verdict. We can however find some VIOLATIONs this way. - sth.addConstraint( - bmgr.or(inputValues.stream().map(v -> fmgr.equalWithFPSemantics(symIn, fmgr.makeNumber(v, DoubleType))) - .collect(Collectors.toList()))); - - return symOut; - } -} diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/UFs/ToLowerCaseUF.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/UFs/ToLowerCaseUF.java deleted file mode 100644 index cb197486..00000000 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/UFs/ToLowerCaseUF.java +++ /dev/null @@ -1,55 +0,0 @@ -package de.uzl.its.swat.symbolic.UFs; - -import de.uzl.its.swat.common.exceptions.NoThreadContextException; -import de.uzl.its.swat.symbolic.trace.SymbolicTraceHandler; -import de.uzl.its.swat.thread.ThreadHandler; -import edu.umd.cs.findbugs.annotations.Nullable; -import org.sosy_lab.java_smt.api.*; - -public class ToLowerCaseUF { - private final FunctionDeclaration toLowerUF; - private final UFManager ufmgr; - private final IntegerFormulaManager imgr; - private final BooleanFormulaManager bmgr; - - public ToLowerCaseUF(SolverContext ctx) throws NoThreadContextException { - SymbolicTraceHandler sth = ThreadHandler.getSymbolicTraceHandler(Thread.currentThread().getId()); - FormulaManager fmgr = ctx.getFormulaManager(); - ufmgr = fmgr.getUFManager(); - imgr = fmgr.getIntegerFormulaManager(); - bmgr = fmgr.getBooleanFormulaManager(); - - toLowerUF = ufmgr.declareUF("toLowerCase", FormulaType.IntegerType, FormulaType.IntegerType); - - // A..Z -> a..z - for (int c = 'A'; c <= 'Z'; c++) { - NumeralFormula.IntegerFormula in = imgr.makeNumber(c); - NumeralFormula.IntegerFormula out = imgr.makeNumber(c + 32); - sth.addConstraint(imgr.equal(ufmgr.callUF(toLowerUF, in), out)); - } - // identity for printable ASCII excluding A..Z - for (int c = 32; c <= 126; c++) { - if (c < 'A' || c > 'Z') { - NumeralFormula.IntegerFormula in = imgr.makeNumber(c); - sth.addConstraint(imgr.equal(ufmgr.callUF(toLowerUF, in), in)); - } - } - // No definition outside printable ASCII (keeps it partial there on purpose) - } - - /** - * Apply toLower with an optional guard that asserts ASCII bounds only when relevant. - */ - public NumeralFormula.IntegerFormula applyToLower( - NumeralFormula.IntegerFormula charCode, - @Nullable BooleanFormula guard) throws NoThreadContextException { - if (guard != null) { - SymbolicTraceHandler sth = ThreadHandler.getSymbolicTraceHandler(Thread.currentThread().getId()); - BooleanFormula inAscii = bmgr.and( - imgr.greaterOrEquals(charCode, imgr.makeNumber(32)), - imgr.lessOrEquals(charCode, imgr.makeNumber(126))); - sth.addConstraint(bmgr.implication(guard, inAscii)); - } - return ufmgr.callUF(toLowerUF, charCode); - } -} diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/UFs/UFHandler.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/UFs/UFHandler.java index 639b2752..adc60e34 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/UFs/UFHandler.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/UFs/UFHandler.java @@ -1,32 +1,8 @@ package de.uzl.its.swat.symbolic.UFs; -import de.uzl.its.swat.common.exceptions.NoThreadContextException; -import de.uzl.its.swat.thread.ThreadHandler; -import static java.lang.Thread.currentThread; - +/** + * Holds the per-thread uninterpreted-function models. The approximate ones it used to hold + * (sin/cos, toLowerCase, equalsIgnoreCase) are replaced by per-call or exact models. + */ public class UFHandler { - private EqualsIgnoreCaseUF equalsIgnoreCaseUF; - private ToLowerCaseUF toLowerCaseUF; - private SinCosUF sinCosUF; - - public EqualsIgnoreCaseUF getEqualsIgnoreCaseUF() throws NoThreadContextException { - if (equalsIgnoreCaseUF == null) { - equalsIgnoreCaseUF = new EqualsIgnoreCaseUF(ThreadHandler.getSolverContext(currentThread().getId())); - } - return equalsIgnoreCaseUF; - } - - public ToLowerCaseUF getToLowerCaseUF() throws NoThreadContextException { - if (toLowerCaseUF == null) { - toLowerCaseUF = new ToLowerCaseUF(ThreadHandler.getSolverContext(currentThread().getId())); - } - return toLowerCaseUF; - } - - public SinCosUF getSinCosUF() throws NoThreadContextException { - if (sinCosUF == null) { - sinCosUF = new SinCosUF(ThreadHandler.getSolverContext(currentThread().getId())); - } - return sinCosUF; - } } diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/dispatcher/InstructionLoggingDispatcher.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/dispatcher/InstructionLoggingDispatcher.java index dcb30e6f..71e7bcf1 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/dispatcher/InstructionLoggingDispatcher.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/dispatcher/InstructionLoggingDispatcher.java @@ -3556,6 +3556,18 @@ public static void MAKE_SYMBOLIC(long iid) throws NoThreadContextException { * @param iid The unique instruction ID set during instrumentation. */ @SuppressWarnings("unused") + public static void SUMMARY_JOIN(long iid, int join) throws NoThreadContextException { + Logger logger = ThreadHandler.getShadowStateLogger(-1); + logger.info("SUMMARY_JOIN {} {}", iid, join); +} + + @SuppressWarnings("unused") + public static void LOOP_HEAD(long iid, String head, Object[] locals) throws NoThreadContextException { + Logger logger = ThreadHandler.getShadowStateLogger(-1); + logger.info("LOOP_HEAD {} {}", iid, head); +} + + @SuppressWarnings("unused") public static void LOOP_BEGIN(long iid) throws NoThreadContextException { Logger logger = ThreadHandler.getShadowStateLogger(-1); logger.info("LOOP_BEGIN {}", iid); diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/dispatcher/NopDispatcher.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/dispatcher/NopDispatcher.java index 69f1fb1c..e914d3b7 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/dispatcher/NopDispatcher.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/dispatcher/NopDispatcher.java @@ -3012,6 +3012,12 @@ public static void MAKE_SYMBOLIC(long iid) {} @SuppressWarnings("unused") public static void LOOP_BEGIN(long iid) {} + @SuppressWarnings("unused") + public static void LOOP_HEAD(long iid, String head, Object[] locals) {} + + @SuppressWarnings("unused") + public static void SUMMARY_JOIN(long iid, int join) {} + /** * Method used to interact between the concrete and symbolic execution. The call to this method diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/dispatcher/SymbolicInstructionDispatcher.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/dispatcher/SymbolicInstructionDispatcher.java index cf3d25ac..df943f32 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/dispatcher/SymbolicInstructionDispatcher.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/dispatcher/SymbolicInstructionDispatcher.java @@ -3554,6 +3554,20 @@ public static void MAKE_SYMBOLIC(long iid) { * * @param iid The unique instruction ID set during instrumentation. */ + @SuppressWarnings("unused") + public static void SUMMARY_JOIN(long iid, int join) { + InstructionProcessor instructionProcessor = + ThreadHandler.getProcessor(Thread.currentThread().getId()); + instructionProcessor.SUMMARY_JOIN(iid, join); + } + + @SuppressWarnings("unused") + public static void LOOP_HEAD(long iid, String head, Object[] locals) { + InstructionProcessor instructionProcessor = + ThreadHandler.getProcessor(Thread.currentThread().getId()); + instructionProcessor.LOOP_HEAD(iid, head, locals); + } + @SuppressWarnings("unused") public static void LOOP_BEGIN(long iid) { InstructionProcessor instructionProcessor = diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/instruction/IVisitor.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/instruction/IVisitor.java index 00cce931..23aab964 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/instruction/IVisitor.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/instruction/IVisitor.java @@ -359,6 +359,10 @@ public interface IVisitor { void visitLOOP_BEGIN(LOOP_BEGIN inst) throws SymbolicInstructionException; + void visitLOOP_HEAD(LOOP_HEAD inst) throws SymbolicInstructionException; + + void visitSUMMARY_JOIN(SUMMARY_JOIN inst) throws SymbolicInstructionException; + void visitLOOP_END(LOOP_END inst) throws SymbolicInstructionException; void visitSPECIAL(SPECIAL inst) throws SymbolicInstructionException; diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/instruction/LOOP_HEAD.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/instruction/LOOP_HEAD.java new file mode 100644 index 00000000..fafaa6ef --- /dev/null +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/instruction/LOOP_HEAD.java @@ -0,0 +1,31 @@ +package de.uzl.its.swat.symbolic.instruction; + +import de.uzl.its.swat.common.exceptions.SymbolicInstructionException; + +/** + * LOOP_HEAD - Probe at the target of a backward jump, carrying the concrete values of the locals + * defined there (see {@link de.uzl.its.swat.symbolic.loopstate.LoopHeads}). + */ +public class LOOP_HEAD extends Instruction { + + /** The loop head's stable name: class, method and position (see LoopHeads). */ + public final String head; + + /** The values of the head's locals, in the order LoopHeads lists their slots. */ + public final Object[] locals; + + public LOOP_HEAD(long iid, String head, Object[] locals) { + super(iid); + this.head = head; + this.locals = locals; + } + + public void accept(IVisitor visitor) throws SymbolicInstructionException { + visitor.visitLOOP_HEAD(this); + } + + @Override + public String toString() { + return genericToString("[LOOP HEAD] " + head); + } +} diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/instruction/SUMMARY_JOIN.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/instruction/SUMMARY_JOIN.java new file mode 100644 index 00000000..4ef705b2 --- /dev/null +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/instruction/SUMMARY_JOIN.java @@ -0,0 +1,27 @@ +package de.uzl.its.swat.symbolic.instruction; + +import de.uzl.its.swat.common.exceptions.SymbolicInstructionException; + +/** + * SUMMARY_JOIN - Probe at the label where the two sides of a merged diamond meet again (see + * {@link de.uzl.its.swat.symbolic.summary.LoopSummaries}). + */ +public class SUMMARY_JOIN extends Instruction { + + /** The join's id (see LoopSummaries.Diamond.join). */ + public final int join; + + public SUMMARY_JOIN(long iid, int join) { + super(iid); + this.join = join; + } + + public void accept(IVisitor visitor) throws SymbolicInstructionException { + visitor.visitSUMMARY_JOIN(this); + } + + @Override + public String toString() { + return genericToString("[SUMMARY JOIN] " + join); + } +} diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/InternalInvocation.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/InternalInvocation.java index 14193734..3c649546 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/InternalInvocation.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/InternalInvocation.java @@ -57,6 +57,14 @@ public class InternalInvocation { return checkSink(args); } else if (name.equals("injectAssignment")) { return PlaceHolder.instance; + } else if (name.equals("enterInlinedSwatCode")) { + // Here rather than in the concrete call, to keep in step with the symbolic processing of + // the branches around it (see ShadowContext.enterInlinedSwatCode) + ThreadHandler.getSymbolicVisitor(currentThread().getId()).getStack().enterInlinedSwatCode(); + return PlaceHolder.instance; + } else if (name.equals("leaveInlinedSwatCode")) { + ThreadHandler.getSymbolicVisitor(currentThread().getId()).getStack().leaveInlinedSwatCode(); + return PlaceHolder.instance; } SWATAssert.check(false, "Unknown internal method {} with {} arguments: {}", name, args.length, Arrays.toString(args)); return PlaceHolder.instance; diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/InvocationHandler.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/InvocationHandler.java index d1c465b9..a66b091a 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/InvocationHandler.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/InvocationHandler.java @@ -1,11 +1,13 @@ package de.uzl.its.swat.symbolic.invoke; +import de.uzl.its.swat.symbolic.value.reference.array.ObjectArrayValue; import ch.qos.logback.classic.Logger; import de.uzl.its.swat.common.exceptions.NoThreadContextException; import de.uzl.its.swat.common.exceptions.NotImplementedException; import de.uzl.its.swat.common.exceptions.ValueConversionException; import de.uzl.its.swat.common.logging.GlobalLogger; import de.uzl.its.swat.common.logging.records.InvocationEntry; +import de.uzl.its.swat.symbolic.invoke.java.util.CollectionInvocation; import de.uzl.its.swat.symbolic.trace.SymbolicTraceHandler; import de.uzl.its.swat.symbolic.value.PlaceHolder; import de.uzl.its.swat.symbolic.value.Value; @@ -27,6 +29,7 @@ public class InvocationHandler { "java/io/PrintStream/println", "de/uzl/its/swat/instrument/Intrinsics", "de/uzl/its/swat/common/UtilInstrumented", + "de/uzl/its/swat/common/RefIdentity", "de/uzl/its/swat/witness/Witness", "de/uzl/its/swat/instrument/svcomp/Verifier", "java/io/PrintStream", @@ -35,6 +38,36 @@ public class InvocationHandler { "java/io/InputStream", "java/util/Scanner")); + // The concatenation keeps the ShadowJar relocation from rewriting the owner + private static final String OBJECT_FACTORY = "org/".concat("sosy_lab/sv_benchmarks/ObjectFactory"); + + /** + * Whether this is the factory call that SV-COMP's {@code Verifier.nondetObject} is rewritten to. + * If it reaches the uninstrumented path, the factory implementation is not instrumented: its + * nondeterministic choices are concrete, so the objects it could have returned are not + * explored, which is a context loss even though no argument is symbolic. + */ + public static boolean isUninstrumentedObjectFactory(String owner, String name) { + return owner.equals(OBJECT_FACTORY) && name.equals("createObject"); + } + + /** + * Whether {@code v} is an object array holding a symbolic value, e.g. the boxed arguments of + * {@code Method.invoke} or of any other varargs method. The array's own formula does not show + * that: its elements are stored as fields. + */ + private static boolean hasSymbolicElement(Value v) throws NotImplementedException { + if (!(v instanceof ObjectArrayValue) || ((ObjectArrayValue) v).getFields() == null) { + return false; + } + for (Value element : ((ObjectArrayValue) v).getFields()) { + if (element != null && element.isSymbolic()) { + return true; + } + } + return false; + } + public static Value invoke( SymbolicTraceHandler symbolicTraceHandler, String desc, @@ -49,10 +82,21 @@ public class InvocationHandler { Type[] types = Type.getArgumentTypes(desc); logger.trace("Invoking method {} in class {} with arguments {}", name, owner, arguments); - // We do that first to include the pointer in instance invocation cases - boolean containsSymbolicArgument = arguments.stream().anyMatch(v -> { + // JDK containers: modelled calls only move references, see CollectionInvocation + Value containerResult = CollectionInvocation.invoke( + owner, name, desc, arguments.toArray(new Value[0]), isInstance, instance, invokeId); + if (containerResult != null) { + return containerResult; + } + + boolean ignored = IGNORED_INVOCATIONS.contains(owner + "/" + name) + || IGNORED_INVOCATIONS.contains(owner); + + // We do that first to include the pointer in instance invocation cases. Ignored + // invocations are never recorded, so their arguments need no (possibly deep) scan. + boolean containsSymbolicArgument = !ignored && arguments.stream().anyMatch(v -> { try { - return v.isSymbolic(); + return v.isSymbolic() || hasSymbolicElement(v); } catch (NotImplementedException e) { // either handle, or wrap in an unchecked exception: throw new RuntimeException(e); @@ -60,7 +104,7 @@ public class InvocationHandler { }); if (isInstance) { - containsSymbolicArgument = containsSymbolicArgument || instance.isSymbolic(); + containsSymbolicArgument = containsSymbolicArgument || (!ignored && instance.isSymbolic()); retValue = instance.invokeMethod(name, types, arguments.toArray(new Value[0])); } else { retValue = @@ -73,9 +117,7 @@ public class InvocationHandler { } // When the method is not implemented and its not on the ignore list, we record it - if (retValue instanceof PlaceHolder && - !(IGNORED_INVOCATIONS.contains(owner + "/" + name) - || IGNORED_INVOCATIONS.contains(owner))) { + if (retValue instanceof PlaceHolder && !ignored) { if(isInstance) arguments.add(0, instance); @@ -96,24 +138,30 @@ public class InvocationHandler { } - ThreadHandler.recordMissingInvocation(Thread.currentThread().getId(), new InvocationEntry( + long threadId = Thread.currentThread().getId(); + InvocationEntry entry = new InvocationEntry( owner, name, desc, isInstance, invokeId, - containsSymbolicArgument)); + containsSymbolicArgument); + ThreadHandler.recordMissingInvocation(threadId, entry); if( - (retValue.equals(PlaceHolder.instance) // To detect a missing implementation + ((retValue.equals(PlaceHolder.instance) // To detect a missing implementation || retValue instanceof VoidValue vv && !vv.isSymbolic()) // To detect a missing implementation that returns nothing - && containsSymbolicArgument) { + && containsSymbolicArgument) + || isUninstrumentedObjectFactory(owner, name)) { // Too strict? What about void methods that always have return value PlaceHolder.instance? logger.warn("Invocation of method {} in class {} with arguments {} cases context loss", name, owner, desc); symbolicTraceHandler.recordSymbolicContextLoss(); + // Record the culprit so the explorer can compute the context-loss subset of the + // missing invocations without scraping logs. + ThreadHandler.recordContextLossInvocation(threadId, entry); } } diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/RefIdentityInvocation.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/RefIdentityInvocation.java new file mode 100644 index 00000000..750ea751 --- /dev/null +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/RefIdentityInvocation.java @@ -0,0 +1,27 @@ +package de.uzl.its.swat.symbolic.invoke; + +import de.uzl.its.swat.common.exceptions.NotImplementedException; +import de.uzl.its.swat.symbolic.trace.SymbolicTraceHandler; +import de.uzl.its.swat.symbolic.value.PlaceHolder; +import de.uzl.its.swat.symbolic.value.Value; + +/** + * Symbolic side of {@link de.uzl.its.swat.common.RefIdentity}. Its results are concrete facts about + * where objects were created; only {@code valueDependentIdentity} affects the symbolic state. + */ +public final class RefIdentityInvocation { + + public static Value invokeStaticMethod( + String name, Value[] args, SymbolicTraceHandler symbolicTraceHandler) throws NotImplementedException { + if (name.equals("valueDependentIdentity")) { + for (Value arg : args) { + if (arg.isSymbolic()) { + // A reference comparison decided by symbolic values that SWAT does not encode. + symbolicTraceHandler.recordReferenceSemanticChange(); + break; + } + } + } + return PlaceHolder.instance; + } +} diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/StaticInvocation.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/StaticInvocation.java index c4de4828..64f80076 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/StaticInvocation.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/StaticInvocation.java @@ -15,6 +15,7 @@ import de.uzl.its.swat.symbolic.invoke.java.lang.MathInvocation; import de.uzl.its.swat.symbolic.invoke.java.lang.ShortInvocation; import de.uzl.its.swat.symbolic.invoke.java.lang.StringInvocation; +import de.uzl.its.swat.symbolic.invoke.java.lang.SystemInvocation; import de.uzl.its.swat.symbolic.invoke.java.util.ObjectsInvocation; import de.uzl.its.swat.symbolic.trace.SymbolicTraceHandler; import de.uzl.its.swat.symbolic.value.PlaceHolder; @@ -47,12 +48,15 @@ public final class StaticInvocation { case "java/lang/Long" -> LongInvocation.invokeStaticMethod( name, args, desc, symbolicStateHandler); case "java/lang/Math", "java/lang/StrictMath" -> MathInvocation.invokeStaticMethod( - name, args, desc, symbolicStateHandler); + name, args, desc, symbolicStateHandler, owner.equals("java/lang/StrictMath")); case "java/lang/Short" -> ShortInvocation.invokeStaticMethod(name, args, desc); case "java/lang/Float" -> FloatInvocation.invokeStaticMethod( name, args, desc, symbolicStateHandler); case "java/lang/Double" -> DoubleInvocation.invokeStaticMethod( name, args, desc, symbolicStateHandler); + case "java/lang/System" -> SystemInvocation.invokeStaticMethod(name, args, desc); + case "de/uzl/its/swat/common/RefIdentity" -> RefIdentityInvocation.invokeStaticMethod( + name, args, symbolicStateHandler); case "java/util/Objects" -> ObjectsInvocation.invokeStaticMethod( name, args, desc, symbolicStateHandler); default -> PlaceHolder.instance; diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/BitOps.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/BitOps.java new file mode 100644 index 00000000..1ad13ad0 --- /dev/null +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/BitOps.java @@ -0,0 +1,124 @@ +package de.uzl.its.swat.symbolic.invoke.java.lang; + +import java.util.ArrayList; +import java.util.List; +import org.sosy_lab.java_smt.api.BitvectorFormula; +import org.sosy_lab.java_smt.api.BitvectorFormulaManager; +import org.sosy_lab.java_smt.api.BooleanFormula; +import org.sosy_lab.java_smt.api.BooleanFormulaManager; +import org.sosy_lab.java_smt.api.IntegerFormulaManager; +import org.sosy_lab.java_smt.api.NumeralFormula.IntegerFormula; +import org.sosy_lab.java_smt.api.SolverContext; +import org.sosy_lab.java_smt.api.StringFormula; +import org.sosy_lab.java_smt.api.StringFormulaManager; + +/** + * Exact bit-vector encodings of the bit-manipulation and radix-conversion methods shared by + * {@link Integer} and {@link Long}. Every method takes a bit-vector of width 32 or 64 and mirrors + * the JDK implementation (or its specification) bit for bit. + */ +final class BitOps { + + private BitOps() {} + + private static BitvectorFormula c(BitvectorFormulaManager bv, int width, long value) { + // the 64-bit masks are truncated to the width of the operand + return bv.makeBitvector(width, width < 64 ? value & ((1L << width) - 1) : value); + } + + /* + * The encodings below avoid terms that are shared by several parents (as in the shift cascades + * of the JDK implementations): the branch constraints are simplified and then traversed as trees, + * and if-then-else lifting turns shared subterms into an exponential blow-up. + */ + + /** The number of one-bits of {@code x} as the sum of its bits; width of x. */ + static BitvectorFormula bitCount(SolverContext ctx, BitvectorFormula x, int width) { + BitvectorFormulaManager bv = ctx.getFormulaManager().getBitvectorFormulaManager(); + BitvectorFormula sum = bv.extend(bv.extract(x, 0, 0), width - 1, false); + for (int k = 1; k < width; k++) { + sum = bv.add(sum, bv.extend(bv.extract(x, k, k), width - 1, false)); + } + return sum; + } + + private static BooleanFormulaManager bools(SolverContext ctx) { + return ctx.getFormulaManager().getBooleanFormulaManager(); + } + + private static BooleanFormula bitSet(BitvectorFormulaManager bv, BitvectorFormula x, int k) { + return bv.equal(bv.extract(x, k, k), bv.makeBitvector(1, 1)); + } + + /** The number of zero bits above the highest one-bit (width for zero); width of x. */ + static BitvectorFormula numberOfLeadingZeros(SolverContext ctx, BitvectorFormula x, int width) { + BitvectorFormulaManager bv = ctx.getFormulaManager().getBitvectorFormulaManager(); + BitvectorFormula result = c(bv, width, width); + for (int k = 0; k < width; k++) { + result = bools(ctx).ifThenElse(bitSet(bv, x, k), c(bv, width, width - 1 - k), result); + } + return result; + } + + /** The number of zero bits below the lowest one-bit (width for zero); width of x. */ + static BitvectorFormula numberOfTrailingZeros(SolverContext ctx, BitvectorFormula x, int width) { + BitvectorFormulaManager bv = ctx.getFormulaManager().getBitvectorFormulaManager(); + BitvectorFormula result = c(bv, width, width); + for (int k = width - 1; k >= 0; k--) { + result = bools(ctx).ifThenElse(bitSet(bv, x, k), c(bv, width, k), result); + } + return result; + } + + /** The bits of {@code x} in reverse order: bit 0 becomes the most significant bit. */ + static BitvectorFormula reverse(SolverContext ctx, BitvectorFormula x, int width) { + BitvectorFormulaManager bv = ctx.getFormulaManager().getBitvectorFormulaManager(); + BitvectorFormula result = bv.extract(x, 0, 0); + for (int k = 1; k < width; k++) { + result = bv.concat(result, bv.extract(x, k, k)); + } + return result; + } + + /** -1, 0 or 1 as a 32-bit vector, comparing x and y as signed or unsigned values. */ + static BitvectorFormula compare(SolverContext ctx, BitvectorFormula x, BitvectorFormula y, boolean signed) { + BitvectorFormulaManager bv = ctx.getFormulaManager().getBitvectorFormulaManager(); + BooleanFormulaManager bmgr = ctx.getFormulaManager().getBooleanFormulaManager(); + return bmgr.ifThenElse(bv.lessThan(x, y, signed), c(bv, 32, -1), + bmgr.ifThenElse(bv.equal(x, y), c(bv, 32, 0), c(bv, 32, 1))); + } + + /** + * The unsigned value of {@code x} in radix 2^shift without leading zeros ("0" for zero), as + * Integer/Long.toBinaryString (shift 1), toOctalString (shift 3) and toHexString (shift 4): the + * string of all digits (each one str.from_code of its value), from the highest non-zero digit on. + * Radixes up to 8 only have decimal digits and use the str.from_int encoding of {@link + * NumberStrings}, which the solver handles far better. + */ + static StringFormula toUnsignedPowerOfTwoString(SolverContext ctx, BitvectorFormula x, int width, int shift) { + if (shift <= 3) { + return NumberStrings.unsignedDigits(ctx, x, width, 1 << shift); + } + BitvectorFormulaManager bv = ctx.getFormulaManager().getBitvectorFormulaManager(); + BooleanFormulaManager bmgr = ctx.getFormulaManager().getBooleanFormulaManager(); + StringFormulaManager smgr = ctx.getFormulaManager().getStringFormulaManager(); + IntegerFormulaManager imgr = ctx.getFormulaManager().getIntegerFormulaManager(); + int digits = (width + shift - 1) / shift; + List chars = new ArrayList<>(); + // index (from the left) of the first character of the result + IntegerFormula start = imgr.makeNumber(digits - 1); + for (int k = 0; k < digits; k++) { + int lo = k * shift; + int hi = Math.min(lo + shift, width) - 1; + IntegerFormula digit = bv.toIntegerFormula(bv.extract(x, hi, lo), false); + IntegerFormula code = imgr.add(digit, bmgr.ifThenElse(imgr.greaterThan(digit, imgr.makeNumber(9)), + imgr.makeNumber('a' - 10), imgr.makeNumber('0'))); + chars.add(0, smgr.fromCodePoint(code)); + if (k > 0) { + start = bmgr.ifThenElse(bv.equal(bv.extract(x, hi, lo), c(bv, hi - lo + 1, 0)), + start, imgr.makeNumber(digits - 1 - k)); + } + } + return smgr.substring(smgr.concat(chars), start, imgr.subtract(imgr.makeNumber(digits), start)); + } +} diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/BooleanInvocation.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/BooleanInvocation.java index 92fc1f08..e38015ec 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/BooleanInvocation.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/BooleanInvocation.java @@ -2,7 +2,7 @@ import de.uzl.its.swat.common.exceptions.NotImplementedException; import de.uzl.its.swat.common.exceptions.ValueConversionException; -import de.uzl.its.swat.symbolic.UFs.EqualsIgnoreCaseUF; +import de.uzl.its.swat.symbolic.value.reference.lang.CaseMapping; import de.uzl.its.swat.symbolic.value.PlaceHolder; import de.uzl.its.swat.symbolic.value.Value; import de.uzl.its.swat.symbolic.value.primitive.numeric.integral.BooleanValue; @@ -229,47 +229,21 @@ private static BooleanValue logicalXor(BooleanValue a, BooleanValue b) { } /** - * Symbolic wrapper for Boolean.parseBoolean(String s). - * Returns true if the string argument equals "true" (case-insensitive). - * Implemented using EqualsIgnoreCaseUF. + * Symbolic wrapper for Boolean.parseBoolean(String s): "true".equalsIgnoreCase(s), i.e. s + * matches "true" char by char ignoring case. * * @param s The string to parse * @return BooleanValue representing the parsed boolean */ private static BooleanValue parseBoolean(StringValue s) { SolverContext ctx = s.context; - StringFormulaManager sfm = ctx.getFormulaManager().getStringFormulaManager(); - - // Fast path: both concrete + boolean result = Boolean.parseBoolean(s.concrete); if (!s.isSymbolic()) { - return new BooleanValue(ctx, Boolean.parseBoolean(s.concrete)); - } - - try { - // Boolean.parseBoolean returns "true".equalsIgnoreCase(s) - StringFormula trueStr = sfm.makeString("true"); - - EqualsIgnoreCaseUF uf = ThreadHandler.getUFHandler(currentThread().getId()).getEqualsIgnoreCaseUF(); - - // The symbolic boolean result - BooleanFormula ufCall = uf.getUFCall(trueStr, s.formula); - - // Create and add constraints that define the UF behavior - BooleanFormula constraints = uf.createEqualsIgnoreCaseConstraints( - trueStr, s.formula, - "true", s.concrete, - false, s.isSymbolic() // "true" is not symbolic, s may be - ); - ThreadHandler.getSymbolicTraceHandler(currentThread().getId()).addConstraint(constraints); - - // Return with concrete result for concolic steering - boolean concreteResult = Boolean.parseBoolean(s.concrete); - return new BooleanValue(ctx, concreteResult, ufCall); - - } catch (Exception e) { - // Fallback to concrete-only if UF setup fails - return new BooleanValue(ctx, Boolean.parseBoolean(s.concrete)); + return new BooleanValue(ctx, result); } + StringFormulaManager smgr = ctx.getFormulaManager().getStringFormulaManager(); + return new BooleanValue(ctx, result, + smgr.in(s.formula, new CaseMapping(ctx.getFormulaManager()).equalIgnoringCase("true"))); } /** diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/ByteInvocation.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/ByteInvocation.java index 537be863..ae57772f 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/ByteInvocation.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/ByteInvocation.java @@ -99,6 +99,10 @@ private static IntValue compare(ByteValue x, ByteValue y) { * Invocation handler for Byte.valueOf(byte). */ private static Value invokeValueOf(Value[] args) throws NotImplementedException, ValueConversionException { + if (args[0] instanceof StringValue sv) { + ByteValue b = NumberParsing.parseByte(sv, args); + return new ByteObjectValue(b.context, b, ObjectValue.ADDRESS_UNKNOWN); + } assert args.length == 1 : "Expected 1 argument for valueOf(), got " + args.length; ByteValue b = args[0].asByteValue(); return new ByteObjectValue(b.context, b, ObjectValue.ADDRESS_UNKNOWN); @@ -109,23 +113,12 @@ private static IntValue compare(ByteValue x, ByteValue y) { */ private static Value invokeParseByte(Value[] args) throws NotImplementedException, ValueConversionException { assert args.length == 1 || args.length == 2 : "Expected 1 or 2 arguments for parseByte(), got " + args.length; - if (args.length == 1 && args[0] instanceof StringValue sv) { - return parseByte(sv); + if (args[0] instanceof StringValue sv) { + return NumberParsing.parseByte(sv, args); } return PlaceHolder.instance; } - /** - * Symbolic wrapper for Byte.parseByte(String s) - */ - private static ByteValue parseByte(StringValue s) { - SolverContext ctx = s.context; - StringFormulaManager sfm = ctx.getFormulaManager().getStringFormulaManager(); - BitvectorFormulaManager bvmgr = ctx.getFormulaManager().getBitvectorFormulaManager(); - return new ByteValue(ctx, Byte.parseByte(s.concrete), - bvmgr.makeBitvector(8, sfm.toIntegerFormula(s.formula))); - } - /** * Invocation handler for Byte.toString(byte). */ diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/CharacterInvocation.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/CharacterInvocation.java index 88f3a1a6..25551400 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/CharacterInvocation.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/CharacterInvocation.java @@ -10,6 +10,9 @@ import de.uzl.its.swat.symbolic.value.primitive.numeric.integral.IntValue; import de.uzl.its.swat.symbolic.value.reference.ObjectValue; import de.uzl.its.swat.symbolic.value.reference.lang.CharacterObjectValue; +import de.uzl.its.swat.symbolic.value.primitive.numeric.integral.ByteValue; +import java.util.Map; +import java.util.function.IntUnaryOperator; import org.objectweb.asm.Type; import org.sosy_lab.java_smt.api.BitvectorFormula; import org.sosy_lab.java_smt.api.BitvectorFormulaManager; @@ -32,7 +35,163 @@ public class CharacterInvocation { case "isValidCodePoint" -> invokeIsValidCodePoint(args); case "toCodePoint" -> invokeToCodePoint(args); case "valueOf" -> invokeValueOf(args, desc); - default -> PlaceHolder.instance; + case "digit" -> invokeDigit(args, desc); + case "forDigit" -> invokeForDigit(args); + case "toString" -> args.length == 1 && desc[0].getSort() == Type.CHAR + ? args[0].asCharValue().asStringValue() : PlaceHolder.instance; + default -> invokeTabulated(name, args, desc); + }; + } + + /** What a tabulated method returns: its result type's bit width (1 for boolean). */ + private enum Result { + BOOLEAN(1), BYTE(8), CHAR(16), INT(32); + final int width; + Result(int width) { this.width = width; } + } + + /** + * A Character method over one char or code point, with its char and int overloads (either may + * be null) and what they return. Modelled exactly by tabulating it, see {@link CodePointTables}. + */ + private record Tabulated(IntUnaryOperator onChar, Result charResult, IntUnaryOperator onInt, Result intResult) {} + + private static Tabulated predicate(IntUnaryOperator onChar, IntUnaryOperator onInt) { + return new Tabulated(onChar, Result.BOOLEAN, onInt, Result.BOOLEAN); + } + + private static int b(boolean value) { return value ? 1 : 0; } + + private static final Map TABULATED = Map.ofEntries( + Map.entry("isDigit", predicate(c -> b(Character.isDigit((char) c)), c -> b(Character.isDigit(c)))), + Map.entry("isLetter", predicate(c -> b(Character.isLetter((char) c)), c -> b(Character.isLetter(c)))), + Map.entry("isLetterOrDigit", predicate(c -> b(Character.isLetterOrDigit((char) c)), c -> b(Character.isLetterOrDigit(c)))), + Map.entry("isAlphabetic", predicate(null, c -> b(Character.isAlphabetic(c)))), + Map.entry("isUpperCase", predicate(c -> b(Character.isUpperCase((char) c)), c -> b(Character.isUpperCase(c)))), + Map.entry("isLowerCase", predicate(c -> b(Character.isLowerCase((char) c)), c -> b(Character.isLowerCase(c)))), + Map.entry("isTitleCase", predicate(c -> b(Character.isTitleCase((char) c)), c -> b(Character.isTitleCase(c)))), + Map.entry("isWhitespace", predicate(c -> b(Character.isWhitespace((char) c)), c -> b(Character.isWhitespace(c)))), + Map.entry("isSpaceChar", predicate(c -> b(Character.isSpaceChar((char) c)), c -> b(Character.isSpaceChar(c)))), + Map.entry("isDefined", predicate(c -> b(Character.isDefined((char) c)), c -> b(Character.isDefined(c)))), + Map.entry("isIdeographic", predicate(null, c -> b(Character.isIdeographic(c)))), + Map.entry("isMirrored", predicate(c -> b(Character.isMirrored((char) c)), c -> b(Character.isMirrored(c)))), + Map.entry("isISOControl", predicate(c -> b(Character.isISOControl((char) c)), c -> b(Character.isISOControl(c)))), + Map.entry("isJavaIdentifierStart", predicate(c -> b(Character.isJavaIdentifierStart((char) c)), c -> b(Character.isJavaIdentifierStart(c)))), + Map.entry("isJavaIdentifierPart", predicate(c -> b(Character.isJavaIdentifierPart((char) c)), c -> b(Character.isJavaIdentifierPart(c)))), + Map.entry("isUnicodeIdentifierStart", predicate(c -> b(Character.isUnicodeIdentifierStart((char) c)), c -> b(Character.isUnicodeIdentifierStart(c)))), + Map.entry("isUnicodeIdentifierPart", predicate(c -> b(Character.isUnicodeIdentifierPart((char) c)), c -> b(Character.isUnicodeIdentifierPart(c)))), + Map.entry("isIdentifierIgnorable", predicate(c -> b(Character.isIdentifierIgnorable((char) c)), c -> b(Character.isIdentifierIgnorable(c)))), + Map.entry("isHighSurrogate", predicate(c -> b(Character.isHighSurrogate((char) c)), null)), + Map.entry("isLowSurrogate", predicate(c -> b(Character.isLowSurrogate((char) c)), null)), + Map.entry("isSurrogate", predicate(c -> b(Character.isSurrogate((char) c)), null)), + Map.entry("isJavaLetter", predicate(c -> b(Character.isJavaLetter((char) c)), null)), + Map.entry("isJavaLetterOrDigit", predicate(c -> b(Character.isJavaLetterOrDigit((char) c)), null)), + Map.entry("isSpace", predicate(c -> b(Character.isSpace((char) c)), null)), + Map.entry("toLowerCase", new Tabulated(c -> Character.toLowerCase((char) c), Result.CHAR, Character::toLowerCase, Result.INT)), + Map.entry("toUpperCase", new Tabulated(c -> Character.toUpperCase((char) c), Result.CHAR, Character::toUpperCase, Result.INT)), + Map.entry("toTitleCase", new Tabulated(c -> Character.toTitleCase((char) c), Result.CHAR, Character::toTitleCase, Result.INT)), + Map.entry("getType", new Tabulated(c -> Character.getType((char) c), Result.INT, Character::getType, Result.INT)), + Map.entry("getNumericValue", new Tabulated(c -> Character.getNumericValue((char) c), Result.INT, Character::getNumericValue, Result.INT)), + Map.entry("getDirectionality", new Tabulated(c -> Character.getDirectionality((char) c), Result.BYTE, Character::getDirectionality, Result.BYTE))); + + private static Value invokeTabulated(String name, Value[] args, Type[] desc) throws NotImplementedException, ValueConversionException { + Tabulated t = TABULATED.get(name); + if (t == null || args.length != 1 || desc.length != 1) return PlaceHolder.instance; + boolean onChar = desc[0].getSort() == Type.CHAR; + IntUnaryOperator f = onChar ? t.onChar() : t.onInt(); + if (f == null) return PlaceHolder.instance; + return tabulated(name + (onChar ? "/C" : "/I"), f, onChar ? t.charResult() : t.intResult(), args[0], onChar); + } + + /** + * Invocation handler for Character.digit(char, int) and Character.digit(int, int): the value of + * the char in radix 36 (tabulated), if the radix is in [2, 36] and the value below it, else -1. + */ + private static Value invokeDigit(Value[] args, Type[] desc) throws NotImplementedException, ValueConversionException { + if (args.length != 2) return PlaceHolder.instance; + IntValue radix = args[1].asIntValue(); + boolean onChar = desc[0].getSort() == Type.CHAR; + Value base36 = tabulated("digit/36" + (onChar ? "/C" : "/I"), + onChar ? c -> Character.digit((char) c, Character.MAX_RADIX) : c -> Character.digit(c, Character.MAX_RADIX), + Result.INT, args[0], onChar); + if (!(base36 instanceof IntValue value)) return PlaceHolder.instance; + int result = onChar ? Character.digit((char) args[0].asCharValue().concrete, radix.concrete) + : Character.digit(args[0].asIntValue().concrete, radix.concrete); + if (!value.isSymbolic() && !radix.isSymbolic()) return new IntValue(radix.context, result); + BitvectorFormulaManager bvmgr = radix.context.getFormulaManager().getBitvectorFormulaManager(); + BooleanFormulaManager bmgr = radix.context.getFormulaManager().getBooleanFormulaManager(); + BitvectorFormula r = radix.formula; + BitvectorFormula v = value.formula; + BooleanFormula valid = bmgr.and( + bvmgr.greaterOrEquals(r, bvmgr.makeBitvector(32, Character.MIN_RADIX), true), + bvmgr.lessOrEquals(r, bvmgr.makeBitvector(32, Character.MAX_RADIX), true), + bvmgr.greaterOrEquals(v, bvmgr.makeBitvector(32, 0), true), + bvmgr.lessThan(v, r, true)); + return new IntValue(radix.context, result, bmgr.ifThenElse(valid, v, bvmgr.makeBitvector(32, -1))); + } + + /** + * Invocation handler for Character.forDigit(int digit, int radix): '\0' unless digit is in + * [0, radix) and radix in [2, 36]; else '0' + digit below 10 and 'a' + digit - 10 from there. + */ + private static Value invokeForDigit(Value[] args) throws NotImplementedException, ValueConversionException { + if (args.length != 2) return PlaceHolder.instance; + IntValue digit = args[0].asIntValue(); + IntValue radix = args[1].asIntValue(); + char result = Character.forDigit(digit.concrete, radix.concrete); + if (!digit.isSymbolic() && !radix.isSymbolic()) return new CharValue(digit.context, result); + BitvectorFormulaManager bvmgr = digit.context.getFormulaManager().getBitvectorFormulaManager(); + BooleanFormulaManager bmgr = digit.context.getFormulaManager().getBooleanFormulaManager(); + BitvectorFormula d = digit.formula; + BitvectorFormula r = radix.formula; + BooleanFormula valid = bmgr.and( + bvmgr.greaterOrEquals(d, bvmgr.makeBitvector(32, 0), true), + bvmgr.lessThan(d, r, true), + bvmgr.greaterOrEquals(r, bvmgr.makeBitvector(32, Character.MIN_RADIX), true), + bvmgr.lessOrEquals(r, bvmgr.makeBitvector(32, Character.MAX_RADIX), true)); + BitvectorFormula chars = bmgr.ifThenElse(bvmgr.lessThan(d, bvmgr.makeBitvector(32, 10), true), + bvmgr.add(d, bvmgr.makeBitvector(32, '0')), bvmgr.add(d, bvmgr.makeBitvector(32, 'a' - 10))); + BitvectorFormula code = bmgr.ifThenElse(valid, chars, bvmgr.makeBitvector(32, 0)); + return new CharValue(digit.context, result, bvmgr.extract(code, 15, 0)); + } + + /** Applies the tabulated function {@code f}, cached under {@code key}, to the char or code point {@code arg}. */ + private static Value tabulated(String key, IntUnaryOperator f, Result result, Value arg, boolean onChar) throws NotImplementedException, ValueConversionException { + SolverContext ctx; + BitvectorFormula x; + int in; + if (onChar) { + CharValue c = arg.asCharValue(); + ctx = c.context; + x = c.formula; + in = c.concrete; + } else { + IntValue i = arg.asIntValue(); + ctx = i.context; + x = i.formula; + in = i.concrete; + } + int out = f.applyAsInt(in); + if (!arg.isSymbolic()) { + return switch (result) { + case BOOLEAN -> new BooleanValue(ctx, out != 0); + case BYTE -> new ByteValue(ctx, (byte) out); + case CHAR -> new CharValue(ctx, (char) out); + case INT -> new IntValue(ctx, out); + }; + } + int inWidth = onChar ? 16 : 32; + CodePointTables.Table table = CodePointTables.table(key, f, onChar ? Character.MAX_VALUE : Character.MAX_CODE_POINT, + result.width, result != Result.BOOLEAN, !onChar); + if (table == null) return PlaceHolder.instance; + if (result == Result.BOOLEAN) { + return new BooleanValue(ctx, out != 0, CodePointTables.applyPredicate(ctx, table, x, inWidth)); + } + BitvectorFormula formula = CodePointTables.apply(ctx, table, x, inWidth, result.width); + return switch (result) { + case BYTE -> new ByteValue(ctx, (byte) out, formula); + case CHAR -> new CharValue(ctx, (char) out, formula); + default -> new IntValue(ctx, out, formula); }; } diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/CodePointTables.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/CodePointTables.java new file mode 100644 index 00000000..47ae46da --- /dev/null +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/CodePointTables.java @@ -0,0 +1,127 @@ +package de.uzl.its.swat.symbolic.invoke.java.lang; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.IntUnaryOperator; +import org.sosy_lab.java_smt.api.BitvectorFormula; +import org.sosy_lab.java_smt.api.BitvectorFormulaManager; +import org.sosy_lab.java_smt.api.BooleanFormula; +import org.sosy_lab.java_smt.api.BooleanFormulaManager; +import org.sosy_lab.java_smt.api.SolverContext; + +/** + * Exact symbolic models of functions over chars or code points, such as the Unicode predicates and + * case mappings of {@link Character}, built by tabulating the function in the running JVM. + * + *

The domain (all chars, or all valid code points) is split into maximal segments on which the + * function is either constant or a constant offset from its input (as with most case mappings). + * The model is a balanced if-then-else tree over the segment bounds. Because the table comes from + * the very JDK that runs the target, the model matches its Unicode version exactly. + * + *

For int inputs, every value outside [0, max] must behave the same (a constant or an offset), + * which holds for the {@code Character} methods since they treat all invalid code points alike. + * This is checked on sample values; a function failing the check gets no table. + */ +final class CodePointTables { + + /** Starting at {@code lo}, the function is {@code value}, or input + {@code value} if offset. */ + record Segment(int lo, long value, boolean offset) {} + + /** Segments covering [0, max], and the behaviour outside that range (null for char input). */ + record Table(Segment[] segments, int max, Segment outside) {} + + private static final Map CACHE = new ConcurrentHashMap<>(); + + private static final int[] OUTSIDE_SAMPLES = { + -1, -2, -0x10000, Integer.MIN_VALUE, Character.MAX_CODE_POINT + 1, 0x7FFF_0000, Integer.MAX_VALUE + }; + + private CodePointTables() {} + + /** + * The table of {@code f} over [0, max], with results taken modulo 2^outWidth, cached under + * {@code key}. Returns null if {@code checkOutside} and the values outside [0, max] disagree. + */ + static Table table(String key, IntUnaryOperator f, int max, int outWidth, boolean allowOffset, boolean checkOutside) { + Table cached = CACHE.get(key); + if (cached != null) return cached; + long mask = outWidth == 64 ? -1L : (1L << outWidth) - 1; + List segments = new ArrayList<>(); + int v = 0; + while (v <= max) { + int lo = v; + long c = f.applyAsInt(lo) & mask; + long d = (c - lo) & mask; + int mode = 0; // 0: undecided, 1: constant, 2: offset + v++; + while (v <= max) { + long fv = f.applyAsInt(v) & mask; + if (mode != 2 && fv == c) mode = 1; + else if (mode != 1 && allowOffset && ((fv - v) & mask) == d) mode = 2; + else break; + v++; + } + segments.add(mode == 2 ? new Segment(lo, d, true) : new Segment(lo, c, false)); + } + Segment outside = null; + if (checkOutside) { + outside = outsideSegment(f, mask, allowOffset); + if (outside == null) return null; + } + Table table = new Table(segments.toArray(new Segment[0]), max, outside); + CACHE.put(key, table); + return table; + } + + private static Segment outsideSegment(IntUnaryOperator f, long mask, boolean allowOffset) { + boolean constant = true; + boolean offset = allowOffset; + long c = f.applyAsInt(OUTSIDE_SAMPLES[0]) & mask; + long d = (c - OUTSIDE_SAMPLES[0]) & mask; + for (int s : OUTSIDE_SAMPLES) { + long fs = f.applyAsInt(s) & mask; + constant &= fs == c; + offset &= ((fs - s) & mask) == d; + } + if (constant) return new Segment(0, c, false); + if (offset) return new Segment(0, d, true); + return null; + } + + /** The table as a bitvector of width {@code outWidth}, applied to {@code x} of width {@code inWidth}. */ + static BitvectorFormula apply(SolverContext ctx, Table t, BitvectorFormula x, int inWidth, int outWidth) { + BitvectorFormulaManager bvmgr = ctx.getFormulaManager().getBitvectorFormulaManager(); + BooleanFormulaManager bmgr = ctx.getFormulaManager().getBooleanFormulaManager(); + // The input as seen by an offset segment: zero-extended or truncated to the output width. + BitvectorFormula conv = inWidth == outWidth ? x + : inWidth < outWidth ? bvmgr.extend(x, outWidth - inWidth, false) + : bvmgr.extract(x, outWidth - 1, 0); + BitvectorFormula tree = tree(bvmgr, bmgr, t.segments(), 0, t.segments().length, x, inWidth, conv, outWidth); + if (t.outside() == null) return tree; + BooleanFormula isOutside = bvmgr.greaterThan(x, bvmgr.makeBitvector(inWidth, t.max()), false); // unsigned: negatives too + return bmgr.ifThenElse(isOutside, leaf(bvmgr, t.outside(), conv, outWidth), tree); + } + + /** The table of a predicate (values 0 and 1) as a boolean formula. */ + static BooleanFormula applyPredicate(SolverContext ctx, Table t, BitvectorFormula x, int inWidth) { + BitvectorFormulaManager bvmgr = ctx.getFormulaManager().getBitvectorFormulaManager(); + return bvmgr.equal(apply(ctx, t, x, inWidth, 1), bvmgr.makeBitvector(1, 1)); + } + + private static BitvectorFormula tree(BitvectorFormulaManager bvmgr, BooleanFormulaManager bmgr, Segment[] segs, + int from, int to, BitvectorFormula x, int inWidth, BitvectorFormula conv, int outWidth) { + if (to - from == 1) return leaf(bvmgr, segs[from], conv, outWidth); + int mid = (from + to) >>> 1; + BooleanFormula below = bvmgr.lessThan(x, bvmgr.makeBitvector(inWidth, segs[mid].lo()), false); + return bmgr.ifThenElse(below, + tree(bvmgr, bmgr, segs, from, mid, x, inWidth, conv, outWidth), + tree(bvmgr, bmgr, segs, mid, to, x, inWidth, conv, outWidth)); + } + + private static BitvectorFormula leaf(BitvectorFormulaManager bvmgr, Segment s, BitvectorFormula conv, int outWidth) { + BitvectorFormula value = bvmgr.makeBitvector(outWidth, s.value()); + return s.offset() ? bvmgr.add(conv, value) : value; + } +} diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/DoubleInvocation.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/DoubleInvocation.java index 3960684b..a226956c 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/DoubleInvocation.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/DoubleInvocation.java @@ -2,16 +2,21 @@ import de.uzl.its.swat.common.exceptions.NotImplementedException; import de.uzl.its.swat.common.exceptions.SWATAssert; +import de.uzl.its.swat.symbolic.value.reference.lang.StringValue; import de.uzl.its.swat.common.exceptions.ValueConversionException; import de.uzl.its.swat.symbolic.trace.SymbolicTraceHandler; +import de.uzl.its.swat.symbolic.value.primitive.numeric.BitvectorEncoding; import de.uzl.its.swat.symbolic.value.PlaceHolder; import de.uzl.its.swat.symbolic.value.Value; import de.uzl.its.swat.symbolic.value.primitive.numeric.floatingpoint.DoubleValue; import de.uzl.its.swat.symbolic.value.primitive.numeric.integral.BooleanValue; import de.uzl.its.swat.symbolic.value.primitive.numeric.integral.IntValue; +import de.uzl.its.swat.symbolic.value.primitive.numeric.integral.LongValue; import de.uzl.its.swat.symbolic.value.reference.ObjectValue; import de.uzl.its.swat.symbolic.value.reference.lang.DoubleObjectValue; import org.objectweb.asm.Type; +import org.sosy_lab.java_smt.api.BitvectorFormula; +import org.sosy_lab.java_smt.api.BitvectorFormulaManager; import org.sosy_lab.java_smt.api.BooleanFormula; import org.sosy_lab.java_smt.api.BooleanFormulaManager; import org.sosy_lab.java_smt.api.FloatingPointFormulaManager; @@ -27,10 +32,15 @@ public class DoubleInvocation { SymbolicTraceHandler symbolicTraceHandler) throws NotImplementedException, ValueConversionException { return switch (name) { case "compare" -> invokeCompare(args); + case "doubleToLongBits" -> invokeDoubleToLongBits(args); + case "doubleToRawLongBits" -> invokeDoubleToRawLongBits(args, symbolicTraceHandler); case "isFinite" -> invokeIsFinite(args); case "isInfinite" -> invokeIsInfinite(args); case "isNaN" -> invokeIsNaN(args); - case "valueOf" -> invokeValueOf(args); + case "parseDouble" -> args[0] instanceof StringValue sv ? NumberParsing.parseDouble(sv, symbolicTraceHandler) : PlaceHolder.instance; + case "valueOf" -> args[0] instanceof StringValue sv + ? new DoubleObjectValue(sv.context, NumberParsing.parseDouble(sv, symbolicTraceHandler), ObjectValue.ADDRESS_UNKNOWN) + : invokeValueOf(args); default -> PlaceHolder.instance; }; @@ -56,33 +66,46 @@ public class DoubleInvocation { */ private static IntValue compare(DoubleValue d1, DoubleValue d2) { SolverContext ctx = d1.context; - FloatingPointFormulaManager fpmgr = ctx.getFormulaManager().getFloatingPointFormulaManager(); - BooleanFormulaManager bmgr = ctx.getFormulaManager().getBooleanFormulaManager(); - IntegerFormulaManager imgr = ctx.getFormulaManager().getIntegerFormulaManager(); - - // Check for NaN cases first - BooleanFormula d1IsNaN = fpmgr.isNaN(d1.formula); - BooleanFormula d2IsNaN = fpmgr.isNaN(d2.formula); - - // Regular comparisons (when neither is NaN) - BooleanFormula isEqual = fpmgr.equalWithFPSemantics(d1.formula, d2.formula); - BooleanFormula isLess = fpmgr.lessThan(d1.formula, d2.formula); + BitvectorFormula result = BitvectorEncoding.javaCompare(ctx.getFormulaManager(), d1.formula, d2.formula); + return new IntValue(ctx, Double.compare(d1.concrete, d2.concrete), result); + } - // Build the result: - // - If both are NaN: return 0 - // - If only d1 is NaN: return 1 (NaN > anything) - // - If only d2 is NaN: return -1 (anything < NaN) - // - If equal: return 0 - // - If d1 < d2: return -1 - // - Otherwise: return 1 - NumeralFormula.IntegerFormula normalResult = bmgr.ifThenElse(isEqual, imgr.makeNumber(0), - bmgr.ifThenElse(isLess, imgr.makeNumber(-1), imgr.makeNumber(1))); + /** + * Invocation handler for Double.doubleToLongBits(double). + */ + private static Value invokeDoubleToLongBits(Value[] args) + throws ValueConversionException, NotImplementedException { + SWATAssert.enforce(args.length == 1, "Expected 1 argument for doubleToLongBits(), got " + args.length); + return doubleToLongBits(args[0].asDoubleValue()); + } - NumeralFormula.IntegerFormula result = bmgr.ifThenElse(bmgr.and(d1IsNaN, d2IsNaN), imgr.makeNumber(0), - bmgr.ifThenElse(d1IsNaN, imgr.makeNumber(1), - bmgr.ifThenElse(d2IsNaN, imgr.makeNumber(-1), normalResult))); + /** + * Symbolic wrapper for Double.doubleToLongBits(double value): the IEEE 754 bit layout, with every + * NaN collapsed to the canonical 0x7ff8000000000000L as in the JDK (the bit-vector of a NaN is + * unspecified in SMT-LIB). + */ + private static LongValue doubleToLongBits(DoubleValue d) { + SolverContext ctx = d.context; + FloatingPointFormulaManager fpmgr = ctx.getFormulaManager().getFloatingPointFormulaManager(); + BitvectorFormulaManager bvmgr = ctx.getFormulaManager().getBitvectorFormulaManager(); + BooleanFormulaManager bmgr = ctx.getFormulaManager().getBooleanFormulaManager(); + BitvectorFormula bits = bmgr.ifThenElse(fpmgr.isNaN(d.formula), + bvmgr.makeBitvector(64, 0x7ff8000000000000L), fpmgr.toIeeeBitvector(d.formula)); + return new LongValue(ctx, Double.doubleToLongBits(d.concrete), bits); + } - return new IntValue(ctx, Double.compare(d1.concrete, d2.concrete), result); + /** + * Invocation handler for Double.doubleToRawLongBits(double). The model is that of + * doubleToLongBits: SMT floating point has a single NaN, so the payload of a NaN is not + * represented and a symbolic argument records precision loss. + */ + private static Value invokeDoubleToRawLongBits(Value[] args, SymbolicTraceHandler symbolicTraceHandler) + throws ValueConversionException, NotImplementedException { + SWATAssert.enforce(args.length == 1, "Expected 1 argument for doubleToRawLongBits(), got " + args.length); + DoubleValue d = args[0].asDoubleValue(); + LongValue bits = doubleToLongBits(d); + if (symbolicTraceHandler != null && d.isSymbolic()) symbolicTraceHandler.recordSymbolicPrecisionLoss(); + return new LongValue(d.context, Double.doubleToRawLongBits(d.concrete), bits.formula); } /** diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/FloatInvocation.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/FloatInvocation.java index 28c72d6b..c2974145 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/FloatInvocation.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/FloatInvocation.java @@ -13,8 +13,10 @@ import de.uzl.its.swat.common.exceptions.NotImplementedException; import de.uzl.its.swat.common.exceptions.SWATAssert; +import de.uzl.its.swat.symbolic.value.reference.lang.StringValue; import de.uzl.its.swat.common.exceptions.ValueConversionException; import de.uzl.its.swat.symbolic.trace.SymbolicTraceHandler; +import de.uzl.its.swat.symbolic.value.primitive.numeric.BitvectorEncoding; import de.uzl.its.swat.symbolic.value.PlaceHolder; import de.uzl.its.swat.symbolic.value.Value; import de.uzl.its.swat.symbolic.value.primitive.numeric.floatingpoint.FloatValue; @@ -32,14 +34,17 @@ public class FloatInvocation { return switch (name) { case "compare" -> invokeCompare(args); case "floatToIntBits" -> invokeFloatToIntBits(args); - case "floatToRawIntBits" -> invokeFloatToIntBits(args); // Same as floatToIntBits for symbolic execution + case "floatToRawIntBits" -> invokeFloatToRawIntBits(args, symbolicTraceHandler); case "isFinite" -> invokeIsFinite(args); case "isInfinite" -> invokeIsInfinite(args); case "isNaN" -> invokeIsNaN(args); case "max" -> invokeMax(args); case "min" -> invokeMin(args); case "sum" -> invokeSum(args); - case "valueOf" -> invokeValueOf(args); + case "parseFloat" -> args[0] instanceof StringValue sv ? NumberParsing.parseFloat(sv, symbolicTraceHandler) : PlaceHolder.instance; + case "valueOf" -> args[0] instanceof StringValue sv + ? new FloatObjectValue(sv.context, NumberParsing.parseFloat(sv, symbolicTraceHandler), ObjectValue.ADDRESS_UNKNOWN) + : invokeValueOf(args); default -> PlaceHolder.instance; }; @@ -75,32 +80,7 @@ public class FloatInvocation { */ private static IntValue compare(FloatValue f1, FloatValue f2) { SolverContext ctx = f1.context; - FloatingPointFormulaManager fpmgr = ctx.getFormulaManager().getFloatingPointFormulaManager(); - BooleanFormulaManager bmgr = ctx.getFormulaManager().getBooleanFormulaManager(); - IntegerFormulaManager imgr = ctx.getFormulaManager().getIntegerFormulaManager(); - - // Check for NaN cases first - BooleanFormula f1IsNaN = fpmgr.isNaN(f1.formula); - BooleanFormula f2IsNaN = fpmgr.isNaN(f2.formula); - - // Regular comparisons (when neither is NaN) - BooleanFormula isEqual = fpmgr.equalWithFPSemantics(f1.formula, f2.formula); - BooleanFormula isLess = fpmgr.lessThan(f1.formula, f2.formula); - - // Build the result using IntegerFormula (will be converted to bitvector by IntValue constructor): - // - If both are NaN: return 0 - // - If only f1 is NaN: return 1 (NaN > anything) - // - If only f2 is NaN: return -1 (anything < NaN) - // - If equal: return 0 - // - If f1 < f2: return -1 - // - Otherwise: return 1 - NumeralFormula.IntegerFormula normalResult = bmgr.ifThenElse(isEqual, imgr.makeNumber(0), - bmgr.ifThenElse(isLess, imgr.makeNumber(-1), imgr.makeNumber(1))); - - NumeralFormula.IntegerFormula result = bmgr.ifThenElse(bmgr.and(f1IsNaN, f2IsNaN), imgr.makeNumber(0), - bmgr.ifThenElse(f1IsNaN, imgr.makeNumber(1), - bmgr.ifThenElse(f2IsNaN, imgr.makeNumber(-1), normalResult))); - + BitvectorFormula result = BitvectorEncoding.javaCompare(ctx.getFormulaManager(), f1.formula, f2.formula); return new IntValue(ctx, Float.compare(f1.concrete, f2.concrete), result); } /** @@ -314,10 +294,27 @@ private static IntValue floatToIntBits(FloatValue f) { FloatingPointFormulaManager fpmgr = ctx.getFormulaManager().getFloatingPointFormulaManager(); BitvectorFormulaManager bvmgr = ctx.getFormulaManager().getBitvectorFormulaManager(); - // Convert the floating-point formula to a 32-bit bitvector representation - // This gives us the IEEE 754 bit representation - BitvectorFormula bvFormula = fpmgr.toIeeeBitvector(f.formula); + BooleanFormulaManager bmgr = ctx.getFormulaManager().getBooleanFormulaManager(); + + // The IEEE 754 bit layout; every NaN is collapsed to the canonical 0x7fc00000, as in the JDK + // (the bit-vector of a NaN is unspecified in SMT-LIB) + BitvectorFormula bvFormula = bmgr.ifThenElse(fpmgr.isNaN(f.formula), + bvmgr.makeBitvector(32, 0x7fc00000), fpmgr.toIeeeBitvector(f.formula)); return new IntValue(ctx, Float.floatToIntBits(f.concrete), bvFormula); } + + /** + * Invocation handler for Float.floatToRawIntBits(float). The model is that of floatToIntBits: + * SMT floating point has a single NaN, so the payload of a NaN is not represented and a symbolic + * argument records precision loss. + */ + private static Value invokeFloatToRawIntBits(Value[] args, SymbolicTraceHandler symbolicTraceHandler) + throws ValueConversionException, NotImplementedException { + SWATAssert.enforce(args.length == 1, "Expected 1 argument for floatToRawIntBits(), got " + args.length); + FloatValue f = args[0].asFloatValue(); + IntValue bits = floatToIntBits(f); + if (symbolicTraceHandler != null && f.isSymbolic()) symbolicTraceHandler.recordSymbolicPrecisionLoss(); + return new IntValue(f.context, Float.floatToRawIntBits(f.concrete), bits.formula); + } } \ No newline at end of file diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/IntegerInvocation.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/IntegerInvocation.java index e18f0273..f11a5552 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/IntegerInvocation.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/IntegerInvocation.java @@ -1,5 +1,6 @@ package de.uzl.its.swat.symbolic.invoke.java.lang; +import de.uzl.its.swat.common.exceptions.NoThreadContextException; import de.uzl.its.swat.common.exceptions.NotImplementedException; import de.uzl.its.swat.common.exceptions.ValueConversionException; import de.uzl.its.swat.symbolic.value.PlaceHolder; @@ -16,9 +17,9 @@ public class IntegerInvocation { - public static Value invokeStaticMethod(String name, Value[] args, Type[] desc) throws ValueConversionException, NotImplementedException { + public static Value invokeStaticMethod(String name, Value[] args, Type[] desc) throws ValueConversionException, NotImplementedException, NoThreadContextException { return switch (name) { - case "bitCode" -> invokeBitCode(args); + case "bitCount" -> invokeBitCount(args); case "compare" -> invokeCompare(args); case "compareUnsigned" -> invokeCompareUnsigned(args); case "decode" -> invokeDecode(args); @@ -52,7 +53,7 @@ public class IntegerInvocation { } /** - * Invocation handler for Integer.bitCode(*), handles argument conversion. + * Invocation handler for Integer.bitCount(*), handles argument conversion. * * @param args The invocation arguments from the shadow stack * @return The resulting Value @@ -60,10 +61,11 @@ public class IntegerInvocation { * @throws ValueConversionException Thrown if the argument cannot be converted to the desired * type, likely a missing implementation */ - private static Value invokeBitCode(Value[] args) - throws IndexOutOfBoundsException, ValueConversionException { + private static Value invokeBitCount(Value[] args) + throws IndexOutOfBoundsException, ValueConversionException, NotImplementedException { assert args.length == 1 : "[SWAT] Expected 1 argument(s), got " + args.length; - return PlaceHolder.instance; + IntValue i = args[0].asIntValue(); + return new IntValue(i.context, Integer.bitCount(i.concrete), BitOps.bitCount(i.context, i.formula, 32)); } /** @@ -91,9 +93,9 @@ public class IntegerInvocation { * href="https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Integer.html#compare(int,int) */ private static IntValue compare(IntValue x, IntValue y) { - SolverContext context = x.context; - BitvectorFormulaManager bvmgr = context.getFormulaManager().getBitvectorFormulaManager(); - return new IntValue(context, x.concrete - y.concrete, bvmgr.subtract(x.formula, y.formula)); + // (x < y) ? -1 : ((x == y) ? 0 : 1); x - y would overflow + return new IntValue(x.context, Integer.compare(x.concrete, y.concrete), + BitOps.compare(x.context, x.formula, y.formula, true)); } /** @@ -407,9 +409,10 @@ private static IntValue min(IntValue a, IntValue b) { * type, likely a missing implementation */ private static Value invokeNumberOfLeadingZeros(Value[] args) - throws IndexOutOfBoundsException, ValueConversionException { + throws IndexOutOfBoundsException, ValueConversionException, NotImplementedException { assert args.length == 1 : "Expected 1 argument(s), got " + args.length; - return PlaceHolder.instance; + IntValue i = args[0].asIntValue(); + return new IntValue(i.context, Integer.numberOfLeadingZeros(i.concrete), BitOps.numberOfLeadingZeros(i.context, i.formula, 32)); } /** @@ -422,9 +425,10 @@ private static IntValue min(IntValue a, IntValue b) { * type, likely a missing implementation */ private static Value invokeNumberOfTrailingZeros(Value[] args) - throws IndexOutOfBoundsException, ValueConversionException { + throws IndexOutOfBoundsException, ValueConversionException, NotImplementedException { assert args.length == 1 : "Expected 1 argument(s), got " + args.length; - return PlaceHolder.instance; + IntValue i = args[0].asIntValue(); + return new IntValue(i.context, Integer.numberOfTrailingZeros(i.concrete), BitOps.numberOfTrailingZeros(i.context, i.formula, 32)); } /** @@ -440,27 +444,12 @@ private static IntValue min(IntValue a, IntValue b) { throws IndexOutOfBoundsException, ValueConversionException, NotImplementedException { assert args.length == 1 || args.length == 2 : "Expected 1 or 2 argument(s), got " + args.length; - if (args.length == 1 && args[0] instanceof StringValue sv) { - return parseInt(sv); + if (args[0] instanceof StringValue sv) { + return NumberParsing.parseInt(sv, args); } - // parseInt with radix not yet supported return PlaceHolder.instance; } - /** - * Symbolic wrapper for Integer.parseInt(String s) - * Parses the string argument as a signed decimal integer. - * - * @param s The String containing the integer representation to be parsed - * @return The integer value represented by the argument - * @see Integer.parseInt(String) - */ - private static IntValue parseInt(StringValue s) { - SolverContext ctx = s.context; - StringFormulaManager sfm = ctx.getFormulaManager().getStringFormulaManager(); - return new IntValue(ctx, Integer.parseInt(s.concrete), sfm.toIntegerFormula(s.formula)); - } - /** * Invocation handler for Integer.parseUnsignedInt(*), handles argument conversion. * @@ -521,9 +510,10 @@ private static IntValue remainderUnsigned(IntValue dividend, IntValue divisor) { * type, likely a missing implementation */ private static Value invokeReverse(Value[] args) - throws IndexOutOfBoundsException, ValueConversionException { + throws IndexOutOfBoundsException, ValueConversionException, NotImplementedException { assert args.length == 1 : "Expected 1 argument(s), got " + args.length; - return PlaceHolder.instance; + IntValue i = args[0].asIntValue(); + return new IntValue(i.context, Integer.reverse(i.concrete), BitOps.reverse(i.context, i.formula, 32)); } /** @@ -694,9 +684,11 @@ private static IntValue sum(IntValue a, IntValue b) { * type, likely a missing implementation */ private static Value invokeToBinaryString(Value[] args) - throws IndexOutOfBoundsException, ValueConversionException { + throws IndexOutOfBoundsException, ValueConversionException, NotImplementedException { assert args.length == 1 : "Expected 1 argument(s), got " + args.length; - return PlaceHolder.instance; + IntValue i = args[0].asIntValue(); + return new StringValue(i.context, Integer.toBinaryString(i.concrete), + BitOps.toUnsignedPowerOfTwoString(i.context, i.formula, 32, 1), ObjectValue.ADDRESS_UNKNOWN); } /** @@ -709,9 +701,11 @@ private static IntValue sum(IntValue a, IntValue b) { * type, likely a missing implementation */ private static Value invokeToHexString(Value[] args) - throws IndexOutOfBoundsException, ValueConversionException { + throws IndexOutOfBoundsException, ValueConversionException, NotImplementedException { assert args.length == 1 : "Expected 1 argument(s), got " + args.length; - return PlaceHolder.instance; + IntValue i = args[0].asIntValue(); + return new StringValue(i.context, Integer.toHexString(i.concrete), + BitOps.toUnsignedPowerOfTwoString(i.context, i.formula, 32, 4), ObjectValue.ADDRESS_UNKNOWN); } /** @@ -724,9 +718,11 @@ private static IntValue sum(IntValue a, IntValue b) { * type, likely a missing implementation */ private static Value invokeToOctalString(Value[] args) - throws IndexOutOfBoundsException, ValueConversionException { + throws IndexOutOfBoundsException, ValueConversionException, NotImplementedException { assert args.length == 1 : "Expected 1 argument(s), got " + args.length; - return PlaceHolder.instance; + IntValue i = args[0].asIntValue(); + return new StringValue(i.context, Integer.toOctalString(i.concrete), + BitOps.toUnsignedPowerOfTwoString(i.context, i.formula, 32, 3), ObjectValue.ADDRESS_UNKNOWN); } /** @@ -739,14 +735,16 @@ private static IntValue sum(IntValue a, IntValue b) { * type, likely a missing implementation */ private static Value invokeToString(Value[] args) - throws IndexOutOfBoundsException, ValueConversionException, NotImplementedException { + throws IndexOutOfBoundsException, ValueConversionException, NotImplementedException, NoThreadContextException { assert args.length == 1 || args.length == 2 : "Expected 1 or 2 argument(s), got " + args.length; - if (args.length == 1 && args[0] instanceof IntValue iv) { - return toString(iv); + if (args.length == 1) { + return args[0] instanceof IntValue iv ? toString(iv) : PlaceHolder.instance; } - // toString with radix not yet supported - return PlaceHolder.instance; + IntValue i = args[0].asIntValue(); + int radix = NumberStrings.selectRadix(args[1].asIntValue()); + return new StringValue(i.context, Integer.toString(i.concrete, radix), + NumberStrings.toString(i.context, i.formula, 32, true, radix), ObjectValue.ADDRESS_UNKNOWN); } /** @@ -830,10 +828,13 @@ private static LongValue toUnsignedLong(IntValue x) { * type, likely a missing implementation */ private static Value invokeToUnsignedString(Value[] args) - throws IndexOutOfBoundsException, ValueConversionException { + throws IndexOutOfBoundsException, ValueConversionException, NotImplementedException, NoThreadContextException { assert args.length == 1 || args.length == 2 : "Expected 1 or 2 argument(s), got " + args.length; - return PlaceHolder.instance; + IntValue i = args[0].asIntValue(); + int radix = args.length == 1 ? 10 : NumberStrings.selectRadix(args[1].asIntValue()); + return new StringValue(i.context, Integer.toUnsignedString(i.concrete, radix), + NumberStrings.toString(i.context, i.formula, 32, false, radix), ObjectValue.ADDRESS_UNKNOWN); } /** @@ -846,12 +847,15 @@ private static LongValue toUnsignedLong(IntValue x) { * type, likely a missing implementation */ private static Value invokeValueOf(Value[] args) - throws IndexOutOfBoundsException, ValueConversionException { + throws IndexOutOfBoundsException, ValueConversionException, NotImplementedException { assert args.length == 1 || args.length == 2 : "Expected 1 or 2 argument(s), got " + args.length; if (args.length == 1 && args[0] instanceof IntValue val) { return valueOf(val); } + if (args[0] instanceof StringValue sv) { + return valueOf(NumberParsing.parseInt(sv, args)); + } return PlaceHolder.instance; } diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/LongInvocation.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/LongInvocation.java index 601e6af3..46268b54 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/LongInvocation.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/LongInvocation.java @@ -1,5 +1,6 @@ package de.uzl.its.swat.symbolic.invoke.java.lang; +import de.uzl.its.swat.common.exceptions.NoThreadContextException; import de.uzl.its.swat.common.exceptions.NotImplementedException; import de.uzl.its.swat.common.exceptions.ValueConversionException; import de.uzl.its.swat.symbolic.trace.SymbolicTraceHandler; @@ -27,7 +28,7 @@ public class LongInvocation { String name, Value[] args, Type[] desc, - SymbolicTraceHandler symbolicTraceHandler) throws ValueConversionException, NotImplementedException { + SymbolicTraceHandler symbolicTraceHandler) throws ValueConversionException, NotImplementedException, NoThreadContextException { return switch (name) { case "bitCount" -> invokeBitCount(args); case "compare" -> invokeCompare(args); @@ -71,9 +72,15 @@ public class LongInvocation { * type, likely a missing implementation */ private static Value invokeBitCount(Value[] args) - throws IndexOutOfBoundsException, ValueConversionException { + throws IndexOutOfBoundsException, ValueConversionException, NotImplementedException { assert args.length == 1 : "[SWAT] Expected 1 argument(s), got " + args.length; - return PlaceHolder.instance; + LongValue i = args[0].asLongValue(); + return new IntValue(i.context, Long.bitCount(i.concrete), toInt(i, BitOps.bitCount(i.context, i.formula, 64))); + } + + /** The low 32 bits of a 64-bit count, which is at most 64. */ + private static BitvectorFormula toInt(LongValue i, BitvectorFormula count) { + return i.context.getFormulaManager().getBitvectorFormulaManager().extract(count, 31, 0); } /** * Invocation handler for Long.compare(*), handles argument conversion. @@ -406,9 +413,10 @@ private static LongValue min(LongValue a, LongValue b) { * type, likely a missing implementation */ private static Value invokeNumberOfLeadingZeros(Value[] args) - throws IndexOutOfBoundsException, ValueConversionException { + throws IndexOutOfBoundsException, ValueConversionException, NotImplementedException { assert args.length == 1 : "Expected 1 argument(s), got " + args.length; - return PlaceHolder.instance; + LongValue i = args[0].asLongValue(); + return new IntValue(i.context, Long.numberOfLeadingZeros(i.concrete), toInt(i, BitOps.numberOfLeadingZeros(i.context, i.formula, 64))); } /** @@ -421,9 +429,10 @@ private static LongValue min(LongValue a, LongValue b) { * type, likely a missing implementation */ private static Value invokeNumberOfTrailingZeros(Value[] args) - throws IndexOutOfBoundsException, ValueConversionException { + throws IndexOutOfBoundsException, ValueConversionException, NotImplementedException { assert args.length == 1 : "Expected 1 argument(s), got " + args.length; - return PlaceHolder.instance; + LongValue i = args[0].asLongValue(); + return new IntValue(i.context, Long.numberOfTrailingZeros(i.concrete), toInt(i, BitOps.numberOfTrailingZeros(i.context, i.formula, 64))); } /** @@ -439,28 +448,12 @@ private static LongValue min(LongValue a, LongValue b) { throws IndexOutOfBoundsException, ValueConversionException, NotImplementedException { assert args.length == 1 || args.length == 2 : "Expected 1 or 2 argument(s), got " + args.length; - if (args.length == 1 && args[0] instanceof StringValue sv) { - return parseLong(sv); + if (args[0] instanceof StringValue sv) { + return NumberParsing.parseLong(sv, args); } - // parseLong with radix not yet supported return PlaceHolder.instance; } - /** - * Symbolic wrapper for Long.parseLong(String s) - * Parses the string argument as a signed decimal long. - * - * @param s The String containing the long representation to be parsed - * @return The long value represented by the argument - */ - private static LongValue parseLong(StringValue s) { - SolverContext ctx = s.context; - StringFormulaManager sfm = ctx.getFormulaManager().getStringFormulaManager(); - BitvectorFormulaManager bvmgr = ctx.getFormulaManager().getBitvectorFormulaManager(); - return new LongValue(ctx, Long.parseLong(s.concrete), - bvmgr.makeBitvector(64, sfm.toIntegerFormula(s.formula))); - } - /** * Invocation handler for Long.parseUnsignedLong(*), handles argument conversion. * @@ -521,9 +514,10 @@ private static LongValue remainderUnsigned(LongValue dividend, LongValue divisor * type, likely a missing implementation */ private static Value invokeReverse(Value[] args) - throws IndexOutOfBoundsException, ValueConversionException { + throws IndexOutOfBoundsException, ValueConversionException, NotImplementedException { assert args.length == 1 : "Expected 1 argument(s), got " + args.length; - return PlaceHolder.instance; + LongValue i = args[0].asLongValue(); + return new LongValue(i.context, Long.reverse(i.concrete), BitOps.reverse(i.context, i.formula, 64)); } /** @@ -701,9 +695,11 @@ private static LongValue sum(LongValue a, LongValue b) { * type, likely a missing implementation */ private static Value invokeToBinaryString(Value[] args) - throws IndexOutOfBoundsException, ValueConversionException { + throws IndexOutOfBoundsException, ValueConversionException, NotImplementedException { assert args.length == 1 : "Expected 1 argument(s), got " + args.length; - return PlaceHolder.instance; + LongValue i = args[0].asLongValue(); + return new StringValue(i.context, Long.toBinaryString(i.concrete), + BitOps.toUnsignedPowerOfTwoString(i.context, i.formula, 64, 1), ObjectValue.ADDRESS_UNKNOWN); } /** @@ -716,9 +712,11 @@ private static LongValue sum(LongValue a, LongValue b) { * type, likely a missing implementation */ private static Value invokeToHexString(Value[] args) - throws IndexOutOfBoundsException, ValueConversionException { + throws IndexOutOfBoundsException, ValueConversionException, NotImplementedException { assert args.length == 1 : "Expected 1 argument(s), got " + args.length; - return PlaceHolder.instance; + LongValue i = args[0].asLongValue(); + return new StringValue(i.context, Long.toHexString(i.concrete), + BitOps.toUnsignedPowerOfTwoString(i.context, i.formula, 64, 4), ObjectValue.ADDRESS_UNKNOWN); } /** @@ -731,9 +729,11 @@ private static LongValue sum(LongValue a, LongValue b) { * type, likely a missing implementation */ private static Value invokeToOctalString(Value[] args) - throws IndexOutOfBoundsException, ValueConversionException { + throws IndexOutOfBoundsException, ValueConversionException, NotImplementedException { assert args.length == 1 : "Expected 1 argument(s), got " + args.length; - return PlaceHolder.instance; + LongValue i = args[0].asLongValue(); + return new StringValue(i.context, Long.toOctalString(i.concrete), + BitOps.toUnsignedPowerOfTwoString(i.context, i.formula, 64, 3), ObjectValue.ADDRESS_UNKNOWN); } /** @@ -746,14 +746,16 @@ private static LongValue sum(LongValue a, LongValue b) { * type, likely a missing implementation */ private static Value invokeToString(Value[] args) - throws IndexOutOfBoundsException, ValueConversionException, NotImplementedException { + throws IndexOutOfBoundsException, ValueConversionException, NotImplementedException, NoThreadContextException { assert args.length == 1 || args.length == 2 : "Expected 1 or 2 argument(s), got " + args.length; - if (args.length == 1 && args[0] instanceof LongValue lv) { - return toString(lv); + if (args.length == 1) { + return args[0] instanceof LongValue lv ? toString(lv) : PlaceHolder.instance; } - // toString with radix not yet supported - return PlaceHolder.instance; + LongValue l = args[0].asLongValue(); + int radix = NumberStrings.selectRadix(args[1].asIntValue()); + return new StringValue(l.context, Long.toString(l.concrete, radix), + NumberStrings.toString(l.context, l.formula, 64, true, radix), ObjectValue.ADDRESS_UNKNOWN); } /** @@ -806,10 +808,13 @@ private static StringValue toString(LongValue l) { * type, likely a missing implementation */ private static Value invokeToUnsignedString(Value[] args) - throws IndexOutOfBoundsException, ValueConversionException { + throws IndexOutOfBoundsException, ValueConversionException, NotImplementedException, NoThreadContextException { assert args.length == 1 || args.length == 2 : "Expected 1 or 2 argument(s), got " + args.length; - return PlaceHolder.instance; + LongValue l = args[0].asLongValue(); + int radix = args.length == 1 ? 10 : NumberStrings.selectRadix(args[1].asIntValue()); + return new StringValue(l.context, Long.toUnsignedString(l.concrete, radix), + NumberStrings.toString(l.context, l.formula, 64, false, radix), ObjectValue.ADDRESS_UNKNOWN); } /** @@ -828,6 +833,10 @@ private static StringValue toString(LongValue l) { if (args.length == 1 && args[0] instanceof LongValue val) { return new LongObjectValue(val.context, val, ObjectValue.ADDRESS_UNKNOWN); } + if (args[0] instanceof StringValue sv) { + LongValue val = NumberParsing.parseLong(sv, args); + return new LongObjectValue(val.context, val, ObjectValue.ADDRESS_UNKNOWN); + } return PlaceHolder.instance; } } diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/MathInvocation.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/MathInvocation.java index 698fb6e9..cb2f0fea 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/MathInvocation.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/MathInvocation.java @@ -3,7 +3,6 @@ import de.uzl.its.swat.common.exceptions.NotImplementedException; import de.uzl.its.swat.common.exceptions.SWATAssert; import de.uzl.its.swat.common.exceptions.ValueConversionException; -import de.uzl.its.swat.symbolic.UFs.SinCosUF; import de.uzl.its.swat.symbolic.trace.SymbolicTraceHandler; import de.uzl.its.swat.symbolic.value.PlaceHolder; import de.uzl.its.swat.symbolic.value.Value; @@ -12,7 +11,6 @@ import de.uzl.its.swat.symbolic.value.primitive.numeric.floatingpoint.FloatValue; import de.uzl.its.swat.symbolic.value.primitive.numeric.integral.IntValue; import de.uzl.its.swat.symbolic.value.primitive.numeric.integral.LongValue; -import de.uzl.its.swat.thread.ThreadHandler; import org.objectweb.asm.Type; import org.sosy_lab.java_smt.api.*; @@ -23,101 +21,132 @@ public class MathInvocation { Value[] args, Type[] desc, SymbolicTraceHandler symbolicTraceHandler) throws NotImplementedException, ValueConversionException { + return invokeStaticMethod(name, args, desc, symbolicTraceHandler, false); + } + + /** + * @param strict whether the owner is StrictMath, whose transcendental functions may round + * differently from Math's + */ + public static Value invokeStaticMethod( + String name, + Value[] args, + Type[] desc, + SymbolicTraceHandler symbolicTraceHandler, + boolean strict) throws NotImplementedException, ValueConversionException { return switch (name) { - case "sin" -> invokeSin(args); - case "cos" -> invokeCos(args); case "abs" -> invokeAbs(args); case "max" -> invokeMax(args); case "min" -> invokeMin(args); case "round" -> invokeRound(args); case "sqrt" -> invokeSqrt(args); - - default -> PlaceHolder.instance; + case "floor" -> roundToIntegral(args, FloatingPointRoundingMode.TOWARD_NEGATIVE, Math::floor); + case "ceil" -> roundToIntegral(args, FloatingPointRoundingMode.TOWARD_POSITIVE, Math::ceil); + case "rint" -> roundToIntegral(args, FloatingPointRoundingMode.NEAREST_TIES_TO_EVEN, Math::rint); + // Both are a single multiplication by a constant (Math.toRadians is angdeg * DEGREES_TO_RADIANS). + case "toRadians" -> scale(args, 0.017453292519943295, strict ? StrictMath::toRadians : Math::toRadians); + case "toDegrees" -> scale(args, 57.29577951308232, strict ? StrictMath::toDegrees : Math::toDegrees); + case "getExponent" -> invokeGetExponent(args); + case "signum" -> invokeSignum(args); + case "floorDiv" -> invokeFloorDivMod(args, false); + case "floorMod" -> invokeFloorDivMod(args, true); + case "pow" -> SampledMath.binary("pow" + (strict ? "/strict" : ""), args, strict ? StrictMath::pow : Math::pow, + // inverses: pow(x, y0) = t at x = t^(1/y0), pow(x0, y) = t at y = log(t) / log(x0) + (t, y0) -> y0 == 0 ? Double.NaN : Math.pow(t, 1 / y0), + (t, x0) -> Math.log(t) / Math.log(x0), + symbolicTraceHandler); + case "atan2" -> SampledMath.binary("atan2" + (strict ? "/strict" : ""), args, strict ? StrictMath::atan2 : Math::atan2, + // inverses: atan2(y, x0) = t at y = x0 * tan(t), atan2(y0, x) = t at x = y0 / tan(t) + (t, x0) -> x0 * Math.tan(t), + (t, y0) -> y0 / Math.tan(t), + symbolicTraceHandler); + default -> SampledMath.unary(name, strict, args, symbolicTraceHandler); }; } - /** - * Invocation handler for Math.sin(double a). - * Uses an UF. - * - * @see Math.sin(double a) - */ - private static Value invokeSin(Value[] args) { - SWATAssert.enforce(args.length == 1, "Wrong argument counts"); - SWATAssert.enforce(args[0] instanceof DoubleValue, "Wrong argument type"); - return sin((DoubleValue) args[0]); + /** Math.floor, ceil and rint: IEEE round-to-integral in the given mode, which is exactly what they compute. */ + private static Value roundToIntegral(Value[] args, FloatingPointRoundingMode mode, java.util.function.DoubleUnaryOperator concrete) { + if (args.length != 1 || !(args[0] instanceof DoubleValue a)) return PlaceHolder.instance; + FloatingPointFormulaManager fpfm = a.context.getFormulaManager().getFloatingPointFormulaManager(); + return new DoubleValue(a.context, concrete.applyAsDouble(a.concrete), fpfm.round(a.formula, mode)); + } + + /** Math.toRadians and toDegrees: one correctly rounded multiplication by a constant. */ + private static Value scale(Value[] args, double factor, java.util.function.DoubleUnaryOperator concrete) { + if (args.length != 1 || !(args[0] instanceof DoubleValue a)) return PlaceHolder.instance; + FloatingPointFormulaManager fpfm = a.context.getFormulaManager().getFloatingPointFormulaManager(); + return new DoubleValue(a.context, concrete.applyAsDouble(a.concrete), + fpfm.multiply(a.formula, new DoubleValue(a.context, factor).formula)); } /** - * Symbolic wrapper for Math.sin(double a). - * Uses an UF. - * - * @see Math.sin(double a) + * Math.getExponent(float) and getExponent(double): the biased exponent field minus the bias. + * This also gives MAX_EXPONENT + 1 for NaN and infinities and MIN_EXPONENT - 1 for zeros and + * subnormals, as specified. */ - private static DoubleValue sin(DoubleValue a) { - SolverContext ctx = a.context; - - // Fast path: concrete value only - if (!a.isSymbolic()) { - return new DoubleValue(ctx, Math.sin(a.concrete)); - // potentially we could do an addConstraint to remember this concrete case for the UF or other trigonometric UFs + private static Value invokeGetExponent(Value[] args) { + if (args.length != 1) return PlaceHolder.instance; + if (args[0] instanceof DoubleValue d) { + BitvectorFormulaManager bvmgr = d.context.getFormulaManager().getBitvectorFormulaManager(); + BitvectorFormula bits = d.context.getFormulaManager().getFloatingPointFormulaManager().toIeeeBitvector(d.formula); + BitvectorFormula field = bvmgr.extend(bvmgr.extract(bits, 62, 52), 21, false); + return new IntValue(d.context, Math.getExponent(d.concrete), bvmgr.subtract(field, bvmgr.makeBitvector(32, Double.MAX_EXPONENT))); + } else if (args[0] instanceof FloatValue f) { + BitvectorFormulaManager bvmgr = f.context.getFormulaManager().getBitvectorFormulaManager(); + BitvectorFormula bits = f.context.getFormulaManager().getFloatingPointFormulaManager().toIeeeBitvector(f.formula); + BitvectorFormula field = bvmgr.extend(bvmgr.extract(bits, 30, 23), 24, false); + return new IntValue(f.context, Math.getExponent(f.concrete), bvmgr.subtract(field, bvmgr.makeBitvector(32, Float.MAX_EXPONENT))); } + return PlaceHolder.instance; + } - try { - SinCosUF uf = ThreadHandler.getUFHandler(Thread.currentThread().getId()).getSinCosUF(); - - // The symbolic result - var ufResult = uf.applySin(a.formula); - - // Return with concrete result for concolic steering - return new DoubleValue(ctx, Math.sin(a.concrete), ufResult); - - } catch (Exception e) { - // Fallback to concrete-only if UF setup fails - return new DoubleValue(ctx, Math.sin(a.concrete)); + /** Math.signum: the argument itself for zeros and NaN, otherwise 1.0 with its sign. */ + private static Value invokeSignum(Value[] args) { + if (args.length != 1) return PlaceHolder.instance; + if (args[0] instanceof DoubleValue d) { + return new DoubleValue(d.context, Math.signum(d.concrete), signum(d.context, d.formula, new DoubleValue(d.context, 1.0).formula)); + } else if (args[0] instanceof FloatValue f) { + return new FloatValue(f.context, Math.signum(f.concrete), signum(f.context, f.formula, new FloatValue(f.context, 1.0f).formula)); } + return PlaceHolder.instance; } - /** - * Invocation handler for Math.cos(double a). - * Uses an UF. - * - * @see Math.cos(double a) - */ - private static Value invokeCos(Value[] args) { - SWATAssert.enforce(args.length == 1, "Wrong argument counts"); - SWATAssert.enforce(args[0] instanceof DoubleValue, "Wrong argument type"); - return cos((DoubleValue) args[0]); + private static FloatingPointFormula signum(SolverContext ctx, FloatingPointFormula x, FloatingPointFormula one) { + FloatingPointFormulaManager fpfm = ctx.getFormulaManager().getFloatingPointFormulaManager(); + BooleanFormulaManager bmgr = ctx.getFormulaManager().getBooleanFormulaManager(); + return bmgr.ifThenElse(bmgr.or(fpfm.isZero(x), fpfm.isNaN(x)), x, + bmgr.ifThenElse(fpfm.isNegative(x), fpfm.negate(one), one)); } /** - * Symbolic wrapper for Math.cos(double a). - * Uses an UF. - * - * @see Math.cos(double a) + * Math.floorDiv and floorMod of two ints or two longs, for a concrete non-zero divisor. With a + * symbolic divisor the ArithmeticException for zero would need a branch of its own, so that + * case stays unmodelled. */ - private static DoubleValue cos(DoubleValue a) { - SolverContext ctx = a.context; - - // Fast path: concrete value only - if (!a.isSymbolic()) { - return new DoubleValue(ctx, Math.cos(a.concrete)); - // potentially we could do an addConstraint to remember this concrete case for the UF or other trigonometric UFs + private static Value invokeFloorDivMod(Value[] args, boolean mod) { + if (args.length != 2) return PlaceHolder.instance; + if (args[0] instanceof LongValue x && args[1] instanceof LongValue y) { + if (y.isSymbolic() || y.concrete == 0) return PlaceHolder.instance; + BitvectorFormula r = floorDivMod(x.context, x.formula, y.formula, 64, mod); + return new LongValue(x.context, mod ? Math.floorMod(x.concrete, y.concrete) : Math.floorDiv(x.concrete, y.concrete), r); + } else if (args[0] instanceof IntValue x && args[1] instanceof IntValue y) { + if (y.isSymbolic() || y.concrete == 0) return PlaceHolder.instance; + BitvectorFormula r = floorDivMod(x.context, x.formula, y.formula, 32, mod); + return new IntValue(x.context, mod ? Math.floorMod(x.concrete, y.concrete) : Math.floorDiv(x.concrete, y.concrete), r); } + return PlaceHolder.instance; + } - try { - SinCosUF uf = ThreadHandler.getUFHandler(Thread.currentThread().getId()).getSinCosUF(); - - // The symbolic result - var ufResult = uf.applyCos(a.formula); - - // Return with concrete result for concolic steering - return new DoubleValue(ctx, Math.cos(a.concrete), ufResult); - - } catch (Exception e) { - // Fallback to concrete-only if UF setup fails - return new DoubleValue(ctx, Math.cos(a.concrete)); - } + /** As the JDK: q = x / y, minus one if the signs differ and the division is not exact; mod = x - q * y. */ + private static BitvectorFormula floorDivMod(SolverContext ctx, BitvectorFormula x, BitvectorFormula y, int width, boolean mod) { + BitvectorFormulaManager bvmgr = ctx.getFormulaManager().getBitvectorFormulaManager(); + BooleanFormulaManager bmgr = ctx.getFormulaManager().getBooleanFormulaManager(); + BitvectorFormula q = bvmgr.divide(x, y, true); + BooleanFormula adjust = bmgr.and( + bvmgr.lessThan(bvmgr.xor(x, y), bvmgr.makeBitvector(width, 0), true), + bmgr.not(bvmgr.equal(bvmgr.multiply(q, y), x))); + BitvectorFormula floorQ = bmgr.ifThenElse(adjust, bvmgr.subtract(q, bvmgr.makeBitvector(width, 1)), q); + return mod ? bvmgr.subtract(x, bvmgr.multiply(floorQ, y)) : floorQ; } private static Value invokeAbs(Value[] args) throws NotImplementedException, ValueConversionException { @@ -157,11 +186,9 @@ private static DoubleValue cos(DoubleValue a) { BooleanFormulaManager bfm = args[0].context.getFormulaManager().getBooleanFormulaManager(); if (args[0] instanceof FloatValue v && args[1] instanceof FloatValue w) { - FloatingPointFormulaManager fpfm = v.context.getFormulaManager().getFloatingPointFormulaManager(); - return new FloatValue(v.context, Math.max(v.concrete, w.concrete), fpfm.max(v.formula, w.formula)); + return new FloatValue(v.context, Math.max(v.concrete, w.concrete), fpMinMax(v.context, v.formula, w.formula, true)); } else if (args[0] instanceof DoubleValue v && args[1] instanceof DoubleValue w) { - FloatingPointFormulaManager fpfm = v.context.getFormulaManager().getFloatingPointFormulaManager(); - return new DoubleValue(v.context, Math.max(v.concrete, w.concrete), fpfm.max(v.formula, w.formula)); + return new DoubleValue(v.context, Math.max(v.concrete, w.concrete), fpMinMax(v.context, v.formula, w.formula, true)); } else if (args[0] instanceof LongValue v && args[1] instanceof LongValue w) { BitvectorFormulaManager bvmgr = v.context.getFormulaManager().getBitvectorFormulaManager(); return new LongValue(v.context, Math.max(v.concrete, w.concrete), bfm.ifThenElse( @@ -186,11 +213,9 @@ private static DoubleValue cos(DoubleValue a) { BooleanFormulaManager bfm = args[0].context.getFormulaManager().getBooleanFormulaManager(); if (args[0] instanceof FloatValue v && args[1] instanceof FloatValue w) { - FloatingPointFormulaManager fpfm = v.context.getFormulaManager().getFloatingPointFormulaManager(); - return new FloatValue(v.context, Math.min(v.concrete, w.concrete), fpfm.min(v.formula, w.formula)); + return new FloatValue(v.context, Math.min(v.concrete, w.concrete), fpMinMax(v.context, v.formula, w.formula, false)); } else if (args[0] instanceof DoubleValue v && args[1] instanceof DoubleValue w) { - FloatingPointFormulaManager fpfm = v.context.getFormulaManager().getFloatingPointFormulaManager(); - return new DoubleValue(v.context, Math.min(v.concrete, w.concrete), fpfm.min(v.formula, w.formula)); + return new DoubleValue(v.context, Math.min(v.concrete, w.concrete), fpMinMax(v.context, v.formula, w.formula, false)); } else if (args[0] instanceof LongValue v && args[1] instanceof LongValue w) { BitvectorFormulaManager bvmgr = v.context.getFormulaManager().getBitvectorFormulaManager(); return new LongValue(v.context, Math.min(v.concrete, w.concrete), bfm.ifThenElse( @@ -208,6 +233,24 @@ private static DoubleValue cos(DoubleValue a) { } } + /** + * Math.max and min of two floats or doubles as the JDK computes them: NaN if either argument is + * NaN, and +0.0 (max) or -0.0 (min) for two zeros of different sign. SMT-LIB's fp.max and fp.min + * differ in both: they return the other argument for one NaN and either zero for two zeros. + */ + private static FloatingPointFormula fpMinMax(SolverContext ctx, FloatingPointFormula a, FloatingPointFormula b, boolean max) { + FloatingPointFormulaManager fpfm = ctx.getFormulaManager().getFloatingPointFormulaManager(); + BooleanFormulaManager bmgr = ctx.getFormulaManager().getBooleanFormulaManager(); + // max: if (a != a) a; else if (a, b zero and a = -0.0) b; else a >= b ? a : b + // min: if (a != a) a; else if (a, b zero and b = -0.0) b; else a <= b ? a : b + BooleanFormula negZero = max + ? bmgr.and(fpfm.isZero(a), fpfm.isNegative(a)) + : bmgr.and(fpfm.isZero(b), fpfm.isNegative(b)); + return bmgr.ifThenElse(fpfm.isNaN(a), a, + bmgr.ifThenElse(bmgr.and(fpfm.isZero(a), fpfm.isZero(b), negZero), b, + bmgr.ifThenElse(max ? fpfm.greaterOrEquals(a, b) : fpfm.lessOrEquals(a, b), a, b))); + } + private static Value invokeSqrt(Value[] args) throws NotImplementedException, ValueConversionException { SWATAssert.enforce(args.length == 1, "Wrong argument counts"); SWATAssert.enforce(args[0] instanceof DoubleValue, "Wrong argument type"); @@ -225,7 +268,7 @@ private static DoubleValue cos(DoubleValue a) { * - NaN returns 0 * - Negative infinity or values <= MIN_VALUE return MIN_VALUE * - Positive infinity or values >= MAX_VALUE return MAX_VALUE - * - Otherwise, round to nearest integer + * - Otherwise, round to the nearest integer, ties toward positive infinity * * Note: SMT's fp.to_sbv has undefined behavior for NaN/infinity, so we must * explicitly encode Java's semantics with if-then-else. @@ -264,7 +307,7 @@ private static DoubleValue cos(DoubleValue a) { fpfm.greaterOrEquals(v.formula, maxThreshold)); // Normal case: round and convert to int - FloatingPointFormula rounded = fpfm.round(v.formula, FloatingPointRoundingMode.NEAREST_TIES_TO_EVEN); + FloatingPointFormula rounded = roundHalfUp(v.context, v.formula, new FloatValue(v.context, 0.5f).formula, new FloatValue(v.context, 1.0f).formula); BitvectorFormula normalResult = fpfm.castTo(rounded, true, FormulaType.getBitvectorTypeWithSize(32), FloatingPointRoundingMode.NEAREST_TIES_TO_EVEN); @@ -309,7 +352,7 @@ private static DoubleValue cos(DoubleValue a) { fpfm.greaterOrEquals(v.formula, maxThreshold)); // Normal case: round and convert to long - FloatingPointFormula rounded = fpfm.round(v.formula, FloatingPointRoundingMode.NEAREST_TIES_TO_EVEN); + FloatingPointFormula rounded = roundHalfUp(v.context, v.formula, new DoubleValue(v.context, 0.5).formula, new DoubleValue(v.context, 1.0).formula); BitvectorFormula normalResult = fpfm.castTo(rounded, true, FormulaType.getBitvectorTypeWithSize(64), FloatingPointRoundingMode.NEAREST_TIES_TO_EVEN); @@ -327,6 +370,19 @@ private static DoubleValue cos(DoubleValue a) { return PlaceHolder.instance; } } + /** + * The integer closest to x, ties toward positive infinity, as Math.round rounds: floor(x), plus + * one if x - floor(x) >= 0.5. The difference is exact wherever it can reach 0.5 (Sterbenz; for x + * in (-0.5, 0) it is above 0.5 and rounding keeps it there), and beyond 2^52 (2^23) x is + * integral, so floor(x) + 1 is only formed where it is exact. + */ + private static FloatingPointFormula roundHalfUp(SolverContext ctx, FloatingPointFormula x, FloatingPointFormula half, FloatingPointFormula one) { + FloatingPointFormulaManager fpfm = ctx.getFormulaManager().getFloatingPointFormulaManager(); + BooleanFormulaManager bmgr = ctx.getFormulaManager().getBooleanFormulaManager(); + FloatingPointFormula floor = fpfm.round(x, FloatingPointRoundingMode.TOWARD_NEGATIVE); + return bmgr.ifThenElse(fpfm.greaterOrEquals(fpfm.subtract(x, floor), half), fpfm.add(floor, one), floor); + } + /* * * private static IntValue invokeMax(IntValue a, IntValue b) { diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/NumberParsing.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/NumberParsing.java new file mode 100644 index 00000000..ff76c08a --- /dev/null +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/NumberParsing.java @@ -0,0 +1,532 @@ +package de.uzl.its.swat.symbolic.invoke.java.lang; + +import de.uzl.its.swat.symbolic.invoke.java.lang.StringCallBranches.Branch; +import de.uzl.its.swat.symbolic.value.Value; +import de.uzl.its.swat.common.exceptions.NotImplementedException; +import de.uzl.its.swat.common.exceptions.ValueConversionException; +import de.uzl.its.swat.symbolic.trace.SymbolicTraceHandler; +import de.uzl.its.swat.symbolic.value.primitive.numeric.floatingpoint.DoubleValue; +import de.uzl.its.swat.symbolic.value.primitive.numeric.floatingpoint.FloatValue; +import de.uzl.its.swat.symbolic.value.primitive.numeric.integral.ByteValue; +import de.uzl.its.swat.symbolic.value.primitive.numeric.integral.IntValue; +import de.uzl.its.swat.symbolic.value.primitive.numeric.integral.LongValue; +import de.uzl.its.swat.symbolic.value.primitive.numeric.integral.ShortValue; +import de.uzl.its.swat.symbolic.value.reference.lang.StringValue; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.IntPredicate; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.sosy_lab.java_smt.api.BitvectorFormulaManager; +import org.sosy_lab.java_smt.api.BooleanFormula; +import org.sosy_lab.java_smt.api.BooleanFormulaManager; +import org.sosy_lab.java_smt.api.FloatingPointFormula; +import org.sosy_lab.java_smt.api.FloatingPointFormulaManager; +import org.sosy_lab.java_smt.api.FormulaManager; +import org.sosy_lab.java_smt.api.FormulaType; +import org.sosy_lab.java_smt.api.IntegerFormulaManager; +import org.sosy_lab.java_smt.api.NumeralFormula.IntegerFormula; +import org.sosy_lab.java_smt.api.RegexFormula; +import org.sosy_lab.java_smt.api.SolverContext; +import org.sosy_lab.java_smt.api.StringFormula; +import org.sosy_lab.java_smt.api.StringFormulaManager; + +/** + * Models of parsing a number from a String: {@code Integer.parseInt}, {@code Long.parseLong}, + * {@code Short.parseShort}, {@code Byte.parseByte} (with or without a radix), {@code + * Float.parseFloat}, {@code Double.parseDouble}, and the {@code valueOf(String)} variants. + * + *

Whether such a call throws a NumberFormatException is a branch on the string (see {@link + * #branches}), which the visitor records on both sides of the call, so the solver can ask for a + * parseable string or for one that is not. + * + *

Integers are exact for strings of any length. A string is parseable iff it is an optional + * sign followed by at least one digit ({@link Character#digit(char, int)}, which accepts every + * Unicode decimal digit and the fullwidth Latin letters), and its value is in range. A value in + * range has at most K significant digits (r^K exceeds the range), so all digits but the last K are + * zeros, and the value is the sum over the last K digits. + * + *

Floats are exact where the decimal-to-binary rounding is a single IEEE operation: no + * exponent, no surrounding whitespace, the digits as an integer M exactly representable (M < + * 2^24 for float, 2^53 for double) and at most 10 (22) digits after the point, so that the value + * is the correctly rounded quotient M / 10^k of two exact operands. NaN and Infinity are exact as + * well. For the other parseable strings (exponents, hexadecimal, many digits, whitespace) the value + * is concrete and the call records precision loss; whether the string is parseable at all is exact + * for every string. + */ +public final class NumberParsing { + + /** An integral type: its range, and the name of its parse method. */ + private enum Integral { + BYTE(Byte.MIN_VALUE, Byte.MAX_VALUE), + SHORT(Short.MIN_VALUE, Short.MAX_VALUE), + INT(Integer.MIN_VALUE, Integer.MAX_VALUE), + LONG(Long.MIN_VALUE, Long.MAX_VALUE); + + final long min; + final long max; + + Integral(long min, long max) { + this.min = min; + this.max = max; + } + + /** The number of significant digits a value in range has at most in this radix. */ + int maxDigits(int radix) { + BigInteger limit = BigInteger.valueOf(min).negate(); + BigInteger p = BigInteger.ONE; + int k = 0; + while (p.compareTo(limit) <= 0) { + p = p.multiply(BigInteger.valueOf(radix)); + k++; + } + return k; + } + + /** Java's result of parsing {@code s}, or null if it throws a NumberFormatException. */ + Long parse(String s, int radix) { + try { + return switch (this) { + case BYTE -> (long) Byte.parseByte(s, radix); + case SHORT -> (long) Short.parseShort(s, radix); + case INT -> (long) Integer.parseInt(s, radix); + case LONG -> Long.parseLong(s, radix); + }; + } catch (NumberFormatException e) { + return null; + } + } + } + + private NumberParsing() {} + + // ---- the calls ---- + + /** The integral type a static call parses to, or null if it is not a modelled parse call. */ + private static Integral integral(String owner, String name, String desc) { + if (!desc.startsWith("(Ljava/lang/String;)") && !desc.startsWith("(Ljava/lang/String;I)")) return null; + return switch (owner + "." + name) { + case "java/lang/Integer.parseInt", "java/lang/Integer.valueOf" -> Integral.INT; + case "java/lang/Long.parseLong", "java/lang/Long.valueOf" -> Integral.LONG; + case "java/lang/Short.parseShort", "java/lang/Short.valueOf" -> Integral.SHORT; + case "java/lang/Byte.parseByte", "java/lang/Byte.valueOf" -> Integral.BYTE; + default -> null; + }; + } + + /** Whether a static call parses a float (false) or a double (true); null if it is not one. */ + private static Boolean floating(String owner, String name, String desc) { + if (!desc.startsWith("(Ljava/lang/String;)")) return null; + return switch (owner + "." + name) { + case "java/lang/Float.parseFloat", "java/lang/Float.valueOf" -> false; + case "java/lang/Double.parseDouble", "java/lang/Double.valueOf" -> true; + default -> null; + }; + } + + /** + * The branches of the static call {@code owner.name desc}, in order: empty if it is not a parse + * call or its string is not symbolic. The last one holds iff the call does not throw a + * NumberFormatException; a pin of a symbolic radix comes first. + */ + public static List branches(String owner, String name, String desc, Value[] args) { + try { + if (args.length == 0 || !(args[0] instanceof StringValue s) || !s.isSymbolic()) return List.of(); + Integral type = integral(owner, name, desc); + if (type != null) { + List branches = new ArrayList<>(); + Integer radix = radix(s.context, args, branches); + if (radix == null) return branches; + branches.add(new Branch(intValid(s.context, s.formula, radix, type), type.parse(s.concrete, radix) != null, true)); + return branches; + } + Boolean dbl = floating(owner, name, desc); + if (dbl != null) { + return List.of(new Branch(floatValid(s.context, s.formula), parseFloating(s.concrete, dbl) != null, true)); + } + } catch (Exception e) { + // Arguments of unexpected types: the model returns a PlaceHolder for them as well. + } + return List.of(); + } + + /** + * The branches a parse call that returned normally takes after those of {@link #branches}: for + * floats, whether the value is modelled exactly. It may hold or not. + */ + public static List returnBranches(String owner, String name, String desc, Value[] args) { + if (args.length == 0 || !(args[0] instanceof StringValue s) || !s.isSymbolic()) return List.of(); + Boolean dbl = floating(owner, name, desc); + if (dbl == null || parseFloating(s.concrete, dbl) == null) return List.of(); + // Both sides return: the model only tells the exact fragment apart + return List.of(new Branch(floatExact(s.context, s.formula, dbl), floatExactConcrete(s.concrete, dbl), false)); + } + + /** + * The radix of a parse call (10 without one), with its branches added: if it is symbolic, the + * range check Java does first, and a pin of its concrete value. Null if the radix is out of + * range, so the call throws whatever the string. + */ + private static Integer radix(SolverContext ctx, Value[] args, List branches) throws Exception { + if (args.length < 2) return 10; + IntValue r = args[1].asIntValue(); + boolean inRange = r.concrete >= Character.MIN_RADIX && r.concrete <= Character.MAX_RADIX; + if (r.isSymbolic()) { + IntegerFormulaManager imgr = ctx.getFormulaManager().getIntegerFormulaManager(); + BooleanFormulaManager bmgr = ctx.getFormulaManager().getBooleanFormulaManager(); + IntegerFormula f = r.asIntegerFormula(); + branches.add(new Branch(bmgr.and(imgr.greaterOrEquals(f, imgr.makeNumber(Character.MIN_RADIX)), + imgr.lessOrEquals(f, imgr.makeNumber(Character.MAX_RADIX))), inRange, true)); + if (inRange) branches.add(new Branch(imgr.equal(f, imgr.makeNumber(r.concrete)), true, true)); + } else if (!inRange) { + // Throws for every string: a branch without variables, which fails. + branches.add(new Branch(ctx.getFormulaManager().getBooleanFormulaManager().makeFalse(), false, true)); + } + return inRange ? r.concrete : null; + } + + /** The radix a parse call that returned used. */ + private static int radix(Value[] args) throws NotImplementedException, ValueConversionException { + return args.length < 2 ? 10 : args[1].asIntValue().concrete; + } + + // ---- the models ---- + + /** Integer.parseInt(String[, int]) that returned normally: exact, given the call's branches held. */ + public static IntValue parseInt(StringValue s, Value[] args) throws NotImplementedException, ValueConversionException { + int radix = radix(args); + int v = Integer.parseInt(s.concrete, radix); + if (!s.isSymbolic()) return new IntValue(s.context, v); + return new IntValue(s.context, v, intValue(s.context, s.formula, radix, Integral.INT)); + } + + /** Long.parseLong(String[, int]) that returned normally. */ + public static LongValue parseLong(StringValue s, Value[] args) throws NotImplementedException, ValueConversionException { + int radix = radix(args); + long v = Long.parseLong(s.concrete, radix); + if (!s.isSymbolic()) return new LongValue(s.context, v); + BitvectorFormulaManager bvmgr = s.context.getFormulaManager().getBitvectorFormulaManager(); + return new LongValue(s.context, v, bvmgr.makeBitvector(64, intValue(s.context, s.formula, radix, Integral.LONG))); + } + + /** Short.parseShort(String[, int]) that returned normally. */ + public static ShortValue parseShort(StringValue s, Value[] args) throws NotImplementedException, ValueConversionException { + int radix = radix(args); + short v = Short.parseShort(s.concrete, radix); + if (!s.isSymbolic()) return new ShortValue(s.context, v); + return new ShortValue(s.context, v, intValue(s.context, s.formula, radix, Integral.SHORT)); + } + + /** Byte.parseByte(String[, int]) that returned normally. */ + public static ByteValue parseByte(StringValue s, Value[] args) throws NotImplementedException, ValueConversionException { + int radix = radix(args); + byte v = Byte.parseByte(s.concrete, radix); + if (!s.isSymbolic()) return new ByteValue(s.context, v); + return new ByteValue(s.context, v, intValue(s.context, s.formula, radix, Integral.BYTE)); + } + + /** + * Float.parseFloat(String) that returned normally: exact if {@link #floatExact} holds, which + * the call's branches record; otherwise concrete, with precision loss. + */ + public static FloatValue parseFloat(StringValue s, SymbolicTraceHandler sth) { + float v = Float.parseFloat(s.concrete); + if (!s.isSymbolic()) return new FloatValue(s.context, v); + if (!floatExactConcrete(s.concrete, false)) { + sth.recordSymbolicPrecisionLoss(); + return new FloatValue(s.context, v); + } + return new FloatValue(s.context, v, floatValue(s.context, s.formula, false)); + } + + /** Double.parseDouble(String) that returned normally, as {@link #parseFloat}. */ + public static DoubleValue parseDouble(StringValue s, SymbolicTraceHandler sth) { + double v = Double.parseDouble(s.concrete); + if (!s.isSymbolic()) return new DoubleValue(s.context, v); + if (!floatExactConcrete(s.concrete, true)) { + sth.recordSymbolicPrecisionLoss(); + return new DoubleValue(s.context, v); + } + return new DoubleValue(s.context, v, floatValue(s.context, s.formula, true)); + } + + // ---- integers ---- + + private static final Map> CHAR_CLASSES = new ConcurrentHashMap<>(); + private static final Map DIGIT_TABLES = new ConcurrentHashMap<>(); + + /** The parts of a string parsed as an integer, as formulas. */ + private record IntParts(IntegerFormula signLength, BooleanFormula negative, IntegerFormula digitCount, IntegerFormula value) {} + + private static IntParts intParts(SolverContext ctx, StringFormula s, int radix, Integral type) { + FormulaManager fmgr = ctx.getFormulaManager(); + StringFormulaManager smgr = fmgr.getStringFormulaManager(); + IntegerFormulaManager imgr = fmgr.getIntegerFormulaManager(); + BooleanFormulaManager bmgr = fmgr.getBooleanFormulaManager(); + IntegerFormula len = smgr.length(s); + BooleanFormula negative = smgr.prefix(smgr.makeString("-"), s); + BooleanFormula signed = bmgr.or(negative, smgr.prefix(smgr.makeString("+"), s)); + IntegerFormula signLength = bmgr.ifThenElse(signed, imgr.makeNumber(1), imgr.makeNumber(0)); + IntegerFormula digitCount = imgr.subtract(len, signLength); + CodePointTables.Table digits = digitTable(radix); + List terms = new ArrayList<>(); + BigInteger weight = BigInteger.ONE; + for (int j = 0; j < type.maxDigits(radix); j++) { + IntegerFormula pos = imgr.subtract(len, imgr.makeNumber(j + 1)); + IntegerFormula digit = digitValue(imgr, bmgr, digits.segments(), 0, digits.segments().length, + smgr.toCodePoint(smgr.charAt(s, pos))); + IntegerFormula term = bmgr.ifThenElse(imgr.greaterThan(digitCount, imgr.makeNumber(j)), digit, imgr.makeNumber(0)); + terms.add(weight.equals(BigInteger.ONE) ? term : imgr.multiply(term, imgr.makeNumber(weight))); + weight = weight.multiply(BigInteger.valueOf(radix)); + } + return new IntParts(signLength, negative, digitCount, imgr.sum(terms)); + } + + /** + * Whether {@code s} parses as an integer of {@code type} in {@code radix} (in [2, 36]): an + * optional sign, then at least one digit, of which all but the last K are zeros, and the value + * in range. + */ + private static BooleanFormula intValid(SolverContext ctx, StringFormula s, int radix, Integral type) { + FormulaManager fmgr = ctx.getFormulaManager(); + StringFormulaManager smgr = fmgr.getStringFormulaManager(); + IntegerFormulaManager imgr = fmgr.getIntegerFormulaManager(); + BooleanFormulaManager bmgr = fmgr.getBooleanFormulaManager(); + IntParts p = intParts(ctx, s, radix, type); + RegexFormula digits = smgr.concat(smgr.closure(digitRegex(smgr, radix, true)), + upTo(smgr, digitRegex(smgr, radix, false), type.maxDigits(radix))); + IntegerFormula bound = bmgr.ifThenElse(p.negative(), + imgr.makeNumber(BigInteger.valueOf(type.min).negate()), imgr.makeNumber(type.max)); + return bmgr.and( + imgr.greaterOrEquals(p.digitCount(), imgr.makeNumber(1)), + smgr.in(smgr.substring(s, p.signLength(), p.digitCount()), digits), + imgr.lessOrEquals(p.value(), bound)); + } + + /** The value of {@code s} parsed as an integer of {@code type}, exact if {@link #intValid} holds. */ + private static IntegerFormula intValue(SolverContext ctx, StringFormula s, int radix, Integral type) { + IntParts p = intParts(ctx, s, radix, type); + IntegerFormulaManager imgr = ctx.getFormulaManager().getIntegerFormulaManager(); + return ctx.getFormulaManager().getBooleanFormulaManager().ifThenElse(p.negative(), imgr.negate(p.value()), p.value()); + } + + /** For the tests: {@link #intValid} for the type named int, long, short or byte. */ + public static BooleanFormula intValid(SolverContext ctx, StringFormula s, int radix, String type) { + return intValid(ctx, s, radix, Integral.valueOf(type.toUpperCase())); + } + + /** For the tests: {@link #intValue} for the type named int, long, short or byte. */ + public static IntegerFormula intValue(SolverContext ctx, StringFormula s, int radix, String type) { + return intValue(ctx, s, radix, Integral.valueOf(type.toUpperCase())); + } + + /** Character.digit(char, radix), tabulated over all chars: constant or offset segments. */ + private static CodePointTables.Table digitTable(int radix) { + return DIGIT_TABLES.computeIfAbsent(radix, r -> CodePointTables.table("digit/" + r + "/C", + c -> Character.digit((char) c, r), Character.MAX_VALUE, 32, true, false)); + } + + /** The digit value of code point x (-1 for a non-digit) as a balanced if-then-else tree. */ + private static IntegerFormula digitValue(IntegerFormulaManager imgr, BooleanFormulaManager bmgr, + CodePointTables.Segment[] segs, int from, int to, IntegerFormula x) { + if (to - from == 1) { + CodePointTables.Segment seg = segs[from]; + IntegerFormula value = imgr.makeNumber((int) seg.value()); + return seg.offset() ? imgr.add(x, value) : value; + } + int mid = (from + to) >>> 1; + return bmgr.ifThenElse(imgr.lessThan(x, imgr.makeNumber(segs[mid].lo())), + digitValue(imgr, bmgr, segs, from, mid, x), + digitValue(imgr, bmgr, segs, mid, to, x)); + } + + /** The chars that are digits in the radix ({@code zero}: whose value is 0), as a regex. */ + private static RegexFormula digitRegex(StringFormulaManager smgr, int radix, boolean zero) { + List ranges = CHAR_CLASSES.computeIfAbsent(radix + "/" + zero, k -> charRanges(zero + ? c -> Character.digit((char) c, radix) == 0 + : c -> Character.digit((char) c, radix) >= 0)); + RegexFormula result = null; + for (int[] r : ranges) { + RegexFormula range = smgr.range((char) r[0], (char) r[1]); + result = result == null ? range : smgr.union(result, range); + } + return result == null ? smgr.none() : result; + } + + /** The maximal ranges of chars satisfying {@code p}. */ + private static List charRanges(IntPredicate p) { + List ranges = new ArrayList<>(); + int c = 0; + while (c <= Character.MAX_VALUE) { + if (!p.test(c)) { + c++; + continue; + } + int lo = c; + while (c + 1 <= Character.MAX_VALUE && p.test(c + 1)) c++; + ranges.add(new int[] {lo, c}); + c++; + } + return ranges; + } + + /** r{0,n} */ + private static RegexFormula upTo(StringFormulaManager smgr, RegexFormula r, int n) { + RegexFormula result = smgr.makeRegex(""); + for (int i = 0; i < n; i++) result = smgr.optional(smgr.concat(r, result)); + return result; + } + + // ---- floats ---- + + /** The decimal forms modelled exactly: sign, integer digits, fraction digits. */ + private static final Pattern EXACT_DECIMAL = Pattern.compile("[+-]?(?:([0-9]+)(?:\\.([0-9]*))?|\\.([0-9]+))[fFdD]?"); + private static final Pattern EXACT_SPECIAL = Pattern.compile("[+-]?(?:NaN|Infinity)"); + + /** Java's result of parsing {@code s} as a float or double, or null if that throws. */ + private static Double parseFloating(String s, boolean dbl) { + try { + return dbl ? Double.parseDouble(s) : (double) Float.parseFloat(s); + } catch (NumberFormatException e) { + return null; + } + } + + /** The largest number of fraction digits whose power of ten the type holds exactly. */ + private static int maxFractionDigits(boolean dbl) { + return dbl ? 22 : 10; + } + + /** 2^p, p the type's precision: the digits as an integer must be below it. */ + private static BigInteger mantissaBound(boolean dbl) { + return BigInteger.ONE.shiftLeft(dbl ? 53 : 24); + } + + /** Whether the value of the parseable string {@code s} is modelled exactly (concretely). */ + public static boolean floatExactConcrete(String s, boolean dbl) { + if (EXACT_SPECIAL.matcher(s).matches()) return true; + Matcher m = EXACT_DECIMAL.matcher(s); + if (!m.matches()) return false; + String integer = m.group(1) == null ? "" : m.group(1); + String fraction = m.group(3) != null ? m.group(3) : m.group(2) == null ? "" : m.group(2); + return fraction.length() <= maxFractionDigits(dbl) + && new BigInteger(integer + fraction).compareTo(mantissaBound(dbl)) < 0; + } + + /** + * Whether {@code s} parses as a float or double, i.e. matches the grammar of {@link + * Double#valueOf(String)}, surrounding chars up to U+0020 included. + */ + public static BooleanFormula floatValid(SolverContext ctx, StringFormula s) { + StringFormulaManager smgr = ctx.getFormulaManager().getStringFormulaManager(); + RegexFormula ws = smgr.closure(smgr.range('\u0000', ' ')); + RegexFormula digits = smgr.cross(smgr.range('0', '9')); + RegexFormula hexDigits = smgr.cross(smgr.union(smgr.range('0', '9'), smgr.union(smgr.range('a', 'f'), smgr.range('A', 'F')))); + RegexFormula sign = smgr.optional(smgr.union(smgr.makeRegex("+"), smgr.makeRegex("-"))); + RegexFormula point = smgr.makeRegex("."); + RegexFormula exp = smgr.concat(smgr.union(smgr.makeRegex("e"), smgr.makeRegex("E")), sign, digits); + RegexFormula hexPrefix = smgr.concat(smgr.makeRegex("0"), smgr.union(smgr.makeRegex("x"), smgr.makeRegex("X"))); + RegexFormula decimal = smgr.union( + smgr.concat(digits, smgr.optional(point), smgr.optional(digits), smgr.optional(exp)), + smgr.concat(point, digits, smgr.optional(exp))); + RegexFormula hex = smgr.concat( + smgr.union( + smgr.concat(hexPrefix, hexDigits, smgr.optional(point)), + smgr.concat(hexPrefix, smgr.optional(hexDigits), point, hexDigits)), + smgr.union(smgr.makeRegex("p"), smgr.makeRegex("P")), sign, digits); + RegexFormula suffix = smgr.optional(smgr.union(smgr.union(smgr.makeRegex("f"), smgr.makeRegex("F")), + smgr.union(smgr.makeRegex("d"), smgr.makeRegex("D")))); + RegexFormula number = smgr.union( + smgr.union(smgr.makeRegex("NaN"), smgr.makeRegex("Infinity")), + smgr.concat(smgr.union(decimal, hex), suffix)); + return smgr.in(s, smgr.concat(ws, sign, number, ws)); + } + + /** The parts of a decimal string without exponent, as formulas. */ + private record DecimalParts(BooleanFormula form, BooleanFormula negative, IntegerFormula mantissa, IntegerFormula fractionDigits) {} + + private static DecimalParts decimalParts(SolverContext ctx, StringFormula s) { + FormulaManager fmgr = ctx.getFormulaManager(); + StringFormulaManager smgr = fmgr.getStringFormulaManager(); + IntegerFormulaManager imgr = fmgr.getIntegerFormulaManager(); + BooleanFormulaManager bmgr = fmgr.getBooleanFormulaManager(); + RegexFormula digits = smgr.cross(smgr.range('0', '9')); + RegexFormula point = smgr.makeRegex("."); + RegexFormula suffixChar = smgr.union(smgr.union(smgr.makeRegex("f"), smgr.makeRegex("F")), + smgr.union(smgr.makeRegex("d"), smgr.makeRegex("D"))); + RegexFormula form = smgr.concat( + smgr.optional(smgr.union(smgr.makeRegex("+"), smgr.makeRegex("-"))), + smgr.union(smgr.concat(digits, smgr.optional(smgr.concat(point, smgr.closure(smgr.range('0', '9'))))), + smgr.concat(point, digits)), + smgr.optional(suffixChar)); + IntegerFormula zero = imgr.makeNumber(0); + IntegerFormula one = imgr.makeNumber(1); + IntegerFormula len = smgr.length(s); + BooleanFormula negative = smgr.prefix(smgr.makeString("-"), s); + IntegerFormula start = bmgr.ifThenElse(bmgr.or(negative, smgr.prefix(smgr.makeString("+"), s)), one, zero); + IntegerFormula end = bmgr.ifThenElse(smgr.in(smgr.charAt(s, imgr.subtract(len, one)), suffixChar), + imgr.subtract(len, one), len); + IntegerFormula dot = smgr.indexOf(s, smgr.makeString("."), zero); + BooleanFormula hasDot = imgr.greaterOrEquals(dot, zero); + IntegerFormula integerEnd = bmgr.ifThenElse(hasDot, dot, end); + IntegerFormula fractionStart = imgr.add(dot, one); + IntegerFormula fractionDigits = bmgr.ifThenElse(hasDot, imgr.subtract(end, fractionStart), zero); + StringFormula integer = smgr.substring(s, start, imgr.subtract(integerEnd, start)); + StringFormula fraction = smgr.substring(s, fractionStart, fractionDigits); + StringFormula all = smgr.concat(integer, bmgr.ifThenElse(hasDot, fraction, smgr.makeString(""))); + return new DecimalParts(smgr.in(s, form), negative, smgr.toIntegerFormula(all), fractionDigits); + } + + private static RegexFormula special(StringFormulaManager smgr, String name) { + return smgr.concat(smgr.optional(smgr.union(smgr.makeRegex("+"), smgr.makeRegex("-"))), smgr.makeRegex(name)); + } + + /** + * Whether the value of a parseable {@code s} is modelled exactly: NaN, Infinity, or a decimal + * without exponent and whitespace whose digits form an integer below 2^p, with at most 10 (22) + * of them after the point. + */ + public static BooleanFormula floatExact(SolverContext ctx, StringFormula s, boolean dbl) { + FormulaManager fmgr = ctx.getFormulaManager(); + StringFormulaManager smgr = fmgr.getStringFormulaManager(); + IntegerFormulaManager imgr = fmgr.getIntegerFormulaManager(); + BooleanFormulaManager bmgr = fmgr.getBooleanFormulaManager(); + DecimalParts p = decimalParts(ctx, s); + return bmgr.or( + smgr.in(s, special(smgr, "NaN")), + smgr.in(s, special(smgr, "Infinity")), + bmgr.and(p.form(), + imgr.lessThan(p.mantissa(), imgr.makeNumber(mantissaBound(dbl))), + imgr.lessOrEquals(p.fractionDigits(), imgr.makeNumber(maxFractionDigits(dbl))))); + } + + /** The value of {@code s} parsed as a float or double: exact if {@link #floatExact} holds. */ + public static FloatingPointFormula floatValue(SolverContext ctx, StringFormula s, boolean dbl) { + FormulaManager fmgr = ctx.getFormulaManager(); + StringFormulaManager smgr = fmgr.getStringFormulaManager(); + IntegerFormulaManager imgr = fmgr.getIntegerFormulaManager(); + BooleanFormulaManager bmgr = fmgr.getBooleanFormulaManager(); + BitvectorFormulaManager bvmgr = fmgr.getBitvectorFormulaManager(); + FloatingPointFormulaManager fpmgr = fmgr.getFloatingPointFormulaManager(); + FormulaType.FloatingPointType type = dbl + ? FormulaType.getDoublePrecisionFloatingPointType() + : FormulaType.getSinglePrecisionFloatingPointType(); + DecimalParts p = decimalParts(ctx, s); + // M below 2^p converts exactly; 10^k for k up to the maximum is exact too, so the division + // rounds once, as Java's conversion of the exact decimal value does. + FloatingPointFormula mantissa = fpmgr.castFrom(bvmgr.makeBitvector(dbl ? 64 : 32, p.mantissa()), true, type); + FloatingPointFormula divisor = fpmgr.makeNumber(1, type); + for (int k = 1; k <= maxFractionDigits(dbl); k++) { + divisor = bmgr.ifThenElse(imgr.equal(p.fractionDigits(), imgr.makeNumber(k)), + fpmgr.makeNumber(BigInteger.TEN.pow(k).toString(), type), divisor); + } + FloatingPointFormula magnitude = fpmgr.divide(mantissa, divisor); + FloatingPointFormula decimal = bmgr.ifThenElse(p.negative(), fpmgr.negate(magnitude), magnitude); + FloatingPointFormula infinity = bmgr.ifThenElse(p.negative(), fpmgr.makeMinusInfinity(type), fpmgr.makePlusInfinity(type)); + return bmgr.ifThenElse(smgr.in(s, special(smgr, "NaN")), fpmgr.makeNaN(type), + bmgr.ifThenElse(smgr.in(s, special(smgr, "Infinity")), infinity, decimal)); + } +} diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/NumberStrings.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/NumberStrings.java new file mode 100644 index 00000000..940e3d18 --- /dev/null +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/NumberStrings.java @@ -0,0 +1,246 @@ +package de.uzl.its.swat.symbolic.invoke.java.lang; + +import de.uzl.its.swat.common.exceptions.NoThreadContextException; +import de.uzl.its.swat.symbolic.value.primitive.numeric.integral.IntValue; +import de.uzl.its.swat.thread.ThreadHandler; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; +import org.sosy_lab.java_smt.api.BitvectorFormula; +import org.sosy_lab.java_smt.api.BitvectorFormulaManager; +import org.sosy_lab.java_smt.api.BooleanFormula; +import org.sosy_lab.java_smt.api.BooleanFormulaManager; +import org.sosy_lab.java_smt.api.FormulaManager; +import org.sosy_lab.java_smt.api.IntegerFormulaManager; +import org.sosy_lab.java_smt.api.NumeralFormula.IntegerFormula; +import org.sosy_lab.java_smt.api.SolverContext; +import org.sosy_lab.java_smt.api.StringFormula; +import org.sosy_lab.java_smt.api.StringFormulaManager; + +/** + * Exact encodings of the integral number-to-string conversions: Integer/Long/Short/Byte toString, + * toUnsignedString and their radix variants, and the string conversion of numbers in string + * concatenation. Every method takes a bit-vector of the given width and returns the string the JDK + * produces for it, digit for digit. + * + *

A digit string that only has decimal digits (radix 10 and below) is the str.from_int of the + * integer whose decimal digits it has. Z3 decides str.from_int directly, whereas a concatenation of + * one string per digit, cut to a symbolic length, makes even an equality with a constant time out + * or yield a model that violates the constraint. Only radixes above 10 need per-digit characters. + */ +public final class NumberStrings { + + private NumberStrings() {} + + /** + * The decimal string of {@code x}, read as a signed or an unsigned number of {@code width} bits. + * str.from_int is undefined ("") for negative numbers, so a negative value is "-" followed by + * the digits of its magnitude 2^width - x. + */ + public static StringFormula decimal(SolverContext ctx, BitvectorFormula x, int width, boolean signed) { + FormulaManager fmgr = ctx.getFormulaManager(); + BitvectorFormulaManager bv = fmgr.getBitvectorFormulaManager(); + StringFormulaManager smgr = fmgr.getStringFormulaManager(); + IntegerFormulaManager imgr = fmgr.getIntegerFormulaManager(); + IntegerFormula unsigned = bv.toIntegerFormula(x, false); + if (!signed) { + return smgr.toStringFormula(unsigned); + } + IntegerFormula magnitude = imgr.subtract(imgr.makeNumber(BigInteger.ONE.shiftLeft(width)), unsigned); + return fmgr.getBooleanFormulaManager().ifThenElse( + bv.lessThan(x, bv.makeBitvector(width, 0), true), + smgr.concat(smgr.makeString("-"), smgr.toStringFormula(magnitude)), + smgr.toStringFormula(unsigned)); + } + + /** + * The string of {@code x} in {@code radix} (2 to 36, see {@link #normaliseRadix}), as + * Integer/Long.toString(x, radix) for a signed and toUnsignedString(x, radix) for an unsigned + * {@code x}: the digits of the magnitude without leading zeros ("0" for zero), digits above 9 as + * the letters a to z, and "-" in front of a negative value. + */ + public static StringFormula toString(SolverContext ctx, BitvectorFormula x, int width, boolean signed, int radix) { + assert radix >= Character.MIN_RADIX && radix <= Character.MAX_RADIX : "[SWAT] Radix out of range: " + radix; + if (radix == 10) { + return decimal(ctx, x, width, signed); + } + if (!signed) { + return unsignedDigits(ctx, x, width, radix); + } + FormulaManager fmgr = ctx.getFormulaManager(); + BitvectorFormulaManager bv = fmgr.getBitvectorFormulaManager(); + BooleanFormulaManager bmgr = fmgr.getBooleanFormulaManager(); + StringFormulaManager smgr = fmgr.getStringFormulaManager(); + BooleanFormula negative = bv.lessThan(x, bv.makeBitvector(width, 0), true); + // as an unsigned number, -MIN_VALUE is the magnitude of MIN_VALUE + BitvectorFormula magnitude = bmgr.ifThenElse(negative, bv.negate(x), x); + StringFormula digits = unsignedDigits(ctx, magnitude, width, radix); + return bmgr.ifThenElse(negative, smgr.concat(smgr.makeString("-"), digits), digits); + } + + /** The radix the JDK uses for {@code radix}: radixes outside 2 to 36 mean 10. */ + public static int normaliseRadix(int radix) { + return radix < Character.MIN_RADIX || radix > Character.MAX_RADIX ? 10 : radix; + } + + /** + * The radix a call with the radix argument {@code radix} converts to, after {@link + * #normaliseRadix}. A symbolic radix is pinned to its concrete value by choice branches: a + * binary search over 2 to 36 on the normalised radix, one branch {@code radix < mid} per step, + * each taken as the concrete radix says. The conversion is then exact on this path with a + * constant radix, and the explorer reaches the other radixes by flipping the branches. (A + * formula that chooses between the radixes itself is exact too, but no solver query on it + * finishes.) + * + * @param radix The radix argument + * @return The normalised concrete radix + */ + public static int selectRadix(IntValue radix) throws NoThreadContextException { + int concrete = normaliseRadix(radix.concrete); + SolverContext ctx = radix.context; + FormulaManager fmgr = ctx.getFormulaManager(); + try { + if (fmgr.extractVariables(fmgr.simplify(radix.formula)).isEmpty()) { + return concrete; + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + BitvectorFormulaManager bv = fmgr.getBitvectorFormulaManager(); + BooleanFormulaManager bmgr = fmgr.getBooleanFormulaManager(); + BitvectorFormula r = radix.formula; + BitvectorFormula normalised = bmgr.ifThenElse( + bmgr.or(bv.lessThan(r, bv.makeBitvector(32, Character.MIN_RADIX), true), + bv.greaterThan(r, bv.makeBitvector(32, Character.MAX_RADIX), true)), + bv.makeBitvector(32, 10), r); + long threadId = Thread.currentThread().getId(); + // the call site of the conversion: its frame is the active one while the model runs + long iid = ThreadHandler.getSymbolicVisitor(threadId).getStack().getActiveFrame().getCallSite(); + int lo = Character.MIN_RADIX; + int hi = Character.MAX_RADIX + 1; + int step = 0; + while (hi - lo > 1) { + int mid = lo + (hi - lo) / 2; + boolean below = concrete < mid; + BooleanFormula constraint = bv.lessThan(normalised, bv.makeBitvector(32, mid), true); + ThreadHandler.getSymbolicTraceHandler(threadId).addChoiceBranch(below, constraint, iid, "#radix" + step++); + if (below) { + hi = mid; + } else { + lo = mid; + } + } + return concrete; + } + + /** + * The digits of the unsigned value of {@code m} in {@code radix} (2 to 36, not 10 for a value + * that str.from_int handles directly), without leading zeros ("0" for zero). + */ + static StringFormula unsignedDigits(SolverContext ctx, BitvectorFormula m, int width, int radix) { + int shift = Integer.numberOfTrailingZeros(radix); + boolean powerOfTwo = Integer.bitCount(radix) == 1; + if (radix == 10) { + return decimal(ctx, m, width, false); + } + if (radix < 10) { + return decimalDigitString(ctx, digits(ctx, m, width, radix)); + } + if (powerOfTwo) { + return BitOps.toUnsignedPowerOfTwoString(ctx, m, width, shift); + } + return letterDigitString(ctx, m, width, radix, digits(ctx, m, width, radix)); + } + + /** The number of digits of the largest unsigned value of {@code width} bits in {@code radix}. */ + private static int digitCount(int width, int radix) { + BigInteger max = BigInteger.ONE.shiftLeft(width).subtract(BigInteger.ONE); + BigInteger r = BigInteger.valueOf(radix); + int n = 1; + for (BigInteger p = r; p.compareTo(max) <= 0; p = p.multiply(r)) { + n++; + } + return n; + } + + /** + * The digits of the unsigned value of {@code m} in {@code radix}, least significant first, each + * a bit-vector of width at most 6 (the number of digits {@link #digitCount}). Power-of-two + * digits are bit fields; the others come from a chain of divisions by the constant radix, which + * repeats no subterm (q_k only appears in q_k+1 and in digit k). + */ + private static List digits(SolverContext ctx, BitvectorFormula m, int width, int radix) { + BitvectorFormulaManager bv = ctx.getFormulaManager().getBitvectorFormulaManager(); + int n = digitCount(width, radix); + List digits = new ArrayList<>(n); + if (Integer.bitCount(radix) == 1) { + int shift = Integer.numberOfTrailingZeros(radix); + for (int k = 0; k < n; k++) { + int lo = k * shift; + digits.add(bv.extract(m, Math.min(lo + shift, width) - 1, lo)); + } + return digits; + } + BitvectorFormula r = bv.makeBitvector(width, radix); + BitvectorFormula q = m; + for (int k = 0; k < n; k++) { + digits.add(bv.extract(bv.remainder(q, r, false), 5, 0)); + if (k < n - 1) { + q = bv.divide(q, r, false); + } + } + return digits; + } + + /** + * The string of decimal digits {@code digits} (least significant first, each below 10) without + * leading zeros: str.from_int of the sum of digit_k * 10^k. + */ + private static StringFormula decimalDigitString(SolverContext ctx, List digits) { + FormulaManager fmgr = ctx.getFormulaManager(); + BitvectorFormulaManager bv = fmgr.getBitvectorFormulaManager(); + IntegerFormulaManager imgr = fmgr.getIntegerFormulaManager(); + List terms = new ArrayList<>(digits.size()); + BigInteger power = BigInteger.ONE; + for (BitvectorFormula digit : digits) { + terms.add(imgr.multiply(bv.toIntegerFormula(digit, false), imgr.makeNumber(power))); + power = power.multiply(BigInteger.TEN); + } + return fmgr.getStringFormulaManager().toStringFormula(imgr.sum(terms)); + } + + /** + * The string of the radix-{@code radix} digits {@code digits} of {@code m} (least significant + * first), digits above 9 as the letters a to z: all digits as characters, from the most + * significant non-zero one on. + */ + private static StringFormula letterDigitString(SolverContext ctx, BitvectorFormula m, int width, int radix, + List digits) { + FormulaManager fmgr = ctx.getFormulaManager(); + BitvectorFormulaManager bv = fmgr.getBitvectorFormulaManager(); + BooleanFormulaManager bmgr = fmgr.getBooleanFormulaManager(); + IntegerFormulaManager imgr = fmgr.getIntegerFormulaManager(); + StringFormulaManager smgr = fmgr.getStringFormulaManager(); + int n = digits.size(); + List chars = new ArrayList<>(n); + // index (from the left) of the first character of the result + IntegerFormula start = imgr.makeNumber(n - 1); + BigInteger power = BigInteger.ONE; + for (int k = 0; k < n; k++) { + // the character code in 8 bits: digit + '0' or digit - 10 + 'a' + BitvectorFormula digit = bv.extend(digits.get(k), 8 - bv.getLength(digits.get(k)), false); + BitvectorFormula code = bv.add(digit, bmgr.ifThenElse( + bv.greaterThan(digit, bv.makeBitvector(8, 9), false), + bv.makeBitvector(8, 'a' - 10), bv.makeBitvector(8, '0'))); + chars.add(0, smgr.fromCodePoint(bv.toIntegerFormula(code, false))); + if (k > 0) { + // m >= radix^k: digit k is not a leading zero + start = bmgr.ifThenElse( + bv.greaterOrEquals(m, bv.makeBitvector(width, power), false), + imgr.makeNumber(n - 1 - k), start); + } + power = power.multiply(BigInteger.valueOf(radix)); + } + return smgr.substring(smgr.concat(chars), start, imgr.subtract(imgr.makeNumber(n), start)); + } +} diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/SampledMath.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/SampledMath.java new file mode 100644 index 00000000..224a7f88 --- /dev/null +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/SampledMath.java @@ -0,0 +1,206 @@ +package de.uzl.its.swat.symbolic.invoke.java.lang; + +import de.uzl.its.swat.common.exceptions.NotImplementedException; +import de.uzl.its.swat.symbolic.trace.SymbolRegistry; +import de.uzl.its.swat.symbolic.trace.SymbolicTraceHandler; +import de.uzl.its.swat.symbolic.value.PlaceHolder; +import de.uzl.its.swat.symbolic.value.Value; +import de.uzl.its.swat.symbolic.value.primitive.numeric.floatingpoint.DoubleValue; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.DoubleBinaryOperator; +import java.util.function.DoubleUnaryOperator; +import org.sosy_lab.java_smt.api.BooleanFormula; +import org.sosy_lab.java_smt.api.BooleanFormulaManager; +import org.sosy_lab.java_smt.api.FloatingPointFormula; +import org.sosy_lab.java_smt.api.FloatingPointFormulaManager; +import org.sosy_lab.java_smt.api.FormulaManager; +import org.sosy_lab.java_smt.api.FormulaType; +import org.sosy_lab.java_smt.api.FunctionDeclaration; +import org.sosy_lab.java_smt.api.SolverContext; + +/** + * Approximate symbolic models of transcendental Math functions (sin, tan, log, pow, ...), which have + * no exact encoding in floating-point SMT. + * + *

Each call is an application of its own uninterpreted function, {@code math__(x)}, + * defined by a trace constraint as a lookup table: exact at a fixed set of sample inputs, and the + * concrete result of this execution everywhere else. Where the result was concrete before, it + * still is, so the model never constrains a path more than concretization did; but the solver can + * now pick a sample input to reach a branch that needs a particular result. Samples are "nice" + * inputs plus inputs at which the function hits "nice" outputs, found through its inverse. + * + *

The function application keeps the approximation visible: a branch constraint that contains + * one is reported as precision loss (see DTOBuilder), so an exhausted search proves nothing, and + * the explorer can tell which queries depend on a guess. A result that never reaches a branch + * leaves the explored paths exact, so it costs no precision. Every call has its own function, so + * definitions never constrain each other: each one can always be satisfied by its own function + * value, whatever the inputs, and leaves the queries that do not mention it unchanged. + */ +public final class SampledMath { + + private static final double[] NICE_INPUTS = { + 0.0, -0.0, 0.5, -0.5, 1, -1, 2, -2, 3, -3, 4, -4, 5, -5, 10, -10, 100, -100, 1000, 0.1, -0.1, + Math.PI, -Math.PI, Math.PI / 2, -Math.PI / 2, Math.PI / 3, Math.PI / 4, -Math.PI / 4, Math.PI / 6, Math.E, + 1.5 * Math.PI, -1.5 * Math.PI, 2 * Math.PI, -2 * Math.PI + }; + + /** + * Inputs at the edges of the double range and of the integral types a double is cast from or to. + * Results that overflow to infinity or turn into NaN (e.g. {@code Math.pow(0.7, (int) (1 / beta))} + * with a tiny negative beta: the int cast gives Integer.MIN_VALUE, and the power +Infinity) lie + * only out here, so without these the model cannot reach a branch that needs them. + */ + private static final double[] EXTREME_INPUTS = { + Integer.MIN_VALUE, Integer.MAX_VALUE, Long.MIN_VALUE, Long.MAX_VALUE, + -Double.MAX_VALUE, Double.MAX_VALUE, Double.MIN_VALUE, -Double.MIN_VALUE, + Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, Double.NaN + }; + + private static final double[] NICE_OUTPUTS = {0.0, 0.5, -0.5, 1, -1, 2, -2, 3, -3, 5, -5, 10, -10, 100}; + + /** Grid for functions of two symbolic arguments. */ + private static final double[] GRID = {-2, -1, -0.5, 0, 0.5, 1, 2, 3, 4, 10}; + + private record Unary(DoubleUnaryOperator f, DoubleUnaryOperator strictF, DoubleUnaryOperator inverse) {} + + private static final Map UNARY = Map.ofEntries( + Map.entry("sin", new Unary(Math::sin, StrictMath::sin, Math::asin)), + Map.entry("cos", new Unary(Math::cos, StrictMath::cos, Math::acos)), + Map.entry("tan", new Unary(Math::tan, StrictMath::tan, Math::atan)), + Map.entry("atan", new Unary(Math::atan, StrictMath::atan, Math::tan)), + Map.entry("asin", new Unary(Math::asin, StrictMath::asin, Math::sin)), + Map.entry("acos", new Unary(Math::acos, StrictMath::acos, Math::cos)), + Map.entry("log", new Unary(Math::log, StrictMath::log, Math::exp)), + Map.entry("log10", new Unary(Math::log10, StrictMath::log10, t -> Math.pow(10, t))), + Map.entry("log1p", new Unary(Math::log1p, StrictMath::log1p, Math::expm1)), + Map.entry("exp", new Unary(Math::exp, StrictMath::exp, Math::log)), + Map.entry("expm1", new Unary(Math::expm1, StrictMath::expm1, Math::log1p)), + Map.entry("cbrt", new Unary(Math::cbrt, StrictMath::cbrt, t -> t * t * t))); + + private static final Map SAMPLES = new ConcurrentHashMap<>(); + + /** Prefix of the functions the models are built from; the explorer recognises them by it. */ + static final String UF_PREFIX = "math_"; + + private static final FormulaType.FloatingPointType DOUBLE = FormulaType.getDoublePrecisionFloatingPointType(); + + /** Numbers the calls of this execution, so that every call has its own function. */ + private static final AtomicInteger CALLS = new AtomicInteger(); + + private SampledMath() {} + + static Value unary(String name, boolean strict, Value[] args, SymbolicTraceHandler sth) throws NotImplementedException { + Unary u = UNARY.get(name); + if (u == null || args.length != 1 || !(args[0] instanceof DoubleValue x)) return PlaceHolder.instance; + DoubleUnaryOperator f = strict ? u.strictF() : u.f(); + double result = f.applyAsDouble(x.concrete); + if (!x.isSymbolic()) return new DoubleValue(x.context, result); + String key = name + (strict ? "_strict" : ""); + double[] samples = SAMPLES.computeIfAbsent(key, k -> samples(f, u.inverse())); + return new DoubleValue(x.context, result, define(x.context, key, sth, table(x.context, x.formula, f, samples, result), x.formula)); + } + + /** + * A function of two doubles. With one argument concrete, it is a function of the other, sampled + * with the help of {@code inverseFirst} (the first argument giving a result, given the second) + * or {@code inverseSecond}; with both symbolic, it is sampled on a grid. + */ + static Value binary(String key, Value[] args, DoubleBinaryOperator f, + DoubleBinaryOperator inverseFirst, DoubleBinaryOperator inverseSecond, + SymbolicTraceHandler sth) throws NotImplementedException { + if (args.length != 2 || !(args[0] instanceof DoubleValue a) || !(args[1] instanceof DoubleValue b)) return PlaceHolder.instance; + SolverContext ctx = a.context; + double result = f.applyAsDouble(a.concrete, b.concrete); + if (!a.isSymbolic() && !b.isSymbolic()) return new DoubleValue(ctx, result); + FloatingPointFormula table; + if (!b.isSymbolic()) { + double second = b.concrete; + DoubleUnaryOperator g = v -> f.applyAsDouble(v, second); + double[] samples = SAMPLES.computeIfAbsent(key + "/1/" + Double.doubleToRawLongBits(second), + k -> samples(g, t -> inverseFirst.applyAsDouble(t, second))); + table = table(ctx, a.formula, g, samples, result); + } else if (!a.isSymbolic()) { + double first = a.concrete; + DoubleUnaryOperator g = v -> f.applyAsDouble(first, v); + double[] samples = SAMPLES.computeIfAbsent(key + "/2/" + Double.doubleToRawLongBits(first), + k -> samples(g, t -> inverseSecond.applyAsDouble(t, first))); + table = table(ctx, b.formula, g, samples, result); + } else { + FloatingPointFormulaManager fpfm = ctx.getFormulaManager().getFloatingPointFormulaManager(); + BooleanFormulaManager bmgr = ctx.getFormulaManager().getBooleanFormulaManager(); + table = constant(ctx, result); + for (double x : GRID) { + for (double y : GRID) { + BooleanFormula at = bmgr.and(fpfm.assignment(a.formula, constant(ctx, x)), fpfm.assignment(b.formula, constant(ctx, y))); + table = bmgr.ifThenElse(at, constant(ctx, f.applyAsDouble(x, y)), table); + } + } + } + return new DoubleValue(ctx, result, define(ctx, key, sth, table, a.formula, b.formula)); + } + + /** + * A fresh function {@code math__} applied to {@code args}, defined as {@code value} by a + * trace constraint, and declared approximate: a branch over it is a precision loss, and the + * explorer treats queries over it as guesses. For any model that is exact only in part (see + * also JavaRemainder). + */ + public static FloatingPointFormula define(SolverContext ctx, String key, SymbolicTraceHandler sth, + FloatingPointFormula value, FloatingPointFormula... args) { + FormulaManager fmgr = ctx.getFormulaManager(); + List> argTypes = new ArrayList<>(); + for (FloatingPointFormula arg : args) argTypes.add(fmgr.getFormulaType(arg)); + String name = UF_PREFIX + key + "_" + CALLS.getAndIncrement(); + FunctionDeclaration uf = fmgr.getUFManager().declareUF(name, fmgr.getFormulaType(value), argTypes); + // The table is exact only at the samples: a branch over the function is a precision loss + // (DTOBuilder). The definition alone constrains nothing else, as the function is fresh. + SymbolRegistry.declare(name, SymbolRegistry.Kind.APPROXIMATE_MODEL_FUNCTION); + FloatingPointFormula application = fmgr.getUFManager().callUF(uf, Arrays.asList(args)); + sth.addConstraint(fmgr.getFloatingPointFormulaManager().assignment(application, value)); + return application; + } + + /** ite(x = s1, f(s1), ite(x = s2, f(s2), ... otherwise)), comparing bit patterns so -0.0 and NaN are their own samples. */ + private static FloatingPointFormula table(SolverContext ctx, FloatingPointFormula x, DoubleUnaryOperator f, double[] samples, double otherwise) { + FloatingPointFormulaManager fpfm = ctx.getFormulaManager().getFloatingPointFormulaManager(); + BooleanFormulaManager bmgr = ctx.getFormulaManager().getBooleanFormulaManager(); + FloatingPointFormula table = constant(ctx, otherwise); + for (double s : samples) { + table = bmgr.ifThenElse(fpfm.assignment(x, constant(ctx, s)), constant(ctx, f.applyAsDouble(s)), table); + } + return table; + } + + private static FloatingPointFormula constant(SolverContext ctx, double v) { + return new DoubleValue(ctx, v).formula; + } + + /** + * The nice and the extreme inputs, and for each nice output t the input inverse(t), or a + * neighbour of it at which f gives exactly t (dropped if not finite). + */ + static double[] samples(DoubleUnaryOperator f, DoubleUnaryOperator inverse) { + Map samples = new LinkedHashMap<>(); + for (double x : NICE_INPUTS) samples.put(Double.doubleToRawLongBits(x), x); + for (double x : EXTREME_INPUTS) samples.put(Double.doubleToRawLongBits(x), x); + for (double t : NICE_OUTPUTS) { + double x = inverse.applyAsDouble(t); + if (!Double.isFinite(x)) continue; + double chosen = x; + for (double c : new double[] {x, Math.nextUp(x), Math.nextDown(x), Math.nextUp(Math.nextUp(x)), Math.nextDown(Math.nextDown(x))}) { + if (f.applyAsDouble(c) == t) { + chosen = c; + break; + } + } + samples.put(Double.doubleToRawLongBits(chosen), chosen); + } + return samples.values().stream().mapToDouble(Double::doubleValue).toArray(); + } +} diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/ShortInvocation.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/ShortInvocation.java index 63c36e91..634d2a7c 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/ShortInvocation.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/ShortInvocation.java @@ -97,19 +97,8 @@ private static IntValue compare(ShortValue x, ShortValue y) { private static IntValue compareUnsigned(ShortValue x, ShortValue y) { SolverContext ctx = x.context; BitvectorFormulaManager bvmgr = ctx.getFormulaManager().getBitvectorFormulaManager(); - BooleanFormulaManager bmgr = ctx.getFormulaManager().getBooleanFormulaManager(); - - BitvectorFormula zero = bvmgr.makeBitvector(32, 0); - BitvectorFormula one = bvmgr.makeBitvector(32, 1); - BitvectorFormula negOne = bvmgr.makeBitvector(32, -1); - - // Use unsigned comparison on the 16-bit values - BooleanFormula isEqual = bvmgr.equal(x.formula, y.formula); - BooleanFormula isLess = bvmgr.lessThan(x.formula, y.formula, false); // false = unsigned - - BitvectorFormula result = bmgr.ifThenElse(isEqual, zero, - bmgr.ifThenElse(isLess, negOne, one)); - + // Short.toUnsignedInt(x) - Short.toUnsignedInt(y), as in the JDK (not -1/0/1) + BitvectorFormula result = bvmgr.subtract(bvmgr.extend(x.formula, 16, false), bvmgr.extend(y.formula, 16, false)); return new IntValue(ctx, Short.compareUnsigned(x.concrete, y.concrete), result); } @@ -171,28 +160,12 @@ private static IntValue hashCode(ShortValue value) { throws IndexOutOfBoundsException, ValueConversionException, NotImplementedException { assert args.length == 1 || args.length == 2 : "[SWAT] Expected 1 or 2 argument(s), got " + args.length; - if (args.length == 1 && args[0] instanceof StringValue sv) { - return parseShort(sv); + if (args[0] instanceof StringValue sv) { + return NumberParsing.parseShort(sv, args); } - // parseShort with radix not yet supported return PlaceHolder.instance; } - /** - * Symbolic wrapper for Short.parseShort(String s) - * Parses the string argument as a signed decimal short. - * - * @param s The String containing the short representation to be parsed - * @return The short value represented by the argument - */ - private static ShortValue parseShort(StringValue s) { - SolverContext ctx = s.context; - StringFormulaManager sfm = ctx.getFormulaManager().getStringFormulaManager(); - BitvectorFormulaManager bvmgr = ctx.getFormulaManager().getBitvectorFormulaManager(); - return new ShortValue(ctx, Short.parseShort(s.concrete), - bvmgr.makeBitvector(16, sfm.toIntegerFormula(s.formula))); - } - /** * Invocation handler for Short.reverseBytes(*), handles argument conversion. ToDo: Implement * symbolic handling @@ -355,12 +328,15 @@ private static LongValue toUnsignedLong(ShortValue x) { * type, likely a missing implementation */ private static Value invokeValueOf(Value[] args) - throws IndexOutOfBoundsException, ValueConversionException { + throws IndexOutOfBoundsException, ValueConversionException, NotImplementedException { assert args.length == 1 || args.length == 2 : "Expected 1 or 2 argument(s), got " + args.length; if (args.length == 1 && args[0] instanceof ShortValue val) { return valueOf(val); } + if (args[0] instanceof StringValue sv) { + return valueOf(NumberParsing.parseShort(sv, args)); + } return PlaceHolder.instance; } diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/StringCallBranches.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/StringCallBranches.java new file mode 100644 index 00000000..02f4298f --- /dev/null +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/StringCallBranches.java @@ -0,0 +1,330 @@ +package de.uzl.its.swat.symbolic.invoke.java.lang; + +import de.uzl.its.swat.symbolic.value.Value; +import de.uzl.its.swat.symbolic.value.primitive.numeric.integral.BooleanValue; +import de.uzl.its.swat.symbolic.value.primitive.numeric.integral.IntValue; +import de.uzl.its.swat.symbolic.value.reference.ObjectValue; +import de.uzl.its.swat.symbolic.value.reference.StringBuilderValue; +import de.uzl.its.swat.symbolic.value.reference.array.CharArrayValue; +import de.uzl.its.swat.symbolic.value.reference.lang.CaseMapping; +import de.uzl.its.swat.symbolic.value.reference.lang.StringValue; +import java.util.ArrayList; +import java.util.List; +import org.sosy_lab.java_smt.api.BooleanFormula; +import org.sosy_lab.java_smt.api.BooleanFormulaManager; +import org.sosy_lab.java_smt.api.FormulaManager; +import org.sosy_lab.java_smt.api.IntegerFormulaManager; +import org.sosy_lab.java_smt.api.NumeralFormula.IntegerFormula; +import org.sosy_lab.java_smt.api.SolverContext; +import org.sosy_lab.java_smt.api.StringFormula; +import org.sosy_lab.java_smt.api.StringFormulaManager; + +/** + * The branches a call of a String or StringBuilder method takes inside the library, which the + * models of these methods (StringValue, StringBuilderValue) rely on. The visitor records them in the + * trace, so the solver can take their other side: + * + *

    + *
  • Index checks: the method returns normally iff its indices are in range, and throws an + * IndexOutOfBoundsException otherwise. One check per exception the method may throw, in the + * order Java checks them, so all inputs that fail the same check throw the same exception. + *
  • Splits: a model with several cases (e.g. strings of equal and of different lengths) + * records which one the call took. + *
  • Pins: a model that works on the concrete value of a symbolic int (e.g. a loop over the + * concrete length of a string) is exact only for that value. The branch {@code x == concrete} + * makes the solver try the other values, in other runs. + *
+ * + * Index checks come first: the pins are only reached when the indices are in range. + */ +public final class StringCallBranches { + + /** + * A branch: its condition and whether it held in this run, and whether it is a check the call + * throws at if it fails (else the model only distinguishes the cases, and both sides return). + */ + public record Branch(BooleanFormula condition, boolean holds, boolean check) {} + + /** An int the checks are about, as a formula over the integers and concretely. */ + private record Int(IntegerFormula f, long c, boolean symbolic) {} + + private final SolverContext context; + private final BooleanFormulaManager bmgr; + private final IntegerFormulaManager imgr; + private final StringFormulaManager smgr; + private final List branches = new ArrayList<>(); + + private StringCallBranches(SolverContext context) { + this.context = context; + FormulaManager fmgr = context.getFormulaManager(); + this.bmgr = fmgr.getBooleanFormulaManager(); + this.imgr = fmgr.getIntegerFormulaManager(); + this.smgr = fmgr.getStringFormulaManager(); + } + + /** + * The branches of calling {@code name} with descriptor {@code desc} on {@code instance}, in + * order: empty if the method has none or is not modelled. The concrete values are those before + * the call. + */ + public static List of(Value instance, String name, String desc, Value[] args) { + try { + if (instance instanceof StringValue str) { + StringCallBranches b = new StringCallBranches(str.context); + b.ofString(str, name, desc, args); + return b.branches; + } else if (instance instanceof StringBuilderValue sb && sb.getContent() != null) { + StringCallBranches b = new StringCallBranches(sb.context); + b.ofStringBuilder(sb.getContent(), name, desc, args); + return b.branches; + } + } catch (Exception e) { + // Arguments of unexpected types: the model returns a PlaceHolder for them as well. + } + return List.of(); + } + + private void ofString(StringValue s, String name, String desc, Value[] args) throws Exception { + switch (name + desc) { + case "charAt(I)C" -> index(s, args[0]); + case "substring(I)Ljava/lang/String;" -> range(s, num(args[0]), length(s)); + case "substring(II)Ljava/lang/String;", "subSequence(II)Ljava/lang/CharSequence;" -> + range(s, num(args[0]), num(args[1])); + case "getChars(II[CI)V" -> getChars(s, args); + case "toCharArray()[C" -> pin(length(s)); + case "([C)V" -> pin(size(args[0])); + case "([CII)V" -> { + // checkBoundsOffCount(offset, count, value.length), then the chars by the concrete + // offset and count + Int offset = num(args[1]); + Int count = num(args[2]); + Int size = size(args[0]); + add(bmgr.and(imgr.greaterOrEquals(offset.f(), zero()), imgr.greaterOrEquals(count.f(), zero()), + imgr.lessOrEquals(offset.f(), imgr.subtract(size.f(), count.f()))), + offset.c() >= 0 && count.c() >= 0 && offset.c() <= size.c() - count.c()); + pin(offset); + pin(count); + pin(size); + } + case "lastIndexOf(I)I", "lastIndexOf(II)I" -> pin(num(args[0])); + case "codePointAt(I)I" -> index(s, args[0]); + case "codePointBefore(I)I" -> codePointBefore(s, args[0]); + case "codePointCount(II)I" -> codePointCount(s, args); + case "repeat(I)Ljava/lang/String;" -> { + Int count = num(args[0]); + add(imgr.greaterOrEquals(count.f(), zero()), count.c() >= 0); + if (count.c() >= 0) pin(count); + } + case "equalsIgnoreCase(Ljava/lang/String;)Z" -> { + if (args[0] instanceof StringValue other && (s.isSymbolic() || other.isSymbolic()) + && !StringValue.equalsIgnoreCaseByRegex(s, other)) { + // Strings of different lengths differ, else the comparison of ASCII strings by + // the pinned length. (The pin comes first: with the length fixed, the solver + // decides the other side of the ASCII split easily.) + Int len = length(s); + Int otherLen = length(other); + split(imgr.equal(len.f(), otherLen.f()), len.c() == otherLen.c()); + if (len.c() == otherLen.c()) { + pin(len); + ascii(s, other); + } + } + } + case "regionMatches(ZILjava/lang/String;II)Z" -> { + // Ignoring case (maybe): the bounds check, then the comparison by the pinned len + BooleanValue ignoreCase = args[0].asBooleanValue(); + if ((ignoreCase.isSymbolic() || ignoreCase.concrete) && args[2] instanceof StringValue other) { + Int toffset = num(args[1]); + Int ooffset = num(args[3]); + Int len = num(args[4]); + boolean inBounds = StringValue.regionInBounds(s.concrete, (int) toffset.c(), other.concrete, (int) ooffset.c(), (int) len.c()); + split(bmgr.and(imgr.greaterOrEquals(ooffset.f(), zero()), imgr.greaterOrEquals(toffset.f(), zero()), + imgr.lessOrEquals(toffset.f(), imgr.subtract(length(s).f(), len.f())), + imgr.lessOrEquals(ooffset.f(), imgr.subtract(length(other).f(), len.f()))), + inBounds); + if (inBounds) { + pin(len); + ascii(s, other); + } + } + } + case "compareToIgnoreCase(Ljava/lang/String;)I" -> { + if (args[0] instanceof StringValue other) { + pin(length(s)); + pin(length(other)); + ascii(s, other); + } + } + default -> { } + } + } + + private void ofStringBuilder(StringValue s, String name, String desc, Value[] args) throws Exception { + String sig = name + desc.substring(0, desc.indexOf(')') + 1); + switch (sig) { + case "charAt(I)", "setCharAt(IC)", "deleteCharAt(I)", "codePointAt(I)" -> index(s, args[0]); + case "codePointBefore(I)" -> codePointBefore(s, args[0]); + case "codePointCount(II)" -> codePointCount(s, args); + case "reverse()" -> pin(length(s)); + case "delete(II)", "replace(IILjava/lang/String;)" -> { + // An end past the length is taken as the length: start in [0, min(end, length)]. + Int start = num(args[0]); + Int end = num(args[1]); + Int len = length(s); + add(bmgr.and(imgr.greaterOrEquals(start.f(), zero()), imgr.lessOrEquals(start.f(), end.f()), + imgr.lessOrEquals(start.f(), len.f())), + 0 <= start.c() && start.c() <= end.c() && start.c() <= len.c()); + } + case "substring(I)" -> range(s, num(args[0]), length(s)); + case "substring(II)", "subSequence(II)" -> range(s, num(args[0]), num(args[1])); + case "setLength(I)" -> { + Int n = num(args[0]); + add(imgr.greaterOrEquals(n.f(), zero()), n.c() >= 0); + pin(n); + } + case "getChars(II[CI)" -> getChars(s, args); + case "appendCodePoint(I)" -> { + Int cp = num(args[0]); + add(bmgr.and(imgr.greaterOrEquals(cp.f(), zero()), imgr.lessOrEquals(cp.f(), imgr.makeNumber(Character.MAX_CODE_POINT))), + Character.isValidCodePoint((int) cp.c())); + } + case "append([C)" -> pin(size(args[0])); + case "append([CII)" -> { + // checkRange(offset, offset + len, str.length), in int arithmetic: with all three + // pinned, the check is concrete. + pin(num(args[1])); + pin(num(args[2])); + pin(size(args[0])); + } + case "append(Ljava/lang/CharSequence;II)" -> { + StringValue seq = sequence(args[0]); + if (seq != null) range(seq, num(args[1]), num(args[2])); + } + default -> { + if (name.equals("insert")) insert(s, desc, args); + } + } + } + + private void insert(StringValue s, String desc, Value[] args) throws Exception { + // insert(int offset, ...): the offset in [0, length] + Int offset = num(args[0]); + Int len = length(s); + add(bmgr.and(imgr.greaterOrEquals(offset.f(), zero()), imgr.lessOrEquals(offset.f(), len.f())), + 0 <= offset.c() && offset.c() <= len.c()); + if (desc.startsWith("(I[CII)")) { + pin(num(args[2])); + pin(num(args[3])); + pin(size(args[1])); + } else if (desc.startsWith("(I[C)")) { + pin(size(args[1])); + } else if (desc.startsWith("(ILjava/lang/CharSequence;II)")) { + StringValue seq = sequence(args[1]); + if (seq != null) range(seq, num(args[2]), num(args[3])); + } + } + + /** getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin) of String and StringBuilder. */ + private void getChars(StringValue s, Value[] args) throws Exception { + Int srcBegin = num(args[0]); + Int srcEnd = num(args[1]); + range(s, srcBegin, srcEnd); + if (!(args[2] instanceof CharArrayValue)) return; + Int dstBegin = num(args[3]); + Int dstLength = size(args[2]); + // dst[dstBegin, dstBegin + srcEnd - srcBegin) in range (srcBegin <= srcEnd holds here) + IntegerFormula end = imgr.add(dstBegin.f(), imgr.subtract(srcEnd.f(), srcBegin.f())); + add(bmgr.and(imgr.greaterOrEquals(dstBegin.f(), zero()), imgr.lessOrEquals(end, dstLength.f())), + dstBegin.c() >= 0 && dstBegin.c() + srcEnd.c() - srcBegin.c() <= dstLength.c()); + // The model copies by the concrete indices. + pin(srcBegin); + pin(srcEnd); + pin(dstBegin); + } + + /** + * Whether the strings are ASCII, the case the case-insensitive models cover (see CaseMapping): + * a split between ASCII strings and the others. + */ + private boolean ascii(StringValue... strings) { + CaseMapping cases = new CaseMapping(context.getFormulaManager()); + BooleanFormula all = bmgr.makeTrue(); + for (StringValue str : strings) all = bmgr.and(all, cases.isAscii(str.formula)); + boolean holds = StringValue.asciiStrings(strings); + split(all, holds); + return holds; + } + + /** codePointBefore(int index): 0 <= index - 1 < length of s */ + private void codePointBefore(StringValue s, Value indexArg) throws Exception { + Int i = num(indexArg); + Int len = length(s); + add(bmgr.and(imgr.greaterOrEquals(i.f(), imgr.makeNumber(1)), imgr.lessOrEquals(i.f(), len.f())), + 1 <= i.c() && i.c() <= len.c()); + } + + /** codePointCount(int begin, int end): 0 <= begin <= end <= length of s, and end - begin pinned */ + private void codePointCount(StringValue s, Value[] args) throws Exception { + Int begin = num(args[0]); + Int end = num(args[1]); + range(s, begin, end); + if (0 <= begin.c() && begin.c() <= end.c() && end.c() <= s.concrete.length()) { + pin(new Int(imgr.subtract(end.f(), begin.f()), end.c() - begin.c(), begin.symbolic() || end.symbolic())); + } + } + + /** 0 <= index < length of s */ + private void index(StringValue s, Value indexArg) throws Exception { + Int i = num(indexArg); + Int len = length(s); + add(bmgr.and(imgr.greaterOrEquals(i.f(), zero()), imgr.lessThan(i.f(), len.f())), + 0 <= i.c() && i.c() < len.c()); + } + + /** 0 <= begin <= end <= length of s */ + private void range(StringValue s, Int b, Int e) { + Int len = length(s); + add(bmgr.and(imgr.greaterOrEquals(b.f(), zero()), imgr.lessOrEquals(b.f(), e.f()), imgr.lessOrEquals(e.f(), len.f())), + 0 <= b.c() && b.c() <= e.c() && e.c() <= len.c()); + } + + private Int num(Value v) throws Exception { + IntValue i = v.asIntValue(); + return new Int(i.asIntegerFormula(), i.concrete, i.isSymbolic()); + } + + private Int length(StringValue s) { + return new Int(smgr.length(s.formula), s.concrete.length(), s.isSymbolic()); + } + + private Int size(Value array) throws Exception { + return num(((CharArrayValue) array).size); + } + + /** The chars of a CharSequence argument; null is taken as "null", as by append and insert. */ + private StringValue sequence(Value v) { + if (v instanceof ObjectValue o && o.isNullValue()) return new StringValue(context, "null", -1); + return StringValue.charsOf(v); + } + + /** x == its concrete value, if x is symbolic */ + private void pin(Int x) { + if (x.symbolic()) { + add(imgr.equal(x.f(), imgr.makeNumber(x.c())), true); + } + } + + private IntegerFormula zero() { + return imgr.makeNumber(0); + } + + /** A check: the call throws if it fails. */ + private void add(BooleanFormula condition, boolean holds) { + branches.add(new Branch(condition, holds, true)); + } + + /** A branch between the cases of a model, which both return. */ + private void split(BooleanFormula condition, boolean holds) { + branches.add(new Branch(condition, holds, false)); + } +} diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/StringInvocation.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/StringInvocation.java index 82be8143..8de9ead1 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/StringInvocation.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/StringInvocation.java @@ -5,14 +5,23 @@ import de.uzl.its.swat.symbolic.value.PlaceHolder; import de.uzl.its.swat.symbolic.value.Value; import de.uzl.its.swat.symbolic.value.primitive.numeric.integral.IntValue; +import de.uzl.its.swat.symbolic.value.reference.ObjectValue; +import de.uzl.its.swat.symbolic.value.reference.StringBuilderValue; import de.uzl.its.swat.symbolic.value.reference.array.CharArrayValue; +import de.uzl.its.swat.symbolic.value.reference.array.ObjectArrayValue; +import de.uzl.its.swat.symbolic.value.reference.lang.BoxedValue; +import de.uzl.its.swat.symbolic.value.reference.lang.StringValue; import org.objectweb.asm.Type; +import org.sosy_lab.java_smt.api.SolverContext; +import org.sosy_lab.java_smt.api.StringFormula; +import org.sosy_lab.java_smt.api.StringFormulaManager; public class StringInvocation { public static Value invokeStaticMethod(String name, Value[] args, Type[] desc) throws NotImplementedException, ValueConversionException { return switch (name) { case "valueOf" -> invokeValueOf(args, desc); + case "join" -> invokeJoin(args, desc); default -> PlaceHolder.instance; }; } @@ -30,6 +39,7 @@ public class StringInvocation { .asCharArrayValue() .asStringValue(); case "Z" -> args[0].asBooleanValue().asStringValue(); + case "Ljava/lang/Object;" -> invokeValueOfObject(args[0]); default -> PlaceHolder.instance; }; } else if (args.length == 3) { @@ -42,6 +52,57 @@ public class StringInvocation { } } + /** + * String.valueOf(Object) is obj.toString() for a non-null obj, which is modelled for strings + * and boxed primitives (a boxed toString that is not modelled stays a PlaceHolder). + */ + private static Value invokeValueOfObject(Value obj) throws NotImplementedException, ValueConversionException { + if (obj instanceof StringValue str) return str; + if (obj instanceof ObjectValue o && o.isNullValue()) return new StringValue(o.context, "null", -1); + if (obj instanceof StringBuilderValue sb) { + StringValue chars = sb.getContent(); + return chars == null ? PlaceHolder.instance : new StringValue(chars.context, chars.concrete, chars.formula, -1); + } + if (obj instanceof BoxedValue boxed) return boxed.invokeMethod("toString", new Type[0], new Value[0]); + return PlaceHolder.instance; + } + + /** + * String.join(CharSequence delimiter, CharSequence... elements) for an array of a concrete + * size: the elements (a null one as "null") with the delimiter between them. join(delimiter, + * Iterable) is not modelled. + */ + private static Value invokeJoin(Value[] args, Type[] desc) throws NotImplementedException { + if (args.length != 2 || !desc[1].getDescriptor().equals("[Ljava/lang/CharSequence;") + || !(args[1] instanceof ObjectArrayValue elements) || elements.getSize().isSymbolic()) { + return PlaceHolder.instance; + } + StringValue delimiter = StringValue.charsOf(args[0]); + if (delimiter == null) return PlaceHolder.instance; + SolverContext ctx = delimiter.context; + StringFormulaManager smgr = ctx.getFormulaManager().getStringFormulaManager(); + StringBuilder concrete = new StringBuilder(); + StringFormula formula = smgr.makeString(""); + for (int i = 0; i < elements.getSize().concrete; i++) { + Value element; + try { + element = elements.getElement(new IntValue(ctx, i)); + } catch (Exception e) { + return PlaceHolder.instance; + } + StringValue chars = element instanceof ObjectValue o && o.isNullValue() + ? new StringValue(ctx, "null", -1) : StringValue.charsOf(element); + if (chars == null) return PlaceHolder.instance; + if (i > 0) { + concrete.append(delimiter.concrete); + formula = smgr.concat(formula, delimiter.formula); + } + concrete.append(chars.concrete); + formula = smgr.concat(formula, chars.formula); + } + return new StringValue(ctx, concrete.toString(), formula, -1); + } + private static Value invokeValueOf(CharArrayValue data, IntValue offset, IntValue count) { return PlaceHolder.instance; } diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/SystemInvocation.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/SystemInvocation.java new file mode 100644 index 00000000..112089f3 --- /dev/null +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/SystemInvocation.java @@ -0,0 +1,87 @@ +package de.uzl.its.swat.symbolic.invoke.java.lang; + +import de.uzl.its.swat.common.exceptions.NotImplementedException; +import de.uzl.its.swat.symbolic.value.PlaceHolder; +import de.uzl.its.swat.symbolic.value.Value; +import de.uzl.its.swat.symbolic.value.VoidValue; +import de.uzl.its.swat.symbolic.value.primitive.numeric.integral.IntValue; +import de.uzl.its.swat.symbolic.value.reference.array.AbstractArrayValue; +import de.uzl.its.swat.symbolic.value.reference.array.ArrayArrayValue; +import de.uzl.its.swat.symbolic.value.reference.array.ObjectArrayValue; +import java.lang.reflect.Array; +import org.objectweb.asm.Type; + +public final class SystemInvocation { + + /** Longer copies are not modelled: each element adds a store to the array formula. */ + static final int MAX_ARRAYCOPY_LENGTH = 4096; + + private SystemInvocation() {} + + public static Value invokeStaticMethod(String name, Value[] args, Type[] desc) + throws NotImplementedException { + if (name.equals("arraycopy") && args.length == 5) { + return invokeArraycopy(args); + } + return PlaceHolder.instance; + } + + /** + * {@code System.arraycopy(src, srcPos, dest, destPos, length)} with concrete positions and + * length: the shadow elements are copied as the JVM copies the elements, as if through a + * temporary array. The model runs after the concrete call returned, so the arguments were + * valid. + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + private static Value invokeArraycopy(Value[] args) throws NotImplementedException { + for (int i : new int[] {1, 3, 4}) { + if (!(args[i] instanceof IntValue) || args[i].isSymbolic()) return PlaceHolder.instance; + } + int srcPos = ((IntValue) args[1]).concrete; + int destPos = ((IntValue) args[3]).concrete; + int length = ((IntValue) args[4]).concrete; + if (length < 0 || length > MAX_ARRAYCOPY_LENGTH) { + return PlaceHolder.instance; + } + Value src = args[0]; + Value dest = args[2]; + + if (src instanceof ObjectArrayValue s && dest instanceof ObjectArrayValue d) { + Value[] from = s.getFields(); + Value[] to = d.getFields(); + if (from == null || to == null || srcPos + length > from.length || destPos + length > to.length) { + return PlaceHolder.instance; + } + System.arraycopy(from, srcPos, to, destPos, length); + return VoidValue.instance; + } + + if (!(src instanceof AbstractArrayValue s) || !(dest instanceof AbstractArrayValue d) + || src.getClass() != dest.getClass() || src instanceof ArrayArrayValue) { + return PlaceHolder.instance; + } + if (s.concrete == null || d.concrete == null + || srcPos + length > Array.getLength(s.concrete) || destPos + length > Array.getLength(d.concrete)) { + return PlaceHolder.instance; + } + // The shadow of an array seen through GETVALUE keeps the real array as its concrete value, + // which the call has already updated; its concrete values must not be written again. + boolean destIsLive = System.identityHashCode(d.concrete) == d.getAddress(); + Object liveValues = null; + if (destIsLive) { + liveValues = Array.newInstance(d.concrete.getClass().getComponentType(), length); + System.arraycopy(d.concrete, destPos, liveValues, 0, length); + } + Value[] elements = new Value[length]; + for (int k = 0; k < length; k++) { + elements[k] = s.getElement(new IntValue(s.getContext(), srcPos + k)); + } + for (int k = 0; k < length; k++) { + d.storeElement(new IntValue(d.getContext(), destPos + k), elements[k]); + } + if (destIsLive) { + System.arraycopy(liveValues, 0, d.concrete, destPos, length); + } + return VoidValue.instance; + } +} diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/util/CollectionInvocation.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/util/CollectionInvocation.java new file mode 100644 index 00000000..9647eb0d --- /dev/null +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/util/CollectionInvocation.java @@ -0,0 +1,416 @@ +package de.uzl.its.swat.symbolic.invoke.java.util; + +import ch.qos.logback.classic.Logger; +import de.uzl.its.swat.common.exceptions.NoThreadContextException; +import de.uzl.its.swat.common.exceptions.NotImplementedException; +import de.uzl.its.swat.common.logging.GlobalLogger; +import de.uzl.its.swat.common.logging.records.InvocationEntry; +import de.uzl.its.swat.symbolic.shadow.ShadowContext; +import de.uzl.its.swat.symbolic.trace.SymbolicTraceHandler; +import de.uzl.its.swat.symbolic.value.ContainerPlaceHolder; +import de.uzl.its.swat.symbolic.value.ContainerPlaceHolder.Result; +import de.uzl.its.swat.symbolic.value.PlaceHolder; +import de.uzl.its.swat.symbolic.value.Value; +import de.uzl.its.swat.symbolic.value.ValueFactory; +import de.uzl.its.swat.symbolic.value.primitive.numeric.integral.IntValue; +import de.uzl.its.swat.symbolic.value.reference.ContainerContents; +import de.uzl.its.swat.symbolic.value.reference.ContainerContents.Kind; +import de.uzl.its.swat.symbolic.value.reference.ObjectValue; +import de.uzl.its.swat.symbolic.value.reference.array.AbstractArrayValue; +import de.uzl.its.swat.symbolic.value.reference.array.ObjectArrayValue; +import de.uzl.its.swat.symbolic.value.reference.lang.BoxedValue; +import de.uzl.its.swat.symbolic.value.reference.lang.StringValue; +import de.uzl.its.swat.thread.ThreadHandler; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Models of JDK containers ({@code java.util} lists, deques and hash maps, their views and + * iterators, and a few {@code Collections}/{@code Arrays} factories) that only move references: + * storing an element, copying a container, returning an element, a view or an iterator. + * + *

The concrete call still runs; the model records the shadow of what goes in (the {@link + * ContainerContents} of the container's shadow) and, for a call that returns a reference, picks + * the shadow of what comes out when the concrete result is known ({@link #resolve}). A call that + * inspects the elements ({@code contains}, {@code indexOf}, {@code remove(Object)}, sorting, + * {@code equals}, {@code hashCode}, {@code toString}, streams, ...) is not modelled: a container + * holding a symbolic value counts as symbolic, so such a call records context loss as before. + * + *

Hashed parts are modelled only while they are concrete: a map key must not be symbolic, and a + * map whose stored keys have become symbolic is not modelled any more. Index arguments must be + * concrete too. Anything else falls back to the generic handling of a missing invocation. + */ +public final class CollectionInvocation { + private static final Logger logger = GlobalLogger.getSymbolicExecutionLogger(); + + /** The JDK classes whose shadow tracks contents, with the calls modelled on them. */ + private static final Map KIND_BY_CLASS = new HashMap<>(); + + static { + for (String c : List.of("ArrayList", "LinkedList", "Vector", "Stack", "ArrayDeque")) { + KIND_BY_CLASS.put("java/util/" + c, Kind.LIST); + } + for (String c : List.of("HashMap", "LinkedHashMap", "Hashtable")) { + KIND_BY_CLASS.put("java/util/" + c, Kind.MAP); + } + } + + private enum Op { + INIT, // a new, empty container + STORE, // stores argument `arg` (lists) or the value `arg` under key `key` (maps) + COPY, // stores what the container in argument `arg` holds + ELEMENT, // returns an element + STORE_ELEMENT, // STORE, then returns an element (set, put, Stack.push) + VIEW, // returns a view or iterator of the container + ARRAY, // returns a new Object[] with the elements + PLAIN // returns a primitive or nothing and only depends on concrete state + } + + /** + * @param op what the call does + * @param arg the argument stored or copied, -1 for none + * @param key the map-key argument, which must be concrete, -1 for none + * @param initFirst whether the call is a constructor that starts with empty contents + */ + private record Model(Op op, int arg, int key, boolean initFirst) {} + + private static final Map LIST = new HashMap<>(); + private static final Map MAP = new HashMap<>(); + private static final Map VIEW = new HashMap<>(); + + private static void put(Map table, Op op, int arg, int key, String... sigs) { + for (String sig : sigs) table.put(sig, new Model(op, arg, key, sig.startsWith(""))); + } + + static { + put(LIST, Op.INIT, -1, -1, "()V", "(I)V"); + put(LIST, Op.COPY, 0, -1, "(Ljava/util/Collection;)V", "addAll(Ljava/util/Collection;)Z"); + put(LIST, Op.COPY, 1, -1, "addAll(ILjava/util/Collection;)Z"); + put(LIST, Op.STORE, 0, -1, + "add(Ljava/lang/Object;)Z", "addLast(Ljava/lang/Object;)V", "addFirst(Ljava/lang/Object;)V", + "offer(Ljava/lang/Object;)Z", "offerLast(Ljava/lang/Object;)Z", "offerFirst(Ljava/lang/Object;)Z", + "push(Ljava/lang/Object;)V", "addElement(Ljava/lang/Object;)V", + "insertElementAt(Ljava/lang/Object;I)V", "setElementAt(Ljava/lang/Object;I)V"); + put(LIST, Op.STORE, 1, -1, "add(ILjava/lang/Object;)V"); + put(LIST, Op.STORE_ELEMENT, 1, -1, "set(ILjava/lang/Object;)Ljava/lang/Object;"); + put(LIST, Op.STORE_ELEMENT, 0, -1, "push(Ljava/lang/Object;)Ljava/lang/Object;"); + put(LIST, Op.ELEMENT, -1, -1, + "get(I)Ljava/lang/Object;", "getFirst()Ljava/lang/Object;", "getLast()Ljava/lang/Object;", + "element()Ljava/lang/Object;", "peek()Ljava/lang/Object;", "peekFirst()Ljava/lang/Object;", + "peekLast()Ljava/lang/Object;", "poll()Ljava/lang/Object;", "pollFirst()Ljava/lang/Object;", + "pollLast()Ljava/lang/Object;", "pop()Ljava/lang/Object;", "remove()Ljava/lang/Object;", + "remove(I)Ljava/lang/Object;", "removeFirst()Ljava/lang/Object;", "removeLast()Ljava/lang/Object;", + "firstElement()Ljava/lang/Object;", "lastElement()Ljava/lang/Object;", + "elementAt(I)Ljava/lang/Object;"); + put(LIST, Op.VIEW, -1, -1, + "iterator()Ljava/util/Iterator;", "listIterator()Ljava/util/ListIterator;", + "listIterator(I)Ljava/util/ListIterator;", "descendingIterator()Ljava/util/Iterator;", + "elements()Ljava/util/Enumeration;", "subList(II)Ljava/util/List;"); + put(LIST, Op.ARRAY, -1, -1, "toArray()[Ljava/lang/Object;"); + put(LIST, Op.PLAIN, -1, -1, + "size()I", "isEmpty()Z", "clear()V", "trimToSize()V", "ensureCapacity(I)V"); + + put(MAP, Op.INIT, -1, -1, "()V", "(I)V", "(IF)V"); + put(MAP, Op.COPY, 0, -1, "(Ljava/util/Map;)V", "putAll(Ljava/util/Map;)V"); + put(MAP, Op.STORE_ELEMENT, 1, 0, + "put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + "putIfAbsent(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); + put(MAP, Op.ELEMENT, -1, 0, + "get(Ljava/lang/Object;)Ljava/lang/Object;", "remove(Ljava/lang/Object;)Ljava/lang/Object;", + "getOrDefault(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); + put(MAP, Op.PLAIN, -1, 0, "containsKey(Ljava/lang/Object;)Z"); + put(MAP, Op.VIEW, -1, -1, + "keySet()Ljava/util/Set;", "values()Ljava/util/Collection;", "entrySet()Ljava/util/Set;", + "keys()Ljava/util/Enumeration;", "elements()Ljava/util/Enumeration;"); + put(MAP, Op.PLAIN, -1, -1, "size()I", "isEmpty()Z", "clear()V"); + + put(VIEW, Op.PLAIN, -1, -1, + "hasNext()Z", "hasMoreElements()Z", "hasPrevious()Z", "nextIndex()I", "previousIndex()I", + "size()I", "isEmpty()Z", "remove()V"); + put(VIEW, Op.ELEMENT, -1, -1, + "next()Ljava/lang/Object;", "nextElement()Ljava/lang/Object;", "previous()Ljava/lang/Object;", + "getKey()Ljava/lang/Object;", "getValue()Ljava/lang/Object;", "get(I)Ljava/lang/Object;"); + put(VIEW, Op.VIEW, -1, -1, "iterator()Ljava/util/Iterator;", "listIterator()Ljava/util/ListIterator;"); + put(VIEW, Op.ARRAY, -1, -1, "toArray()[Ljava/lang/Object;"); + } + + private CollectionInvocation() {} + + /** + * Applies the model of a call that was not instrumented. + * + * @return the return value of the call, or {@code null} if the call is not modelled + */ + public static Value invoke( + String owner, + String name, + String desc, + Value[] args, + boolean isInstance, + ObjectValue instance, + long invokeId) throws NotImplementedException { + if (isInstance) { + if (instance == null || instance.isNullValue()) { + return null; + } + ContainerContents contents = instance.getContents(); + Kind kind = contents != null ? contents.kind : kindOf(instance); + if (kind == null) { + return null; + } + Model model = (switch (kind) { + case LIST -> LIST; + case MAP -> MAP; + case VIEW -> VIEW; + }).get(name + desc); + if (model == null) { + return null; + } + return apply(model, kind, owner, name, desc, args, instance, invokeId); + } + if (owner.equals("java/util/Collections") || owner.equals("java/util/Arrays")) { + return invokeStatic(owner, name, desc, args, invokeId); + } + return null; + } + + private static Kind kindOf(ObjectValue instance) { + if (instance.className == null || instance instanceof AbstractArrayValue) { + return null; + } + return KIND_BY_CLASS.get(instance.className.replace('.', '/')); + } + + private static Value apply( + Model model, + Kind kind, + String owner, + String name, + String desc, + Value[] args, + ObjectValue instance, + long invokeId) throws NotImplementedException { + // Every argument the model does not store must be concrete: indices, sizes, keys + for (int i = 0; i < args.length; i++) { + if (i != model.arg() && args[i].isSymbolic()) { + return null; + } + } + ContainerContents contents = instance.getContents(); + if (model.initFirst() || contents == null) { + contents = new ContainerContents(kind); + } + if (kind == Kind.MAP && contents.keysSymbolic(new HashSet<>())) { + return null; + } + Value stored = model.arg() >= 0 ? args[model.arg()] : null; + if (model.op() == Op.COPY) { + ContainerContents source = sourceOf(stored, kind == Kind.MAP); + if (source == null) { + return null; + } + contents.addSource(source); + } + if (model.op() == Op.STORE || model.op() == Op.STORE_ELEMENT) { + if (model.key() >= 0) { + contents.addKey(args[model.key()]); + } + contents.addValue(stored); + } + instance.setContents(contents); + InvocationEntry call = new InvocationEntry(owner, name, desc, true, invokeId, true); + logger.debug("Modelled container call {}.{}{} on {}", owner, name, desc, instance); + return switch (model.op()) { + case INIT, STORE, COPY, PLAIN -> PlaceHolder.instance; + case ELEMENT, STORE_ELEMENT -> { + if (contents.kind == Kind.VIEW && contents.yieldsEntries && !name.startsWith("get")) { + yield new ContainerPlaceHolder(Result.VIEW, contents, false, List.of(), call); + } + List> candidates = + name.equals("getOrDefault") || name.equals("push") ? List.of(args[args.length - 1]) : List.of(); + yield new ContainerPlaceHolder(Result.ELEMENT, contents, false, candidates, call); + } + case VIEW -> new ContainerPlaceHolder( + Result.VIEW, contents, name.equals("entrySet") || contents.yieldsEntries, List.of(), call); + case ARRAY -> new ContainerPlaceHolder(Result.ARRAY, contents, false, List.of(), call); + }; + } + + private static Value invokeStatic(String owner, String name, String desc, Value[] args, long invokeId) + throws NotImplementedException { + InvocationEntry call = new InvocationEntry(owner, name, desc, false, invokeId, true); + if (owner.equals("java/util/Arrays")) { + if (name.equals("asList") && desc.equals("([Ljava/lang/Object;)Ljava/util/List;") + && args[0] instanceof ObjectArrayValue array) { + ContainerContents view = new ContainerContents(Kind.VIEW); + view.addSource(array); + return new ContainerPlaceHolder(Result.VIEW, view, false, List.of(), call); + } + return null; + } + switch (name + desc) { + case "singleton(Ljava/lang/Object;)Ljava/util/Set;", + "singletonList(Ljava/lang/Object;)Ljava/util/List;" -> { + ContainerContents view = new ContainerContents(Kind.VIEW); + view.addValue(args[0]); + return new ContainerPlaceHolder(Result.VIEW, view, false, List.of(), call); + } + case "enumeration(Ljava/util/Collection;)Ljava/util/Enumeration;", + "unmodifiableCollection(Ljava/util/Collection;)Ljava/util/Collection;", + "unmodifiableList(Ljava/util/List;)Ljava/util/List;", + "unmodifiableSet(Ljava/util/Set;)Ljava/util/Set;", + "unmodifiableMap(Ljava/util/Map;)Ljava/util/Map;" -> { + ContainerContents source = sourceOf(args[0], false); + if (source == null) { + return null; + } + return new ContainerPlaceHolder(Result.VIEW, source, source.yieldsEntries, List.of(), call); + } + default -> { + return null; + } + } + } + + /** + * The contents of a container argument that is copied or viewed: its recorded contents, or + * empty contents for a container that holds nothing symbolic. {@code null} (not modelled) for + * an untracked container that is symbolic, or, when the copy goes into a map, for a source that + * is not a map or whose keys may be symbolic. + */ + private static ContainerContents sourceOf(Value arg, boolean intoMap) throws NotImplementedException { + if (!(arg instanceof ObjectValue ov) || ov.isNullValue()) { + return null; + } + ContainerContents source = ov.getContents(); + if (source == null) { + return ov.isSymbolic() ? null : new ContainerContents(Kind.VIEW); + } + if (intoMap && (source.kind != Kind.MAP || source.keysSymbolic(new HashSet<>()))) { + return null; + } + return source; + } + + /** + * Picks the shadow of the object a modelled call returned, once the concrete object is known. + * + *

    + *
  • An element is the shadow stored under the object's address. If there is none, the + * shadow is recovered as for any unmodelled call; if the container may hold a symbolic + * value, that recovery may have lost it, which is recorded as context loss. + *
  • A view or iterator gets contents backed by the container's. + *
  • An array returned by {@code toArray()} gets the stored shadows as its elements. + *
+ */ + public static Value resolve( + ContainerPlaceHolder ph, + Object concrete, + int address, + ShadowContext stack, + SymbolicTraceHandler symbolicTraceHandler) throws Exception { + if (address == ObjectValue.ADDRESS_NULL || concrete == null) { + return ValueFactory.createNULLValue(); + } + switch (ph.result) { + case ELEMENT -> { + Value match = ph.contents.lookup(address); + if (match == null) { + for (Value c : ph.candidates) { + if (c instanceof ObjectValue ov && ov.getAddress() == address) match = c; + } + } + if (match != null && matches(match, concrete)) { + return match; + } + Value recovered = recover(concrete, address, stack); + if (ph.contents.isSymbolic(new HashSet<>())) { + recordContextLoss(ph.call, symbolicTraceHandler); + } + return recovered; + } + case VIEW -> { + Value recovered = recover(concrete, address, stack); + if (recovered instanceof ObjectValue ov + && !(ov instanceof StringValue) + && !(ov instanceof BoxedValue) + && !(ov instanceof AbstractArrayValue) + && !(ov instanceof ObjectArrayValue)) { + if (ov.getContents() == null) { + ov.setContents(ContainerContents.viewOf(ph.contents, ph.yieldsEntries)); + } else { + ov.getContents().addSource(ph.contents); + } + } else if (ph.contents.isSymbolic(new HashSet<>())) { + recordContextLoss(ph.call, symbolicTraceHandler); + } + return recovered; + } + case ARRAY -> { + if (!(concrete instanceof Object[] elements)) { + return recover(concrete, address, stack); + } + long threadId = Thread.currentThread().getId(); + IntValue size = new IntValue(ThreadHandler.getSolverContext(threadId), elements.length); + ObjectArrayValue array = ValueFactory.createObjectArrayValue("java/lang/Object", size); + array.setAddress(address); + boolean unmatched = false; + for (int i = 0; i < elements.length; i++) { + if (elements[i] == null) { + array.storeElement(new IntValue(size.context, i), ValueFactory.createNULLValue()); + continue; + } + Value match = ph.contents.lookup(System.identityHashCode(elements[i])); + if (match != null && matches(match, elements[i])) { + array.storeElement(new IntValue(size.context, i), match); + } else { + // left unset: read back like any element of an untracked array + unmatched = true; + } + } + if (unmatched && ph.contents.isSymbolic(new HashSet<>())) { + recordContextLoss(ph.call, symbolicTraceHandler); + } + stack.putToHeap(address, array); + return array; + } + } + return recover(concrete, address, stack); + } + + /** Whether a stored shadow can stand for the concrete object (guards against address reuse). */ + private static boolean matches(Value shadow, Object concrete) { + if (shadow instanceof StringValue s) { + return concrete instanceof String && concrete.equals(s.concrete); + } + if (shadow instanceof ObjectValue ov && ov.className != null && !(ov instanceof BoxedValue) + && !(ov instanceof AbstractArrayValue) && !(ov instanceof ObjectArrayValue)) { + return ov.className.replace('.', '/').equals(concrete.getClass().getName().replace('.', '/')); + } + return true; + } + + /** What {@code GETVALUE_Object} does for the result of an unmodelled call. */ + private static Value recover(Object concrete, int address, ShadowContext stack) throws Exception { + Value heap = stack.getFromHeap(address); + if (heap != null) { + return heap; + } + Value created = ValueFactory.createObjectValue(concrete, address); + stack.putToHeap(address, created); + return created; + } + + private static void recordContextLoss(InvocationEntry call, SymbolicTraceHandler symbolicTraceHandler) + throws NoThreadContextException { + logger.warn("Result of {}.{}{} could not be matched to a stored shadow, causes context loss", + call.owner(), call.name(), call.desc()); + symbolicTraceHandler.recordSymbolicContextLoss(); + long threadId = Thread.currentThread().getId(); + ThreadHandler.recordMissingInvocation(threadId, call); + ThreadHandler.recordContextLossInvocation(threadId, call); + } +} diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/util/ObjectsInvocation.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/util/ObjectsInvocation.java index 2f6cf86a..68d77efa 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/util/ObjectsInvocation.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/util/ObjectsInvocation.java @@ -7,15 +7,10 @@ import de.uzl.its.swat.symbolic.trace.SymbolicTraceHandler; import de.uzl.its.swat.symbolic.value.PlaceHolder; import de.uzl.its.swat.symbolic.value.Value; -import de.uzl.its.swat.symbolic.value.primitive.numeric.integral.CharValue; +import de.uzl.its.swat.symbolic.value.primitive.numeric.integral.BooleanValue; import de.uzl.its.swat.symbolic.value.reference.ObjectValue; -import de.uzl.its.swat.symbolic.value.reference.lang.CharacterObjectValue; -import de.uzl.its.swat.symbolic.value.reference.lang.StringValue; -import de.uzl.its.swat.thread.ThreadHandler; import org.objectweb.asm.Type; -import static java.lang.Thread.currentThread; - public class ObjectsInvocation { public static Value invokeStaticMethod( @@ -35,21 +30,9 @@ public class ObjectsInvocation { ObjectValue a = args[0].asObjectValue(); ObjectValue b = args[1].asObjectValue(); - // Check for user-de-interned strings with different addresses. - // This detects when refEquals (which calls Objects.equals) compares strings - // that were explicitly created with new String() in user code. - // In such cases, reference equality semantics may differ from what the user intended. - if (a instanceof StringValue s1 && b instanceof StringValue s2) { - if ((s1.isUserDeInterned() || s2.isUserDeInterned()) - && s1.getAddress() != s2.getAddress()) { - // Record that reference equality semantics may have changed - ThreadHandler.getSymbolicTraceHandler(currentThread().getId()) - .recordReferenceSemanticChange(); - } - } - if(a.getAddress() == ObjectValue.ADDRESS_NULL || b.getAddress() == ObjectValue.ADDRESS_NULL) { - return PlaceHolder.instance; + // A null operand: equal iff both are null, independent of any symbolic content. + return new BooleanValue(a.context, a.getAddress() == b.getAddress()); } return a.invokeMethod("equals", null, new Value[]{b} ); } diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/loopstate/ClassInits.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/loopstate/ClassInits.java new file mode 100644 index 00000000..b58727f4 --- /dev/null +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/loopstate/ClassInits.java @@ -0,0 +1,26 @@ +package de.uzl.its.swat.symbolic.loopstate; + +import java.util.Set; +import java.util.TreeSet; + +/** + * The application classes whose initialization has started. The instrumentation calls + * {@link #started} first thing in every application class's static initializer (and adds one to a + * class that has none), so the set is exact: a class missing from it is uninitialized, its static + * fields still hold their defaults and its initializer is still to run. + */ +public final class ClassInits { + + private static final Set started = new TreeSet<>(); + + private ClassInits() {} + + public static synchronized void started(String internalName) { + started.add(internalName); + } + + /** The classes, in name order. */ + static synchronized String[] snapshot() { + return started.toArray(new String[0]); + } +} diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/loopstate/LoopHeads.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/loopstate/LoopHeads.java new file mode 100644 index 00000000..6085559a --- /dev/null +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/loopstate/LoopHeads.java @@ -0,0 +1,194 @@ +package de.uzl.its.swat.symbolic.loopstate; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.JumpInsnNode; +import org.objectweb.asm.tree.LabelNode; +import org.objectweb.asm.tree.LookupSwitchInsnNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.TableSwitchInsnNode; +import org.objectweb.asm.tree.analysis.Analyzer; +import org.objectweb.asm.tree.analysis.AnalyzerException; +import org.objectweb.asm.tree.analysis.BasicInterpreter; +import org.objectweb.asm.tree.analysis.BasicValue; +import org.objectweb.asm.tree.analysis.Frame; + +/** + * The loop heads of the instrumented methods: the targets of backward jumps at which the operand + * stack is empty. For each the instrumentation records which locals are defined there and of what + * kind, so that the probe it inserts can pass their values (see {@link #emitProbe}). + */ +public final class LoopHeads { + + /** Value kinds of a local: the JVM computational types. */ + public static final char INT = 'I', LONG = 'J', FLOAT = 'F', DOUBLE = 'D', REFERENCE = 'A'; + + /** + * A loop head. + * + * @param name stable name: owner, method name and descriptor, and the head's position among the + * method's heads + * @param owner internal name of the class + * @param method name of the method + * @param slots the local slots defined at the head + * @param kinds the kind of each slot + */ + public record Head(String name, String owner, String method, int[] slots, char[] kinds) {} + + private static final Map heads = new ConcurrentHashMap<>(); + + private LoopHeads() {} + + public static Head get(String name) { + return heads.get(name); + } + + /** + * Finds the loop heads of a method. Returns them keyed by the label the method node replays + * (LabelNode.getLabel()), or an empty map if the method cannot be analysed. + */ + public static Map analyze(String owner, MethodNode mn) { + Map result = new IdentityHashMap<>(); + if (mn.instructions.size() == 0 || mn.name.equals("")) { + return result; + } + InsnList insns = mn.instructions; + List targets = new ArrayList<>(); + Map seen = new IdentityHashMap<>(); + for (AbstractInsnNode n : insns) { + List jumps = new ArrayList<>(); + if (n instanceof JumpInsnNode j) { + if (j.getOpcode() == Opcodes.JSR) { + return result; // subroutines: the analysis below does not apply + } + jumps.add(j.label); + } else if (n instanceof TableSwitchInsnNode t) { + jumps.add(t.dflt); + jumps.addAll(t.labels); + } else if (n instanceof LookupSwitchInsnNode l) { + jumps.add(l.dflt); + jumps.addAll(l.labels); + } + int at = insns.indexOf(n); + for (LabelNode target : jumps) { + if (insns.indexOf(target) < at && seen.put(target, Boolean.TRUE) == null) { + targets.add(target); + } + } + } + if (targets.isEmpty()) { + return result; + } + Frame[] frames; + try { + frames = new Analyzer<>(new BasicInterpreter()).analyze(owner, mn); + } catch (AnalyzerException | RuntimeException e) { + return result; + } + targets.sort((a, b) -> Integer.compare(insns.indexOf(a), insns.indexOf(b))); + int ordinal = 0; + for (LabelNode target : targets) { + Frame f = frames[insns.indexOf(target)]; + int position = ordinal++; + if (f == null || f.getStackSize() != 0) { + continue; // unreachable, or values on the operand stack the probe cannot see + } + List slots = new ArrayList<>(); + StringBuilder kinds = new StringBuilder(); + boolean usable = true; + for (int i = 0; i < f.getLocals(); i++) { + BasicValue v = f.getLocal(i); + if (v == null || v == BasicValue.UNINITIALIZED_VALUE) { + continue; // never read before it is written again + } + if (v == BasicValue.RETURNADDRESS_VALUE) { + usable = false; + break; + } + char kind = switch (v.getType().getSort()) { + case Type.BOOLEAN, Type.CHAR, Type.BYTE, Type.SHORT, Type.INT -> INT; + case Type.LONG -> LONG; + case Type.FLOAT -> FLOAT; + case Type.DOUBLE -> DOUBLE; + case Type.OBJECT, Type.ARRAY -> REFERENCE; + default -> 0; + }; + if (kind == 0) { + usable = false; + break; + } + slots.add(i); + kinds.append(kind); + } + if (!usable) { + continue; + } + String name = owner + "." + mn.name + mn.desc + "#" + position; + Head head = new Head(name, owner, mn.name, + slots.stream().mapToInt(Integer::intValue).toArray(), kinds.toString().toCharArray()); + heads.put(name, head); + result.put(target.getLabel(), head); + } + return result; + } + + /** + * Emits the probe of a loop head: LOOP_HEAD(iid, name, locals), where locals holds the values + * of the head's locals, primitives boxed. + */ + public static void emitProbe(MethodVisitor mv, String dispatcher, long iid, Head head) { + mv.visitLdcInsn(iid); + mv.visitLdcInsn(head.name()); + push(mv, head.slots().length); + mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object"); + for (int k = 0; k < head.slots().length; k++) { + mv.visitInsn(Opcodes.DUP); + push(mv, k); + int slot = head.slots()[k]; + switch (head.kinds()[k]) { + case INT -> box(mv, Opcodes.ILOAD, slot, "java/lang/Integer", "I"); + case LONG -> box(mv, Opcodes.LLOAD, slot, "java/lang/Long", "J"); + case FLOAT -> box(mv, Opcodes.FLOAD, slot, "java/lang/Float", "F"); + case DOUBLE -> box(mv, Opcodes.DLOAD, slot, "java/lang/Double", "D"); + default -> mv.visitVarInsn(Opcodes.ALOAD, slot); + } + mv.visitInsn(Opcodes.AASTORE); + } + mv.visitMethodInsn(Opcodes.INVOKESTATIC, dispatcher, "LOOP_HEAD", + "(JLjava/lang/String;[Ljava/lang/Object;)V", false); + } + + private static void box(MethodVisitor mv, int load, int slot, String owner, String desc) { + mv.visitVarInsn(load, slot); + mv.visitMethodInsn(Opcodes.INVOKESTATIC, owner, "valueOf", "(" + desc + ")L" + owner + ";", false); + } + + private static void push(MethodVisitor mv, int value) { + if (value >= -1 && value <= 5) { + mv.visitInsn(Opcodes.ICONST_0 + value); + } else if (value <= Short.MAX_VALUE) { + mv.visitIntInsn(Opcodes.SIPUSH, value); + } else { + mv.visitLdcInsn(value); + } + } + + /** + * Emits the call that records the start of a class's initialization, see {@link ClassInits}. + */ + public static void emitClassInit(MethodVisitor mv, String owner) { + mv.visitLdcInsn(owner); + mv.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(ClassInits.class), "started", + "(Ljava/lang/String;)V", false); + } +} diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/loopstate/LoopStateMatcher.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/loopstate/LoopStateMatcher.java new file mode 100644 index 00000000..68cb95ec --- /dev/null +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/loopstate/LoopStateMatcher.java @@ -0,0 +1,414 @@ +package de.uzl.its.swat.symbolic.loopstate; + +import de.uzl.its.swat.common.Util; +import de.uzl.its.swat.symbolic.instruction.LOOP_HEAD; +import de.uzl.its.swat.symbolic.shadow.Frame; +import de.uzl.its.swat.symbolic.shadow.ShadowContext; +import de.uzl.its.swat.symbolic.value.Value; +import ch.qos.logback.classic.Logger; +import de.uzl.its.swat.common.logging.GlobalLogger; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.security.DigestOutputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; + +/** + * Hashes the program state at a loop head. + * + *

The state is everything the rest of the execution depends on, apart from the inputs still to + * be read: the loop head, the locals defined there, the static fields of the initialized + * application classes, the set of those classes, and the heap reachable from all of these. The + * hash is only computed when the state is fully known and fully concrete; otherwise there is none: + * + *

    + *
  • the head's method is the only application frame on the stack (the frames below it run no + * application code after it returns) and the target runs no other thread; + *
  • no shadow value of the head's locals, of another shadow frame, of a static field or of the + * shadow heap is symbolic. A concrete state is then the same on every execution that takes + * the same branches up to the head, whatever its inputs; + *
  • the reachable heap consists of application objects, enum constants of application enums, + * arrays and plain {@code Object}s only. Strings, boxed values and library objects are not + * hashed, since part of their identity (interning, caches) or content lies outside the + * reachable heap. + *
+ * + * The heap is encoded as a graph, by the order in which a depth-first walk from the roots meets + * its objects, so two states have the same encoding iff their reachable heaps are isomorphic with + * equal contents. Floating-point values are encoded by their bits. + * + *

Why a branch that follows a repeated state need not be flipped (the explorer side): order the + * executions by the number of branches, then by their branch outcomes (lexicographically), then by + * the number of loop-head states they pass. Take a smallest execution that violates the property + * and suppose it passes, before some branch, a state S that some executed path reached earlier in + * this order. The executed prefix up to S, followed by the rest of the violating execution after + * S, is an execution too (the state is the same, and the inputs read afterwards are fresh), it + * violates the property as well, and it is smaller. So a smallest violating execution never passes + * a state that was reached earlier in this order, and the search still finds it with those + * branches left unflipped. + */ +public final class LoopStateMatcher { + + /** Larger reachable heaps are not hashed. */ + private static final int MAX_OBJECTS = 20_000; + + private static final String SWAT_PACKAGE = "de/uzl/its/swat/"; + + /** + * Time one run may spend on hashing. Leaving out a state is always sound (no branch is left + * out because of it), so once the budget is used up the run reports no more states. + */ + private static final long BUDGET_NANOS = 1_000_000_000L; + + private static long spentNanos = 0; + + /** The states this run reported. */ + private static final java.util.Set seen = new java.util.HashSet<>(); + + /** Whether the run reports no more states: the budget is used up or a state repeated. */ + private static boolean stopped = false; + + private LoopStateMatcher() {} + + /** Thrown when the state cannot be hashed. */ + private static final class Unhashable extends Exception { + Unhashable() { + super(null, null, false, false); + } + } + + private static final Unhashable UNHASHABLE = new Unhashable(); + + private static final Logger logger = GlobalLogger.getSymbolicExecutionLogger(); + + /** Logs why a state is not hashed. */ + private static Unhashable unhashable(String why) { + logger.debug("[LOOP STATE] Not hashed: {}", why); + return UNHASHABLE; + } + + /** + * Returns the hash of the state at a loop head, or null if it has none (see the class comment). + * + * @param inst the probe of the head + * @param shadow the thread's shadow stack and heap + * @param shadowStatics the shadow static fields by class name + * @param threads the number of target threads + */ + public static String stateOf( + LOOP_HEAD inst, ShadowContext shadow, Map[]> shadowStatics, int threads) { + if (stopped) { + return null; + } + LoopHeads.Head head = LoopHeads.get(inst.head); + if (head == null || threads != 1 || inst.locals.length != head.slots().length) { + logger.debug("[LOOP STATE] Not hashed: unknown head or several threads"); + return null; + } + long start = System.nanoTime(); + try { + checkStack(head); + checkShadow(head, shadow, shadowStatics); + String h = hash(head, inst.locals); + logger.debug("[LOOP STATE] {}: {}", inst.head, h); + if (!seen.add(h)) { + // The explorer leaves out everything after a repeat on this path anyway + stopped = true; + } + return h; + } catch (Unhashable e) { + return null; + } finally { + spentNanos += System.nanoTime() - start; + if (spentNanos > BUDGET_NANOS) { + logger.debug("[LOOP STATE] Time budget used up, no more states this run"); + stopped = true; + } + } + } + + /** + * Notes a method call of the run. A call into code that is not tracked, other than a few pure + * methods, may read or change state outside the hashed state (a System property, the interrupt + * flag, the static fields of a class in an excluded package): the run reports no more states. + * Every state a run reports thus has a prefix without such calls, so the hidden state is the + * initial one wherever it is reported. + */ + public static void call(String owner, String name, boolean isStatic) { + if (!stopped && !isPureCall(owner.replace('.', '/'), name, isStatic)) { + logger.debug("[LOOP STATE] Call to {}.{}, no more states this run", owner, name); + stopped = true; + } + } + + /** An invokedynamic call site (lambdas, string concatenation) runs library code. */ + public static void invokeDynamic() { + stopped = true; + } + + /** Notes an access to a static field: one of an untracked class is state outside the hash. */ + public static void staticField(String owner) { + if (!stopped && owner != null) { + String name = owner.replace('.', '/'); + if (!name.startsWith(SWAT_PACKAGE) && !isApplicationClassName(name)) { + logger.debug("[LOOP STATE] Static field of {}, no more states this run", owner); + stopped = true; + } + } + } + + private static boolean isPureCall(String owner, String name, boolean isStatic) { + if (owner.startsWith(SWAT_PACKAGE) || isApplicationClassName(owner)) { + return true; // tracked code (SWAT's Verifier included) + } + switch (owner) { + case "org/sosy_lab/sv_benchmarks/Verifier", "java/lang/Math", "java/lang/StrictMath": + return true; + case "java/lang/Object": + return name.equals(""); + case "java/lang/Class": + // The static initializer of every class with an assert calls it (javac's + // $assertionsDisabled). It reads the JVM's assertion setting, the same in every + // run, and the result lands in a static field of the class, which is hashed. + return name.equals("desiredAssertionStatus"); + case "java/lang/Double", "java/lang/Float", "java/lang/Integer", "java/lang/Long": + // getInteger and getLong read System properties + return isStatic && !name.equals("getInteger") && !name.equals("getLong"); + default: + return false; + } + } + + /** The head's method must be the only application frame on the stack. */ + private static void checkStack(LoopHeads.Head head) throws Unhashable { + List app = StackWalker.getInstance().walk(s -> s + .map(f -> f.getClassName().replace('.', '/')) + .filter(LoopStateMatcher::isApplicationClassName) + .limit(2) + .toList()); + if (app.size() != 1 || !app.get(0).equals(head.owner())) { + throw unhashable("other application frames on the stack"); + } + } + + private static void checkShadow(LoopHeads.Head head, ShadowContext shadow, + Map[]> shadowStatics) throws Unhashable { + Frame active = shadow.getActiveFrame(); + // The entry method runs in the initial frame + boolean entry = active.getClassName().equals(ShadowContext.getINITIAL_FRAME_NAME()) + && shadow.getFrameStack().size() == 1; + boolean same = head.owner().equals(active.getClassName()) && head.method().equals(active.getMethodName()); + if (!(entry || same) || !active.getOperandStack().isEmpty()) { + throw unhashable("shadow frame out of step"); + } + List> locals = active.getLocals(); + for (int slot : head.slots()) { + if (slot < locals.size()) { + requireConcrete(locals.get(slot)); + } + } + for (Frame frame : shadow.getFrameStack()) { + if (frame != active) { + requireConcrete(frame.getLocals()); + requireConcrete(frame.getOperandStack()); + } + } + for (Map.Entry[]> e : shadowStatics.entrySet()) { + if (e.getKey() != null && !e.getKey().startsWith(SWAT_PACKAGE)) { + requireConcrete(Arrays.asList(e.getValue())); + } + } + requireConcrete(shadow.heapValues()); + } + + private static void requireConcrete(Collection> values) throws Unhashable { + for (Value v : values) { + requireConcrete(v); + } + } + + private static void requireConcrete(Value v) throws Unhashable { + if (v == null) { + return; + } + boolean symbolic; + try { + symbolic = v.isSymbolic(); + } catch (Throwable t) { + symbolic = true; // unknown counts as symbolic + } + if (symbolic) { + throw unhashable("symbolic value"); + } + } + + private static boolean isApplicationClassName(String internalName) { + return !internalName.startsWith(SWAT_PACKAGE) && Util.shouldInstrument(internalName); + } + + private static boolean isApplicationClass(Class c) { + return c.getClassLoader() != null && !c.isHidden() && isApplicationClassName(c.getName().replace('.', '/')); + } + + private static String hash(LoopHeads.Head head, Object[] locals) throws Unhashable { + MessageDigest md; + try { + md = MessageDigest.getInstance("SHA-256"); + } catch (NoSuchAlgorithmException e) { + throw unhashable("no SHA-256"); + } + try (DataOutputStream out = new DataOutputStream(new DigestOutputStream(OutputStream.nullOutputStream(), md))) { + Encoder enc = new Encoder(out); + out.writeUTF(head.name()); + for (int k = 0; k < locals.length; k++) { + out.writeInt(head.slots()[k]); + enc.local(head.kinds()[k], locals[k]); + } + String[] classes = ClassInits.snapshot(); + out.writeInt(classes.length); + for (String name : classes) { + if (name.startsWith(SWAT_PACKAGE)) { + continue; + } + out.writeUTF(name); + Class c = load(name); + for (Field f : sortedFields(c, true)) { + out.writeUTF(f.getName()); + enc.field(f, null); + } + } + } catch (IOException | ReflectiveOperationException | RuntimeException | LinkageError e) { + // Includes a class whose initialization failed: reflection on it throws + // NoClassDefFoundError, and its state cannot be read, so it must stay unhashable. + throw unhashable("reflection failed"); + } + byte[] digest = md.digest(); + StringBuilder sb = new StringBuilder(32); + for (int i = 0; i < 16; i++) { + sb.append(String.format("%02x", digest[i])); + } + return sb.toString(); + } + + private static Class load(String internalName) throws ClassNotFoundException { + String name = internalName.replace('/', '.'); + ClassLoader context = Thread.currentThread().getContextClassLoader(); + try { + return Class.forName(name, false, context != null ? context : ClassLoader.getSystemClassLoader()); + } catch (ClassNotFoundException e) { + return Class.forName(name, false, ClassLoader.getSystemClassLoader()); + } + } + + private static Field[] sortedFields(Class c, boolean statics) { + Field[] fields = Arrays.stream(c.getDeclaredFields()) + .filter(f -> Modifier.isStatic(f.getModifiers()) == statics) + .sorted(Comparator.comparing(Field::getName)) + .toArray(Field[]::new); + for (Field f : fields) { + f.setAccessible(true); + } + return fields; + } + + /** Writes values and the heap graph reachable from them. */ + private static final class Encoder { + private final DataOutputStream out; + private final IdentityHashMap ids = new IdentityHashMap<>(); + + Encoder(DataOutputStream out) { + this.out = out; + } + + void local(char kind, Object value) throws IOException, Unhashable, IllegalAccessException { + switch (kind) { + case LoopHeads.INT -> out.writeInt((Integer) value); + case LoopHeads.LONG -> out.writeLong((Long) value); + case LoopHeads.FLOAT -> out.writeInt(Float.floatToRawIntBits((Float) value)); + case LoopHeads.DOUBLE -> out.writeLong(Double.doubleToRawLongBits((Double) value)); + default -> ref(value); + } + } + + void field(Field f, Object owner) throws IOException, Unhashable, IllegalAccessException { + Class t = f.getType(); + if (t == int.class) out.writeInt(f.getInt(owner)); + else if (t == long.class) out.writeLong(f.getLong(owner)); + else if (t == boolean.class) out.writeBoolean(f.getBoolean(owner)); + else if (t == byte.class) out.writeByte(f.getByte(owner)); + else if (t == char.class) out.writeChar(f.getChar(owner)); + else if (t == short.class) out.writeShort(f.getShort(owner)); + else if (t == float.class) out.writeInt(Float.floatToRawIntBits(f.getFloat(owner))); + else if (t == double.class) out.writeLong(Double.doubleToRawLongBits(f.getDouble(owner))); + else ref(f.get(owner)); + } + + void ref(Object o) throws IOException, Unhashable, IllegalAccessException { + if (o == null) { + out.writeByte('N'); + return; + } + Integer id = ids.get(o); + if (id != null) { + out.writeByte('R'); + out.writeInt(id); + return; + } + if (ids.size() >= MAX_OBJECTS) { + throw unhashable("heap too large"); + } + ids.put(o, ids.size()); + Class c = o.getClass(); + if (c.isArray()) { + out.writeByte('Y'); + out.writeUTF(c.getName()); + int n = Array.getLength(o); + out.writeInt(n); + Class e = c.getComponentType(); + if (o instanceof int[] a) for (int v : a) out.writeInt(v); + else if (o instanceof long[] a) for (long v : a) out.writeLong(v); + else if (o instanceof boolean[] a) for (boolean v : a) out.writeBoolean(v); + else if (o instanceof byte[] a) out.write(a); + else if (o instanceof char[] a) for (char v : a) out.writeChar(v); + else if (o instanceof short[] a) for (short v : a) out.writeShort(v); + else if (o instanceof float[] a) for (float v : a) out.writeInt(Float.floatToRawIntBits(v)); + else if (o instanceof double[] a) for (double v : a) out.writeLong(Double.doubleToRawLongBits(v)); + else if (!e.isPrimitive()) for (Object v : (Object[]) o) ref(v); + else throw unhashable("unknown array type"); + return; + } + if (c == Object.class) { + out.writeByte('O'); + return; + } + if (!isApplicationClass(c)) { + throw unhashable("non-application object"); + } + out.writeByte('C'); + out.writeUTF(c.getName()); + for (Class k = c; k != Object.class; k = k.getSuperclass()) { + if (k == Enum.class) { + // name and ordinal are final and fixed by the constant + out.writeInt(((Enum) o).ordinal()); + break; + } + if (k == null || !isApplicationClass(k)) { + throw unhashable("non-application superclass"); + } + for (Field f : sortedFields(k, false)) { + field(f, o); + } + } + } + } +} diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/loopstate/package-info.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/loopstate/package-info.java new file mode 100644 index 00000000..3a30b921 --- /dev/null +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/loopstate/package-info.java @@ -0,0 +1,8 @@ +/** + * Loop-head state matching: at the target of every backward jump the executor hashes the program + * state, provided no part of it is symbolic, and reports the hash with the next branch. The + * explorer does not flip a branch that follows a state it has already reached on a shorter (or + * equally long, but earlier ordered) path: whatever that branch could still lead to is reachable + * from the earlier occurrence as well (see LoopStateMatcher). + */ +package de.uzl.its.swat.symbolic.loopstate; diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/processor/AbstractInstructionProcessor.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/processor/AbstractInstructionProcessor.java index 848d95f0..35a3e1b3 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/processor/AbstractInstructionProcessor.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/processor/AbstractInstructionProcessor.java @@ -766,6 +766,14 @@ public void LOOP_BEGIN(long iid) { processInstruction(new LOOP_BEGIN(iid)); } + public void SUMMARY_JOIN(long iid, int join) { + processInstruction(new SUMMARY_JOIN(iid, join)); + } + + public void LOOP_HEAD(long iid, String head, Object[] locals) { + processInstruction(new LOOP_HEAD(iid, head, locals)); + } + public void LOOP_END(long iid) { processInstruction(new LOOP_END(iid)); } diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/processor/DummyInstructionProcessor.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/processor/DummyInstructionProcessor.java index d99826ab..9043912d 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/processor/DummyInstructionProcessor.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/processor/DummyInstructionProcessor.java @@ -376,6 +376,12 @@ public void MAKE_SYMBOLIC(long iid) {} @Override public void LOOP_BEGIN(long iid) {} + @Override + public void LOOP_HEAD(long iid, String head, Object[] locals) {} + + @Override + public void SUMMARY_JOIN(long iid, int join) {} + @Override public void LOOP_END(long iid) {} diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/processor/InstructionProcessor.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/processor/InstructionProcessor.java index 7963aa73..6cfe4c78 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/processor/InstructionProcessor.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/processor/InstructionProcessor.java @@ -373,6 +373,10 @@ void INVOKEDYNAMIC( void LOOP_BEGIN(long iid); + void LOOP_HEAD(long iid, String head, Object[] locals); + + void SUMMARY_JOIN(long iid, int join); + void LOOP_END(long iid); void SPECIAL(long iid, int i); diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/processor/SymbolicInstructionProcessor.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/processor/SymbolicInstructionProcessor.java index 6fa6fb8f..14da9376 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/processor/SymbolicInstructionProcessor.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/processor/SymbolicInstructionProcessor.java @@ -3,9 +3,11 @@ import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Logger; import de.uzl.its.swat.common.ErrorHandler; +import de.uzl.its.swat.common.StackOverflowGuard; import de.uzl.its.swat.common.Util; import de.uzl.its.swat.common.exceptions.SWATAssert; import de.uzl.its.swat.common.logging.GlobalLogger; +import de.uzl.its.swat.common.logging.records.InvocationEntry; import de.uzl.its.swat.config.Config; import de.uzl.its.swat.coverage.CoverageType; import de.uzl.its.swat.instrument.GlobalStateForInstrumentation; @@ -13,7 +15,9 @@ import de.uzl.its.swat.metadata.ClassDepotRuntime; import de.uzl.its.swat.symbolic.SymbolicInstructionVisitor; import de.uzl.its.swat.symbolic.instruction.*; +import de.uzl.its.swat.symbolic.shadow.Frame; import de.uzl.its.swat.symbolic.shadow.ShadowContext; +import de.uzl.its.swat.symbolic.value.Value; import de.uzl.its.swat.thread.ThreadHandler; import java.util.*; @@ -36,6 +40,18 @@ public SymbolicInstructionProcessor() {} private boolean isInDelegation = false; // Track when delegation from non-instrumented to instrumented code occurs private final Stack openInvokeIds = new Stack<>(); + /** The open invokes that are static initializers (CLINIT). */ + private final Set clinitInvokeIds = new HashSet<>(); + /** + * The open invokes of a constructor called from within a constructor. The instrumentation + * cannot wrap them in a try/catch (the receiver may be the uninitialized this), so an exception + * leaving them emits no INVOKEMETHOD_EXCEPTION. + */ + private final Set unwrappedInitInvokeIds = new HashSet<>(); + /** Set by the probe the instrumentation places before such a constructor call. */ + private boolean nextInvokeIsUnwrappedInit = false; + /** The value of that probe (see InstructionMethodAdapter.visitMethodInsn). */ + private static final int UNWRAPPED_INIT_PROBE = 420; /** * Returns whether we're currently in a delegation context (non-instrumented → instrumented). @@ -170,6 +186,10 @@ private boolean isActualMethodNameExpectedMethodName(String actualMethodName, St */ @Override public void processInstruction(Instruction nextInstruction) { + // After a stack overflow the shadow state is no longer reliable: stop tracking + if (StackOverflowGuard.trackingLost | StackOverflowGuard.targetOverflowed) { + return; + } try { SWATAssert.enforce(!isActive, "Symbolic execution is already active!"); isActive = true; @@ -212,6 +232,8 @@ public void processInstruction(Instruction nextInstruction) { ThreadHandler.setNextInstruction(id, nextInstruction); // Needs to be moved down? visitor.setNextInst(ThreadHandler.getNextInstruction(id)); + closeClinitsLeftByException(visitor.getStack(), currentInstruction, logger); + String expectedCnameFull = Util.formatClassName(visitor.getStack().getActiveFrame().getClassName()); String expectedCname = Util.extractBaseCname(expectedCnameFull); String expectedMethodName = visitor.getStack().getActiveFrame().getMethodName(); @@ -254,6 +276,14 @@ public void processInstruction(Instruction nextInstruction) { boolean isInitialFrame = expectedCname.equals(ShadowContext.getINITIAL_FRAME_NAME()); + if (!isInUnknownSpace && !isInitialFrame && !isLambdaFrame + && !(actualClassNameIsExpectedClassName && actualMethodNameIsExpectedMethodName) + && adoptLambdaFrame(visitor.getStack(), expectedCnameFull, + actualClassNameFull, actualMethodIdentifier, logger)) { + actualClassNameIsExpectedClassName = true; + actualMethodNameIsExpectedMethodName = true; + } + if ((actualClassNameIsExpectedClassName && actualMethodNameIsExpectedMethodName) || isInitialFrame) { if (isInUnknownSpace) { logger.info("Leaving unknown space."); @@ -265,6 +295,14 @@ public void processInstruction(Instruction nextInstruction) { } } + if (isDelegation && !hasAncestor(actualClassNameFull, expectedCname)) { + // Library code called back into application code, which runs in the library + // call's frame: its branches are not in the static pre-analysis graph. (An + // application class implementing the frame's library type was called by the + // application itself through that type, e.g. Runnable.run(): the graph has it.) + visitor.getStack().markActiveFrameCallback(); + } + // Track when we enter delegation context if (isDelegation && !isInDelegation) { logger.info("Entering delegation context (non-instrumented → instrumented)."); @@ -276,7 +314,7 @@ public void processInstruction(Instruction nextInstruction) { boolean isActiveClinit = currentInstruction instanceof CLINIT && !isInUnknownSpace; - boolean closeOpenInvoke = handleInvokeStack(currentInstruction, logger); + boolean closeOpenInvoke = handleInvokeStack(currentInstruction, visitor.getStack(), logger); // ToDo: Is handling for nested CLINIT necessary? if ((!isInUnknownSpace @@ -299,6 +337,13 @@ public void processInstruction(Instruction nextInstruction) { } } else { String unknownSpaceState = !isInUnknownSpace ? "Entering" : "In"; + if (!isInUnknownSpace) { + // Instrumented code is running that no tracked invocation led to (e.g. a + // callback from library code). Its instructions are skipped, so whatever + // it branches on or writes is lost to the symbolic state. + recordUnknownSpaceContextLoss(id, expectedCnameFull, expectedMethodName, + actualMethodIdentifier, logger); + } isInUnknownSpace = true; logger.info(Util.DIVIDER); logger.info("Stack mismatch! {} unknown space.", unknownSpaceState); @@ -320,16 +365,166 @@ public void processInstruction(Instruction nextInstruction) { ThreadHandler.setCurrentInstruction(id, ThreadHandler.getNextInstruction(id)); } + } catch (StackOverflowError e) { + // Only field writes here: the stack is still exhausted. The error is swallowed, so the + // target continues as it would without SWAT; the trace is marked as context loss. + StackOverflowGuard.trackingLost = true; } catch (Throwable t) { - new ErrorHandler().handleException("Error visiting Instruction!", t); + if (StackOverflowGuard.findStackOverflow(t) != null) { + StackOverflowGuard.trackingLost = true; + } else { + new ErrorHandler().handleException("Error visiting Instruction!", t); + } } finally { - long id = Thread.currentThread().getId(); - ThreadHandler.checkThreadContextAbortTimer(id); + isActive = false; + } + if (!(StackOverflowGuard.trackingLost | StackOverflowGuard.targetOverflowed)) { + ThreadHandler.checkThreadContextAbortTimer(Thread.currentThread().getId()); } - isActive = false; } - private boolean handleInvokeStack(Instruction currentInstruction, Logger logger) { + /** + * Lets a lambda body run as the interface method it implements. + * + *

Calling an interface method on a lambda goes through a class the JVM generates at run + * time, which is not instrumented, into the synthetic {@code lambda$...} method that holds the + * body. The frame pushed for the call is named after the interface method, so without this the + * body would not match it and be skipped. For a lambda that captures nothing (a static + * {@code lambda$...} method taking exactly the interface method's arguments) the generated + * class only forwards the arguments, so the frame is replaced by one for the body with the + * same arguments. Capturing lambdas are left alone: their captured values are not on the + * frame. + * + * @return whether the frame was replaced + */ + private boolean adoptLambdaFrame(ShadowContext stack, String expectedClassFull, String actualClassFull, + String actualMethodIdentifier, Logger logger) { + if (Util.shouldInstrument(expectedClassFull) || !Util.shouldInstrument(actualClassFull)) { + return false; + } + String[] parts = actualMethodIdentifier.split(Util.METHOD_DIVIDER); + if (parts.length != 3 || !parts[1].startsWith("lambda$")) { + return false; + } + String name = parts[1]; + String desc = parts[2]; + if (!isStaticMethod(actualClassFull, name, desc)) { + return false; + } + Frame expected = stack.getActiveFrame(); + List> locals = expected.getLocals(); + int argSlots = 0; + for (org.objectweb.asm.Type t : org.objectweb.asm.Type.getArgumentTypes(desc)) { + argSlots += t.getSize(); + } + // The interface call's frame holds the lambda object, then the arguments. + if (locals.size() - 1 != argSlots + || org.objectweb.asm.Type.getReturnType(desc).getSize() != expected.nReturnWords) { + return false; + } + Frame body = new Frame(actualClassFull, name, expected.nReturnWords); + body.setCallSite(expected.getCallSite()); + for (Value local : locals.subList(1, locals.size())) { + body.addLocal(local); + } + stack.popFrame(); + stack.pushFrame(body); + logger.info("Running lambda body {} as {}:{}", actualMethodIdentifier, expectedClassFull, + expected.getMethodName()); + return true; + } + + private static boolean isStaticMethod(String classFull, String name, String desc) { + try { + Class cls = Class.forName(classFull.replace('/', '.'), false, ClassLoader.getSystemClassLoader()); + for (java.lang.reflect.Method m : cls.getDeclaredMethods()) { + if (m.getName().equals(name) && org.objectweb.asm.Type.getMethodDescriptor(m).equals(desc)) { + return java.lang.reflect.Modifier.isStatic(m.getModifiers()); + } + } + } catch (Throwable t) { + // Not resolvable here: do not adopt. + } + return false; + } + + private void recordUnknownSpaceContextLoss(long threadId, String expectedClassFull, String expectedMethodName, + String actualMethodIdentifier, Logger logger) { + try { + logger.warn("Untracked instrumented code ({}) runs inside {}:{}: recording symbolic context loss", + actualMethodIdentifier, expectedClassFull, expectedMethodName); + ThreadHandler.getSymbolicTraceHandler(threadId).recordSymbolicContextLoss(); + ThreadHandler.recordContextLossInvocation(threadId, new InvocationEntry( + expectedClassFull, expectedMethodName, "", true, -1, true)); + } catch (Throwable t) { + new ErrorHandler().handleException("Error recording context loss", t); + } + } + + /** + * Closes static initializers that an exception left, once an instruction of the frame below + * them runs. unwindAbandonedInitializers only catches this when an invoke below ends; an + * initializer triggered by a field access or {@code new} has none, and the next instruction + * already belongs to the handler of the frame below. Without this, that handler would run as + * untracked code inside the initializer's frame. + */ + private void closeClinitsLeftByException(ShadowContext stack, Instruction inst, Logger logger) { + if (isInUnknownSpace || inst.iid == -1) { + return; + } + int cid = (int) GlobalStateForInstrumentation.extractCid(inst.iid); + int mid = (int) GlobalStateForInstrumentation.extractMid(inst.iid); + String actualClass = Util.formatClassName(classDepot.getClassName(cid)); + String actualMethod = Util.extractMethodName(classDepot.getMethodIdentifier(cid, mid)); + Stack frames = stack.getFrameStack(); + while (frames.size() > 1 && stack.getActiveFrame().getMethodName().equals("") + && !openInvokeIds.empty() && clinitInvokeIds.contains(openInvokeIds.peek())) { + Frame below = frames.get(frames.size() - 2); + // main runs in the initial frame, which matches any instruction (see process()) + boolean belowIsInitial = Util.extractBaseCname(Util.formatClassName(below.getClassName())) + .equals(ShadowContext.getINITIAL_FRAME_NAME()); + if (!belowIsInitial && (!Util.formatClassName(below.getClassName()).equals(actualClass) + || !below.getMethodName().equals(actualMethod))) { + return; + } + if (belowIsInitial && actualMethod.equals("")) { + return; // still inside an initializer + } + long clinit = openInvokeIds.pop(); + clinitInvokeIds.remove(clinit); + stack.popFrame(); + logger.info("Closing static initializer (invoke {}) left by an exception", clinit); + } + } + + /** + * A static initializer left by an exception emits no INVOKECLINIT_END, and a constructor + * called from within a constructor left by an exception emits no INVOKEMETHOD_EXCEPTION, so + * their invokes and shadow frames stay open. When the end (or exception) of an invoke below + * them arrives, they are certainly over: close them first, as the JVM did when it unwound them. + */ + private void unwindAbandonedInitializers(long invokeId, ShadowContext stack, Logger logger) { + if (openInvokeIds.empty() || openInvokeIds.peek() == invokeId || !openInvokeIds.contains(invokeId)) { + return; + } + List above = openInvokeIds.subList(openInvokeIds.lastIndexOf(invokeId) + 1, openInvokeIds.size()); + for (long id : above) { + if (!clinitInvokeIds.contains(id) && !unwrappedInitInvokeIds.contains(id)) { + return; // not only initializers: leave it to the consistency check below + } + } + while (openInvokeIds.peek() != invokeId) { + long open = openInvokeIds.pop(); + String method = clinitInvokeIds.remove(open) ? "" : ""; + unwrappedInitInvokeIds.remove(open); + if (stack.getActiveFrame().getMethodName().equals(method)) { + stack.popFrame(); + } + logger.info("Closing initializer {} (invoke {}) left by an exception", method, open); + } + } + + private boolean handleInvokeStack(Instruction currentInstruction, ShadowContext stack, Logger logger) { boolean closeOpenInvoke = false; boolean currentInstructionIsInvokeX = @@ -347,16 +542,30 @@ private boolean handleInvokeStack(Instruction currentInstruction, Logger logger) || currentInstruction instanceof INVOKEMETHOD_EXCEPTION || currentInstruction instanceof INVOKECLINIT_END; + if (currentInstruction instanceof SPECIAL special && special.i == UNWRAPPED_INIT_PROBE) { + nextInvokeIsUnwrappedInit = !isInUnknownSpace; + } + if (!isInUnknownSpace && currentInstructionIsInvokeX) { openInvokeIds.push(((InvokeIdInstruction) currentInstruction).invokeId); + if (currentInstruction instanceof CLINIT) { + clinitInvokeIds.add(((InvokeIdInstruction) currentInstruction).invokeId); + } + if (nextInvokeIsUnwrappedInit && currentInstruction instanceof INVOKESPECIAL) { + unwrappedInitInvokeIds.add(((InvokeIdInstruction) currentInstruction).invokeId); + } + } + if (currentInstructionIsInvokeX) { + nextInvokeIsUnwrappedInit = false; } if (currentInstructionIsMethodEndOrException) { + unwindAbandonedInitializers(((InvokeIdInstruction) currentInstruction).invokeId, stack, logger); if (!openInvokeIds.empty() && openInvokeIds.peek() > 0 && openInvokeIds.peek() == ((InvokeIdInstruction) currentInstruction).invokeId) { - openInvokeIds.pop(); + unwrappedInitInvokeIds.remove(openInvokeIds.pop()); closeOpenInvoke = true; if (isInUnknownSpace) { logger.info("Leaving unknown space."); diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/shadow/Frame.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/shadow/Frame.java index 8cf11a6e..a166e159 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/shadow/Frame.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/shadow/Frame.java @@ -21,21 +21,66 @@ public class Frame { /** Number of words that are returned on invoke method end */ public final int nReturnWords; /** The symbolic version of Javas locals */ - @Getter private final ArrayList> locals = new ArrayList<>(8); + @Getter + private final ArrayList> locals = new ArrayList<>(8); /** The symbolic version of Javas operand stack */ - @Getter private final ArrayList> operandStack = new ArrayList<>(8); + @Getter + private final ArrayList> operandStack = new ArrayList<>(8); /** The return value of the symbolic stack frame */ - @Getter private Value ret; + @Getter + private Value ret; /** The class name of the method that is invoked */ - @Getter private final String className; + @Getter + private final String className; /** The method name of the method that is invoked */ - @Getter private final String methodName; + @Getter + private final String methodName; + /** + * Whether application code runs in this library frame: library code called back into it (a + * delegation, see SymbolicInstructionProcessor). Its branches, and those of whatever it calls, + * are not in the static pre-analysis graph (see ShadowContext.inCallback). + */ + @Getter + private boolean callback = false; + + void markCallback() { + callback = true; + } + + /** The iid of the call instruction that pushed this frame, or -1 (see BranchKeys). */ + @Getter @lombok.Setter + private long callSite = -1; /** * Constructor for Frame * * @param nReturnWords Number of words that are returned on invoke method end */ + /** Loop summaries in progress in this frame (see symbolic/summary/Merger), created on demand. */ + private java.util.Map summaryJoins; + private java.util.Map summaryLoops; + + public java.util.Map summaryJoins() { + if (summaryJoins == null) { + summaryJoins = new java.util.HashMap<>(); + } + return summaryJoins; + } + + public java.util.Map summaryJoinsIfAny() { + return summaryJoins; + } + + /** The counting loop whose back edge this frame took last, until its head is reached. */ + public Object summaryBackEdge; + + public java.util.Map summaryLoops() { + if (summaryLoops == null) { + summaryLoops = new java.util.HashMap<>(); + } + return summaryLoops; + } + public Frame(String className, String methodName, int nReturnWords) { this.nReturnWords = nReturnWords; ret = PlaceHolder.instance; @@ -103,6 +148,11 @@ public void setLocal(int index, Value o) { locals.set(index, o); } + /** Removes all symbolic locals */ + public void clearLocals() { + locals.clear(); + } + /** * Gets a Value from the symbolic locals * @@ -249,14 +299,19 @@ public void printStack() throws NoThreadContextException { // purposes. Logger stateLogger = ThreadHandler.getShadowStateLogger(id); - int cnt = 3; + int cnt = 4; for (int i = operandStack.size() - 1; i >= 0; i--) { stateLogger.info("[{}]: {}", operandStack.size() - i, operandStack.get(i)); - if(--cnt == 0) break; + if (--cnt == 0) + break; } int remaining = operandStack.size() - 4; - if (remaining >= 0) stateLogger.info("... ({} more)", remaining); + if (remaining > 0) + stateLogger.info("... ({} more)", remaining); + + // stateLogger.info("LOCALS: {}", locals); } + /** * Override of the default toString method for printing the Current symbolic stack frame and * locals diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/shadow/JVMHeap.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/shadow/JVMHeap.java index b18efde5..37911184 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/shadow/JVMHeap.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/shadow/JVMHeap.java @@ -20,6 +20,10 @@ public void put(int hashCode, Value value) { objects.put(hashCode, value); } + public java.util.Collection> values() { + return java.util.Collections.unmodifiableCollection(objects.values()); + } + public Value get(int hashCode) { return objects.get(hashCode); } diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/shadow/ShadowContext.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/shadow/ShadowContext.java index 143343ed..4980c982 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/shadow/ShadowContext.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/shadow/ShadowContext.java @@ -49,6 +49,11 @@ public void putToHeap(int hashCode, Value value) { heap.put(hashCode, value); } + /** All values of the shadow heap. */ + public java.util.Collection> heapValues() { + return heap.values(); + } + public Value getFromHeap(int hashCode) { return heap.get(hashCode); } @@ -62,6 +67,85 @@ public void putToHeap(int hashCode, Value value) { public void pushFrame(Frame frame) { activeFrame = frame; frameStack.push(frame); + if (isSwatHelper(frame)) { + helperFrames++; + } else { + pushedSinceBranch.add(frame); + } + } + + /** + * A frame of SWAT's own instrumented helpers (e.g. {@code UtilInstrumented.refEquals}). They + * are not in the static pre-analysis graph: how many branches {@code refEquals} takes depends on + * the runtime types of its arguments. So the stack changes the trace reports leave their frames + * out, and their branches are reported like callbacks ({@link #inSwatHelper}), which the walk + * holds its place for. + */ + private static boolean isSwatHelper(Frame frame) { + return frame.getClassName() != null && frame.getClassName().startsWith("de/uzl/its/swat/"); + } + + /** + * How the frame stack changed since the last branch (see {@link #takeStackDelta}): the number + * of the frames then present that were popped, and the frames pushed since that are still there. + */ + private int poppedSinceBranch = 0; + private final ArrayList pushedSinceBranch = new ArrayList<>(); + + /** How many frames on the stack are library frames that application code was called back in. */ + private int callbackFrames = 0; + + /** How many frames on the stack are SWAT helper frames (see {@link #isSwatHelper}). */ + private int helperFrames = 0; + + /** How deep code SWAT inlined into the target runs (see {@link #enterInlinedSwatCode}). */ + private int inlinedSwatCode = 0; + + /** Whether a SWAT helper runs, e.g. a rewritten reference comparison: its branches are not in the graph. */ + public boolean inSwatHelper() { + return helperFrames > 0 || inlinedSwatCode > 0; + } + + /** + * Code SWAT inlines into the target's own method starts, e.g. the loop lifting the elements of + * a list parameter of an endpoint. It is not in the original bytecode, so not in the graph + * either: its branches are reported like a helper's (Intrinsics.enterInlinedSwatCode). + */ + public void enterInlinedSwatCode() { + inlinedSwatCode++; + } + + /** See {@link #enterInlinedSwatCode}. */ + public void leaveInlinedSwatCode() { + if (inlinedSwatCode > 0) inlinedSwatCode--; + } + + /** + * Marks the active frame, a library frame, as running application code that the library + * called back (a delegation). + */ + public void markActiveFrameCallback() { + if (!activeFrame.isCallback()) { + activeFrame.markCallback(); + callbackFrames++; + } + } + + /** Whether application code runs inside a library call, i.e. a callback is on the stack. */ + public boolean inCallback() { + return callbackFrames > 0; + } + + /** The change of the frame stack since the last call, as frames popped and frames pushed. */ + public record StackDelta(int popped, List pushed) { + } + + /** Returns how the frame stack changed since the last call, and starts counting anew. */ + public StackDelta takeStackDelta() { + StackDelta delta = new StackDelta(poppedSinceBranch, List.copyOf(pushedSinceBranch)); + poppedSinceBranch = 0; + pushedSinceBranch.clear(); + return delta; } /** @@ -72,6 +156,16 @@ public void pushFrame(Frame frame) { public Frame popFrame() { assert !frameStack.isEmpty() : "[SWAT] Unable to remove method frame, as no stack frames are left"; Frame oldFrame = frameStack.pop(); + if (oldFrame.isCallback()) { + callbackFrames--; + } + if (isSwatHelper(oldFrame)) { + helperFrames--; // not counted in the stack changes on push either + } else if (pushedSinceBranch.isEmpty()) { + poppedSinceBranch++; + } else { + pushedSinceBranch.remove(pushedSinceBranch.size() - 1); + } activeFrame = getActiveFrame(); return oldFrame; } @@ -318,9 +412,11 @@ private void setArguments(Type[] types, Value[] arguments) { * @param owner The parent object of the invoked method * @param name The name of the method * @param isInstance If the method is a static method or an instance of an object + * @param callSite The iid of the call instruction */ - public void newStackFrame(String desc, String owner, String name, boolean isInstance) throws NoThreadContextException, NotImplementedException, ValueConversionException { + public void newStackFrame(String desc, String owner, String name, boolean isInstance, long callSite) throws NoThreadContextException, NotImplementedException, ValueConversionException { Frame newFrame = initializeFrame(owner, name, desc); + newFrame.setCallSite(callSite); Type[] types = Type.getArgumentTypes(desc); Value[] arguments = fetchArguments(types); diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/summary/BodyAnalysis.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/summary/BodyAnalysis.java new file mode 100644 index 00000000..0c0957d0 --- /dev/null +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/summary/BodyAnalysis.java @@ -0,0 +1,215 @@ +package de.uzl.its.swat.symbolic.summary; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.FrameNode; +import org.objectweb.asm.tree.IincInsnNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.IntInsnNode; +import org.objectweb.asm.tree.JumpInsnNode; +import org.objectweb.asm.tree.LabelNode; +import org.objectweb.asm.tree.LdcInsnNode; +import org.objectweb.asm.tree.LineNumberNode; +import org.objectweb.asm.tree.LookupSwitchInsnNode; +import org.objectweb.asm.tree.TableSwitchInsnNode; +import org.objectweb.asm.tree.analysis.AnalyzerException; +import org.objectweb.asm.tree.analysis.BasicInterpreter; +import org.objectweb.asm.tree.analysis.BasicValue; +import org.objectweb.asm.tree.analysis.Frame; +import org.objectweb.asm.tree.analysis.Interpreter; + +/** + * What one iteration of a counting loop's body does to the method's locals: for every local it + * writes, whether the new value is the old one plus a constant (a step), or a constant. The body + * runs once through ASM's frame machinery over abstract values {@code local + c}, {@code c} or + * unknown, so it may contain any straight-line code; a local written with anything else (a field, + * an array element, a call's result, another local) rules the loop out. + * + * @param pure whether the body is int arithmetic on locals only (see LoopSummaries.op) + */ +record BodyAnalysis(Map steps, Map constants, boolean pure) { + + /** An abstract value: {@code local + add}, the constant {@code add}, or unknown. */ + private static final class Lin extends BasicValue { + static final int CONSTANT = -1; + static final int UNKNOWN = -2; + final int local; + final int add; + + Lin(Type type, int local, int add) { + super(type); + this.local = local; + this.add = add; + } + + static Lin unknown(BasicValue v) { + return v == null ? null : new Lin(v.getType(), UNKNOWN, 0); + } + + boolean known() { + return local != UNKNOWN; + } + + Lin plus(int c) { + return known() ? new Lin(Type.INT_TYPE, local, add + c) : unknown(BasicValue.INT_VALUE); + } + } + + private static final class LinInterpreter extends Interpreter { + private final BasicInterpreter basic = new BasicInterpreter(); + + LinInterpreter() { + super(Opcodes.ASM9); + } + + @Override + public Lin newValue(Type type) { + BasicValue v = basic.newValue(type); + return v == null ? null : new Lin(v.getType(), Lin.UNKNOWN, 0); + } + + @Override + public Lin newOperation(AbstractInsnNode insn) throws AnalyzerException { + int op = insn.getOpcode(); + if (op >= Opcodes.ICONST_M1 && op <= Opcodes.ICONST_5) { + return new Lin(Type.INT_TYPE, Lin.CONSTANT, op - Opcodes.ICONST_0); + } + if (insn instanceof IntInsnNode ii && (op == Opcodes.BIPUSH || op == Opcodes.SIPUSH)) { + return new Lin(Type.INT_TYPE, Lin.CONSTANT, ii.operand); + } + if (insn instanceof LdcInsnNode ldc && ldc.cst instanceof Integer c) { + return new Lin(Type.INT_TYPE, Lin.CONSTANT, c); + } + return Lin.unknown(basic.newOperation(insn)); + } + + @Override + public Lin copyOperation(AbstractInsnNode insn, Lin value) { + return value; // the same value: a local loaded and stored back is unchanged + } + + @Override + public Lin unaryOperation(AbstractInsnNode insn, Lin value) throws AnalyzerException { + if (insn instanceof IincInsnNode inc) { + return value.plus(inc.incr); + } + return Lin.unknown(basic.unaryOperation(insn, value)); + } + + @Override + public Lin binaryOperation(AbstractInsnNode insn, Lin v1, Lin v2) throws AnalyzerException { + int op = insn.getOpcode(); + if (op == Opcodes.IADD || op == Opcodes.ISUB) { + if (v2.local == Lin.CONSTANT && v1.known()) { + return v1.plus(op == Opcodes.IADD ? v2.add : -v2.add); + } + if (op == Opcodes.IADD && v1.local == Lin.CONSTANT && v2.known()) { + return v2.plus(v1.add); + } + } + return Lin.unknown(basic.binaryOperation(insn, v1, v2)); + } + + @Override + public Lin ternaryOperation(AbstractInsnNode insn, Lin v1, Lin v2, Lin v3) throws AnalyzerException { + return Lin.unknown(basic.ternaryOperation(insn, v1, v2, v3)); + } + + @Override + public Lin naryOperation(AbstractInsnNode insn, List values) throws AnalyzerException { + return Lin.unknown(basic.naryOperation(insn, values)); + } + + @Override + public void returnOperation(AbstractInsnNode insn, Lin value, Lin expected) { + } + + @Override + public Lin merge(Lin v, Lin w) { + return v; // straight-line code: never merged + } + } + + /** + * Analyses the body from after {@code cond} (the loop's head branch) up to {@code back} (its back + * edge), or returns null if it is not straight-line code or writes a local we cannot follow. + */ + static BodyAnalysis run(InsnList insns, JumpInsnNode cond, JumpInsnNode back, Map targets, + Frame[] frames) { + AbstractInsnNode first = cond.getNext(); + Frame start = frames[insns.indexOf(first)]; + if (start == null || start.getStackSize() != 0) { + return null; + } + Frame f = new Frame<>(start.getLocals(), Math.max(start.getMaxStackSize(), 1)); + Lin[] initial = new Lin[start.getLocals()]; + for (int k = 0; k < start.getLocals(); k++) { + BasicValue v = start.getLocal(k); + Lin l = v == BasicValue.INT_VALUE ? new Lin(Type.INT_TYPE, k, 0) + : new Lin(v == null ? null : v.getType(), Lin.UNKNOWN, 0); + initial[k] = l; + f.setLocal(k, l); + } + LinInterpreter interpreter = new LinInterpreter(); + boolean pure = true; + for (AbstractInsnNode n = first; n != back; n = n.getNext()) { + if (n == null) { + return null; + } + if (n instanceof LabelNode l) { + if (targets.containsKey(l)) { + return null; + } + continue; + } + if (n instanceof LineNumberNode || n instanceof FrameNode) { + continue; + } + int op = n.getOpcode(); + if (n instanceof JumpInsnNode || n instanceof TableSwitchInsnNode || n instanceof LookupSwitchInsnNode + || (op >= Opcodes.IRETURN && op <= Opcodes.RETURN) || op == Opcodes.ATHROW + || op == Opcodes.RET) { + return null; + } + pure &= LoopSummaries.op(n) != null; + try { + f.execute(n, interpreter); + } catch (AnalyzerException | RuntimeException e) { + return null; + } + } + if (f.getStackSize() != 0) { + return null; + } + Map steps = new HashMap<>(); + Map constants = new HashMap<>(); + for (int k = 0; k < start.getLocals(); k++) { + Lin end = f.getLocal(k); + if (end == initial[k]) { + continue; + } + if (start.getLocal(k) == BasicValue.UNINITIALIZED_VALUE) { + // Not defined at the head, which is also the way out: a temporary of the body, + // which nothing after the loop reads without writing it first. + continue; + } + if (initial[k].local != k) { + return null; // a non-int local (or an int one that is not defined at the head) written + } + if (end.local == k) { + if (end.add != 0) { + steps.put(k, end.add); + } + } else if (end.local == Lin.CONSTANT) { + constants.put(k, end.add); + } else { + return null; + } + } + return new BodyAnalysis(steps, constants, pure); + } +} diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/summary/LoopSummaries.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/summary/LoopSummaries.java new file mode 100644 index 00000000..cfcd02d6 --- /dev/null +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/summary/LoopSummaries.java @@ -0,0 +1,619 @@ +package de.uzl.its.swat.symbolic.summary; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.FrameNode; +import org.objectweb.asm.tree.IincInsnNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.InsnNode; +import org.objectweb.asm.tree.IntInsnNode; +import org.objectweb.asm.tree.JumpInsnNode; +import org.objectweb.asm.tree.LabelNode; +import org.objectweb.asm.tree.LdcInsnNode; +import org.objectweb.asm.tree.LineNumberNode; +import org.objectweb.asm.tree.LookupSwitchInsnNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.TableSwitchInsnNode; +import org.objectweb.asm.tree.TryCatchBlockNode; +import org.objectweb.asm.tree.VarInsnNode; + +/** + * Loop summaries: code whose paths the executor merges into one, so that the explorer has no + * branch to enumerate there. + * + *

Two shapes, found in a method's bytecode before it is instrumented: + * + *

    + *
  • Diamonds: an {@code if} on ints whose two sides are straight-line int arithmetic on + * locals (no calls, fields, arrays, divisions or other jumps) and meet again at a join label, + * e.g. {@code if (i < val) count++;} or {@code s = i < val ? s + 2 : s;}. At the {@code if} + * the executor computes what both sides do to the locals (and to the value a conditional + * expression leaves on the stack), and at the join it gives each of them the formula + * {@code ite(condition, jump side, fall-through side)}. Inside a loop this summarizes the + * loop: a counter fed by an input-dependent branch becomes a formula of the input. + *
  • Counting loops: a loop whose head tests {@code i < b} (or {@code i > b}) and whose + * body is straight-line int arithmetic that steps {@code i} by +1 (or -1), adds constants to + * other locals or sets them to constants, with {@code b} unchanged. At the loop's exit the + * executor sets every such local to its closed form in the trip count {@code N = max(b - i0, + * 0)}, e.g. {@code s0 + 3 * N}. With a strict bound and a step of one, {@code i} cannot + * overflow, and bit-vector arithmetic wraps around like Java's ints, so the closed forms + * are exact. + *
+ * + * Both are exact: every path through the code ends in the merged state, which is a formula of the + * state before it. The branches that the merged code reports are flagged {@code merged}; the + * explorer neither flips them nor keeps their constraints, which the merged formulas replace. + */ +public final class LoopSummaries { + + /** + * An instruction of a side: opcode, operand (slot or constant), increment; or, with {@code + * nested}, a diamond inside the side, whose opcode is that of its {@code if}. + */ + public record Op(int opcode, int operand, int increment, Diamond nested) { + public Op(int opcode, int operand, int increment) { + this(opcode, operand, increment, null); + } + } + + public sealed interface Plan permits Diamond, CountingLoop, BackEdge {} + + /** + * A diamond: the ops of the fall-through side and of the jump side, the id of the probe at the + * join, and how many values the sides leave on the stack (0, or 1 for a conditional expression). + */ + public record Diamond(List fallThrough, List jump, int join, int stackDelta) implements Plan {} + + /** + * A counting loop with head {@code i < b} (step +1) or {@code i > b} (step -1), or with + * {@code inclusive}, {@code i <= b} or {@code i >= b}. {@code bound} is + * a local slot, or -1 with {@code boundConstant}. {@code exitOnJump}: the head's branch leaves + * the loop when it jumps. {@code steps}: the locals the body adds a constant to (the induction + * variable among them), {@code constants}: those it sets to a constant. + * + *

{@code pure}: the body is int arithmetic on locals only (every instruction is one that + * {@link #op} accepts). The closed forms of the locals are exact either way, since nothing else + * in the body can change a local; but an impure body also does other things (calls, field or + * array accesses) once per iteration, and a summary that drops the head's constraints cannot + * account for those. So the branches of an impure loop are flagged {@code mergedConditional} + * (see Merger), and the explorer only treats them as merged where its relevance analysis shows + * that the loop's other effects cannot matter to a target ({@code Relevance.summary_ok}, in + * {@code strategy/DFS.py}); elsewhere it keeps them as ordinary branches. + */ + public record CountingLoop(int i, int step, int bound, int boundConstant, boolean exitOnJump, + Map steps, Map constants, + boolean pure, boolean inclusive) implements Plan {} + + /** + * The back edge of a counting loop: tells the executor that the loop's head is reached again + * in the same activation (rather than entered anew, e.g. after an exception left the loop). + */ + public record BackEdge(CountingLoop loop) implements Plan {} + + /** What the instrumentation needs for a method: plans by jump ordinal, probes by join label. */ + public record MethodPlans(Map byJump, Map joins) { + public static final MethodPlans NONE = new MethodPlans(Map.of(), Map.of()); + } + + private static final Map plans = new ConcurrentHashMap<>(); + private static final AtomicInteger joinIds = new AtomicInteger(); + + private LoopSummaries() {} + + /** Called by the instrumentation with the iid it gave a jump that has a plan. */ + public static void register(long iid, Plan plan) { + plans.put(iid, plan); + } + + /** The plan of the jump with this (instrumentation) iid, or null. */ + public static Plan planAt(long iid) { + return plans.get(iid); + } + + /** Emits the probe at a join label (see SUMMARY_JOIN). */ + public static void emitJoinProbe(MethodVisitor mv, String dispatcher, long iid, int join) { + mv.visitLdcInsn(iid); + mv.visitLdcInsn(join); + mv.visitMethodInsn(Opcodes.INVOKESTATIC, dispatcher, "SUMMARY_JOIN", "(JI)V", false); + } + + /** Finds the diamonds and counting loops of a method. */ + public static MethodPlans analyze(MethodNode mn) { + return analyze("java/lang/Object", mn); + } + + /** + * Finds the diamonds and counting loops of a method of {@code owner} whose merged values stay + * symbolic (see TaintCheck). + */ + public static MethodPlans analyze(String owner, MethodNode mn) { + InsnList insns = mn.instructions; + if (insns.size() == 0) { + return MethodPlans.NONE; + } + TaintCheck taint = null; + org.objectweb.asm.tree.analysis.Frame[] frames = null; + // Labels something jumps to (or a handler starts at): code between two of them is entered + // only at its start. + Map targets = new IdentityHashMap<>(); + for (AbstractInsnNode n : insns) { + if (n instanceof JumpInsnNode j) { + if (j.getOpcode() == Opcodes.JSR) { + return MethodPlans.NONE; + } + targets.put(j.label, Boolean.TRUE); + } else if (n instanceof TableSwitchInsnNode t) { + targets.put(t.dflt, Boolean.TRUE); + t.labels.forEach(l -> targets.put(l, Boolean.TRUE)); + } else if (n instanceof LookupSwitchInsnNode l) { + targets.put(l.dflt, Boolean.TRUE); + l.labels.forEach(x -> targets.put(x, Boolean.TRUE)); + } + } + for (TryCatchBlockNode tc : mn.tryCatchBlocks) { + targets.put(tc.handler, Boolean.TRUE); + } + // Who jumps to each label (a switch or a handler counts as a source no diamond owns). + Map> sources = new IdentityHashMap<>(); + for (AbstractInsnNode n : insns) { + if (n instanceof JumpInsnNode j) { + sources.computeIfAbsent(j.label, k -> new ArrayList<>()).add(n); + } else if (n instanceof TableSwitchInsnNode t) { + sources.computeIfAbsent(t.dflt, k -> new ArrayList<>()).add(n); + t.labels.forEach(l -> sources.computeIfAbsent(l, k -> new ArrayList<>()).add(n)); + } else if (n instanceof LookupSwitchInsnNode l) { + sources.computeIfAbsent(l.dflt, k -> new ArrayList<>()).add(n); + l.labels.forEach(x -> sources.computeIfAbsent(x, k -> new ArrayList<>()).add(n)); + } + } + for (TryCatchBlockNode tc : mn.tryCatchBlocks) { + sources.computeIfAbsent(tc.handler, k -> new ArrayList<>()).add(tc.handler); + } + + Map byJump = new HashMap<>(); + Map joins = new IdentityHashMap<>(); + int ordinal = 0; + for (AbstractInsnNode n : insns) { + if (!(n instanceof JumpInsnNode j)) { + continue; + } + int jumpOrdinal = ordinal++; + if (isIntCondition(j.getOpcode())) { + Map joinCandidate = new IdentityHashMap<>(); + Diamond d = diamond(insns, j, targets, joinCandidate, joins, sources); + if (d != null) { + if (taint == null && (taint = TaintCheck.of(owner, mn)) == null) { + return MethodPlans.NONE; + } + LabelNode join = joinLabel(insns, joinCandidate.keySet().iterator().next()); + java.util.Set slots = new java.util.HashSet<>(written(d.fallThrough())); + slots.addAll(written(d.jump())); + if (!taint.leaks(join, slots, d.stackDelta() == 1)) { + joins.putAll(joinCandidate); + byJump.put(jumpOrdinal, d); + } + continue; + } + } + if (j.getOpcode() == Opcodes.GOTO && insns.indexOf(j.label) < insns.indexOf(j)) { + if (frames == null) { + frames = basicFrames(owner, mn); + } + Map.Entry loop = frames == null ? null : countingLoop(insns, j, targets, frames); + if (loop != null) { + if (taint == null && (taint = TaintCheck.of(owner, mn)) == null) { + return MethodPlans.NONE; + } + CountingLoop l = loop.getValue(); + java.util.Set slots = new java.util.HashSet<>(l.steps().keySet()); + slots.addAll(l.constants().keySet()); + if (!taint.leaks(loop.getKey().label, slots, false)) { + byJump.put(jumpOrdinal(insns, loop.getKey()), l); + byJump.put(jumpOrdinal, new BackEdge(l)); + } + } + } + } + return new MethodPlans(byJump, joins); + } + + private static java.util.Set written(List ops) { + java.util.Set slots = new java.util.HashSet<>(); + for (Op op : ops) { + if (op.nested() != null) { + slots.addAll(written(op.nested().fallThrough())); + slots.addAll(written(op.nested().jump())); + } else if (op.opcode() == Opcodes.ISTORE || op.opcode() == Opcodes.IINC) { + slots.add(op.operand()); + } + } + return slots; + } + + private static LabelNode joinLabel(InsnList insns, Label label) { + for (AbstractInsnNode n : insns) { + if (n instanceof LabelNode l && l.getLabel() == label) { + return l; + } + } + throw new IllegalStateException("join label not in its method"); + } + + private static int jumpOrdinal(InsnList insns, JumpInsnNode target) { + int ordinal = 0; + for (AbstractInsnNode n : insns) { + if (n == target) { + return ordinal; + } + if (n instanceof JumpInsnNode) { + ordinal++; + } + } + throw new IllegalStateException("jump not in its method"); + } + + private static boolean isIntCondition(int opcode) { + return opcode >= Opcodes.IFEQ && opcode <= Opcodes.IF_ICMPLE; + } + + /** Whether a node is a real instruction (not a label, line number or frame). */ + private static boolean isReal(AbstractInsnNode n) { + return !(n instanceof LabelNode || n instanceof LineNumberNode || n instanceof FrameNode); + } + + /** The op for an allowed straight-line int instruction, or null. */ + static Op op(AbstractInsnNode n) { + int opc = n.getOpcode(); + if (n instanceof VarInsnNode v && (opc == Opcodes.ILOAD || opc == Opcodes.ISTORE)) { + return new Op(opc, v.var, 0); + } + if (n instanceof IincInsnNode inc) { + return new Op(Opcodes.IINC, inc.var, inc.incr); + } + if (opc >= Opcodes.ICONST_M1 && opc <= Opcodes.ICONST_5) { + return new Op(Opcodes.LDC, opc - Opcodes.ICONST_0, 0); + } + if (n instanceof IntInsnNode ii && (opc == Opcodes.BIPUSH || opc == Opcodes.SIPUSH)) { + return new Op(Opcodes.LDC, ii.operand, 0); + } + if (n instanceof LdcInsnNode ldc && ldc.cst instanceof Integer c) { + return new Op(Opcodes.LDC, c, 0); + } + if (n instanceof InsnNode) { + switch (opc) { + case Opcodes.IADD, Opcodes.ISUB, Opcodes.IMUL, Opcodes.INEG, Opcodes.ISHL, + Opcodes.ISHR, Opcodes.IUSHR, Opcodes.IAND, Opcodes.IOR, Opcodes.IXOR: + return new Op(opc, 0, 0); + default: + return null; + } + } + return null; + } + + /** How an op changes the stack depth: {pops, pushes}. */ + private static int[] stackEffect(Op op) { + if (op.nested() != null) { + // The inner if pops its operands: IFEQ..IFLE compare one value with zero, IF_ICMPEQ.. + // IF_ICMPLE two values (nested ops are only built for these, see isIntCondition); its + // sides then leave the inner diamond's stackDelta. + return new int[] {op.opcode() <= Opcodes.IFLE ? 1 : 2, op.nested().stackDelta()}; + } + return switch (op.opcode()) { + case Opcodes.ILOAD, Opcodes.LDC -> new int[] {0, 1}; + case Opcodes.ISTORE -> new int[] {1, 0}; + case Opcodes.IINC -> new int[] {0, 0}; + case Opcodes.INEG -> new int[] {1, 1}; + case Opcodes.IADD, Opcodes.ISUB, Opcodes.IMUL, Opcodes.IDIV, Opcodes.IREM, Opcodes.ISHL, + Opcodes.ISHR, Opcodes.IUSHR, Opcodes.IAND, Opcodes.IOR, Opcodes.IXOR -> new int[] {2, 1}; + // Every op that op() and block() admit is listed above + default -> throw new IllegalStateException("no stack effect for opcode " + op.opcode()); + }; + } + + /** + * The ops from {@code from} (exclusive) up to the first label in {@code ends} or the first + * instruction that is not one: returns the ops and the node that ended the block, or null if a + * label something else jumps to comes first. An {@code if} whose sides are blocks again and + * meet within this one becomes a nested op; a division is allowed by a nonzero constant only + * (it cannot throw then). + */ + private static Map.Entry, AbstractInsnNode> block(AbstractInsnNode from, + Map targets, Set ends, Map> sources) { + List ops = new ArrayList<>(); + for (AbstractInsnNode n = from.getNext(); n != null; n = n.getNext()) { + if (n instanceof LabelNode l) { + if (ends.contains(l)) { + return Map.entry(ops, n); + } + if (targets.containsKey(l)) { + return null; + } + continue; + } + if (!isReal(n)) { + continue; + } + if (n instanceof JumpInsnNode k && isIntCondition(k.getOpcode()) && sources != null) { + Parsed inner = parse(k, targets, sources); + if (inner == null) { + return Map.entry(ops, n); + } + // Control enters the rest of this block only from the inner diamond's own jumps. + for (AbstractInsnNode src : sources.getOrDefault(inner.join(), List.of())) { + if (src != k && src != inner.gotoNode() && !ends.contains(inner.join())) { + return null; + } + } + ops.add(new Op(k.getOpcode(), 0, 0, new Diamond(inner.fall(), inner.jump(), -1, inner.depth()))); + if (ends.contains(inner.join())) { + return Map.entry(ops, inner.join()); + } + n = inner.join(); + continue; + } + int opc = n.getOpcode(); + if ((opc == Opcodes.IDIV || opc == Opcodes.IREM) && !ops.isEmpty() + && ops.get(ops.size() - 1).opcode() == Opcodes.LDC && ops.get(ops.size() - 1).nested() == null + && ops.get(ops.size() - 1).operand() != 0) { + ops.add(new Op(opc, 0, 0)); + continue; + } + Op op = op(n); + if (op == null) { + return Map.entry(ops, n); // ends at a non-arithmetic instruction (e.g. a GOTO) + } + ops.add(op); + } + return null; + } + + /** The stack depth at the end of a side, or -1 if it pops below its start. */ + private static int depth(List ops) { + int depth = 0; + for (Op op : ops) { + int[] e = stackEffect(op); + depth -= e[0]; + if (depth < 0) { + return -1; + } + depth += e[1]; + } + return depth; + } + + private static boolean writes(List ops) { + return !written(ops).isEmpty(); + } + + /** A diamond's sides, its join, the GOTO that ends its fall-through side (if any), its depth. */ + private record Parsed(List fall, List jump, LabelNode join, AbstractInsnNode gotoNode, int depth) {} + + /** The diamond of the {@code if} {@code j}, or null. */ + private static Parsed parse(JumpInsnNode j, Map targets, + Map> sources) { + LabelNode jumpTarget = j.label; + if (!isBefore(j, jumpTarget)) { + return null; + } + Map.Entry, AbstractInsnNode> fall = block(j, targets, Set.of(jumpTarget), sources); + if (fall == null) { + return null; + } + List fallOps = fall.getKey(); + List jumpOps; + LabelNode join; + AbstractInsnNode gotoNode = null; + if (fall.getValue() == jumpTarget) { + // if (c) { fall-through side } -- the jump side is empty + jumpOps = List.of(); + join = jumpTarget; + } else if (fall.getValue() instanceof JumpInsnNode g && g.getOpcode() == Opcodes.GOTO + && isBefore(jumpTarget, g.label) && nextLabelIs(g, jumpTarget)) { + // if (c) { fall-through side } else { jump side } + join = g.label; + gotoNode = g; + Map.Entry, AbstractInsnNode> jump = block(jumpTarget, targets, Set.of(join), sources); + if (jump == null || jump.getValue() != join) { + return null; + } + jumpOps = jump.getKey(); + } else { + return null; + } + int fallDepth = depth(fallOps); + int jumpDepth = depth(jumpOps); + if (fallDepth < 0 || fallDepth != jumpDepth || fallDepth > 1) { + return null; + } + // The jump target is entered only by this if (the fall-through side may also end there) + for (AbstractInsnNode src : sources.getOrDefault(jumpTarget, List.of())) { + if (src != j && join != jumpTarget) { + return null; + } + } + return new Parsed(List.copyOf(fallOps), List.copyOf(jumpOps), join, gotoNode, fallDepth); + } + + /** Whether {@code a} comes before {@code b} in their method. */ + private static boolean isBefore(AbstractInsnNode a, AbstractInsnNode b) { + for (AbstractInsnNode n = a.getNext(); n != null; n = n.getNext()) { + if (n == b) { + return true; + } + } + return false; + } + + private static Diamond diamond(InsnList insns, JumpInsnNode j, Map targets, + Map joins, Map existingJoins, + Map> sources) { + Parsed p = parse(j, targets, sources); + if (p == null) { + return null; + } + if (p.depth() == 0 && !writes(p.fall()) && !writes(p.jump())) { + return null; + } + LabelNode join = p.join(); + // Diamonds that meet at the same label share its probe + Integer id = existingJoins.containsKey(join.getLabel()) ? existingJoins.get(join.getLabel()) + : Integer.valueOf(joinIds.incrementAndGet()); + joins.put(join.getLabel(), id); + return new Diamond(p.fall(), p.jump(), id, p.depth()); + } + + /** Whether nothing but non-instructions separates {@code from} and the label {@code label}. */ + private static boolean nextLabelIs(AbstractInsnNode from, LabelNode label) { + for (AbstractInsnNode n = from.getNext(); n != null; n = n.getNext()) { + if (n == label) { + return true; + } + if (isReal(n)) { + return false; + } + } + return false; + } + + // --- counting loops --------------------------------------------------------------------------- + + /** + * The counting loop whose back edge is {@code back}, with the head's branch, or null. The head + * is {@code ILOAD i; (ILOAD b | constant); IF_ICMPxx exit} or {@code ILOAD i; IFxx exit}; the + * body runs from there to {@code back}, and the exit label follows {@code back}. + */ + private static Map.Entry countingLoop(InsnList insns, JumpInsnNode back, + Map targets, + org.objectweb.asm.tree.analysis.Frame[] frames) { + LabelNode head = back.label; + List h = new ArrayList<>(); + AbstractInsnNode n = head.getNext(); + for (; n != null && h.size() < 3; n = n.getNext()) { + if (n instanceof LabelNode l && targets.containsKey(l)) { + return null; + } + if (isReal(n)) { + h.add(n); + if (n instanceof JumpInsnNode) { + break; + } + } + } + if (h.isEmpty() || !(h.get(h.size() - 1) instanceof JumpInsnNode cond)) { + return null; + } + if (!isIntCondition(cond.getOpcode())) { + return null; + } + LabelNode exit = cond.label; + if (!nextLabelIs(back, exit)) { + return null; + } + // The head's operands. + int opcode = cond.getOpcode(); + int i; + int bound = -1; + int boundConstant = 0; + if (opcode <= Opcodes.IFLE) { + if (h.size() != 2 || !(h.get(0) instanceof VarInsnNode v) || v.getOpcode() != Opcodes.ILOAD) { + return null; + } + i = v.var; + opcode = opcode - Opcodes.IFEQ + Opcodes.IF_ICMPEQ; // compare with the constant 0 + } else { + if (h.size() != 3) { + return null; + } + Op a = op(h.get(0)); + Op b = op(h.get(1)); + if (a == null || b == null) { + return null; + } + if (a.opcode() == Opcodes.ILOAD && (b.opcode() == Opcodes.ILOAD || b.opcode() == Opcodes.LDC)) { + i = a.operand(); + if (b.opcode() == Opcodes.ILOAD) { + bound = b.operand(); + } else { + boundConstant = b.operand(); + } + } else if (b.opcode() == Opcodes.ILOAD && a.opcode() == Opcodes.LDC) { + // constant OP i: mirror the comparison + i = b.operand(); + boundConstant = a.operand(); + opcode = mirror(opcode); + } else { + return null; + } + } + // The head exits (jumps) on i >= b (the loop runs while i < b) or on i <= b (while i > b). + int step; + boolean inclusive = false; + if (opcode == Opcodes.IF_ICMPGE) { + step = 1; + } else if (opcode == Opcodes.IF_ICMPLE) { + step = -1; + } else if (opcode == Opcodes.IF_ICMPGT) { + // runs while i <= b: never ends for b = 2^31 - 1, where nothing after it runs either, + // so closed forms that are wrong only there add no execution that exists + step = 1; + inclusive = true; + } else if (opcode == Opcodes.IF_ICMPLT) { + step = -1; // while i >= b, likewise + inclusive = true; + } else { + return null; + } + // The body: straight-line code up to the back edge. Calls cannot change the method's locals, + // so whatever else the body does (calls, fields, arrays), the closed forms of its int locals + // stay exact; such a loop is impure, and the explorer only uses its summary if that other + // code cannot matter to the targets (see BranchElement.mergedConditional). + BodyAnalysis body = BodyAnalysis.run(insns, cond, back, targets, frames); + if (body == null) { + return null; + } + Map steps = body.steps(); + Map constants = body.constants(); + if (steps.getOrDefault(i, 0) != step || constants.containsKey(i)) { + return null; + } + if (bound >= 0 && (steps.containsKey(bound) || constants.containsKey(bound) || bound == i)) { + return null; + } + return Map.entry(cond, new CountingLoop(i, step, bound, boundConstant, true, + Collections.unmodifiableMap(steps), Collections.unmodifiableMap(constants), body.pure(), inclusive)); + } + + /** The method's frames (types of locals and stack values), or null if it cannot be analysed. */ + private static org.objectweb.asm.tree.analysis.Frame[] basicFrames( + String owner, MethodNode mn) { + try { + return new org.objectweb.asm.tree.analysis.Analyzer<>( + new org.objectweb.asm.tree.analysis.BasicInterpreter()).analyze(owner, mn); + } catch (Exception e) { + return null; + } + } + + /** The comparison with its operands swapped: {@code a < b} is {@code b > a}. */ + private static int mirror(int opcode) { + return switch (opcode) { + case Opcodes.IF_ICMPLT -> Opcodes.IF_ICMPGT; + case Opcodes.IF_ICMPGT -> Opcodes.IF_ICMPLT; + case Opcodes.IF_ICMPLE -> Opcodes.IF_ICMPGE; + case Opcodes.IF_ICMPGE -> Opcodes.IF_ICMPLE; + default -> opcode; + }; + } +} diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/summary/Merger.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/summary/Merger.java new file mode 100644 index 00000000..b2d37b60 --- /dev/null +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/summary/Merger.java @@ -0,0 +1,447 @@ +package de.uzl.its.swat.symbolic.summary; + +import de.uzl.its.swat.symbolic.shadow.Frame; +import de.uzl.its.swat.symbolic.shadow.ShadowContext; +import de.uzl.its.swat.symbolic.summary.LoopSummaries.CountingLoop; +import de.uzl.its.swat.symbolic.summary.LoopSummaries.Diamond; +import de.uzl.its.swat.symbolic.summary.LoopSummaries.Op; +import de.uzl.its.swat.symbolic.trace.BranchElement; +import de.uzl.its.swat.symbolic.value.Value; +import de.uzl.its.swat.symbolic.value.primitive.numeric.integral.IntValue; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import org.objectweb.asm.Opcodes; +import org.sosy_lab.java_smt.api.BitvectorFormula; +import org.sosy_lab.java_smt.api.BitvectorFormulaManager; +import org.sosy_lab.java_smt.api.BooleanFormula; +import org.sosy_lab.java_smt.api.BooleanFormulaManager; +import org.sosy_lab.java_smt.api.SolverContext; + +/** + * Applies loop summaries (see {@link LoopSummaries}) while the executor runs: at a summarized + * branch it records what to merge, at the join (a diamond) or the loop's exit (a counting loop) it + * gives the locals their merged formulas. Their concrete values are never changed: the merged + * value's concrete part is the one the execution computed, which is checked; if it differs, the + * summary is dropped and its branches are reported as ordinary ones. + */ +public final class Merger { + + private static final int BITS = 32; + + /** A diamond waiting for its join: the merged locals and stack value, and its branch. */ + private record PendingJoin(Map locals, IntValue top, BranchElement branch) {} + + /** A counting loop being executed: its values at entry, and the branches its head reported. */ + private static final class ActiveLoop { + final IntValue i0; + final IntValue bound; + final Map entry = new HashMap<>(); + final List branches = new ArrayList<>(); + final boolean summarize; + + ActiveLoop(IntValue i0, IntValue bound, boolean summarize) { + this.i0 = i0; + this.bound = bound; + this.summarize = summarize; + } + } + + private final ShadowContext stack; + + public Merger(ShadowContext stack) { + this.stack = stack; + } + + /** + * Called for every int branch after it was recorded: {@code jumpCondition} holds iff the branch + * jumps, {@code jumped} says whether it did. + */ + public void atBranch(long instIid, boolean jumped, BooleanFormula jumpCondition, BranchElement branch, + SolverContext context) { + if (branch == null) { + return; + } + LoopSummaries.Plan plan = LoopSummaries.planAt(instIid); + Frame current = stack.getActiveFrame(); + Object backEdge = current == null ? null : current.summaryBackEdge; + if (current != null) { + current.summaryBackEdge = null; + } + if (plan instanceof Diamond d) { + diamond(d, jumped, jumpCondition, branch, context); + } else if (plan instanceof CountingLoop l) { + loopHead(instIid, l, jumped, branch, context, backEdge == l); + } + } + + /** At a GOTO: remembers the back edge of a counting loop, so that its head knows it is looping. */ + public void atGoto(long instIid) { + if (LoopSummaries.planAt(instIid) instanceof LoopSummaries.BackEdge b) { + Frame frame = stack.getActiveFrame(); + if (frame != null) { + frame.summaryBackEdge = b.loop(); + } + } + } + + // --- diamonds ------------------------------------------------------------------------------- + + private void diamond(Diamond d, boolean jumped, BooleanFormula jumpCondition, BranchElement branch, + SolverContext context) { + if (context.getFormulaManager().extractVariables(jumpCondition).isEmpty()) { + return; // a concrete branch: there is nothing to merge + } + Frame frame = stack.getActiveFrame(); + Side fall = simulate(d.fallThrough(), frame, context); + Side jump = simulate(d.jump(), frame, context); + if (fall == null || jump == null) { + return; + } + BitvectorFormulaManager bv = context.getFormulaManager().getBitvectorFormulaManager(); + BooleanFormulaManager bm = context.getFormulaManager().getBooleanFormulaManager(); + Map merged = new LinkedHashMap<>(); + java.util.Set slots = new java.util.TreeSet<>(fall.written); + slots.addAll(jump.written); + for (int slot : slots) { + IntValue j = jump.locals.get(slot); + IntValue f = fall.locals.get(slot); + if (j == null || f == null) { + return; + } + IntValue taken = jumped ? j : f; + merged.put(slot, new IntValue(context, taken.concrete, bm.ifThenElse(jumpCondition, j.formula, f.formula))); + } + IntValue top = null; + if (d.stackDelta() == 1) { + IntValue j = jump.top; + IntValue f = fall.top; + top = new IntValue(context, (jumped ? j : f).concrete, bm.ifThenElse(jumpCondition, j.formula, f.formula)); + } + // Diamonds may share a join (an if inside the side of another that meets at the same label): + // their merges are applied there innermost first. + @SuppressWarnings("unchecked") + java.util.Deque pending = (java.util.Deque) frame.summaryJoins() + .computeIfAbsent(d.join(), k -> new java.util.ArrayDeque()); + pending.push(new PendingJoin(merged, top, branch)); + branch.setMerged(true); + } + + /** What a side does: the locals it leaves (read or written), those it wrote, its stack top. */ + private record Side(Map locals, java.util.Set written, IntValue top) {} + + /** Runs a side's ops on the frame's current values, without changing the frame. */ + private Side simulate(List ops, Frame frame, SolverContext context) { + Map locals = new HashMap<>(); + java.util.Set written = new java.util.HashSet<>(); + List st = new ArrayList<>(); + try { + if (!run(ops, frame, context, locals, written, st)) { + return null; + } + } catch (RuntimeException e) { + return null; + } + // The other side's written locals need this side's value too: its current one. + for (int slot : new java.util.ArrayList<>(locals.keySet())) { + if (!written.contains(slot)) { + locals.remove(slot); + } + } + return new Side(new LocalsView(locals, frame), written, st.isEmpty() ? null : st.get(st.size() - 1)); + } + + /** Runs ops on the given locals (falling back to the frame's) and stack; false if it cannot. */ + private boolean run(List ops, Frame frame, SolverContext context, Map locals, + java.util.Set written, List st) { + for (Op op : ops) { + if (op.nested() != null) { + if (!nested(op, frame, context, locals, written, st)) { + return false; + } + continue; + } + switch (op.opcode()) { + case Opcodes.ILOAD -> { + IntValue v = local(locals, frame, op.operand()); + if (v == null) { + return false; + } + st.add(v); + } + case Opcodes.ISTORE -> { + locals.put(op.operand(), st.remove(st.size() - 1)); + written.add(op.operand()); + } + case Opcodes.IINC -> { + IntValue v = local(locals, frame, op.operand()); + if (v == null) { + return false; + } + locals.put(op.operand(), v.IINC(op.increment())); + written.add(op.operand()); + } + case Opcodes.LDC -> st.add(new IntValue(context, op.operand())); + case Opcodes.INEG -> st.add(st.remove(st.size() - 1).INEG()); + default -> { + IntValue y = st.remove(st.size() - 1); + IntValue x = st.remove(st.size() - 1); + st.add(switch (op.opcode()) { + case Opcodes.IADD -> x.IADD(y); + case Opcodes.ISUB -> x.ISUB(y); + case Opcodes.IMUL -> x.IMUL(y); + case Opcodes.IDIV -> x.IDIV(y); // by a nonzero constant (LoopSummaries.block) + case Opcodes.IREM -> x.IREM(y); + case Opcodes.ISHL -> x.ISHL(y); + case Opcodes.ISHR -> x.ISHR(y); + case Opcodes.IUSHR -> x.IUSHR(y); + case Opcodes.IAND -> x.IAND(y); + case Opcodes.IOR -> x.IOR(y); + case Opcodes.IXOR -> x.IXOR(y); + default -> throw new IllegalStateException("op " + op.opcode()); + }); + } + } + } + return true; + } + + /** A diamond inside a side: both of its sides run, and their results are merged by its condition. */ + private boolean nested(Op op, Frame frame, SolverContext context, Map locals, + java.util.Set written, List st) { + int opcode = op.opcode(); + IntValue y = opcode <= Opcodes.IFLE ? null : st.remove(st.size() - 1); + IntValue x = st.remove(st.size() - 1); + BooleanFormula condition = switch (opcode) { + case Opcodes.IFEQ -> x.IFEQ(); + case Opcodes.IFNE -> x.IFNE(); + case Opcodes.IFLT -> x.IFLT(); + case Opcodes.IFGE -> x.IFGE(); + case Opcodes.IFGT -> x.IFGT(); + case Opcodes.IFLE -> x.IFLE(); + case Opcodes.IF_ICMPEQ -> x.IF_ICMPEQ(y); + case Opcodes.IF_ICMPNE -> x.IF_ICMPNE(y); + case Opcodes.IF_ICMPLT -> x.IF_ICMPLT(y); + case Opcodes.IF_ICMPGE -> x.IF_ICMPGE(y); + case Opcodes.IF_ICMPGT -> x.IF_ICMPGT(y); + case Opcodes.IF_ICMPLE -> x.IF_ICMPLE(y); + default -> throw new IllegalStateException("if " + opcode); + }; + int a = x.concrete; + int b = y == null ? 0 : y.concrete; + boolean jumps = switch (opcode) { + case Opcodes.IFEQ, Opcodes.IF_ICMPEQ -> a == b; + case Opcodes.IFNE, Opcodes.IF_ICMPNE -> a != b; + case Opcodes.IFLT, Opcodes.IF_ICMPLT -> a < b; + case Opcodes.IFGE, Opcodes.IF_ICMPGE -> a >= b; + case Opcodes.IFGT, Opcodes.IF_ICMPGT -> a > b; + default -> a <= b; + }; + Map jl = new HashMap<>(locals); + Map fl = new HashMap<>(locals); + java.util.Set jw = new java.util.HashSet<>(); + java.util.Set fw = new java.util.HashSet<>(); + List js = new ArrayList<>(st); + List fs = new ArrayList<>(st); + if (!run(op.nested().jump(), frame, context, jl, jw, js) || !run(op.nested().fallThrough(), frame, context, fl, fw, fs)) { + return false; + } + BooleanFormulaManager bm = context.getFormulaManager().getBooleanFormulaManager(); + java.util.Set slots = new java.util.TreeSet<>(jw); + slots.addAll(fw); + for (int slot : slots) { + IntValue j = local(jl, frame, slot); + IntValue f = local(fl, frame, slot); + if (j == null || f == null) { + return false; + } + locals.put(slot, new IntValue(context, (jumps ? j : f).concrete, bm.ifThenElse(condition, j.formula, f.formula))); + written.add(slot); + } + if (op.nested().stackDelta() == 1) { + IntValue j = js.get(js.size() - 1); + IntValue f = fs.get(fs.size() - 1); + st.add(new IntValue(context, (jumps ? j : f).concrete, bm.ifThenElse(condition, j.formula, f.formula))); + } + return true; + } + + /** A side's locals: those it wrote, else the frame's current ones. */ + private static final class LocalsView extends HashMap { + private final Frame frame; + + LocalsView(Map written, Frame frame) { + super(written); + this.frame = frame; + } + + @Override + public IntValue get(Object key) { + IntValue v = super.get(key); + return v != null ? v : asInt(frame, (Integer) key); + } + } + + private static IntValue local(Map locals, Frame frame, int slot) { + IntValue v = locals.get(slot); + return v != null ? v : asInt(frame, slot); + } + + /** The frame's local as an int value, or null if it is not one (a placeholder, a boolean). */ + private static IntValue asInt(Frame frame, int slot) { + try { + Value v = frame.getLocal(slot); + return v != null && v.getClass() == IntValue.class ? (IntValue) v : null; + } catch (Exception e) { + return null; + } + } + + /** At a join probe: gives the locals (and the stack top) of the pending diamond their merge. */ + public void atJoin(int join) { + Frame frame = stack.getActiveFrame(); + if (frame == null || frame.summaryJoinsIfAny() == null) { + return; + } + Object p = frame.summaryJoinsIfAny().remove(join); + if (!(p instanceof java.util.Deque queue)) { + return; + } + while (!queue.isEmpty()) { + apply(frame, (PendingJoin) queue.pop()); + } + } + + /** Gives the locals (and the stack top) of a diamond whose join was reached their merge. */ + private void apply(Frame frame, PendingJoin pending) { + // The execution must have computed what the summary says, or the summary is wrong here. + for (Map.Entry e : pending.locals().entrySet()) { + IntValue current = asInt(frame, e.getKey()); + if (current == null || current.concrete.intValue() != e.getValue().concrete.intValue()) { + pending.branch().setMerged(false); + return; + } + } + IntValue top = null; + if (pending.top() != null) { + Value t = frame.peek(); + if (t == null || t.getClass() != IntValue.class || ((IntValue) t).concrete.intValue() != pending.top().concrete.intValue()) { + pending.branch().setMerged(false); + return; + } + top = pending.top(); + } + for (Map.Entry e : pending.locals().entrySet()) { + frame.setLocal(e.getKey(), e.getValue()); + } + if (top != null) { + try { + frame.pop(); + frame.push(top); + } catch (Exception e) { + pending.branch().setMerged(false); + } + } + } + + // --- counting loops -------------------------------------------------------------------------- + + private void loopHead(long instIid, CountingLoop l, boolean jumped, BranchElement branch, SolverContext context, + boolean looping) { + Frame frame = stack.getActiveFrame(); + Map loops = frame.summaryLoops(); + ActiveLoop a = (ActiveLoop) loops.get(instIid); + if (a != null && !looping) { + // Not reached through the back edge: an earlier activation was left another way (an + // exception, a return), and its branches were never flagged. + a = null; + } + if (a == null) { + // Entering the loop: its head is reached for the first time since the loop was left. + IntValue i0 = asInt(frame, l.i()); + IntValue b = l.bound() >= 0 ? asInt(frame, l.bound()) : new IntValue(context, l.boundConstant()); + boolean summarize = i0 != null && b != null && (i0.isSymbolic() || b.isSymbolic()); + a = new ActiveLoop(i0, b, summarize); + if (summarize) { + for (int slot : l.steps().keySet()) { + IntValue v = asInt(frame, slot); + if (v == null) { + a = new ActiveLoop(i0, b, false); + break; + } + a.entry.put(slot, v); + } + } + if (a.summarize) { + for (int slot : l.constants().keySet()) { + IntValue v = asInt(frame, slot); + if (v == null) { + a = new ActiveLoop(i0, b, false); + break; + } + a.entry.put(slot, v); + } + } + loops.put(instIid, a); + } + if (a.summarize) { + a.branches.add(branch); // flagged only once the loop leaves through its head + } + if (jumped != l.exitOnJump()) { + return; // another iteration + } + loops.remove(instIid); + if (!a.summarize) { + return; + } + // The loop ran N = max(b - i0, 0) times (or i0 - b for a step of -1). + BitvectorFormulaManager bv = context.getFormulaManager().getBitvectorFormulaManager(); + BooleanFormulaManager bm = context.getFormulaManager().getBooleanFormulaManager(); + BitvectorFormula i0 = a.i0.formula; + BitvectorFormula b = a.bound.formula; + // With an inclusive bound the loop stops one past b: count from b + step (it never ends + // when b + step overflows, and then nothing after it runs). + BitvectorFormula one = bv.makeBitvector(BITS, l.step()); + BitvectorFormula stop = l.inclusive() ? bv.add(b, one) : b; + BooleanFormula runs = l.step() > 0 + ? (l.inclusive() ? bv.lessOrEquals(i0, b, true) : bv.lessThan(i0, b, true)) + : (l.inclusive() ? bv.greaterOrEquals(i0, b, true) : bv.greaterThan(i0, b, true)); + BitvectorFormula zero = bv.makeBitvector(BITS, 0); + BitvectorFormula n = bm.ifThenElse(runs, l.step() > 0 ? bv.subtract(stop, i0) : bv.subtract(i0, stop), zero); + int ci0 = a.i0.concrete; + int cb = a.bound.concrete; + int cstop = l.inclusive() ? cb + l.step() : cb; + boolean cruns = l.step() > 0 ? (l.inclusive() ? ci0 <= cb : ci0 < cb) : (l.inclusive() ? ci0 >= cb : ci0 > cb); + int cn = cruns ? (l.step() > 0 ? cstop - ci0 : ci0 - cstop) : 0; + Map merged = new HashMap<>(); + for (Map.Entry s : l.steps().entrySet()) { + int slot = s.getKey(); + IntValue x0 = a.entry.get(slot); + if (slot == l.i()) { + merged.put(slot, new IntValue(context, cruns ? cstop : ci0, bm.ifThenElse(runs, stop, i0))); + } else { + int d = s.getValue(); + merged.put(slot, new IntValue(context, x0.concrete + d * cn, + bv.add(x0.formula, bv.multiply(bv.makeBitvector(BITS, d), n)))); + } + } + for (Map.Entry c : l.constants().entrySet()) { + IntValue y0 = a.entry.get(c.getKey()); + merged.put(c.getKey(), new IntValue(context, cruns ? c.getValue() : y0.concrete, + bm.ifThenElse(runs, bv.makeBitvector(BITS, c.getValue()), y0.formula))); + } + for (Map.Entry e : merged.entrySet()) { + IntValue current = asInt(frame, e.getKey()); + if (current == null || current.concrete.intValue() != e.getValue().concrete.intValue()) { + return; // the summary does not match the execution: its branches stay unflagged + } + } + merged.forEach(frame::setLocal); + for (BranchElement element : a.branches) { + element.setMerged(true); + element.setMergedConditional(!l.pure()); + } + } +} diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/summary/TaintCheck.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/summary/TaintCheck.java new file mode 100644 index 00000000..c235b5eb --- /dev/null +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/summary/TaintCheck.java @@ -0,0 +1,267 @@ +package de.uzl.its.swat.symbolic.summary; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.JumpInsnNode; +import org.objectweb.asm.tree.LabelNode; +import org.objectweb.asm.tree.LookupSwitchInsnNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.TableSwitchInsnNode; +import org.objectweb.asm.tree.TryCatchBlockNode; +import org.objectweb.asm.tree.analysis.Analyzer; +import org.objectweb.asm.tree.analysis.AnalyzerException; +import org.objectweb.asm.tree.analysis.BasicInterpreter; +import org.objectweb.asm.tree.analysis.BasicValue; +import org.objectweb.asm.tree.analysis.Frame; +import org.objectweb.asm.tree.analysis.Interpreter; + +/** + * Whether merged values stay where the executor keeps them symbolic. + * + *

A merged branch's constraint is dropped: its outcome lives on only in the merged values' + * formulas. That is exact as long as the executor tracks those values symbolically wherever they + * go. Where it concretizes them -- an argument to library code (SWAT's {@code Verifier.assume} + * only looks at the concrete boolean, a {@code assume(n > 0)} being exactly such a merged + * conditional expression), a value stored to the heap or an array, an array index or size, a + * return value -- the dropped constraint would have been the only record, and a solution could + * violate it unnoticed. So a summary is only applied if, from its join (or the loop's exit) on, + * every value computed from a merged one is only used in int arithmetic, locals, comparisons and + * switches: a forward taint analysis over the method. + */ +final class TaintCheck { + + /** A value: its basic type, and whether it is computed from a merged value. */ + private static final class TV extends BasicValue { + final boolean tainted; + + TV(Type type, boolean tainted) { + super(type); + this.tainted = tainted; + } + + @Override + public boolean equals(Object o) { + return o instanceof TV t && t.tainted == tainted && super.equals(o); + } + + @Override + public int hashCode() { + return super.hashCode() * 2 + (tainted ? 1 : 0); + } + } + + /** BasicInterpreter's types plus taint; records whether a tainted value reaches a sink. */ + private static final class Taint extends Interpreter { + private final BasicInterpreter basic = new BasicInterpreter(); + boolean leaked; + + Taint() { + super(Opcodes.ASM9); + } + + private static TV of(BasicValue v, boolean tainted) { + return v == null ? null : new TV(v.getType(), tainted); + } + + @Override + public TV newValue(Type type) { + BasicValue v = basic.newValue(type); + return v == null ? null : (v == BasicValue.UNINITIALIZED_VALUE ? new TV(null, false) : of(v, false)); + } + + @Override + public TV newOperation(AbstractInsnNode insn) throws AnalyzerException { + return of(basic.newOperation(insn), false); + } + + @Override + public TV copyOperation(AbstractInsnNode insn, TV value) throws AnalyzerException { + return of(basic.copyOperation(insn, value), value.tainted); + } + + @Override + public TV unaryOperation(AbstractInsnNode insn, TV value) throws AnalyzerException { + if (value.tainted) { + switch (insn.getOpcode()) { + case Opcodes.NEWARRAY, Opcodes.ANEWARRAY, Opcodes.PUTSTATIC, Opcodes.ATHROW, + Opcodes.MONITORENTER, Opcodes.MONITOREXIT -> leaked = true; + default -> { } + } + } + return of(basic.unaryOperation(insn, value), value.tainted); + } + + @Override + public TV binaryOperation(AbstractInsnNode insn, TV v1, TV v2) throws AnalyzerException { + int op = insn.getOpcode(); + if ((op >= Opcodes.IALOAD && op <= Opcodes.SALOAD && (v1.tainted || v2.tainted)) + || (op == Opcodes.PUTFIELD && (v1.tainted || v2.tainted))) { + leaked = true; + } + return of(basic.binaryOperation(insn, v1, v2), v1.tainted || v2.tainted); + } + + @Override + public TV ternaryOperation(AbstractInsnNode insn, TV v1, TV v2, TV v3) throws AnalyzerException { + if (v1.tainted || v2.tainted || v3.tainted) { + leaked = true; // an array store + } + return of(basic.ternaryOperation(insn, v1, v2, v3), false); + } + + @Override + public TV naryOperation(AbstractInsnNode insn, List values) throws AnalyzerException { + if (values.stream().anyMatch(v -> v.tainted)) { + leaked = true; // a call (or a multi-dimensional allocation) + } + return of(basic.naryOperation(insn, values), false); + } + + @Override + public void returnOperation(AbstractInsnNode insn, TV value, TV expected) throws AnalyzerException { + if (value.tainted) { + leaked = true; + } + } + + @Override + public TV merge(TV v, TV w) { + boolean tainted = v.tainted || w.tainted; + Type type = java.util.Objects.equals(v.getType(), w.getType()) ? v.getType() : null; + if (java.util.Objects.equals(type, v.getType()) && v.tainted == tainted) { + return v; + } + return new TV(type, tainted); + } + } + + private final MethodNode mn; + private final Frame[] frames; + + private TaintCheck(MethodNode mn, Frame[] frames) { + this.mn = mn; + this.frames = frames; + } + + /** The check for a method, or null if the method cannot be analysed (then nothing is merged). */ + static TaintCheck of(String owner, MethodNode mn) { + try { + Frame[] frames = new Analyzer<>(new Taint()).analyze(owner, mn); + return new TaintCheck(mn, frames); + } catch (AnalyzerException | RuntimeException e) { + return null; + } + } + + /** + * Whether, with {@code slots} (and with {@code top}, the operand stack's top value) merged at + * {@code start}, a value computed from them may reach a place the executor concretizes. + */ + boolean leaks(LabelNode start, Set slots, boolean top) { + InsnList insns = mn.instructions; + int at = insns.indexOf(start); + Frame f0 = frames[at]; + if (f0 == null) { + return false; // unreachable + } + Frame init = new Frame<>(f0); + for (int slot : slots) { + if (slot >= init.getLocals()) { + return true; + } + TV v = init.getLocal(slot); + init.setLocal(slot, new TV(v == null ? null : v.getType(), true)); + } + if (top) { + if (init.getStackSize() == 0) { + return true; + } + TV v = init.pop(); + init.push(new TV(v.getType(), true)); + } + Taint taint = new Taint(); + Map> in = new IdentityHashMap<>(); + Deque work = new ArrayDeque<>(); + in.put(start, init); + work.add(start); + int steps = 0; + while (!work.isEmpty()) { + if (++steps > 200_000) { + return true; // give up: no summary + } + AbstractInsnNode n = work.poll(); + Frame f = new Frame<>(in.get(n)); + int op = n.getOpcode(); + try { + if (op >= 0) { + f.execute(n, taint); + } + } catch (AnalyzerException | RuntimeException e) { + return true; + } + if (taint.leaked) { + return true; + } + for (AbstractInsnNode s : successors(insns, n)) { + flow(in, work, s, f, taint); + } + // Exceptions: a handler sees the locals with the exception on the stack. + int idx = insns.indexOf(n); + for (TryCatchBlockNode tc : mn.tryCatchBlocks) { + if (insns.indexOf(tc.start) <= idx && idx < insns.indexOf(tc.end)) { + Frame h = new Frame<>(in.get(n)); + h.clearStack(); + h.push(new TV(Type.getObjectType(tc.type == null ? "java/lang/Throwable" : tc.type), false)); + flow(in, work, tc.handler, h, taint); + } + } + } + return false; + } + + private static void flow(Map> in, Deque work, + AbstractInsnNode s, Frame f, Taint taint) { + Frame old = in.get(s); + if (old == null) { + in.put(s, new Frame<>(f)); + work.add(s); + return; + } + try { + if (old.merge(f, taint)) { + work.add(s); + } + } catch (AnalyzerException | RuntimeException e) { + taint.leaked = true; + } + } + + private static List successors(InsnList insns, AbstractInsnNode n) { + int op = n.getOpcode(); + java.util.ArrayList s = new java.util.ArrayList<>(); + if (n instanceof JumpInsnNode j) { + s.add(j.label); + if (op != Opcodes.GOTO) { + s.add(n.getNext()); + } + } else if (n instanceof TableSwitchInsnNode t) { + s.add(t.dflt); + s.addAll(t.labels); + } else if (n instanceof LookupSwitchInsnNode l) { + s.add(l.dflt); + s.addAll(l.labels); + } else if (!(op >= Opcodes.IRETURN && op <= Opcodes.RETURN) && op != Opcodes.ATHROW) { + s.add(n.getNext()); + } + s.removeIf(x -> x == null); + return s; + } +} diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/trace/BranchElement.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/trace/BranchElement.java index ae0e9c5d..cdfbaa6b 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/trace/BranchElement.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/trace/BranchElement.java @@ -10,13 +10,42 @@ /** Represents a branch element in the symbolic trace. */ @Getter -final class BranchElement extends Element { +public final class BranchElement extends Element { // Indicates whether the branch was taken or not private final boolean branched; // The constraint of the branch private final BooleanFormula constraint; + // A branch that only mirrors a guard the static pre-analysis graph has, e.g. the bounds check of + // an array SWAT does not track. It keeps the explorer's walk of that graph in step, but has no + // symbolic constraint, so the explorer never tries to flip it. + private final boolean concreteOnly; + // A branch the executor adds itself to enumerate the values of a symbolic operand (e.g. which + // element of an object array a symbolic index selects). The static pre-analysis graph has no + // such branch, so the explorer flips it but does not walk the graph over it. + @lombok.Setter private boolean choice; + + // Where the branch is, for the explorer to check and resynchronise its walk of the static + // pre-analysis graph (see BranchKeys): the branch's key, or null if its instruction has none, + // and how the frame stack changed since the previous branch -- the number of frames popped, + // then the call sites (keys, null if unknown) of the frames pushed. + @lombok.Setter private String key; + @lombok.Setter private int pops; + @lombok.Setter private java.util.List pushes = java.util.List.of(); + // Recorded where the static pre-analysis graph has no counterpart: in application code that + // library code called back (see ShadowContext.inCallback), or in a SWAT helper such as the + // reference comparison (see ShadowContext.inSwatHelper). + @lombok.Setter private boolean callback; + // The loop-head states the execution reached since the previous branch, in order (see + // LoopStateMatcher). + @lombok.Setter private java.util.List loopStates = java.util.List.of(); + // Merged by a loop summary (see symbolic/summary): both outcomes lead to the same, merged state, + // so the explorer neither flips the branch nor keeps its constraint. + @lombok.Setter private boolean merged; + // The merge is only exact if what the loop does besides its locals cannot matter: the explorer + // decides that with its relevance analysis (see symbolic/summary/LoopSummaries). + @lombok.Setter private boolean mergedConditional; /** * Constructs a branch element. @@ -26,8 +55,13 @@ final class BranchElement extends Element { * @param iid The unique identifier of the branch element derived from branch-instruction */ BranchElement(boolean branched, BooleanFormula constraint, long iid) { + this(branched, constraint, iid, false); + } + + BranchElement(boolean branched, BooleanFormula constraint, long iid, boolean concreteOnly) { this.branched = branched; this.constraint = constraint; + this.concreteOnly = concreteOnly; this.setIid(iid); } @@ -35,6 +69,7 @@ final class BranchElement extends Element { private BranchElement() { this.branched = false; this.constraint = null; + this.concreteOnly = false; } /** diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/trace/DTOBuilder.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/trace/DTOBuilder.java index 8fa442ea..631c599c 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/trace/DTOBuilder.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/trace/DTOBuilder.java @@ -8,15 +8,25 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import de.uzl.its.swat.common.ErrorHandler; +import de.uzl.its.swat.common.StackOverflowGuard; import de.uzl.its.swat.common.exceptions.NoThreadContextException; import de.uzl.its.swat.common.exceptions.NotImplementedException; import de.uzl.its.swat.common.logging.GlobalLogger; +import de.uzl.its.swat.common.logging.StatsStorage; +import de.uzl.its.swat.common.logging.records.InvocationEntry; +import de.uzl.its.swat.config.Config; +import de.uzl.its.swat.solver.SolverMode; import de.uzl.its.swat.coverage.InstrCoverage; import de.uzl.its.swat.symbolic.trace.dto.*; import de.uzl.its.swat.thread.ThreadHandler; +import de.uzl.its.swat.witness.Witness; +import de.uzl.its.swat.symbolic.value.reference.lang.StringValue; import java.util.ArrayList; import java.util.HashSet; +import java.util.Set; import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; import org.sosy_lab.java_smt.api.BooleanFormula; import org.sosy_lab.java_smt.api.Formula; @@ -28,14 +38,42 @@ */ class DTOBuilder { private static final Logger logger = GlobalLogger.getSymbolicExecutionLogger(); + private static final Pattern INPUT_NAME = Pattern.compile("[A-Z].*_[0-9].*"); /** * Encodes the symbolic trace as a JSON string. * * @param symbolicTrace The symbolic trace to be encoded. + * @param statsStorage The per-execution statistics of the thread. + * @param threadId The thread whose solver context the formulas belong to. * @return The symbolic trace encoded as a JSON string. */ - protected static String encodeTrace(SymbolicTrace symbolicTrace) throws NoThreadContextException, JsonProcessingException, NotImplementedException { - return buildRequestBody(buildTraceDTO(symbolicTrace)); + protected static String encodeTrace(SymbolicTrace symbolicTrace, StatsStorage statsStorage, long threadId) throws NoThreadContextException, JsonProcessingException, NotImplementedException { + return buildRequestBody(buildTraceDTO(symbolicTrace, statsStorage, threadId)); + } + + /** + * Builds the list of missing-invocation DTOs from the per-execution statistics. The full set of + * missing invocations is the superset; entries present in the context-loss set are flagged so + * the explorer can compute the context-loss subset without log scraping. + * + * @param statsStorage The per-execution statistics accumulator. + * @return The missing invocations encoded as DTOs. + */ + private static ArrayList buildMissingInvocations(StatsStorage statsStorage) { + ArrayList missingInvocations = new ArrayList<>(); + for (Map.Entry e : statsStorage.getInvocations().entrySet()) { + InvocationEntry entry = e.getKey(); + missingInvocations.add( + new InvocationDTO( + entry.owner(), + entry.name(), + entry.desc(), + entry.isInstance(), + entry.isSymbolic(), + statsStorage.getContextLossInvocations().contains(entry), + e.getValue())); + } + return missingInvocations; } /** @@ -46,16 +84,17 @@ protected static String encodeTrace(SymbolicTrace symbolicTrace) throws NoThread * @return A {@link TraceDTO ConstraintDTO} that contains relevant all relevant information * to transfer symbolic traces */ - private static TraceDTO buildTraceDTO(SymbolicTrace symbolicTrace) throws NoThreadContextException, NotImplementedException { + private static TraceDTO buildTraceDTO(SymbolicTrace symbolicTrace, StatsStorage statsStorage, long threadId) throws NoThreadContextException, NotImplementedException { ArrayList inputs = new ArrayList<>(); ArrayList ufs = new ArrayList<>(); ArrayList trace = new ArrayList<>(); FormulaManager fmgr = - ThreadHandler.getSolverContext(currentThread().getId()).getFormulaManager(); + ThreadHandler.getSolverContext(threadId).getFormulaManager(); + FormulaDumper dumper = FormulaDumper.of(fmgr); for (InputElement ie : symbolicTrace.getInputs()) { - String lowerBound = String.valueOf(fmgr.dumpFormula(ie.getValue().getBounds(false))); - String upperBound = String.valueOf(fmgr.dumpFormula(ie.getValue().getBounds(true))); + String lowerBound = dumper.dump(ie.getValue().getBounds(false)); + String upperBound = dumper.dump(ie.getValue().getBounds(true)); InputDTO iDto = new InputDTO( ie.getName(), @@ -66,11 +105,13 @@ private static TraceDTO buildTraceDTO(SymbolicTrace symbolicTrace) throws NoThre inputs.add(iDto); } for (BooleanFormula uf : symbolicTrace.getConstraints()) { - String formula = String.valueOf(fmgr.dumpFormula(uf)); + String formula = dumper.dump(uf); ufs.add(new UFDTO(formula)); } logger.trace("Parsing constraints..."); - boolean symbolicPrecisionLoss = false; + // A constraint over an approximate model function, or over a symbol no model declared, + // means the encoding may be imprecise (see SymbolRegistry). + boolean symbolicPrecisionLoss = symbolicTrace.isSymbolicPrecisionLoss(); for (Element el : symbolicTrace.getTrace()) { if (el instanceof BranchElement be) { String constraint; @@ -80,33 +121,52 @@ private static TraceDTO buildTraceDTO(SymbolicTrace symbolicTrace) throws NoThre if(fmgr.getBooleanFormulaManager().isTrue(f) || fmgr.getBooleanFormulaManager().isFalse(f)){ constraint = "(assert true)"; } else { - constraint = String.valueOf(fmgr.dumpFormula(f)); - if (!fmgr.extractVariablesAndUFs(f).isEmpty() && - !fmgr.extractVariablesAndUFs(f).keySet().stream().allMatch(s -> s.matches("[A-Z].*_[0-9].*"))) { - symbolicPrecisionLoss = true; - } + constraint = dumper.dump(f); + symbolicPrecisionLoss |= hasImpreciseSymbol(fmgr, f); } - //fmgr.extractVariablesAndUFs(f).keySet().forEach(System.out::println); - // assert fmgr.extractVariablesAndUFs(f).keySet().stream() - // .allMatch(s -> s.matches("[A-Z].*_[0-9].*")): "[SWAT] UF introduced in: " + constraint; - } catch (InterruptedException e) { BooleanFormula f = be.getConstraint(); logger.warn("Error while simplifying formula", e); - constraint = String.valueOf(fmgr.dumpFormula(f)); - //fmgr.extractVariablesAndUFs(f).keySet().forEach(System.out::println); - // assert fmgr.extractVariablesAndUFs(f).keySet().stream() - // .allMatch(s -> s.matches("[A-Z].*_[0-9].*")): "[SWAT] UF introduced in: " + constraint; - if (!fmgr.extractVariablesAndUFs(f).keySet().stream().allMatch(s -> s.matches("[A-Z].*_[0-9].*"))) { - symbolicPrecisionLoss = true; - } + constraint = dumper.dump(f); + symbolicPrecisionLoss |= hasImpreciseSymbol(fmgr, f); } - trace.add(new BranchDTO(be.getIid(), constraint, be.isBranched())); + trace.add(new BranchDTO(be.getIid(), constraint, be.isBranched(), be.isConcreteOnly()) + .place(be.getKey(), be.getPops(), be.getPushes(), be.isCallback()) + .choice(be.isChoice()) + .merged(be.isMerged()) + .mergedConditional(be.isMergedConditional()) + .loopStates(be.getLoopStates())); } else if (el instanceof SpecialElement se) { trace.add(new BranchDTO(se.getIid(), se.getInst())); } } - return new TraceDTO(inputs, trace, ufs, symbolicTrace.isSymbolicContextLoss(), symbolicPrecisionLoss, symbolicTrace.isReferenceSemanticChange()); + // Symbolic tracking stopped at a stack overflow: the rest of the execution is not in the trace + boolean stackOverflow = StackOverflowGuard.isOverflowed(); + if (stackOverflow) { + logger.warn("Stack overflow during the execution (in SWAT: {}, reached the target: {}): recording symbolic context loss", + StackOverflowGuard.trackingLost, StackOverflowGuard.targetOverflowed); + } + return new TraceDTO(inputs, trace, ufs, buildMissingInvocations(statsStorage), Witness.getNondetReturns(), Witness.getViolation(), symbolicTrace.isSymbolicContextLoss() || stackOverflow, symbolicPrecisionLoss, symbolicTrace.isReferenceSemanticChange(), + symbolicTrace.isSymbolicPrecisionLoss(), StackOverflowGuard.targetOverflowed); + } + + /** Symbols already reported as undeclared, so each is logged once. */ + private static final Set reportedUndeclared = java.util.concurrent.ConcurrentHashMap.newKeySet(); + + /** + * Whether the formula mentions a symbol whose constraints are not encoded precisely: an + * approximate model function, or a symbol that was never declared in the {@link SymbolRegistry}. + */ + static boolean hasImpreciseSymbol(FormulaManager fmgr, Formula f) { + boolean imprecise = false; + for (String name : fmgr.extractVariablesAndUFs(f).keySet()) { + if (SymbolRegistry.isPrecise(name)) continue; + if (SymbolRegistry.kindOf(name) == null && reportedUndeclared.add(name)) { + logger.warn("Undeclared symbol {} in a branch constraint; treating it as imprecise", name); + } + imprecise = true; + } + return imprecise; } protected static String encodeCoverage(InstrCoverage instrCoverage) throws JsonProcessingException { @@ -136,6 +196,10 @@ private static String buildRequestBody(Object payload) throws JsonProcessingExce ObjectMapper objectMapper = new ObjectMapper(); objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); - return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(payload); + // Indented only where a person reads it (PRINT); the explorer gets the compact form. + if (Config.instance().getSolverMode() == SolverMode.PRINT) { + return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(payload); + } + return objectMapper.writeValueAsString(payload); } } diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/trace/FormulaDumper.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/trace/FormulaDumper.java new file mode 100644 index 00000000..51204d84 --- /dev/null +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/trace/FormulaDumper.java @@ -0,0 +1,103 @@ +package de.uzl.its.swat.symbolic.trace; + +import ch.qos.logback.classic.Logger; +import com.microsoft.z3.Native; +import de.uzl.its.swat.common.logging.GlobalLogger; +import java.lang.reflect.Method; +import org.sosy_lab.java_smt.api.BooleanFormula; +import org.sosy_lab.java_smt.api.Formula; +import org.sosy_lab.java_smt.api.FormulaManager; +import org.sosy_lab.java_smt.basicimpl.AbstractFormulaManager; +import org.sosy_lab.java_smt.basicimpl.FormulaCreator; + +/** + * Dumps formulas as SMT-LIB like {@link FormulaManager#dumpFormula}, with the same output, but + * faster for many formulas. For Z3, java-smt creates a new solver for every formula, asserts the + * formula in it and prints the solver, and initialising that solver costs more than printing a + * small formula. This dumper keeps one solver and asserts each formula in its own scope. + * + *

The Z3 handles are only reachable through protected java-smt methods, so they are looked up + * reflectively; if that fails (another java-smt version or solver), every formula goes through + * {@link FormulaManager#dumpFormula} instead. + * + *

The dumper of the last formula manager is kept: there is one per solver context, and one + * context per thread. Its solver lives as long as the context. + */ +final class FormulaDumper { + private static final Logger logger = GlobalLogger.getSymbolicExecutionLogger(); + + private final FormulaManager fmgr; + private final FormulaCreator creator; + private final Method extractInfo; + private final Method sanitize; + private final long context; + private final long solver; + + private FormulaDumper( + FormulaManager fmgr, + FormulaCreator creator, + Method extractInfo, + Method sanitize, + long context, + long solver) { + this.fmgr = fmgr; + this.creator = creator; + this.extractInfo = extractInfo; + this.sanitize = sanitize; + this.context = context; + this.solver = solver; + } + + private static volatile FormulaDumper last; + + /** The dumper for the formulas of the given manager. */ + static FormulaDumper of(FormulaManager fmgr) { + FormulaDumper dumper = last; + if (dumper == null || dumper.fmgr != fmgr) { + dumper = create(fmgr); + last = dumper; + } + return dumper; + } + + /** A dumper for the formulas of the given manager; one that falls back if Z3 is not reachable. */ + private static FormulaDumper create(FormulaManager fmgr) { + try { + if (fmgr instanceof AbstractFormulaManager afm + && afm.getFormulaCreator().getEnv() instanceof Long context + && afm.getClass().getSimpleName().equals("Z3FormulaManager")) { + Method extractInfo = FormulaCreator.class.getDeclaredMethod("extractInfo", Formula.class); + extractInfo.setAccessible(true); + Method sanitize = AbstractFormulaManager.class.getDeclaredMethod("sanitize", String.class); + sanitize.setAccessible(true); + long solver = Native.mkSolver(context); + Native.solverIncRef(context, solver); + return new FormulaDumper(fmgr, afm.getFormulaCreator(), extractInfo, sanitize, context, solver); + } + } catch (ReflectiveOperationException | RuntimeException e) { + logger.debug("Dumping formulas through java-smt: {}", e.toString()); + } + return new FormulaDumper(fmgr, null, null, null, 0, 0); + } + + /** The formula as SMT-LIB: its declarations and one assert, as {@link FormulaManager#dumpFormula}. */ + String dump(BooleanFormula f) { + if (creator == null) { + return String.valueOf(fmgr.dumpFormula(f)); + } + try { + long expr = (Long) extractInfo.invoke(creator, f); + String dumped; + Native.solverPush(context, solver); + try { + Native.solverAssert(context, solver, expr); + dumped = Native.solverToString(context, solver); + } finally { + Native.solverPop(context, solver, 1); + } + return (String) sanitize.invoke(fmgr, dumped); + } catch (ReflectiveOperationException e) { + return String.valueOf(fmgr.dumpFormula(f)); + } + } +} diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/trace/SymbolRegistry.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/trace/SymbolRegistry.java new file mode 100644 index 00000000..4191fdd1 --- /dev/null +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/trace/SymbolRegistry.java @@ -0,0 +1,57 @@ +package de.uzl.its.swat.symbolic.trace; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Records, where a symbol is declared, what kind of symbol it is. The {@link DTOBuilder} uses it + * to decide whether a branch constraint is encoded precisely: a constraint over a symbol that is + * approximate, or that was never declared here, is a precision loss. + * + *

A precise symbol may still be under-constrained (a fresh value, an array base outside the + * stored range, a partially defined model function). That only over-approximates: an UNSAT query + * stays a proof, and a guessed model that does not reach its branch is caught as a divergence. + * A model whose constraints may exclude real behaviour must be declared {@link + * Kind#APPROXIMATE_MODEL_FUNCTION}, and must also call {@link + * SymbolicTraceHandler#recordSymbolicPrecisionLoss()} where it adds such a constraint, since the + * constraint reaches every query whether or not a branch mentions the function. + */ +public final class SymbolRegistry { + + public enum Kind { + /** A Verifier input, solved for by the explorer. */ + INPUT, + /** A symbolic value of the program that is not an input (e.g. a havocked field). */ + VALUE, + /** The base symbol of an allocated array, overwritten by its initial contents. */ + ARRAY_BASE, + /** A model function whose definition holds for every argument. */ + MODEL_FUNCTION, + /** A model function whose encoding may exclude real behaviour. */ + APPROXIMATE_MODEL_FUNCTION + } + + private static final Map kinds = new ConcurrentHashMap<>(); + + private SymbolRegistry() {} + + /** + * Declares the kind of a symbol. An input stays an input; an approximate model function stays + * approximate. + */ + public static void declare(String name, Kind kind) { + kinds.merge(name, kind, (old, k) -> + old == Kind.INPUT || old == Kind.APPROXIMATE_MODEL_FUNCTION ? old : k); + } + + /** Returns the kind of the symbol, or null if it was never declared. */ + public static Kind kindOf(String name) { + return kinds.get(name); + } + + /** Whether constraints over the symbol are encoded precisely (see the class comment). */ + public static boolean isPrecise(String name) { + Kind kind = kinds.get(name); + return kind != null && kind != Kind.APPROXIMATE_MODEL_FUNCTION; + } +} diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/trace/SymbolicTrace.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/trace/SymbolicTrace.java index 4e980f4f..756c2e69 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/trace/SymbolicTrace.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/trace/SymbolicTrace.java @@ -25,6 +25,12 @@ class SymbolicTrace { // If true, an invocation occurred that was not instrumented but received symbolic arguments. private boolean symbolicContextLoss = false; + // If true, a model was used that is only approximate, e.g. object array elements tracked at the + // concrete index only. Branch constraints with UFs (which include the sampled Math functions) + // are detected as precision loss separately. Also sent on its own (TraceDTO.approximateModel), + // so the explorer can expect divergences from such traces. + private boolean symbolicPrecisionLoss = false; + // If true, reference equality semantics may have changed due to comparing user-de-interned strings. private boolean referenceSemanticChange = false; diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/trace/SymbolicTraceHandler.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/trace/SymbolicTraceHandler.java index 6d99da5f..4946d93f 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/trace/SymbolicTraceHandler.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/trace/SymbolicTraceHandler.java @@ -6,6 +6,10 @@ import de.uzl.its.swat.common.exceptions.NoThreadContextException; import de.uzl.its.swat.common.exceptions.NotImplementedException; import de.uzl.its.swat.common.logging.GlobalLogger; +import de.uzl.its.swat.common.logging.StatsStorage; +import de.uzl.its.swat.instrument.BranchKeys; +import de.uzl.its.swat.symbolic.shadow.Frame; +import de.uzl.its.swat.symbolic.shadow.ShadowContext; import de.uzl.its.swat.symbolic.value.Value; import de.uzl.its.swat.thread.ThreadHandler; import java.util.ArrayList; @@ -13,6 +17,7 @@ import java.util.List; import org.sosy_lab.java_smt.api.BooleanFormula; +import org.sosy_lab.java_smt.api.BooleanFormulaManager; import org.sosy_lab.java_smt.api.FormulaManager; /** @@ -38,7 +43,16 @@ public SymbolicTraceHandler() { * @param constraint The constraint of the branch. * @param iid The iid of the branch instruction. */ - public void checkAndSetBranch(boolean result, BooleanFormula constraint, long iid) throws NoThreadContextException { + public BranchElement checkAndSetBranch(boolean result, BooleanFormula constraint, long iid) throws NoThreadContextException { + return checkAndSetBranch(result, constraint, iid, ""); + } + + /** + * As {@link #checkAndSetBranch(boolean, BooleanFormula, long)}, for one of several branches an + * instruction reports: {@code suffix} tells them apart in the branch's key ({@code #} for a switch case, {@code #} for the n-th dimension MULTIANEWARRAY checks). + */ + public BranchElement checkAndSetBranch(boolean result, BooleanFormula constraint, long iid, String suffix) throws NoThreadContextException { long threadId = Thread.currentThread().getId(); BranchElement current; @@ -49,7 +63,95 @@ public void checkAndSetBranch(boolean result, BooleanFormula constraint, long ii current = new BranchElement(result, pathConstraint, iid); // check if the path constraint contains a free/ symbolic variable + place(current, iid, suffix); + takeLoopStates(current); symbolicTrace.addTraceElement(current); + return current; + } + + /** + * Records a branch the executor adds itself to enumerate the values of a symbolic operand, e.g. + * one step of the search for the element a symbolic array index selects. It is a branch like + * any other for the explorer, which flips it to try the other values, but the static + * pre-analysis graph has no counterpart for it. + * + * @param result Whether {@code constraint} holds on this execution + * @param constraint The condition branched on + * @param iid The iid of the instruction the operand belongs to + * @param suffix Tells the instruction's branches apart in their keys + */ + public void addChoiceBranch(boolean result, BooleanFormula constraint, long iid, String suffix) throws NoThreadContextException { + FormulaManager fmgr = ThreadHandler.getSolverContext(Thread.currentThread().getId()).getFormulaManager(); + BranchElement branch = new BranchElement(result, result ? constraint : fmgr.getBooleanFormulaManager().not(constraint), iid); + branch.setChoice(true); + place(branch, iid, suffix); + takeLoopStates(branch); + symbolicTrace.addTraceElement(branch); + } + + /** + * As {@link #checkAndSetBranch(boolean, BooleanFormula, long, String)}, for a branch inside a + * library call that SWAT models (e.g. the index check of String.charAt). The static pre-analysis + * graph walks over library calls, so the branch is marked as one in a callback: the explorer's + * walk of the graph holds its place, as for application code a library calls back. + */ + public void checkAndSetLibraryBranch(boolean result, BooleanFormula constraint, long iid, String suffix) throws NoThreadContextException { + BooleanFormulaManager bmgr = ThreadHandler.getSolverContext(Thread.currentThread().getId()) + .getFormulaManager().getBooleanFormulaManager(); + BranchElement branch = new BranchElement(result, result ? constraint : bmgr.not(constraint), iid); + place(branch, iid, suffix); + branch.setCallback(true); + takeLoopStates(branch); + symbolicTrace.addTraceElement(branch); + } + + /** The loop-head states reached since the last branch (see LoopStateMatcher). */ + private final List loopStates = new ArrayList<>(); + + /** Records the state at a loop head; it is reported with the next branch. */ + public void addLoopState(String state) { + loopStates.add(state); + } + + private void takeLoopStates(BranchElement branch) { + if (!loopStates.isEmpty()) { + branch.setLoopStates(List.copyOf(loopStates)); + loopStates.clear(); + } + } + + /** Records where a branch is: its key and the change of the frame stack since the last branch. */ + private static void place(BranchElement branch, long iid, String suffix) throws NoThreadContextException { + String key = BranchKeys.keyOf(iid); + branch.setKey(key == null ? null : key + suffix); + ShadowContext stack = ThreadHandler.getSymbolicVisitor(Thread.currentThread().getId()).getStack(); + // Neither callbacks nor SWAT's helpers are in the static pre-analysis graph: the walk holds its place. + branch.setCallback(stack.inCallback() || stack.inSwatHelper()); + ShadowContext.StackDelta delta = stack.takeStackDelta(); + branch.setPops(delta.popped()); + List pushes = new ArrayList<>(delta.pushed().size()); + for (Frame frame : delta.pushed()) { + pushes.add(frame.getCallSite() < 0 ? null : BranchKeys.keyOf(frame.getCallSite())); + } + branch.setPushes(pushes); + } + + /** + * Records a branch without a symbolic constraint, whose outcome is known only concretely. Used + * where the static pre-analysis graph has a branch that SWAT has no symbolic value for (e.g. the + * bounds check of an untracked array): the explorer walks that graph in step with the trace, by + * position, so the branch must be in the trace, but it is never a target for the solver. + * + * @param result Whether the branch was taken + * @param iid The iid of the branching instruction + */ + public void addConcreteBranch(boolean result, long iid) throws NoThreadContextException { + BooleanFormulaManager bmgr = ThreadHandler.getSolverContext(Thread.currentThread().getId()) + .getFormulaManager().getBooleanFormulaManager(); + BranchElement branch = new BranchElement(result, bmgr.makeTrue(), iid, true); + place(branch, iid, ""); + takeLoopStates(branch); + symbolicTrace.addTraceElement(branch); } /** @@ -82,6 +184,7 @@ public void recordInvocation(long iid, String inst) { public void addInput(String name, Value value) { logger.info("Input with name: {} and value: {} registered", name, value); symbolicTrace.addInputElement(new InputElement(name, value)); + SymbolRegistry.declare(name, SymbolRegistry.Kind.INPUT); } /** @@ -108,8 +211,16 @@ public List getConstraints() { * * @return The symbolic trace encoded as a JSON string. */ - public String getTraceDTO() throws NoThreadContextException, JsonProcessingException, NotImplementedException { - return DTOBuilder.encodeTrace(symbolicTrace); + public String getTraceDTO(StatsStorage statsStorage) throws NoThreadContextException, JsonProcessingException, NotImplementedException { + return DTOBuilder.encodeTrace(symbolicTrace, statsStorage, Thread.currentThread().getId()); + } + + /** + * Encodes the trace with the solver context of the given thread, for callers running on + * another thread (the shutdown hook sending the trace of the target's main thread). + */ + public String getTraceDTO(StatsStorage statsStorage, long threadId) throws NoThreadContextException, JsonProcessingException, NotImplementedException { + return DTOBuilder.encodeTrace(symbolicTrace, statsStorage, threadId); } /** @@ -207,6 +318,13 @@ public void recordSymbolicContextLoss() { symbolicTrace.setSymbolicContextLoss(true); } + /** + * Records that a symbolic model was only approximate, so an exhausted search proves nothing. + */ + public void recordSymbolicPrecisionLoss() { + symbolicTrace.setSymbolicPrecisionLoss(true); + } + /** * Records that reference equality semantics may have changed. This happens when Objects.equals * is called (via refEquals transformation) on strings where at least one was explicitly created diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/trace/dto/BranchDTO.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/trace/dto/BranchDTO.java index e26780cd..39c2c5bd 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/trace/dto/BranchDTO.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/trace/dto/BranchDTO.java @@ -5,6 +5,7 @@ public class BranchDTO { private final long iid; @SuppressWarnings("unused") + @com.fasterxml.jackson.annotation.JsonInclude(com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL) private String constraint; @SuppressWarnings("unused") @@ -14,12 +15,83 @@ public class BranchDTO { private final String type; @SuppressWarnings("unused") + @com.fasterxml.jackson.annotation.JsonInclude(com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL) private String inst; + @SuppressWarnings("unused") + @com.fasterxml.jackson.annotation.JsonInclude(com.fasterxml.jackson.annotation.JsonInclude.Include.NON_DEFAULT) + private boolean concreteOnly; + // See BranchElement: the branch's key and the frame stack change since the previous branch. + @SuppressWarnings("unused") + @com.fasterxml.jackson.annotation.JsonInclude(com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL) + private String key; + @SuppressWarnings("unused") + @com.fasterxml.jackson.annotation.JsonInclude(com.fasterxml.jackson.annotation.JsonInclude.Include.NON_DEFAULT) + private int pops; + @SuppressWarnings("unused") + @com.fasterxml.jackson.annotation.JsonInclude(com.fasterxml.jackson.annotation.JsonInclude.Include.NON_EMPTY) + private java.util.List pushes; + + @SuppressWarnings("unused") + @com.fasterxml.jackson.annotation.JsonInclude(com.fasterxml.jackson.annotation.JsonInclude.Include.NON_DEFAULT) + private boolean callback; + + // See BranchElement: a branch the executor adds that the static pre-analysis graph does not have. + @SuppressWarnings("unused") + @com.fasterxml.jackson.annotation.JsonInclude(com.fasterxml.jackson.annotation.JsonInclude.Include.NON_DEFAULT) + private boolean choice; + + public BranchDTO choice(boolean choice) { + this.choice = choice; + return this; + } + + // See BranchElement: merged by a loop summary. + @SuppressWarnings("unused") + @com.fasterxml.jackson.annotation.JsonInclude(com.fasterxml.jackson.annotation.JsonInclude.Include.NON_DEFAULT) + private boolean merged; + + public BranchDTO merged(boolean merged) { + this.merged = merged; + return this; + } + + // See BranchElement: merged only if the explorer finds the loop's other effects irrelevant. + @SuppressWarnings("unused") + @com.fasterxml.jackson.annotation.JsonInclude(com.fasterxml.jackson.annotation.JsonInclude.Include.NON_DEFAULT) + private boolean mergedConditional; + + public BranchDTO mergedConditional(boolean mergedConditional) { + this.mergedConditional = mergedConditional; + return this; + } + + // See BranchElement: the loop-head states reached since the previous branch. + @SuppressWarnings("unused") + @com.fasterxml.jackson.annotation.JsonInclude(com.fasterxml.jackson.annotation.JsonInclude.Include.NON_EMPTY) + private java.util.List loopStates; + + public BranchDTO loopStates(java.util.List loopStates) { + this.loopStates = loopStates; + return this; + } + + public BranchDTO place(String key, int pops, java.util.List pushes, boolean callback) { + this.key = key; + this.pops = pops; + this.pushes = pushes; + this.callback = callback; + return this; + } public BranchDTO(long iid, String constraint, boolean branched) { + this(iid, constraint, branched, false); + } + + public BranchDTO(long iid, String constraint, boolean branched, boolean concreteOnly) { this.iid = iid; this.constraint = constraint; this.branched = branched; + this.concreteOnly = concreteOnly; this.type = "Branch"; } diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/trace/dto/FrameDTO.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/trace/dto/FrameDTO.java new file mode 100644 index 00000000..57f9572c --- /dev/null +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/trace/dto/FrameDTO.java @@ -0,0 +1,38 @@ +package de.uzl.its.swat.symbolic.trace.dto; + +/** One stack frame of the throwable that ended the execution (see {@link ViolationDTO}). */ +public class FrameDTO { + // The declaring class of the frame (dotted name) + private String className; + // The source file of the frame, empty if unknown + private String sourceFile; + // The source line of the frame, -1 if unknown + private int line; + + public FrameDTO(String className, String sourceFile, int line) { + this.className = className; + this.sourceFile = sourceFile; + this.line = line; + } + + /** Private default constructor for serialization */ + @SuppressWarnings("unused") + private FrameDTO() {} + + public String getClassName() { + return className; + } + + public String getSourceFile() { + return sourceFile; + } + + public int getLine() { + return line; + } + + @Override + public String toString() { + return className + "(" + sourceFile + ":" + line + ")"; + } +} diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/trace/dto/InvocationDTO.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/trace/dto/InvocationDTO.java new file mode 100644 index 00000000..1ecef897 --- /dev/null +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/trace/dto/InvocationDTO.java @@ -0,0 +1,55 @@ +package de.uzl.its.swat.symbolic.trace.dto; + +/** + * Describes a single method invocation that could not be modelled symbolically during execution + * (i.e. it returned a {@link de.uzl.its.swat.symbolic.value.PlaceHolder}). These are collected per + * execution and shipped to the Symbolic Explorer as part of the trace so that the explorer owns the + * consolidated per-testcase statistics instead of relying on log/stats-file scraping. + * + *

{@code contextLoss} marks the dangerous subset: invocations that received symbolic arguments + * and therefore caused symbolic context loss (see {@code InvocationHandler}). Missing invocations + * are the superset; context-loss invocations are the subset. + */ +public class InvocationDTO { + @SuppressWarnings("unused") + private String owner; + + @SuppressWarnings("unused") + private String name; + + @SuppressWarnings("unused") + private String desc; + + @SuppressWarnings("unused") + private boolean isInstance; + + @SuppressWarnings("unused") + private boolean isSymbolic; + + @SuppressWarnings("unused") + private boolean contextLoss; + + @SuppressWarnings("unused") + private int count; + + public InvocationDTO( + String owner, + String name, + String desc, + boolean isInstance, + boolean isSymbolic, + boolean contextLoss, + int count) { + this.owner = owner; + this.name = name; + this.desc = desc; + this.isInstance = isInstance; + this.isSymbolic = isSymbolic; + this.contextLoss = contextLoss; + this.count = count; + } + + /** Private default constructor for serialization */ + @SuppressWarnings("unused") + private InvocationDTO() {} +} diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/trace/dto/NondetReturnDTO.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/trace/dto/NondetReturnDTO.java new file mode 100644 index 00000000..aa7928e5 --- /dev/null +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/trace/dto/NondetReturnDTO.java @@ -0,0 +1,111 @@ +package de.uzl.its.swat.symbolic.trace.dto; + +/** + * A value returned by a {@code Verifier.nondet*()} call together with the location of the call. + * The explorer turns the sequence of these into the {@code function_return} waypoints of an + * SV-COMP violation witness. + */ +public class NondetReturnDTO { + // The Verifier method name, e.g. nondetInt + private String function; + // The Java type of the value: boolean|byte|short|char|int|long|float|double|String + private String type; + // The value in its witness string form: integers decimal, char as its code point, float/double + // as Java's Float/Double.toString, boolean true/false, String the raw string. + private String value; + // Exact IEEE-754 bits of a float/double value as 0x-prefixed hex, null for other types + private String bits; + // The class containing the call (dotted name) + private String className; + // The method containing the call as name + descriptor, e.g. main([Ljava/lang/String;)V + private String method; + // The SourceFile attribute of that class, empty if unknown + private String sourceFile; + // The source line of the call + private int line; + // 0-based index among the nondet calls on that source line of that class (evaluation order) + private int callIndex; + + public NondetReturnDTO( + String function, + String type, + String value, + String bits, + String className, + String method, + String sourceFile, + int line, + int callIndex) { + this.function = function; + this.type = type; + this.value = value; + this.bits = bits; + this.className = className; + this.method = method; + this.sourceFile = sourceFile; + this.line = line; + this.callIndex = callIndex; + } + + /** Private default constructor for serialization */ + @SuppressWarnings("unused") + private NondetReturnDTO() {} + + public String getFunction() { + return function; + } + + public String getType() { + return type; + } + + public String getValue() { + return value; + } + + public String getBits() { + return bits; + } + + public String getClassName() { + return className; + } + + public String getMethod() { + return method; + } + + public String getSourceFile() { + return sourceFile; + } + + public int getLine() { + return line; + } + + public int getCallIndex() { + return callIndex; + } + + @Override + public String toString() { + return "NondetReturnDTO{" + + function + + " -> " + + type + + " " + + value + + (bits == null ? "" : " (" + bits + ")") + + " at " + + className + + "." + + method + + "(" + + sourceFile + + ":" + + line + + ")#" + + callIndex + + '}'; + } +} diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/trace/dto/TraceDTO.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/trace/dto/TraceDTO.java index 2f8b3946..04b6b48f 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/trace/dto/TraceDTO.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/trace/dto/TraceDTO.java @@ -12,20 +12,48 @@ public class TraceDTO { @SuppressWarnings("unused") private ArrayList ufs; + // The methods encountered during execution that could not be modelled symbolically. The + // superset of all missing invocations; entries with contextLoss=true are the dangerous subset. + @SuppressWarnings("unused") + private ArrayList missingInvocations; + + // The values returned by Verifier.nondet*() calls with their call locations (witness data) + @SuppressWarnings("unused") + private ArrayList nondetReturns; + + // The throwable that escaped the entry method, null if the execution ended normally + @SuppressWarnings("unused") + private ViolationDTO violation; + @SuppressWarnings("unused") private boolean symbolicContextLoss = false; @SuppressWarnings("unused") private boolean symbolicPrecisionLoss = false; @SuppressWarnings("unused") private boolean referenceSemanticChange = false; + // Whether an approximate model was used that is precision loss wherever it is used (e.g. object + // array elements at the concrete index). Unlike symbolicPrecisionLoss, which also covers exact + // UF-based models, solutions from such a trace are only guesses. Sampled Math functions are + // recognised per query instead, by their function names. + @SuppressWarnings("unused") + private boolean approximateModel = false; + // A StackOverflowError reached the target's code: the path depends on the JVM's stack size, + // so a violation of this execution is not a verdict (see StackOverflowGuard) + @SuppressWarnings("unused") + private boolean stackOverflow = false; - public TraceDTO(ArrayList inputs, ArrayList trace, ArrayList ufs, boolean symbolicContextLoss, boolean symbolicPrecisionLoss, boolean referenceSemanticChange) { + public TraceDTO(ArrayList inputs, ArrayList trace, ArrayList ufs, ArrayList missingInvocations, ArrayList nondetReturns, ViolationDTO violation, boolean symbolicContextLoss, boolean symbolicPrecisionLoss, boolean referenceSemanticChange, boolean approximateModel, boolean stackOverflow) { this.trace = trace; this.inputs = inputs; this.ufs = ufs; + this.missingInvocations = missingInvocations; + this.nondetReturns = nondetReturns; + this.violation = violation; this.symbolicContextLoss = symbolicContextLoss; this.symbolicPrecisionLoss = symbolicPrecisionLoss; this.referenceSemanticChange = referenceSemanticChange; + this.approximateModel = approximateModel; + this.stackOverflow = stackOverflow; } diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/trace/dto/ViolationDTO.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/trace/dto/ViolationDTO.java new file mode 100644 index 00000000..f9afd782 --- /dev/null +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/trace/dto/ViolationDTO.java @@ -0,0 +1,37 @@ +package de.uzl.its.swat.symbolic.trace.dto; + +import java.util.List; + +/** + * The throwable that escaped the entry method, recorded by the surrounding try/catch of main. The + * explorer derives the target waypoint of a violation witness from the innermost frame that lies + * in the task sources. + */ +public class ViolationDTO { + // The class name of the throwable, e.g. java.lang.AssertionError + private String throwable; + // The stack frames, innermost first; JDK and SWAT frames are left out + private List frames; + + public ViolationDTO(String throwable, List frames) { + this.throwable = throwable; + this.frames = frames; + } + + /** Private default constructor for serialization */ + @SuppressWarnings("unused") + private ViolationDTO() {} + + public String getThrowable() { + return throwable; + } + + public List getFrames() { + return frames; + } + + @Override + public String toString() { + return "ViolationDTO{" + throwable + " " + frames + '}'; + } +} diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/ContainerPlaceHolder.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/ContainerPlaceHolder.java new file mode 100644 index 00000000..258d66f9 --- /dev/null +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/ContainerPlaceHolder.java @@ -0,0 +1,50 @@ +package de.uzl.its.swat.symbolic.value; + +import de.uzl.its.swat.common.logging.records.InvocationEntry; +import de.uzl.its.swat.symbolic.value.reference.ContainerContents; +import java.util.List; + +/** + * The return value of a modelled call on a JDK container that returns a reference. The concrete + * object is only known when the value is read back ({@code GETVALUE_Object}); the shadow is chosen + * there, see {@code CollectionInvocation.resolve}. + */ +public class ContainerPlaceHolder extends PlaceHolder { + + public enum Result { + /** An element (or map key/value) of {@link #contents}. */ + ELEMENT, + /** A view, iterator, enumeration or entry backed by {@link #contents}. */ + VIEW, + /** A new object array holding elements of {@link #contents} ({@code toArray()}). */ + ARRAY + } + + public final Result result; + public final ContainerContents contents; + /** Whether a returned view yields map entries. */ + public final boolean yieldsEntries; + /** Arguments the call may return as they are (e.g. the default of {@code getOrDefault}). */ + public final List> candidates; + /** The call, recorded as the culprit if the result cannot be matched to a shadow. */ + public final InvocationEntry call; + + public ContainerPlaceHolder( + Result result, + ContainerContents contents, + boolean yieldsEntries, + List> candidates, + InvocationEntry call) { + super(false); + this.result = result; + this.contents = contents; + this.yieldsEntries = yieldsEntries; + this.candidates = candidates; + this.call = call; + } + + @Override + public String toString() { + return "ContainerPlaceHolder (" + result + ")"; + } +} diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/Value.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/Value.java index 09ab749e..d0a17b03 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/Value.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/Value.java @@ -7,6 +7,7 @@ import de.uzl.its.swat.common.exceptions.SWATAssert; import de.uzl.its.swat.common.exceptions.ValueConversionException; import de.uzl.its.swat.common.logging.GlobalLogger; +import de.uzl.its.swat.symbolic.trace.SymbolRegistry; import de.uzl.its.swat.symbolic.value.primitive.numeric.NumericalValue; import de.uzl.its.swat.symbolic.value.primitive.numeric.floatingpoint.DoubleValue; import de.uzl.its.swat.symbolic.value.primitive.numeric.floatingpoint.FloatValue; @@ -69,19 +70,23 @@ protected void initSymbolic(String namePrefix) { SWATAssert.enforce(namePrefix != null, "Name prefix cannot be null"); name = namePrefix + "_" + symbol; symbol = symbol + inc; + SymbolRegistry.declare(name, SymbolRegistry.Kind.VALUE); } //Todo: this is a hotfix... protected void initSymbolicWithoutIdx(String symbolicName) { SWATAssert.enforce(symbolicName != null, "Name prefix cannot be null"); name = symbolicName; + SymbolRegistry.declare(name, SymbolRegistry.Kind.VALUE); } protected void initSymbolic(String namePrefix, long idx) { name = namePrefix + "_" + idx; + SymbolRegistry.declare(name, SymbolRegistry.Kind.VALUE); } protected void initSymbolic(String namePrefix, String idx) { name = namePrefix + "_" + idx; + SymbolRegistry.declare(name, SymbolRegistry.Kind.VALUE); } diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/ValueFactory.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/ValueFactory.java index fe32e31c..f048803a 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/ValueFactory.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/ValueFactory.java @@ -116,6 +116,9 @@ public static StringValue createStringValue(String concrete, int address) throws return new StringValue(context, s, address); } else if (concrete instanceof IASString s) { return new StringValue(context, s.getString(), address); + } else if (concrete instanceof StringBuilder || concrete instanceof StringBuffer) { + return new StringBuilderValue(context, Util.formatClassName(concrete.getClass().getName()), + new StringValue(context, concrete.toString(), -1), address); } else if (concrete instanceof Boolean b) { return new BooleanObjectValue(context, new BooleanValue(context, b), address); } else if (concrete instanceof Byte b) { @@ -152,10 +155,15 @@ public static StringValue createStringValue(String concrete, int address) throws Class cls = concrete.getClass(); String className = cls.getName(); int fieldCount = classDepot.getFieldCountWithReflection(className, cls, false); - return new ObjectValue<>( + ObjectValue object = new ObjectValue<>( context, Util.formatClassName(className), createNumericalValue(ValueType.intValue, fieldCount).asIntValue(), address); + if (concrete instanceof Locale) { + // Immutable: the case mappings of String depend on its language. + object.concrete = concrete; + } + return object; } // throw new RuntimeException("ERROR: Unknown type in getObjectValue"); } @@ -163,7 +171,7 @@ public static StringValue createStringValue(String concrete, int address) throws public static ObjectValue createObjectValue(int nFields, String className) throws NoThreadContextException, NotImplementedException, TypeException, ValueConversionException { SolverContext context = ThreadHandler.getSolverContext(Thread.currentThread().getId()); List classInterfacesAndParents = - ClassDepot.getRuntimeInstance().getInterfacesForClass(className); + new ArrayList<>(ClassDepot.getRuntimeInstance().getInterfacesForClass(className)); classInterfacesAndParents.addAll( ClassDepot.getRuntimeInstance().getParentsForClass(className)); @@ -194,8 +202,9 @@ public static StringValue createStringValue(String concrete, int address) throws || classInterfacesAndParents.contains(Util.formatClassName(Character.class.getName()))) { ret = new CharacterObjectValue(context, new CharValue(context, '\u0000'), ObjectValue.ADDRESS_UNKNOWN); } else if (className.equals(Util.formatClassName(StringBuilder.class.getName())) - || classInterfacesAndParents.contains(Util.formatClassName(StringBuilder.class.getName()))) { - ret = new StringBuilderValue(context, null, ObjectValue.ADDRESS_UNKNOWN); //Todo what is the correct initialization value here? + || className.equals(Util.formatClassName(StringBuffer.class.getName()))) { + // The chars are set by the constructor, which is the next call. + ret = new StringBuilderValue(context, className, null, ObjectValue.ADDRESS_UNKNOWN); } else if (className.equals(Util.formatClassName(String.class.getName())) || classInterfacesAndParents.contains(Util.formatClassName(String.class.getName()))) { ret = new StringValue(context, "", ObjectValue.ADDRESS_UNKNOWN); // This is not correct but _normally_ the next call should be the function diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/primitive/numeric/BitvectorEncoding.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/primitive/numeric/BitvectorEncoding.java new file mode 100644 index 00000000..f49831aa --- /dev/null +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/primitive/numeric/BitvectorEncoding.java @@ -0,0 +1,222 @@ +package de.uzl.its.swat.symbolic.value.primitive.numeric; + +import java.math.BigInteger; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.sosy_lab.java_smt.api.BitvectorFormula; +import org.sosy_lab.java_smt.api.BitvectorFormulaManager; +import org.sosy_lab.java_smt.api.BooleanFormula; +import org.sosy_lab.java_smt.api.BooleanFormulaManager; +import org.sosy_lab.java_smt.api.FloatingPointFormula; +import org.sosy_lab.java_smt.api.FloatingPointFormulaManager; +import org.sosy_lab.java_smt.api.Formula; +import org.sosy_lab.java_smt.api.FormulaManager; +import org.sosy_lab.java_smt.api.FunctionDeclaration; +import org.sosy_lab.java_smt.api.FunctionDeclarationKind; +import org.sosy_lab.java_smt.api.NumeralFormula.IntegerFormula; +import org.sosy_lab.java_smt.api.visitors.DefaultFormulaVisitor; + +/** + * Converts Integer-sorted formulas to bit-vectors without mixing the two sorts where possible. + * + *

{@link #toBitvector} returns a formula that is equal to {@code (_ int_to_bv width) f} in every + * model, but built over bit-vectors where {@code f} allows it: + * + *

    + *
  • numerals become bit-vector constants (modulo 2^width), + *
  • {@code ite}, {@code +}, {@code -}, {@code *} and unary minus are rebuilt over bit-vectors + * ({@code int_to_bv} is a ring homomorphism modulo 2^width), + *
  • {@code bv2int} (signed or unsigned) of a bit-vector is replaced by that bit-vector, + * extracted or extended to the width. + *
+ * + * Anything else (string lengths, {@code str.to_code}, division, ...) keeps its {@code int_to_bv}. + * + *

Z3 handles {@code int_to_bv} badly: a query over {@code int_to_bv(ite(fp.lt x c, 1, -1))} + * (a DCMP result) may not return at all, while the same query over bit-vector constants is decided + * at once. + */ +public final class BitvectorEncoding { + + /** Deeper Integer terms keep their int_to_bv rather than risk a deep recursion. */ + private static final int MAX_DEPTH = 64; + + private BitvectorEncoding() {} + + /** + * Returns a bit-vector of the given width equal to {@code (_ int_to_bv width) f}. + * + * @param fmgr the formula manager + * @param f the Integer-sorted formula + * @param width the bit width of the result + * @return the bit-vector formula + */ + public static BitvectorFormula toBitvector(FormulaManager fmgr, IntegerFormula f, int width) { + return new Lowering(fmgr, width).lower(f, 0); + } + + /** + * Returns the three-way comparison result {@code eq ? 0 : (gt ? 1 : -1)} as a bit-vector of the + * given width, as LCMP/FCMP/DCMP and the compare methods produce it. + * + * @param fmgr the formula manager + * @param eq the equality condition + * @param gt the greater-than condition + * @param width the bit width + * @return the bit-vector formula + */ + public static BitvectorFormula compareResult( + FormulaManager fmgr, BooleanFormula eq, BooleanFormula gt, int width) { + BitvectorFormulaManager bvmgr = fmgr.getBitvectorFormulaManager(); + BooleanFormulaManager bmgr = fmgr.getBooleanFormulaManager(); + return bmgr.ifThenElse( + eq, + bvmgr.makeBitvector(width, 0), + bmgr.ifThenElse(gt, bvmgr.makeBitvector(width, 1), bvmgr.makeBitvector(width, -1))); + } + + /** + * Returns {@code Double.compare(a, b)} (or {@code Float.compare}) as a 32-bit bit-vector: NaN is + * equal to itself and greater than every other value, and -0.0 is less than 0.0. + * + * @param fmgr the formula manager + * @param a the first value + * @param b the second value + * @return the bit-vector formula, -1, 0 or 1 + */ + public static BitvectorFormula javaCompare(FormulaManager fmgr, FloatingPointFormula a, FloatingPointFormula b) { + BitvectorFormulaManager bvmgr = fmgr.getBitvectorFormulaManager(); + BooleanFormulaManager bmgr = fmgr.getBooleanFormulaManager(); + FloatingPointFormulaManager fpmgr = fmgr.getFloatingPointFormulaManager(); + BitvectorFormula minus = bvmgr.makeBitvector(32, -1); + BitvectorFormula zero = bvmgr.makeBitvector(32, 0); + BitvectorFormula one = bvmgr.makeBitvector(32, 1); + BooleanFormula aNaN = fpmgr.isNaN(a); + BooleanFormula bNaN = fpmgr.isNaN(b); + BooleanFormula aNeg = fpmgr.isNegative(a); + BooleanFormula bNeg = fpmgr.isNegative(b); + // Equal in FP terms but not as bits: -0.0 and 0.0 + BitvectorFormula equal = bmgr.ifThenElse( + bmgr.and(aNeg, bmgr.not(bNeg)), + minus, + bmgr.ifThenElse(bmgr.and(bNeg, bmgr.not(aNeg)), one, zero)); + BitvectorFormula ordered = bmgr.ifThenElse( + fpmgr.lessThan(a, b), minus, bmgr.ifThenElse(fpmgr.greaterThan(a, b), one, equal)); + return bmgr.ifThenElse( + aNaN, + bmgr.ifThenElse(bNaN, zero, one), + bmgr.ifThenElse(bNaN, minus, ordered)); + } + + private static final class Lowering { + private final FormulaManager fmgr; + private final BitvectorFormulaManager bvmgr; + private final BooleanFormulaManager bmgr; + private final int width; + private final BigInteger modulus; + private final Map done = new HashMap<>(); + + Lowering(FormulaManager fmgr, int width) { + this.fmgr = fmgr; + this.bvmgr = fmgr.getBitvectorFormulaManager(); + this.bmgr = fmgr.getBooleanFormulaManager(); + this.width = width; + this.modulus = BigInteger.ONE.shiftLeft(width); + } + + BitvectorFormula lower(IntegerFormula f, int depth) { + BitvectorFormula known = done.get(f); + if (known != null) { + return known; + } + BitvectorFormula result = + depth > MAX_DEPTH ? bvmgr.makeBitvector(width, f) : fmgr.visit(f, visitor(f, depth)); + done.put(f, result); + return result; + } + + private DefaultFormulaVisitor visitor(IntegerFormula f, int depth) { + return new DefaultFormulaVisitor<>() { + @Override + protected BitvectorFormula visitDefault(Formula formula) { + return bvmgr.makeBitvector(width, f); + } + + @Override + public BitvectorFormula visitConstant(Formula formula, Object value) { + if (value instanceof BigInteger v) { + return bvmgr.makeBitvector(width, v.mod(modulus)); + } + return visitDefault(formula); + } + + @Override + public BitvectorFormula visitFunction( + Formula formula, List args, FunctionDeclaration decl) { + switch (decl.getKind()) { + case ITE: + if (args.size() == 3 + && args.get(0) instanceof BooleanFormula cond + && args.get(1) instanceof IntegerFormula a + && args.get(2) instanceof IntegerFormula b) { + return bmgr.ifThenElse(cond, lower(a, depth + 1), lower(b, depth + 1)); + } + break; + case ADD: + case SUB: + case MUL: + if (args.size() >= 2 && allInteger(args)) { + BitvectorFormula acc = lower((IntegerFormula) args.get(0), depth + 1); + for (Formula arg : args.subList(1, args.size())) { + BitvectorFormula next = lower((IntegerFormula) arg, depth + 1); + acc = switch (decl.getKind()) { + case ADD -> bvmgr.add(acc, next); + case SUB -> bvmgr.subtract(acc, next); + default -> bvmgr.multiply(acc, next); + }; + } + return acc; + } + break; + case UMINUS: + if (args.size() == 1 && args.get(0) instanceof IntegerFormula a) { + return bvmgr.negate(lower(a, depth + 1)); + } + break; + case UBV_TO_INT: + case SBV_TO_INT: + if (args.size() == 1 && args.get(0) instanceof BitvectorFormula bv) { + return resize(bv, decl.getKind() == FunctionDeclarationKind.SBV_TO_INT); + } + break; + default: + break; + } + return visitDefault(formula); + } + }; + } + + /** int_to_bv(bv2int(bv)) at this width: bv itself, its low bits, or bv extended. */ + private BitvectorFormula resize(BitvectorFormula bv, boolean signed) { + int length = bvmgr.getLength(bv); + if (length == width) { + return bv; + } + if (length > width) { + return bvmgr.extract(bv, width - 1, 0); + } + return bvmgr.extend(bv, width - length, signed); + } + + private static boolean allInteger(List args) { + for (Formula arg : args) { + if (!(arg instanceof IntegerFormula)) { + return false; + } + } + return true; + } + } +} diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/primitive/numeric/NumericalValue.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/primitive/numeric/NumericalValue.java index 8de636d7..ba5e0ed8 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/primitive/numeric/NumericalValue.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/primitive/numeric/NumericalValue.java @@ -130,7 +130,7 @@ protected FloatingPointFormula intToFpFormula( BitvectorFormulaManager bvmgr = context.getFormulaManager().getBitvectorFormulaManager(); // Step 1: Convert integer to signed bitvector - BitvectorFormula bv = bvmgr.makeBitvector(sourceBitWidth, intFormula); + BitvectorFormula bv = BitvectorEncoding.toBitvector(context.getFormulaManager(), intFormula, sourceBitWidth); // Step 2: Cast bitvector to floating-point using RNE rounding (Java's default) FloatingPointFormula fpFormula = fpmgr.castFrom( diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/primitive/numeric/floatingpoint/DoubleValue.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/primitive/numeric/floatingpoint/DoubleValue.java index 0bacd823..4c3665dc 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/primitive/numeric/floatingpoint/DoubleValue.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/primitive/numeric/floatingpoint/DoubleValue.java @@ -1,5 +1,10 @@ package de.uzl.its.swat.symbolic.value.primitive.numeric.floatingpoint; +import de.uzl.its.swat.symbolic.trace.SymbolicTraceHandler; + +import de.uzl.its.swat.common.exceptions.NoThreadContextException; +import de.uzl.its.swat.symbolic.value.reference.lang.StringValue; +import de.uzl.its.swat.thread.ThreadHandler; import org.sosy_lab.java_smt.api.BitvectorFormula; import org.sosy_lab.java_smt.api.BitvectorFormulaManager; import org.sosy_lab.java_smt.api.BooleanFormula; @@ -12,6 +17,7 @@ import org.sosy_lab.java_smt.api.NumeralFormula; import org.sosy_lab.java_smt.api.SolverContext; +import de.uzl.its.swat.symbolic.value.primitive.numeric.BitvectorEncoding; import de.uzl.its.swat.symbolic.value.primitive.numeric.NumericalValue; import de.uzl.its.swat.symbolic.value.primitive.numeric.integral.ByteValue; import de.uzl.its.swat.symbolic.value.primitive.numeric.integral.CharValue; @@ -21,6 +27,11 @@ import de.uzl.its.swat.symbolic.value.reference.ObjectValue; import de.uzl.its.swat.symbolic.value.reference.lang.DoubleObjectValue; import lombok.Getter; +import de.uzl.its.swat.common.exceptions.NoThreadContextException; +import de.uzl.its.swat.symbolic.value.reference.lang.StringValue; +import de.uzl.its.swat.thread.ThreadHandler; + +import static java.lang.Thread.currentThread; /** * Wrapper for doubles to represent symbolic and concrete information on the symbolic stack @@ -166,16 +177,20 @@ public DoubleValue DDIV(DoubleValue d) { } /** - * Gets the remainder from the division of two doubles WARNING: This is not symbolically - * tracked! + * Gets the remainder from the division of two doubles, as Java computes it; symbolic as an + * approximate model (see {@link JavaRemainder}). * * @param d The other double + * @param sth Where the model's definition goes (only used if an operand is symbolic) * @return The resulting DoubleValue */ - public DoubleValue DREM(DoubleValue d) { - // ToDo (Nils): This is not (yet) symbolically tracked! - // See https://git.its.uni-luebeck.de/research-projects/pet-hmr/knife-fuzzer/-/issues/64 - return new DoubleValue(context, concrete % d.concrete); + public DoubleValue DREM(DoubleValue d, SymbolicTraceHandler sth) { + double result = concrete % d.concrete; + if (!isSymbolic() && !d.isSymbolic()) { + return new DoubleValue(context, result); + } + return new DoubleValue(context, result, + JavaRemainder.approximate(context, formula, d.formula, new DoubleValue(context, result).formula, sth)); } /** @@ -236,7 +251,6 @@ private IntValue DCMP(DoubleValue f, int nanResult) { } BooleanFormulaManager bmgr = context.getFormulaManager().getBooleanFormulaManager(); - IntegerFormulaManager imgr = context.getFormulaManager().getIntegerFormulaManager(); FloatingPointFormulaManager fmgr = context.getFormulaManager().getFloatingPointFormulaManager(); BooleanFormula isNaN1 = fmgr.isNaN(formula); @@ -246,18 +260,12 @@ private IntValue DCMP(DoubleValue f, int nanResult) { BooleanFormula gtCond = fmgr.greaterThan(formula, f.formula); BooleanFormula eqCond = fmgr.equalWithFPSemantics(formula, f.formula); - NumeralFormula.IntegerFormula res = bmgr.ifThenElse( + // Built over bit-vectors: Z3 decides int_to_bv over FP comparisons badly. + BitvectorFormulaManager bvmgr = context.getFormulaManager().getBitvectorFormulaManager(); + BitvectorFormula res = bmgr.ifThenElse( isNaN, - imgr.makeNumber(nanResult), - bmgr.ifThenElse( - gtCond, - imgr.makeNumber(1), - bmgr.ifThenElse( - eqCond, - imgr.makeNumber(0), - imgr.makeNumber(-1) - ) - ) + bvmgr.makeBitvector(32, nanResult), + BitvectorEncoding.compareResult(context.getFormulaManager(), eqCond, gtCond, 32) ); return new IntValue(context, c, res); @@ -441,10 +449,22 @@ public LongValue asLongValue() { return D2L(); } - //@Override - //public StringValue asStringValue() { - // return new StringValue(context, String.valueOf(concrete), -1); - //} + /** + * The string of the concrete value, as String.valueOf(double). The shortest decimal string that + * identifies the value has no practical exact encoding, so a symbolic value loses its link to + * the string: context loss. + */ + @Override + public StringValue asStringValue() { + if (isSymbolic()) { + try { + ThreadHandler.getSymbolicTraceHandler(Thread.currentThread().getId()).recordSymbolicContextLoss(); + } catch (NoThreadContextException e) { + throw new IllegalStateException("[SWAT] No thread context for a symbolic double", e); + } + } + return new StringValue(context, String.valueOf(concrete), ObjectValue.ADDRESS_UNKNOWN); + } @Override public DoubleObjectValue asObjectValue(){ diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/primitive/numeric/floatingpoint/FloatValue.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/primitive/numeric/floatingpoint/FloatValue.java index 2f14df0e..a2aad9a6 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/primitive/numeric/floatingpoint/FloatValue.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/primitive/numeric/floatingpoint/FloatValue.java @@ -1,5 +1,10 @@ package de.uzl.its.swat.symbolic.value.primitive.numeric.floatingpoint; +import de.uzl.its.swat.symbolic.trace.SymbolicTraceHandler; + +import de.uzl.its.swat.common.exceptions.NoThreadContextException; +import de.uzl.its.swat.symbolic.value.reference.lang.StringValue; +import de.uzl.its.swat.thread.ThreadHandler; import org.sosy_lab.java_smt.api.BitvectorFormula; import org.sosy_lab.java_smt.api.BitvectorFormulaManager; import org.sosy_lab.java_smt.api.BooleanFormula; @@ -12,6 +17,7 @@ import org.sosy_lab.java_smt.api.NumeralFormula; import org.sosy_lab.java_smt.api.SolverContext; +import de.uzl.its.swat.symbolic.value.primitive.numeric.BitvectorEncoding; import de.uzl.its.swat.symbolic.value.primitive.numeric.NumericalValue; import de.uzl.its.swat.symbolic.value.primitive.numeric.integral.ByteValue; import de.uzl.its.swat.symbolic.value.primitive.numeric.integral.CharValue; @@ -21,6 +27,11 @@ import de.uzl.its.swat.symbolic.value.reference.ObjectValue; import de.uzl.its.swat.symbolic.value.reference.lang.FloatObjectValue; import lombok.Getter; +import de.uzl.its.swat.common.exceptions.NoThreadContextException; +import de.uzl.its.swat.symbolic.value.reference.lang.StringValue; +import de.uzl.its.swat.thread.ThreadHandler; + +import static java.lang.Thread.currentThread; public class FloatValue extends NumericalValue { @Getter @@ -146,13 +157,20 @@ public FloatValue FNEG() { } /** - * Gets the remainder from the division of two floats WARNING: This is not symbolically tracked! + * Gets the remainder from the division of two floats, as Java computes it; symbolic as an + * approximate model (see {@link JavaRemainder}). * - * @param f The other flaot + * @param f The other float + * @param sth Where the model's definition goes (only used if an operand is symbolic) * @return The resulting FloatValue */ - public FloatValue FREM(FloatValue f) { - return new FloatValue(context, concrete % f.concrete); + public FloatValue FREM(FloatValue f, SymbolicTraceHandler sth) { + float result = concrete % f.concrete; + if (!isSymbolic() && !f.isSymbolic()) { + return new FloatValue(context, result); + } + return new FloatValue(context, result, + JavaRemainder.approximate(context, formula, f.formula, new FloatValue(context, result).formula, sth)); } /** @@ -276,49 +294,56 @@ public LongValue F2L() { /** * Compares two floats and returns: 0 if both values are equal; 1 if this float is greater or - * either float is NaN; -1 if the parameter is greater ToDo (Nils): This method is not validated + * either float is NaN; -1 if the parameter is greater * * @param f The other FloatValue * @return The resulting IntValue with the boolean conditions */ public IntValue FCMPG(FloatValue f) { - if (concrete.isNaN() || f.concrete.isNaN()) { - return new IntValue(context, 1); - } - return FCMP(f); + return FCMP(f, 1); } /** * Compares two floats and returns: 0 if both values are equal 1 if this float is greater -1 if - * the parameter is greater or either float is NaN ToDo (Nils): This method is not validated + * the parameter is greater or either float is NaN * * @param f The other FloatValue * @return The resulting IntValue with the boolean conditions */ public IntValue FCMPL(FloatValue f) { - if (concrete.isNaN() || f.concrete.isNaN()) { - return new IntValue(context, -1); - } - return FCMP(f); + return FCMP(f, -1); } /** - * Compares two floats and returns: 0 if both values are equal 1 if this float is greater -1 if - * the parameter is greater + * Compares two floats as FCMPG/FCMPL do: 0 if both values are equal (also -0.0 and 0.0), 1 if + * this float is greater, -1 if the parameter is greater, and nanResult if either is NaN * * @param f The other FloatValue + * @param nanResult The result if either float is NaN * @return The resulting IntValue with the boolean conditions */ - private IntValue FCMP(FloatValue f) { - int c = concrete.compareTo(f.concrete); + private IntValue FCMP(FloatValue f, int nanResult) { + int c; + if (Float.isNaN(concrete) || Float.isNaN(f.concrete)) { + c = nanResult; + } else if (concrete > f.concrete) { + c = 1; + } else if (concrete.floatValue() == f.concrete.floatValue()) { + // Explicit unboxing: 'concrete' is a boxed Float, so '==' would compare references + c = 0; + } else { + c = -1; + } BooleanFormulaManager bmgr = context.getFormulaManager().getBooleanFormulaManager(); - IntegerFormulaManager imgr = context.getFormulaManager().getIntegerFormulaManager(); + BooleanFormula isNaN = bmgr.or(fmgr.isNaN(formula), fmgr.isNaN(f.formula)); BooleanFormula eqCond = fmgr.equalWithFPSemantics(formula, f.formula); BooleanFormula gtCond = fmgr.greaterThan(formula, f.formula); - NumeralFormula.IntegerFormula tmp = - bmgr.ifThenElse(gtCond, imgr.makeNumber(1), imgr.makeNumber(-1)); - NumeralFormula.IntegerFormula res = bmgr.ifThenElse(eqCond, imgr.makeNumber(0), tmp); + // Built over bit-vectors: Z3 decides int_to_bv over FP comparisons badly. + BitvectorFormula res = bmgr.ifThenElse( + isNaN, + context.getFormulaManager().getBitvectorFormulaManager().makeBitvector(32, nanResult), + BitvectorEncoding.compareResult(context.getFormulaManager(), eqCond, gtCond, 32)); return new IntValue(context, c, res); } @@ -391,10 +416,22 @@ public DoubleValue asDoubleValue() { return F2D(); } - //@Override - //public StringValue asStringValue() { - // return new StringValue(context, String.valueOf(concrete), -1); - //} + /** + * The string of the concrete value, as String.valueOf(float). The shortest decimal string that + * identifies the value has no practical exact encoding, so a symbolic value loses its link to + * the string: context loss. + */ + @Override + public StringValue asStringValue() { + if (isSymbolic()) { + try { + ThreadHandler.getSymbolicTraceHandler(Thread.currentThread().getId()).recordSymbolicContextLoss(); + } catch (NoThreadContextException e) { + throw new IllegalStateException("[SWAT] No thread context for a symbolic float", e); + } + } + return new StringValue(context, String.valueOf(concrete), ObjectValue.ADDRESS_UNKNOWN); + } @Override public FloatObjectValue asObjectValue(){ diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/primitive/numeric/floatingpoint/JavaRemainder.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/primitive/numeric/floatingpoint/JavaRemainder.java new file mode 100644 index 00000000..77abf87e --- /dev/null +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/primitive/numeric/floatingpoint/JavaRemainder.java @@ -0,0 +1,54 @@ +package de.uzl.its.swat.symbolic.value.primitive.numeric.floatingpoint; + +import de.uzl.its.swat.symbolic.invoke.java.lang.SampledMath; +import de.uzl.its.swat.symbolic.trace.SymbolicTraceHandler; +import org.sosy_lab.java_smt.api.FloatingPointFormula; +import org.sosy_lab.java_smt.api.FloatingPointFormulaManager; +import org.sosy_lab.java_smt.api.SolverContext; + +/** + * Java's floating-point remainder ({@code x % y}, DREM/FREM) as an approximate model. + * + *

It is exact where the quotient is 0 or 1 in magnitude, which is the common case (an angle + * modulo 360, a phase modulo a period): + * + *

    + *
  • |x| < |y|: the remainder is x (also for a zero x and for an infinite y); + *
  • |y| <= |x| < 2|y|: it is |x| - |y| with the sign of x, which is exact (Sterbenz); + *
+ * + * and the concrete result of this run anywhere else (NaN operands, a zero divisor, an infinite + * dividend included). So it is a model like those of the transcendental Math functions: a fresh + * function, declared approximate (SampledMath#define), so that a branch over it is a precision + * loss and an exhausted search proves nothing. + * + *

The exact SMT encoding, {@code fp.rem} (the IEEE remainder, quotient rounded to the nearest + * integer) adjusted by one |y| where its sign differs from x's, is correct but far too slow: Z3 did + * not decide {@code angle % 360 < -180} for a symbolic double within 120 s, while this model takes + * milliseconds. Before this model the remainder had no formula at all, so every branch on it looked + * concrete and a search that never ran into the violation reported SAFE (sv-benchmarks PieSegment). + */ +final class JavaRemainder { + + private JavaRemainder() {} + + /** The model of x % y, for operands of which at least one is symbolic. */ + static FloatingPointFormula approximate(SolverContext context, FloatingPointFormula x, FloatingPointFormula y, + FloatingPointFormula concreteResult, SymbolicTraceHandler sth) { + return SampledMath.define(context, "rem", sth, exactPieces(context, x, y, concreteResult), x, y); + } + + /** x % y where the quotient is at most 1 in magnitude, `otherwise` elsewhere. */ + static FloatingPointFormula exactPieces(SolverContext context, FloatingPointFormula x, FloatingPointFormula y, + FloatingPointFormula otherwise) { + FloatingPointFormulaManager fp = context.getFormulaManager().getFloatingPointFormulaManager(); + var b = context.getFormulaManager().getBooleanFormulaManager(); + FloatingPointFormula ax = fp.abs(x); + FloatingPointFormula ay = fp.abs(y); + // -(|x| - |y|) rather than x + |y|: for x = -|y| Java gives -0.0, and the sum would be +0.0 + FloatingPointFormula diff = fp.subtract(ax, ay); + FloatingPointFormula second = b.ifThenElse(fp.isNegative(x), fp.negate(diff), diff); + FloatingPointFormula twoAy = fp.add(ay, ay); // exact, or infinite where 2|y| is not representable + return b.ifThenElse(fp.lessThan(ax, ay), x, b.ifThenElse(fp.lessThan(ax, twoAy), second, otherwise)); + } +} diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/primitive/numeric/integral/BooleanValue.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/primitive/numeric/integral/BooleanValue.java index 16a21333..2bfaf99f 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/primitive/numeric/integral/BooleanValue.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/primitive/numeric/integral/BooleanValue.java @@ -114,11 +114,11 @@ public BooleanFormula getBounds(boolean upper) { @Override public IntValue asIntValue() { - imgr = context.getFormulaManager().getIntegerFormulaManager(); + BitvectorFormulaManager bvmgr = context.getFormulaManager().getBitvectorFormulaManager(); return new IntValue( context, concrete ? 1 : 0, - bmgr.ifThenElse(formula, imgr.makeNumber(1), imgr.makeNumber(0))); + bmgr.ifThenElse(formula, bvmgr.makeBitvector(32, 1), bvmgr.makeBitvector(32, 0))); } @Override diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/primitive/numeric/integral/ByteValue.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/primitive/numeric/integral/ByteValue.java index c3db10b1..c7c902f5 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/primitive/numeric/integral/ByteValue.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/primitive/numeric/integral/ByteValue.java @@ -1,5 +1,6 @@ package de.uzl.its.swat.symbolic.value.primitive.numeric.integral; +import de.uzl.its.swat.symbolic.value.primitive.numeric.BitvectorEncoding; import de.uzl.its.swat.symbolic.value.primitive.numeric.NumericalValue; import de.uzl.its.swat.symbolic.value.primitive.numeric.floatingpoint.DoubleValue; import de.uzl.its.swat.symbolic.value.primitive.numeric.floatingpoint.FloatValue; @@ -67,8 +68,8 @@ public ByteValue(SolverContext context, byte concrete, NumeralFormula.IntegerFor this.bvmgr = context.getFormulaManager().getBitvectorFormulaManager(); this.imgr = context.getFormulaManager().getIntegerFormulaManager(); this.concrete = concrete; - // Convert IntegerFormula to BitvectorFormula - this.formula = bvmgr.makeBitvector(BIT_WIDTH, intFormula); + // Convert IntegerFormula to BitvectorFormula, over bit-vectors where it allows + this.formula = BitvectorEncoding.toBitvector(context.getFormulaManager(), intFormula, BIT_WIDTH); } /** diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/primitive/numeric/integral/CharValue.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/primitive/numeric/integral/CharValue.java index f1f2fbd7..d06cd5ff 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/primitive/numeric/integral/CharValue.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/primitive/numeric/integral/CharValue.java @@ -1,5 +1,6 @@ package de.uzl.its.swat.symbolic.value.primitive.numeric.integral; +import de.uzl.its.swat.symbolic.value.primitive.numeric.BitvectorEncoding; import de.uzl.its.swat.symbolic.value.primitive.numeric.NumericalValue; import de.uzl.its.swat.symbolic.value.primitive.numeric.floatingpoint.DoubleValue; import de.uzl.its.swat.symbolic.value.primitive.numeric.floatingpoint.FloatValue; @@ -83,8 +84,8 @@ public CharValue(SolverContext context, char concrete, NumeralFormula.IntegerFor this.bvmgr = context.getFormulaManager().getBitvectorFormulaManager(); this.imgr = context.getFormulaManager().getIntegerFormulaManager(); this.concrete = concrete; - // Convert IntegerFormula to BitvectorFormula - this.formula = bvmgr.makeBitvector(BIT_WIDTH, intFormula); + // Convert IntegerFormula to BitvectorFormula, over bit-vectors where it allows + this.formula = BitvectorEncoding.toBitvector(context.getFormulaManager(), intFormula, BIT_WIDTH); } /** @@ -250,7 +251,9 @@ public NumeralFormula.IntegerFormula asIntegerFormula() { @Override public String getConcreteEncoded() { - return Character.toString(concrete); + // As a number: the explorer passes it back on the command line, where Verifier.nondetChar + // parses it with Integer.parseInt (and a raw '\0' would not even survive the command line). + return Integer.toString(concrete); } @Override diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/primitive/numeric/integral/IntValue.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/primitive/numeric/integral/IntValue.java index 632e32c0..39e50db6 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/primitive/numeric/integral/IntValue.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/primitive/numeric/integral/IntValue.java @@ -1,6 +1,7 @@ package de.uzl.its.swat.symbolic.value.primitive.numeric.integral; import de.uzl.its.swat.common.exceptions.SWATAssert; +import de.uzl.its.swat.symbolic.value.primitive.numeric.BitvectorEncoding; import de.uzl.its.swat.symbolic.value.primitive.numeric.NumericalValue; import de.uzl.its.swat.symbolic.value.primitive.numeric.floatingpoint.DoubleValue; import de.uzl.its.swat.symbolic.value.primitive.numeric.floatingpoint.FloatValue; @@ -65,8 +66,8 @@ public IntValue(SolverContext context, int concrete, NumeralFormula.IntegerFormu this.bvmgr = context.getFormulaManager().getBitvectorFormulaManager(); this.imgr = context.getFormulaManager().getIntegerFormulaManager(); this.concrete = concrete; - // Convert IntegerFormula to BitvectorFormula - this.formula = bvmgr.makeBitvector(BIT_WIDTH, intFormula); + // Convert IntegerFormula to BitvectorFormula, over bit-vectors where it allows + this.formula = BitvectorEncoding.toBitvector(context.getFormulaManager(), intFormula, BIT_WIDTH); } /** diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/primitive/numeric/integral/LongValue.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/primitive/numeric/integral/LongValue.java index 36c157ef..1e2b70d4 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/primitive/numeric/integral/LongValue.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/primitive/numeric/integral/LongValue.java @@ -1,5 +1,7 @@ package de.uzl.its.swat.symbolic.value.primitive.numeric.integral; +import de.uzl.its.swat.symbolic.invoke.java.lang.NumberStrings; +import de.uzl.its.swat.symbolic.value.primitive.numeric.BitvectorEncoding; import de.uzl.its.swat.symbolic.value.primitive.numeric.NumericalValue; import de.uzl.its.swat.symbolic.value.primitive.numeric.floatingpoint.DoubleValue; import de.uzl.its.swat.symbolic.value.primitive.numeric.floatingpoint.FloatValue; @@ -268,16 +270,11 @@ public LongValue LXOR(LongValue l) { public IntValue LCMP(LongValue l) { int c = concrete.compareTo(l.concrete); - BooleanFormulaManager bmgr = context.getFormulaManager().getBooleanFormulaManager(); - IntegerFormulaManager imgr = context.getFormulaManager().getIntegerFormulaManager(); - // Use signed comparison for bitvectors BooleanFormula eqCond = bvmgr.equal(formula, l.formula); BooleanFormula gtCond = bvmgr.greaterThan(formula, l.formula, true); // true = signed - NumeralFormula.IntegerFormula tmp = - bmgr.ifThenElse(gtCond, imgr.makeNumber(1), imgr.makeNumber(-1)); - NumeralFormula.IntegerFormula res = bmgr.ifThenElse(eqCond, imgr.makeNumber(0), tmp); + BitvectorFormula res = BitvectorEncoding.compareResult(context.getFormulaManager(), eqCond, gtCond, 32); return new IntValue(context, c, res); } @@ -402,12 +399,11 @@ public DoubleValue asDoubleValue() { @Override public StringValue asStringValue() { - // String theory requires IntegerFormula, so convert BV to Int - NumeralFormula.IntegerFormula intFormula = bvmgr.toIntegerFormula(formula, true); + // str.from_int of a negative number is "", so the sign is handled separately return new StringValue( this.context, String.valueOf(concrete), - context.getFormulaManager().getStringFormulaManager().toStringFormula(intFormula), + NumberStrings.decimal(context, formula, 64, true), -1); } diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/primitive/numeric/integral/ShortValue.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/primitive/numeric/integral/ShortValue.java index 16645268..25130377 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/primitive/numeric/integral/ShortValue.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/primitive/numeric/integral/ShortValue.java @@ -1,5 +1,7 @@ package de.uzl.its.swat.symbolic.value.primitive.numeric.integral; +import de.uzl.its.swat.symbolic.invoke.java.lang.NumberStrings; +import de.uzl.its.swat.symbolic.value.primitive.numeric.BitvectorEncoding; import de.uzl.its.swat.symbolic.value.primitive.numeric.NumericalValue; import de.uzl.its.swat.symbolic.value.primitive.numeric.floatingpoint.DoubleValue; import de.uzl.its.swat.symbolic.value.primitive.numeric.floatingpoint.FloatValue; @@ -69,8 +71,8 @@ public ShortValue(SolverContext context, short concrete, NumeralFormula.IntegerF this.bvmgr = context.getFormulaManager().getBitvectorFormulaManager(); this.imgr = context.getFormulaManager().getIntegerFormulaManager(); this.concrete = concrete; - // Convert IntegerFormula to BitvectorFormula - this.formula = bvmgr.makeBitvector(BIT_WIDTH, intFormula); + // Convert IntegerFormula to BitvectorFormula, over bit-vectors where it allows + this.formula = BitvectorEncoding.toBitvector(context.getFormulaManager(), intFormula, BIT_WIDTH); } /** @@ -219,12 +221,11 @@ public ShortObjectValue asObjectValue() { @Override public StringValue asStringValue() { - // String theory requires IntegerFormula, so convert BV to Int - NumeralFormula.IntegerFormula intFormula = bvmgr.toIntegerFormula(formula, true); + // str.from_int of a negative number is "", so the sign is handled separately return new StringValue( this.context, String.valueOf(concrete), - context.getFormulaManager().getStringFormulaManager().toStringFormula(intFormula), + NumberStrings.decimal(context, formula, 16, true), -1); } diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/reference/ContainerContents.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/reference/ContainerContents.java new file mode 100644 index 00000000..0a042e90 --- /dev/null +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/reference/ContainerContents.java @@ -0,0 +1,223 @@ +package de.uzl.its.swat.symbolic.value.reference; + +import de.uzl.its.swat.common.exceptions.NotImplementedException; +import de.uzl.its.swat.symbolic.value.PlaceHolder; +import de.uzl.its.swat.symbolic.value.Value; +import de.uzl.its.swat.symbolic.value.reference.array.ObjectArrayValue; +import de.uzl.its.swat.symbolic.value.reference.lang.BoxedValue; +import de.uzl.its.swat.symbolic.value.reference.lang.StringValue; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * The shadow values a JDK container (a {@code java.util} collection or map, or a view, iterator or + * entry derived from one) has been given through modelled calls. A container is not instrumented, + * so its fields stay empty in the shadow; the shadow of what it holds is kept here instead. + * + *

The record only grows: removals are not tracked, so it over-approximates the contents. That + * is enough for its two uses: + * + *

    + *
  • {@link #isSymbolic} makes a container that may hold a symbolic value count as symbolic, + * so an unmodelled call that gets it still records context loss; + *
  • {@link #lookup} gives back the shadow of an element a modelled read returned, found by the + * address of the concrete object. + *
+ * + *

Contents can have sources: the contents of another container (a copy, a view) or an object + * array (the array behind {@code Arrays.asList}). Their values count as values of this container. + */ +public class ContainerContents { + + /** What the container is, which decides the calls that are modelled on it. */ + public enum Kind { + /** A list, queue or deque: elements are only stored and returned, never compared. */ + LIST, + /** A hash map: the keys are hashed and compared, the values are only stored and returned. */ + MAP, + /** A view, iterator, enumeration or map entry, or an immutable container: only read. */ + VIEW + } + + /** Marks an address under which different shadows were stored. */ + private static final Value AMBIGUOUS = new PlaceHolder(false); + + public final Kind kind; + /** Whether the elements of this view are map entries (an entry set and its iterators). */ + public final boolean yieldsEntries; + + /** Mutable shadows (objects, arrays, containers), whose symbolic state can change later. */ + private final List> mutables = new ArrayList<>(); + /** Whether a value-typed (String, boxed) element was symbolic when it was stored. */ + private boolean symbolicImmutable = false; + /** Mutable map keys; a modelled store only accepts keys that are not symbolic. */ + private final List> keys = new ArrayList<>(); + + private final Map> byAddress = new HashMap<>(); + private final List> addressUnknown = new ArrayList<>(); + private final List sources = new ArrayList<>(); + + public ContainerContents(Kind kind) { + this(kind, false); + } + + public ContainerContents(Kind kind, boolean yieldsEntries) { + this.kind = kind; + this.yieldsEntries = yieldsEntries; + } + + /** A view of {@code source}: a read-only container that holds what the source holds. */ + public static ContainerContents viewOf(ContainerContents source, boolean yieldsEntries) { + ContainerContents view = new ContainerContents(Kind.VIEW, yieldsEntries); + view.addSource(source); + return view; + } + + public void addValue(Value value) throws NotImplementedException { + if (value == null) { + return; + } + if (isValueTyped(value)) { + symbolicImmutable = symbolicImmutable || value.isSymbolic(); + } else if (value instanceof ObjectValue ov && !ov.isNullValue()) { + mutables.add(value); + } else if (!(value instanceof ObjectValue)) { + symbolicImmutable = symbolicImmutable || value.isSymbolic(); + } + index(value); + } + + public void addKey(Value key) throws NotImplementedException { + if (key == null) { + return; + } + // A String or boxed key was concrete when stored and stays so; only mutable keys are rechecked + if (!isValueTyped(key)) { + keys.add(key); + } + index(key); + } + + /** Adds the contents of another container, or the elements of an object array. */ + public void addSource(Object source) { + if (source != this && (source instanceof ContainerContents || source instanceof ObjectArrayValue)) { + for (Object s : sources) { + if (s == source) return; + } + sources.add(source); + } + } + + /** Whether a symbolic value may be stored in the container (keys included). */ + public boolean isSymbolic(Set> visited) throws NotImplementedException { + return isSymbolic(visited, Collections.newSetFromMap(new IdentityHashMap<>())); + } + + private boolean isSymbolic(Set> visited, Set seen) throws NotImplementedException { + if (!seen.add(this)) { + return false; + } + if (symbolicImmutable) { + return true; + } + for (Value v : mutables) { + if (v.isSymbolic(visited)) return true; + } + if (keysSymbolic(visited)) { + return true; + } + for (Object s : sources) { + if (s instanceof ContainerContents c && c.isSymbolic(visited, seen)) return true; + if (s instanceof ObjectArrayValue a && seen.add(a) && a.isSymbolic(visited)) return true; + } + return false; + } + + /** Whether a map key stored here (not in the sources) has become symbolic. */ + public boolean keysSymbolic(Set> visited) throws NotImplementedException { + for (Value k : keys) { + if (k.isSymbolic(visited)) return true; + } + return false; + } + + /** + * The shadow of the value with this address that was stored in the container or one of its + * sources, or {@code null} if there is none or there are several different ones. + */ + public Value lookup(int address) { + Value found = lookup(address, Collections.newSetFromMap(new IdentityHashMap<>())); + return found == AMBIGUOUS ? null : found; + } + + private Value lookup(int address, Set seen) { + if (!seen.add(this)) { + return null; + } + resolvePendingAddresses(); + Value found = byAddress.get(address); + for (Object s : sources) { + Value candidate = null; + if (s instanceof ContainerContents c) { + candidate = c.lookup(address, seen); + } else if (s instanceof ObjectArrayValue a && seen.add(a)) { + candidate = lookupInArray(a, address); + } + found = merge(found, candidate); + } + return found; + } + + private static Value lookupInArray(ObjectArrayValue array, int address) { + Value found = null; + Value[] elements = array.getFields(); + if (elements == null) { + return null; + } + for (Value e : elements) { + if (e instanceof ObjectValue ov && ov.getAddress() == address) { + found = merge(found, e); + } + } + return found; + } + + private static Value merge(Value a, Value b) { + if (a == null) return b; + if (b == null || a == b) return a; + return AMBIGUOUS; + } + + private void index(Value value) { + if (!(value instanceof ObjectValue ov) || ov.isNullValue()) { + return; + } + if (ov.getAddress() == ObjectValue.ADDRESS_UNKNOWN) { + addressUnknown.add(value); + } else { + byAddress.merge(ov.getAddress(), value, ContainerContents::merge); + } + } + + /** Values stored before their address was known are indexed once it is. */ + private void resolvePendingAddresses() { + if (addressUnknown.isEmpty()) { + return; + } + List> pending = new ArrayList<>(addressUnknown); + addressUnknown.clear(); + for (Value v : pending) { + index(v); + } + } + + /** Strings and boxed values: their shadow is fixed once created. */ + private static boolean isValueTyped(Value value) { + return value instanceof StringValue || value instanceof BoxedValue; + } +} diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/reference/ObjectValue.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/reference/ObjectValue.java index 8a23a94f..0842cded 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/reference/ObjectValue.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/reference/ObjectValue.java @@ -38,6 +38,8 @@ public class ObjectValue extends Value { // For array object, fields are the content of the array. // For plain object, fields are the instance variables. @Setter @Getter private Value[] fields; + // For uninstrumented JDK containers: the shadow of what modelled calls stored in them. + @Setter @Getter private ContainerContents contents; @Getter private IntValue nFields; private BooleanFormulaManager bfm; @@ -271,10 +273,17 @@ public boolean isSymbolic() throws NotImplementedException { @Override public boolean isSymbolic(Set> visited) throws NotImplementedException, RuntimeException { + // Strings, boxed values and primitive arrays decide on their formula, not on fields + if (OVERRIDES_IS_SYMBOLIC.get(getClass())) { + return isSymbolic(); + } // If this object is already being checked, return false to avoid recursion if (!visited.add(this)) { return false; } + if (contents != null && contents.isSymbolic(visited)) { + return true; + } return fields != null && Arrays.stream(fields) .filter(Objects::nonNull) .anyMatch(field -> { @@ -286,6 +295,18 @@ public boolean isSymbolic(Set> visited) throws NotImplementedExcepti }); } + /** Whether a subclass has its own {@code isSymbolic()}, which the field walk must use. */ + private static final ClassValue OVERRIDES_IS_SYMBOLIC = new ClassValue<>() { + @Override + protected Boolean computeValue(Class type) { + try { + return type.getMethod("isSymbolic").getDeclaringClass() != ObjectValue.class; + } catch (NoSuchMethodException e) { + return false; + } + } + }; + @Override public ImmutableSet getSymbolicVariables() throws NotImplementedException, RuntimeException { if (fields == null) { diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/reference/StringBuilderValue.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/reference/StringBuilderValue.java index 06a3111b..895714d1 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/reference/StringBuilderValue.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/reference/StringBuilderValue.java @@ -1,5 +1,6 @@ package de.uzl.its.swat.symbolic.value.reference; +import com.google.common.collect.ImmutableSet; import de.uzl.its.swat.common.exceptions.NotImplementedException; import de.uzl.its.swat.common.exceptions.ValueConversionException; import de.uzl.its.swat.config.Config; @@ -9,18 +10,58 @@ import de.uzl.its.swat.symbolic.value.primitive.numeric.integral.CharValue; import de.uzl.its.swat.symbolic.value.primitive.numeric.integral.IntValue; import de.uzl.its.swat.symbolic.value.reference.array.CharArrayValue; +import de.uzl.its.swat.symbolic.value.reference.lang.BoxedValue; +import de.uzl.its.swat.symbolic.value.reference.lang.Utf16; import de.uzl.its.swat.symbolic.value.reference.lang.StringValue; +import java.util.Set; import org.objectweb.asm.Type; import org.sosy_lab.java_smt.api.*; +/** + * Shadow of a StringBuilder + * or a StringBuffer + * (the same methods, synchronized): its chars as a {@link StringValue}, which is replaced (never changed: it may be a String the + * program holds) on every change. + * + *

The index checks of the methods (when they throw an IndexOutOfBoundsException) and the + * concrete values the models rely on are recorded by the caller as branches, see + * StringCallBranches. The models below assume those hold. + * + *

After a change SWAT does not model (it returned a PlaceHolder), the chars are unknown: every + * method then returns a PlaceHolder, until the builder is recovered from the heap with its concrete + * value ({@link #resync}). + */ public final class StringBuilderValue extends ObjectValue { private final StringFormulaManager smgr; private final IntegerFormulaManager imgr; private StringValue stringValue; + + /** + * The capacity while it is the one the constructor set; null after a change that may grow it + * (the growth policy is not modelled). + */ private IntValue capacity; + /** The methods that do not change the capacity. */ + private static final Set KEEP_CAPACITY = Set.of("charAt", "codePointAt", "codePointBefore", + "codePointCount", "compareTo", "delete", "deleteCharAt", + "getChars", "indexOf", "lastIndexOf", "length", "reverse", "setCharAt", "subSequence", "substring", + "toString", "capacity"); + + /** Whether a change SWAT does not model left the chars unknown. */ + private boolean unknown = false; + + /** The methods that change the builder: if one is not modelled, the chars are unknown. */ + private static final Set MUTATORS = Set.of("", "append", "appendCodePoint", "delete", + "deleteCharAt", "insert", "replace", "reverse", "setCharAt", "setLength"); + + private static final String STRING_BUILDER = "java/lang/StringBuilder"; + public StringBuilderValue(SolverContext context) { super(context, ObjectValue.ADDRESS_UNKNOWN); + this.className = STRING_BUILDER; this.smgr = context.getFormulaManager().getStringFormulaManager(); this.imgr = context.getFormulaManager().getIntegerFormulaManager(); @@ -28,7 +69,13 @@ public StringBuilderValue(SolverContext context) { } public StringBuilderValue(SolverContext context, StringValue v, int address) { + this(context, STRING_BUILDER, v, address); + } + + /** @param className java/lang/StringBuilder or java/lang/StringBuffer */ + public StringBuilderValue(SolverContext context, String className, StringValue v, int address) { super(context, address); + this.className = className; this.smgr = context.getFormulaManager().getStringFormulaManager(); this.imgr = context.getFormulaManager().getIntegerFormulaManager(); this.stringValue = v; @@ -38,6 +85,25 @@ public StringBuilderValue(SolverContext context, StringValue v, int address) { return stringValue; } + /** The chars, or null if they are not known. */ + public StringValue getContent() { + return unknown ? null : stringValue; + } + + public boolean isUnknown() { + return unknown; + } + + /** + * Takes the concrete chars of the builder, e.g. when it is recovered from the heap after a + * change SWAT does not model. A symbolic content was lost at that change already (with a + * context loss: the call returned a PlaceHolder for a symbolic receiver). + */ + public void resync(String chars) { + this.stringValue = new StringValue(context, chars, -1); + this.unknown = false; + } + /** * Handles method invocation for Java's StringBuilder @@ -51,824 +117,329 @@ public StringBuilderValue(SolverContext context, StringValue v, int address) { */ @Override public Value invokeMethod(String name, Type[] desc, Value[] args) throws NotImplementedException, ValueConversionException { - return switch (name) { - case "" -> invokeInit(args, desc); - case "append" -> invokeAppend(args, desc); - case "appendCodePoint" -> invokeAppendCodePoint(args, desc); - case "capacity" -> invokeCapacity(args, desc); - case "charAt" -> invokeCharAt(args, desc); - case "chars" -> invokeChars(args, desc); - case "codePointAt" -> invokeCodePointAt(args, desc); - case "codePointBefore" -> invokeCodePointBefore(args, desc); - case "codePointCount" -> invokeCodePointCount(args, desc); - case "codePoints" -> invokeCodePoints(args, desc); - case "compareTo" -> invokeCompareTo(args, desc); - case "delete" -> invokeDelete(args, desc); - case "deleteCharAt" -> invokeDeleteCharAt(args, desc); - case "ensureCapacity" -> invokeEnsureCapacity(args, desc); - case "getChars" -> invokeGetChars(args, desc); - case "indexOf" -> invokeIndexOf(args, desc); - case "insert" -> invokeInsert(args, desc); - case "lastIndexOf" -> invokeLastIndexOf(args, desc); - case "length" -> invokeLength(args, desc); - case "offsetByCodePoints" -> invokeOffsetByCodePoints(args, desc); - case "replace" -> invokeReplace(args, desc); - case "reverse" -> invokeReverse(args, desc); - case "setCharAt" -> invokeSetCharAt(args, desc); - case "setLength" -> invokeSetLength(args, desc); - case "subSequence" -> invokeSubSequence(args, desc); - case "substring" -> invokeSubstring(args, desc); - case "toString" -> invokeToString(args, desc); - case "trimToSize" -> invokeTrimToSize(args, desc); - default -> PlaceHolder.instance; - }; - } - - /** - * Invocation handling for the String instance method Init(). - * Returns PlaceHolder::instance if the method is not yet implemented, or the Value that - * represents the result of the method including symbolic handling. - * - * @param args List of Values that correspond to the method arguments - * @param desc Array of Type descriptions of the methods' signature. No guarantee is given that - * the Value in args is of the same type. - * @return The resulting Value or PlaceHolder::instance - */ - private Value invokeInit(Value[] args, Type[] desc) throws NotImplementedException, ValueConversionException { - int numberOfArgs = args.length; - if (numberOfArgs == 0) { - return invokeInit(); - } else if (numberOfArgs == 1) { - return switch (desc[0].getDescriptor()) { - case "I" -> invokeInit(args[0].asIntValue()); - case "Ljava/lang/String;" -> invokeInit(args[0].asStringValue()); - case "Ljava/lang/CharSequence;" -> invokeInit(args[0].asObjectValue()); + if ((unknown || stringValue == null) && !name.equals("")) return PlaceHolder.instance; + Value result; + try { + result = switch (name) { + case "" -> invokeInit(args, desc); + case "append" -> invokeAppend(args, desc); + case "appendCodePoint" -> invokeAppendCodePoint(args); + case "capacity" -> capacity != null ? capacity : PlaceHolder.instance; + case "charAt" -> invokeCharAt(args); + case "compareTo" -> invokeCompareTo(args); + case "delete" -> invokeDelete(args); + case "deleteCharAt" -> invokeDeleteCharAt(args); + case "ensureCapacity", "trimToSize" -> VoidValue.instance; // the chars stay the same + case "getChars" -> invokeGetChars(args); + case "indexOf", "lastIndexOf", "codePointAt", "codePointBefore", "codePointCount" -> + stringValue.invokeMethod(name, desc, args); + case "insert" -> invokeInsert(args, desc); + case "length" -> new IntValue(context, stringValue.concrete.length(), smgr.length(stringValue.formula)); + case "replace" -> invokeReplace(args); + case "reverse" -> invokeReverse(); + case "setCharAt" -> invokeSetCharAt(args); + case "setLength" -> invokeSetLength(args); + case "subSequence", "substring" -> invokeSubstring(args); + case "toString" -> new StringValue(context, stringValue.concrete, stringValue.formula, -1); default -> PlaceHolder.instance; }; + } catch (NotImplementedException e) { + // e.g. a double appended, which has no model as a String + result = PlaceHolder.instance; } - return PlaceHolder.instance; - } - - private Value invokeInit() { - this.capacity = new IntValue(this.context, 16); - this.stringValue = new StringValue(this.context, "", -1); - return VoidValue.instance; - } - - private Value invokeInit(IntValue capacity) { - this.capacity = capacity; - this.stringValue = new StringValue(this.context, "", -1); - return VoidValue.instance; - } - - private Value invokeInit(@SuppressWarnings("unused") ObjectValue seq) { - return PlaceHolder.instance; - } - - private Value invokeInit(StringValue str) { - this.capacity = new IntValue(this.context, 16 + str.concrete.length()); - this.stringValue = str; - return VoidValue.instance; - } - - /** - * Invocation handling for the String instance method Append(). - * Returns PlaceHolder::instance if the method is not yet implemented, or the Value that - * represents the result of the method including symbolic handling. - * - * @param args List of Values that correspond to the method arguments - * @param desc Array of Type descriptions of the methods' signature. No guarantee is given that - * the Value in args is of the same type. - * @return The resulting Value or PlaceHolder::instance - */ - private Value invokeAppend(Value[] args, Type[] desc) throws NotImplementedException, ValueConversionException { - int numberOfArgs = args.length; - if (numberOfArgs == 1) { - return switch (desc[0].getDescriptor()) { - case "I" -> invokeAppend(args[0].asIntValue().asStringValue()); - case "Z" -> invokeAppend(args[0].asBooleanValue().asStringValue()); - case "C" -> invokeAppend(args[0].asCharValue().asStringValue()); - case "D" -> invokeAppend(args[0].asDoubleValue().asStringValue()); - case "F" -> invokeAppend(args[0].asFloatValue().asStringValue()); - case "J" -> invokeAppend(args[0].asLongValue().asStringValue()); - case "Ljava/lang/String;" -> invokeAppend(args[0].asStringValue()); - case "Ljava/lang/CharSequence;", - "Ljava/lang/StringBuffer;", - "Ljava/lang/Object;" -> invokeAppend(args[0].asObjectValue()); - case "[C" -> invokeAppend( - args[0].asObjectValue().asArrayValue().asCharArrayValue()); - default -> PlaceHolder.instance; - }; - } else if (numberOfArgs == 3) { - return switch (desc[0].getDescriptor()) { - case "[C" -> invokeAppend( - args[0].asObjectValue().asArrayValue().asCharArrayValue(), - args[1].asIntValue(), - args[2].asIntValue()); - case "Ljava/lang/CharSequence;" -> invokeAppend( - args[0].asObjectValue(), args[1].asIntValue(), args[2].asIntValue()); - default -> PlaceHolder.instance; - }; + if (!name.equals("") && !KEEP_CAPACITY.contains(name)) { + capacity = null; } - return PlaceHolder.instance; - } - - private StringBuilderValue invokeAppend(StringValue s) throws NotImplementedException, ValueConversionException { - String desc = "(Ljava.lang.String;)"; - Type[] type = Type.getArgumentTypes(desc); - this.stringValue = - (StringValue) stringValue.invokeMethod("concat", type, new StringValue[] {s}); - return this; - } - - private Value invokeAppend(CharArrayValue str) throws NotImplementedException, ValueConversionException { - int size = str.size.concrete; - for (int i = 0; i < size; i++) { - invokeAppend(str.getElement(new IntValue(str.getContext(), i)).asStringValue()); + if (result instanceof PlaceHolder && MUTATORS.contains(name)) { + unknown = true; } - - return this; - } - - private Value invokeAppend(@SuppressWarnings("unused") ObjectValue obj) { - return PlaceHolder.instance; - } - - private Value invokeAppend(ObjectValue obj, IntValue start, IntValue end) { - try { - StringValue s = obj.asStringValue(); - Type[] desc = new Type[] {Type.INT_TYPE, Type.INT_TYPE}; - Value[] args = new Value[] {start, end}; - StringValue substring = (StringValue) s.invokeMethod("substring", desc, args); - return invokeAppend(substring); - } catch (Exception ignored) { - return PlaceHolder.instance; + return result; + } + + private Value invokeInit(Value[] args, Type[] desc) { + StringValue chars; + capacity = null; + if (args.length == 0) { + chars = new StringValue(context, "", -1); + capacity = new IntValue(context, 16); + } else if (desc[0].getSort() == Type.INT) { + // new StringBuilder(int capacity) + chars = new StringValue(context, "", -1); + capacity = args[0] instanceof IntValue c ? c : null; + } else if (args.length == 1) { + // new StringBuilder(String), new StringBuilder(CharSequence): length + 16 + chars = StringValue.charsOf(args[0]); + if (chars != null) { + capacity = new IntValue(context, chars.concrete.length() + 16, + imgr.add(smgr.length(chars.formula), imgr.makeNumber(16))); + } + } else { + chars = null; } + if (chars == null) return PlaceHolder.instance; + this.stringValue = chars; + this.unknown = false; + return VoidValue.instance; } - private Value invokeAppend(CharArrayValue str, IntValue offset, IntValue len) throws NotImplementedException, ValueConversionException { - for (int i = 0; i < len.concrete; i++) { - invokeAppend( - str.getElement(new IntValue(str.getContext(), i + offset.concrete)) - .asStringValue()); - } - - return this; + private void setChars(String concrete, StringFormula formula) { + this.stringValue = new StringValue(context, concrete, formula, -1); } - /** - * Invocation handling for the String instance method AppendCodePoint(). - * Returns PlaceHolder::instance if the method is not yet implemented, or the Value that - * represents the result of the method including symbolic handling. - * - * @param args List of Values that correspond to the method arguments - * @param desc Array of Type descriptions of the methods' signature. No guarantee is given that - * the Value in args is of the same type. - * @return The resulting Value or PlaceHolder::instance - */ - private Value invokeAppendCodePoint( - @SuppressWarnings("unused") Value[] args, - @SuppressWarnings("unused") Type[] desc) { - return PlaceHolder.instance; + private StringValue concat(StringValue a, StringValue b) { + return new StringValue(context, a.concrete + b.concrete, smgr.concat(a.formula, b.formula), -1); } - /** - * Invocation handling for the String instance method Capacity(). - * Returns PlaceHolder::instance if the method is not yet implemented, or the Value that - * represents the result of the method including symbolic handling. - * - * @param args List of Values that correspond to the method arguments - * @param desc Array of Type descriptions of the methods' signature. No guarantee is given that - * the Value in args is of the same type. - * @return The resulting Value or PlaceHolder::instance - */ - private Value invokeCapacity( - @SuppressWarnings("unused") Value[] args, - @SuppressWarnings("unused") Type[] desc) { - return capacity; + private NumeralFormula.IntegerFormula length() { + return smgr.length(stringValue.formula); } - /** - * Invocation handling for the String instance method CharAt(). - * Returns PlaceHolder::instance if the method is not yet implemented, or the Value that - * represents the result of the method including symbolic handling. - * - * @param args List of Values that correspond to the method arguments - * @param desc Array of Type descriptions of the methods' signature. No guarantee is given that - * the Value in args is of the same type. - * @return The resulting Value or PlaceHolder::instance - */ - private Value invokeCharAt(Value[] args, @SuppressWarnings("unused") Type[] desc) throws NotImplementedException, ValueConversionException { - return new CharValue( - context, - this.stringValue.concrete.charAt(args[0].asIntValue().concrete), - this.smgr.charAt(this.stringValue.formula, args[0].asIntValue().asIntegerFormula())); + /** The chars from begin to end of the content. */ + private StringFormula chars(NumeralFormula.IntegerFormula begin, NumeralFormula.IntegerFormula end) { + return smgr.substring(stringValue.formula, begin, imgr.subtract(end, begin)); } /** - * Invocation handling for the String instance method Chars(). - * Returns PlaceHolder::instance if the method is not yet implemented, or the Value that - * represents the result of the method including symbolic handling. - * - * @param args List of Values that correspond to the method arguments - * @param desc Array of Type descriptions of the methods' signature. No guarantee is given that - * the Value in args is of the same type. - * @return The resulting Value or PlaceHolder::instance + * The chars a value of type {@code type} is appended or inserted as (String.valueOf), or null + * if that is not modelled. */ - private Value invokeChars( - @SuppressWarnings("unused") Value[] args, - @SuppressWarnings("unused") Type[] desc) { - return PlaceHolder.instance; + private StringValue charsOf(Value value, Type type) throws NotImplementedException, ValueConversionException { + return switch (type.getDescriptor()) { + case "I" -> value.asIntValue().asStringValue(); + case "Z" -> value.asBooleanValue().asStringValue(); + case "C" -> value.asCharValue().asStringValue(); + case "J" -> value.asLongValue().asStringValue(); + case "D" -> value.isSymbolic() ? null + : new StringValue(context, String.valueOf(value.asDoubleValue().concrete), -1); + case "F" -> value.isSymbolic() ? null + : new StringValue(context, String.valueOf(value.asFloatValue().concrete), -1); + case "[C" -> value instanceof CharArrayValue array ? array.asStringValue() : null; + default -> objectChars(value); + }; } - /** - * Invocation handling for the String instance method CodePointAt(). - * Returns PlaceHolder::instance if the method is not yet implemented, or the Value that - * represents the result of the method including symbolic handling. - * - * @param args List of Values that correspond to the method arguments - * @param desc Array of Type descriptions of the methods' signature. No guarantee is given that - * the Value in args is of the same type. - * @return The resulting Value or PlaceHolder::instance - */ - private Value invokeCodePointAt( - @SuppressWarnings("unused") Value[] args, - @SuppressWarnings("unused") Type[] desc) { - return PlaceHolder.instance; + /** String.valueOf(obj) for an Object, CharSequence or String argument, or null if not modelled. */ + private StringValue objectChars(Value obj) throws NotImplementedException, ValueConversionException { + if (obj instanceof ObjectValue o && o.isNullValue()) return new StringValue(context, "null", -1); + StringValue chars = StringValue.charsOf(obj); + if (chars != null) return chars; + if (obj instanceof BoxedValue boxed + && boxed.invokeMethod("toString", new Type[0], new Value[0]) instanceof StringValue str) { + return str; + } + return null; } - /** - * Invocation handling for the String instance method CodePointBefore(). - * Returns PlaceHolder::instance if the method is not yet implemented, or the Value that - * represents the result of the method including symbolic handling. - * - * @param args List of Values that correspond to the method arguments - * @param desc Array of Type descriptions of the methods' signature. No guarantee is given that - * the Value in args is of the same type. - * @return The resulting Value or PlaceHolder::instance - */ - private Value invokeCodePointBefore( - @SuppressWarnings("unused") Value[] args, - @SuppressWarnings("unused") Type[] desc) { - return PlaceHolder.instance; + private Value invokeAppend(Value[] args, Type[] desc) throws NotImplementedException, ValueConversionException { + StringValue chars; + if (args.length == 1) { + chars = charsOf(args[0], desc[0]); + } else if (args.length == 3 && args[0] instanceof CharArrayValue array) { + // append(char[] str, int offset, int len), by the concrete offset and len (pinned) + int offset = args[1].asIntValue().concrete; + int len = args[2].asIntValue().concrete; + chars = new StringValue(context, "", -1); + for (int i = offset; i < offset + len; i++) { + chars = concat(chars, array.getElement(new IntValue(context, i)).asStringValue()); + } + } else if (args.length == 3 && desc[0].getDescriptor().equals("Ljava/lang/CharSequence;")) { + // append(CharSequence s, int start, int end); s == null appends a part of "null" + StringValue s = objectChars(args[0]); + if (s == null) return PlaceHolder.instance; + IntValue start = args[1].asIntValue(); + IntValue end = args[2].asIntValue(); + chars = new StringValue(context, s.concrete.substring(start.concrete, end.concrete), + smgr.substring(s.formula, start.asIntegerFormula(), + imgr.subtract(end.asIntegerFormula(), start.asIntegerFormula())), -1); + } else { + chars = null; + } + if (chars == null) return PlaceHolder.instance; + setChars(stringValue.concrete + chars.concrete, smgr.concat(stringValue.formula, chars.formula)); + return this; } - /** - * Invocation handling for the String instance method CodePointCount(). - * Returns PlaceHolder::instance if the method is not yet implemented, or the Value that - * represents the result of the method including symbolic handling. - * - * @param args List of Values that correspond to the method arguments - * @param desc Array of Type descriptions of the methods' signature. No guarantee is given that - * the Value in args is of the same type. - * @return The resulting Value or PlaceHolder::instance - */ - private Value invokeCodePointCount( - @SuppressWarnings("unused") Value[] args, - @SuppressWarnings("unused") Type[] desc) { - return PlaceHolder.instance; + private Value invokeAppendCodePoint(Value[] args) throws NotImplementedException, ValueConversionException { + // A valid code point (else it throws, which StringCallBranches checks) + IntValue codePoint = args[0].asIntValue(); + String chars = new String(Character.toChars(codePoint.concrete)); + setChars(stringValue.concrete + chars, smgr.concat(stringValue.formula, stringValue.codePointString(codePoint))); + return this; } - /** - * Invocation handling for the String instance method CodePoints(). - * Returns PlaceHolder::instance if the method is not yet implemented, or the Value that - * represents the result of the method including symbolic handling. - * - * @param args List of Values that correspond to the method arguments - * @param desc Array of Type descriptions of the methods' signature. No guarantee is given that - * the Value in args is of the same type. - * @return The resulting Value or PlaceHolder::instance - */ - private Value invokeCodePoints( - @SuppressWarnings("unused") Value[] args, - @SuppressWarnings("unused") Type[] desc) { - return PlaceHolder.instance; + private Value invokeCharAt(Value[] args) throws NotImplementedException, ValueConversionException { + IntValue index = args[0].asIntValue(); + return new CharValue(context, stringValue.concrete.charAt(index.concrete), + smgr.charAt(stringValue.formula, index.asIntegerFormula())); } - /** - * Invocation handling for the String instance method CompareTo(). - * Returns PlaceHolder::instance if the method is not yet implemented, or the Value that - * represents the result of the method including symbolic handling. - * - * @param args List of Values that correspond to the method arguments - * @param desc Array of Type descriptions of the methods' signature. No guarantee is given that - * the Value in args is of the same type. - * @return The resulting Value or PlaceHolder::instance - */ - private Value invokeCompareTo( - @SuppressWarnings("unused") Value[] args, - @SuppressWarnings("unused") Type[] desc) { - return PlaceHolder.instance; + private Value invokeCompareTo(Value[] args) throws NotImplementedException, ValueConversionException { + // As the Strings of both: compareTo(StringBuilder another) compares lexicographically too. + StringValue other = StringValue.charsOf(args[0]); + if (other == null) return PlaceHolder.instance; + return stringValue.invokeMethod("compareTo", new Type[] {Type.getType(String.class)}, new Value[] {other}); } - /** - * Invocation handling for the String instance method Delete(). - * Returns PlaceHolder::instance if the method is not yet implemented, or the Value that - * represents the result of the method including symbolic handling. - * - * @param args List of Values that correspond to the method arguments - * @param desc Array of Type descriptions of the methods' signature. No guarantee is given that - * the Value in args is of the same type. - * @return The resulting Value or PlaceHolder::instance - */ - private Value invokeDelete(Value[] args, @SuppressWarnings("unused") Type[] desc) { - IntValue start = (IntValue) args[0]; - IntValue end = (IntValue) args[1]; - NumeralFormula.IntegerFormula remainingLength = - this.imgr.subtract(this.smgr.length(this.stringValue.formula), end.asIntegerFormula()); - this.stringValue.formula = - this.smgr.concat( - this.smgr.substring( - this.stringValue.formula, - new IntValue(context, 0).asIntegerFormula(), - start.asIntegerFormula()), - this.smgr.substring( - this.stringValue.formula, end.asIntegerFormula(), remainingLength)); - StringBuilder deleteBuilder = new StringBuilder(this.stringValue.concrete); - this.stringValue.concrete = deleteBuilder.delete(start.concrete, end.concrete).toString(); + private Value invokeDelete(Value[] args) throws NotImplementedException, ValueConversionException { + IntValue start = args[0].asIntValue(); + IntValue end = args[1].asIntValue(); + // An end past the length deletes up to the end; substring gives "" from there. + NumeralFormula.IntegerFormula e = end.asIntegerFormula(); + setChars(new StringBuilder(stringValue.concrete).delete(start.concrete, end.concrete).toString(), + smgr.concat(chars(imgr.makeNumber(0), start.asIntegerFormula()), + smgr.substring(stringValue.formula, e, imgr.subtract(length(), e)))); return this; } - /** - * Invocation handling for the String instance method DeleteCharAt(). - * Returns PlaceHolder::instance if the method is not yet implemented, or the Value that - * represents the result of the method including symbolic handling. - * - * @param args List of Values that correspond to the method arguments - * @param desc Array of Type descriptions of the methods' signature. No guarantee is given that - * the Value in args is of the same type. - * @return The resulting Value or PlaceHolder::instance - */ - private Value invokeDeleteCharAt( - Value[] args, @SuppressWarnings("unused") Type[] desc) throws NotImplementedException, ValueConversionException { - IntValue endIndex = - new IntValue( - this.context, - args[0].asIntValue().concrete + 1, - this.imgr.add( - args[0].asIntValue().asIntegerFormula(), - new IntValue(this.context, 1).asIntegerFormula())); - return invokeDelete( - new Value[] {args[0], endIndex}, new Type[] {Type.INT_TYPE, Type.INT_TYPE}); - } - - /** - * Invocation handling for the String instance method EnsureCapacity(). - * Returns PlaceHolder::instance if the method is not yet implemented, or the Value that - * represents the result of the method including symbolic handling. - * - * @param args List of Values that correspond to the method arguments - * @param desc Array of Type descriptions of the methods' signature. No guarantee is given that - * the Value in args is of the same type. - * @return The resulting Value or PlaceHolder::instance - */ - private Value invokeEnsureCapacity( - @SuppressWarnings("unused") Value[] args, - @SuppressWarnings("unused") Type[] desc) { - return PlaceHolder.instance; - } - - /** - * Invocation handling for the String instance method GetChars(). - * Returns PlaceHolder::instance if the method is not yet implemented, or the Value that - * represents the result of the method including symbolic handling. - * - * @param args List of Values that correspond to the method arguments - * @param desc Array of Type descriptions of the methods' signature. No guarantee is given that - * the Value in args is of the same type. - * @return The resulting Value or PlaceHolder::instance - */ - private Value invokeGetChars( - @SuppressWarnings("unused") Value[] args, - @SuppressWarnings("unused") Type[] desc) { - return PlaceHolder.instance; + private Value invokeDeleteCharAt(Value[] args) throws NotImplementedException, ValueConversionException { + IntValue index = args[0].asIntValue(); + IntValue end = new IntValue(context, index.concrete + 1, + imgr.add(index.asIntegerFormula(), imgr.makeNumber(1))); + return invokeDelete(new Value[] {index, end}); } - /** - * Invocation handling for the String instance method IndexOf(). - * Returns PlaceHolder::instance if the method is not yet implemented, or the Value that - * represents the result of the method including symbolic handling. - * - * @param args List of Values that correspond to the method arguments - * @param desc Array of Type descriptions of the methods' signature. No guarantee is given that - * the Value in args is of the same type. - * @return The resulting Value or PlaceHolder::instance - */ - private Value invokeIndexOf(Value[] args, @SuppressWarnings("unused") Type[] desc) throws NotImplementedException, ValueConversionException { - if (args.length == 1) { - return invokeIndexOf(args[0].asStringValue()); - } else if (args.length == 2) { - return invokeIndexOf(args[0].asStringValue(), args[1].asIntValue()); + private Value invokeGetChars(Value[] args) throws NotImplementedException, ValueConversionException { + // getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin), by the concrete indices (pinned) + if (!(args[2] instanceof CharArrayValue dst)) return PlaceHolder.instance; + int srcBegin = args[0].asIntValue().concrete; + int srcEnd = args[1].asIntValue().concrete; + int dstBegin = args[3].asIntValue().concrete; + for (int i = srcBegin; i < srcEnd; i++) { + dst.storeElement(new IntValue(context, dstBegin + i - srcBegin), + (CharValue) invokeCharAt(new Value[] {new IntValue(context, i)})); } - return PlaceHolder.instance; - } - - private Value invokeIndexOf(StringValue str) { - return new IntValue( - this.context, - this.stringValue.concrete.indexOf(str.concrete), - this.smgr.indexOf( - this.stringValue.formula, - str.formula, - new IntValue(this.context, 0).asIntegerFormula())); - } - - private Value invokeIndexOf(StringValue str, IntValue fromIndex) { - return new IntValue( - this.context, - this.stringValue.concrete.indexOf(str.concrete), - this.smgr.indexOf(this.stringValue.formula, str.formula, fromIndex.asIntegerFormula())); + return VoidValue.instance; } - /** - * Invocation handling for the String instance method Insert(). - * Returns PlaceHolder::instance if the method is not yet implemented, or the Value that - * represents the result of the method including symbolic handling. - * - * @param args List of Values that correspond to the method arguments - * @param desc Array of Type descriptions of the methods' signature. No guarantee is given that - * the Value in args is of the same type. - * @return The resulting Value or PlaceHolder::instance - */ private Value invokeInsert(Value[] args, Type[] desc) throws NotImplementedException, ValueConversionException { + IntValue offset = args[0].asIntValue(); + StringValue chars; if (args.length == 2) { - return switch (desc[1].getDescriptor()) { - case "I" -> invokeInsert( - args[0].asIntValue(), args[1].asIntValue().asStringValue()); - case "Z" -> invokeInsert( - args[0].asIntValue(), args[1].asBooleanValue().asStringValue()); - case "C" -> invokeInsert( - args[0].asIntValue(), args[1].asCharValue().asStringValue()); - case "D" -> invokeInsert( - args[0].asIntValue(), args[1].asDoubleValue().asStringValue()); - case "F" -> invokeInsert( - args[0].asIntValue(), args[1].asFloatValue().asStringValue()); - case "J" -> invokeInsert( - args[0].asIntValue(), args[1].asLongValue().asStringValue()); - case "Ljava/lang/String;" -> invokeInsert( - args[0].asIntValue(), args[1].asStringValue()); - case "Ljava/lang/CharSequence;", "Ljava/lang/Object;" -> invokeInsert( - args[0].asIntValue(), args[1].asObjectValue()); - case "[C" -> invokeInsert( - args[0].asIntValue(), - args[1].asObjectValue().asArrayValue().asCharArrayValue()); - default -> PlaceHolder.instance; - }; - } else if (args.length == 4) { - return switch (desc[1].getDescriptor()) { - case "Ljava/lang/CharSequence;" -> invokeInsert( - args[0].asIntValue(), - args[1].asObjectValue(), - args[2].asIntValue(), - args[3].asIntValue()); - case "[C" -> invokeInsert( - args[0].asIntValue(), - args[1].asObjectValue().asArrayValue().asCharArrayValue(), - args[2].asIntValue(), - args[3].asIntValue()); - default -> PlaceHolder.instance; - }; + chars = charsOf(args[1], desc[1]); + } else if (args.length == 4 && args[1] instanceof CharArrayValue array) { + // insert(int index, char[] str, int offset, int len), by the concrete offset and len (pinned) + int from = args[2].asIntValue().concrete; + int len = args[3].asIntValue().concrete; + chars = new StringValue(context, "", -1); + for (int i = from; i < from + len; i++) { + chars = concat(chars, array.getElement(new IntValue(context, i)).asStringValue()); + } + } else if (args.length == 4 && desc[1].getDescriptor().equals("Ljava/lang/CharSequence;")) { + // insert(int dstOffset, CharSequence s, int start, int end) + StringValue s = objectChars(args[1]); + if (s == null) return PlaceHolder.instance; + IntValue start = args[2].asIntValue(); + IntValue end = args[3].asIntValue(); + chars = new StringValue(context, s.concrete.substring(start.concrete, end.concrete), + smgr.substring(s.formula, start.asIntegerFormula(), + imgr.subtract(end.asIntegerFormula(), start.asIntegerFormula())), -1); + } else { + chars = null; } - return PlaceHolder.instance; - } - - private Value invokeInsert(IntValue offset, StringValue str) { - this.stringValue.formula = - this.smgr.concat( - smgr.substring( - this.stringValue.formula, - new IntValue(this.context, 0).asIntegerFormula(), - offset.asIntegerFormula()), - str.formula, - smgr.substring( - this.stringValue.formula, - offset.asIntegerFormula(), - smgr.length(stringValue.formula))); - this.stringValue.concrete = - this.stringValue - .concrete - .substring(0, offset.concrete) - .concat(String.valueOf(str.concrete)) - .concat(this.stringValue.concrete.substring(offset.concrete)); + if (chars == null) return PlaceHolder.instance; + NumeralFormula.IntegerFormula off = offset.asIntegerFormula(); + String c = stringValue.concrete; + setChars(c.substring(0, offset.concrete) + chars.concrete + c.substring(offset.concrete), + smgr.concat(chars(imgr.makeNumber(0), off), chars.formula, chars(off, length()))); return this; } - private Value invokeInsert( - @SuppressWarnings("unused") IntValue offset, - @SuppressWarnings("unused") ObjectValue obj) { - return PlaceHolder.instance; - } - - private Value invokeInsert(IntValue offset, CharArrayValue str) throws NotImplementedException, ValueConversionException { - int size = str.size.concrete; - StringValue newStr = new StringValue(this.context, "", -1); - for (int i = 0; i < size; i++) { - newStr.invokeMethod( - "concat", - new Type[] {Type.getType("Ljava/lang/String;")}, - new Value[] { - str.getElement(new IntValue(str.getContext(), i)).asStringValue() - }); - } - invokeInsert(offset, newStr); + private Value invokeReplace(Value[] args) throws NotImplementedException, ValueConversionException { + // replace(int start, int end, String str); an end past the length replaces up to the end + if (!(args[2] instanceof StringValue str)) return PlaceHolder.instance; + IntValue start = args[0].asIntValue(); + IntValue end = args[1].asIntValue(); + NumeralFormula.IntegerFormula e = end.asIntegerFormula(); + setChars(new StringBuilder(stringValue.concrete).replace(start.concrete, end.concrete, str.concrete).toString(), + smgr.concat(chars(imgr.makeNumber(0), start.asIntegerFormula()), str.formula, + smgr.substring(stringValue.formula, e, imgr.subtract(length(), e)))); return this; } - private Value invokeInsert( - IntValue index, CharArrayValue str, IntValue offset, IntValue len) throws NotImplementedException, ValueConversionException { - StringValue newStr = new StringValue(this.context, "", -1); - for (int i = 0; i < len.concrete; i++) { - newStr.invokeMethod( - "concat", - new Type[] {Type.getType("Ljava/lang/String;")}, - new Value[] { - str.getElement(new IntValue(str.getContext(), i + offset.concrete)) - .asStringValue() - }); + private Value invokeReverse() { + // By the concrete length (pinned, see StringCallBranches): the chars in reverse order, + // where Java then puts each surrogate pair (a low surrogate followed by a high one after + // the reversal) back in order, scanning from the start. + String reversed = new StringBuilder(stringValue.concrete).reverse().toString(); + if (!stringValue.isSymbolic()) { + setChars(reversed, new Utf16(context.getFormulaManager()).chars(reversed)); + return this; } - invokeInsert(index, newStr); - return this; - } - - private Value invokeInsert( - @SuppressWarnings("unused") IntValue index, - @SuppressWarnings("unused") ObjectValue str, - @SuppressWarnings("unused") IntValue offset, - @SuppressWarnings("unused") IntValue len) { - return PlaceHolder.instance; - } - - /** - * Invocation handling for the String instance method LastIndexOf(). - * Returns PlaceHolder::instance if the method is not yet implemented, or the Value that - * represents the result of the method including symbolic handling. - * - * @param args List of Values that correspond to the method arguments - * @param desc Array of Type descriptions of the methods' signature. No guarantee is given that - * the Value in args is of the same type. - * @return The resulting Value or PlaceHolder::instance - */ - private Value invokeLastIndexOf( - @SuppressWarnings("unused") Value[] args, - @SuppressWarnings("unused") Type[] desc) { - return PlaceHolder.instance; - } - - /** - * Invocation handling for the String instance method Length(). - * Returns PlaceHolder::instance if the method is not yet implemented, or the Value that - * represents the result of the method including symbolic handling. - * - * @param args List of Values that correspond to the method arguments - * @param desc Array of Type descriptions of the methods' signature. No guarantee is given that - * the Value in args is of the same type. - * @return The resulting Value or PlaceHolder::instance - */ - private Value invokeLength( - @SuppressWarnings("unused") Value[] args, - @SuppressWarnings("unused") Type[] desc) { - return new IntValue( - this.context, - this.stringValue.concrete.length(), - this.smgr.length(this.stringValue.formula)); - } - - /** - * Invocation handling for the String instance method OffsetByCodePoints(). - * Returns PlaceHolder::instance if the method is not yet implemented, or the Value that - * represents the result of the method including symbolic handling. - * - * @param args List of Values that correspond to the method arguments - * @param desc Array of Type descriptions of the methods' signature. No guarantee is given that - * the Value in args is of the same type. - * @return The resulting Value or PlaceHolder::instance - */ - private Value invokeOffsetByCodePoints( - @SuppressWarnings("unused") Value[] args, - @SuppressWarnings("unused") Type[] desc) { - return PlaceHolder.instance; - } - - /** - * Invocation handling for the String instance method Replace(). - * Returns PlaceHolder::instance if the method is not yet implemented, or the Value that - * represents the result of the method including symbolic handling. - * - * @param args List of Values that correspond to the method arguments - * @param desc Array of Type descriptions of the methods' signature. No guarantee is given that - * the Value in args is of the same type. - * @return The resulting Value or PlaceHolder::instance - */ - private Value invokeReplace(Value[] args, @SuppressWarnings("unused") Type[] desc) { - IntValue start = (IntValue) args[0]; - IntValue end = (IntValue) args[1]; - StringValue replacement = (StringValue) args[2]; - StringFormula substring = - this.smgr.substring( - this.stringValue.formula, - start.asIntegerFormula(), - this.imgr.subtract(end.asIntegerFormula(), start.asIntegerFormula())); - this.stringValue.formula = - smgr.replace(this.stringValue.formula, substring, replacement.formula); - StringBuilder builderReplace = new StringBuilder(this.stringValue.concrete); - this.stringValue.concrete = - builderReplace - .replace(start.concrete, end.concrete, replacement.concrete) - .toString(); - return this; - } - - /** - * Invocation handling for the String instance method Reverse(). - * Returns PlaceHolder::instance if the method is not yet implemented, or the Value that - * represents the result of the method including symbolic handling. - * - * @param args List of Values that correspond to the method arguments - * @param desc Array of Type descriptions of the methods' signature. No guarantee is given that - * the Value in args is of the same type. - * @return The resulting Value or PlaceHolder::instance - */ - private Value invokeReverse( - @SuppressWarnings("unused") Value[] args, - @SuppressWarnings("unused") Type[] desc) { - return PlaceHolder.instance; - } - - /** - * Invocation handling for the String instance method SetCharAt(). - * Returns PlaceHolder::instance if the method is not yet implemented, or the Value that - * represents the result of the method including symbolic handling. - * - * @param args List of Values that correspond to the method arguments - * @param desc Array of Type descriptions of the methods' signature. No guarantee is given that - * the Value in args is of the same type. - * @return The resulting Value or PlaceHolder::instance - */ - private Value invokeSetCharAt( - @SuppressWarnings("unused") Value[] args, - @SuppressWarnings("unused") Type[] desc) { - /* - IntValue index = args[0].asIntValue(); - CharValue newChar = args[1].asCharValue(); - - NumeralFormula.IntegerFormula secondIndex = this.imgr.add(index.formula, new IntValue(this.context, 1).formula); - NumeralFormula.IntegerFormula lengthOfSecondSubstring = - this.imgr.subtract(this.smgr.length(this.stringValue.formula), secondIndex); - this.stringValue.formula = this.smgr.concat( - this.smgr.substring(this.stringValue.formula, new IntValue(this.context, 0).formula, index.formula), - newChar.formula, - this.smgr.substring(this.stringValue.formula, this.imgr.add(index.formula, secondIndex), lengthOfSecondSubstring)); - - StringBuilder builderSetCharAt = new StringBuilder(this.stringValue.concrete); - builderSetCharAt.setCharAt(index.concrete, newChar.concrete); - this.stringValue.concrete = builderSetCharAt.toString(); + int n = stringValue.concrete.length(); + if (n > 256) return PlaceHolder.instance; + BooleanFormulaManager bmgr = context.getFormulaManager().getBooleanFormulaManager(); + Utf16 chars = new Utf16(context.getFormulaManager()); + NumeralFormula.IntegerFormula[] r = new NumeralFormula.IntegerFormula[n]; + for (int i = 0; i < n; i++) r[i] = chars.codeAt(stringValue.formula, imgr.makeNumber(n - 1 - i)); + // swapped[i]: the pair at i, i + 1 is put back in order + BooleanFormula[] swapped = new BooleanFormula[n]; + for (int i = 0; i < n; i++) { + swapped[i] = i + 1 < n + ? bmgr.and(i > 0 ? bmgr.not(swapped[i - 1]) : bmgr.makeTrue(), chars.isLowSurrogate(r[i]), chars.isHighSurrogate(r[i + 1])) + : bmgr.makeFalse(); } - */ - return PlaceHolder.instance; - } - - /** - * Invocation handling for the String instance method SetLength(). - * Returns PlaceHolder::instance if the method is not yet implemented, or the Value that - * represents the result of the method including symbolic handling. - * - * @param args List of Values that correspond to the method arguments - * @param desc Array of Type descriptions of the methods' signature. No guarantee is given that - * the Value in args is of the same type. - * @return The resulting Value or PlaceHolder::instance - */ - private Value invokeSetLength( - @SuppressWarnings("unused") Value[] args, - @SuppressWarnings("unused") Type[] desc) { - // ToDo this does not work! Null bytes need to be append to fill the sequence! - /* - IntValue newLength = args[0].asIntValue(); - this.stringValue.formula = this.smgr.substring(this.stringValue.formula, new IntValue(this.context, 0).formula, newLength.formula); - // Does this behave correctly for newLength > length(concrete) and for newLength == 0? - this.stringValue.concrete = this.stringValue.concrete.substring(0, newLength.concrete); - - */ - return PlaceHolder.instance; + StringFormula result = smgr.makeString(""); + for (int i = 0; i < n; i++) { + NumeralFormula.IntegerFormula c = r[i]; + if (i + 1 < n) c = bmgr.ifThenElse(swapped[i], r[i + 1], c); + if (i > 0) c = bmgr.ifThenElse(swapped[i - 1], r[i - 1], c); + result = smgr.concat(result, smgr.fromCodePoint(c)); + } + setChars(reversed, result); + return this; } - /** - * Invocation handling for the String instance method SubSequence(). - * Returns PlaceHolder::instance if the method is not yet implemented, or the Value that - * represents the result of the method including symbolic handling. - * - * @param args List of Values that correspond to the method arguments - * @param desc Array of Type descriptions of the methods' signature. No guarantee is given that - * the Value in args is of the same type. - * @return The resulting Value or PlaceHolder::instance - */ - private Value invokeSubSequence( - @SuppressWarnings("unused") Value[] args, - @SuppressWarnings("unused") Type[] desc) { - return PlaceHolder.instance; + private Value invokeSetCharAt(Value[] args) throws NotImplementedException, ValueConversionException { + IntValue index = args[0].asIntValue(); + CharValue ch = args[1].asCharValue(); + NumeralFormula.IntegerFormula i = index.asIntegerFormula(); + StringBuilder sb = new StringBuilder(stringValue.concrete); + sb.setCharAt(index.concrete, ch.concrete); + setChars(sb.toString(), smgr.concat(chars(imgr.makeNumber(0), i), ch.asStringValue().formula, + chars(imgr.add(i, imgr.makeNumber(1)), length()))); + return VoidValue.instance; } - /** - * Invocation handling for the String instance method Substring(). - * Returns PlaceHolder::instance if the method is not yet implemented, or the Value that - * represents the result of the method including symbolic handling. - * - * @param args List of Values that correspond to the method arguments - * @param desc Array of Type descriptions of the methods' signature. No guarantee is given that - * the Value in args is of the same type. - * @return The resulting Value or PlaceHolder::instance - */ - private Value invokeSubstring( - Value[] args, @SuppressWarnings("unused") Type[] desc) throws NotImplementedException, ValueConversionException { - if (args.length == 1) { - return invokeSubstring(args[0].asIntValue()); - } else if (args.length == 2) { - return invokeSubstring(args[0].asIntValue(), args[1].asIntValue()); - } - return PlaceHolder.instance; + private Value invokeSetLength(Value[] args) throws NotImplementedException, ValueConversionException { + // By the concrete length (pinned): the first n chars of the content padded with n '\0's. + int n = args[0].asIntValue().concrete; + StringBuilder sb = new StringBuilder(stringValue.concrete); + sb.setLength(n); + setChars(sb.toString(), smgr.substring(smgr.concat(stringValue.formula, smgr.makeString("\0".repeat(n))), + imgr.makeNumber(0), imgr.makeNumber(n))); + return VoidValue.instance; } - private Value invokeSubstring(IntValue start) { - NumeralFormula.IntegerFormula remainingLength = - this.imgr.subtract(this.smgr.length(this.stringValue.formula), start.asIntegerFormula()); - this.stringValue.formula = - this.smgr.substring(this.stringValue.formula, start.asIntegerFormula(), remainingLength); - this.stringValue.concrete = this.stringValue.concrete.substring(start.concrete); - return new StringValue( - this.context, this.stringValue.concrete, this.stringValue.formula, -1); + private Value invokeSubstring(Value[] args) throws NotImplementedException, ValueConversionException { + // substring(start) is substring(start, length()); subSequence(start, end) is substring(start, end) + if (args.length != 1 && args.length != 2) return PlaceHolder.instance; + IntValue start = args[0].asIntValue(); + IntValue end = args.length == 2 ? args[1].asIntValue() + : new IntValue(context, stringValue.concrete.length(), length()); + return new StringValue(context, stringValue.concrete.substring(start.concrete, end.concrete), + chars(start.asIntegerFormula(), end.asIntegerFormula()), -1); } - private Value invokeSubstring(IntValue start, IntValue end) { - this.stringValue.formula = - this.smgr.substring(this.stringValue.formula, start.asIntegerFormula(), end.asIntegerFormula()); - this.stringValue.concrete = - this.stringValue.concrete.substring(start.concrete, end.concrete); - return new StringValue( - this.context, this.stringValue.concrete, this.stringValue.formula, -1); + @Override + public boolean isSymbolic() { + return stringValue != null && stringValue.isSymbolic(); } - /** - * Invocation handling for the String instance method ToString(). - * Returns PlaceHolder::instance if the method is not yet implemented, or the Value that - * represents the result of the method including symbolic handling. - * - * @param args List of Values that correspond to the method arguments - * @param desc Array of Type descriptions of the methods' signature. No guarantee is given that - * the Value in args is of the same type. - * @return The resulting Value or PlaceHolder::instance - */ - private Value invokeToString( - @SuppressWarnings("unused") Value[] args, - @SuppressWarnings("unused") Type[] desc) { - return new StringValue( - this.context, this.stringValue.concrete, this.stringValue.formula, -1); + @Override + public boolean isSymbolic(Set> visited) { + return isSymbolic(); } - /** - * Invocation handling for the String instance method TrimToSize(). - * Returns PlaceHolder::instance if the method is not yet implemented, or the Value that - * represents the result of the method including symbolic handling. - * - * @param args List of Values that correspond to the method arguments - * @param desc Array of Type descriptions of the methods' signature. No guarantee is given that - * the Value in args is of the same type. - * @return The resulting Value or PlaceHolder::instance - */ - private Value invokeTrimToSize( - @SuppressWarnings("unused") Value[] args, - @SuppressWarnings("unused") Type[] desc) { - return PlaceHolder.instance; + @Override + public ImmutableSet getSymbolicVariables() { + return stringValue == null ? ImmutableSet.of() : stringValue.getSymbolicVariables(); } @Override - public StringValue asStringValue() { + public StringValue asStringValue() throws NotImplementedException { + if (unknown || stringValue == null) { + throw new NotImplementedException("The chars of this StringBuilder are not known"); + } return this.stringValue; } @@ -886,8 +457,9 @@ public String toString() { formulaString.substring(0, Config.instance().getLoggingFormulaLength()) + "..."; } - return "Ljava/lang/StringBuilder @" + return "L" + className + " @" + Integer.toHexString(address) + + (unknown ? " (unknown)" : "") + " (" + concreteString + ", " diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/reference/array/AbstractArrayValue.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/reference/array/AbstractArrayValue.java index 361c8f06..f60056c0 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/reference/array/AbstractArrayValue.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/reference/array/AbstractArrayValue.java @@ -4,6 +4,7 @@ import de.uzl.its.swat.common.exceptions.NotImplementedException; import de.uzl.its.swat.common.exceptions.NotSupportedException; import de.uzl.its.swat.config.Config; +import de.uzl.its.swat.symbolic.trace.SymbolRegistry; import de.uzl.its.swat.symbolic.value.Value; import de.uzl.its.swat.symbolic.value.primitive.numeric.integral.IntValue; import de.uzl.its.swat.symbolic.value.reference.ObjectValue; @@ -60,12 +61,11 @@ public AbstractArrayValue( super(context, address); this.context = context; this.amgr = context.getFormulaManager().getArrayFormulaManager(); - Value.symbol = Value.symbol + Value.inc; this.indexFormulaType = indexFormulaType; this.elementFormulaType = elementFormulaType; formulaType = FormulaType.getArrayType(indexFormulaType, elementFormulaType); - this.formula = amgr.makeArray(symbolicPrefix + "_" + (Value.symbol - Value.inc), formulaType); + this.formula = makeBaseArray(amgr, symbolicPrefix, formulaType); this.size = size; this.parentRefIdx = null; this.parentRef = null; @@ -88,6 +88,19 @@ public AbstractArrayValue( this.size = size; } + /** + * Creates the base symbol of an allocated array, which its initial contents are stored over. + * Its name is outside the input namespace ({@code _}): an input array of the same + * prefix and index would otherwise share, and constrain, the allocated array's contents. + */ + protected static ArrayFormula makeBaseArray( + ArrayFormulaManager amgr, String symbolicPrefix, FormulaType.ArrayFormulaType type) { + String name = symbolicPrefix + "_alloc" + Value.symbol; + Value.symbol = Value.symbol + Value.inc; + SymbolRegistry.declare(name, SymbolRegistry.Kind.ARRAY_BASE); + return amgr.makeArray(name, type); + } + /** * Returns the default value for the type of array. * @@ -256,7 +269,7 @@ public BooleanFormula getBounds(boolean upper) { @Override public boolean isSymbolic() { - return !context.getFormulaManager().extractVariables(formula).isEmpty(); + return !context.getFormulaManager().extractVariables(formula).isEmpty() || size.isSymbolic(); } @Override diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/reference/array/ArrayArrayValue.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/reference/array/ArrayArrayValue.java index 1b03526f..2a6847a8 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/reference/array/ArrayArrayValue.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/reference/array/ArrayArrayValue.java @@ -46,8 +46,7 @@ public ArrayArrayValue( } this.dims = dims; this.desc = desc; - Value.symbol = Value.symbol + Value.inc; - this.formula = amgr.makeArray("A_" + (Value.symbol - Value.inc), formulaType); + this.formula = makeBaseArray(amgr, "A", formulaType); this.parentRefIdx = parentRefIdx; this.parentRef = parentRef; initSymbolicArray(); diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/reference/array/ObjectArrayValue.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/reference/array/ObjectArrayValue.java index 1910f6c8..416093a8 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/reference/array/ObjectArrayValue.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/reference/array/ObjectArrayValue.java @@ -1,6 +1,7 @@ package de.uzl.its.swat.symbolic.value.reference.array; import de.uzl.its.swat.common.exceptions.NoThreadContextException; +import de.uzl.its.swat.common.exceptions.NotImplementedException; import de.uzl.its.swat.symbolic.value.Value; import de.uzl.its.swat.symbolic.value.primitive.numeric.integral.IntValue; import de.uzl.its.swat.symbolic.value.reference.ObjectValue; @@ -88,4 +89,14 @@ public IntValue getSize() { public String toString() { return "[" + className + " @" + Integer.toHexString(address) + " (" + size + " x 1" + ")"; } + + /** Symbolic if an element is, or if the length is (e.g. {@code new Object[n]} for a symbolic n). */ + @Override + @SuppressWarnings({"rawtypes", "unchecked"}) + public boolean isSymbolic(java.util.Set visited) throws NotImplementedException, RuntimeException { + if (!visited.contains(this) && size != null && size.isSymbolic()) { + return true; + } + return super.isSymbolic(visited); + } } diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/reference/array/StringArrayValue.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/reference/array/StringArrayValue.java index efe5c9ce..2018ca00 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/reference/array/StringArrayValue.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/reference/array/StringArrayValue.java @@ -101,7 +101,8 @@ protected void initArray(String[] array) { StringFormulaManager smgr = context.getFormulaManager().getStringFormulaManager(); // ToDo (Nils): Is this needed or correct? for (int i = 0; i < array.length; i++) { - formula = amgr.store(formula, getIndex(i), smgr.makeString(concrete[i])); + formula = amgr.store(formula, getIndex(i), + StringValue.literal(smgr, context.getFormulaManager().getIntegerFormulaManager(), concrete[i])); } } diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/reference/lang/CaseMapping.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/reference/lang/CaseMapping.java new file mode 100644 index 00000000..883c9e9b --- /dev/null +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/reference/lang/CaseMapping.java @@ -0,0 +1,125 @@ +package de.uzl.its.swat.symbolic.value.reference.lang; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import org.sosy_lab.java_smt.api.BooleanFormula; +import org.sosy_lab.java_smt.api.BooleanFormulaManager; +import org.sosy_lab.java_smt.api.FormulaManager; +import org.sosy_lab.java_smt.api.IntegerFormulaManager; +import org.sosy_lab.java_smt.api.NumeralFormula.IntegerFormula; +import org.sosy_lab.java_smt.api.RegexFormula; +import org.sosy_lab.java_smt.api.StringFormula; +import org.sosy_lab.java_smt.api.StringFormulaManager; + +/** + * Java's ignore-case comparisons as formulas: against a concrete string as a regular expression + * of the chars' case classes (tabulated from the running JVM), and of two ASCII strings char by + * char. + * + *

Beyond ASCII the case mappings are tables of a few hundred ranges; unrolled over a string, Z3 + * does not decide queries over them (even simple ones time out), so a model separates the ASCII + * strings by a branch and leaves the others unmodelled. + */ +public final class CaseMapping { + + /** The chars by their key Character.toLowerCase(Character.toUpperCase(c)), by which the ignore-case comparisons compare. */ + private static final class FoldClasses { + static final Map> CLASSES = new HashMap<>(); + + static { + for (int c = 0; c <= 0xFFFF; c++) { + CLASSES.computeIfAbsent(fold(c), k -> new ArrayList<>()).add(c); + } + } + } + + private final BooleanFormulaManager bmgr; + private final IntegerFormulaManager imgr; + private final StringFormulaManager smgr; + + public CaseMapping(FormulaManager fmgr) { + this.bmgr = fmgr.getBooleanFormulaManager(); + this.imgr = fmgr.getIntegerFormulaManager(); + this.smgr = fmgr.getStringFormulaManager(); + } + + /** The key two chars are compared by when case is ignored. */ + static int fold(int c) { + return Character.toLowerCase(Character.toUpperCase(c)); + } + + /** + * Whether String.toLowerCase/toUpperCase with this locale maps ASCII letters to ASCII letters: + * Java has special rules only for Turkish, Azerbaijani ('I' to dotless 'ı') and Lithuanian. + */ + public static boolean rootRules(Locale locale) { + String lang = locale.getLanguage(); + return !lang.equals("tr") && !lang.equals("az") && !lang.equals("lt"); + } + + /** + * The strings equal to t ignoring case (t has no surrogates): at each position, the chars with + * the same key as t's char there. A surrogate of the other string matches none of them, also + * as part of a pair (Java compares the pair's code point then, whose key is not one of t's). + */ + public RegexFormula equalIgnoringCase(String t) { + RegexFormula regex = smgr.makeRegex(""); + for (char c : t.toCharArray()) { + RegexFormula chars = null; + for (int other : FoldClasses.CLASSES.get(fold(c))) { + RegexFormula one = smgr.range((char) other, (char) other); + chars = chars == null ? one : smgr.union(chars, one); + } + regex = smgr.concat(regex, chars); + } + return regex; + } + + /** Whether all chars of s are ASCII (below 128). */ + public BooleanFormula isAscii(StringFormula s) { + return smgr.in(s, smgr.closure(smgr.range('\u0000', '\u007f'))); + } + + /** The code of the char of s at index i */ + public IntegerFormula codeAt(StringFormula s, IntegerFormula i) { + return smgr.toCodePoint(smgr.charAt(s, i)); + } + + private BooleanFormula between(IntegerFormula c, char lo, char hi) { + return bmgr.and(imgr.greaterOrEquals(c, imgr.makeNumber(lo)), imgr.lessOrEquals(c, imgr.makeNumber(hi))); + } + + /** The key of an ASCII char: the lower case of a letter, else the char itself. */ + public IntegerFormula foldAscii(IntegerFormula c) { + return bmgr.ifThenElse(between(c, 'A', 'Z'), imgr.add(c, imgr.makeNumber('a' - 'A')), c); + } + + /** The result of an ignore-case comparison: whether no char differed, and the int compareToIgnoreCase returns. */ + public record Comparison(BooleanFormula noDifference, IntegerFormula value) {} + + /** + * Java's ignore-case comparison of a[aStart, aStart + tlen) with b[bStart, bStart + olen), both + * ASCII, char by char over the concrete lengths: the difference of the keys of the first chars + * that differ, else tlen - olen. + */ + public Comparison compareAsciiIgnoringCase(StringFormula a, IntegerFormula aStart, int tlen, + StringFormula b, IntegerFormula bStart, int olen) { + int n = Math.min(tlen, olen); + List equal = new ArrayList<>(); + IntegerFormula value = imgr.makeNumber(tlen - olen); + IntegerFormula[] diff = new IntegerFormula[n]; + BooleanFormula[] same = new BooleanFormula[n]; + for (int k = 0; k < n; k++) { + IntegerFormula x = foldAscii(codeAt(a, imgr.add(aStart, imgr.makeNumber(k)))); + IntegerFormula y = foldAscii(codeAt(b, imgr.add(bStart, imgr.makeNumber(k)))); + same[k] = imgr.equal(x, y); + diff[k] = imgr.subtract(x, y); + equal.add(same[k]); + } + for (int k = n - 1; k >= 0; k--) value = bmgr.ifThenElse(same[k], value, diff[k]); + return new Comparison(bmgr.and(equal), value); + } +} diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/reference/lang/DoubleObjectValue.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/reference/lang/DoubleObjectValue.java index afd1e0a1..b849dffc 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/reference/lang/DoubleObjectValue.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/reference/lang/DoubleObjectValue.java @@ -5,6 +5,7 @@ import de.uzl.its.swat.common.exceptions.NotImplementedException; import de.uzl.its.swat.common.exceptions.SWATAssert; import de.uzl.its.swat.common.exceptions.ValueConversionException; +import de.uzl.its.swat.symbolic.value.primitive.numeric.BitvectorEncoding; import de.uzl.its.swat.symbolic.value.PlaceHolder; import de.uzl.its.swat.symbolic.value.Value; import de.uzl.its.swat.symbolic.value.VoidValue; @@ -267,18 +268,9 @@ public BooleanFormula getBounds(boolean upper) { DoubleObjectValue other = args[0].asDoubleObjectValue(); int result = val.concrete.compareTo(other.val.concrete); - // Use floating point formula managers for proper symbolic handling - BooleanFormula equal = fpfm.equalWithFPSemantics(val.formula, other.val.formula); - BooleanFormula greater = fpfm.greaterThan(val.formula, other.val.formula); - - // Create the symbolic result using if-then-else logic - var imgr = context.getFormulaManager().getIntegerFormulaManager(); - var symbolicResult = context.getFormulaManager().getBooleanFormulaManager().ifThenElse(equal, - imgr.makeNumber(0), - context.getFormulaManager().getBooleanFormulaManager().ifThenElse(greater, - imgr.makeNumber(1), - imgr.makeNumber(-1))); - + // Double.compareTo semantics: NaN is greater than everything, -0.0 is less than 0.0 + var symbolicResult = BitvectorEncoding.javaCompare(context.getFormulaManager(), val.formula, other.val.formula); + return new IntValue(context, result, symbolicResult); } else { return PlaceHolder.instance; diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/reference/lang/FloatObjectValue.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/reference/lang/FloatObjectValue.java index 8df3ffde..98b92dfa 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/reference/lang/FloatObjectValue.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/reference/lang/FloatObjectValue.java @@ -11,6 +11,7 @@ import de.uzl.its.swat.common.exceptions.NotImplementedException; import de.uzl.its.swat.common.exceptions.SWATAssert; import de.uzl.its.swat.common.exceptions.ValueConversionException; +import de.uzl.its.swat.symbolic.value.primitive.numeric.BitvectorEncoding; import de.uzl.its.swat.symbolic.value.PlaceHolder; import de.uzl.its.swat.symbolic.value.Value; import de.uzl.its.swat.symbolic.value.VoidValue; @@ -264,18 +265,9 @@ public String MAKE_SYMBOLIC() throws NullPointerException { FloatObjectValue other = args[0].asFloatObjectValue(); int result = val.concrete.compareTo(other.val.concrete); - // Use floating point formula managers for proper symbolic handling - BooleanFormula equal = fpfm.equalWithFPSemantics(val.formula, other.val.formula); - BooleanFormula greater = fpfm.greaterThan(val.formula, other.val.formula); - - // Create the symbolic result using if-then-else logic - var imgr = context.getFormulaManager().getIntegerFormulaManager(); - var symbolicResult = context.getFormulaManager().getBooleanFormulaManager().ifThenElse(equal, - imgr.makeNumber(0), - context.getFormulaManager().getBooleanFormulaManager().ifThenElse(greater, - imgr.makeNumber(1), - imgr.makeNumber(-1))); - + // Float.compareTo semantics: NaN is greater than everything, -0.0 is less than 0.0 + var symbolicResult = BitvectorEncoding.javaCompare(context.getFormulaManager(), val.formula, other.val.formula); + return new IntValue(context, result, symbolicResult); } else { return PlaceHolder.instance; diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/reference/lang/JavaRegex.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/reference/lang/JavaRegex.java new file mode 100644 index 00000000..4cc15486 --- /dev/null +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/reference/lang/JavaRegex.java @@ -0,0 +1,285 @@ +package de.uzl.its.swat.symbolic.value.reference.lang; + +import java.util.ArrayList; +import java.util.List; +import org.sosy_lab.java_smt.api.RegexFormula; +import org.sosy_lab.java_smt.api.StringFormulaManager; + +/** + * Translates the simple Java regular expressions (java.util.regex.Pattern, no flags) whose + * language an SMT regular expression describes exactly: literal chars, the escaped + * metacharacters, positive character classes ([a-z0-9_], \d, \s, \w), groups, alternation and the + * greedy and reluctant quantifiers. Their atoms match only chars outside the surrogates, so a + * surrogate (alone or in a pair, which Java matches as one code point) matches none of them, char + * by char as in Java. + * + *

Everything else (".", negated classes, anchors, back references, lookaround, possessive + * quantifiers, inline flags, ...) is not translated: {@link #toRegex} then returns null. + */ +public final class JavaRegex { + private static final String METACHARS = ".$^|?*+()[]{}\\"; + + private final StringFormulaManager smgr; + private final String pattern; + private int pos = 0; + + private JavaRegex(StringFormulaManager smgr, String pattern) { + this.smgr = smgr; + this.pattern = pattern; + } + + /** The SMT regular expression for the Java one, or null if it is not one of the simple ones. */ + public static RegexFormula toRegex(StringFormulaManager smgr, String pattern) { + JavaRegex parser = new JavaRegex(smgr, pattern); + try { + RegexFormula regex = parser.alternation(); + return parser.pos == pattern.length() ? regex : null; + } catch (Unsupported e) { + return null; + } + } + + /** + * The literal a regular expression stands for if it has no construct but literal chars and + * escaped metacharacters (e.g. "\\*"), else null. + */ + public static String literal(String pattern) { + StringBuilder chars = new StringBuilder(); + for (int i = 0; i < pattern.length(); i++) { + char c = pattern.charAt(i); + if (c == '\\') { + if (i + 1 == pattern.length()) return null; + char next = pattern.charAt(++i); + if (Character.isLetterOrDigit(next) || Character.isSurrogate(next)) return null; + chars.append(next); + } else if (METACHARS.indexOf(c) >= 0 || Character.isSurrogate(c)) { + return null; + } else { + chars.append(c); + } + } + return chars.toString(); + } + + /** Whether a replacement string of replaceAll/replaceFirst stands for itself (no '$' or '\'). */ + public static boolean literalReplacement(String replacement) { + return replacement.indexOf('$') < 0 && replacement.indexOf('\\') < 0; + } + + private static final class Unsupported extends Exception { + Unsupported() { + super(null, null, false, false); + } + } + + private boolean more() { + return pos < pattern.length(); + } + + private char peek() { + return pattern.charAt(pos); + } + + private RegexFormula alternation() throws Unsupported { + RegexFormula regex = sequence(); + while (more() && peek() == '|') { + pos++; + regex = smgr.union(regex, sequence()); + } + return regex; + } + + private RegexFormula sequence() throws Unsupported { + List parts = new ArrayList<>(); + while (more() && peek() != '|' && peek() != ')') { + parts.add(quantified(atom())); + } + if (parts.isEmpty()) return smgr.makeRegex(""); + return parts.size() == 1 ? parts.get(0) : smgr.concat(parts.toArray(new RegexFormula[0])); + } + + private RegexFormula quantified(RegexFormula atom) throws Unsupported { + RegexFormula regex = atom; + if (!more()) return regex; + char c = peek(); + if (c == '*') { + pos++; + regex = smgr.closure(atom); + } else if (c == '+') { + pos++; + regex = smgr.cross(atom); + } else if (c == '?') { + pos++; + regex = smgr.optional(atom); + } else if (c == '{') { + pos++; + int min = number(); + int max = min; + if (more() && peek() == ',') { + pos++; + max = more() && peek() == '}' ? -1 : number(); + } + expect('}'); + if (max != -1 && max < min) throw new Unsupported(); + regex = repeat(atom, min); + if (max == -1) { + regex = smgr.concat(regex, smgr.closure(atom)); + } else { + for (int i = min; i < max; i++) regex = smgr.concat(regex, smgr.optional(atom)); + } + } else { + return regex; + } + // Reluctant matching does not change which strings match; possessive matching does. + if (more() && peek() == '?') { + pos++; + } else if (more() && peek() == '+') { + throw new Unsupported(); + } + if (more() && (peek() == '*' || peek() == '+' || peek() == '?' || peek() == '{')) throw new Unsupported(); + return regex; + } + + /** atom, n times */ + private RegexFormula repeat(RegexFormula atom, int n) { + RegexFormula regex = smgr.makeRegex(""); + for (int i = 0; i < n; i++) regex = smgr.concat(regex, atom); + return regex; + } + + private int number() throws Unsupported { + int start = pos; + while (more() && Character.isDigit(peek()) && peek() < 128) pos++; + if (pos == start || pos - start > 4) throw new Unsupported(); + return Integer.parseInt(pattern.substring(start, pos)); + } + + private void expect(char c) throws Unsupported { + if (!more() || peek() != c) throw new Unsupported(); + pos++; + } + + private RegexFormula atom() throws Unsupported { + char c = peek(); + switch (c) { + case '(' -> { + pos++; + if (more() && peek() == '?') { + // only the non-capturing group (?:...) + pos++; + expect(':'); + } + RegexFormula inner = alternation(); + expect(')'); + return inner; + } + case '[' -> { + pos++; + return characterClass(); + } + case '\\' -> { + pos++; + return escape(false); + } + default -> { + if (METACHARS.indexOf(c) >= 0 || c == ']' || c == '}') throw new Unsupported(); + pos++; + return single(c); + } + } + } + + private RegexFormula single(char c) throws Unsupported { + if (Character.isSurrogate(c)) throw new Unsupported(); + return smgr.range(c, c); + } + + /** After a backslash: a predefined class, a control char, or an escaped non-alphanumeric char. */ + private RegexFormula escape(boolean inClass) throws Unsupported { + if (!more()) throw new Unsupported(); + char c = pattern.charAt(pos++); + return switch (c) { + case 'd' -> smgr.range('0', '9'); + case 's' -> smgr.union(smgr.range(' ', ' '), smgr.range('\t', '\r')); // [ \t\n\x0B\f\r] + case 'w' -> smgr.union(smgr.union(smgr.range('a', 'z'), smgr.range('A', 'Z')), + smgr.union(smgr.range('0', '9'), smgr.range('_', '_'))); + case 't' -> single('\t'); + case 'n' -> single('\n'); + case 'r' -> single('\r'); + case 'f' -> single('\f'); + default -> { + if (Character.isLetterOrDigit(c) || Character.isSurrogate(c)) throw new Unsupported(); + yield single(c); + } + }; + } + + /** A positive class [...] of chars, ranges and predefined classes. */ + private RegexFormula characterClass() throws Unsupported { + if (more() && peek() == '^') throw new Unsupported(); + RegexFormula regex = null; + boolean first = true; + while (more() && (peek() != ']' || first)) { + char c = peek(); + RegexFormula part; + if (c == '[' || (c == '&' && pos + 1 < pattern.length() && pattern.charAt(pos + 1) == '&')) { + throw new Unsupported(); + } else if (c == '\\') { + pos++; + char next = more() ? peek() : 0; + if (next == 'd' || next == 's' || next == 'w') { + part = escape(true); + if (pos + 1 < pattern.length() && peek() == '-' && pattern.charAt(pos + 1) != ']') throw new Unsupported(); + } else { + part = rangeFrom(escapedChar()); + } + } else { + pos++; + part = rangeFrom(c); + } + regex = regex == null ? part : smgr.union(regex, part); + first = false; + } + expect(']'); + if (regex == null) throw new Unsupported(); + return regex; + } + + /** The char of an escape inside a class (not a predefined class). */ + private char escapedChar() throws Unsupported { + if (!more()) throw new Unsupported(); + char c = pattern.charAt(pos++); + return switch (c) { + case 't' -> '\t'; + case 'n' -> '\n'; + case 'r' -> '\r'; + case 'f' -> '\f'; + default -> { + if (Character.isLetterOrDigit(c) || Character.isSurrogate(c)) throw new Unsupported(); + yield c; + } + }; + } + + /** The char from, or the range from-to if a '-' and an end follow. */ + private RegexFormula rangeFrom(char from) throws Unsupported { + if (pos + 1 < pattern.length() && peek() == '-' && pattern.charAt(pos + 1) != ']') { + pos++; + char to; + if (peek() == '\\') { + pos++; + to = escapedChar(); + } else if (peek() == '[') { + throw new Unsupported(); + } else { + to = pattern.charAt(pos++); + } + if (to < from || Character.isSurrogate(from) || Character.isSurrogate(to) + || (from < Character.MIN_SURROGATE && to > Character.MAX_SURROGATE)) { + throw new Unsupported(); + } + return smgr.range(from, to); + } + return single(from); + } +} diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/reference/lang/StringValue.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/reference/lang/StringValue.java index ebba9054..7448119f 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/reference/lang/StringValue.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/reference/lang/StringValue.java @@ -5,7 +5,7 @@ import de.uzl.its.swat.common.exceptions.SWATAssert; import de.uzl.its.swat.common.exceptions.ValueConversionException; import de.uzl.its.swat.config.Config; -import de.uzl.its.swat.symbolic.UFs.EqualsIgnoreCaseUF; +import de.uzl.its.swat.symbolic.trace.SymbolRegistry; import de.uzl.its.swat.symbolic.value.PlaceHolder; import de.uzl.its.swat.symbolic.value.Value; import de.uzl.its.swat.symbolic.value.VoidValue; @@ -29,6 +29,7 @@ public class StringValue extends ObjectValue { static final int REPLACE_COUNT = 10; private StringFormulaManager smgr; + private IntegerFormulaManager imgr; /** * Flag indicating this string was explicitly created via new String() in user code, @@ -42,13 +43,26 @@ public class StringValue extends ObjectValue { public StringValue(SolverContext context, String concrete, int address) { super(context, address); this.smgr = context.getFormulaManager().getStringFormulaManager(); + this.imgr = context.getFormulaManager().getIntegerFormulaManager(); this.concrete = concrete; - this.formula = smgr.makeString(concrete); + this.formula = literal(smgr, imgr, concrete); + } + + /** + * A Java string as the solver's string: one char per UTF-16 char. makeString would take a + * surrogate pair for one char (and rejects code points above the third plane). + */ + public static StringFormula literal(StringFormulaManager smgr, IntegerFormulaManager imgr, String s) { + if (s.chars().noneMatch(c -> Character.isSurrogate((char) c))) return smgr.makeString(s); + StringFormula chars = smgr.makeString(""); + for (char c : s.toCharArray()) chars = smgr.concat(chars, smgr.fromCodePoint(imgr.makeNumber(c))); + return chars; } public StringValue(SolverContext context, String concrete, StringFormula formula, int address) { super(context, address); this.smgr = context.getFormulaManager().getStringFormulaManager(); + this.imgr = context.getFormulaManager().getIntegerFormulaManager(); this.concrete = concrete; this.formula = formula; } @@ -69,6 +83,7 @@ public BooleanFormula IF_ACMPNE(ObjectValue o2) { return super.IF_ACMPNE(o2); } } + /** * Compares if two references are identical * @@ -91,10 +106,10 @@ public Object getConcrete() { } public String MAKE_SYMBOLIC(String prefixOrIdx) { - if (prefixOrIdx.matches("-?\\d+")){ + if (prefixOrIdx.matches("-?\\d+")) { // We assume a constructed idx was passed as it is a number initSymbolic(symbolicPrefix, prefixOrIdx); - } else if (prefixOrIdx.matches(".*-?\\d+")){ + } else if (prefixOrIdx.matches(".*-?\\d+")) { // Its a list which already has prefix and idx initSymbolicWithoutIdx(prefixOrIdx); } else { @@ -110,6 +125,7 @@ public String MAKE_SYMBOLIC() { formula = smgr.makeVariable(name); return name; } + /** * Turns this IntValue into a symbolic variable * @@ -131,7 +147,13 @@ public String MAKE_SYMBOLIC(long idx) { */ @Override public BooleanFormula getBounds(boolean upper) { - return context.getFormulaManager().getBooleanFormulaManager().makeBoolean(true); + if (!upper) { + return context.getFormulaManager().getBooleanFormulaManager().makeBoolean(true); + } + // SMT strings range over all Unicode code points, Java chars over 16 bits only. Without + // this bound the solver may pick a character above U+FFFF, which charAt (the code point + // truncated to 16 bits) treats as a different one: the next run then diverges. + return smgr.in(formula, smgr.closure(smgr.range('\u0000', '\uffff'))); } /** @@ -146,7 +168,8 @@ public BooleanFormula getBounds(boolean upper) { * implemented or void should be returned */ @Override - public Value invokeMethod(String name, Type[] desc, Value[] args) throws NotImplementedException, ValueConversionException { + public Value invokeMethod(String name, Type[] desc, Value[] args) + throws NotImplementedException, ValueConversionException { return switch (name) { case "" -> invokeInit(args, desc); case "charAt" -> invokeCharAt(args, desc); @@ -214,14 +237,36 @@ public BooleanFormula getBounds(boolean upper) { * the Value in args is of the same type. * @return The resulting Value or PlaceHolder::instance */ - private Value invokeInit(Value[] args, Type[] desc) { - if(args.length == 1 && args[0] instanceof StringValue s) { - this.concrete = s.concrete; - this.formula = s.formula; - } + private Value invokeInit(Value[] args, Type[] desc) throws NotImplementedException, ValueConversionException { // Mark this string as user-de-interned since it was created via new String() // in user code (as opposed to SWAT's NoCacheMethodAdapter de-interning) this.userDeInterned = true; + StringValue chars; + if (args.length == 0) { + chars = new StringValue(context, "", -1); + } else if (args.length == 1 && args[0] instanceof CharArrayValue array) { + // The concrete number of chars, which the call's branches pin (StringCallBranches). + chars = array.asStringValue(); + } else if (args.length == 1) { + // new String(String), new String(StringBuilder), new String(StringBuffer) + chars = charsOf(args[0]); + } else if (args.length == 3 && args[0] instanceof CharArrayValue array && desc[1].getSort() == Type.INT) { + // new String(char[] value, int offset, int count), by the concrete offset and count, + // which the call's branches check and pin (StringCallBranches). + int offset = args[1].asIntValue().concrete; + int count = args[2].asIntValue().concrete; + chars = new StringValue(context, "", -1); + for (int i = offset; i < offset + count; i++) { + CharValue c = array.getElement(new IntValue(context, i)); + chars = new StringValue(context, chars.concrete + c.concrete, + smgr.concat(chars.formula, c.asStringValue().formula), -1); + } + } else { + chars = null; + } + if (chars == null) return PlaceHolder.instance; + this.concrete = chars.concrete; + this.formula = chars.formula; return VoidValue.instance; } @@ -236,7 +281,8 @@ public BooleanFormula getBounds(boolean upper) { * the Value in args is of the same type. * @return The resulting Value or PlaceHolder::instance */ - private Value invokeCharAt(Value[] args, Type[] desc) throws NotImplementedException, ValueConversionException { + private Value invokeCharAt(Value[] args, Type[] desc) + throws NotImplementedException, ValueConversionException { if (args.length == 1) { IntValue index = args[0].asIntValue(); char result = concrete.charAt(index.concrete); @@ -272,8 +318,21 @@ public BooleanFormula getBounds(boolean upper) { * the Value in args is of the same type. * @return The resulting Value or PlaceHolder::instance */ - private Value invokeCodePointAt(Value[] args, Type[] desc) { - return PlaceHolder.instance; + private Value invokeCodePointAt(Value[] args, Type[] desc) throws NotImplementedException, ValueConversionException { + // The index is in range (the call's branch, StringCallBranches): the char there, or the + // code point of the surrogate pair it starts. + if (args.length != 1) return PlaceHolder.instance; + IntValue index = args[0].asIntValue(); + int result = concrete.codePointAt(index.concrete); + if (!isSymbolic() && !index.isSymbolic()) return new IntValue(context, result); + BooleanFormulaManager bmgr = context.getFormulaManager().getBooleanFormulaManager(); + Utf16 chars = new Utf16(context.getFormulaManager()); + NumeralFormula.IntegerFormula i = index.asIntegerFormula(); + NumeralFormula.IntegerFormula next = imgr.add(i, imgr.makeNumber(1)); + NumeralFormula.IntegerFormula c = chars.codeAt(formula, i); + NumeralFormula.IntegerFormula d = chars.codeAt(formula, next); + BooleanFormula pair = bmgr.and(chars.isHighSurrogate(c), imgr.lessThan(next, smgr.length(formula)), chars.isLowSurrogate(d)); + return new IntValue(context, result, bmgr.ifThenElse(pair, chars.toCodePoint(c, d), c)); } /** @@ -287,8 +346,21 @@ public BooleanFormula getBounds(boolean upper) { * the Value in args is of the same type. * @return The resulting Value or PlaceHolder::instance */ - private Value invokeCodePointBefore(Value[] args, Type[] desc) { - return PlaceHolder.instance; + private Value invokeCodePointBefore(Value[] args, Type[] desc) throws NotImplementedException, ValueConversionException { + // index - 1 is in range (the call's branch, StringCallBranches): the char before index, + // or the code point of the surrogate pair it ends. + if (args.length != 1) return PlaceHolder.instance; + IntValue index = args[0].asIntValue(); + int result = concrete.codePointBefore(index.concrete); + if (!isSymbolic() && !index.isSymbolic()) return new IntValue(context, result); + BooleanFormulaManager bmgr = context.getFormulaManager().getBooleanFormulaManager(); + Utf16 chars = new Utf16(context.getFormulaManager()); + NumeralFormula.IntegerFormula before = imgr.subtract(index.asIntegerFormula(), imgr.makeNumber(1)); + NumeralFormula.IntegerFormula twoBefore = imgr.subtract(index.asIntegerFormula(), imgr.makeNumber(2)); + NumeralFormula.IntegerFormula c = chars.codeAt(formula, before); + NumeralFormula.IntegerFormula b = chars.codeAt(formula, twoBefore); + BooleanFormula pair = bmgr.and(chars.isLowSurrogate(c), imgr.greaterOrEquals(twoBefore, imgr.makeNumber(0)), chars.isHighSurrogate(b)); + return new IntValue(context, result, bmgr.ifThenElse(pair, chars.toCodePoint(b, c), c)); } /** @@ -302,8 +374,29 @@ public BooleanFormula getBounds(boolean upper) { * the Value in args is of the same type. * @return The resulting Value or PlaceHolder::instance */ - private Value invokeCodePointCount(Value[] args, Type[] desc) { - return PlaceHolder.instance; + private Value invokeCodePointCount(Value[] args, Type[] desc) throws NotImplementedException, ValueConversionException { + // The range is in bounds and its length pinned (the call's branches, StringCallBranches): + // its chars, less one for each surrogate pair, scanned from the start as Java does. + if (args.length != 2) return PlaceHolder.instance; + IntValue begin = args[0].asIntValue(); + IntValue end = args[1].asIntValue(); + int result = concrete.codePointCount(begin.concrete, end.concrete); + if (!isSymbolic() && !begin.isSymbolic() && !end.isSymbolic()) return new IntValue(context, result); + int n = end.concrete - begin.concrete; + if (n > MAX_UNROLLED) return PlaceHolder.instance; + BooleanFormulaManager bmgr = context.getFormulaManager().getBooleanFormulaManager(); + Utf16 chars = new Utf16(context.getFormulaManager()); + NumeralFormula.IntegerFormula[] c = new NumeralFormula.IntegerFormula[n]; + for (int k = 0; k < n; k++) c[k] = chars.codeAt(formula, imgr.add(begin.asIntegerFormula(), imgr.makeNumber(k))); + NumeralFormula.IntegerFormula count = imgr.makeNumber(n); + BooleanFormula pairEndsHere = bmgr.makeFalse(); + for (int k = 0; k + 1 < n; k++) { + // a pair starts at k unless k ends one + BooleanFormula pairStarts = bmgr.and(bmgr.not(pairEndsHere), chars.isHighSurrogate(c[k]), chars.isLowSurrogate(c[k + 1])); + count = bmgr.ifThenElse(pairStarts, imgr.subtract(count, imgr.makeNumber(1)), count); + pairEndsHere = pairStarts; + } + return new IntValue(context, result, count); } /** @@ -332,8 +425,58 @@ public BooleanFormula getBounds(boolean upper) { * the Value in args is of the same type. * @return The resulting Value or PlaceHolder::instance */ - private Value invokeCompareTo(Value[] args, Type[] desc) { - return PlaceHolder.instance; + private Value invokeCompareTo(Value[] args, Type[] desc) throws NotImplementedException { + if (args.length != 1 || !(args[0] instanceof StringValue other)) return PlaceHolder.instance; + int result = concrete.compareTo(other.concrete); + if (!isSymbolic() && !other.isSymbolic()) return new IntValue(context, result); + BooleanFormulaManager bmgr = context.getFormulaManager().getBooleanFormulaManager(); + NumeralFormula.IntegerFormula len = smgr.length(formula); + NumeralFormula.IntegerFormula otherLen = smgr.length(other.formula); + // k = the length of the longest common prefix, defined by a UF of both strings: the + // prefixes up to k agree, and k is the end of one string or the chars at k differ. + FunctionDeclaration lcp = context.getFormulaManager().getUFManager() + .declareUF("commonPrefixLength", FormulaType.IntegerType, FormulaType.StringType, FormulaType.StringType); + SymbolRegistry.declare("commonPrefixLength", SymbolRegistry.Kind.MODEL_FUNCTION); + NumeralFormula.IntegerFormula k = context.getFormulaManager().getUFManager().callUF(lcp, formula, other.formula); + BooleanFormula kInsideBoth = bmgr.and(imgr.lessThan(k, len), imgr.lessThan(k, otherLen)); + addDefinition(bmgr.and( + imgr.greaterOrEquals(k, imgr.makeNumber(0)), + imgr.lessOrEquals(k, len), + imgr.lessOrEquals(k, otherLen), + smgr.equal(smgr.substring(formula, imgr.makeNumber(0), k), smgr.substring(other.formula, imgr.makeNumber(0), k)), + bmgr.implication(kInsideBoth, bmgr.not(smgr.equal(smgr.charAt(formula, k), smgr.charAt(other.formula, k)))))); + // Java: the difference of the first differing chars, or else of the lengths. + NumeralFormula.IntegerFormula cmp = bmgr.ifThenElse(kInsideBoth, + imgr.subtract(smgr.toCodePoint(smgr.charAt(formula, k)), smgr.toCodePoint(smgr.charAt(other.formula, k))), + imgr.subtract(len, otherLen)); + return new IntValue(context, result, cmp); + } + + /** Records that a model is not exact for this call, so an exhausted search proves nothing. */ + static void recordPrecisionLoss() { + try { + ThreadHandler.getSymbolicTraceHandler(currentThread().getId()).recordSymbolicPrecisionLoss(); + } catch (Exception e) { + throw new IllegalStateException("No symbolic trace handler for the current thread", e); + } + } + + /** + * The chars of a CharSequence SWAT tracks: a String or a StringBuilder's content, else null. + */ + public static StringValue charsOf(Value sequence) { + if (sequence instanceof StringValue str) return str; + if (sequence instanceof de.uzl.its.swat.symbolic.value.reference.StringBuilderValue sb) return sb.getContent(); + return null; + } + + /** Adds a constraint that defines the UFs a model introduced; it holds on every path and is never negated. */ + private static void addDefinition(BooleanFormula definition) { + try { + ThreadHandler.getSymbolicTraceHandler(currentThread().getId()).addConstraint(definition); + } catch (Exception e) { + throw new IllegalStateException("No symbolic trace handler for the current thread", e); + } } /** @@ -348,7 +491,68 @@ public BooleanFormula getBounds(boolean upper) { * @return The resulting Value or PlaceHolder::instance */ private Value invokeCompareToIgnoreCase(Value[] args, Type[] desc) { - return PlaceHolder.instance; + if (args.length != 1 || !(args[0] instanceof StringValue other)) return PlaceHolder.instance; + int result = concrete.compareToIgnoreCase(other.concrete); + if (!isSymbolic() && !other.isSymbolic()) return new IntValue(context, result); + // ASCII strings (the call's branches separate them, StringCallBranches) by the pinned lengths + if (!asciiStrings(this, other)) return PlaceHolder.instance; + int tlen = concrete.length(); + int olen = other.concrete.length(); + if (Math.max(tlen, olen) > MAX_UNROLLED) return PlaceHolder.instance; + return new IntValue(context, result, new CaseMapping(context.getFormulaManager()) + .compareAsciiIgnoringCase(formula, imgr.makeNumber(0), tlen, other.formula, imgr.makeNumber(0), olen).value()); + } + + /** Whether all chars of the strings are ASCII, concretely. */ + public static boolean asciiStrings(StringValue... strings) { + for (StringValue s : strings) { + if (!s.concrete.chars().allMatch(c -> c < 128)) return false; + } + return true; + } + + /** The longest string a model is unrolled over, char by char; longer ones are not modelled. */ + static final int MAX_UNROLLED = 256; + + /** + * Whether a.equalsIgnoreCase(b) is modelled as a regular expression: one of them is concrete + * and has no surrogate, so the other must match it char by char (a surrogate of the other + * matches none of its chars, also as part of a pair). Otherwise the model is unrolled over the + * concrete length (pinned), for ASCII strings. + */ + public static boolean equalsIgnoreCaseByRegex(StringValue a, StringValue b) { + if (a.isSymbolic() == b.isSymbolic()) return false; + String t = a.isSymbolic() ? b.concrete : a.concrete; + return t.chars().noneMatch(c -> Character.isSurrogate((char) c)); + } + + /** The concrete Locale of a Locale argument, or null if it is not known. */ + public static java.util.Locale localeOf(Value v) { + return v instanceof ObjectValue o && o.concrete instanceof java.util.Locale locale ? locale : null; + } + + /** + * The locale of a toLowerCase/toUpperCase call with these arguments, if its case mappings are + * modelled (those of the root locale), else null. + */ + public static java.util.Locale caseMappingLocale(Value[] args) { + java.util.Locale locale = args.length == 0 ? java.util.Locale.getDefault() : localeOf(args[0]); + return locale != null && CaseMapping.rootRules(locale) ? locale : null; + } + + /** + * toLowerCase()/toLowerCase(Locale) and toUpperCase()/toUpperCase(Locale) of a concrete string. + * Of a symbolic one they are not modelled: unrolled over a pinned length, the search reached + * hardly any other length, and unrolled up to a bound Z3 took seconds per query (see + * CaseMapping for the chars beyond ASCII). + */ + private Value invokeCaseMapping(boolean lower, Value[] args) { + if (args.length > 1) return PlaceHolder.instance; + java.util.Locale locale = caseMappingLocale(args); + if (locale == null) return PlaceHolder.instance; + String result = lower ? concrete.toLowerCase(locale) : concrete.toUpperCase(locale); + if (isSymbolic()) return PlaceHolder.instance; + return new StringValue(context, result, new Utf16(context.getFormulaManager()).chars(result), -1); } /** @@ -384,7 +588,8 @@ public BooleanFormula getBounds(boolean upper) { * @return The resulting Value or PlaceHolder::instance */ private Value invokeContains(Value[] args, Type[] desc) { - if (args.length == 1 && args[0] instanceof StringValue s) { + StringValue s = args.length == 1 ? charsOf(args[0]) : null; + if (s != null) { boolean result = concrete.contains(s.concrete); return new BooleanValue(context, result, smgr.contains(formula, s.formula)); } else { @@ -404,7 +609,9 @@ public BooleanFormula getBounds(boolean upper) { * @return The resulting Value or PlaceHolder::instance */ private Value invokeContentEquals(Value[] args, Type[] desc) { - return PlaceHolder.instance; + StringValue other = args.length == 1 ? charsOf(args[0]) : null; + if (other == null) return PlaceHolder.instance; + return new BooleanValue(context, concrete.contentEquals(other.concrete), smgr.equal(formula, other.formula)); } /** @@ -456,15 +663,14 @@ public BooleanFormula getBounds(boolean upper) { */ private Value invokeEquals(Value[] args, Type[] desc) { if (args.length == 1) { - Value anObject = args[0]; - try { - StringValue str = anObject.asStringValue(); + // Only a String is equal to a String (not, e.g., a StringBuilder with the same chars). + if (args[0] instanceof StringValue str) { boolean result = concrete.equals(str.concrete); return new BooleanValue(context, result, smgr.equal(formula, str.formula)); - } catch (Exception ignored) { - System.out.println("No String indeed!"); - return PlaceHolder.instance; + } else if (args[0] instanceof ObjectValue obj && obj.getAddress() == ObjectValue.ADDRESS_NULL) { + return new BooleanValue(context, false); } + return PlaceHolder.instance; } else { return PlaceHolder.instance; } @@ -481,42 +687,28 @@ public BooleanFormula getBounds(boolean upper) { * the Value in args is of the same type. * @return The resulting Value or PlaceHolder::instance */ - private Value invokeEqualsIgnoreCase(Value[] args, Type[] desc) throws NotImplementedException { - SWATAssert.enforce(args.length == 1, - "Wrong number of arguments for String.equalsIgnoreCase"); - - try { - StringValue rhs = args[0].asStringValue(); - - // fast path if both concrete - if (!this.isSymbolic() && !rhs.isSymbolic()) { - return new BooleanValue(context, this.concrete.equalsIgnoreCase(rhs.concrete)); - } - - EqualsIgnoreCaseUF uf = ThreadHandler.getUFHandler(currentThread().getId()).getEqualsIgnoreCaseUF(); - - // (1) the symbolic boolean you'll return on the stack - BooleanFormula ufCall = uf.getUFCall(this.formula, rhs.formula); - - // (2) "general" constraints that define the UF (never negated) - BooleanFormula constraints = uf.createEqualsIgnoreCaseConstraints( - this.formula, rhs.formula, - this.concrete, rhs.concrete, - this.isSymbolic(), rhs.isSymbolic() - ); - ThreadHandler.getSymbolicTraceHandler(currentThread().getId()).addConstraint(constraints); - - // also carry the concrete result for concolic steering - boolean concreteResult = this.concrete.equalsIgnoreCase(rhs.concrete); - return new BooleanValue(context, concreteResult, ufCall); - - } catch (Exception e) { - logger.error("Error invoking equalsIgnoreCase on StringValue: {}", e.getMessage()); - return PlaceHolder.instance; + private Value invokeEqualsIgnoreCase(Value[] args, Type[] desc) { + if (args.length != 1) return PlaceHolder.instance; + if (args[0] instanceof ObjectValue o && o.isNullValue()) return new BooleanValue(context, false); + if (!(args[0] instanceof StringValue other)) return PlaceHolder.instance; + boolean result = concrete.equalsIgnoreCase(other.concrete); + if (!isSymbolic() && !other.isSymbolic()) return new BooleanValue(context, result); + CaseMapping cases = new CaseMapping(context.getFormulaManager()); + if (equalsIgnoreCaseByRegex(this, other)) { + StringValue symbolic = isSymbolic() ? this : other; + String t = isSymbolic() ? other.concrete : concrete; + return new BooleanValue(context, result, smgr.in(symbolic.formula, cases.equalIgnoringCase(t))); } + // The call's branches (StringCallBranches): whether the lengths are equal, then the length + // is pinned and the ASCII strings separated. Strings of different lengths are not equal. + BooleanFormulaManager bmgr = context.getFormulaManager().getBooleanFormulaManager(); + int n = concrete.length(); + if (n != other.concrete.length()) return new BooleanValue(context, false, bmgr.makeFalse()); + if (n > MAX_UNROLLED || !asciiStrings(this, other)) return PlaceHolder.instance; + return new BooleanValue(context, result, cases.compareAsciiIgnoringCase(formula, imgr.makeNumber(0), n, + other.formula, imgr.makeNumber(0), n).noDifference()); } - /** * Invocation handling for the String instance method Formatted(). @@ -565,11 +757,12 @@ public BooleanFormula getBounds(boolean upper) { IntValue srcEnd = args[1].asIntValue(); CharArrayValue dst = args[2].asObjectValue().asArrayValue().asCharArrayValue(); IntValue dstBegin = args[3].asIntValue(); + // By the concrete indices, which the call's branches pin (StringCallBranches). for (int idx = srcBegin.concrete; idx < srcEnd.concrete; idx++) { - Value[] charAtArgs = new Value[] {new IntValue(context, idx)}; - Type[] charAtDesc = new Type[] {Type.INT_TYPE}; + Value[] charAtArgs = new Value[] { new IntValue(context, idx) }; + Type[] charAtDesc = new Type[] { Type.INT_TYPE }; dst.storeElement( - new IntValue(context, dstBegin.concrete + idx), + new IntValue(context, dstBegin.concrete + idx - srcBegin.concrete), (CharValue) invokeCharAt(charAtArgs, charAtDesc)); } return VoidValue.instance; @@ -624,16 +817,19 @@ public BooleanFormula getBounds(boolean upper) { * the Value in args is of the same type. * @return The resulting Value or PlaceHolder::instance */ - private Value invokeIndexOf(Value[] args, Type[] desc) throws NotImplementedException, ValueConversionException { + private Value invokeIndexOf(Value[] args, Type[] desc) + throws NotImplementedException, ValueConversionException { if (args.length == 1) { return switch (desc[0].getDescriptor()) { - case "I" -> invokeIndexOf(args[0].asIntValue()); + case "I" -> args[0] instanceof CharValue c + ? invokeIndexOf(c, new IntValue(context, 0)) : invokeIndexOf(args[0].asIntValue()); case "Ljava/lang/String;" -> invokeIndexOf(args[0].asStringValue()); default -> PlaceHolder.instance; }; } else if (args.length == 2) { return switch (desc[0].getDescriptor()) { - case "I" -> invokeIndexOf(args[0].asIntValue(), args[1].asIntValue()); + case "I" -> args[0] instanceof CharValue c + ? invokeIndexOf(c, args[1].asIntValue()) : invokeIndexOf(args[0].asIntValue(), args[1].asIntValue()); case "Ljava/lang/String;" -> invokeIndexOf( args[0].asStringValue(), args[1].asIntValue()); default -> PlaceHolder.instance; @@ -648,10 +844,16 @@ public BooleanFormula getBounds(boolean upper) { } private Value invokeIndexOf(StringValue str, IntValue fromIndex) { + // Java clamps fromIndex into [0, length]; str.indexof returns -1 outside that range. + BooleanFormulaManager bmgr = context.getFormulaManager().getBooleanFormulaManager(); + NumeralFormula.IntegerFormula from = fromIndex.asIntegerFormula(); + NumeralFormula.IntegerFormula length = smgr.length(formula); + NumeralFormula.IntegerFormula clamped = bmgr.ifThenElse(imgr.lessThan(from, imgr.makeNumber(0)), + imgr.makeNumber(0), bmgr.ifThenElse(imgr.greaterThan(from, length), length, from)); return new IntValue( context, concrete.indexOf(str.concrete, fromIndex.concrete), - smgr.indexOf(formula, str.formula, fromIndex.asIntegerFormula())); + smgr.indexOf(formula, str.formula, clamped)); } private Value invokeIndexOf(IntValue ch) { @@ -659,7 +861,65 @@ public BooleanFormula getBounds(boolean upper) { } private Value invokeIndexOf(IntValue ch, IntValue fromIndex) { - return invokeIndexOf(ch.asStringValue(), fromIndex); + int result = concrete.indexOf(ch.concrete, fromIndex.concrete); + if (!isSymbolic() && !ch.isSymbolic() && !fromIndex.isSymbolic()) return new IntValue(context, result); + BooleanFormulaManager bmgr = context.getFormulaManager().getBooleanFormulaManager(); + IntValue index = (IntValue) invokeIndexOf(new StringValue(context, "", codePointString(ch), -1), fromIndex); + // No code point, no match (as Java, which searches for a code point, not for its digits). + return new IntValue(context, result, bmgr.ifThenElse(isCodePoint(ch), index.asIntegerFormula(), imgr.makeNumber(-1))); + } + + /** + * indexOf(int) of a char: a char is a code point in the BMP, so it is searched for as itself, + * without the surrogate pair and range cases of an int (whose div and mod slow Z3 down). + */ + private Value invokeIndexOf(CharValue ch, IntValue fromIndex) { + int result = concrete.indexOf(ch.concrete, fromIndex.concrete); + if (!isSymbolic() && !ch.isSymbolic() && !fromIndex.isSymbolic()) return new IntValue(context, result); + IntValue index = (IntValue) invokeIndexOf( + new StringValue(context, "", smgr.fromCodePoint(ch.asIntegerFormula()), -1), fromIndex); + return new IntValue(context, result, index.formula); + } + + /** Whether ch is a code point, i.e. in [0, 0x10FFFF]: the chars indexOf(int)/lastIndexOf(int) can find. */ + private BooleanFormula isCodePoint(IntValue ch) { + BooleanFormulaManager bmgr = context.getFormulaManager().getBooleanFormulaManager(); + if (!ch.isSymbolic()) return bmgr.makeBoolean(Character.isValidCodePoint(ch.concrete)); + BitvectorFormulaManager bvmgr = context.getFormulaManager().getBitvectorFormulaManager(); + // Unsigned: the negative ints are above every code point. + return bvmgr.lessOrEquals(ch.formula, bvmgr.makeBitvector(32, Character.MAX_CODE_POINT), false); + } + + /** + * The chars of code point ch in a Java string (String.valueOf(Character.toChars(ch))): the char + * itself in the BMP, else its surrogate pair. Meaningful only if {@link #isCodePoint} holds. + */ + public StringFormula codePointString(IntValue ch) { + BooleanFormulaManager bmgr = context.getFormulaManager().getBooleanFormulaManager(); + if (!ch.isSymbolic()) { + // Char by char: makeString would take a surrogate pair for one (SMT) character. + if (!Character.isValidCodePoint(ch.concrete)) return smgr.makeString(""); + StringFormula chars = smgr.makeString(""); + for (char c : Character.toChars(ch.concrete)) chars = smgr.concat(chars, smgr.fromCodePoint(imgr.makeNumber(c))); + return chars; + } + // Over bit-vectors, as ch is one: Integer div and mod of a converted bit-vector slow Z3 down + // badly (a query on s.indexOf(ch) went from minutes to under a second). + BitvectorFormulaManager bvmgr = context.getFormulaManager().getBitvectorFormulaManager(); + BitvectorFormula c = ch.formula; + BitvectorFormula offset = bvmgr.subtract(c, bvmgr.makeBitvector(32, Character.MIN_SUPPLEMENTARY_CODE_POINT)); + BitvectorFormula high = bvmgr.add(bvmgr.makeBitvector(32, Character.MIN_HIGH_SURROGATE), + bvmgr.shiftRight(offset, bvmgr.makeBitvector(32, 10), false)); + BitvectorFormula low = bvmgr.add(bvmgr.makeBitvector(32, Character.MIN_LOW_SURROGATE), + bvmgr.and(offset, bvmgr.makeBitvector(32, 0x3FF))); + return bmgr.ifThenElse(bvmgr.lessThan(c, bvmgr.makeBitvector(32, Character.MIN_SUPPLEMENTARY_CODE_POINT), false), + smgr.fromCodePoint(charCode(bvmgr, c)), + smgr.concat(smgr.fromCodePoint(charCode(bvmgr, high)), smgr.fromCodePoint(charCode(bvmgr, low)))); + } + + /** The low 16 bits of b as an Integer: a char code, where b is known to fit them. */ + private static NumeralFormula.IntegerFormula charCode(BitvectorFormulaManager bvmgr, BitvectorFormula b) { + return bvmgr.toIntegerFormula(bvmgr.extract(b, 15, 0), false); } /** @@ -704,7 +964,7 @@ public BooleanFormula getBounds(boolean upper) { * @return The resulting Value or PlaceHolder::instance */ private Value invokeIsEmpty(Value[] args, Type[] desc) { - return PlaceHolder.instance; + return new BooleanValue(context, concrete.length() == 0, imgr.equal(smgr.length(formula), imgr.makeNumber(0))); } /** @@ -718,8 +978,52 @@ public BooleanFormula getBounds(boolean upper) { * the Value in args is of the same type. * @return The resulting Value or PlaceHolder::instance */ - private Value invokeLastIndexOf(Value[] args, Type[] desc) { - return PlaceHolder.instance; + private Value invokeLastIndexOf(Value[] args, Type[] desc) throws NotImplementedException, ValueConversionException { + if (args.length < 1 || args.length > 2) return PlaceHolder.instance; + IntValue from = args.length == 2 ? args[1].asIntValue() : new IntValue(context, Integer.MAX_VALUE); + StringValue target; + IntValue ch = null; + int result; + if (desc[0].getSort() == Type.INT) { + // The chars of the code point; for anything else there is no match. By the concrete + // char, which the call's branches pin (StringCallBranches): with a symbolic one the + // window encoding below is too hard for the solver. + ch = new IntValue(context, args[0].asIntValue().concrete); + target = new StringValue(context, "", codePointString(ch), -1); + result = concrete.lastIndexOf(ch.concrete, from.concrete); + } else if (args[0] instanceof StringValue str) { + target = str; + result = concrete.lastIndexOf(str.concrete, from.concrete); + } else { + return PlaceHolder.instance; + } + if (!isSymbolic() && !target.isSymbolic() && !from.isSymbolic()) return new IntValue(context, result); + BooleanFormulaManager bmgr = context.getFormulaManager().getBooleanFormulaManager(); + UFManager ufmgr = context.getFormulaManager().getUFManager(); + NumeralFormula.IntegerFormula tlen = smgr.length(target.formula); + NumeralFormula.IntegerFormula fromF = from.asIntegerFormula(); + // The last start position a match may have: min(from, length - target length). + NumeralFormula.IntegerFormula maxStart = imgr.subtract(smgr.length(formula), tlen); + NumeralFormula.IntegerFormula last = bmgr.ifThenElse(imgr.lessThan(fromF, maxStart), fromF, maxStart); + FunctionDeclaration uf = ufmgr.declareUF("lastIndexOf", FormulaType.IntegerType, + FormulaType.StringType, FormulaType.StringType, FormulaType.IntegerType); + SymbolRegistry.declare("lastIndexOf", SymbolRegistry.Kind.MODEL_FUNCTION); + NumeralFormula.IntegerFormula r = ufmgr.callUF(uf, formula, target.formula, last); + // Matches starting at or before `last` are the occurrences of target in the window s[0, last + tlen). + // (Encoded with contains rather than indexOf, which Z3 solves much faster.) + StringFormula window = smgr.substring(formula, imgr.makeNumber(0), imgr.add(last, tlen)); + StringFormula afterR = smgr.substring(window, imgr.add(r, imgr.makeNumber(1)), + imgr.subtract(smgr.length(window), imgr.add(r, imgr.makeNumber(1)))); + BooleanFormula noMatch = bmgr.or(imgr.lessThan(last, imgr.makeNumber(0)), bmgr.not(smgr.contains(window, target.formula))); + // r is -1 without a match; `last` for an empty target; otherwise a match with none after it in the window. + addDefinition(bmgr.ifThenElse(noMatch, + imgr.equal(r, imgr.makeNumber(-1)), + bmgr.ifThenElse(imgr.equal(tlen, imgr.makeNumber(0)), + imgr.equal(r, last), + bmgr.and(imgr.greaterOrEquals(r, imgr.makeNumber(0)), imgr.lessOrEquals(r, last), + smgr.equal(smgr.substring(formula, r, tlen), target.formula), + bmgr.not(smgr.contains(afterR, target.formula)))))); + return new IntValue(context, result, ch == null ? r : bmgr.ifThenElse(isCodePoint(ch), r, imgr.makeNumber(-1))); } /** @@ -764,7 +1068,13 @@ public BooleanFormula getBounds(boolean upper) { * @return The resulting Value or PlaceHolder::instance */ private Value invokeMatches(Value[] args, Type[] desc) { - return PlaceHolder.instance; + // A concrete regular expression of the simple kind (JavaRegex), else not modelled + if (args.length != 1 || !(args[0] instanceof StringValue regex) || regex.isSymbolic()) return PlaceHolder.instance; + RegexFormula language = JavaRegex.toRegex(smgr, regex.concrete); + if (language == null) return PlaceHolder.instance; + boolean result = concrete.matches(regex.concrete); + if (!isSymbolic()) return new BooleanValue(context, result); + return new BooleanValue(context, result, smgr.in(formula, language)); } /** @@ -793,8 +1103,56 @@ public BooleanFormula getBounds(boolean upper) { * the Value in args is of the same type. * @return The resulting Value or PlaceHolder::instance */ - private Value invokeRegionMatches(Value[] args, Type[] desc) { - return PlaceHolder.instance; + private Value invokeRegionMatches(Value[] args, Type[] desc) throws NotImplementedException, ValueConversionException { + int i = 0; + BooleanValue ignoreCase = null; + if (args.length == 5) { + ignoreCase = args[0].asBooleanValue(); + if (!ignoreCase.isSymbolic() && !ignoreCase.concrete) ignoreCase = null; + i = 1; + } else if (args.length != 4) { + return PlaceHolder.instance; + } + if (!(args[i + 1] instanceof StringValue other)) return PlaceHolder.instance; + IntValue toffset = args[i].asIntValue(); + IntValue ooffset = args[i + 2].asIntValue(); + IntValue len = args[i + 3].asIntValue(); + boolean result = concrete.regionMatches(toffset.concrete, other.concrete, ooffset.concrete, len.concrete); + BooleanFormulaManager bmgr = context.getFormulaManager().getBooleanFormulaManager(); + NumeralFormula.IntegerFormula t = toffset.asIntegerFormula(); + NumeralFormula.IntegerFormula o = ooffset.asIntegerFormula(); + NumeralFormula.IntegerFormula l = len.asIntegerFormula(); + NumeralFormula.IntegerFormula zero = imgr.makeNumber(0); + // As String.regionMatches: false if either region starts before 0 or ends past its string, + // else true for an empty region, else the regions are compared. + BooleanFormula inBounds = bmgr.and(imgr.greaterOrEquals(t, zero), imgr.greaterOrEquals(o, zero), + imgr.lessOrEquals(t, imgr.subtract(smgr.length(formula), l)), + imgr.lessOrEquals(o, imgr.subtract(smgr.length(other.formula), l))); + BooleanFormula matches = bmgr.or(imgr.lessOrEquals(l, zero), + smgr.equal(smgr.substring(formula, t, l), smgr.substring(other.formula, o, l))); + if (ignoreCase == null) return new BooleanValue(context, result, bmgr.and(inBounds, matches)); + // Ignoring case (maybe): the call's branches (StringCallBranches) check the bounds, then + // pin len, over which the comparison is unrolled. + boolean ic = ignoreCase.concrete; + result = concrete.regionMatches(ic, toffset.concrete, other.concrete, ooffset.concrete, len.concrete); + if (!isSymbolic() && !other.isSymbolic() && !ignoreCase.isSymbolic() && !toffset.isSymbolic() + && !ooffset.isSymbolic() && !len.isSymbolic()) { + return new BooleanValue(context, result); + } + if (!regionInBounds(concrete, toffset.concrete, other.concrete, ooffset.concrete, len.concrete)) { + return new BooleanValue(context, false, bmgr.makeFalse()); + } + int n = len.concrete; + if (n > MAX_UNROLLED || !asciiStrings(this, other)) return PlaceHolder.instance; + BooleanFormula ignoringCase = n <= 0 ? bmgr.makeTrue() + : new CaseMapping(context.getFormulaManager()).compareAsciiIgnoringCase(formula, t, n, other.formula, o, n).noDifference(); + return new BooleanValue(context, result, ignoreCase.isSymbolic() + ? bmgr.ifThenElse(ignoreCase.formula, ignoringCase, matches) : ignoringCase); + } + + /** The bounds check of regionMatches: both regions start at 0 or later and end in their string. */ + public static boolean regionInBounds(String s, int toffset, String other, int ooffset, int len) { + return ooffset >= 0 && toffset >= 0 && toffset <= (long) s.length() - len && ooffset <= (long) other.length() - len; } /** @@ -808,8 +1166,17 @@ public BooleanFormula getBounds(boolean upper) { * the Value in args is of the same type. * @return The resulting Value or PlaceHolder::instance */ - private Value invokeRepeat(Value[] args, Type[] desc) { - return PlaceHolder.instance; + private Value invokeRepeat(Value[] args, Type[] desc) throws NotImplementedException, ValueConversionException { + // count >= 0 (else it throws) and count pinned (the call's branches, StringCallBranches) + if (args.length != 1) return PlaceHolder.instance; + IntValue count = args[0].asIntValue(); + if (count.concrete < 0 || (long) count.concrete * concrete.length() > Integer.MAX_VALUE) return PlaceHolder.instance; + String result = concrete.repeat(count.concrete); + if (!isSymbolic()) return new StringValue(context, result, new Utf16(context.getFormulaManager()).chars(result), -1); + if (count.concrete > MAX_UNROLLED) return PlaceHolder.instance; + StringFormula repeated = smgr.makeString(""); + for (int k = 0; k < count.concrete; k++) repeated = smgr.concat(repeated, formula); + return new StringValue(context, result, repeated, -1); } /** @@ -823,7 +1190,8 @@ public BooleanFormula getBounds(boolean upper) { * the Value in args is of the same type. * @return The resulting Value or PlaceHolder::instance */ - private Value invokeReplace(Value[] args, Type[] desc) throws NotImplementedException, ValueConversionException { + private Value invokeReplace(Value[] args, Type[] desc) + throws NotImplementedException, ValueConversionException { if (args.length == 2) { return switch (desc[0].getDescriptor()) { case "C" -> invokeReplace(args[0].asCharValue(), args[1].asCharValue()); @@ -836,38 +1204,34 @@ public BooleanFormula getBounds(boolean upper) { } private Value invokeReplace(ObjectValue target, ObjectValue replacement) { - if (target instanceof StringValue s1 && replacement instanceof StringValue s2) { - // ToDo (Nils): This is the correct implementation. However replaceAll is currently not - // supported by Z3 - /* - this.formula = smgr.replaceAll(this.formula, oldString.formula, newString.formula); - */ - this.concrete = this.concrete.replace(s1.concrete, s2.concrete); - for (int i = 0; i < REPLACE_COUNT; i++) { - this.formula = smgr.replace(this.formula, s1.formula, s2.formula); - } - return this; - } else { - return PlaceHolder.instance; - } + StringValue oldString = charsOf(target); + StringValue newString = charsOf(replacement); + if (oldString == null || newString == null) return PlaceHolder.instance; + return replaceAll(oldString, newString); } private Value invokeReplace(CharValue oldChar, CharValue newChar) { - StringValue oldString = oldChar.asStringValue(); - StringValue newString = newChar.asStringValue(); - // ToDo (Nils): This is the correct implementation. However replaceAll is currently not - // supported by Z3 - // See: https://git.its.uni-luebeck.de/research-projects/pet-hmr/knife-fuzzer/-/issues/54 - - /* - this.formula = smgr.replaceAll(this.formula, oldString.formula, newString.formula); - */ - this.concrete = this.concrete.replace(oldString.concrete, newString.concrete); + return replaceAll(oldChar.asStringValue(), newChar.asStringValue()); + } + + /** + * String.replace: every occurrence of target, left to right. Z3 hardly decides str.replace_all + * (with a concrete target it gave up or hung where this did not), so only the first + * REPLACE_COUNT occurrences are replaced, one by one, which is exact only for strings with as + * many occurrences at most (and str.replace differs from Java for an empty target): precision + * loss. + */ + private StringValue replaceAll(StringValue target, StringValue replacement) { + String newConcrete = this.concrete.replace(target.concrete, replacement.concrete); + if (!isSymbolic() && !target.isSymbolic() && !replacement.isSymbolic()) { + return new StringValue(context, newConcrete, ObjectValue.ADDRESS_UNKNOWN); + } + recordPrecisionLoss(); + StringFormula newFormula = formula; for (int i = 0; i < REPLACE_COUNT; i++) { - this.formula = smgr.replace(this.formula, oldString.formula, newString.formula); + newFormula = smgr.replace(newFormula, target.formula, replacement.formula); } - // ToDo: A new string is created as we need a new address, should the entire object be recreated above? - return new StringValue(context, this.concrete, this.formula, ObjectValue.ADDRESS_UNKNOWN); + return new StringValue(context, newConcrete, newFormula, ObjectValue.ADDRESS_UNKNOWN); } /** @@ -882,7 +1246,24 @@ public BooleanFormula getBounds(boolean upper) { * @return The resulting Value or PlaceHolder::instance */ private Value invokeReplaceAll(Value[] args, Type[] desc) { - return PlaceHolder.instance; + // A literal regular expression and replacement: replace(target, replacement). The empty + // regex matches between the chars of a surrogate pair or not, which is left unmodelled. + String target = literalRegexArgument(args); + if (target == null || target.isEmpty()) return PlaceHolder.instance; + return replaceAll(new StringValue(context, target, -1), (StringValue) args[1]); + } + + /** + * The literal the regular expression of replaceAll/replaceFirst(regex, replacement) stands + * for, if both are concrete and literal, else null. + */ + private static String literalRegexArgument(Value[] args) { + if (args.length != 2 || !(args[0] instanceof StringValue regex) || regex.isSymbolic() + || !(args[1] instanceof StringValue replacement) || replacement.isSymbolic() + || !JavaRegex.literalReplacement(replacement.concrete)) { + return null; + } + return JavaRegex.literal(regex.concrete); } /** @@ -897,7 +1278,14 @@ public BooleanFormula getBounds(boolean upper) { * @return The resulting Value or PlaceHolder::instance */ private Value invokeReplaceFirst(Value[] args, Type[] desc) { - return PlaceHolder.instance; + // A literal regular expression and replacement: the first occurrence is replaced, as by + // str.replace (an empty one is inserted at the start). + String target = literalRegexArgument(args); + if (target == null) return PlaceHolder.instance; + StringValue replacement = (StringValue) args[1]; + String result = concrete.replaceFirst(((StringValue) args[0]).concrete, replacement.concrete); + if (!isSymbolic()) return new StringValue(context, result, -1); + return new StringValue(context, result, smgr.replace(formula, literal(smgr, imgr, target), replacement.formula), -1); } /** @@ -941,7 +1329,8 @@ public BooleanFormula getBounds(boolean upper) { * the Value in args is of the same type. * @return The resulting Value or PlaceHolder::instance */ - private Value invokeStartsWith(Value[] args, Type[] desc) throws NotImplementedException, ValueConversionException { + private Value invokeStartsWith(Value[] args, Type[] desc) + throws NotImplementedException, ValueConversionException { if (args.length == 1) { return invokeStartsWith(args[0].asStringValue()); } else if (args.length == 2) { @@ -959,12 +1348,17 @@ public BooleanFormula getBounds(boolean upper) { } private Value invokeStartsWith(StringValue prefix, IntValue toffset) { + // As Java: false unless the prefix fits at toffset, i.e. 0 <= toffset <= length - prefix length. + BooleanFormulaManager bmgr = context.getFormulaManager().getBooleanFormulaManager(); + NumeralFormula.IntegerFormula t = toffset.asIntegerFormula(); + NumeralFormula.IntegerFormula plen = smgr.length(prefix.formula); return new BooleanValue( context, - concrete.startsWith(prefix.concrete), - smgr.equal( - prefix.formula, - smgr.substring(formula, toffset.asIntegerFormula(), smgr.length(prefix.formula)))); + concrete.startsWith(prefix.concrete, toffset.concrete), + bmgr.and( + imgr.greaterOrEquals(t, imgr.makeNumber(0)), + imgr.lessOrEquals(t, imgr.subtract(smgr.length(formula), plen)), + smgr.equal(prefix.formula, smgr.substring(formula, t, plen)))); } /** @@ -1038,8 +1432,10 @@ public BooleanFormula getBounds(boolean upper) { * the Value in args is of the same type. * @return The resulting Value or PlaceHolder::instance */ - private Value invokeSubSequence(Value[] args, Type[] desc) { - return PlaceHolder.instance; + private Value invokeSubSequence(Value[] args, Type[] desc) + throws NotImplementedException, ValueConversionException { + // subSequence(begin, end) is substring(begin, end) + return args.length == 2 ? invokeSubstring(args, desc) : PlaceHolder.instance; } /** @@ -1053,7 +1449,8 @@ public BooleanFormula getBounds(boolean upper) { * the Value in args is of the same type. * @return The resulting Value or PlaceHolder::instance */ - private Value invokeSubstring(Value[] args, Type[] desc) throws NotImplementedException, ValueConversionException { + private Value invokeSubstring(Value[] args, Type[] desc) + throws NotImplementedException, ValueConversionException { if (args.length == 1) { return invokeSubstring(args[0].asIntValue()); } else if (args.length == 2) { @@ -1091,24 +1488,19 @@ public BooleanFormula getBounds(boolean upper) { * the Value in args is of the same type. * @return The resulting Value or PlaceHolder::instance */ - private Value invokeToCharArray(Value[] args, Type[] desc) throws NotImplementedException, ValueConversionException { - - if (!concrete.isEmpty()) { - CharArrayValue dst = - new CharArrayValue(context, new IntValue(context, concrete.length()), -1); - for (int idx = 0; idx < concrete.length(); idx++) { - Value[] charAtArgs = new Value[] {new IntValue(context, idx)}; - Type[] charAtDesc = new Type[] {Type.INT_TYPE}; - dst.storeElement( - new IntValue(context, idx), - (CharValue) invokeCharAt(charAtArgs, charAtDesc)); - } - return dst; - } else { - new CharArrayValue(context, new IntValue(context, 0), -1); + private Value invokeToCharArray(Value[] args, Type[] desc) + throws NotImplementedException, ValueConversionException { + + // As long as the concrete string, which the call's branches pin (StringCallBranches). + CharArrayValue dst = new CharArrayValue(context, new IntValue(context, concrete.length()), -1); + for (int idx = 0; idx < concrete.length(); idx++) { + Value[] charAtArgs = new Value[] { new IntValue(context, idx) }; + Type[] charAtDesc = new Type[] { Type.INT_TYPE }; + dst.storeElement( + new IntValue(context, idx), + (CharValue) invokeCharAt(charAtArgs, charAtDesc)); } - - return PlaceHolder.instance; + return dst; } /** @@ -1123,7 +1515,7 @@ public BooleanFormula getBounds(boolean upper) { * @return The resulting Value or PlaceHolder::instance */ private Value invokeToLowerCase(Value[] args, Type[] desc) { - return PlaceHolder.instance; + return invokeCaseMapping(true, args); } /** @@ -1153,7 +1545,7 @@ public BooleanFormula getBounds(boolean upper) { * @return The resulting Value or PlaceHolder::instance */ private Value invokeToUpperCase(Value[] args, Type[] desc) { - return PlaceHolder.instance; + return invokeCaseMapping(false, args); } /** @@ -1198,7 +1590,31 @@ public BooleanFormula getBounds(boolean upper) { * @return The resulting Value or PlaceHolder::instance */ private Value invokeTrim(Value[] args, Type[] desc) { - return PlaceHolder.instance; + if (args.length != 0) return PlaceHolder.instance; + String result = concrete.trim(); + if (!isSymbolic()) return new StringValue(context, result, -1); + BooleanFormulaManager bmgr = context.getFormulaManager().getBooleanFormulaManager(); + UFManager ufmgr = context.getFormulaManager().getUFManager(); + // trim() keeps [lo, hi): lo and hi are UFs of the string, defined by what they cut off + // being made of chars <= ' ' only, and by the kept part, if any, not starting or ending in one. + FunctionDeclaration loUF = ufmgr.declareUF("trimStart", FormulaType.IntegerType, FormulaType.StringType); + FunctionDeclaration hiUF = ufmgr.declareUF("trimEnd", FormulaType.IntegerType, FormulaType.StringType); + SymbolRegistry.declare("trimStart", SymbolRegistry.Kind.MODEL_FUNCTION); + SymbolRegistry.declare("trimEnd", SymbolRegistry.Kind.MODEL_FUNCTION); + NumeralFormula.IntegerFormula lo = ufmgr.callUF(loUF, formula); + NumeralFormula.IntegerFormula hi = ufmgr.callUF(hiUF, formula); + NumeralFormula.IntegerFormula len = smgr.length(formula); + RegexFormula trimmed = smgr.range((char) 0, ' '); + addDefinition(bmgr.and( + imgr.greaterOrEquals(lo, imgr.makeNumber(0)), + imgr.lessOrEquals(lo, hi), + imgr.lessOrEquals(hi, len), + smgr.in(smgr.substring(formula, imgr.makeNumber(0), lo), smgr.closure(trimmed)), + smgr.in(smgr.substring(formula, hi, imgr.subtract(len, hi)), smgr.closure(trimmed)), + bmgr.implication(imgr.lessThan(lo, hi), bmgr.and( + bmgr.not(smgr.in(smgr.charAt(formula, lo), trimmed)), + bmgr.not(smgr.in(smgr.charAt(formula, imgr.subtract(hi, imgr.makeNumber(1))), trimmed)))))); + return new StringValue(context, result, smgr.substring(formula, lo, imgr.subtract(hi, lo)), -1); } @Override @@ -1232,7 +1648,7 @@ public ImmutableSet getSymbolicVariables() { } @Override - public String getSymPrefix(){ + public String getSymPrefix() { return symbolicPrefix; } @@ -1242,11 +1658,10 @@ public String toString() { String concreteString = null != concrete ? concrete : ""; if (formulaString.length() > Config.instance().getLoggingFormulaLength()) { - formulaString = - formulaString.substring(0, Config.instance().getLoggingFormulaLength()) + "..."; + formulaString = formulaString.substring(0, Config.instance().getLoggingFormulaLength()) + "..."; } - return "StringValue[" + this.internalID +"]; @" + return "StringValue[" + this.internalID + "]; @" + Integer.toHexString(address) + " (" + concreteString diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/reference/lang/Utf16.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/reference/lang/Utf16.java new file mode 100644 index 00000000..dd5f061e --- /dev/null +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/value/reference/lang/Utf16.java @@ -0,0 +1,53 @@ +package de.uzl.its.swat.symbolic.value.reference.lang; + +import org.sosy_lab.java_smt.api.BooleanFormula; +import org.sosy_lab.java_smt.api.BooleanFormulaManager; +import org.sosy_lab.java_smt.api.FormulaManager; +import org.sosy_lab.java_smt.api.IntegerFormulaManager; +import org.sosy_lab.java_smt.api.NumeralFormula.IntegerFormula; +import org.sosy_lab.java_smt.api.StringFormula; +import org.sosy_lab.java_smt.api.StringFormulaManager; + +/** The UTF-16 chars of a Java string as formulas: their codes, surrogates and surrogate pairs. */ +public final class Utf16 { + private final BooleanFormulaManager bmgr; + private final IntegerFormulaManager imgr; + private final StringFormulaManager smgr; + + public Utf16(FormulaManager fmgr) { + this.bmgr = fmgr.getBooleanFormulaManager(); + this.imgr = fmgr.getIntegerFormulaManager(); + this.smgr = fmgr.getStringFormulaManager(); + } + + /** The code of the char of s at index i */ + public IntegerFormula codeAt(StringFormula s, IntegerFormula i) { + return smgr.toCodePoint(smgr.charAt(s, i)); + } + + public BooleanFormula isHighSurrogate(IntegerFormula c) { + return bmgr.and(imgr.greaterOrEquals(c, imgr.makeNumber(Character.MIN_HIGH_SURROGATE)), + imgr.lessOrEquals(c, imgr.makeNumber(Character.MAX_HIGH_SURROGATE))); + } + + public BooleanFormula isLowSurrogate(IntegerFormula c) { + return bmgr.and(imgr.greaterOrEquals(c, imgr.makeNumber(Character.MIN_LOW_SURROGATE)), + imgr.lessOrEquals(c, imgr.makeNumber(Character.MAX_LOW_SURROGATE))); + } + + /** Character.toCodePoint(high, low) */ + public IntegerFormula toCodePoint(IntegerFormula high, IntegerFormula low) { + return imgr.add(imgr.multiply(imgr.subtract(high, imgr.makeNumber(Character.MIN_HIGH_SURROGATE)), imgr.makeNumber(1024)), + imgr.add(imgr.subtract(low, imgr.makeNumber(Character.MIN_LOW_SURROGATE)), + imgr.makeNumber(Character.MIN_SUPPLEMENTARY_CODE_POINT))); + } + + /** The chars of a Java string, one SMT char per UTF-16 char (makeString would join surrogate pairs). */ + public StringFormula chars(String s) { + StringFormula f = smgr.makeString(""); + for (char c : s.toCharArray()) { + f = smgr.concat(f, smgr.fromCodePoint(imgr.makeNumber(c))); + } + return f; + } +} diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/thread/DeadlockWatchdog.java b/symbolic-executor/src/main/java/de/uzl/its/swat/thread/DeadlockWatchdog.java new file mode 100644 index 00000000..5eaa0058 --- /dev/null +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/thread/DeadlockWatchdog.java @@ -0,0 +1,97 @@ +package de.uzl.its.swat.thread; + +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadMXBean; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * Ends a run whose application threads are deadlocked. + * + *

A target can deadlock (the SV-COMP no-deadlock tasks are made to): its threads then wait + * forever without using CPU, so neither the target nor the explorer's CPU budget ever ends the run, + * and only the harness' wall-clock timeout kills it, before the explorer reports anything. This + * watchdog checks twice a second whether every live application thread is BLOCKED or WAITING (not + * TIMED_WAITING) and has used no CPU since the previous check; if that holds for {@link + * #STUCK_MS}, no application thread can ever wake another, and the execution has ended. It then + * reports the deadlock on stdout and ends the JVM normally, so that the agent's shutdown hook sends + * the trace of whatever execution is still active (see Intrinsics.terminateOnShutdown). + * + *

Application threads are those of the main thread group and its subgroups, except the JVM's + * DestroyJavaVM thread, this one and the workers of SWAT's asynchronous log appenders. + */ +public final class DeadlockWatchdog implements Runnable { + + /** How long every application thread must be stuck before the run is ended. */ + static final long STUCK_MS = 3000; + private static final long PERIOD_MS = 500; + /** Marker the explorer looks for in the target's output. */ + public static final String MARKER = "[SWAT] Deadlock: every application thread waits forever"; + + private DeadlockWatchdog() {} + + public static void start() { + Thread t = new Thread(new DeadlockWatchdog(), "swat-deadlock-watchdog"); + t.setDaemon(true); + t.start(); + } + + @Override + public void run() { + ThreadMXBean mx = ManagementFactory.getThreadMXBean(); + ThreadGroup main = Thread.currentThread().getThreadGroup(); + Map previous = null; + long stuckSince = -1; + while (true) { + try { + Thread.sleep(PERIOD_MS); + } catch (InterruptedException e) { + return; + } + Map state = stuckState(mx, main); + long now = System.currentTimeMillis(); + if (state == null || !Objects.equals(state, previous)) { + previous = state; + stuckSince = state == null ? -1 : now; + continue; + } + if (now - stuckSince >= STUCK_MS) { + System.out.println(MARKER + ": " + state.values()); + System.out.flush(); + System.exit(0); + return; + } + } + } + + /** + * The application threads with their state and CPU time, if every one of them waits without a + * timeout; null if some application thread can still make progress (or there is none). + */ + private static Map stuckState(ThreadMXBean mx, ThreadGroup main) { + Map state = new HashMap<>(); + for (Thread t : Thread.getAllStackTraces().keySet()) { + if (t == Thread.currentThread() || !t.isAlive() || !inGroup(t, main) + || t.getName().equals("DestroyJavaVM") || t.getName().startsWith("AsyncAppender-Worker")) { + continue; + } + Thread.State s = t.getState(); + if (s != Thread.State.BLOCKED && s != Thread.State.WAITING) { + return null; + } + long cpu = mx.isThreadCpuTimeSupported() ? mx.getThreadCpuTime(t.getId()) : -1; + state.put(t.getId(), t.getName() + " " + s + " " + cpu); + } + return state.isEmpty() ? null : state; + } + + private static boolean inGroup(Thread t, ThreadGroup group) { + for (ThreadGroup g = t.getThreadGroup(); g != null; g = g.getParent()) { + if (g == group) { + return true; + } + } + return false; + } +} diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/thread/ThreadContext.java b/symbolic-executor/src/main/java/de/uzl/its/swat/thread/ThreadContext.java index 25fb19f7..15764df5 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/thread/ThreadContext.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/thread/ThreadContext.java @@ -8,7 +8,6 @@ import de.uzl.its.swat.common.exceptions.ThreadAlreadyDisabledException; import de.uzl.its.swat.common.exceptions.ThreadAlreadyEnabledException; import de.uzl.its.swat.common.logging.GlobalLogger; -import de.uzl.its.swat.common.logging.records.ErrorRecord; import de.uzl.its.swat.common.logging.records.InvocationEntry; import de.uzl.its.swat.common.logging.LoggerUtils; import de.uzl.its.swat.common.logging.StatsStorage; @@ -23,9 +22,6 @@ import de.uzl.its.swat.symbolic.trace.SymbolicTraceHandler; import de.uzl.its.swat.symbolic.value.PlaceHolder; import de.uzl.its.swat.symbolic.value.Value; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.PrintStream; import java.util.HashMap; import java.util.concurrent.TimeUnit; @@ -35,7 +31,6 @@ import org.sosy_lab.common.configuration.Configuration; import org.sosy_lab.common.configuration.ConfigurationBuilder; import org.sosy_lab.common.configuration.InvalidConfigurationException; -import org.sosy_lab.common.log.BasicLogManager; import org.sosy_lab.common.log.LogManager; import org.sosy_lab.java_smt.SolverContextFactory; import org.sosy_lab.java_smt.api.SolverContext; @@ -53,7 +48,6 @@ public class ThreadContext { @Getter @Setter private Instruction current; @Getter @Setter private Long instCnt = 0L; @Getter private Instruction next; - @Getter private PrintStream statsStream; @Getter private boolean disabled; @Getter private boolean aborted; Config config = Config.instance(); @@ -70,6 +64,14 @@ public class ThreadContext { @Setter @Getter private int endpointID; @Setter @Getter private String endpointName; + // Number of endpoints entered while this context was already running, e.g. a harness main + // that calls another main. Their init/terminate calls only adjust this count. + @Getter @Setter private int nestedEndpoints = 0; + + // Set while the execution was started by the entry class's static initializer and the + // endpoint itself has not been entered yet. + @Getter @Setter private boolean startedBeforeEndpoint = false; + // Tracks if liftValue is called multiple times with the same index. @Getter private final HashMap symbolicIdxOccurrence = new java.util.HashMap<>(); private HashMap SubUid = new HashMap<>(); @@ -121,7 +123,9 @@ public ThreadContext(long id, String endpointName, int endpointID) throws Invali logger.info(SMTConfig.asPropertiesString()); - LogManager logger = BasicLogManager.create(SMTConfig); + // java-smt's own log is not read; a BasicLogManager would also register a JMX bean, which + // loads the JMX classes in every run + LogManager logger = LogManager.createNullLogManager(); ShutdownManager shutdown = ShutdownManager.create(); // SolverContext is a class wrapping a solver context. @@ -136,17 +140,6 @@ public ThreadContext(long id, String endpointName, int endpointID) throws Invali this.coverageTraceHandler = new CoverageTraceHandler(); - - try { - - this.statsStream = - new PrintStream( - new FileOutputStream( - config.getLoggingDirectory() + "/stats_" + id + ".json", - true)); - } catch (FileNotFoundException e) { - this.statsStream = System.out; - } } void checkAbortTimerExpiration() { @@ -207,6 +200,15 @@ public void setStaticField(int cIdx, int fIdx, Value value) statics.get(cIdx)[fIdx] = value; } + /** The shadow values of the static fields accessed so far, keyed by class name. */ + public java.util.Map[]> getStaticFields() { + java.util.Map[]> byName = new java.util.HashMap<>(); + for (java.util.Map.Entry[]> e : statics.entrySet()) { + byName.put(classDepot.getClassName(e.getKey()), e.getValue()); + } + return byName; + } + public Value getStaticField(int classIndex, int runtimeFieldIndex) throws ThreadAlreadyEnabledException, ThreadAlreadyDisabledException, NoThreadContextException, ClassNotFoundException { SWATAssert.check(runtimeFieldIndex >= 0, "Field ID must be non-negative"); @@ -236,6 +238,10 @@ public void recordMissingInvocation(InvocationEntry entry) { statsStorage.getInvocations().merge(entry, 1, Integer::sum); } + public void recordContextLossInvocation(InvocationEntry entry) { + statsStorage.recordContextLossInvocation(entry); + } + public long getNextSubUid(String symbolicVar) { long next = SubUid.getOrDefault(symbolicVar, 0L); @@ -244,13 +250,4 @@ public long getNextSubUid(String symbolicVar) { return next; } - /** - * Records an exception that occurred during the execution of the symbolic execution. This includes swallowed - * assertions. - * - * @param record The error record - */ - public void recordException(ErrorRecord record) { - statsStorage.getErrors().add(record); - } } diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/thread/ThreadHandler.java b/symbolic-executor/src/main/java/de/uzl/its/swat/thread/ThreadHandler.java index 1aab04eb..2154e2ef 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/thread/ThreadHandler.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/thread/ThreadHandler.java @@ -1,10 +1,10 @@ package de.uzl.its.swat.thread; +import de.uzl.its.swat.common.Profile; import ch.qos.logback.classic.Logger; import com.fasterxml.jackson.core.JsonProcessingException; import de.uzl.its.swat.common.exceptions.*; import de.uzl.its.swat.common.logging.GlobalLogger; -import de.uzl.its.swat.common.logging.records.ErrorRecord; import de.uzl.its.swat.common.logging.records.InvocationEntry; import de.uzl.its.swat.config.Config; import de.uzl.its.swat.coverage.CoverageRequest; @@ -18,16 +18,14 @@ import de.uzl.its.swat.symbolic.processor.SymbolicInstructionProcessor; import de.uzl.its.swat.symbolic.trace.SymbolicTraceHandler; import de.uzl.its.swat.symbolic.value.Value; -import java.io.PrintStream; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.concurrent.ThreadLocalRandom; import java.util.logging.LogRecord; import java.util.logging.SimpleFormatter; -import org.sosy_lab.common.ShutdownManager; -import org.sosy_lab.common.configuration.Configuration; import org.sosy_lab.common.configuration.InvalidConfigurationException; -import org.sosy_lab.common.log.BasicLogManager; -import org.sosy_lab.java_smt.SolverContextFactory; import org.sosy_lab.java_smt.api.SolverContext; class LogFormatter extends SimpleFormatter { @@ -47,21 +45,9 @@ public final class ThreadHandler { private static final HashMap threadContextHashMap = new HashMap<>(); - @SuppressWarnings("unused") - private static SolverContext solverContext; - private static final LogFormatter logFormatter = new LogFormatter(); private static final Config config = Config.instance(); - public static void init() throws InvalidConfigurationException { - solverContext = - SolverContextFactory.createSolverContext( - Configuration.defaultConfiguration(), - BasicLogManager.create(Configuration.defaultConfiguration()), - ShutdownManager.create().getNotifier(), - SolverContextFactory.Solvers.Z3); - } - public static boolean hasThreadContext(long id) { return threadContextHashMap.containsKey(id); } @@ -79,6 +65,64 @@ public static void addThreadContext(long id, String endpointName, int endpointId } } + /** + * Marks the thread's execution as started before its endpoint was entered. + * + * @param id the thread id + */ + public static void setStartedBeforeEndpoint(long id) throws NoThreadContextException { + ThreadContext context = threadContextHashMap.get(id); + if (context != null) { + context.setStartedBeforeEndpoint(true); + } else { + throw new NoThreadContextException(id); + } + } + + /** + * Enters the endpoint of an execution that was started before it. + * + * @param id the thread id + * @return true if the execution was waiting for its endpoint, false otherwise + */ + public static boolean enterStartedEndpoint(long id) { + ThreadContext context = threadContextHashMap.get(id); + if (context != null && context.isStartedBeforeEndpoint()) { + context.setStartedBeforeEndpoint(false); + return true; + } + return false; + } + + /** + * Records that an endpoint was entered on a thread whose symbolic execution is already running. + * + * @param id the thread id + */ + public static void enterNestedEndpoint(long id) throws NoThreadContextException { + ThreadContext context = threadContextHashMap.get(id); + if (context != null) { + context.setNestedEndpoints(context.getNestedEndpoints() + 1); + } else { + throw new NoThreadContextException(id); + } + } + + /** + * Leaves a nested endpoint if one is open. + * + * @param id the thread id + * @return true if a nested endpoint was left, false if the outermost endpoint ends + */ + public static boolean leaveNestedEndpoint(long id) { + ThreadContext context = threadContextHashMap.get(id); + if (context != null && context.getNestedEndpoints() > 0) { + context.setNestedEndpoints(context.getNestedEndpoints() - 1); + return true; + } + return false; + } + public static void removeThreadContext(long id) throws NoThreadContextException { ThreadContext context = threadContextHashMap.get(id); if (context != null) { @@ -139,6 +183,15 @@ public static UFHandler getUFHandler(long id) throws NoThreadContextException { } } + public static de.uzl.its.swat.common.logging.StatsStorage getStatsStorage(long id) throws NoThreadContextException { + ThreadContext context = threadContextHashMap.get(id); + if (context != null) { + return context.getStatsStorage(); + } else { + throw new NoThreadContextException(id); + } + } + public static void sendData(long id) throws NoThreadContextException, JsonProcessingException, NotImplementedException { ThreadContext context = threadContextHashMap.get(id); if (context != null) { @@ -155,8 +208,10 @@ public static void sendData(long id) throws NoThreadContextException, JsonProces } if(!config.isCoverageOnly()) { - ConstraintRequest.sendConstraints( - symbolicStateHandler.getTraceDTO(), endpointID, traceID); + String traceDTO = symbolicStateHandler.getTraceDTO(context.getStatsStorage(), id); + Profile.mark("trace.serialized"); + ConstraintRequest.sendConstraints(traceDTO, endpointID, traceID); + Profile.mark("trace.sent"); } } else { @@ -168,6 +223,20 @@ public static int getThreadCount() { return threadContextHashMap.size(); } + /** + * The ids of the target threads whose symbolic execution has started and not been terminated + * (the context of the instrumentation itself, id -1, is left out). + */ + public static List getActiveThreadContextIds() { + List ids = new ArrayList<>(); + for (Map.Entry e : threadContextHashMap.entrySet()) { + if (e.getKey() != -1 && !e.getValue().isDisabled()) { + ids.add(e.getKey()); + } + } + return ids; + } + public static void removeSolverContext(long id) throws NoThreadContextException { ThreadContext context = threadContextHashMap.get(id); if (context != null) { @@ -273,25 +342,12 @@ public static void recordMissingInvocation( } } - public static void recordException(long id, ErrorRecord er) throws NoThreadContextException { - ThreadContext context = threadContextHashMap.get(id); - if (context != null) { - context.recordException(er); - } else { - throw new NoThreadContextException(id); - } - } - - public static void logStats(long id) throws NoThreadContextException { + public static void recordContextLossInvocation( + long id, InvocationEntry entry) throws NoThreadContextException { if (!config.isLoggingStats()) return; ThreadContext context = threadContextHashMap.get(id); if (context != null) { - PrintStream ps = context.getStatsStream(); - try { - ps.println(context.getStatsStorage().convertToJson()); - } catch (JsonProcessingException e) { - logger.warn("Error logging missing invocation context."); - } + context.recordContextLossInvocation(entry); } else { throw new NoThreadContextException(id); } @@ -353,6 +409,16 @@ public static void setStaticField( } } + /** The shadow values of the static fields, by class: those the thread has accessed so far. */ + public static java.util.Map[]> getStaticFields(long id) throws NoThreadContextException { + ThreadContext context = threadContextHashMap.get(id); + if (context != null) { + return context.getStaticFields(); + } else { + throw new NoThreadContextException(id); + } + } + public static Value getStaticField(long id, int classIndex, int runtimeFieldIndex) throws ThreadAlreadyEnabledException, ThreadAlreadyDisabledException, NoThreadContextException, ClassNotFoundException { ThreadContext context = threadContextHashMap.get(id); diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/witness/Witness.java b/symbolic-executor/src/main/java/de/uzl/its/swat/witness/Witness.java index 10e1e79d..55d3af61 100644 --- a/symbolic-executor/src/main/java/de/uzl/its/swat/witness/Witness.java +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/witness/Witness.java @@ -1,77 +1,225 @@ package de.uzl.its.swat.witness; -import java.math.BigDecimal; -import java.util.Base64; +import de.uzl.its.swat.symbolic.trace.dto.FrameDTO; +import de.uzl.its.swat.symbolic.trace.dto.NondetReturnDTO; +import de.uzl.its.swat.symbolic.trace.dto.ViolationDTO; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; -public class Witness { - public static void recordValue(boolean value, int lineNumber, String cname, String desc) { - String witness = value + "@@@" + lineNumber + "@@@" + cname + "@@@" + desc; - String enc = Base64.getEncoder().encodeToString(witness.getBytes()); - System.out.println("[WITNESS] " + enc); +/** + * Records the data an SV-COMP violation witness is built from. The data travels to the explorer as + * part of the trace ({@code TraceDTO.nondetReturns} and {@code TraceDTO.violation}); nothing is + * printed for it. + * + *

    + *
  • {@link #recordValue} is invoked by the SV-COMP instrumentation right after every {@code + * Verifier.nondet*()} call with the returned value and the location of the call: the class, + * the calling method (name + descriptor), the class' SourceFile attribute, the line and the + * index of the call among the nondet calls on that line (bytecode order, which follows the + * left-to-right evaluation order). + *
  • {@link #recordThrow} is invoked by the SV-COMP instrumentation before every {@code athrow} + * of the task. An {@code AssertionError} is the violation of an {@code assert}, and it counts + * where it is thrown, whether or not it later escapes the entry method (a {@code finally} + * that calls {@code System.exit}, returns or throws would otherwise hide it). + *
  • {@link #recordTarget} is invoked by the surrounding try/catch of the entry method with the + * throwable that escaped it; its stack trace locates the violating statement. + *
+ * + *

The state is static: every execution of the target runs in its own JVM, and the recording + * calls may happen before a thread context exists (e.g. in static initializers). + */ +public final class Witness { + /** Upper bound on the recorded stack frames of the violation (after dropping JDK/SWAT frames). */ + static final int MAX_FRAMES = 32; + + /** Class name prefixes of frames that cannot be the violating statement of the task. */ + private static final String[] SKIPPED_FRAME_PREFIXES = { + "java.", "javax.", "jdk.", "sun.", "com.sun.", "de.uzl.its.swat.", "shadow.swat." + }; + + private static final List nondetReturns = + Collections.synchronizedList(new ArrayList<>()); + private static volatile ViolationDTO violation = null; + // The AssertionError reported by recordThrow, if any (the first one thrown in the task) + private static volatile Throwable thrownAssertion = null; + + private Witness() {} + + private static void record( + String function, + String type, + String value, + String bits, + String cname, + String method, + String sourceFile, + int line, + int callIndex) { + nondetReturns.add( + new NondetReturnDTO( + function, + type, + value, + bits, + cname.replace('/', '.'), + method, + sourceFile == null ? "" : sourceFile, + line, + callIndex)); + } + + public static void recordValue( + boolean value, String function, String cname, String method, String sourceFile, int line, int callIndex) { + record(function, "boolean", String.valueOf(value), null, cname, method, sourceFile, line, callIndex); + } + + public static void recordValue( + byte value, String function, String cname, String method, String sourceFile, int line, int callIndex) { + record(function, "byte", String.valueOf(value), null, cname, method, sourceFile, line, callIndex); + } + + public static void recordValue( + short value, String function, String cname, String method, String sourceFile, int line, int callIndex) { + record(function, "short", String.valueOf(value), null, cname, method, sourceFile, line, callIndex); + } + + public static void recordValue( + char value, String function, String cname, String method, String sourceFile, int line, int callIndex) { + // Chars travel as their (unsigned) code point; the witness renders the literal + record(function, "char", String.valueOf((int) value), null, cname, method, sourceFile, line, callIndex); } - public static void recordValue(byte value, int lineNumber, String cname, String desc) { - String witness = value + "@@@" + lineNumber + "@@@" + cname + "@@@" + desc; - String enc = Base64.getEncoder().encodeToString(witness.getBytes()); - System.out.println("[WITNESS] " + enc); + public static void recordValue( + int value, String function, String cname, String method, String sourceFile, int line, int callIndex) { + record(function, "int", String.valueOf(value), null, cname, method, sourceFile, line, callIndex); } - public static void recordValue(char value, int lineNumber, String cname, String desc) { - int encVal = Integer.valueOf(value); - String witness = encVal + "@@@" + lineNumber + "@@@" + cname + "@@@" + desc; - String enc = Base64.getEncoder().encodeToString(witness.getBytes()); - System.out.println("[WITNESS] " + enc); + public static void recordValue( + long value, String function, String cname, String method, String sourceFile, int line, int callIndex) { + record(function, "long", String.valueOf(value), null, cname, method, sourceFile, line, callIndex); } - public static void recordValue(short value, int lineNumber, String cname, String desc) { - String witness = value + "@@@" + lineNumber + "@@@" + cname + "@@@" + desc; - String enc = Base64.getEncoder().encodeToString(witness.getBytes()); - System.out.println("[WITNESS] " + enc); + public static void recordValue( + float value, String function, String cname, String method, String sourceFile, int line, int callIndex) { + // Float.toString is exact enough to round-trip (incl. NaN, Infinity, -0.0); the raw bits + // let the witness reproduce the value bit for bit. + record( + function, + "float", + Float.toString(value), + "0x" + Integer.toHexString(Float.floatToRawIntBits(value)), + cname, + method, + sourceFile, + line, + callIndex); } - public static void recordValue(int value, int lineNumber, String cname, String desc) { - String witness = value + "@@@" + lineNumber + "@@@" + cname + "@@@" + desc; - String enc = Base64.getEncoder().encodeToString(witness.getBytes()); - System.out.println("[WITNESS] " + enc); + public static void recordValue( + double value, String function, String cname, String method, String sourceFile, int line, int callIndex) { + record( + function, + "double", + Double.toString(value), + "0x" + Long.toHexString(Double.doubleToRawLongBits(value)), + cname, + method, + sourceFile, + line, + callIndex); } - public static void recordValue(float value, int lineNumber, String cname, String desc) { - // Use BigDecimal to avoid scientific notation (e.g., -2.022784E-6) - // but handle special values (NaN, Infinity) which BigDecimal doesn't support - String valueStr; - if (Float.isNaN(value) || Float.isInfinite(value)) { - valueStr = Float.toString(value); - } else { - valueStr = BigDecimal.valueOf(value).toPlainString(); + public static void recordValue( + String value, String function, String cname, String method, String sourceFile, int line, int callIndex) { + record(function, "String", value == null ? "" : value, null, cname, method, sourceFile, line, callIndex); + } + + /** + * Records the throwable that escaped the entry method and prints its stack trace (the explorer + * derives the verdict from that output). The frames locate the violating statement for the + * witness' target waypoint. + * + * @param throwable the uncaught throwable + */ + public static void recordTarget(Throwable throwable) { + if (throwable == thrownAssertion) { + // Reported when it was thrown + return; + } + throwable.printStackTrace(); + violation = describe(throwable); + } + + /** + * Reports the first {@code AssertionError} thrown by the task: prints its stack trace (the + * explorer derives the verdict from that output) and records it as the violation. Under the + * SV-COMP semantics the assertion fails where it is thrown, so the report must not depend on the + * error escaping the entry method. Other throwables are left to {@link #recordTarget}; a rethrow + * of the reported error (e.g. at the end of a {@code finally}) is ignored. + * + * @param throwable the throwable about to be thrown + */ + public static void recordThrow(Throwable throwable) { + if (!(throwable instanceof AssertionError) || thrownAssertion != null) { + return; + } + synchronized (Witness.class) { + if (thrownAssertion != null) return; + thrownAssertion = throwable; } - String witness = valueStr + "@@@" + lineNumber + "@@@" + cname + "@@@" + desc; - String enc = Base64.getEncoder().encodeToString(witness.getBytes()); - System.out.println("[WITNESS] " + enc); + throwable.printStackTrace(); + violation = describe(throwable); } - public static void recordValue(long value, int lineNumber, String cname, String desc) { - String witness = value + "@@@" + lineNumber + "@@@" + cname + "@@@" + desc; - String enc = Base64.getEncoder().encodeToString(witness.getBytes()); - System.out.println("[WITNESS] " + enc); + /** + * Builds the violation entry for a throwable: its class name and its stack frames, innermost + * first, without the JDK and SWAT frames (which cannot be the violating statement), capped at + * {@link #MAX_FRAMES}. + * + * @param throwable the throwable + * @return the violation entry + */ + public static ViolationDTO describe(Throwable throwable) { + List frames = new ArrayList<>(); + for (StackTraceElement frame : throwable.getStackTrace()) { + if (frames.size() >= MAX_FRAMES) break; + if (isSkipped(frame.getClassName())) continue; + frames.add( + new FrameDTO( + frame.getClassName(), + frame.getFileName() == null ? "" : frame.getFileName(), + frame.getLineNumber())); + } + return new ViolationDTO(throwable.getClass().getName(), frames); + } + + private static boolean isSkipped(String className) { + for (String prefix : SKIPPED_FRAME_PREFIXES) { + if (className.startsWith(prefix)) return true; + } + return false; } - public static void recordValue(double value, int lineNumber, String cname, String desc) { - // Use BigDecimal to avoid scientific notation (e.g., 1.23E-10) - // but handle special values (NaN, Infinity) which BigDecimal doesn't support - String valueStr; - if (Double.isNaN(value) || Double.isInfinite(value)) { - valueStr = Double.toString(value); - } else { - valueStr = BigDecimal.valueOf(value).toPlainString(); + /** The recorded nondet return values in execution order (a copy). */ + public static ArrayList getNondetReturns() { + synchronized (nondetReturns) { + return new ArrayList<>(nondetReturns); } - String witness = valueStr + "@@@" + lineNumber + "@@@" + cname + "@@@" + desc; - String enc = Base64.getEncoder().encodeToString(witness.getBytes()); - System.out.println("[WITNESS] " + enc); } - public static void recordValue(String value, int lineNumber, String cname, String desc) { - String witness = value + "@@@" + lineNumber + "@@@" + cname + "@@@" + desc; - String enc = Base64.getEncoder().encodeToString(witness.getBytes()); - System.out.println("[WITNESS] " + enc); + /** The recorded violation, or null if no throwable escaped the entry method (yet). */ + public static ViolationDTO getViolation() { + return violation; + } + + /** + * Clears the recorded data: for tests, and after each trace of a target that runs many + * executions in one JVM (see Intrinsics.terminate). + */ + public static void reset() { + nondetReturns.clear(); + violation = null; + thrownAssertion = null; } } diff --git a/symbolic-executor/src/test/groovy/de/uzl/its/swat/common/CheckClassAdapterSwitchSpec.groovy b/symbolic-executor/src/test/groovy/de/uzl/its/swat/common/CheckClassAdapterSwitchSpec.groovy new file mode 100644 index 00000000..4d509bf6 --- /dev/null +++ b/symbolic-executor/src/test/groovy/de/uzl/its/swat/common/CheckClassAdapterSwitchSpec.groovy @@ -0,0 +1,59 @@ +package de.uzl.its.swat.common + +import org.objectweb.asm.ClassVisitor +import org.objectweb.asm.ClassWriter +import org.objectweb.asm.Opcodes +import org.objectweb.asm.util.CheckClassAdapter +import spock.lang.Specification + +/** + * The transformers write instrumented classes through ASM's CheckClassAdapter only if + * {@code instrumentation.checkClassAdapter} is set (for the class). + */ +class CheckClassAdapterSwitchSpec extends Specification { + + def "the writer is wrapped only when the check is on for the class"() { + given: + def cw = new ClassWriter(0) + + expect: + (Util.withCheckClassAdapter(cw, "a/B", enabled, filter) instanceof CheckClassAdapter) == wrapped + Util.withCheckClassAdapter(cw, "a/B", enabled, filter).is(cw) == !wrapped + + where: + enabled | filter || wrapped + false | "*" || false + false | "a/B" || false + true | "*" || true + true | "a/B" || true + true | "a/C" || false + } + + def "with the check on, an invalid class is rejected while it is written"() { + given: + ClassVisitor cv = Util.withCheckClassAdapter(new ClassWriter(0), "a/B", true, "*") + cv.visit(Opcodes.V17, Opcodes.ACC_PUBLIC, "a/B", null, "java/lang/Object", null) + def mv = cv.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "m", "()V", null, null) + mv.visitCode() + + when: "an instruction with an invalid opcode for visitInsn" + mv.visitInsn(Opcodes.ILOAD) + + then: + thrown(IllegalArgumentException) + } + + def "with the check off, the same visit is passed on unchecked"() { + given: + ClassVisitor cv = Util.withCheckClassAdapter(new ClassWriter(0), "a/B", false, "*") + cv.visit(Opcodes.V17, Opcodes.ACC_PUBLIC, "a/B", null, "java/lang/Object", null) + def mv = cv.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "m", "()V", null, null) + mv.visitCode() + + when: + mv.visitInsn(Opcodes.ILOAD) + + then: + noExceptionThrown() + } +} diff --git a/symbolic-executor/src/test/groovy/de/uzl/its/swat/common/RefEqualsSpec.groovy b/symbolic-executor/src/test/groovy/de/uzl/its/swat/common/RefEqualsSpec.groovy new file mode 100644 index 00000000..331432a4 --- /dev/null +++ b/symbolic-executor/src/test/groovy/de/uzl/its/swat/common/RefEqualsSpec.groovy @@ -0,0 +1,110 @@ +package de.uzl.its.swat.common + +import spock.lang.Specification + +/** + * Concrete semantics of the IF_ACMP replacement: on objects de-interned the way the NoCache + * transformer does it, refEquals must give the answer the original program's {@code ==} gives. + */ +class RefEqualsSpec extends Specification { + + /** What the NoCache transformer emits for a string literal. */ + private static String literal(String s) { + String copy = new String(s) + RefIdentity.markDeInterned(copy) + return copy + } + + /** What the NoCache transformer emits for Integer.valueOf. */ + private static Integer boxed(int v) { + Integer copy = new Integer(v) + RefIdentity.markDeInterned(copy) + return copy + } + + def "two de-interned copies of one literal are the same instance"() { + expect: + UtilInstrumented.refEquals(literal("test"), literal("test")) + !UtilInstrumented.refEquals(literal("test"), literal("other")) + } + + def "a string built with new String is never a literal"() { + given: + String s1 = new String(literal("test")) + + expect: + !UtilInstrumented.refEquals(s1, literal("test")) + UtilInstrumented.refEquals(s1, s1) + } + + def "new String of an input differs from the input"() { + given: + String input = new String("fixed".toCharArray()) + String s1 = new String(input) + + expect: + !UtilInstrumented.refEquals(s1, input) + UtilInstrumented.refEquals(input, input) + } + + def "a pooled string from uninstrumented code equals a literal of its value"() { + expect: + UtilInstrumented.refEquals(Boolean.toString(true), literal("true")) + !UtilInstrumented.refEquals(Boolean.toString(false), literal("true")) + } + + def "intern returns the instance a literal of the same value stands for"() { + given: + String built = new StringBuilder("te").append("st").toString() + + expect: + UtilInstrumented.refEquals(UtilInstrumented.intern(built), literal("test")) + !UtilInstrumented.refEquals(built, literal("test")) + } + + def "boxes share an instance only inside the cache range"() { + expect: + UtilInstrumented.refEquals(boxed(v), boxed(v)) == shared + + where: + v || shared + 0 || true + 127 || true + -128 || true + 128 || false + -129 || false + 1000 || false + } + + def "a box is identical to itself outside the cache range"() { + given: + Integer b = boxed(1000) + + expect: + UtilInstrumented.refEquals(b, b) + } + + def "boxes created with new are distinct"() { + expect: + !UtilInstrumented.refEquals(new Integer(5), new Integer(5)) + !UtilInstrumented.refEquals(new Integer(5), boxed(5)) + UtilInstrumented.refEquals(Integer.valueOf(5), boxed(5)) + } + + def "boxes of different types or with null"() { + expect: + !UtilInstrumented.refEquals(boxed(5), Long.valueOf(5)) + !UtilInstrumented.refEquals(boxed(5), null) + UtilInstrumented.refEquals(null, null) + !UtilInstrumented.refEquals(literal("a"), null) + } + + def "other objects compare by identity"() { + given: + Object o = new Object() + + expect: + UtilInstrumented.refEquals(o, o) + !UtilInstrumented.refEquals(o, new Object()) + } +} diff --git a/symbolic-executor/src/test/groovy/de/uzl/its/swat/instrument/EntryAgentSpec.groovy b/symbolic-executor/src/test/groovy/de/uzl/its/swat/instrument/EntryAgentSpec.groovy new file mode 100644 index 00000000..0d45e30e --- /dev/null +++ b/symbolic-executor/src/test/groovy/de/uzl/its/swat/instrument/EntryAgentSpec.groovy @@ -0,0 +1,104 @@ +package de.uzl.its.swat.instrument + +import com.fasterxml.jackson.databind.ObjectMapper +import spock.lang.Specification + +import javax.tools.ToolProvider +import java.nio.file.Files +import java.nio.file.Path +import java.util.concurrent.TimeUnit + +/** + * L2 (forked agent): how the SV-COMP instrumentation starts and ends the symbolic execution of an + * entry method. Each target under {@code targets/entry/} is compiled with the SV-COMP + * Verifier stub and run with the agent in {@code solver.mode=PRINT}; the TraceDTO printed to + * stdout is checked. Runs via the {@code agentTest} Gradle task (needs the agent jar from + * {@code copyJar}). + */ +class EntryAgentSpec extends Specification { + + static final Path AGENT_JAR = Path.of("lib", "symbolic-executor.jar").toAbsolutePath() + static final Path NATIVE_LIBS = Path.of("..", "libs", "java-library-path").toAbsolutePath() + static final Path TARGETS = Path.of("src", "test", "resources", "targets").toAbsolutePath() + + def "an input read by the entry class's static initializer is tracked"() { + when: + def output = run("clinit", ["-Dswat.input.I_0=7"]) + def trace = parseTrace(output) + + then: + !output.contains("NoThreadContextException") + trace.inputs*.name == ["I_0"] + !trace.trace.isEmpty() + trace.violation?.throwable == "java.lang.AssertionError" + } + + def "a main called from the entry method continues the running execution"() { + when: + def output = run("nested", ["-Dswat.input.I_0=2", "-Dswat.input.I_1=2"]) + def trace = parseTrace(output) + + then: + !output.contains("DuplicateThreadContextException") + traceCount(output) == 1 + trace.inputs*.name.toSet() == ["I_0", "I_1"].toSet() + trace.violation?.throwable == "java.lang.AssertionError" + } + + def "an exception leaving a super constructor does not break the invocation stack"() { + when: + def output = run("initexception", ["-Dswat.input.I_0=3"]) + def trace = parseTrace(output) + + then: + !output.contains("Invocation stack is broken") + trace.inputs*.name == ["I_0"] + trace.violation?.throwable == "java.lang.AssertionError" + } + + private static String run(String target, List inputs) { + assert Files.isRegularFile(AGENT_JAR): "agent jar missing, run copyJar first: ${AGENT_JAR}" + Path classes = Files.createTempDirectory("swat-entry-agent-spec") + try { + compile([TARGETS.resolve("entry").resolve(target), TARGETS.resolve("svcomp").resolve("org")], classes) + def java = Path.of(System.getProperty("java.home"), "bin", "java").toString() + def cmd = [java, + "-javaagent:${AGENT_JAR}".toString(), + "-Djava.library.path=${NATIVE_LIBS}".toString(), + "-Dconfig.path=/nonexistent/swat.cfg", + "-Dinstrumentation.transformer=SV_COMP", + "-Dsolver.mode=PRINT", + "-DexitOnError=false", + "-DlogShadowStateToConsole=false"] + inputs + + ["-cp", classes.toString(), "-ea", "Main"] + def proc = new ProcessBuilder(cmd).directory(classes.toFile()).redirectErrorStream(true).start() + def out = proc.inputStream.text + assert proc.waitFor(5, TimeUnit.MINUTES): "target did not finish:\n${out}" + return out + } finally { + classes.toFile().deleteDir() + } + } + + private static void compile(List sourceDirs, Path outDir) { + def sources = sourceDirs.collectMany { dir -> + Files.walk(dir).filter { it.toString().endsWith(".java") }.collect { it.toString() } + } + def args = ["-source", "1.8", "-target", "1.8", "-g", "-d", outDir.toString()] + sources + def err = new ByteArrayOutputStream() + int rc = ToolProvider.getSystemJavaCompiler().run(null, null, err, args as String[]) + assert rc == 0: "javac failed: ${err}" + } + + private static int traceCount(String out) { + return out.split("\n").count { it == "{" } + } + + /** The TraceDTO is the single pretty-printed JSON object whose braces start at column 0. */ + private static Map parseTrace(String out) { + int start = out.indexOf("\n{\n") + int end = out.indexOf("\n}\n", start) + assert start >= 0 && end > start: "no trace JSON in output:\n${out}" + return new ObjectMapper().readValue(out.substring(start + 1, end + 2), Map) + } +} diff --git a/symbolic-executor/src/test/groovy/de/uzl/its/swat/instrument/svcomp/SvcompNondetObjectAgentSpec.groovy b/symbolic-executor/src/test/groovy/de/uzl/its/swat/instrument/svcomp/SvcompNondetObjectAgentSpec.groovy new file mode 100644 index 00000000..8d6bf633 --- /dev/null +++ b/symbolic-executor/src/test/groovy/de/uzl/its/swat/instrument/svcomp/SvcompNondetObjectAgentSpec.groovy @@ -0,0 +1,111 @@ +package de.uzl.its.swat.instrument.svcomp + +import com.fasterxml.jackson.databind.ObjectMapper +import spock.lang.Shared +import spock.lang.Specification + +import javax.tools.ToolProvider +import java.nio.file.Files +import java.nio.file.Path +import java.util.concurrent.TimeUnit + +/** + * L2 (forked agent): {@code Verifier.nondetObject(type, factory)} is rewritten to a call of the + * factory, so the factory's nondet choices are symbolic and the object it returns is tracked. A + * factory that is not instrumented makes concrete choices, which must show up as a context loss. + * Runs via the {@code agentTest} Gradle task (needs the agent jar from {@code copyJar}). + */ +class SvcompNondetObjectAgentSpec extends Specification { + + static final Path AGENT_JAR = Path.of("lib", "symbolic-executor.jar").toAbsolutePath() + static final Path NATIVE_LIBS = Path.of("..", "libs", "java-library-path").toAbsolutePath() + static final Path TARGET_SOURCES = Path.of("src", "test", "resources", "targets", "svcomp-objects").toAbsolutePath() + static final Path SV_COMP_CONFIG = Path.of("..", "targets", "sv-comp", "sv-comp.cfg").toAbsolutePath() + + @Shared Path classes + /** The exclusions of the competition config: the benchmark's org/sosy_lab classes stay uninstrumented. */ + @Shared String excludePackages + + def setupSpec() { + assert Files.isRegularFile(AGENT_JAR): "agent jar missing, run copyJar first: ${AGENT_JAR}" + classes = Files.createTempDirectory("swat-nondet-object-agent-spec") + compile(classes) + excludePackages = Files.readAllLines(SV_COMP_CONFIG) + .find { it.startsWith("instrumentation.excludePackages=") } + .substring("instrumentation.excludePackages=".length()) + } + + def cleanupSpec() { + classes?.toFile()?.deleteDir() + } + + def "the choices of an instrumented factory are symbolic and reach the violation"() { + when: + def output = run("Main", ["-Dswat.input.I_0=1", "-Dswat.input.I_1=42"]) + def trace = parseTrace(output) + + then: + !output.contains("Error while instrumenting") + trace.inputs*.name.findAll { it.startsWith("I_") }.size() == 2 + trace.trace.findAll { it.constraint?.contains("I_0") }.size() > 0 + trace.trace.findAll { it.constraint?.contains("I_1") }.size() > 0 + !trace.symbolicContextLoss + trace.violation?.throwable == "java.lang.AssertionError" + trace.nondetReturns*.function == ["nondetInt", "nondetInt"] + trace.nondetReturns*.className == ["objects.BoxFactory"] * 2 + trace.nondetReturns*.value == ["1", "42"] + } + + def "the null object is a path of its own"() { + when: + def trace = parseTrace(run("Main", ["-Dswat.input.I_0=0"])) + + then: + trace.violation == null + !trace.symbolicContextLoss + trace.trace.findAll { it.constraint?.contains("I_0") }.size() > 0 + } + + def "a factory that is not instrumented causes context loss"() { + when: + def trace = parseTrace(run("PlainMain", [])) + + then: + trace.symbolicContextLoss + trace.missingInvocations.any { it.name == "createObject" && it.contextLoss } + } + + private static void compile(Path outDir) { + def sources = Files.walk(TARGET_SOURCES).filter { it.toString().endsWith(".java") }.collect { it.toString() } + def args = ["-source", "1.8", "-target", "1.8", "-g", "-d", outDir.toString()] + sources + def err = new ByteArrayOutputStream() + int rc = ToolProvider.getSystemJavaCompiler().run(null, null, err, args as String[]) + assert rc == 0: "javac failed: ${err}" + } + + private String run(String mainClass, List inputs) { + def java = Path.of(System.getProperty("java.home"), "bin", "java").toString() + def cmd = [java, + "-javaagent:${AGENT_JAR}".toString(), + "-Djava.library.path=${NATIVE_LIBS}".toString(), + "-Dconfig.path=/nonexistent/swat.cfg", + "-Dinstrumentation.excludePackages=${excludePackages}".toString(), + "-Dinstrumentation.transformer=SV_COMP", + "-Dsolver.mode=PRINT", + "-DexitOnError=false", + "-DlogShadowStateToConsole=false"] + inputs + + ["-cp", classes.toString(), "-ea", mainClass] + def proc = new ProcessBuilder(cmd).directory(classes.toFile()).redirectErrorStream(true).start() + def out = proc.inputStream.text + assert proc.waitFor(5, TimeUnit.MINUTES): "target did not finish:\n${out}" + return out + } + + /** The TraceDTO is the single pretty-printed JSON object whose braces start at column 0. */ + private static Map parseTrace(String out) { + int start = out.indexOf("\n{\n") + int end = out.indexOf("\n}\n", start) + assert start >= 0 && end > start: "no trace JSON in output:\n${out}" + return new ObjectMapper().readValue(out.substring(start + 1, end + 2), Map) + } +} diff --git a/symbolic-executor/src/test/groovy/de/uzl/its/swat/metadata/ClassDepotTypeInfoSpec.groovy b/symbolic-executor/src/test/groovy/de/uzl/its/swat/metadata/ClassDepotTypeInfoSpec.groovy new file mode 100644 index 00000000..ccca79f0 --- /dev/null +++ b/symbolic-executor/src/test/groovy/de/uzl/its/swat/metadata/ClassDepotTypeInfoSpec.groovy @@ -0,0 +1,29 @@ +package de.uzl.its.swat.metadata + +import spock.lang.Specification + +/** + * L0: the registered type metadata of a class is shared, so the lists ClassDepot hands out must + * not grow when callers merge them (as ValueFactory.createObjectValue does on every NEW). + */ +class ClassDepotTypeInfoSpec extends Specification { + + def "the parent and interface lists of a class cannot be modified through the getters"() { + given: + def depot = ClassDepot.getInstrumentationInstance() + depot.registerTypeInfoForClass("spec/TypeInfoA", ["java/lang/Object"], ["java/lang/Runnable"]) + + when: + depot.getInterfacesForClass("spec/TypeInfoA").addAll(depot.getParentsForClass("spec/TypeInfoA")) + + then: + thrown(UnsupportedOperationException) + depot.getInterfacesForClass("spec/TypeInfoA") == ["java/lang/Runnable"] + } + + def "an unregistered class has empty lists"() { + expect: + ClassDepot.getRuntimeInstance().getParentsForClass("spec/TypeInfoUnknown").isEmpty() + ClassDepot.getRuntimeInstance().getInterfacesForClass("spec/TypeInfoUnknown").isEmpty() + } +} diff --git a/symbolic-executor/src/test/groovy/de/uzl/its/swat/symbolic/JdkContainerAgentSpec.groovy b/symbolic-executor/src/test/groovy/de/uzl/its/swat/symbolic/JdkContainerAgentSpec.groovy new file mode 100644 index 00000000..fb9d2395 --- /dev/null +++ b/symbolic-executor/src/test/groovy/de/uzl/its/swat/symbolic/JdkContainerAgentSpec.groovy @@ -0,0 +1,84 @@ +package de.uzl.its.swat.symbolic + +import com.fasterxml.jackson.databind.ObjectMapper +import spock.lang.Shared +import spock.lang.Specification +import spock.lang.Unroll + +import javax.tools.ToolProvider +import java.nio.file.Files +import java.nio.file.Path +import java.util.concurrent.TimeUnit + +/** + * L2 (forked agent): symbolic values that go through modelled JDK container calls stay symbolic + * without context loss; calls that inspect a container holding a symbolic value still record it. + * Runs via the {@code agentTest} Gradle task (needs the agent jar from {@code copyJar}). + */ +class JdkContainerAgentSpec extends Specification { + + static final Path AGENT_JAR = Path.of("lib", "symbolic-executor.jar").toAbsolutePath() + static final Path NATIVE_LIBS = Path.of("..", "libs", "java-library-path").toAbsolutePath() + static final Path TARGET = Path.of("src", "test", "resources", "targets", "collections").toAbsolutePath() + static final Path VERIFIER = Path.of("src", "test", "resources", "targets", "svcomp", "org").toAbsolutePath() + + @Shared Path classes + + def setupSpec() { + assert Files.isRegularFile(AGENT_JAR): "agent jar missing, run copyJar first: ${AGENT_JAR}" + classes = Files.createTempDirectory("swat-container-agent-spec") + def sources = ([TARGET, VERIFIER].collectMany { root -> + Files.walk(root).filter { it.toString().endsWith(".java") }.collect { it.toString() } + }) + def err = new ByteArrayOutputStream() + int rc = ToolProvider.getSystemJavaCompiler().run(null, null, err, + (["-source", "1.8", "-target", "1.8", "-g", "-d", classes.toString()] + sources) as String[]) + assert rc == 0: "javac failed: ${err}" + } + + def cleanupSpec() { + classes?.toFile()?.deleteDir() + } + + @Unroll + def "a symbolic string kept in a container stays symbolic (#scenario)"() { + when: + Map trace = run(scenario) + + then: + !trace.symbolicContextLoss + trace.trace.any { !it.concreteOnly && it.constraint?.contains("String_0") } + + where: + scenario << ["list", "iterator", "map", "array", "arraycopy"] + } + + @Unroll + def "a call that inspects a container holding a symbolic value records context loss (#scenario)"() { + expect: + run(scenario).symbolicContextLoss + + where: + scenario << ["contains", "symbolicKey", "heldByUnmodelled"] + } + + private Map run(String scenario) { + def java = Path.of(System.getProperty("java.home"), "bin", "java").toString() + def cmd = [java, + "-javaagent:${AGENT_JAR}".toString(), + "-Djava.library.path=${NATIVE_LIBS}".toString(), + "-Dconfig.path=/nonexistent/swat.cfg", + "-Dinstrumentation.transformer=SV_COMP", + "-Dsolver.mode=PRINT", + "-DexitOnError=false", + "-DlogShadowStateToConsole=false", + "-cp", classes.toString(), "-ea", "Main", scenario] + def proc = new ProcessBuilder(cmd).directory(classes.toFile()).redirectErrorStream(true).start() + def out = proc.inputStream.text + assert proc.waitFor(5, TimeUnit.MINUTES): "target did not finish:\n${out}" + int start = out.indexOf("\n{\n") + int end = out.indexOf("\n}\n", start) + assert start >= 0 && end > start: "no trace JSON in output:\n${out}" + return new ObjectMapper().readValue(out.substring(start + 1, end + 2), Map) + } +} diff --git a/symbolic-executor/src/test/groovy/de/uzl/its/swat/symbolic/RefEqualsHelperAgentSpec.groovy b/symbolic-executor/src/test/groovy/de/uzl/its/swat/symbolic/RefEqualsHelperAgentSpec.groovy new file mode 100644 index 00000000..5380594c --- /dev/null +++ b/symbolic-executor/src/test/groovy/de/uzl/its/swat/symbolic/RefEqualsHelperAgentSpec.groovy @@ -0,0 +1,97 @@ +package de.uzl.its.swat.symbolic + +import com.fasterxml.jackson.databind.ObjectMapper +import spock.lang.Shared +import spock.lang.Specification + +import javax.tools.ToolProvider +import java.nio.file.Files +import java.nio.file.Path +import java.util.concurrent.TimeUnit + +/** + * L2 (forked agent): the branches of SWAT's helper for reference comparisons + * (UtilInstrumented.refEquals) are flagged like callbacks, so the static pre-analysis walk holds + * its place for them; the comparison's own branch is not, and keeps its key. How many branches the + * helper takes depends on the operands' runtime types, so the graph cannot have them. Runs via the + * {@code agentTest} Gradle task (needs the agent jar from {@code copyJar}). + */ +class RefEqualsHelperAgentSpec extends Specification { + + static final Path AGENT_JAR = Path.of("lib", "symbolic-executor.jar").toAbsolutePath() + static final Path NATIVE_LIBS = Path.of("..", "libs", "java-library-path").toAbsolutePath() + static final Path TARGETS = Path.of("src", "test", "resources", "targets").toAbsolutePath() + static final Path SOURCE = TARGETS.resolve("svcomp-refequals/Main.java") + + @Shared Path classes + @Shared Map trace + @Shared String comparisonKey + + def setupSpec() { + assert Files.isRegularFile(AGENT_JAR): "agent jar missing, run copyJar first: ${AGENT_JAR}" + classes = Files.createTempDirectory("swat-refequals-agent-spec") + compile(classes) + trace = parseTrace(run(classes)) + int line = Files.readAllLines(SOURCE).findIndexOf { it.contains("if (a == b)") } + 1 + comparisonKey = "Main.main([Ljava/lang/String;)V|${line}|if|0".toString() + } + + def cleanupSpec() { + classes?.toFile()?.deleteDir() + } + + private List branches() { + return trace.trace.findAll { it.type != "Special" } + } + + def "the helper's branches are flagged, the comparison's own branch is not"() { + when: + def all = branches() + int comparison = all.findIndexOf { it.key == comparisonKey } + def helper = all.subList(0, comparison).findAll { it.key == null } + + then: + comparison >= 0 + !all[comparison].callback + !helper.isEmpty() + helper.every { it.callback } + } + + def "the helper's frames are not reported as stack changes"() { + expect: + branches().every { b -> (b.pushes ?: []).every { it == null || !it.contains("refEquals") } } + } + + private static void compile(Path outDir) { + def sources = [SOURCE, TARGETS.resolve("svcomp/org/sosy_lab/sv_benchmarks/Verifier.java")]*.toString() + def args = ["-source", "1.8", "-target", "1.8", "-g", "-d", outDir.toString()] + sources + def err = new ByteArrayOutputStream() + int rc = ToolProvider.getSystemJavaCompiler().run(null, null, err, args as String[]) + assert rc == 0: "javac failed: ${err}" + } + + private static String run(Path classes) { + def java = Path.of(System.getProperty("java.home"), "bin", "java").toString() + def cmd = [java, + "-javaagent:${AGENT_JAR}".toString(), + "-Djava.library.path=${NATIVE_LIBS}".toString(), + "-Dconfig.path=/nonexistent/swat.cfg", + "-Dinstrumentation.transformer=SV_COMP", + "-Dsolver.mode=PRINT", + "-DexitOnError=false", + "-DlogShadowStateToConsole=false", + "-cp", classes.toString(), "-ea", "Main"] + def proc = new ProcessBuilder(cmd).directory(classes.toFile()).redirectErrorStream(true).start() + def out = proc.inputStream.text + assert proc.waitFor(5, TimeUnit.MINUTES): "target did not finish:\n${out}" + return out + } + + /** The TraceDTO is the single pretty-printed JSON object whose braces start at column 0. */ + private static Map parseTrace(String out) { + int start = out.indexOf("\n{\n") + int end = out.indexOf("\n}\n", start) + assert start >= 0 && end > start: "no trace JSON in output:\n${out}" + return new ObjectMapper().readValue(out.substring(start + 1, end + 2), Map) + } +} diff --git a/symbolic-executor/src/test/groovy/de/uzl/its/swat/symbolic/StackOverflowAgentSpec.groovy b/symbolic-executor/src/test/groovy/de/uzl/its/swat/symbolic/StackOverflowAgentSpec.groovy new file mode 100644 index 00000000..81ddf82a --- /dev/null +++ b/symbolic-executor/src/test/groovy/de/uzl/its/swat/symbolic/StackOverflowAgentSpec.groovy @@ -0,0 +1,116 @@ +package de.uzl.its.swat.symbolic + +import com.fasterxml.jackson.databind.ObjectMapper +import spock.lang.Shared +import spock.lang.Specification + +import javax.tools.ToolProvider +import java.nio.file.Files +import java.nio.file.Path +import java.util.concurrent.TimeUnit + +/** + * L2 (forked agent): a StackOverflowError in the target behaves as without SWAT. The target + * recurses as deep as its first input, on a small stack so that the overflow comes quickly; the + * second input selects whether main catches the overflow. Run in {@code solver.mode=PRINT}, the + * target must produce its own output, and the trace must be sent and marked as context loss and as + * stack overflow. Runs via the {@code agentTest} Gradle task (needs the agent jar from + * {@code copyJar}). + */ +class StackOverflowAgentSpec extends Specification { + + static final Path AGENT_JAR = Path.of("lib", "symbolic-executor.jar").toAbsolutePath() + static final Path NATIVE_LIBS = Path.of("..", "libs", "java-library-path").toAbsolutePath() + static final Path TARGET_SOURCES = Path.of("src", "test", "resources", "targets", "stackoverflow").toAbsolutePath() + static final String DEEP = "1000000000" + + @Shared Path classes + + def setupSpec() { + assert Files.isRegularFile(AGENT_JAR): "agent jar missing, run copyJar first: ${AGENT_JAR}" + classes = Files.createTempDirectory("swat-stackoverflow-agent-spec") + compile(classes) + } + + def cleanupSpec() { + classes?.toFile()?.deleteDir() + } + + def "a shallow recursion is tracked as usual"() { + when: + def out = run(classes, "100", "0") + def trace = parseTrace(out) + + then: + out.contains("result 100") + !trace.stackOverflow + !trace.symbolicContextLoss + trace.violation == null + } + + def "an overflow caught by the target is handled by the target and recorded in the trace"() { + when: + def out = run(classes, DEEP, "0") + def trace = parseTrace(out) + + then: "the target's handler ran, and SWAT neither crashed nor halted" + out.contains("result -1") + !out.contains("[SWAT Exception]") + !out.contains("NoClassDefFoundError") + + and: "the trace up to the overflow is sent, and cannot lead to SAFE or to a violation" + trace.stackOverflow + trace.symbolicContextLoss + trace.trace.size() > 0 + } + + def "an uncaught overflow ends the target as without SWAT"() { + when: + def out = run(classes, DEEP, "1") + def trace = parseTrace(out) + + then: + !out.contains("result") + !out.contains("[SWAT Exception]") + !out.contains("[SWAT] Uncaught top-level RuntimeException") + trace.violation.throwable == "java.lang.StackOverflowError" + trace.stackOverflow + trace.symbolicContextLoss + } + + private static void compile(Path outDir) { + def sources = Files.walk(TARGET_SOURCES).filter { it.toString().endsWith(".java") }.collect { it.toString() } + def args = ["-source", "1.8", "-target", "1.8", "-g", "-d", outDir.toString()] + sources + def err = new ByteArrayOutputStream() + int rc = ToolProvider.getSystemJavaCompiler().run(null, null, err, args as String[]) + assert rc == 0: "javac failed: ${err}" + } + + private static String run(Path classes, String n, String uncaught) { + def java = Path.of(System.getProperty("java.home"), "bin", "java").toString() + def cmd = [java, + "-Xss256k", + "-javaagent:${AGENT_JAR}".toString(), + "-Djava.library.path=${NATIVE_LIBS}".toString(), + "-Dconfig.path=/nonexistent/swat.cfg", + "-Dinstrumentation.transformer=SV_COMP", + "-Dsolver.mode=PRINT", + "-DexitOnError=true", + "-DlogShadowStateToConsole=false", + "-Dswat.input.I_0=${n}".toString(), + "-Dswat.input.I_1=${uncaught}".toString(), + "-cp", classes.toString(), "-ea", "Main"] + def proc = new ProcessBuilder(cmd).directory(classes.toFile()).redirectErrorStream(true).start() + def out = proc.inputStream.text + assert proc.waitFor(5, TimeUnit.MINUTES): "target did not finish:\n${out}" + return out + } + + /** The TraceDTO is the single pretty-printed JSON object whose braces start at column 0. */ + private static Map parseTrace(String out) { + int start = out.indexOf("\n{\n") + int end = out.indexOf("\n}\n", start) + assert start >= 0 && end > start: "no trace JSON in output:\n${out}" + return new ObjectMapper().readValue(out.substring(start + 1, end + 2), Map) + } +} diff --git a/symbolic-executor/src/test/groovy/de/uzl/its/swat/symbolic/StringIndexCheckAgentSpec.groovy b/symbolic-executor/src/test/groovy/de/uzl/its/swat/symbolic/StringIndexCheckAgentSpec.groovy new file mode 100644 index 00000000..c746d25e --- /dev/null +++ b/symbolic-executor/src/test/groovy/de/uzl/its/swat/symbolic/StringIndexCheckAgentSpec.groovy @@ -0,0 +1,92 @@ +package de.uzl.its.swat.symbolic + +import com.fasterxml.jackson.databind.ObjectMapper +import spock.lang.Shared +import spock.lang.Specification + +import javax.tools.ToolProvider +import java.nio.file.Files +import java.nio.file.Path +import java.util.concurrent.TimeUnit + +/** + * L2 (forked agent): the index check of String.charAt is a branch of the trace, on both sides. A + * tiny SV-COMP style target calls charAt(5) on a nondet string and catches the exception; it is run + * with the agent in {@code solver.mode=PRINT}, once with the default input "fixed" (too short: the + * call throws) and once with a longer one. Runs via the {@code agentTest} Gradle task (needs the + * agent jar from {@code copyJar}). + */ +class StringIndexCheckAgentSpec extends Specification { + + static final Path AGENT_JAR = Path.of("lib", "symbolic-executor.jar").toAbsolutePath() + static final Path NATIVE_LIBS = Path.of("..", "libs", "java-library-path").toAbsolutePath() + static final Path TARGETS = Path.of("src", "test", "resources", "targets").toAbsolutePath() + + @Shared Path classes + @Shared Map shortTrace + @Shared Map longTrace + + def setupSpec() { + assert Files.isRegularFile(AGENT_JAR): "agent jar missing, run copyJar first: ${AGENT_JAR}" + classes = Files.createTempDirectory("swat-string-index-agent-spec") + compile(classes) + shortTrace = parseTrace(run(classes, [])) + longTrace = parseTrace(run(classes, ["-Dswat.input.java/lang/String_0=abcdefgh"])) + } + + def cleanupSpec() { + classes?.toFile()?.deleteDir() + } + + private static List indexChecks(Map trace) { + return trace.trace.findAll { it.callback && it.constraint?.contains("str.len") } + } + + def "a call that throws at the index check records the failed check, not a context loss"() { + expect: + indexChecks(shortTrace).size() == 1 + !indexChecks(shortTrace)[0].branched + !shortTrace.symbolicContextLoss + } + + def "a call that returns records the check as held"() { + expect: + indexChecks(longTrace).size() == 1 + indexChecks(longTrace)[0].branched + !longTrace.symbolicContextLoss + } + + private static void compile(Path outDir) { + def sources = [TARGETS.resolve("svcomp-strings/Main.java"), + TARGETS.resolve("svcomp/org/sosy_lab/sv_benchmarks/Verifier.java")]*.toString() + def args = ["-source", "1.8", "-target", "1.8", "-g", "-d", outDir.toString()] + sources + def err = new ByteArrayOutputStream() + int rc = ToolProvider.getSystemJavaCompiler().run(null, null, err, args as String[]) + assert rc == 0: "javac failed: ${err}" + } + + private static String run(Path classes, List inputs) { + def java = Path.of(System.getProperty("java.home"), "bin", "java").toString() + def cmd = [java, + "-javaagent:${AGENT_JAR}".toString(), + "-Djava.library.path=${NATIVE_LIBS}".toString(), + "-Dconfig.path=/nonexistent/swat.cfg", + "-Dinstrumentation.transformer=SV_COMP", + "-Dsolver.mode=PRINT", + "-DexitOnError=false", + "-DlogShadowStateToConsole=false"] + inputs + + ["-cp", classes.toString(), "-ea", "Main"] + def proc = new ProcessBuilder(cmd).directory(classes.toFile()).redirectErrorStream(true).start() + def out = proc.inputStream.text + assert proc.waitFor(5, TimeUnit.MINUTES): "target did not finish:\n${out}" + return out + } + + /** The TraceDTO is the single pretty-printed JSON object whose braces start at column 0. */ + private static Map parseTrace(String out) { + int start = out.indexOf("\n{\n") + int end = out.indexOf("\n}\n", start) + assert start >= 0 && end > start: "no trace JSON in output:\n${out}" + return new ObjectMapper().readValue(out.substring(start + 1, end + 2), Map) + } +} diff --git a/symbolic-executor/src/test/groovy/de/uzl/its/swat/symbolic/array/ObjectArrayIndexAgentSpec.groovy b/symbolic-executor/src/test/groovy/de/uzl/its/swat/symbolic/array/ObjectArrayIndexAgentSpec.groovy new file mode 100644 index 00000000..8edc50f2 --- /dev/null +++ b/symbolic-executor/src/test/groovy/de/uzl/its/swat/symbolic/array/ObjectArrayIndexAgentSpec.groovy @@ -0,0 +1,95 @@ +package de.uzl.its.swat.symbolic.array + +import com.fasterxml.jackson.databind.ObjectMapper +import spock.lang.Shared +import spock.lang.Specification + +import javax.tools.ToolProvider +import java.nio.file.Files +import java.nio.file.Path +import java.util.concurrent.TimeUnit + +/** + * L2 (forked agent): an access to an object array at a symbolic index branches on which element + * the index selects (a binary search over the array's length), so the explorer can reach every + * element; only an array too large to search records precision loss. Runs via the + * {@code agentTest} Gradle task (needs the agent jar from {@code copyJar}). + */ +class ObjectArrayIndexAgentSpec extends Specification { + + static final Path AGENT_JAR = Path.of("lib", "symbolic-executor.jar").toAbsolutePath() + static final Path NATIVE_LIBS = Path.of("..", "libs", "java-library-path").toAbsolutePath() + static final Path TARGETS = Path.of("src", "test", "resources", "targets").toAbsolutePath() + + @Shared Path classes + @Shared List choices + @Shared Map trace + @Shared Map largeTrace + + def setupSpec() { + assert Files.isRegularFile(AGENT_JAR): "agent jar missing, run copyJar first: ${AGENT_JAR}" + classes = Files.createTempDirectory("swat-object-array-spec") + compile(classes) + trace = parseTrace(run(classes, ["-Dswat.input.I_0=7"], [])) + largeTrace = parseTrace(run(classes, ["-Dswat.input.I_0=7"], ["large"])) + choices = trace.trace.findAll { it.choice } + } + + def cleanupSpec() { + classes?.toFile()?.deleteDir() + } + + def "a load and a store at a symbolic index each search the ten elements in three steps"() { + // [0,10): 7 < 5 no; [5,10): 7 < 7 no; [7,10): 7 < 8 yes. The array of length one needs none. + expect: + choices.size() == 6 + choices*.branched == [false, false, true] * 2 + choices*.key.collect { it.substring(it.indexOf('#')) } == ["#idx0", "#idx1", "#idx2"] * 2 + choices.every { it.constraint.contains("I_0") } + } + + def "the searched accesses record no precision loss"() { + expect: + !trace.symbolicPrecisionLoss + } + + def "an array too large to search records precision loss instead"() { + expect: + largeTrace.symbolicPrecisionLoss + largeTrace.trace.count { it.choice } == 6 + } + + private static void compile(Path outDir) { + def sources = [TARGETS.resolve("objectarray/Main.java"), + TARGETS.resolve("svcomp/org/sosy_lab/sv_benchmarks/Verifier.java")]*.toString() + def args = ["-source", "1.8", "-target", "1.8", "-g", "-d", outDir.toString()] + sources + def err = new ByteArrayOutputStream() + int rc = ToolProvider.getSystemJavaCompiler().run(null, null, err, args as String[]) + assert rc == 0: "javac failed: ${err}" + } + + private static String run(Path classes, List inputs, List args) { + def java = Path.of(System.getProperty("java.home"), "bin", "java").toString() + def cmd = [java, + "-javaagent:${AGENT_JAR}".toString(), + "-Djava.library.path=${NATIVE_LIBS}".toString(), + "-Dconfig.path=/nonexistent/swat.cfg", + "-Dinstrumentation.transformer=SV_COMP", + "-Dsolver.mode=PRINT", + "-DexitOnError=false", + "-DlogShadowStateToConsole=false"] + inputs + + ["-cp", classes.toString(), "-ea", "Main"] + args + def proc = new ProcessBuilder(cmd).directory(classes.toFile()).redirectErrorStream(true).start() + def out = proc.inputStream.text + assert proc.waitFor(5, TimeUnit.MINUTES): "target did not finish:\n${out}" + return out + } + + /** The TraceDTO is the single pretty-printed JSON object whose braces start at column 0. */ + private static Map parseTrace(String out) { + int start = out.indexOf("\n{\n") + int end = out.indexOf("\n}\n", start) + assert start >= 0 && end > start: "no trace JSON in output:\n${out}" + return new ObjectMapper().readValue(out.substring(start + 1, end + 2), Map) + } +} diff --git a/symbolic-executor/src/test/groovy/de/uzl/its/swat/symbolic/invoke/MathInvocationSpec.groovy b/symbolic-executor/src/test/groovy/de/uzl/its/swat/symbolic/invoke/MathInvocationSpec.groovy new file mode 100644 index 00000000..ba04bb3f --- /dev/null +++ b/symbolic-executor/src/test/groovy/de/uzl/its/swat/symbolic/invoke/MathInvocationSpec.groovy @@ -0,0 +1,161 @@ +package de.uzl.its.swat.symbolic.invoke + +import de.uzl.its.swat.symbolic.invoke.java.lang.MathInvocation +import de.uzl.its.swat.symbolic.value.Value +import de.uzl.its.swat.symbolic.value.primitive.numeric.floatingpoint.DoubleValue +import de.uzl.its.swat.symbolic.value.primitive.numeric.floatingpoint.FloatValue +import de.uzl.its.swat.symbolic.value.primitive.numeric.integral.IntValue +import de.uzl.its.swat.symbolic.value.primitive.numeric.integral.LongValue +import org.sosy_lab.common.ShutdownManager +import org.sosy_lab.common.configuration.Configuration +import org.sosy_lab.common.log.BasicLogManager +import org.sosy_lab.java_smt.SolverContextFactory +import org.sosy_lab.java_smt.api.* +import spock.lang.Shared +import spock.lang.Specification + +/** + * The exact Math models agree with the JDK on every input: for symbolic arguments fixed to the + * input, the model's result can only be the JDK's result (bit for bit, so NaN and -0.0 count). + */ +class MathInvocationSpec extends Specification { + + @Shared SolverContext context + @Shared FloatingPointFormulaManager fpmgr + @Shared BitvectorFormulaManager bvmgr + + static final List DOUBLES = [0.0d, -0.0d, 0.5d, -0.5d, 1.5d, -1.5d, 2.5d, -2.5d, 0.49999999999999994d, + -0.49999999999999994d, 1e-300d, -1e-20d, 3.7d, -3.7d, 4503599627370495.5d, 4503599627370497d, + 9.223372036854775807E18d, -9.223372036854775808E18d, 1e300d, Double.MIN_VALUE, Double.MAX_VALUE, + Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NaN] + static final List FLOATS = [0.0f, -0.0f, 0.5f, -0.5f, 2.5f, -2.5f, 0.49999997f, -1e-20f, 3.7f, -3.7f, + 8388607.5f, 2.14748365E9f, -2.14748365E9f, Float.MIN_VALUE, Float.MAX_VALUE, + Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY, Float.NaN] + static final List INTS = [0, 1, -1, 7, -7, 3, -3, Integer.MAX_VALUE, Integer.MIN_VALUE] + static final List LONGS = [0L, 1L, -1L, 7L, -7L, 3L, -3L, Long.MAX_VALUE, Long.MIN_VALUE] + + def setupSpec() { + context = SolverContextFactory.createSolverContext( + Configuration.defaultConfiguration(), + BasicLogManager.create(Configuration.defaultConfiguration()), + ShutdownManager.create().getNotifier(), + SolverContextFactory.Solvers.Z3) + fpmgr = context.getFormulaManager().getFloatingPointFormulaManager() + bvmgr = context.getFormulaManager().getBitvectorFormulaManager() + } + + def cleanupSpec() { + context.close() + } + + private int vars = 0 + + /** A symbolic argument fixed to the value v by the returned constraint. */ + private List symbolic(Object v) { + String name = "X" + (vars++) + if (v instanceof Double) { + def x = fpmgr.makeVariable(name, FormulaType.getDoublePrecisionFloatingPointType()) + return [new DoubleValue(context, v, x), fpmgr.assignment(x, constant(v))] + } else if (v instanceof Float) { + def x = fpmgr.makeVariable(name, FormulaType.getSinglePrecisionFloatingPointType()) + return [new FloatValue(context, v, x), fpmgr.assignment(x, constant(v))] + } else if (v instanceof Long) { + def x = bvmgr.makeVariable(64, name) + return [new LongValue(context, v, x), bvmgr.equal(x, bvmgr.makeBitvector(64, v))] + } else { + def x = bvmgr.makeVariable(32, name) + return [new IntValue(context, v as int, x), bvmgr.equal(x, bvmgr.makeBitvector(32, v as int))] + } + } + + private Formula constant(Object v) { + if (v instanceof Double) return v.isNaN() ? fpmgr.makeNaN(FormulaType.getDoublePrecisionFloatingPointType()) : new DoubleValue(context, v).formula + if (v instanceof Float) return v.isNaN() ? fpmgr.makeNaN(FormulaType.getSinglePrecisionFloatingPointType()) : new FloatValue(context, v).formula + if (v instanceof Long) return bvmgr.makeBitvector(64, v) + return bvmgr.makeBitvector(32, v as int) + } + + /** Whether the model of name on the arguments agrees with expected, symbolically and concretely. */ + private boolean agrees(String name, List args, Object expected) { + def fixed = args.collect { symbolic(it) } + Value result = MathInvocation.invokeStaticMethod(name, fixed.collect { it[0] } as Value[], null, null, false) + def bmgr = context.getFormulaManager().getBooleanFormulaManager() + def differs = result.formula instanceof FloatingPointFormula + ? bmgr.not(fpmgr.assignment(result.formula, constant(expected))) + : bmgr.not(bvmgr.equal(result.formula, constant(expected))) + def prover = context.newProverEnvironment() + try { + fixed.each { prover.addConstraint(it[1]) } + prover.addConstraint(differs) + boolean concreteOk = (expected instanceof Double || expected instanceof Float) + ? Double.compare(((Number) result.concrete).doubleValue(), ((Number) expected).doubleValue()) == 0 + : result.concrete == expected + return prover.isUnsat() && concreteOk + } finally { + prover.close() + } + } + + def "round(double) rounds ties toward positive infinity and saturates"() { + expect: + DOUBLES.every { agrees("round", [it], Math.round(it)) } + } + + def "round(float) rounds ties toward positive infinity and saturates"() { + expect: + FLOATS.every { agrees("round", [it], Math.round(it)) } + } + + def "max and min of doubles keep NaN and order the zeros"() { + expect: + [DOUBLES, DOUBLES].combinations().every { a, b -> + agrees("max", [a, b], Math.max(a, b)) && agrees("min", [a, b], Math.min(a, b)) + } + } + + def "max and min of floats keep NaN and order the zeros"() { + expect: + [FLOATS, FLOATS].combinations().every { a, b -> + agrees("max", [a, b], Math.max(a, b)) && agrees("min", [a, b], Math.min(a, b)) + } + } + + def "signum"() { + expect: + DOUBLES.every { agrees("signum", [it], Math.signum(it)) } + FLOATS.every { agrees("signum", [it], Math.signum(it)) } + } + + def "floorDiv and floorMod for a concrete divisor"() { + expect: + [INTS, INTS.findAll { it != 0 }].combinations().every { a, b -> + agreesWithConcreteDivisor("floorDiv", a, b, Math.floorDiv(a, b)) && agreesWithConcreteDivisor("floorMod", a, b, Math.floorMod(a, b)) + } + [LONGS, LONGS.findAll { it != 0 }].combinations().every { a, b -> + agreesWithConcreteDivisor("floorDiv", a, b, Math.floorDiv(a, b)) && agreesWithConcreteDivisor("floorMod", a, b, Math.floorMod(a, b)) + } + } + + private boolean agreesWithConcreteDivisor(String name, Object a, Object b, Object expected) { + def (x, fixed) = symbolic(a) + def y = b instanceof Long ? new LongValue(context, b) : new IntValue(context, b as int) + Value result = MathInvocation.invokeStaticMethod(name, [x, y] as Value[], null, null, false) + def prover = context.newProverEnvironment() + try { + prover.addConstraint(fixed) + prover.addConstraint(context.getFormulaManager().getBooleanFormulaManager().not(bvmgr.equal(result.formula, constant(expected)))) + return prover.isUnsat() && result.concrete == expected + } finally { + prover.close() + } + } + + def "floorDiv with a symbolic divisor stays unmodelled"() { + given: + def (x, fx) = symbolic(7) + def (y, fy) = symbolic(2) + + expect: + !(MathInvocation.invokeStaticMethod("floorDiv", [x, y] as Value[], null, null, false) instanceof IntValue) + } +} diff --git a/symbolic-executor/src/test/groovy/de/uzl/its/swat/symbolic/invoke/java/lang/IntegralModelsSpec.groovy b/symbolic-executor/src/test/groovy/de/uzl/its/swat/symbolic/invoke/java/lang/IntegralModelsSpec.groovy new file mode 100644 index 00000000..da0d0aae --- /dev/null +++ b/symbolic-executor/src/test/groovy/de/uzl/its/swat/symbolic/invoke/java/lang/IntegralModelsSpec.groovy @@ -0,0 +1,290 @@ +package de.uzl.its.swat.symbolic.invoke.java.lang + +import de.uzl.its.swat.symbolic.value.PlaceHolder +import de.uzl.its.swat.symbolic.value.Value +import de.uzl.its.swat.symbolic.value.primitive.numeric.floatingpoint.DoubleValue +import de.uzl.its.swat.symbolic.value.primitive.numeric.floatingpoint.FloatValue +import de.uzl.its.swat.symbolic.value.primitive.numeric.integral.ByteValue +import de.uzl.its.swat.symbolic.value.primitive.numeric.integral.IntValue +import de.uzl.its.swat.symbolic.value.primitive.numeric.integral.LongValue +import de.uzl.its.swat.symbolic.value.primitive.numeric.integral.ShortValue +import de.uzl.its.swat.symbolic.value.reference.lang.StringValue +import org.sosy_lab.common.ShutdownManager +import org.sosy_lab.common.configuration.Configuration +import org.sosy_lab.common.log.BasicLogManager +import org.sosy_lab.java_smt.SolverContextFactory +import org.sosy_lab.java_smt.api.BitvectorFormulaManager +import org.sosy_lab.java_smt.api.BooleanFormula +import org.sosy_lab.java_smt.api.BooleanFormulaManager +import org.sosy_lab.java_smt.api.FloatingPointFormulaManager +import org.sosy_lab.java_smt.api.FormulaType +import org.sosy_lab.java_smt.api.SolverContext +import org.sosy_lab.java_smt.api.StringFormulaManager +import spock.lang.Shared +import spock.lang.Specification + +/** + * Compares the symbolic models of the Integer/Long/Short/Byte/Double/Float static methods with the + * JDK on edge values. For every input the arguments are fresh variables fixed to the input, and the + * model must agree with the JDK both in its concrete value and in every solution of its formula. + */ +class IntegralModelsSpec extends Specification { + + @Shared SolverContext ctx + @Shared BitvectorFormulaManager bv + @Shared BooleanFormulaManager bm + @Shared FloatingPointFormulaManager fp + @Shared StringFormulaManager sm + @Shared int varCount = 0 + + static final List INTS = intEdges() + static final List LONGS = longEdges() + static final List SHORTS = [0, -1, 1, Short.MIN_VALUE, Short.MAX_VALUE, 0xff, 0x100, 0x1234, -0x1234, 0x8000 - 1].collect { (short) it } + static final List BYTES = [0, -1, 1, Byte.MIN_VALUE, Byte.MAX_VALUE, 0x40, -0x40].collect { (byte) it } + static final List DISTANCES = [0, 1, -1, 5, 31, 32, 33, 63, 64, 65, Integer.MIN_VALUE, Integer.MAX_VALUE] + + static List intEdges() { + def vs = [0, -1, 1, Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE + 1, Integer.MAX_VALUE - 1, + 0x12345678, 0x87654321 as int, 0x55555555, 0xaaaaaaaa as int, 0x0f0f0f0f, -7] + (0..31).each { vs << (1 << it); vs << ((1 << it) - 1); vs << -(1 << it) } + vs.unique() + } + + static List longEdges() { + def vs = [0L, -1L, 1L, Long.MIN_VALUE, Long.MAX_VALUE, Long.MIN_VALUE + 1, Long.MAX_VALUE - 1, + 0x123456789abcdef0L, 0xfedcba9876543210L, 0x5555555555555555L, 0xaaaaaaaaaaaaaaaaL, -7L, + 0xffffffffL, 0x100000000L, Integer.MIN_VALUE as long, Integer.MAX_VALUE as long] + (0..63).each { vs << (1L << it); vs << ((1L << it) - 1); vs << -(1L << it) } + vs.unique() + } + + def setupSpec() { + ctx = SolverContextFactory.createSolverContext( + Configuration.defaultConfiguration(), + BasicLogManager.create(Configuration.defaultConfiguration()), + ShutdownManager.create().getNotifier(), + SolverContextFactory.Solvers.Z3) + bv = ctx.getFormulaManager().getBitvectorFormulaManager() + bm = ctx.getFormulaManager().getBooleanFormulaManager() + fp = ctx.getFormulaManager().getFloatingPointFormulaManager() + sm = ctx.getFormulaManager().getStringFormulaManager() + } + + def cleanupSpec() { + ctx.close() + } + + // A symbolic argument with the given value: a fresh variable, and the constraint fixing it. + List symbolic(Object v) { + String name = "x" + (varCount++) + switch (v) { + case Integer: + def x = bv.makeVariable(32, name) + return [new IntValue(ctx, v as int, x), bv.equal(x, bv.makeBitvector(32, v as long))] + case Long: + def x = bv.makeVariable(64, name) + return [new LongValue(ctx, v as long, x), bv.equal(x, bv.makeBitvector(64, v as long))] + case Short: + def x = bv.makeVariable(16, name) + return [new ShortValue(ctx, v as short, x), bv.equal(x, bv.makeBitvector(16, v as long))] + case Byte: + def x = bv.makeVariable(8, name) + return [new ByteValue(ctx, v as byte, x), bv.equal(x, bv.makeBitvector(8, v as long))] + case Double: + def x = fp.makeVariable(name, FormulaType.getDoublePrecisionFloatingPointType()) + return [new DoubleValue(ctx, v as double, x), fp.assignment(x, fp.makeNumber(v as double, FormulaType.getDoublePrecisionFloatingPointType()))] + case Float: + def x = fp.makeVariable(name, FormulaType.getSinglePrecisionFloatingPointType()) + return [new FloatValue(ctx, v as float, x), fp.assignment(x, fp.makeNumber(v as float, FormulaType.getSinglePrecisionFloatingPointType()))] + } + throw new IllegalArgumentException(v.getClass().toString()) + } + + BooleanFormula equalsExpected(Value res, Object expected) { + switch (res) { + case IntValue: return bv.equal(res.formula, bv.makeBitvector(32, expected as long)) + case LongValue: return bv.equal(res.formula, bv.makeBitvector(64, expected as long)) + case ShortValue: return bv.equal(res.formula, bv.makeBitvector(16, expected as long)) + case StringValue: return sm.equal(res.formula, sm.makeString(expected as String)) + } + throw new IllegalArgumentException(res.getClass().toString()) + } + + /** + * Runs the model on every argument tuple and returns the mismatches: a wrong concrete value, or + * a solution of the formula (with the arguments fixed) that differs from the JDK result. + */ + List mismatches(Closure model, Closure jdk, List inputs) { + def errors = [] + for (List argValues : inputs) { + def expected + try { + expected = jdk(*argValues) + } catch (ArithmeticException ignored) { + continue // the JDK throws, so no value is pushed + } + def syms = argValues.collect { symbolic(it) } + Value res = model(syms.collect { it[0] } as Value[]) + if (res == PlaceHolder.instance) { + errors << "${argValues}: no model" + continue + } + if (res.concrete != expected) { + errors << "${argValues}: concrete ${res.concrete} != ${expected}" + } + def prover = ctx.newProverEnvironment() + try { + syms.each { prover.addConstraint(it[1]) } + prover.addConstraint(bm.not(equalsExpected(res, expected))) + if (!prover.isUnsat()) errors << "${argValues}: formula admits a value other than ${expected}" + } finally { + prover.close() + } + } + errors + } + + static List unary(List vs) { vs.collect { [it] } } + + static List pairs(List vs) { [vs, vs].combinations() } + + static Closure integer(String name) { { Value[] a -> IntegerInvocation.invokeStaticMethod(name, a, null) } } + + static Closure lng(String name) { { Value[] a -> LongInvocation.invokeStaticMethod(name, a, null, null) } } + + def "Integer.#name agrees with the JDK"() { + expect: + mismatches(integer(name), jdk, inputs) == [] + + where: + name | inputs | jdk + "bitCount" | unary(INTS) | { int i -> Integer.bitCount(i) } + "numberOfLeadingZeros" | unary(INTS) | { int i -> Integer.numberOfLeadingZeros(i) } + "numberOfTrailingZeros" | unary(INTS) | { int i -> Integer.numberOfTrailingZeros(i) } + "reverse" | unary(INTS) | { int i -> Integer.reverse(i) } + "reverseBytes" | unary(INTS) | { int i -> Integer.reverseBytes(i) } + "highestOneBit" | unary(INTS) | { int i -> Integer.highestOneBit(i) } + "lowestOneBit" | unary(INTS) | { int i -> Integer.lowestOneBit(i) } + "signum" | unary(INTS) | { int i -> Integer.signum(i) } + "hashCode" | unary(INTS) | { int i -> Integer.hashCode(i) } + "toUnsignedLong" | unary(INTS) | { int i -> Integer.toUnsignedLong(i) } + "toBinaryString" | unary(INTS) | { int i -> Integer.toBinaryString(i) } + "toOctalString" | unary(INTS) | { int i -> Integer.toOctalString(i) } + "toHexString" | unary(INTS) | { int i -> Integer.toHexString(i) } + "toUnsignedString" | unary(INTS) | { int i -> Integer.toUnsignedString(i) } + "toString" | unary(INTS) | { int i -> Integer.toString(i) } + "compare" | pairs(INTS.take(16)) | { int x, int y -> Integer.compare(x, y) } + "compareUnsigned" | pairs(INTS.take(16)) | { int x, int y -> Integer.compareUnsigned(x, y) } + "divideUnsigned" | pairs(INTS.take(16)) | { int x, int y -> Integer.divideUnsigned(x, y) } + "remainderUnsigned" | pairs(INTS.take(16)) | { int x, int y -> Integer.remainderUnsigned(x, y) } + "min" | pairs(INTS.take(16)) | { int x, int y -> Integer.min(x, y) } + "max" | pairs(INTS.take(16)) | { int x, int y -> Integer.max(x, y) } + "sum" | pairs(INTS.take(16)) | { int x, int y -> Integer.sum(x, y) } + "rotateLeft" | [INTS.take(16), DISTANCES].combinations() | { int i, int d -> Integer.rotateLeft(i, d) } + "rotateRight" | [INTS.take(16), DISTANCES].combinations() | { int i, int d -> Integer.rotateRight(i, d) } + } + + def "Long.#name agrees with the JDK"() { + expect: + mismatches(lng(name), jdk, inputs) == [] + + where: + name | inputs | jdk + "bitCount" | unary(LONGS) | { long i -> Long.bitCount(i) } + "numberOfLeadingZeros" | unary(LONGS) | { long i -> Long.numberOfLeadingZeros(i) } + "numberOfTrailingZeros" | unary(LONGS) | { long i -> Long.numberOfTrailingZeros(i) } + "reverse" | unary(LONGS) | { long i -> Long.reverse(i) } + "reverseBytes" | unary(LONGS) | { long i -> Long.reverseBytes(i) } + "highestOneBit" | unary(LONGS) | { long i -> Long.highestOneBit(i) } + "lowestOneBit" | unary(LONGS) | { long i -> Long.lowestOneBit(i) } + "signum" | unary(LONGS) | { long i -> Long.signum(i) } + "hashCode" | unary(LONGS) | { long i -> Long.hashCode(i) } + "toBinaryString" | unary(LONGS) | { long i -> Long.toBinaryString(i) } + "toOctalString" | unary(LONGS) | { long i -> Long.toOctalString(i) } + "toHexString" | unary(LONGS) | { long i -> Long.toHexString(i) } + "toUnsignedString" | unary(LONGS) | { long i -> Long.toUnsignedString(i) } + "toString" | unary(LONGS) | { long i -> Long.toString(i) } + "compare" | pairs(LONGS.take(16)) | { long x, long y -> Long.compare(x, y) } + "compareUnsigned" | pairs(LONGS.take(16)) | { long x, long y -> Long.compareUnsigned(x, y) } + "divideUnsigned" | pairs(LONGS.take(16)) | { long x, long y -> Long.divideUnsigned(x, y) } + "remainderUnsigned" | pairs(LONGS.take(16)) | { long x, long y -> Long.remainderUnsigned(x, y) } + "min" | pairs(LONGS.take(16)) | { long x, long y -> Long.min(x, y) } + "max" | pairs(LONGS.take(16)) | { long x, long y -> Long.max(x, y) } + "sum" | pairs(LONGS.take(16)) | { long x, long y -> Long.sum(x, y) } + "rotateLeft" | [LONGS.take(16), DISTANCES].combinations() | { long i, int d -> Long.rotateLeft(i, d) } + "rotateRight" | [LONGS.take(16), DISTANCES].combinations() | { long i, int d -> Long.rotateRight(i, d) } + } + + def "Short.#name agrees with the JDK"() { + expect: + mismatches({ Value[] a -> ShortInvocation.invokeStaticMethod(name, a, null) }, jdk, inputs) == [] + + where: + name | inputs | jdk + "reverseBytes" | unary(SHORTS) | { short s -> Short.reverseBytes(s) } + "hashCode" | unary(SHORTS) | { short s -> Short.hashCode(s) } + "toUnsignedInt" | unary(SHORTS) | { short s -> Short.toUnsignedInt(s) } + "toUnsignedLong" | unary(SHORTS) | { short s -> Short.toUnsignedLong(s) } + "compare" | pairs(SHORTS) | { short x, short y -> Short.compare(x, y) } + "compareUnsigned" | pairs(SHORTS) | { short x, short y -> Short.compareUnsigned(x, y) } + } + + def "Byte.#name agrees with the JDK"() { + expect: + mismatches({ Value[] a -> ByteInvocation.invokeStaticMethod(name, a, null, null) }, jdk, inputs) == [] + + where: + name | inputs | jdk + "toUnsignedInt" | unary(BYTES) | { byte b -> Byte.toUnsignedInt(b) } + "toUnsignedLong" | unary(BYTES) | { byte b -> Byte.toUnsignedLong(b) } + "compare" | pairs(BYTES) | { byte x, byte y -> Byte.compare(x, y) } + } + + static final List DOUBLES = [0.0d, -0.0d, 1.0d, -1.0d, 2.0d, 0.5d, Double.MIN_VALUE, -Double.MIN_VALUE, + Double.MIN_NORMAL, Double.MAX_VALUE, -Double.MAX_VALUE, + Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NaN, 3.141592653589793d] + static final List FLOATS = [0.0f, -0.0f, 1.0f, -1.0f, 2.0f, 0.5f, Float.MIN_VALUE, -Float.MIN_VALUE, + Float.MIN_NORMAL, Float.MAX_VALUE, -Float.MAX_VALUE, + Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY, Float.NaN, 3.1415927f] + + def "Double.#name agrees with the JDK"() { + expect: + mismatches({ Value[] a -> DoubleInvocation.invokeStaticMethod(name, a, null, null) }, jdk, unary(DOUBLES)) == [] + + where: + name | jdk + "doubleToLongBits" | { double d -> Double.doubleToLongBits(d) } + // the canonical NaN is the only NaN a nondet double or the solver can produce + "doubleToRawLongBits" | { double d -> Double.doubleToRawLongBits(d) } + } + + def "Float.#name agrees with the JDK"() { + expect: + mismatches({ Value[] a -> FloatInvocation.invokeStaticMethod(name, a, null, null) }, jdk, unary(FLOATS)) == [] + + where: + name | jdk + "floatToIntBits" | { float f -> Float.floatToIntBits(f) } + "floatToRawIntBits" | { float f -> Float.floatToRawIntBits(f) } + } + + def "a NaN has a single bit pattern under floatToIntBits and doubleToLongBits"() { + given: "an unconstrained double and float" + def d = fp.makeVariable("dNaN", FormulaType.getDoublePrecisionFloatingPointType()) + def f = fp.makeVariable("fNaN", FormulaType.getSinglePrecisionFloatingPointType()) + Value dBits = DoubleInvocation.invokeStaticMethod("doubleToLongBits", [new DoubleValue(ctx, Double.NaN, d)] as Value[], null, null) + Value fBits = FloatInvocation.invokeStaticMethod("floatToIntBits", [new FloatValue(ctx, Float.NaN, f)] as Value[], null, null) + def prover = ctx.newProverEnvironment() + + when: "either is NaN with a non-canonical pattern" + prover.addConstraint(bm.or( + bm.and(fp.isNaN(d), bm.not(bv.equal(dBits.formula, bv.makeBitvector(64, 0x7ff8000000000000L)))), + bm.and(fp.isNaN(f), bm.not(bv.equal(fBits.formula, bv.makeBitvector(32, 0x7fc00000L)))))) + + then: + prover.isUnsat() + + cleanup: + prover.close() + } +} diff --git a/symbolic-executor/src/test/groovy/de/uzl/its/swat/symbolic/invoke/java/lang/NumberStringsSpec.groovy b/symbolic-executor/src/test/groovy/de/uzl/its/swat/symbolic/invoke/java/lang/NumberStringsSpec.groovy new file mode 100644 index 00000000..cf3715cc --- /dev/null +++ b/symbolic-executor/src/test/groovy/de/uzl/its/swat/symbolic/invoke/java/lang/NumberStringsSpec.groovy @@ -0,0 +1,209 @@ +package de.uzl.its.swat.symbolic.invoke.java.lang + +import de.uzl.its.swat.symbolic.value.PlaceHolder +import de.uzl.its.swat.symbolic.value.Value +import de.uzl.its.swat.symbolic.value.primitive.numeric.floatingpoint.DoubleValue +import de.uzl.its.swat.symbolic.value.primitive.numeric.floatingpoint.FloatValue +import de.uzl.its.swat.symbolic.value.primitive.numeric.integral.ByteValue +import de.uzl.its.swat.symbolic.value.primitive.numeric.integral.IntValue +import de.uzl.its.swat.symbolic.value.primitive.numeric.integral.LongValue +import de.uzl.its.swat.symbolic.value.primitive.numeric.integral.ShortValue +import de.uzl.its.swat.symbolic.value.reference.lang.StringValue +import org.sosy_lab.common.ShutdownManager +import org.sosy_lab.common.configuration.Configuration +import org.sosy_lab.common.log.BasicLogManager +import org.sosy_lab.java_smt.SolverContextFactory +import org.sosy_lab.java_smt.api.BitvectorFormulaManager +import org.sosy_lab.java_smt.api.BooleanFormula +import org.sosy_lab.java_smt.api.BooleanFormulaManager +import org.sosy_lab.java_smt.api.FloatingPointFormulaManager +import org.sosy_lab.java_smt.api.FormulaType +import org.sosy_lab.java_smt.api.SolverContext +import org.sosy_lab.java_smt.api.StringFormulaManager +import spock.lang.Shared +import spock.lang.Specification + +/** + * Compares the number-to-string models with the JDK on edge values: every radix from 2 to 36 and + * radixes outside that range (which the JDK treats as 10). The number is a fresh variable fixed to + * the input, the radix a constant (a symbolic radix is pinned to a constant by choice branches, + * which need a running executor). The model must agree with the JDK in its concrete string and in + * every solution of its formula. + */ +class NumberStringsSpec extends Specification { + + @Shared SolverContext ctx + @Shared BitvectorFormulaManager bv + @Shared BooleanFormulaManager bm + @Shared FloatingPointFormulaManager fp + @Shared StringFormulaManager sm + @Shared int varCount = 0 + + static final List INTS = [0, -1, 1, Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE + 1, + 9, 10, 35, 36, -36, 255, 256, 1295, 1296, 0x12345678, 0x87654321 as int, -1440] + static final List LONGS = [0L, -1L, 1L, Long.MIN_VALUE, Long.MAX_VALUE, Long.MIN_VALUE + 1, 35L, 36L, + 0xffffffffL, 0x100000000L, Integer.MIN_VALUE as long, 0x123456789abcdef0L, + 0xfedcba9876543210L, 47009447931686273L] + static final List RADIXES = (2..36) + [-1, 0, 1, 37, Integer.MIN_VALUE, Integer.MAX_VALUE] + static final List SHORTS = [0, -1, 1, Short.MIN_VALUE, Short.MAX_VALUE, 10, -10, 0x1234, -0x1234].collect { (short) it } + static final List BYTES = [0, -1, 1, Byte.MIN_VALUE, Byte.MAX_VALUE, 10, -10].collect { (byte) it } + + def setupSpec() { + ctx = SolverContextFactory.createSolverContext( + Configuration.defaultConfiguration(), + BasicLogManager.create(Configuration.defaultConfiguration()), + ShutdownManager.create().getNotifier(), + SolverContextFactory.Solvers.Z3) + bv = ctx.getFormulaManager().getBitvectorFormulaManager() + bm = ctx.getFormulaManager().getBooleanFormulaManager() + fp = ctx.getFormulaManager().getFloatingPointFormulaManager() + sm = ctx.getFormulaManager().getStringFormulaManager() + } + + def cleanupSpec() { + ctx.close() + } + + // A symbolic argument with the given value: a fresh variable, and the constraint fixing it. + List symbolic(Object v) { + String name = "n" + (varCount++) + switch (v) { + case Integer: + def x = bv.makeVariable(32, name) + return [new IntValue(ctx, v as int, x), bv.equal(x, bv.makeBitvector(32, v as long))] + case Long: + def x = bv.makeVariable(64, name) + return [new LongValue(ctx, v as long, x), bv.equal(x, bv.makeBitvector(64, v as long))] + case Short: + def x = bv.makeVariable(16, name) + return [new ShortValue(ctx, v as short, x), bv.equal(x, bv.makeBitvector(16, v as long))] + case Byte: + def x = bv.makeVariable(8, name) + return [new ByteValue(ctx, v as byte, x), bv.equal(x, bv.makeBitvector(8, v as long))] + } + throw new IllegalArgumentException(v.getClass().toString()) + } + + IntValue constant(int radix) { + new IntValue(ctx, radix, bv.makeBitvector(32, radix)) + } + + /** The mismatch of {@code res} with {@code expected}, or null: the concrete string and every solution. */ + String mismatch(Value res, String expected, List fixing) { + if (res == PlaceHolder.instance || !(res instanceof StringValue)) { + return "no model" + } + if (res.concrete != expected) { + return "concrete ${res.concrete} != ${expected}" + } + def prover = ctx.newProverEnvironment() + try { + fixing.each { prover.addConstraint(it) } + prover.addConstraint(bm.not(sm.equal(res.formula, sm.makeString(expected)))) + return prover.isUnsat() ? null : "formula admits a string other than ${expected}" + } finally { + prover.close() + } + } + + /** Runs {@code model(number, radix)} on all numbers and radixes and returns the mismatches. */ + List radixMismatches(List numbers, Closure model, Closure jdk) { + def errors = [] + for (def n : numbers) { + for (int r : RADIXES) { + def sym = symbolic(n) + def err = mismatch(model(sym[0], constant(r)), jdk(n, r), [sym[1]]) + if (err != null) errors << "${n}, radix ${r}: ${err}" + } + } + errors + } + + def "Integer.toString(int, int) agrees with the JDK"() { + expect: + radixMismatches(INTS, { x, r -> IntegerInvocation.invokeStaticMethod("toString", [x, r] as Value[], null) }, + { int x, int r -> Integer.toString(x, r) }) == [] + } + + def "Integer.toUnsignedString(int, int) agrees with the JDK"() { + expect: + radixMismatches(INTS, { x, r -> IntegerInvocation.invokeStaticMethod("toUnsignedString", [x, r] as Value[], null) }, + { int x, int r -> Integer.toUnsignedString(x, r) }) == [] + } + + def "Long.toString(long, int) agrees with the JDK"() { + expect: + radixMismatches(LONGS, { x, r -> LongInvocation.invokeStaticMethod("toString", [x, r] as Value[], null, null) }, + { long x, int r -> Long.toString(x, r) }) == [] + } + + def "Long.toUnsignedString(long, int) agrees with the JDK"() { + expect: + radixMismatches(LONGS, { x, r -> LongInvocation.invokeStaticMethod("toUnsignedString", [x, r] as Value[], null, null) }, + { long x, int r -> Long.toUnsignedString(x, r) }) == [] + } + + def "#type.#name agrees with the JDK"() { + expect: + values.findAll { v -> + def sym = symbolic(v) + mismatch(model(sym[0]), jdk(v), [sym[1]]) != null + } == [] + + where: + type | name | values | model | jdk + "Integer" | "toString" | INTS | { x -> IntegerInvocation.invokeStaticMethod("toString", [x] as Value[], null) } | { int x -> Integer.toString(x) } + "Integer" | "toUnsignedString" | INTS | { x -> IntegerInvocation.invokeStaticMethod("toUnsignedString", [x] as Value[], null) } | { int x -> Integer.toUnsignedString(x) } + "Integer" | "toBinaryString" | INTS | { x -> IntegerInvocation.invokeStaticMethod("toBinaryString", [x] as Value[], null) } | { int x -> Integer.toBinaryString(x) } + "Integer" | "toOctalString" | INTS | { x -> IntegerInvocation.invokeStaticMethod("toOctalString", [x] as Value[], null) } | { int x -> Integer.toOctalString(x) } + "Long" | "toString" | LONGS | { x -> LongInvocation.invokeStaticMethod("toString", [x] as Value[], null, null) } | { long x -> Long.toString(x) } + "Long" | "toUnsignedString" | LONGS | { x -> LongInvocation.invokeStaticMethod("toUnsignedString", [x] as Value[], null, null) }| { long x -> Long.toUnsignedString(x) } + "Long" | "toBinaryString" | LONGS | { x -> LongInvocation.invokeStaticMethod("toBinaryString", [x] as Value[], null, null) } | { long x -> Long.toBinaryString(x) } + "Long" | "toOctalString" | LONGS | { x -> LongInvocation.invokeStaticMethod("toOctalString", [x] as Value[], null, null) } | { long x -> Long.toOctalString(x) } + "Short" | "toString" | SHORTS | { x -> ShortInvocation.invokeStaticMethod("toString", [x] as Value[], null) } | { short x -> Short.toString(x) } + "Byte" | "toString" | BYTES | { x -> ByteInvocation.invokeStaticMethod("toString", [x] as Value[], null, null) } | { byte x -> Byte.toString(x) } + } + + def "the string of a #type in concatenation agrees with String.valueOf"() { + expect: + values.findAll { v -> + def sym = symbolic(v) + mismatch(sym[0].asStringValue(), String.valueOf(v), [sym[1]]) != null + } == [] + + where: + type | values + "int" | INTS + "long" | LONGS + "short" | SHORTS + } + + def "a concrete #type converts to the string of String.valueOf"() { + expect: + values.findAll { v -> + mismatch(value(v).asStringValue(), String.valueOf(v), []) != null + } == [] + + where: + type | values | value + "double" | [0.0d, -0.0d, 1.0d, 33.333d, 1e-109d, Double.MAX_VALUE, Double.NaN, Double.NEGATIVE_INFINITY] | { double d -> new DoubleValue(ctx, d, fp.makeNumber(d, FormulaType.getDoublePrecisionFloatingPointType())) } + "float" | [0.0f, -0.0f, 1.0f, 2.5f, 2.4012912E30f, Float.MIN_VALUE, Float.NaN, Float.POSITIVE_INFINITY] | { float f -> new FloatValue(ctx, f, fp.makeNumber(f, FormulaType.getSinglePrecisionFloatingPointType())) } + } + + def "a radix outside 2 to 36 means 10"() { + expect: + NumberStrings.normaliseRadix(radix) == expected + + where: + radix | expected + Integer.MIN_VALUE | 10 + -1 | 10 + 0 | 10 + 1 | 10 + 2 | 2 + 10 | 10 + 36 | 36 + 37 | 10 + Integer.MAX_VALUE | 10 + } +} diff --git a/symbolic-executor/src/test/groovy/de/uzl/its/swat/symbolic/invoke/java/lang/RadixChoiceAgentSpec.groovy b/symbolic-executor/src/test/groovy/de/uzl/its/swat/symbolic/invoke/java/lang/RadixChoiceAgentSpec.groovy new file mode 100644 index 00000000..6c4705c7 --- /dev/null +++ b/symbolic-executor/src/test/groovy/de/uzl/its/swat/symbolic/invoke/java/lang/RadixChoiceAgentSpec.groovy @@ -0,0 +1,85 @@ +package de.uzl.its.swat.symbolic.invoke.java.lang + +import com.fasterxml.jackson.databind.ObjectMapper +import spock.lang.Specification + +import javax.tools.ToolProvider +import java.nio.file.Files +import java.nio.file.Path +import java.util.concurrent.TimeUnit + +/** + * L2 (forked agent): a conversion with a symbolic radix branches on the radix (a binary search over + * 2 to 36 after the JDK's fallback to 10), so that the conversion is exact with a constant radix + * and the explorer reaches the other radixes by flipping the branches. Runs via the {@code + * agentTest} Gradle task (needs the agent jar from {@code copyJar}). + */ +class RadixChoiceAgentSpec extends Specification { + + static final Path AGENT_JAR = Path.of("lib", "symbolic-executor.jar").toAbsolutePath() + static final Path NATIVE_LIBS = Path.of("..", "libs", "java-library-path").toAbsolutePath() + static final Path TARGETS = Path.of("src", "test", "resources", "targets").toAbsolutePath() + + def "radix #radix is searched in #steps.size() steps, as its concrete value says"() { + given: + def classes = Files.createTempDirectory("swat-radix-spec") + compile(classes) + + when: + def trace = parseTrace(run(classes, ["-Dswat.input.I_0=-1295", "-Dswat.input.I_1=" + radix])) + def choices = trace.trace.findAll { it.choice } + + then: "binary search over [2, 37): the branch radix < mid of each step" + choices*.branched == steps + choices*.key.collect { it.substring(it.indexOf('#')) } == (0.. inputs) { + def java = Path.of(System.getProperty("java.home"), "bin", "java").toString() + def cmd = [java, + "-javaagent:${AGENT_JAR}".toString(), + "-Djava.library.path=${NATIVE_LIBS}".toString(), + "-Dconfig.path=/nonexistent/swat.cfg", + "-Dinstrumentation.transformer=SV_COMP", + "-Dsolver.mode=PRINT", + "-DexitOnError=false", + "-DlogShadowStateToConsole=false"] + inputs + + ["-cp", classes.toString(), "-ea", "Main"] + def proc = new ProcessBuilder(cmd).directory(classes.toFile()).redirectErrorStream(true).start() + def out = proc.inputStream.text + assert proc.waitFor(5, TimeUnit.MINUTES): "target did not finish:\n${out}" + return out + } + + /** The TraceDTO is the single pretty-printed JSON object whose braces start at column 0. */ + private static Map parseTrace(String out) { + int start = out.indexOf("\n{\n") + int end = out.indexOf("\n}\n", start) + assert start >= 0 && end > start: "no trace JSON in output:\n${out}" + return new ObjectMapper().readValue(out.substring(start + 1, end + 2), Map) + } +} diff --git a/symbolic-executor/src/test/groovy/de/uzl/its/swat/symbolic/invoke/java/lang/SampledMathSpec.groovy b/symbolic-executor/src/test/groovy/de/uzl/its/swat/symbolic/invoke/java/lang/SampledMathSpec.groovy new file mode 100644 index 00000000..99fac917 --- /dev/null +++ b/symbolic-executor/src/test/groovy/de/uzl/its/swat/symbolic/invoke/java/lang/SampledMathSpec.groovy @@ -0,0 +1,39 @@ +package de.uzl.its.swat.symbolic.invoke.java.lang + +import java.util.function.DoubleUnaryOperator +import spock.lang.Specification + +class SampledMathSpec extends Specification { + + private static Set bits(double[] xs) { + xs.collect { Double.doubleToRawLongBits(it) } as Set + } + + def "the samples include the edges of the double and integral ranges"() { + when: + double[] samples = SampledMath.samples({ x -> Math.sin(x) } as DoubleUnaryOperator, + { t -> Math.asin(t) } as DoubleUnaryOperator) + + then: + bits(samples).containsAll(bits([Integer.MIN_VALUE, Integer.MAX_VALUE, Long.MIN_VALUE, Long.MAX_VALUE, + Double.MAX_VALUE, -Double.MAX_VALUE, Double.MIN_VALUE, + Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NaN] as double[])) + bits(samples).size() == samples.length // each sample once + } + + def "a power that overflows only at an extreme exponent is in the table"() { + // Math.pow(-log(0.5), (int) (1 / beta)) for a tiny negative beta (sv-benchmarks + // WeibullGenerator_exceptionless): the int cast gives Integer.MIN_VALUE, the power +Infinity. + given: + double base = -Math.log(1.0 - 0.5) + DoubleUnaryOperator g = { v -> Math.pow(base, v) } as DoubleUnaryOperator + DoubleUnaryOperator inverse = { t -> Math.log(t) / Math.log(base) } as DoubleUnaryOperator + + when: + double[] samples = SampledMath.samples(g, inverse) + + then: + samples.any { g.applyAsDouble(it) == Double.POSITIVE_INFINITY } + samples.any { g.applyAsDouble(it) == 0.0d } + } +} diff --git a/symbolic-executor/src/test/groovy/de/uzl/its/swat/symbolic/loopstate/LoopHeadsTest.groovy b/symbolic-executor/src/test/groovy/de/uzl/its/swat/symbolic/loopstate/LoopHeadsTest.groovy new file mode 100644 index 00000000..92dd657a --- /dev/null +++ b/symbolic-executor/src/test/groovy/de/uzl/its/swat/symbolic/loopstate/LoopHeadsTest.groovy @@ -0,0 +1,73 @@ +package de.uzl.its.swat.symbolic.loopstate + +import org.objectweb.asm.Label +import org.objectweb.asm.Opcodes +import org.objectweb.asm.tree.MethodNode +import spock.lang.Specification + +class LoopHeadsTest extends Specification { + + /** static void m(int n, long l) { int x = 0; while (x < n) { x++; } } */ + private static MethodNode countingLoop(boolean valueOnStack) { + MethodNode mn = new MethodNode(Opcodes.ASM9, Opcodes.ACC_STATIC, "m", "(IJ)V", null, null) + Label head = new Label() + Label end = new Label() + mn.visitCode() + mn.visitInsn(Opcodes.ICONST_0) + mn.visitVarInsn(Opcodes.ISTORE, 3) + if (valueOnStack) { + mn.visitInsn(Opcodes.ICONST_0) + } + mn.visitLabel(head) + mn.visitVarInsn(Opcodes.ILOAD, 3) + mn.visitVarInsn(Opcodes.ILOAD, 0) + mn.visitJumpInsn(Opcodes.IF_ICMPGE, end) + mn.visitIincInsn(3, 1) + mn.visitJumpInsn(Opcodes.GOTO, head) + mn.visitLabel(end) + if (valueOnStack) { + mn.visitInsn(Opcodes.POP) + } + mn.visitInsn(Opcodes.RETURN) + mn.visitMaxs(3, 4) + mn.visitEnd() + return mn + } + + def "finds the target of a backward jump and the locals defined there"() { + when: + def heads = LoopHeads.analyze("Fixture", countingLoop(false)) + + then: + heads.size() == 1 + def head = heads.values().first() + head.name() == "Fixture.m(IJ)V#0" + head.slots() as List == [0, 1, 3] + head.kinds() as List == ['I' as char, 'J' as char, 'I' as char] + LoopHeads.get(head.name()) == head + } + + def "skips a head with values on the operand stack"() { + expect: + LoopHeads.analyze("Fixture2", countingLoop(true)).isEmpty() + } + + def "a method without backward jumps has no heads"() { + given: + MethodNode mn = new MethodNode(Opcodes.ASM9, Opcodes.ACC_STATIC, "f", "(I)I", null, null) + Label skip = new Label() + mn.visitCode() + mn.visitVarInsn(Opcodes.ILOAD, 0) + mn.visitJumpInsn(Opcodes.IFEQ, skip) + mn.visitInsn(Opcodes.ICONST_1) + mn.visitInsn(Opcodes.IRETURN) + mn.visitLabel(skip) + mn.visitInsn(Opcodes.ICONST_0) + mn.visitInsn(Opcodes.IRETURN) + mn.visitMaxs(1, 1) + mn.visitEnd() + + expect: + LoopHeads.analyze("Fixture3", mn).isEmpty() + } +} diff --git a/symbolic-executor/src/test/groovy/de/uzl/its/swat/symbolic/summary/LoopSummariesTest.groovy b/symbolic-executor/src/test/groovy/de/uzl/its/swat/symbolic/summary/LoopSummariesTest.groovy new file mode 100644 index 00000000..64fb3a0c --- /dev/null +++ b/symbolic-executor/src/test/groovy/de/uzl/its/swat/symbolic/summary/LoopSummariesTest.groovy @@ -0,0 +1,292 @@ +package de.uzl.its.swat.symbolic.summary + +import org.objectweb.asm.Label +import org.objectweb.asm.Opcodes +import org.objectweb.asm.tree.MethodNode +import spock.lang.Specification + +class LoopSummariesTest extends Specification { + + /** + * static void m(int n, int v) { int x = 0, s = 0; while (x < n) { } } + * x is slot 2, s is slot 3; the body ends with the back edge. + */ + private static MethodNode loop(int exitOpcode, Closure body) { + MethodNode mn = new MethodNode(Opcodes.ASM9, Opcodes.ACC_STATIC, "m", "(II)V", null, null) + Label head = new Label() + Label end = new Label() + mn.visitCode() + mn.visitInsn(Opcodes.ICONST_0) + mn.visitVarInsn(Opcodes.ISTORE, 2) + mn.visitInsn(Opcodes.ICONST_0) + mn.visitVarInsn(Opcodes.ISTORE, 3) + mn.visitLabel(head) + mn.visitVarInsn(Opcodes.ILOAD, 2) + mn.visitVarInsn(Opcodes.ILOAD, 0) + mn.visitJumpInsn(exitOpcode, end) + body(mn) + mn.visitJumpInsn(Opcodes.GOTO, head) + mn.visitLabel(end) + mn.visitInsn(Opcodes.RETURN) + mn.visitMaxs(3, 4) + mn.visitEnd() + return mn + } + + def "a counting loop with a constant step gets closed forms"() { + when: + def plans = LoopSummaries.analyze(loop(Opcodes.IF_ICMPGE) { mn -> + mn.visitIincInsn(2, 1) + mn.visitIincInsn(3, 3) + }) + + then: "the head's branch gets the closed forms, the back edge marks the loop's iterations" + plans.byJump().size() == 2 + (plans.byJump().get(1) as LoopSummaries.BackEdge).loop() == plans.byJump().get(0) + def plan = plans.byJump().get(0) as LoopSummaries.CountingLoop + plan.i() == 2 + plan.step() == 1 + plan.bound() == 0 + plan.steps() == [2: 1, 3: 3] + plan.exitOnJump() + plan.pure() + } + + def "a non-strict bound (while x <= n) is an inclusive counting loop"() { + when: + def plan = LoopSummaries.analyze(loop(Opcodes.IF_ICMPGT) { mn -> mn.visitIincInsn(2, 1) }) + .byJump().get(0) as LoopSummaries.CountingLoop + + then: + plan.inclusive() + plan.step() == 1 + } + + def "a body with a call keeps closed forms for its locals, but is impure"() { + when: + def plan = LoopSummaries.analyze(loop(Opcodes.IF_ICMPGE) { mn -> + mn.visitIincInsn(2, 1) + mn.visitVarInsn(Opcodes.ILOAD, 2) + mn.visitMethodInsn(Opcodes.INVOKESTATIC, "X", "f", "(I)V", false) + mn.visitIincInsn(3, 2) + }).byJump().get(0) as LoopSummaries.CountingLoop + + then: + plan.steps() == [2: 1, 3: 2] + !plan.pure() + } + + def "a local set from a call's result has no closed form"() { + expect: + LoopSummaries.analyze(loop(Opcodes.IF_ICMPGE) { mn -> + mn.visitIincInsn(2, 1) + mn.visitMethodInsn(Opcodes.INVOKESTATIC, "X", "g", "()I", false) + mn.visitVarInsn(Opcodes.ISTORE, 3) + }).byJump().isEmpty() + } + + def "x = x + c and x = c in the body are understood"() { + when: + def plan = LoopSummaries.analyze(loop(Opcodes.IF_ICMPGE) { mn -> + mn.visitVarInsn(Opcodes.ILOAD, 3) + mn.visitIntInsn(Opcodes.BIPUSH, 7) + mn.visitInsn(Opcodes.IADD) + mn.visitVarInsn(Opcodes.ISTORE, 3) + mn.visitInsn(Opcodes.ICONST_1) + mn.visitVarInsn(Opcodes.ISTORE, 1) + mn.visitIincInsn(2, 1) + }).byJump().get(0) as LoopSummaries.CountingLoop + + then: + plan.steps() == [2: 1, 3: 7] + plan.constants() == [1: 1] + } + + def "no closed form for #what"() { + expect: + LoopSummaries.analyze(loop(exit, body)).byJump().isEmpty() + + where: + what | exit | body + "a step of two" | Opcodes.IF_ICMPGE | { mn -> mn.visitIincInsn(2, 2) } + "a body that changes bound" | Opcodes.IF_ICMPGE | { mn -> mn.visitIincInsn(2, 1); mn.visitIincInsn(0, 1) } + "a body with a division" | Opcodes.IF_ICMPGE | { mn -> mn.visitIincInsn(2, 1); mn.visitVarInsn(Opcodes.ILOAD, 3); mn.visitInsn(Opcodes.ICONST_2); mn.visitInsn(Opcodes.IDIV); mn.visitVarInsn(Opcodes.ISTORE, 3) } + "s = s + x (not a constant)" | Opcodes.IF_ICMPGE | { mn -> mn.visitIincInsn(2, 1); mn.visitVarInsn(Opcodes.ILOAD, 3); mn.visitVarInsn(Opcodes.ILOAD, 2); mn.visitInsn(Opcodes.IADD); mn.visitVarInsn(Opcodes.ISTORE, 3) } + } + + /** + * static int f(int i, int v) { int c = 0; ; }, c in slot 2; by + * default the use is {@code return c == 0 ? 0 : 1} (only a comparison). + */ + private static MethodNode diamond(Closure sides, Closure use = null) { + MethodNode mn = new MethodNode(Opcodes.ASM9, Opcodes.ACC_STATIC, "f", "(II)I", null, null) + mn.visitCode() + mn.visitInsn(Opcodes.ICONST_0) + mn.visitVarInsn(Opcodes.ISTORE, 2) + mn.visitVarInsn(Opcodes.ILOAD, 0) + mn.visitVarInsn(Opcodes.ILOAD, 1) + sides(mn) + if (use == null) { + Label nonZero = new Label() + mn.visitVarInsn(Opcodes.ILOAD, 2) + mn.visitJumpInsn(Opcodes.IFNE, nonZero) + mn.visitInsn(Opcodes.ICONST_0) + mn.visitInsn(Opcodes.IRETURN) + mn.visitLabel(nonZero) + mn.visitInsn(Opcodes.ICONST_1) + mn.visitInsn(Opcodes.IRETURN) + } else { + use(mn) + } + mn.visitMaxs(4, 4) + mn.visitEnd() + return mn + } + + def "no diamond if the merged value reaches #what"() { + given: + Label join = new Label() + + expect: + LoopSummaries.analyze(diamond({ mn -> + mn.visitJumpInsn(Opcodes.IF_ICMPGE, join) + mn.visitIincInsn(2, 1) + mn.visitLabel(join) + }, use)).byJump().isEmpty() + + where: + what | use + "a return" | { mn -> mn.visitVarInsn(Opcodes.ILOAD, 2); mn.visitInsn(Opcodes.IRETURN) } + "a call" | { mn -> mn.visitVarInsn(Opcodes.ILOAD, 2); mn.visitMethodInsn(Opcodes.INVOKESTATIC, "X", "g", "(I)V", false); mn.visitInsn(Opcodes.ICONST_0); mn.visitInsn(Opcodes.IRETURN) } + "a field store" | { mn -> mn.visitVarInsn(Opcodes.ILOAD, 2); mn.visitFieldInsn(Opcodes.PUTSTATIC, "X", "f", "I"); mn.visitInsn(Opcodes.ICONST_0); mn.visitInsn(Opcodes.IRETURN) } + "a call through arithmetic" | { mn -> mn.visitVarInsn(Opcodes.ILOAD, 2); mn.visitInsn(Opcodes.ICONST_1); mn.visitInsn(Opcodes.IADD); mn.visitVarInsn(Opcodes.ISTORE, 0); mn.visitVarInsn(Opcodes.ILOAD, 0); mn.visitMethodInsn(Opcodes.INVOKESTATIC, "X", "g", "(I)V", false); mn.visitInsn(Opcodes.ICONST_0); mn.visitInsn(Opcodes.IRETURN) } + } + + def "if (i < v) c++ is a diamond"() { + given: + Label join = new Label() + + when: + def plans = LoopSummaries.analyze(diamond { mn -> + mn.visitJumpInsn(Opcodes.IF_ICMPGE, join) + mn.visitIincInsn(2, 1) + mn.visitLabel(join) + }) + + then: + def d = plans.byJump().get(0) as LoopSummaries.Diamond + d.fallThrough() == [new LoopSummaries.Op(Opcodes.IINC, 2, 1)] + d.jump() == [] + d.stackDelta() == 0 + plans.joins().size() == 1 + plans.joins().values().first() == d.join() + } + + def "a conditional expression is a diamond that leaves a value"() { + given: + Label otherwise = new Label() + Label join = new Label() + + when: + def plans = LoopSummaries.analyze(diamond { mn -> + mn.visitJumpInsn(Opcodes.IF_ICMPGE, otherwise) + mn.visitVarInsn(Opcodes.ILOAD, 2) + mn.visitInsn(Opcodes.ICONST_2) + mn.visitInsn(Opcodes.IADD) + mn.visitJumpInsn(Opcodes.GOTO, join) + mn.visitLabel(otherwise) + mn.visitVarInsn(Opcodes.ILOAD, 2) + mn.visitLabel(join) + mn.visitVarInsn(Opcodes.ISTORE, 2) + }) + + then: + def d = plans.byJump().get(0) as LoopSummaries.Diamond + d.stackDelta() == 1 + d.jump() == [new LoopSummaries.Op(Opcodes.ILOAD, 2, 0)] + } + + def "a side with a call is no diamond"() { + given: + Label join = new Label() + + expect: + LoopSummaries.analyze(diamond { mn -> + mn.visitJumpInsn(Opcodes.IF_ICMPGE, join) + mn.visitMethodInsn(Opcodes.INVOKESTATIC, "X", "f", "()V", false) + mn.visitIincInsn(2, 1) + mn.visitLabel(join) + }).byJump().isEmpty() + } + + def "a side that something else jumps into is no diamond"() { + given: + Label join = new Label() + Label middle = new Label() + + expect: + LoopSummaries.analyze(diamond { mn -> + mn.visitJumpInsn(Opcodes.IF_ICMPGE, join) + mn.visitIincInsn(2, 1) + mn.visitLabel(middle) + mn.visitIincInsn(2, 1) + mn.visitLabel(join) + mn.visitVarInsn(Opcodes.ILOAD, 0) + mn.visitJumpInsn(Opcodes.IFNE, middle) + }).byJump().keySet() == [] as Set + } + + /** + * if (i < v) { if (i % 2 == 0) c++; else d++; }, as javac compiles it: the inner if-else + * meets at the outer if's end. c is slot 2, d slot 3. + */ + def "an if inside a side is a nested diamond, and division by a constant is allowed"() { + given: + Label end = new Label() + Label otherwise = new Label() + + when: + def plans = LoopSummaries.analyze(diamond { mn -> + mn.visitJumpInsn(Opcodes.IF_ICMPGE, end) + mn.visitVarInsn(Opcodes.ILOAD, 0) + mn.visitInsn(Opcodes.ICONST_2) + mn.visitInsn(Opcodes.IREM) + mn.visitJumpInsn(Opcodes.IFNE, otherwise) + mn.visitIincInsn(2, 1) + mn.visitJumpInsn(Opcodes.GOTO, end) + mn.visitLabel(otherwise) + mn.visitIincInsn(3, 1) + mn.visitLabel(end) + }) + + then: "the outer if's fall-through side ends in the nested diamond" + def outer = plans.byJump().get(0) as LoopSummaries.Diamond + outer.jump() == [] + outer.fallThrough().size() == 4 + outer.fallThrough()[2].opcode() == Opcodes.IREM + def nested = outer.fallThrough()[3].nested() + nested != null + nested.fallThrough() == [new LoopSummaries.Op(Opcodes.IINC, 2, 1)] + nested.jump() == [new LoopSummaries.Op(Opcodes.IINC, 3, 1)] + + and: "the inner if is a diamond of its own, sharing the outer one's join" + def inner = plans.byJump().get(1) as LoopSummaries.Diamond + inner.join() == outer.join() + plans.joins().size() == 1 + } + + def "division by a variable is no diamond"() { + given: + Label join = new Label() + + expect: + LoopSummaries.analyze(diamond { mn -> + mn.visitJumpInsn(Opcodes.IF_ICMPGE, join) + mn.visitVarInsn(Opcodes.ILOAD, 2) + mn.visitVarInsn(Opcodes.ILOAD, 1) + mn.visitInsn(Opcodes.IDIV) + mn.visitVarInsn(Opcodes.ISTORE, 2) + mn.visitLabel(join) + }).byJump().isEmpty() + } +} diff --git a/symbolic-executor/src/test/groovy/de/uzl/its/swat/symbolic/trace/SymbolRegistrySpec.groovy b/symbolic-executor/src/test/groovy/de/uzl/its/swat/symbolic/trace/SymbolRegistrySpec.groovy new file mode 100644 index 00000000..b7499c84 --- /dev/null +++ b/symbolic-executor/src/test/groovy/de/uzl/its/swat/symbolic/trace/SymbolRegistrySpec.groovy @@ -0,0 +1,119 @@ +package de.uzl.its.swat.symbolic.trace + +import de.uzl.its.swat.symbolic.invoke.java.lang.SampledMath +import de.uzl.its.swat.symbolic.value.Value +import de.uzl.its.swat.symbolic.value.primitive.numeric.floatingpoint.DoubleValue +import de.uzl.its.swat.symbolic.value.primitive.numeric.integral.IntValue +import de.uzl.its.swat.symbolic.value.reference.array.IntArrayValue +import de.uzl.its.swat.symbolic.value.reference.lang.StringValue +import de.uzl.its.swat.thread.ThreadHandler +import org.objectweb.asm.Type +import org.sosy_lab.java_smt.api.FormulaManager +import org.sosy_lab.java_smt.api.FormulaType +import org.sosy_lab.java_smt.api.SolverContext +import spock.lang.Specification + +import static java.lang.Thread.currentThread + +class SymbolRegistrySpec extends Specification { + + SolverContext context + FormulaManager fmgr + + def setup() { + if (ThreadHandler.hasThreadContext(currentThread().id)) { + ThreadHandler.removeThreadContext(currentThread().id) + } + ThreadHandler.addThreadContext(currentThread().id, "Test-Thread", -2) + context = ThreadHandler.getSolverContext(currentThread().id) + fmgr = context.formulaManager + } + + def cleanup() { + context.close() + ThreadHandler.removeThreadContext(currentThread().id) + } + + private boolean tracePrecisionLoss() { + ThreadHandler.getSymbolicTraceHandler(currentThread().id).symbolicTrace.symbolicPrecisionLoss + } + + def "an input and an allocated array's base symbol are precise"() { + given: + IntValue i = new IntValue(context, 1) + String name = i.MAKE_SYMBOLIC(7L) + ThreadHandler.getSymbolicTraceHandler(currentThread().id).addInput(name, i) + IntArrayValue arr = new IntArrayValue(context, new IntValue(context, 3), 1) + + when: + def read = arr.getElement(i) + def f = fmgr.bitvectorFormulaManager.equal(read.formula, fmgr.bitvectorFormulaManager.makeBitvector(32, 0)) + def base = fmgr.extractVariables(arr.formula).keySet() + + then: + SymbolRegistry.kindOf(name) == SymbolRegistry.Kind.INPUT + base.size() == 1 + SymbolRegistry.kindOf(base[0]) == SymbolRegistry.Kind.ARRAY_BASE + // outside the input namespace, so an input array [I_ cannot share it + !(base[0] ==~ /\[I_\d+/) + !DTOBuilder.hasImpreciseSymbol(fmgr, f) + } + + def "an exact String model function is precise"() { + given: + StringValue s = new StringValue(context, " a ", 1) + s.MAKE_SYMBOLIC(3L) + + when: + StringValue t = s.invokeMethod("trim", [] as Type[], [] as IntValue[]) as StringValue + def f = fmgr.stringFormulaManager.equal(t.formula, fmgr.stringFormulaManager.makeString("a")) + + then: + fmgr.extractVariablesAndUFs(f).keySet().containsAll(["trimStart", "trimEnd"]) + !DTOBuilder.hasImpreciseSymbol(fmgr, f) + !tracePrecisionLoss() + } + + def "an approximate model function is a precision loss once a branch mentions it"() { + given: + DoubleValue d = new DoubleValue(context, 0.5d) + d.MAKE_SYMBOLIC(4L) + + when: + def sth = ThreadHandler.getSymbolicTraceHandler(currentThread().id) + def sin = SampledMath.unary("sin", false, [d] as Value[], sth) + def name = fmgr.extractVariablesAndUFs(sin.formula).keySet().find { it.startsWith("math_sin_") } + def f = fmgr.floatingPointFormulaManager.lessThan(sin.formula, fmgr.floatingPointFormulaManager.makeNumber(0.1d, DoubleValue.precision)) + + then: + name != null + SymbolRegistry.kindOf(name) == SymbolRegistry.Kind.APPROXIMATE_MODEL_FUNCTION + DTOBuilder.hasImpreciseSymbol(fmgr, f) + } + + def "an undeclared function is a precision loss"() { + given: + def uf = fmgr.UFManager.declareUF("someUndeclaredFunction", FormulaType.IntegerType, FormulaType.IntegerType) + IntValue i = new IntValue(context, 1) + i.MAKE_SYMBOLIC(5L) + + when: + def call = fmgr.UFManager.callUF(uf, i.asIntegerFormula()) + def f = fmgr.integerFormulaManager.equal(call, fmgr.integerFormulaManager.makeNumber(0)) + + then: + DTOBuilder.hasImpreciseSymbol(fmgr, f) + } + + def "an input or approximate declaration is not weakened by a later one"() { + when: + SymbolRegistry.declare("Spec_input_0", SymbolRegistry.Kind.INPUT) + SymbolRegistry.declare("Spec_input_0", SymbolRegistry.Kind.VALUE) + SymbolRegistry.declare("specApprox", SymbolRegistry.Kind.APPROXIMATE_MODEL_FUNCTION) + SymbolRegistry.declare("specApprox", SymbolRegistry.Kind.MODEL_FUNCTION) + + then: + SymbolRegistry.kindOf("Spec_input_0") == SymbolRegistry.Kind.INPUT + !SymbolRegistry.isPrecise("specApprox") + } +} diff --git a/symbolic-executor/src/test/groovy/de/uzl/its/swat/symbolic/value/primitive/numeric/floatingpoint/JavaRemainderSpec.groovy b/symbolic-executor/src/test/groovy/de/uzl/its/swat/symbolic/value/primitive/numeric/floatingpoint/JavaRemainderSpec.groovy new file mode 100644 index 00000000..ae900766 --- /dev/null +++ b/symbolic-executor/src/test/groovy/de/uzl/its/swat/symbolic/value/primitive/numeric/floatingpoint/JavaRemainderSpec.groovy @@ -0,0 +1,107 @@ +package de.uzl.its.swat.symbolic.value.primitive.numeric.floatingpoint + +import org.sosy_lab.common.ShutdownManager +import org.sosy_lab.common.configuration.Configuration +import org.sosy_lab.common.log.BasicLogManager +import org.sosy_lab.java_smt.SolverContextFactory +import org.sosy_lab.java_smt.api.BooleanFormulaManager +import org.sosy_lab.java_smt.api.FloatingPointFormula +import org.sosy_lab.java_smt.api.FloatingPointFormulaManager +import org.sosy_lab.java_smt.api.FormulaType +import org.sosy_lab.java_smt.api.ProverEnvironment +import org.sosy_lab.java_smt.api.SolverContext +import spock.lang.Shared +import spock.lang.Specification + +/** + * The model of Java's % (JavaRemainder.exactPieces) against Java itself: where the quotient is at + * most 1 in magnitude it must equal Java's result bit for bit (so -0.0 is not 0.0), elsewhere it + * must be the fallback it is given. + */ +class JavaRemainderSpec extends Specification { + + @Shared SolverContext ctx + @Shared FloatingPointFormulaManager fp + @Shared BooleanFormulaManager bm + + static final List VALUES = [0.0d, -0.0d, 1.0d, -1.0d, 0.5d, -0.5d, 2.5d, -2.5d, 3.0d, -3.0d, 7.0d, -7.0d, + 360.0d, -360.0d, 359.9d, -359.9d, 719.9d, -719.9d, 720.0d, -1.0e-3d, + 1.0e300d, -1.0e300d, 1.0e-300d, 9.0e307d, 1.0e308d, + Double.MIN_VALUE, -Double.MIN_VALUE, Double.MAX_VALUE, -Double.MAX_VALUE, + Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NaN, 5.5d, -5.5d, 1.5d] + + def setupSpec() { + ctx = SolverContextFactory.createSolverContext(Configuration.defaultConfiguration(), + BasicLogManager.create(Configuration.defaultConfiguration()), ShutdownManager.create().getNotifier(), + SolverContextFactory.Solvers.Z3) + fp = ctx.getFormulaManager().getFloatingPointFormulaManager() + bm = ctx.getFormulaManager().getBooleanFormulaManager() + } + + def cleanupSpec() { + ctx.close() + } + + /** Whether f equals g (bit for bit) in every model. */ + boolean always(FloatingPointFormula f, FloatingPointFormula g) { + try (ProverEnvironment prover = ctx.newProverEnvironment()) { + prover.addConstraint(bm.not(fp.assignment(f, g))) + return prover.isUnsat() + } + } + + /** null if right, else a description of what is wrong. */ + String check(double x, double y, FormulaType.FloatingPointType t, double expected) { + def other = fp.makeVariable("otherwise", t) + def number = { double v -> Double.isNaN(v) ? fp.makeNaN(t) : fp.makeNumber(v, t) } + def model = JavaRemainder.exactPieces(ctx, number(x), number(y), other) + double ax = Math.abs(x), ay = Math.abs(y) + boolean exact = !Double.isNaN(x) && !Double.isNaN(y) && (ax < ay || ax < 2 * ay) + if (exact) { + return always(model, number(expected)) ? null : "$x % $y should be $expected" + } + return always(model, other) ? null : "$x % $y should fall back" + } + + def "double: Java's remainder where the quotient is at most 1, the fallback elsewhere"() { + expect: + def t = FormulaType.getDoublePrecisionFloatingPointType() + def wrong = [] + for (double x : VALUES) for (double y : VALUES) { + def w = check(x, y, t, x % y) + if (w != null) wrong << w + } + wrong == [] + } + + def "float: Java's remainder where the quotient is at most 1, the fallback elsewhere"() { + expect: + def t = FormulaType.getSinglePrecisionFloatingPointType() + def floats = (VALUES.collect { it as float } + [Float.MIN_VALUE, Float.MAX_VALUE, -Float.MAX_VALUE]).unique() + def wrong = [] + for (float x : floats) for (float y : floats) { + def w = check(x as double, y as double, t, (x % y) as double) + if (w != null) wrong << w + } + wrong == [] + } + + def "the remainder is symbolic: a solver finds an angle whose remainder is below -180"() { + // sv-benchmarks PieSegment: cAngle = angle % 360 must be able to be negative. + given: + def t = FormulaType.getDoublePrecisionFloatingPointType() + def angle = fp.makeVariable("angle", t) + def r = JavaRemainder.exactPieces(ctx, angle, fp.makeNumber(360.0d, t), fp.makeNumber(17.0d, t)) + + when: + double found + try (ProverEnvironment prover = ctx.newProverEnvironment(SolverContext.ProverOptions.GENERATE_MODELS)) { + prover.addConstraint(fp.lessThan(r, fp.makeNumber(-180.0d, t))) + assert !prover.isUnsat() + found = prover.getModel().evaluate(angle).doubleValue() + } + + then: + found % 360.0d < -180.0d + } +} diff --git a/symbolic-executor/src/test/groovy/de/uzl/its/swat/witness/SvcompExitAgentSpec.groovy b/symbolic-executor/src/test/groovy/de/uzl/its/swat/witness/SvcompExitAgentSpec.groovy new file mode 100644 index 00000000..2e34c16a --- /dev/null +++ b/symbolic-executor/src/test/groovy/de/uzl/its/swat/witness/SvcompExitAgentSpec.groovy @@ -0,0 +1,116 @@ +package de.uzl.its.swat.witness + +import com.fasterxml.jackson.databind.ObjectMapper +import spock.lang.Shared +import spock.lang.Specification + +import javax.tools.ToolProvider +import java.nio.file.Files +import java.nio.file.Path +import java.util.concurrent.TimeUnit + +/** + * L2 (forked agent): a target that ends the JVM (System.exit, Runtime.halt) still sends its trace, + * and an assertion that fails before the exit is reported where its AssertionError is thrown, even + * when a {@code finally} exits before the error escapes main. Runs via the {@code agentTest} Gradle + * task (needs the agent jar from {@code copyJar}). + */ +class SvcompExitAgentSpec extends Specification { + + static final Path AGENT_JAR = Path.of("lib", "symbolic-executor.jar").toAbsolutePath() + static final Path NATIVE_LIBS = Path.of("..", "libs", "java-library-path").toAbsolutePath() + static final Path TARGETS = Path.of("src", "test", "resources", "targets").toAbsolutePath() + + @Shared Path classes + + def setupSpec() { + assert Files.isRegularFile(AGENT_JAR): "agent jar missing, run copyJar first: ${AGENT_JAR}" + classes = Files.createTempDirectory("swat-exit-agent-spec") + compile(classes) + } + + def cleanupSpec() { + classes?.toFile()?.deleteDir() + } + + def "an assertion that fails before #exitCall in a finally is reported with its location"() { + when: + def (int rc, String out) = run(mainClass, 7) + Map trace = parseTrace(out) + + then: + rc == 3 + out.contains("java.lang.AssertionError") + trace.violation != null + trace.violation.throwable == "java.lang.AssertionError" + trace.violation.frames*.className == [mainClass] + trace.violation.frames*.line == [8] + + where: + mainClass | exitCall + "ExitInFinally" | "System.exit" + "HaltInFinally" | "Runtime.halt" + } + + def "a run of #mainClass whose assertion holds reports no violation"() { + when: + def (int rc, String out) = run(mainClass, 3) + Map trace = parseTrace(out) + + then: + rc == 0 + !out.contains("java.lang.AssertionError") + trace.violation == null + + where: + mainClass << ["ExitInFinally", "HaltInFinally"] + } + + def "System.exit before a failing assertion sends the trace and reports no violation"() { + when: + def (int rc, String out) = run("CleanExit", 7) + Map trace = parseTrace(out) + + then: + rc == 0 + !out.contains("java.lang.AssertionError") + trace.violation == null + trace.nondetReturns*.value == ["7"] + } + + private static void compile(Path outDir) { + def sources = [TARGETS.resolve("svcomp/org"), TARGETS.resolve("svcomp-exit")].collectMany { dir -> + Files.walk(dir).filter { it.toString().endsWith(".java") }.collect { it.toString() } + } + def args = ["-source", "1.8", "-target", "1.8", "-g", "-d", outDir.toString()] + sources + def err = new ByteArrayOutputStream() + int rc = ToolProvider.getSystemJavaCompiler().run(null, null, err, args as String[]) + assert rc == 0: "javac failed: ${err}" + } + + private List run(String mainClass, int input) { + def java = Path.of(System.getProperty("java.home"), "bin", "java").toString() + def cmd = [java, + "-javaagent:${AGENT_JAR}".toString(), + "-Djava.library.path=${NATIVE_LIBS}".toString(), + "-Dconfig.path=/nonexistent/swat.cfg", + "-Dinstrumentation.transformer=SV_COMP", + "-Dsolver.mode=PRINT", + "-DexitOnError=false", + "-DlogShadowStateToConsole=false", + "-Dswat.input.I_0=${input}".toString(), + "-cp", classes.toString(), "-ea", mainClass] + def proc = new ProcessBuilder(cmd).directory(classes.toFile()).redirectErrorStream(true).start() + def out = proc.inputStream.text + assert proc.waitFor(5, TimeUnit.MINUTES): "target did not finish:\n${out}" + return [proc.exitValue(), out] + } + + /** The TraceDTO is the single pretty-printed JSON object whose braces start at column 0. */ + private static Map parseTrace(String out) { + int start = out.indexOf("\n{\n") + int end = out.indexOf("\n}\n", start) + assert start >= 0 && end > start: "no trace JSON in output:\n${out}" + return new ObjectMapper().readValue(out.substring(start + 1, end + 2), Map) + } +} diff --git a/symbolic-executor/src/test/groovy/de/uzl/its/swat/witness/SvcompWitnessAgentSpec.groovy b/symbolic-executor/src/test/groovy/de/uzl/its/swat/witness/SvcompWitnessAgentSpec.groovy new file mode 100644 index 00000000..2d47c4a4 --- /dev/null +++ b/symbolic-executor/src/test/groovy/de/uzl/its/swat/witness/SvcompWitnessAgentSpec.groovy @@ -0,0 +1,109 @@ +package de.uzl.its.swat.witness + +import com.fasterxml.jackson.databind.ObjectMapper +import spock.lang.Shared +import spock.lang.Specification + +import javax.tools.ToolProvider +import java.nio.file.Files +import java.nio.file.Path +import java.util.concurrent.TimeUnit + +/** + * L2 (forked agent): the SV-COMP instrumentation records the witness data in the trace. A tiny + * SV-COMP style target is compiled and run with the agent in {@code solver.mode=PRINT}; the + * {@code TraceDTO} JSON printed to stdout must carry the nondet returns with their call locations + * and the violation with its frames. Runs via the {@code agentTest} Gradle task (needs the agent + * jar from {@code copyJar}). + */ +class SvcompWitnessAgentSpec extends Specification { + + static final Path AGENT_JAR = Path.of("lib", "symbolic-executor.jar").toAbsolutePath() + static final Path NATIVE_LIBS = Path.of("..", "libs", "java-library-path").toAbsolutePath() + static final Path TARGET_SOURCES = Path.of("src", "test", "resources", "targets", "svcomp").toAbsolutePath() + + @Shared Path classes + @Shared Map trace + @Shared String output + + def setupSpec() { + assert Files.isRegularFile(AGENT_JAR): "agent jar missing, run copyJar first: ${AGENT_JAR}" + classes = Files.createTempDirectory("swat-witness-agent-spec") + compile(classes) + output = run(classes, ["-Dswat.input.I_0=3", "-Dswat.input.I_1=4"]) + trace = parseTrace(output) + } + + def cleanupSpec() { + classes?.toFile()?.deleteDir() + } + + def "the trace carries every nondet return with its call location"() { + expect: + trace.nondetReturns.size() == 4 + trace.nondetReturns*.function == ["nondetInt", "nondetInt", "nondetDouble", "nondetString"] + trace.nondetReturns*.type == ["int", "int", "double", "String"] + trace.nondetReturns*.value == ["3", "4", "0.0", "fixed"] + trace.nondetReturns*.className == ["Main"] * 4 + trace.nondetReturns*.method == ["main([Ljava/lang/String;)V"] * 4 + trace.nondetReturns*.sourceFile == ["Main.java"] * 4 + trace.nondetReturns*.line == [14, 14, 15, 16] + trace.nondetReturns*.callIndex == [0, 1, 0, 0] + trace.nondetReturns[2].bits == "0x0" + trace.nondetReturns[0].bits == null + } + + def "the trace carries the violation with the frames of the task innermost first"() { + expect: + trace.violation != null + trace.violation.throwable == "java.lang.AssertionError" + trace.violation.frames*.className == ["Main", "Main"] + trace.violation.frames*.sourceFile == ["Main.java", "Main.java"] + trace.violation.frames*.line == [9, 18] + } + + def "the inputs of the trace still correspond to the nondet returns"() { + expect: + trace.inputs.size() == 4 + trace.inputs*.name.findAll { it.startsWith("I_") }.size() == 2 + } + + def "nothing is printed for the witness besides the trace"() { + expect: + !output.contains("[WITNESS]") + !output.contains("[WITNESS_TARGET]") + } + + private static void compile(Path outDir) { + def sources = Files.walk(TARGET_SOURCES).filter { it.toString().endsWith(".java") }.collect { it.toString() } + def args = ["-source", "1.8", "-target", "1.8", "-g", "-d", outDir.toString()] + sources + def err = new ByteArrayOutputStream() + int rc = ToolProvider.getSystemJavaCompiler().run(null, null, err, args as String[]) + assert rc == 0: "javac failed: ${err}" + } + + private static String run(Path classes, List inputs) { + def java = Path.of(System.getProperty("java.home"), "bin", "java").toString() + def cmd = [java, + "-javaagent:${AGENT_JAR}".toString(), + "-Djava.library.path=${NATIVE_LIBS}".toString(), + "-Dconfig.path=/nonexistent/swat.cfg", + "-Dinstrumentation.transformer=SV_COMP", + "-Dsolver.mode=PRINT", + "-DexitOnError=false", + "-DlogShadowStateToConsole=false"] + inputs + + ["-cp", classes.toString(), "-ea", "Main"] + def proc = new ProcessBuilder(cmd).directory(classes.toFile()).redirectErrorStream(true).start() + def out = proc.inputStream.text + assert proc.waitFor(5, TimeUnit.MINUTES): "target did not finish:\n${out}" + return out + } + + /** The TraceDTO is the single pretty-printed JSON object whose braces start at column 0. */ + private static Map parseTrace(String out) { + int start = out.indexOf("\n{\n") + int end = out.indexOf("\n}\n", start) + assert start >= 0 && end > start: "no trace JSON in output:\n${out}" + return new ObjectMapper().readValue(out.substring(start + 1, end + 2), Map) + } +} diff --git a/symbolic-executor/src/test/groovy/de/uzl/its/swat/witness/WitnessSpec.groovy b/symbolic-executor/src/test/groovy/de/uzl/its/swat/witness/WitnessSpec.groovy new file mode 100644 index 00000000..0291c399 --- /dev/null +++ b/symbolic-executor/src/test/groovy/de/uzl/its/swat/witness/WitnessSpec.groovy @@ -0,0 +1,192 @@ +package de.uzl.its.swat.witness + +import de.uzl.its.swat.symbolic.trace.dto.NondetReturnDTO +import de.uzl.its.swat.symbolic.trace.dto.ViolationDTO +import spock.lang.Specification + +/** + * L0: the witness recorder renders nondet return values and the violation's stack frames in the + * form the witness library expects (see the witnesscreator API contract). + */ +class WitnessSpec extends Specification { + + def setup() { + Witness.reset() + } + + def cleanup() { + Witness.reset() + } + + def "a nondet return value is recorded with its call location"() { + when: + Witness.recordValue(42, "nondetInt", "pkg/Main", "main([Ljava/lang/String;)V", "Main.java", 12, 1) + List recorded = Witness.getNondetReturns() + + then: + recorded.size() == 1 + with(recorded[0]) { + function == "nondetInt" + type == "int" + value == "42" + bits == null + className == "pkg.Main" + method == "main([Ljava/lang/String;)V" + sourceFile == "Main.java" + line == 12 + callIndex == 1 + } + } + + def "values are rendered in their witness string form (#type #description)"() { + when: + Witness.recordValue(*args) + NondetReturnDTO recorded = Witness.getNondetReturns()[0] + + then: + recorded.type == type + recorded.value == expectedValue + recorded.bits == expectedBits + + where: + description | args | type | expectedValue | expectedBits + "true" | [true, "nondetBoolean", "Main", "main()V", "Main.java", 1, 0] | "boolean" | "true" | null + "negative" | [(byte) -5, "nondetByte", "Main", "main()V", "Main.java", 1, 0] | "byte" | "-5" | null + "min" | [Short.MIN_VALUE, "nondetShort", "Main", "main()V", "Main.java", 1, 0] | "short" | "-32768" | null + "code point" | [(char) 'A', "nondetChar", "Main", "main()V", "Main.java", 1, 0] | "char" | "65" | null + "high code" | [(char) 0xFFFF, "nondetChar", "Main", "main()V", "Main.java", 1, 0] | "char" | "65535" | null + "min" | [Integer.MIN_VALUE, "nondetInt", "Main", "main()V", "Main.java", 1, 0] | "int" | "-2147483648" | null + "max" | [Long.MAX_VALUE, "nondetLong", "Main", "main()V", "Main.java", 1, 0] | "long" | "9223372036854775807" | null + "plain" | [0.1f, "nondetFloat", "Main", "main()V", "Main.java", 1, 0] | "float" | "0.1" | "0x3dcccccd" + "small" | [-2.022784E-6f, "nondetFloat", "Main", "main()V", "Main.java", 1, 0] | "float" | "-2.022784E-6" | "0x" + Integer.toHexString(Float.floatToRawIntBits(-2.022784E-6f)) + "negative zero" | [-0.0f, "nondetFloat", "Main", "main()V", "Main.java", 1, 0] | "float" | "-0.0" | "0x80000000" + "NaN" | [Float.NaN, "nondetFloat", "Main", "main()V", "Main.java", 1, 0] | "float" | "NaN" | "0x7fc00000" + "infinity" | [Float.NEGATIVE_INFINITY, "nondetFloat", "Main", "main()V", "Main.java", 1, 0] | "float" | "-Infinity" | "0xff800000" + "plain" | [1.23E-10d, "nondetDouble", "Main", "main()V", "Main.java", 1, 0] | "double" | "1.23E-10" | "0x" + Long.toHexString(Double.doubleToRawLongBits(1.23E-10d)) + "negative zero" | [-0.0d, "nondetDouble", "Main", "main()V", "Main.java", 1, 0] | "double" | "-0.0" | "0x8000000000000000" + "infinity" | [Double.POSITIVE_INFINITY, "nondetDouble", "Main", "main()V", "Main.java", 1, 0] | "double" | "Infinity" | "0x7ff0000000000000" + "raw" | ["a \"quoted\"\\n", "nondetString", "Main", "main()V", "Main.java", 1, 0] | "String" | "a \"quoted\"\\n" | null + "null" | [(String) null, "nondetString", "Main", "main()V", "Main.java", 1, 0] | "String" | "" | null + } + + def "float and double strings round-trip to the recorded value"() { + when: + Witness.recordValue(f, "nondetFloat", "Main", "main()V", "Main.java", 1, 0) + Witness.recordValue(d, "nondetDouble", "Main", "main()V", "Main.java", 1, 0) + def recorded = Witness.getNondetReturns() + + then: + Float.floatToRawIntBits(Float.parseFloat(recorded[0].value)) == Float.floatToRawIntBits(f) + Double.doubleToRawLongBits(Double.parseDouble(recorded[1].value)) == Double.doubleToRawLongBits(d) + + where: + f << [0.33333334f, Float.MIN_VALUE, 16777217.0f, -3.4028235E38f] + d << [0.3333333333333333d, Double.MIN_VALUE, 9007199254740993.0d, 1.7976931348623157E308d] + } + + def "nondet returns keep their execution order"() { + when: + Witness.recordValue(1, "nondetInt", "Main", "main()V", "Main.java", 5, 0) + Witness.recordValue(2, "nondetInt", "Main", "main()V", "Main.java", 5, 1) + Witness.recordValue(3L, "nondetLong", "Main", "main()V", "Main.java", 6, 0) + + then: + Witness.getNondetReturns()*.value == ["1", "2", "3"] + Witness.getNondetReturns()*.callIndex == [0, 1, 0] + } + + def "the violation records the throwable and its frames innermost first without JDK and SWAT frames"() { + given: + def throwable = new AssertionError("boom") + throwable.setStackTrace([ + new StackTraceElement("java.lang.Integer", "parseInt", "Integer.java", 652), + new StackTraceElement("de.uzl.its.swat.instrument.Intrinsics", "init", "Intrinsics.java", 1), + new StackTraceElement("pkg.Helper", "check", "Helper.java", 20), + new StackTraceElement("jdk.internal.reflect.NativeMethodAccessorImpl", "invoke0", null, -2), + new StackTraceElement("Main", "main", "Main.java", 8), + ] as StackTraceElement[]) + + when: + ViolationDTO violation = Witness.describe(throwable) + + then: + violation.throwable == "java.lang.AssertionError" + violation.frames*.className == ["pkg.Helper", "Main"] + violation.frames*.sourceFile == ["Helper.java", "Main.java"] + violation.frames*.line == [20, 8] + } + + def "the violation frames are capped"() { + given: + def throwable = new RuntimeException() + throwable.setStackTrace((1..(Witness.MAX_FRAMES + 10)).collect { + new StackTraceElement("pkg.Rec", "rec", "Rec.java", it) + } as StackTraceElement[]) + + expect: + Witness.describe(throwable).frames.size() == Witness.MAX_FRAMES + Witness.describe(throwable).frames[0].line == 1 + } + + def "an unknown source file is recorded as empty"() { + given: + def throwable = new RuntimeException() + throwable.setStackTrace([new StackTraceElement("Main", "main", null, -1)] as StackTraceElement[]) + + expect: + Witness.describe(throwable).frames[0].sourceFile == "" + Witness.describe(throwable).frames[0].line == -1 + } + + def "no violation is recorded until a throwable escapes"() { + expect: + Witness.getViolation() == null + } + + def "the first AssertionError thrown is recorded as the violation"() { + given: + def first = new AssertionError("first") + first.setStackTrace([new StackTraceElement("Main", "main", "Main.java", 8)] as StackTraceElement[]) + def second = new AssertionError("second") + second.setStackTrace([new StackTraceElement("Main", "main", "Main.java", 12)] as StackTraceElement[]) + + when: + Witness.recordThrow(first) + Witness.recordThrow(second) + + then: + Witness.getViolation().throwable == "java.lang.AssertionError" + Witness.getViolation().frames*.line == [8] + } + + def "other throwables are not recorded when thrown"() { + when: + Witness.recordThrow(new RuntimeException()) + Witness.recordThrow(null) + + then: + Witness.getViolation() == null + } + + def "the thrown AssertionError escaping later keeps its violation"() { + given: + def error = new AssertionError() + error.setStackTrace([new StackTraceElement("Main", "main", "Main.java", 8)] as StackTraceElement[]) + def escaping = new RuntimeException() + escaping.setStackTrace([new StackTraceElement("Main", "main", "Main.java", 20)] as StackTraceElement[]) + + when: + Witness.recordThrow(error) + Witness.recordTarget(error) + + then: + Witness.getViolation().frames*.line == [8] + + when: "another throwable escapes the entry method" + Witness.recordTarget(escaping) + + then: + Witness.getViolation().throwable == "java.lang.RuntimeException" + Witness.getViolation().frames*.line == [20] + } +} diff --git a/symbolic-executor/src/test/groovy/de/uzl/its/value/primitive/numeric/BitvectorEncodingTest.groovy b/symbolic-executor/src/test/groovy/de/uzl/its/value/primitive/numeric/BitvectorEncodingTest.groovy new file mode 100644 index 00000000..825f3b13 --- /dev/null +++ b/symbolic-executor/src/test/groovy/de/uzl/its/value/primitive/numeric/BitvectorEncodingTest.groovy @@ -0,0 +1,245 @@ +package de.uzl.its.value.primitive.numeric + +import de.uzl.its.swat.symbolic.value.primitive.numeric.BitvectorEncoding +import de.uzl.its.swat.symbolic.value.primitive.numeric.floatingpoint.DoubleValue +import de.uzl.its.swat.symbolic.value.primitive.numeric.floatingpoint.FloatValue +import de.uzl.its.swat.symbolic.value.primitive.numeric.integral.BooleanValue +import de.uzl.its.swat.symbolic.value.primitive.numeric.integral.IntValue +import de.uzl.its.swat.symbolic.value.primitive.numeric.integral.LongValue +import org.sosy_lab.common.ShutdownManager +import org.sosy_lab.common.configuration.Configuration +import org.sosy_lab.common.log.BasicLogManager +import org.sosy_lab.java_smt.SolverContextFactory +import org.sosy_lab.java_smt.api.BitvectorFormula +import org.sosy_lab.java_smt.api.BooleanFormula +import org.sosy_lab.java_smt.api.FormulaType +import org.sosy_lab.java_smt.api.NumeralFormula.IntegerFormula +import org.sosy_lab.java_smt.api.SolverContext +import spock.lang.Specification + +/** + * BitvectorEncoding.toBitvector must equal int_to_bv in every model, and must not use int_to_bv + * where the Integer formula is built from numerals, ite, ring operations and bv2int. + */ +class BitvectorEncodingTest extends Specification { + + SolverContext context + def fmgr, bmgr, bvmgr, imgr, fpmgr, smgr + + def setup() { + context = SolverContextFactory.createSolverContext( + Configuration.defaultConfiguration(), + BasicLogManager.create(Configuration.defaultConfiguration()), + ShutdownManager.create().getNotifier(), + SolverContextFactory.Solvers.Z3) + fmgr = context.getFormulaManager() + bmgr = fmgr.getBooleanFormulaManager() + bvmgr = fmgr.getBitvectorFormulaManager() + imgr = fmgr.getIntegerFormulaManager() + fpmgr = fmgr.getFloatingPointFormulaManager() + smgr = fmgr.getStringFormulaManager() + } + + def cleanup() { + context.close() + } + + /** + * Whether a and b are equal under many concrete assignments of their variables. (Proving it for + * all models is the kind of int_to_bv query Z3 does not return on.) + */ + boolean equivalent(BitvectorFormula a, BitvectorFormula b) { + def vars = new LinkedHashMap(fmgr.extractVariables(a)) + vars.putAll(fmgr.extractVariables(b)) + def random = new Random(42) + def doubles = [0.0d, -0.0d, 1.0d, 2.0d, -1.5d, 1e300d, Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY] + for (int round = 0; round < 40; round++) { + def prover = context.newProverEnvironment() + try { + vars.each { String n, def v -> + def type = fmgr.getFormulaType(v) + if (type.isBitvectorType()) { + int w = type.getSize() + def value = round < 4 ? [0G, 1G, (1G << w) - 1G, 1G << (w - 1)][round] : new BigInteger(w, random) + prover.addConstraint(bvmgr.equal(v, bvmgr.makeBitvector(w, value))) + } else if (type.isFloatingPointType()) { + double d = round < doubles.size() ? doubles[round] : random.nextGaussian() * 4 + prover.addConstraint(fpmgr.assignment(v, fpmgr.makeNumber(d, type))) + } else if (type.isStringType()) { + def str = (0.. ws.collect { w -> [n, w] } } + } + + def "keeps int_to_bv for #name, still equal"() { + when: + def f = build(name) + def lowered = BitvectorEncoding.toBitvector(fmgr, f, 32) + + then: + equivalent(lowered, bvmgr.makeBitvector(32, f)) + + where: + name << ["a string length", "a division", "a length minus one"] + } + + def "DCMP, FCMP and LCMP results are bit-vectors and match the Integer encoding (#kind)"() { + given: + def oldEncoding = { BooleanFormula isNaN, int nanResult, BooleanFormula gt, BooleanFormula eq -> + def ite = bmgr.ifThenElse(eq, num(0), bmgr.ifThenElse(gt, num(1), num(-1))) + bvmgr.makeBitvector(32, isNaN == null ? ite : bmgr.ifThenElse(isNaN, num(nanResult), ite)) + } + + when: + IntValue result + BitvectorFormula expected + switch (kind) { + case "DCMPL": + case "DCMPG": + def a = new DoubleValue(context, 1.0d); a.MAKE_SYMBOLIC("0") + def b = new DoubleValue(context, 2.0d); b.MAKE_SYMBOLIC("1") + result = kind == "DCMPL" ? a.DCMPL(b) : a.DCMPG(b) + expected = oldEncoding(bmgr.or(fpmgr.isNaN(a.formula), fpmgr.isNaN(b.formula)), + kind == "DCMPL" ? -1 : 1, + fpmgr.greaterThan(a.formula, b.formula), fpmgr.equalWithFPSemantics(a.formula, b.formula)) + break + case "FCMPL": + def a = new FloatValue(context, 1.0f); a.MAKE_SYMBOLIC("0") + def b = new FloatValue(context, 2.0f); b.MAKE_SYMBOLIC("1") + result = a.FCMPL(b) + expected = oldEncoding(null, 0, + fpmgr.greaterThan(a.formula, b.formula), fpmgr.equalWithFPSemantics(a.formula, b.formula)) + break + default: + def a = new LongValue(context, 1L); a.MAKE_SYMBOLIC("0") + def b = new LongValue(context, 2L); b.MAKE_SYMBOLIC("1") + result = a.LCMP(b) + expected = oldEncoding(null, 0, + bvmgr.greaterThan(a.formula, b.formula, true), bvmgr.equal(a.formula, b.formula)) + } + + then: + result.concrete == -1 + !mixesSorts(result.formula) + equivalent(result.formula, expected) + + where: + kind << ["DCMPL", "DCMPG", "FCMPL", "LCMP"] + } + + def "boolean to int is a bit-vector ite"() { + given: + def b = new BooleanValue(context, true) + b.MAKE_SYMBOLIC("0") + + when: + def i = b.asIntValue() + + then: + i.concrete == 1 + !mixesSorts(i.formula) + equivalent(i.formula, bvmgr.makeBitvector(32, bmgr.ifThenElse(b.formula, num(1), num(0)))) + } + + def "javaCompare matches Double.compare and Float.compare"() { + given: + def values = [Double.NaN, Double.NEGATIVE_INFINITY, -1.5d, -0.0d, 0.0d, Double.MIN_VALUE, 2.0d, Double.POSITIVE_INFINITY] + + expect: + [FormulaType.getDoublePrecisionFloatingPointType(), FormulaType.getSinglePrecisionFloatingPointType()].each { type -> + def a = fpmgr.makeVariable("a", type) + def b = fpmgr.makeVariable("b", type) + def cmp = BitvectorEncoding.javaCompare(fmgr, a, b) + for (double x : values) { + for (double y : values) { + int expected = type.getMantissaSize() == 52 ? Double.compare(x, y) : Float.compare((float) x, (float) y) + def prover = context.newProverEnvironment() + prover.addConstraint(fpmgr.assignment(a, fpmgr.makeNumber(x, type))) + prover.addConstraint(fpmgr.assignment(b, fpmgr.makeNumber(y, type))) + prover.addConstraint(bmgr.not(bvmgr.equal(cmp, bvmgr.makeBitvector(32, expected)))) + assert prover.isUnsat(): "compare($x, $y) should be $expected" + prover.close() + } + } + } + } +} diff --git a/symbolic-executor/src/test/groovy/de/uzl/its/value/primitive/numeric/floatingpoint/DoubleValueTest.groovy b/symbolic-executor/src/test/groovy/de/uzl/its/value/primitive/numeric/floatingpoint/DoubleValueTest.groovy index 8c16de45..265f6753 100644 --- a/symbolic-executor/src/test/groovy/de/uzl/its/value/primitive/numeric/floatingpoint/DoubleValueTest.groovy +++ b/symbolic-executor/src/test/groovy/de/uzl/its/value/primitive/numeric/floatingpoint/DoubleValueTest.groovy @@ -193,7 +193,7 @@ class DoubleValueTest extends Specification { def d2 = new DoubleValue(context, c2) when: - def d3 = d1.DREM(d2) + def d3 = d1.DREM(d2, null) // concrete operands need no trace handler prover.addConstraint(fpmgr.equalWithFPSemantics(d3.formula, fpmgr.makeNumber(res, DoubleValue.precision))) println fmgr.dumpFormula(fpmgr.equalWithFPSemantics(d3.formula, fpmgr.makeVariable('float', DoubleValue.precision))) println fmgr.dumpFormula(fpmgr.equalWithFPSemantics(d3.formula, fpmgr.makeNumber(res, DoubleValue.precision))) diff --git a/symbolic-executor/src/test/groovy/de/uzl/its/value/primitive/numeric/floatingpoint/FloatValueTest.groovy b/symbolic-executor/src/test/groovy/de/uzl/its/value/primitive/numeric/floatingpoint/FloatValueTest.groovy index 1081d342..b203071b 100644 --- a/symbolic-executor/src/test/groovy/de/uzl/its/value/primitive/numeric/floatingpoint/FloatValueTest.groovy +++ b/symbolic-executor/src/test/groovy/de/uzl/its/value/primitive/numeric/floatingpoint/FloatValueTest.groovy @@ -207,7 +207,7 @@ class FloatValueTest extends Specification { def f2 = new FloatValue(context, c2) when: - def f3 = f1.FREM(f2) + def f3 = f1.FREM(f2, null) // concrete operands need no trace handler prover.addConstraint(fpmgr.equalWithFPSemantics(f3.formula, fpmgr.makeNumber(res, FloatValue.precision))) then: @@ -337,6 +337,59 @@ class FloatValueTest extends Specification { ].transpose().collect { (it[0] == Float.NaN || it[1] == Float.NaN) ? -1 : (it[0] == it[1] ? 0 : (it[0] > it[1] ? 1 : -1)) } } + def "FCMPG/FCMPL: a symbolic NaN operand gives the unordered result"(boolean greater, int unordered) { + given: "0.0f compared with a symbolic x that is currently 1.0f" + def zero = new FloatValue(context, 0.0f) + def x = new FloatValue(context, 1.0f, fpmgr.makeVariable("x", FloatValue.precision)) + + when: + def cmp = greater ? zero.FCMPG(x) : zero.FCMPL(x) + prover.addConstraint(fpmgr.isNaN(x.formula)) + prover.addConstraint(bmgr.not(bvmgr.equal(cmp.formula, bvmgr.makeBitvector(32, unordered)))) + + then: "no NaN x gives another result" + cmp.concrete == -1 + prover.isUnsat() + + where: + greater | unordered + true | 1 + false | -1 + } + + def "FCMPG/FCMPL: signed zeros compare equal"(boolean greater) { + given: + def pos = new FloatValue(context, 0.0f) + def neg = new FloatValue(context, -0.0f) + + when: + def cmp = greater ? pos.FCMPG(neg) : pos.FCMPL(neg) + prover.addConstraint(bvmgr.equal(cmp.formula, bvmgr.makeBitvector(32, 0))) + + then: + cmp.concrete == 0 + !prover.isUnsat() + + where: + greater << [true, false] + } + + def "asStringValue of a concrete float is String.valueOf"(float c1) { + expect: + new FloatValue(context, c1).asStringValue().concrete == String.valueOf(c1) + + where: + c1 << [0.0f, -0.0f, 1.5f, -7.01f, Float.NaN, Float.POSITIVE_INFINITY, Float.MIN_VALUE, 1.0e10f] + } + + def "asStringValue of a concrete double is String.valueOf"(double c1) { + expect: + new DoubleValue(context, c1).asStringValue().concrete == String.valueOf(c1) + + where: + c1 << [0.0d, -0.0d, 1.5d, -7.01d, Double.NaN, Double.NEGATIVE_INFINITY, Double.MIN_VALUE, 1.0e100d] + } + // ======================================================================== // Float Narrowing Conversions - Testing fpToIntFormula Fix // ======================================================================== diff --git a/symbolic-executor/src/test/groovy/de/uzl/its/value/reference/ContainerContentsTest.groovy b/symbolic-executor/src/test/groovy/de/uzl/its/value/reference/ContainerContentsTest.groovy new file mode 100644 index 00000000..91b366c4 --- /dev/null +++ b/symbolic-executor/src/test/groovy/de/uzl/its/value/reference/ContainerContentsTest.groovy @@ -0,0 +1,111 @@ +package de.uzl.its.value.reference + +import de.uzl.its.swat.symbolic.value.primitive.numeric.integral.IntValue +import de.uzl.its.swat.symbolic.value.reference.ContainerContents +import de.uzl.its.swat.symbolic.value.reference.ObjectValue +import de.uzl.its.swat.symbolic.value.reference.lang.StringValue +import de.uzl.its.swat.thread.ThreadHandler +import org.sosy_lab.java_smt.api.SolverContext +import spock.lang.Specification + +import static java.lang.Thread.currentThread + +class ContainerContentsTest extends Specification { + + SolverContext context + + def setup() { + if (ThreadHandler.hasThreadContext(currentThread().id)) { + ThreadHandler.removeThreadContext(currentThread().id) + } + ThreadHandler.addThreadContext(currentThread().id, "Test-Thread", -2) + context = ThreadHandler.getSolverContext(currentThread().id) + } + + private StringValue symbolicString(int address) { + def s = new StringValue(context, "abc", address) + s.MAKE_SYMBOLIC() + return s + } + + private ObjectValue container(String className) { + return new ObjectValue(context, className, new IntValue(context, 0), 42) + } + + def "an object whose field holds a symbolic string is symbolic"() { + given: + def o = new ObjectValue(context, "Foo", new IntValue(context, 1), 7) + o.setField(0, symbolicString(8)) + + expect: + o.isSymbolic() + } + + def "a container holding a symbolic value is symbolic, also through a view of it"() { + given: + def list = container("java/util/ArrayList") + list.setContents(new ContainerContents(ContainerContents.Kind.LIST)) + list.getContents().addValue(new StringValue(context, "c", 9)) + def view = container("java/util/ArrayList\$Itr") + view.setContents(ContainerContents.viewOf(list.getContents(), false)) + + expect: + !list.isSymbolic() + !view.isSymbolic() + + when: + list.getContents().addValue(symbolicString(10)) + + then: + list.isSymbolic() + view.isSymbolic() + } + + def "a stored value is found by address, also through a view, unless addresses collide"() { + given: + def contents = new ContainerContents(ContainerContents.Kind.LIST) + def s = symbolicString(11) + contents.addValue(s) + def view = ContainerContents.viewOf(contents, false) + + expect: + contents.lookup(11).is(s) + view.lookup(11).is(s) + contents.lookup(12) == null + + when: + contents.addValue(new StringValue(context, "abc", 11)) + + then: + contents.lookup(11) == null + } + + def "a value stored before its address was known is found once it is"() { + given: + def contents = new ContainerContents(ContainerContents.Kind.LIST) + def o = new ObjectValue(context, "Foo", new IntValue(context, 0)) + contents.addValue(o) + + when: + o.setAddress(13) + + then: + contents.lookup(13).is(o) + } + + def "a map key that becomes symbolic is reported"() { + given: + def contents = new ContainerContents(ContainerContents.Kind.MAP) + def key = new ObjectValue(context, "Foo", new IntValue(context, 1), 14) + contents.addKey(key) + + expect: + !contents.keysSymbolic(new HashSet<>()) + + when: + key.setField(0, symbolicString(15)) + + then: + contents.keysSymbolic(new HashSet<>()) + } +} diff --git a/symbolic-executor/src/test/groovy/de/uzl/its/value/reference/lang/NumberParsingExactnessTest.groovy b/symbolic-executor/src/test/groovy/de/uzl/its/value/reference/lang/NumberParsingExactnessTest.groovy new file mode 100644 index 00000000..942bfa5f --- /dev/null +++ b/symbolic-executor/src/test/groovy/de/uzl/its/value/reference/lang/NumberParsingExactnessTest.groovy @@ -0,0 +1,304 @@ +package de.uzl.its.value.reference.lang + +import de.uzl.its.swat.symbolic.invoke.java.lang.NumberParsing +import de.uzl.its.swat.symbolic.value.Value +import de.uzl.its.swat.symbolic.value.primitive.numeric.floatingpoint.DoubleValue +import de.uzl.its.swat.symbolic.value.primitive.numeric.floatingpoint.FloatValue +import de.uzl.its.swat.symbolic.value.primitive.numeric.integral.IntValue +import de.uzl.its.swat.symbolic.value.reference.lang.StringValue +import de.uzl.its.swat.thread.ThreadHandler +import org.sosy_lab.java_smt.api.* +import spock.lang.Specification +import spock.lang.Unroll + +import static java.lang.Thread.currentThread + +/** + * L0: the models of parsing numbers from Strings are exact. With the string fixed to a sample, the + * branch of a call must hold iff Java does not throw a NumberFormatException, and the value must + * be Java's only (for floats: where the model claims to be exact). + */ +class NumberParsingExactnessTest extends Specification { + + SolverContext context + FormulaManager fmgr + BooleanFormulaManager bmgr + IntegerFormulaManager imgr + StringFormulaManager smgr + BitvectorFormulaManager bvmgr + FloatingPointFormulaManager fpmgr + + static final List EDGE = ["", "-", "+", "0", "-0", "+0", "+5", "--1", "+-1", "1-", "007", + "2147483647", "2147483648", "-2147483648", "-2147483649", "00000000002147483647", "-000000000002147483648", + " 1", "1 ", "0x1", "1_0", "١", "١٢٣", "-٠٠٧", "12", "a", "१२", + "32767", "32768", "-32768", "-32769", "127", "128", "-128", "-129", + "9223372036854775807", "9223372036854775808", "-9223372036854775808", "-9223372036854775809", + "ff", "FF", "-ff", "z", "Z", "zz", "1a", "7fffffff", "80000000", "-80000000", "zik0zj", "1y2p0ij32e8e7", + "11111111111111111111111111111111", "-10000000000000000000000000000000", + "1111111111111111111111111111111111111111111111111111111111111111"] + + static final List FLOATS = ["0", "1", "-0", "+0", "1.5", ".5", "1.", "-.5F", "0.1", "0.3", "2.50", "33.3333", + "3.3333333", "1.0000001", "123456.7", "16777215", "16777216", "16777217", "0.0000000001", "0.00000000001", + "9007199254740991", "9007199254740992", "0.30000000000000004", "0.1234567890123456789012", "1.5f", "1.5d", "1.5D", + "NaN", "+NaN", "-NaN", "Infinity", "-Infinity", "nan", "NaNf", "1e5", "1E-3", "1e", "e5", ".e1", "0x1p3", + "0x1.8p1", "0X.8P-1f", "0x1", "0x", " 1.5 ", "\t2\n", "1 5", "", " ", ".", "+", "-", "1.5.5", "1_0", "١", + "abc", "fixed", "1f5", "00000000000000000000001.5"] + + def setup() { + if (ThreadHandler.hasThreadContext(currentThread().id)) { + ThreadHandler.removeThreadContext(currentThread().id) + } + ThreadHandler.addThreadContext(currentThread().id, "Test-Thread", -2) + context = ThreadHandler.getSolverContext(currentThread().id) + fmgr = context.formulaManager + bmgr = fmgr.booleanFormulaManager + imgr = fmgr.integerFormulaManager + smgr = fmgr.stringFormulaManager + bvmgr = fmgr.bitvectorFormulaManager + fpmgr = fmgr.floatingPointFormulaManager + } + + def cleanup() { + context.close() + ThreadHandler.removeThreadContext(currentThread().id) + } + + /** A Java string as the solver sees it: one SMT char per UTF-16 char. */ + private StringFormula chars(String s) { + StringFormula f = smgr.makeString("") + for (char c : s.toCharArray()) { + f = smgr.concat(f, smgr.fromCodePoint(imgr.makeNumber((int) c))) + } + return f + } + + private boolean unsat(BooleanFormula... fs) { + context.newProverEnvironment().withCloseable { p -> + fs.each { p.addConstraint(it) } + return p.isUnsat() + } + } + + /** Whether, with s fixed to the sample, f holds (and its negation is unsatisfiable). */ + private boolean holds(StringFormula s, String sample, BooleanFormula f) { + return unsat(smgr.equal(s, chars(sample)), bmgr.not(f)) + } + + private boolean fails(StringFormula s, String sample, BooleanFormula f) { + return unsat(smgr.equal(s, chars(sample)), f) + } + + private static Long javaParse(String type, String s, int radix) { + try { + switch (type) { + case "byte": return (long) Byte.parseByte(s, radix) + case "short": return (long) Short.parseShort(s, radix) + case "int": return (long) Integer.parseInt(s, radix) + default: return Long.parseLong(s, radix) + } + } catch (NumberFormatException e) { + return null + } + } + + /** The branch holds iff Java parses the sample, and then the value is Java's only. */ + private void checkIntegral(String type, String sample, int radix) { + StringFormula s = smgr.makeVariable("s") + Long java = javaParse(type, sample, radix) + BooleanFormula valid = NumberParsing.intValid(context, s, radix, type) + if (java == null) { + assert fails(s, sample, valid): "$type '$sample' radix $radix: Java throws, the model parses" + } else { + assert holds(s, sample, valid): "$type '$sample' radix $radix: Java parses $java, the model throws" + NumeralFormula.IntegerFormula v = NumberParsing.intValue(context, s, radix, type) + assert holds(s, sample, imgr.equal(v, imgr.makeNumber(java))): "$type '$sample' radix $radix: not only $java" + } + } + + @Unroll + def "parsing a #type in radix 10 is exact on the edge strings"() { + expect: + EDGE.each { checkIntegral(type, it, 10) } + + where: + type << ["int", "long", "short", "byte"] + } + + @Unroll + def "parsing an int and a long is exact in radix #radix"() { + expect: + EDGE.each { checkIntegral("int", it, radix) } + EDGE.each { checkIntegral("long", it, radix) } + + where: + radix << (2..36) + } + + @Unroll + def "parsing a short and a byte is exact in radix #radix"() { + expect: + EDGE.each { checkIntegral("short", it, radix) } + EDGE.each { checkIntegral("byte", it, radix) } + + where: + radix << [2, 8, 16, 36] + } + + def "non-ASCII digits parse, as Character.digit accepts them"() { + expect: + Integer.parseInt("١٢") == 12 + checkIntegral("int", "١٢", 10) + checkIntegral("int", "10", 10) + } + + private BooleanFormula sameFloat(FloatingPointFormula f, double java, boolean dbl) { + def type = dbl ? FormulaType.getDoublePrecisionFloatingPointType() : FormulaType.getSinglePrecisionFloatingPointType() + if (Double.isNaN(java)) return fpmgr.isNaN(f) + // Structural equality: tells -0.0 from 0.0. + return fpmgr.assignment(f, dbl ? fpmgr.makeNumber(java, type) : fpmgr.makeNumber((double) (float) java, type)) + } + + @Unroll + def "parsing a #name is exact where the model says so, and its branch holds iff Java parses"() { + expect: + FLOATS.each { sample -> + StringFormula s = smgr.makeVariable("s") + Double java + try { + java = dbl ? Double.parseDouble(sample) : (double) Float.parseFloat(sample) + } catch (NumberFormatException e) { + java = null + } + BooleanFormula valid = NumberParsing.floatValid(context, s) + if (java == null) { + assert fails(s, sample, valid): "'$sample': Java throws, the model parses" + return + } + assert holds(s, sample, valid): "'$sample': Java parses, the model throws" + BooleanFormula exact = NumberParsing.floatExact(context, s, dbl) + boolean claimed = NumberParsing.floatExactConcrete(sample, dbl) + assert claimed ? holds(s, sample, exact) : fails(s, sample, exact): "'$sample': exactness differs from its concrete check" + if (claimed) { + FloatingPointFormula v = NumberParsing.floatValue(context, s, dbl) + assert holds(s, sample, sameFloat(v, java, dbl)): "'$sample': not only $java" + } + } + + where: + name | dbl + "float" | false + "double" | true + } + + def "the float grammar agrees with Java on random strings"() { + given: + def random = new Random(4711) + def alphabet = "0123456789.+-eEpPxXfFdDaANnIity \t_" + + expect: + (1..400).each { + def sample = (1..random.nextInt(8)).collect { alphabet[random.nextInt(alphabet.length())] }.join() + boolean java = true + try { + Double.parseDouble(sample) + } catch (NumberFormatException e) { + java = false + } + StringFormula s = smgr.makeVariable("s") + BooleanFormula valid = NumberParsing.floatValid(context, s) + assert java ? holds(s, sample, valid) : fails(s, sample, valid): "'$sample': Java ${java ? 'parses' : 'throws'}" + if (java) { + boolean claimed = NumberParsing.floatExactConcrete(sample, true) + assert claimed ? holds(s, sample, NumberParsing.floatExact(context, s, true)) + : fails(s, sample, NumberParsing.floatExact(context, s, true)): "'$sample': exactness differs" + } + } + } + + def "the exact fragment covers the plain decimals, not exponents or many digits"() { + expect: + NumberParsing.floatExactConcrete("1.5", false) + NumberParsing.floatExactConcrete("-0", false) + NumberParsing.floatExactConcrete("16777215", false) + !NumberParsing.floatExactConcrete("16777216", false) + NumberParsing.floatExactConcrete("16777216", true) + !NumberParsing.floatExactConcrete("1e5", true) + !NumberParsing.floatExactConcrete(" 1", true) + !NumberParsing.floatExactConcrete("0x1p3", true) + } + + // ---- the branches of a call ---- + + private StringValue symbolicString(String concrete) { + return new StringValue(context, concrete, smgr.makeVariable("s"), -1) + } + + @Unroll + def "the last branch of Integer.parseInt('#sample') holds iff it does not throw"() { + given: + def s = symbolicString(sample) + def branches = NumberParsing.branches("java/lang/Integer", "parseInt", "(Ljava/lang/String;)I", [s] as Value[]) + + expect: + branches.size() == 1 + branches[0].holds() == (javaParse("int", sample, 10) != null) + + where: + sample << ["", "-", "+", "2147483647", "2147483648", "-2147483648", " 1", "1 ", "0x1", "١"] + } + + def "a symbolic radix is range checked and pinned; an invalid one throws for every string"() { + given: + def s = symbolicString("10") + def r = new IntValue(context, 16, bvmgr.makeVariable(32, "r")) + def bad = new IntValue(context, 37) + + when: + def pinned = NumberParsing.branches("java/lang/Integer", "parseInt", "(Ljava/lang/String;I)I", [s, r] as Value[]) + def invalid = NumberParsing.branches("java/lang/Long", "parseLong", "(Ljava/lang/String;I)J", [s, bad] as Value[]) + + then: + pinned*.holds() == [true, true, true] + invalid*.holds() == [false] + } + + def "a concrete string has no branches"() { + expect: + NumberParsing.branches("java/lang/Integer", "parseInt", "(Ljava/lang/String;)I", + [new StringValue(context, "12", -1)] as Value[]).isEmpty() + } + + def "Float.parseFloat has the validity branch, then, on a return, whether its value is exact"() { + given: + def s = symbolicString(sample) + + expect: + def args = [s] as Value[] + (NumberParsing.branches("java/lang/Float", "parseFloat", "(Ljava/lang/String;)F", args) + + NumberParsing.returnBranches("java/lang/Float", "parseFloat", "(Ljava/lang/String;)F", args))*.holds() == holds + + where: + sample | holds + "fixed" | [false] + "0.5" | [true, true] + "1e5" | [true, false] + } + + def "a value outside the exact fragment is concrete, with precision loss"() { + given: + def sth = ThreadHandler.getSymbolicTraceHandler(currentThread().id) + + when: + FloatValue exact = NumberParsing.parseFloat(symbolicString("2.5"), sth) + boolean lossAfterExact = sth.symbolicTrace.symbolicPrecisionLoss + DoubleValue approx = NumberParsing.parseDouble(symbolicString("1e5"), sth) + + then: + exact.concrete == 2.5f + exact.isSymbolic() + !lossAfterExact + approx.concrete == 1e5d + !approx.isSymbolic() + sth.symbolicTrace.symbolicPrecisionLoss + } +} diff --git a/symbolic-executor/src/test/groovy/de/uzl/its/value/reference/lang/StringCaseRegexModelExactnessTest.groovy b/symbolic-executor/src/test/groovy/de/uzl/its/value/reference/lang/StringCaseRegexModelExactnessTest.groovy new file mode 100644 index 00000000..6eddbcbf --- /dev/null +++ b/symbolic-executor/src/test/groovy/de/uzl/its/value/reference/lang/StringCaseRegexModelExactnessTest.groovy @@ -0,0 +1,516 @@ +package de.uzl.its.value.reference.lang + +import de.uzl.its.swat.symbolic.invoke.java.lang.CharacterInvocation +import de.uzl.its.swat.symbolic.invoke.java.lang.StringCallBranches +import de.uzl.its.swat.symbolic.invoke.java.lang.StringInvocation +import de.uzl.its.swat.symbolic.value.PlaceHolder +import de.uzl.its.swat.symbolic.value.Value +import de.uzl.its.swat.symbolic.value.primitive.numeric.integral.BooleanValue +import de.uzl.its.swat.symbolic.value.primitive.numeric.integral.CharValue +import de.uzl.its.swat.symbolic.value.primitive.numeric.integral.IntValue +import de.uzl.its.swat.symbolic.value.reference.ObjectValue +import de.uzl.its.swat.symbolic.value.reference.StringBuilderValue +import de.uzl.its.swat.symbolic.value.reference.array.CharArrayValue +import de.uzl.its.swat.symbolic.value.reference.array.ObjectArrayValue +import de.uzl.its.swat.symbolic.value.reference.lang.CaseMapping +import de.uzl.its.swat.symbolic.value.reference.lang.JavaRegex +import de.uzl.its.swat.symbolic.value.reference.lang.StringValue +import de.uzl.its.swat.thread.ThreadHandler +import org.objectweb.asm.Type +import org.sosy_lab.java_smt.api.* +import spock.lang.Specification +import spock.lang.Unroll + +import static java.lang.Thread.currentThread + +/** + * L0: the case mappings, ignore-case comparisons, simple regular expressions, code point methods, + * StringBuffer and the other String models added after StringModelExactnessTest are exact. As + * there: a model is applied to symbolic strings and ints whose concrete values are the sample, the + * call's branches (index checks, pins) are taken as they hold for the sample, and with the + * variables fixed to the sample the solver must not find a result other than Java's. + */ +class StringCaseRegexModelExactnessTest extends Specification { + + SolverContext context + FormulaManager fmgr + BooleanFormulaManager bmgr + IntegerFormulaManager imgr + StringFormulaManager smgr + BitvectorFormulaManager bvmgr + + static final Type STRING = Type.getType(String) + static final Type LOCALE = Type.getType(Locale) + static final Type INT = Type.INT_TYPE + static final Type BOOLEAN = Type.BOOLEAN_TYPE + + static final String DESERET_UPPER = "𐐀" // U+10400 + static final String DESERET_LOWER = "𐐨" // U+10428 + + /** Strings with the chars whose case mappings are special: 'ß', 'İ', 'ı', 'ſ', 'µ', 'ÿ', title case 'Dž', 'ʼn', a ligature, Greek, surrogates. */ + static final List CASE_SAMPLES = ["", "a", "Ab", "aB", "ß", "SS", "ss", "İ", "i", "I", "ı", + "ſ", "s", "µ", "Μ", "μ", "ÿ", "Ÿ", "DŽ", + "Dž", "dž", "ʼn", "ff", "σ", "ς", "ΐ", + "x" + DESERET_UPPER, "x" + DESERET_LOWER, "\uD801", "\uDC00\uD801", + "Ⴀ", "ⴀ", "", "", "W1E!+O", "w1e!+o"] + + def setup() { + if (ThreadHandler.hasThreadContext(currentThread().id)) { + ThreadHandler.removeThreadContext(currentThread().id) + } + ThreadHandler.addThreadContext(currentThread().id, "Test-Thread", -2) + context = ThreadHandler.getSolverContext(currentThread().id) + fmgr = context.formulaManager + bmgr = fmgr.booleanFormulaManager + imgr = fmgr.integerFormulaManager + smgr = fmgr.stringFormulaManager + bvmgr = fmgr.bitvectorFormulaManager + } + + def cleanup() { + context.close() + ThreadHandler.removeThreadContext(currentThread().id) + } + + // ---- helpers ---- + + protected StringFormula chars(String s) { + StringFormula f = smgr.makeString("") + for (char c : s.toCharArray()) { + f = smgr.concat(f, smgr.fromCodePoint(imgr.makeNumber((int) c))) + } + return f + } + + /** A symbolic string whose concrete value is the sample (the pinned models use it). */ + protected StringValue symbolicString(String name, String concrete) { + return new StringValue(context, concrete, smgr.makeVariable(name), -1) + } + + protected IntValue symbolicInt(String name, int concrete) { + return new IntValue(context, concrete, bvmgr.makeVariable(32, name)) + } + + protected BooleanFormula fix(StringValue v) { + return smgr.equal(v.formula, chars(v.concrete)) + } + + protected BooleanFormula fix(IntValue v) { + return bvmgr.equal(v.formula, bvmgr.makeBitvector(32, v.concrete)) + } + + /** The call's branches as they hold for the concrete values. */ + protected List branches(Value receiver, String name, String desc, List> args) { + return StringCallBranches.of(receiver, name, desc, args as Value[]).collect { + it.holds() ? it.condition() : bmgr.not(it.condition()) + } + } + + protected boolean precisionLoss() { + return ThreadHandler.getSymbolicTraceHandler(currentThread().id).symbolicTrace.symbolicPrecisionLoss + } + + protected static boolean ascii(String... strings) { + return strings.every { it.chars().allMatch { c -> c < 128 } } + } + + protected boolean onlyJavasResult(List fixed, BooleanFormula other) { + context.newProverEnvironment().withCloseable { p -> + ThreadHandler.getSymbolicTraceHandler(currentThread().id).getConstraints().each { p.addConstraint(it) } + fixed.each { p.addConstraint(it) } + p.addConstraint(other) + return p.isUnsat() + } + } + + protected boolean satisfiable(List constraints) { + context.newProverEnvironment().withCloseable { p -> + constraints.each { p.addConstraint(it) } + return !p.isUnsat() + } + } + + protected boolean exactInt(List fixed, IntValue result, int java) { + return result.concrete == java && onlyJavasResult(fixed, bmgr.not(imgr.equal(result.asIntegerFormula(), imgr.makeNumber(java)))) + } + + protected boolean exactChar(List fixed, CharValue result, char java) { + return result.concrete == java && onlyJavasResult(fixed, bmgr.not(imgr.equal(result.asIntegerFormula(), imgr.makeNumber((int) java)))) + } + + protected boolean exactBoolean(List fixed, BooleanValue result, boolean java) { + return result.concrete == java && onlyJavasResult(fixed, java ? bmgr.not(result.formula) : result.formula) + } + + protected boolean exactString(List fixed, StringValue result, String java) { + return result.concrete == java && onlyJavasResult(fixed, bmgr.not(smgr.equal(result.formula, chars(java)))) + } + + protected Value call(Value receiver, String name, List desc, List> args) { + return ((ObjectValue) receiver).invokeMethod(name, desc as Type[], args as Value[]) + } + + protected ObjectValue locale(Locale l) { + def v = new ObjectValue(context, "java/util/Locale", new IntValue(context, 0), -1) + v.concrete = l + return v + } + + // ---- toLowerCase / toUpperCase ---- + + def "toLowerCase/toUpperCase of a concrete string are concrete, of a symbolic one not modelled"() { + given: + def c = new StringValue(context, "Ab\u00df", -1) + def s = symbolicString("s", "Ab") + + expect: + ((StringValue) call(c, "toLowerCase", [LOCALE], [locale(Locale.UK)])).concrete == "ab\u00df" + ((StringValue) call(c, "toUpperCase", [], [])).concrete == "AbSS".toUpperCase() + call(s, "toLowerCase", [], []) instanceof PlaceHolder + call(c, "toLowerCase", [LOCALE], [locale(new Locale("tr"))]) instanceof PlaceHolder + StringCallBranches.of(s, "toLowerCase", "()Ljava/lang/String;", [] as Value[]).isEmpty() + } + + // ---- equalsIgnoreCase / regionMatches(true, ...) / compareToIgnoreCase ---- + + @Unroll + def "equalsIgnoreCase is exact, a concrete and a symbolic string: '#a' '#b'"() { + given: + def s = symbolicString("s", a) + def t = new StringValue(context, b, -1) + + when: + def r1 = call(s, "equalsIgnoreCase", [STRING], [t]) + def r2 = call(t, "equalsIgnoreCase", [STRING], [s]) + + then: + // Against a concrete string with a surrogate the comparison is modelled for strings of + // different lengths and ASCII strings only. + b.chars().anyMatch { Character.isSurrogate((char) it) } && a.length() == b.length() ? (r1 instanceof PlaceHolder && r2 instanceof PlaceHolder) : + (exactBoolean([fix(s)] + branches(s, "equalsIgnoreCase", "(Ljava/lang/String;)Z", [t]), (BooleanValue) r1, a.equalsIgnoreCase(b)) + && exactBoolean([fix(s)] + branches(t, "equalsIgnoreCase", "(Ljava/lang/String;)Z", [s]), (BooleanValue) r2, b.equalsIgnoreCase(a))) + !precisionLoss() + + where: + [a, b] << [CASE_SAMPLES, ["", "a", "SS", "ß", "i", "ı", "µ", "Dž", "x" + DESERET_LOWER, ""]].combinations() + } + + @Unroll + def "equalsIgnoreCase is exact, two symbolic strings: '#a' '#b'"() { + given: + def s = symbolicString("s", a) + def t = symbolicString("t", b) + + when: + def r = call(s, "equalsIgnoreCase", [STRING], [t]) + def fixed = [fix(s), fix(t)] + branches(s, "equalsIgnoreCase", "(Ljava/lang/String;)Z", [t]) + + then: + (ascii(a, b) || a.length() != b.length()) ? exactBoolean(fixed, (BooleanValue) r, a.equalsIgnoreCase(b)) : r instanceof PlaceHolder + + where: + [a, b] << ([["", "a", "A", "ab", "AB", "ß", "DŽ", "dž", "ı", "I", DESERET_UPPER, DESERET_LOWER, + "\uD801𐐀", "𐐨X", "𐐨\uD801"], ["", "a", "AB", "Dž", "i", + DESERET_LOWER, "𐐨X", "\uD801𐐀"]].combinations() + + [["\uD801𐐀", "𐐨X"], ["x𐐀", "x𐐨"]]) + } + + def "equalsIgnoreCase(null) is false"() { + expect: + !((BooleanValue) call(symbolicString("s", "a"), "equalsIgnoreCase", [STRING], [new ObjectValue(context, 0)])).concrete + } + + @Unroll + def "regionMatches(#ic, ...) is exact: '#a' #to '#b' #oo #len"() { + given: + def s = symbolicString("s", a) + def t = symbolicString("t", b) + def ignoreCase = new BooleanValue(context, ic, bmgr.makeVariable("ic")) + def toffset = symbolicInt("to", to) + def ooffset = symbolicInt("oo", oo) + def length = symbolicInt("len", len) + def args = [ignoreCase, toffset, t, ooffset, length] + + when: + def r = call(s, "regionMatches", [BOOLEAN, INT, STRING, INT, INT], args) + def fixed = [fix(s), fix(t), fix(toffset), fix(ooffset), fix(length), ic ? ignoreCase.formula : bmgr.not(ignoreCase.formula)] + + branches(s, "regionMatches", "(ZILjava/lang/String;II)Z", args) + + then: + (r instanceof PlaceHolder && !ascii(a, b) && StringValue.regionInBounds(a, to, b, oo, len)) + || exactBoolean(fixed, (BooleanValue) r, a.regionMatches(ic, to, b, oo, len)) + + where: + [a, b, ic, to, oo, len] << [["", "aBc", "x" + DESERET_UPPER], ["ABC", "bc", "X" + DESERET_LOWER], [true, false], + [-1, 1], [0, 1], [-1, 2, 5]].combinations() + } + + @Unroll + def "compareToIgnoreCase is exact: '#a' '#b'"() { + given: + def s = symbolicString("s", a) + def t = symbolicString("t", b) + + when: + def r = call(s, "compareToIgnoreCase", [STRING], [t]) + def fixed = [fix(s), fix(t)] + branches(s, "compareToIgnoreCase", "(Ljava/lang/String;)I", [t]) + + then: + ascii(a, b) ? exactInt(fixed, (IntValue) r, a.compareToIgnoreCase(b)) : r instanceof PlaceHolder + + where: + [a, b] << [["", "a", "B", "abc", "ß", "ÿ", "Ÿ", "Dž", "x" + DESERET_UPPER, "x\uD801", "Ā"], + ["", "A", "b", "ABD", "Ÿ", "x" + DESERET_LOWER, "x\uDC28", "xa", "ā"]].combinations() + } + + def "Boolean.parseBoolean is exact"() { + given: + def s = symbolicString("s", sample) + + expect: + exactBoolean([fix(s)], (BooleanValue) de.uzl.its.swat.symbolic.invoke.java.lang.BooleanInvocation.invokeStaticMethod( + "parseBoolean", [s] as Value[], [STRING] as Type[]), Boolean.parseBoolean(sample)) + + where: + sample << ["", "true", "TRUE", "tRuE", "true ", "ırue", "tru"] + } + + // ---- regular expressions ---- + + static final List REGEXES = ["[A-Z][a-zA-Z]*", "[a-zA-z]+(['-][a-zA-Z]+)*", "\\d+\\s+([a-zA-Z]+|[a-zA-Z]+\\s[a-zA-Z]+)", + "([a-zA-Z]+|[a-zA-Z]+\\s[a-zA-Z]+)", "\\d{5}", "[1-9]\\d{2}-[1-9]\\d{2}-\\d{4}", "a{2,}b?", + "(?:ab|c)*?", "\\w*\\*", "", "[-a]+", "[a-]", "x{0}", "a{1,3}"] + + @Unroll + def "matches(#regex) is exact: '#sample'"() { + given: + def s = symbolicString("s", sample) + + when: + def r = call(s, "matches", [STRING], [new StringValue(context, regex, -1)]) + + then: + exactBoolean([fix(s)], (BooleanValue) r, sample.matches(regex)) + + where: + [regex, sample] << [REGEXES, ["", "a", "Ab", "O'Neil-Smith", "12 Main St", "12 Main", "Lake City", "12345", "1234", + "234-567-8901", "aab", "aaab", "abcab", "x_1*", "a-", "-", "😀", "a\uD83D"]].combinations() + } + + def "regular expressions outside the simple ones are not translated"() { + expect: + ["a.b", "[^a]", '^a', 'a$', "\\D", "\\bx", "(a)\\1", "a*+", "(?i)a", "(?=a)", "\\p{L}", "[a&&b]", "a{2", "\\Qa\\E", "\\u0041"].every { + JavaRegex.toRegex(smgr, it) == null + } + } + + @Unroll + def "replaceFirst with a literal regex is exact, replaceAll a precision loss: '#sample' #regex"() { + given: + def s = symbolicString("s", sample) + def args = [new StringValue(context, regex, -1), new StringValue(context, "^", -1)] + + when: + def first = call(s, "replaceFirst", [STRING, STRING], args) + def all = call(s, "replaceAll", [STRING, STRING], args) + + then: + exactString([fix(s)], (StringValue) first, sample.replaceFirst(regex, "^")) + exactString([fix(s)], (StringValue) all, sample.replaceAll(regex, "^")) + precisionLoss() + + where: + [sample, regex] << [["", "a*b", "**", "Automatic x Automatic"], ["\\*", "Automatic", "b"]].combinations() + } + + def "replaceAll/replaceFirst with a regex or a replacement that is not literal are not modelled"() { + given: + def s = symbolicString("s", "ab") + + expect: + call(s, "replaceAll", [STRING, STRING], [new StringValue(context, "\\w+", -1), new StringValue(context, "x", -1)]) instanceof PlaceHolder + call(s, "replaceAll", [STRING, STRING], [new StringValue(context, "a", -1), new StringValue(context, "\$1", -1)]) instanceof PlaceHolder + call(s, "replaceAll", [STRING, STRING], [new StringValue(context, "", -1), new StringValue(context, "x", -1)]) instanceof PlaceHolder + } + + // ---- code points, repeat, join, new String(char[], int, int) ---- + + @Unroll + def "codePointAt/codePointBefore/codePointCount are exact: '#sample' #i #j"() { + given: + def s = symbolicString("s", sample) + def x = symbolicInt("x", i) + def y = symbolicInt("y", j) + + when: + Integer at = orNull { sample.codePointAt(i) } + Integer before = orNull { sample.codePointBefore(i) } + Integer count = orNull { sample.codePointCount(i, j) } + + then: + at == null || exactInt([fix(s), fix(x)] + branches(s, "codePointAt", "(I)I", [x]), + (IntValue) call(s, "codePointAt", [INT], [x]), at) + before == null || exactInt([fix(s), fix(x)] + branches(s, "codePointBefore", "(I)I", [x]), + (IntValue) call(s, "codePointBefore", [INT], [x]), before) + count == null || exactInt([fix(s), fix(x), fix(y)] + branches(s, "codePointCount", "(II)I", [x, y]), + (IntValue) call(s, "codePointCount", [INT, INT], [x, y]), count) + + where: + [sample, i, j] << [["", "a", "a😀b", "\uDE00\uD83D", "\uD83D😀"], [-1, 0, 1, 2, 3], [0, 2, 3, 4]].combinations() + } + + /** Java's result, or null if it throws (the index checks are tested elsewhere; the model is not used then). */ + protected static Integer orNull(Closure java) { + try { + return java() + } catch (IndexOutOfBoundsException ignored) { + return null + } + } + + def "the index check of codePointBefore holds iff Java does not throw"() { + given: + def s = symbolicString("s", sample) + def x = symbolicInt("x", i) + def b = StringCallBranches.of(s, "codePointBefore", "(I)I", [x] as Value[]) + boolean javaThrows = false + try { sample.codePointBefore(i) } catch (IndexOutOfBoundsException ignored) { javaThrows = true } + + expect: + b.size() == 1 + b[0].holds() == !javaThrows + onlyJavasResult([fix(s), fix(x)], javaThrows ? b[0].condition() : bmgr.not(b[0].condition())) + + where: + [sample, i] << [["", "ab"], [-1, 0, 1, 2, 3]].combinations() + } + + @Unroll + def "repeat(#n) is exact: '#sample'"() { + given: + def s = symbolicString("s", sample) + def count = symbolicInt("n", n) + + expect: + exactString([fix(s), fix(count)] + branches(s, "repeat", "(I)Ljava/lang/String;", [count]), + (StringValue) call(s, "repeat", [INT], [count]), sample.repeat(n)) + + where: + [sample, n] << [["", "ab"], [0, 1, 3]].combinations() + } + + def "String.join of an array is exact"() { + given: + def s = symbolicString("s", "x") + def array = new ObjectArrayValue(context, "java/lang/CharSequence", new IntValue(context, 3)) + array.storeElement(new IntValue(context, 0), s) + array.storeElement(new IntValue(context, 1), new ObjectValue(context, 0)) + array.storeElement(new IntValue(context, 2), new StringBuilderValue(context, new StringValue(context, "y", -1), -1)) + + when: + def r = StringInvocation.invokeStaticMethod("join", [new StringValue(context, ", ", -1), array] as Value[], + [Type.getType(CharSequence), Type.getType(CharSequence[])] as Type[]) + + then: + exactString([fix(s)], (StringValue) r, String.join(", ", "x", null, "y")) + } + + @Unroll + def "new String(char[], #offset, #count) is exact"() { + given: + def array = new CharArrayValue(context, "hello".toCharArray(), -1) + def o = symbolicInt("o", offset) + def c = symbolicInt("c", count) + def s = new StringValue(context, "", -1) + def args = [array, o, c] + def b = StringCallBranches.of(s, "", "([CII)V", args as Value[]) + boolean javaThrows = false + try { new String("hello".toCharArray(), offset, count) } catch (IndexOutOfBoundsException ignored) { javaThrows = true } + + if (!javaThrows) call(s, "", [Type.getType(char[]), INT, INT], args) + + expect: + b[0].holds() == !javaThrows + onlyJavasResult([fix(o), fix(c)], javaThrows ? b[0].condition() : bmgr.not(b[0].condition())) + javaThrows || exactString([fix(o), fix(c)] + b*.condition(), s, new String("hello".toCharArray(), offset, count)) + + where: + [offset, count] << [[-1, 0, 2, 5], [-1, 0, 3, 6]].combinations() + } + + // ---- StringBuilder.reverse, StringBuffer ---- + + @Unroll + def "reverse of a symbolic builder is exact, surrogate pairs kept in order: '#sample'"() { + given: + def s = symbolicString("s", sample) + def sb = new StringBuilderValue(context, s, -1) + def fixed = [fix(s)] + branches(sb, "reverse", "()Ljava/lang/StringBuilder;", []) + + when: + call(sb, "reverse", [], []) + + then: + exactString(fixed, sb.getContent(), new StringBuilder(sample).reverse().toString()) + + where: + sample << ["", "a", "abc", "a😀b", "\uDE00\uD83D", "😀😁", "\uDE00\uD83D\uD83D"] + } + + def "a StringBuffer is modelled as a StringBuilder"() { + given: + def s = symbolicString("s", "ab") + def sb = new StringBuilderValue(context, "java/lang/StringBuffer", null, -1) + + when: + call(sb, "", [STRING], [s]) + call(sb, "append", [STRING], [new StringValue(context, "!", -1)]) + call(sb, "insert", [INT, STRING], [new IntValue(context, 0), new StringValue(context, "<", -1)]) + def str = call(sb, "toString", [], []) + + then: + sb.className == "java/lang/StringBuffer" + sb.isSymbolic() + exactString([fix(s)], (StringValue) str, "[], [Type.getType(Object)] as Type[]), "[], + [Type.CHAR_TYPE, INT] as Type[], null), Character.digit(c as char, radix)) + + where: + [c, radix] << [['0', '7', 'a', 'Z', 'A', '٣', '-'], [-1, 0, 2, 8, 10, 16, 36, 37]].combinations() + } + + def "Character.digit(c, r) is digit(c, 36) if that is below r, for every char and radix"() { + expect: + (0..0xFFFF).every { int c -> + int d = Character.digit((char) c, 36) + (Character.MIN_RADIX..Character.MAX_RADIX).every { int r -> Character.digit((char) c, r) == (d < r ? d : -1) } + } + } + + @Unroll + def "Character.forDigit is exact: #d #radix"() { + given: + def digit = symbolicInt("d", d) + def r = symbolicInt("r", radix) + + expect: + exactChar([fix(digit), fix(r)], (CharValue) CharacterInvocation.invokeStaticMethod("forDigit", [digit, r] as Value[], + [INT, INT] as Type[], null), Character.forDigit(d, radix)) + + where: + [d, radix] << [[-1, 0, 7, 9, 10, 15, 35, 36], [1, 2, 10, 16, 36, 37]].combinations() + } +} diff --git a/symbolic-executor/src/test/groovy/de/uzl/its/value/reference/lang/StringModelExactnessTest.groovy b/symbolic-executor/src/test/groovy/de/uzl/its/value/reference/lang/StringModelExactnessTest.groovy new file mode 100644 index 00000000..eaeea540 --- /dev/null +++ b/symbolic-executor/src/test/groovy/de/uzl/its/value/reference/lang/StringModelExactnessTest.groovy @@ -0,0 +1,701 @@ +package de.uzl.its.value.reference.lang + +import de.uzl.its.swat.symbolic.invoke.java.lang.StringCallBranches +import de.uzl.its.swat.symbolic.value.PlaceHolder +import de.uzl.its.swat.symbolic.value.Value +import de.uzl.its.swat.symbolic.value.primitive.numeric.integral.BooleanValue +import de.uzl.its.swat.symbolic.value.primitive.numeric.integral.CharValue +import de.uzl.its.swat.symbolic.value.primitive.numeric.integral.IntValue +import de.uzl.its.swat.symbolic.value.primitive.numeric.integral.LongValue +import de.uzl.its.swat.symbolic.value.reference.ObjectValue +import de.uzl.its.swat.symbolic.value.reference.StringBuilderValue +import de.uzl.its.swat.symbolic.value.reference.array.CharArrayValue +import de.uzl.its.swat.symbolic.value.reference.lang.StringValue +import de.uzl.its.swat.thread.ThreadHandler +import org.objectweb.asm.Type +import org.sosy_lab.java_smt.api.* +import spock.lang.Specification +import spock.lang.Unroll + +import static java.lang.Thread.currentThread + +/** + * L0: the String and StringBuilder models are exact. A model is applied to a symbolic string (and + * symbolic ints); then, with the variables fixed to a sample, the solver must not find a result + * other than Java's. A model that is too strict there would make the solver call a feasible path + * infeasible: a false SAFE. + */ +class StringModelExactnessTest extends Specification { + + SolverContext context + ProverEnvironment prover + FormulaManager fmgr + BooleanFormulaManager bmgr + IntegerFormulaManager imgr + StringFormulaManager smgr + BitvectorFormulaManager bvmgr + + static final Type STRING = Type.getType(String) + static final Type CHAR_SEQUENCE = Type.getType(CharSequence) + static final Type OBJECT = Type.getType(Object) + static final Type INT = Type.INT_TYPE + + static final List SAMPLES = ["", "a", "abc", "aXa", "97", "xaxax", "a😀b", " \t pad \n", "aaaaaaaaaaaaaaa"] + + def setup() { + if (ThreadHandler.hasThreadContext(currentThread().id)) { + ThreadHandler.removeThreadContext(currentThread().id) + } + ThreadHandler.addThreadContext(currentThread().id, "Test-Thread", -2) + context = ThreadHandler.getSolverContext(currentThread().id) + prover = context.newProverEnvironment(SolverContext.ProverOptions.GENERATE_MODELS) + fmgr = context.formulaManager + bmgr = fmgr.booleanFormulaManager + imgr = fmgr.integerFormulaManager + smgr = fmgr.stringFormulaManager + bvmgr = fmgr.bitvectorFormulaManager + } + + def cleanup() { + prover.close() + context.close() + ThreadHandler.removeThreadContext(currentThread().id) + } + + // ---- helpers ---- + + /** A Java string as the solver sees it: one SMT char per UTF-16 char (makeString would join surrogate pairs). */ + private StringFormula chars(String s) { + StringFormula f = smgr.makeString("") + for (char c : s.toCharArray()) { + f = smgr.concat(f, smgr.fromCodePoint(imgr.makeNumber((int) c))) + } + return f + } + + private StringValue symbolicString(String name, String concrete) { + return new StringValue(context, concrete, smgr.makeVariable(name), -1) + } + + private IntValue symbolicInt(String name, int concrete) { + return new IntValue(context, concrete, bvmgr.makeVariable(32, name)) + } + + private BooleanFormula fixString(StringValue v, String sample) { + return smgr.equal(v.formula, chars(sample)) + } + + private BooleanFormula fixInt(IntValue v, int sample) { + return bvmgr.equal(v.formula, bvmgr.makeBitvector(32, sample)) + } + + private boolean precisionLoss() { + return ThreadHandler.getSymbolicTraceHandler(currentThread().id).symbolicTrace.symbolicPrecisionLoss + } + + /** Whether, under the given fixing constraints (and the definitions of the UFs), `other` is unsatisfiable. */ + private boolean onlyJavasResult(List fixed, BooleanFormula other) { + // A fresh prover per query: after a push, Z3's incremental mode gets stuck on some of them. + context.newProverEnvironment().withCloseable { p -> + ThreadHandler.getSymbolicTraceHandler(currentThread().id).getConstraints().each { p.addConstraint(it) } + fixed.each { p.addConstraint(it) } + p.addConstraint(other) + return p.isUnsat() + } + } + + /** + * Whether, under the given fixing constraints, Java's result is possible: the direction that + * matters for soundness (the converse, that it is the only one, Z3 cannot always show for UFs). + */ + private boolean allowsJava(List fixed, BooleanFormula java) { + return !onlyJavasResult(fixed, java) + } + + private boolean exactInt(List fixed, IntValue result, int java) { + return onlyJavasResult(fixed, bmgr.not(imgr.equal(result.asIntegerFormula(), imgr.makeNumber(java)))) + } + + private boolean exactBoolean(List fixed, BooleanValue result, boolean java) { + return onlyJavasResult(fixed, java ? bmgr.not(result.formula) : result.formula) + } + + private boolean exactString(List fixed, StringFormula result, String java) { + return onlyJavasResult(fixed, bmgr.not(smgr.equal(result, chars(java)))) + } + + private Value call(Value receiver, String name, List desc, List> args) { + return ((ObjectValue) receiver).invokeMethod(name, desc as Type[], args as Value[]) + } + + // ---- String ---- + + @Unroll + def "indexOf(int ch) and indexOf(int ch, int from) search for the char, not its digits: #sample, #ch, #from"() { + given: + def s = symbolicString("s", "fixed") + def c = symbolicInt("ch", 97) + def f = symbolicInt("from", 0) + def fixed = [fixString(s, sample), fixInt(c, ch), fixInt(f, from)] + + when: + def one = (IntValue) call(s, "indexOf", [INT], [c]) + def two = (IntValue) call(s, "indexOf", [INT, INT], [c, f]) + + then: + exactInt(fixed, one, sample.indexOf(ch)) + exactInt(fixed, two, sample.indexOf(ch, from)) + + where: + [sample, ch, from] << [["", "a", "aXa", "97", "a\uD83D\uDE00b"], [(int) 'a', (int) '9', -1, 0x1F600, 0xD83D, 0x110000], + [-3, 0, 2]].combinations() + } + + @Unroll + def "indexOf of a char (as its own code point) is exact: #sample, #ch, #from"() { + given: + def s = symbolicString("s", "fixed") + def c = new CharValue(context, (char) 'a', bvmgr.makeVariable(16, "ch")) + def f = symbolicInt("from", 0) + def fixed = [fixString(s, sample), bvmgr.equal(c.formula, bvmgr.makeBitvector(16, ch)), fixInt(f, from)] + + when: + def one = (IntValue) call(s, "indexOf", [INT], [c]) + def two = (IntValue) call(s, "indexOf", [INT, INT], [c, f]) + + then: + exactInt(fixed, one, sample.indexOf(ch)) + exactInt(fixed, two, sample.indexOf(ch, from)) + + where: + [sample, ch, from] << [["", "a", "aXa", "97", "a😀b"], [(int) 'a', (int) '9', 0xD83D, 0xDE00, 0xFFFF], + [-3, 0, 2]].combinations() + } + + def "indexOf('a') on a concrete char has the Java result concretely"() { + expect: + ((IntValue) call(new StringValue(context, "xab", -1), "indexOf", [INT], [new IntValue(context, (int) 'a')])).concrete == 1 + ((IntValue) call(new StringValue(context, "x97a", -1), "indexOf", [INT, INT], [new IntValue(context, (int) 'a'), new IntValue(context, 0)])).concrete == 3 + } + + @Unroll + def "lastIndexOf(int ch, int from) is exact, with the char pinned: #sample, #ch, #from"() { + given: + def s = symbolicString("s", "fixed") + def c = symbolicInt("ch", ch) + def f = symbolicInt("from", 0) + def pins = StringCallBranches.of(s, "lastIndexOf", "(II)I", [c, f] as Value[]) + def fixed = [fixString(s, sample), fixInt(f, from)] + pins*.condition() + + when: + def r = (IntValue) call(s, "lastIndexOf", [INT, INT], [c, f]) + + then: + exactInt(fixed, r, sample.lastIndexOf(ch, from)) + + where: + [sample, ch, from] << [["", "a", "aXa", "a\uD83D\uDE00b"], [(int) 'a', -1, 0x1F600, 0x110000], [-1, 0, 2, 100]].combinations() + } + + @Unroll + def "lastIndexOf(String str, int from) is exact: #sample, #target, #from"() { + given: + def s = symbolicString("s", "fixed") + def t = symbolicString("t", "x") + def f = symbolicInt("from", 0) + def fixed = [fixString(s, sample), fixString(t, target), fixInt(f, from)] + + when: + def r = (IntValue) call(s, "lastIndexOf", [STRING, INT], [t, f]) + + then: + exactInt(fixed, r, sample.lastIndexOf(target, from)) + + where: + [sample, target, from] << [["", "aXa", "xaxax"], ["", "a", "xa"], [-1, 0, 3, 100]].combinations() + } + + @Unroll + def "indexOf(String str, int from) is exact: #sample, #target, #from"() { + given: + def s = symbolicString("s", "fixed") + def t = symbolicString("t", "x") + def f = symbolicInt("from", 0) + def fixed = [fixString(s, sample), fixString(t, target), fixInt(f, from)] + + when: + def r = (IntValue) call(s, "indexOf", [STRING, INT], [t, f]) + + then: + exactInt(fixed, r, sample.indexOf(target, from)) + + where: + [sample, target, from] << [["", "aXa", "xaxax"], ["", "a", "xa"], [-1, 0, 3, 100]].combinations() + } + + @Unroll + def "startsWith(String prefix, int toffset) is false for an offset out of range: #sample, #prefix, #offset"() { + given: + def s = symbolicString("s", "fixed") + def p = symbolicString("p", "x") + def o = symbolicInt("o", 0) + def fixed = [fixString(s, sample), fixString(p, prefix), fixInt(o, offset)] + + when: + def r = (BooleanValue) call(s, "startsWith", [STRING, INT], [p, o]) + + then: + exactBoolean(fixed, r, sample.startsWith(prefix, offset)) + + where: + [sample, prefix, offset] << [["", "abc"], ["", "a", "abc"], [-1, 0, 1, 3, 4]].combinations() + } + + def "startsWith(String prefix, int toffset) has the concrete result of the offset"() { + expect: + ((BooleanValue) call(new StringValue(context, "tested", -1), "startsWith", [STRING, INT], + [new StringValue(context, "ste", -1), new IntValue(context, 2)])).concrete + } + + @Unroll + def "replace is exact up to ten occurrences, and precision loss: #sample, #target -> #replacement"() { + given: + def s = symbolicString("s", "fixed") + def fixed = [fixString(s, sample)] + + when: + def r = (StringValue) call(s, "replace", [CHAR_SEQUENCE, CHAR_SEQUENCE], + [new StringValue(context, target, -1), new StringValue(context, replacement, -1)]) + + then: + precisionLoss() + exactString(fixed, r.formula, sample.replace(target, replacement)) + + where: + [sample, target, replacement] << [["", "aaaaaaaaa", "xaxax"], ["a", "aa", "xa"], ["", "b"]].combinations() + } + + def "replace(char, char) of more than ten occurrences is not exact, and precision loss"() { + given: + def s = symbolicString("s", "fixed") + def sample = "a" * 25 + + when: + def r = (StringValue) call(s, "replace", [Type.CHAR_TYPE, Type.CHAR_TYPE], + [new CharValue(context, 'a' as char), new CharValue(context, 'c' as char)]) + + then: + precisionLoss() + !exactString([fixString(s, sample)], r.formula, sample.replace('a' as char, 'c' as char)) + } + + def "replace on concrete strings is concrete"() { + when: + def r = (StringValue) call(new StringValue(context, "abab", -1), "replace", [Type.CHAR_TYPE, Type.CHAR_TYPE], + [new CharValue(context, 'a' as char), new CharValue(context, 'c' as char)]) + + then: + r.concrete == "cbcb" + !precisionLoss() + } + + def "replace with an empty or a symbolic target is not modelled exactly"() { + when: + call(symbolicString("s", "ab"), "replace", [CHAR_SEQUENCE, CHAR_SEQUENCE], + [new StringValue(context, "", -1), new StringValue(context, "-", -1)]) + + then: + precisionLoss() + } + + @Unroll + def "trim() is exact: '#sample'"() { + given: + def s = symbolicString("s", "fixed") + + when: + def r = (StringValue) call(s, "trim", [], []) + + then: + exactString([fixString(s, sample)], r.formula, sample.trim()) + + where: + sample << SAMPLES + [" ", "\u0000a\u0000", " a b "] + } + + @Unroll + def "compareTo(String) allows the Java result: #a vs #b"() { + given: + def s = symbolicString("s", "fixed") + def t = symbolicString("t", "fixed") + + when: + def r = (IntValue) call(s, "compareTo", [STRING], [t]) + + then: + allowsJava([fixString(s, a), fixString(t, b)], imgr.equal(r.asIntegerFormula(), imgr.makeNumber(a.compareTo(b)))) + // (That it is the only one, Z3 does not show within reasonable time for some samples.) + + where: + // (Some samples, e.g. "B" vs "abd", take Z3 minutes even to satisfy.) + [a, b] << [["", "a", "ab"], ["", "a", "abd"]].combinations() + } + + @Unroll + def "regionMatches(int, String, int, int) is exact: #toffset #ooffset #len"() { + given: + def s = symbolicString("s", "fixed") + def o = symbolicString("o", "fixed") + def t = symbolicInt("t", 0) + def oo = symbolicInt("oo", 0) + def l = symbolicInt("l", 0) + def fixed = [fixString(s, "abcab"), fixString(o, "xab"), fixInt(t, toffset), fixInt(oo, ooffset), fixInt(l, len)] + + when: + def r = (BooleanValue) call(s, "regionMatches", [INT, STRING, INT, INT], [t, o, oo, l]) + + then: + exactBoolean(fixed, r, "abcab".regionMatches(toffset, "xab", ooffset, len)) + + where: + [toffset, ooffset, len] << [[-1, 0, 3], [-1, 1, 2], [-1, 0, 2]].combinations() + } + + def "equals(StringBuilder) and equals(null) are false"() { + given: + def s = new StringValue(context, "ab", -1) + def sb = new StringBuilderValue(context, new StringValue(context, "ab", -1), -1) + + expect: + call(s, "equals", [OBJECT], [sb]) == PlaceHolder.instance + !((BooleanValue) call(s, "equals", [OBJECT], [new ObjectValue(context, ObjectValue.ADDRESS_NULL)])).concrete + } + + def "contentEquals(StringBuilder) compares the chars"() { + given: + def s = symbolicString("s", "ab") + def sb = new StringBuilderValue(context, new StringValue(context, "ab", -1), -1) + + when: + def r = (BooleanValue) call(s, "contentEquals", [CHAR_SEQUENCE], [sb]) + + then: + r.concrete + exactBoolean([fixString(s, "ab")], r, true) + exactBoolean([fixString(s, "abc")], r, false) + } + + def "getChars copies to the destination offset"() { + given: + def s = new StringValue(context, "abcdef", -1) + def dst = new CharArrayValue(context, new IntValue(context, 4), -1) + + when: + call(s, "getChars", [INT, INT, Type.getType(char[]), INT], + [new IntValue(context, 2), new IntValue(context, 4), dst, new IntValue(context, 1)]) + + then: + (0..3).collect { dst.getElement(new IntValue(context, it)).concrete } == ['\u0000', 'c', 'd', '\u0000'].collect { it as char } + } + + def "toCharArray of the empty string is an empty array"() { + expect: + ((CharArrayValue) call(new StringValue(context, "", -1), "toCharArray", [], [])).size.concrete == 0 + } + + def "new String(char[]) takes the chars of the array"() { + given: + def s = new StringValue(context, "", -1) + def array = new CharArrayValue(context, "hi".toCharArray(), -1) + + when: + call(s, "", [Type.getType(char[])], [array]) + + then: + s.concrete == "hi" + exactString([], s.formula, "hi") + } + + @Unroll + def "long #v and short #v as a String keep their sign"() { + given: + def l = new LongValue(context, (long) v, bvmgr.makeVariable(64, "l")) + + when: + def r = l.asStringValue() + + then: + exactString([bvmgr.equal(l.formula, bvmgr.makeBitvector(64, (long) v))], r.formula, String.valueOf((long) v)) + + where: + v << [0L, 7L, -7L, Long.MIN_VALUE, Long.MAX_VALUE] + } + + // ---- StringBuilder ---- + + private StringBuilderValue builder(StringValue chars) { + return new StringBuilderValue(context, chars, -1) + } + + @Unroll + def "setLength(#n) truncates or pads a symbolic builder: '#sample'"() { + given: + def s = symbolicString("s", "fixed") + def sb = builder(s) + + when: + call(sb, "setLength", [INT], [new IntValue(context, n)]) + + then: + def java = new StringBuilder(sample) + java.setLength(n) + exactString([fixString(s, sample)], sb.content.formula, java.toString()) + + where: + [sample, n] << [["", "ab", "abcdef"], [0, 1, 3, 5]].combinations() + } + + def "substring does not change the builder, and substring(start, end) takes end - start chars"() { + given: + def s = symbolicString("s", "abcdefgh") + def sb = builder(s) + + when: + def sub = (StringValue) call(sb, "substring", [INT, INT], [new IntValue(context, 2), new IntValue(context, 5)]) + def rest = (StringValue) call(sb, "substring", [INT], [new IntValue(context, 6)]) + + then: + sub.concrete == "cde" + rest.concrete == "gh" + sb.content.concrete == "abcdefgh" + exactString([fixString(s, "abcdefgh")], sub.formula, "cde") + exactString([fixString(s, "abcdefgh")], sb.content.formula, "abcdefgh") + } + + def "a builder made from a String does not change that String"() { + given: + def s = new StringValue(context, "abc", -1) + def sb = builder(s) + + when: + call(sb, "deleteCharAt", [INT], [new IntValue(context, 0)]) + call(sb, "insert", [INT, STRING], [new IntValue(context, 0), new StringValue(context, "x", -1)]) + + then: + s.concrete == "abc" + exactString([], s.formula, "abc") + sb.content.concrete == "xbc" + } + + @Unroll + def "replace(#start, #end, str) replaces that range: '#sample'"() { + given: + def s = symbolicString("s", "fixed") + def sb = builder(s) + + when: + call(sb, "replace", [INT, INT, STRING], [new IntValue(context, start), new IntValue(context, end), new StringValue(context, "XY", -1)]) + + then: + exactString([fixString(s, sample)], sb.content.formula, new StringBuilder(sample).replace(start, end, "XY").toString()) + + where: + [sample, start, end] << [["abab", "abcdef"], [0, 1, 2], [2, 3, 10]].combinations() + } + + @Unroll + def "delete(#start, #end) is exact: '#sample'"() { + given: + def s = symbolicString("s", "fixed") + def sb = builder(s) + + when: + call(sb, "delete", [INT, INT], [new IntValue(context, start), new IntValue(context, end)]) + + then: + exactString([fixString(s, sample)], sb.content.formula, new StringBuilder(sample).delete(start, end).toString()) + + where: + [sample, start, end] << [["abab", "abcdef"], [0, 1, 2], [2, 3, 10]].combinations() + } + + def "indexOf(String, int) of a builder uses the from index"() { + given: + def s = symbolicString("s", "xaxa") + def f = symbolicInt("f", 2) + + when: + def r = (IntValue) call(builder(s), "indexOf", [STRING, INT], [new StringValue(context, "a", -1), f]) + + then: + r.concrete == 3 + exactInt([fixString(s, "xaxa"), fixInt(f, 2)], r, 3) + exactInt([fixString(s, "xaxa"), fixInt(f, -4)], r, 1) + } + + def "setCharAt is modelled"() { + given: + def s = symbolicString("s", "Hello") + def sb = builder(s) + + when: + call(sb, "setCharAt", [INT, Type.CHAR_TYPE], [new IntValue(context, 1), new CharValue(context, 'a' as char)]) + + then: + sb.content.concrete == "Hallo" + exactString([fixString(s, "Hello")], sb.content.formula, "Hallo") + exactString([fixString(s, "xy")], sb.content.formula, "xa") + } + + def "append(Object) appends a String, a builder and null"() { + given: + def sb = builder(new StringValue(context, "a", -1)) + + when: + call(sb, "append", [OBJECT], [new StringValue(context, "b", -1)]) + call(sb, "append", [CHAR_SEQUENCE], [builder(new StringValue(context, "c", -1))]) + call(sb, "append", [OBJECT], [new ObjectValue(context, ObjectValue.ADDRESS_NULL)]) + call(sb, "append", [Type.DOUBLE_TYPE], [new de.uzl.its.swat.symbolic.value.primitive.numeric.floatingpoint.DoubleValue(context, 1.5d)]) + + then: + sb.content.concrete == "abcnull1.5" + !sb.unknown + } + + def "after a change that is not modelled the chars are unknown, until resynced"() { + given: + def sb = builder(symbolicString("s", "ab")) + def unmodelled = new ObjectValue(context, "x/Unknown", new IntValue(context, 0), -1) + + when: + call(sb, "insert", [INT, OBJECT], [new IntValue(context, 0), unmodelled]) + + then: + sb.unknown + sb.symbolic + call(sb, "length", [], []) == PlaceHolder.instance + call(sb, "toString", [], []) == PlaceHolder.instance + + when: + sb.resync("ba") + + then: + !sb.unknown + ((StringValue) call(sb, "toString", [], [])).concrete == "ba" + } + + def "the capacity is the constructor's until the builder may grow"() { + given: + def s = symbolicString("s", "abc") + def sb = builder(null) + call(sb, "", [STRING], [s]) + + when: + def capacity = (IntValue) call(sb, "capacity", [], []) + + then: + capacity.concrete == 19 + exactInt([fixString(s, "hello")], capacity, 21) + + when: + call(sb, "append", [STRING], [new StringValue(context, "x", -1)]) + + then: + call(sb, "capacity", [], []) == PlaceHolder.instance + } + + def "a builder with symbolic chars is symbolic"() { + expect: + builder(symbolicString("s", "ab")).symbolic + !builder(new StringValue(context, "ab", -1)).symbolic + } + + // ---- branches of the library calls ---- + + private boolean branchHoldsExactly(List branches, List fixed, boolean javaThrows) { + // The call returns normally iff all of its index checks hold. + BooleanFormula all = bmgr.and(branches*.condition()) + return onlyJavasResult(fixed, javaThrows ? all : bmgr.not(all)) + } + + private static boolean throwsIn(Closure c) { + try { + c() + return false + } catch (IndexOutOfBoundsException ignored) { + return true + } + } + + @Unroll + def "the index checks of String.#name hold iff Java does not throw: '#sample' #a #b"() { + given: + def s = symbolicString("s", "fixed") + def x = symbolicInt("x", a) + def y = symbolicInt("y", b) + def args = desc.startsWith("(II") ? [x, y] : [x] + def fixed = [fixString(s, sample), fixInt(x, a), fixInt(y, b)] + + when: + def branches = StringCallBranches.of(s, name, desc, args as Value[]) + + then: + !branches.isEmpty() + branchHoldsExactly(branches, fixed, throwsIn(java.curry(sample, a, b))) + + where: + [sample, a, b, call] << [["", "abc"], [-1, 0, 3, 4], [-1, 2, 4], + [["charAt", "(I)C", { String str, int i, int j -> str.charAt(i) }], + ["substring", "(I)Ljava/lang/String;", { String str, int i, int j -> str.substring(i) }], + ["substring", "(II)Ljava/lang/String;", { String str, int i, int j -> str.substring(i, j) }]]].combinations() + name = call[0] + desc = call[1] + java = call[2] + } + + @Unroll + def "the index checks of StringBuilder.#name hold iff Java does not throw: '#sample' #a #b"() { + given: + def s = symbolicString("s", "fixed") + def x = symbolicInt("x", a) + def y = symbolicInt("y", b) + def fixed = [fixString(s, sample), fixInt(x, a), fixInt(y, b)] + def args = desc.startsWith("(II") ? [x, y] : desc.startsWith("(IC") ? [x, new CharValue(context, 'z' as char)] + : desc.startsWith("(ILjava/lang/String;") ? [x, new StringValue(context, "q", -1)] : [x] + if (desc.startsWith("(IILjava/lang/String;")) args = [x, y, new StringValue(context, "q", -1)] + + when: + def branches = StringCallBranches.of(builder(s), name, desc, args as Value[]) + + then: + !branches.isEmpty() + branchHoldsExactly(branches, fixed, throwsIn(java.curry(new StringBuilder(sample), a, b))) + + where: + [sample, a, b, call] << [["", "abc"], [-1, 0, 3, 4], [-1, 2, 4], + [["charAt", "(I)C", { StringBuilder str, int i, int j -> str.charAt(i) }], + ["setCharAt", "(IC)V", { StringBuilder str, int i, int j -> str.setCharAt(i, 'z' as char) }], + ["deleteCharAt", "(I)Ljava/lang/StringBuilder;", { StringBuilder str, int i, int j -> str.deleteCharAt(i) }], + ["delete", "(II)Ljava/lang/StringBuilder;", { StringBuilder str, int i, int j -> str.delete(i, j) }], + ["replace", "(IILjava/lang/String;)Ljava/lang/StringBuilder;", { StringBuilder str, int i, int j -> str.replace(i, j, "q") }], + ["insert", "(ILjava/lang/String;)Ljava/lang/StringBuilder;", { StringBuilder str, int i, int j -> str.insert(i, "q") }], + ["substring", "(II)Ljava/lang/String;", { StringBuilder str, int i, int j -> str.substring(i, j) }], + ["setLength", "(I)V", { StringBuilder str, int i, int j -> str.setLength(i) }]]].combinations() + name = call[0] + desc = call[1] + java = call[2] + } + + def "a model that uses a concrete length pins it"() { + given: + def s = symbolicString("s", "abc") + + when: + def branches = StringCallBranches.of(s, "toCharArray", "()[C", [] as Value[]) + + then: + branches.size() == 1 + branches[0].holds() + onlyJavasResult([branches[0].condition()], bmgr.not(imgr.equal(smgr.length(s.formula), imgr.makeNumber(3)))) + } +} diff --git a/symbolic-executor/src/test/groovy/de/uzl/its/value/reference/lang/StringValueTest.groovy b/symbolic-executor/src/test/groovy/de/uzl/its/value/reference/lang/StringValueTest.groovy index d170c2f7..dfca1e34 100644 --- a/symbolic-executor/src/test/groovy/de/uzl/its/value/reference/lang/StringValueTest.groovy +++ b/symbolic-executor/src/test/groovy/de/uzl/its/value/reference/lang/StringValueTest.groovy @@ -1,5 +1,6 @@ package de.uzl.its.value.reference.lang +import de.uzl.its.swat.symbolic.invoke.java.lang.StringCallBranches import de.uzl.its.swat.symbolic.value.Value import de.uzl.its.swat.symbolic.value.primitive.numeric.integral.BooleanValue import de.uzl.its.swat.symbolic.value.reference.lang.StringValue @@ -8,7 +9,7 @@ import org.objectweb.asm.Type import org.sosy_lab.java_smt.api.* import spock.lang.Specification import spock.lang.Unroll - +import spock.lang.Tag import static java.lang.Thread.currentThread @@ -26,7 +27,6 @@ class StringValueTest extends Specification { private static final Type[] STRING_DESC = [Type.getType("Ljava/lang/String")] as Type[] def setup() { - ThreadHandler.init() if (ThreadHandler.hasThreadContext(currentThread().id)) { ThreadHandler.removeThreadContext(currentThread().id) } @@ -157,6 +157,7 @@ class StringValueTest extends Specification { "Case" | "case" | true } + @Tag("slow") @Unroll def "equalsIgnoreCase(String anotherString) - Two symbolic strings" (String s1, String s2, boolean expected) { setup: @@ -193,7 +194,7 @@ class StringValueTest extends Specification { } @Unroll - def "equalsIgnoreCase(String anotherString) - Two symbolic strings - enforced length" (String s1, String s2, boolean expected) { + def "equalsIgnoreCase(String anotherString) - Two symbolic strings - other strings of the pinned length" (String s1, String s2, boolean expected) { setup: prover.push() def lhs = new StringValue(context, s1, smgr.makeVariable("s1"), -1) @@ -203,20 +204,24 @@ class StringValueTest extends Specification { when: def result = (BooleanValue) lhs.invokeMethod("equalsIgnoreCase", STRING_DESC, args) addUFConstraintsFromTrace() + // The call's branches as they hold here: lengths, pinned length, ASCII strings + StringCallBranches.of(lhs, "equalsIgnoreCase", "(Ljava/lang/String;)Z", args).each { + prover.addConstraint(it.holds() ? it.condition() : bmgr.not(it.condition())) + } + // Other strings than the concrete ones, with the opposite result + prover.addConstraint(bmgr.not(smgr.equal(lhs.formula, smgr.makeString(s1)))) forceMismatch(result.formula, expected) - // |s1| = 10 - prover.addConstraint(imgr.equal(smgr.length(lhs.formula), imgr.makeNumber(10))) then: - !prover.isUnsat() result.concrete == expected noExceptionThrown() and: + // Strings with the other result exist (of the same length), and the model gives it them + !prover.isUnsat() def (s1Model, s2Model) = evaluateModel(lhs.formula, rhs.formula) - assert s1Model != null - assert s1Model.length() == 10 - assert (s1Model.equalsIgnoreCase(s2Model)) != expected + s1Model.length() == s1.length() + s1Model.equalsIgnoreCase(s2Model) != expected cleanup: prover.pop() @@ -224,6 +229,6 @@ class StringValueTest extends Specification { where: s1 | s2 | expected "hello" | "HELLO" | true - "Hello" | "bar" | false + "Hello" | "World" | false } } diff --git a/symbolic-executor/src/test/resources/targets/collections/Main.java b/symbolic-executor/src/test/resources/targets/collections/Main.java new file mode 100644 index 00000000..9d031082 --- /dev/null +++ b/symbolic-executor/src/test/resources/targets/collections/Main.java @@ -0,0 +1,86 @@ +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import org.sosy_lab.sv_benchmarks.Verifier; + +/** + * Target for the agent test of the JDK container models. The first argument picks a scenario; + * each one ends in a branch on the symbolic string, after the string went through a container. + */ +public class Main { + static void branch(Object o) { + if (((String) o).startsWith("x")) { + System.out.println("x"); + } + } + + public static void main(String[] args) { + String s = Verifier.nondetString(); + switch (args[0]) { + case "list": { + LinkedList l = new LinkedList<>(); + l.addLast(s); + l.addFirst("a"); + branch(l.getLast()); + break; + } + case "iterator": { + List l = new ArrayList<>(); + l.add(s); + List copy = new ArrayList<>(l); + for (Iterator it = copy.iterator(); it.hasNext(); ) { + branch(it.next()); + } + break; + } + case "map": { + Map m = new HashMap<>(); + m.put("k", s); + for (Map.Entry e : m.entrySet()) { + if (e.getKey().equals("k")) branch(e.getValue()); + } + branch(m.get("k")); + break; + } + case "array": { + List l = new ArrayList<>(); + l.add(s); + Object[] a = l.toArray(); + branch(Arrays.asList(a).get(0)); + break; + } + case "contains": { + List l = new ArrayList<>(); + l.add(s); + if (l.contains("x")) System.out.println("x"); + break; + } + case "symbolicKey": { + Map m = new HashMap<>(); + m.put(s, "v"); + branch(m.get("k") == null ? "y" : "x"); + break; + } + case "heldByUnmodelled": { + List l = new ArrayList<>(); + l.add(s); + branch(String.join(",", l)); + break; + } + case "arraycopy": { + int[] a = new int[4]; + int[] b = new int[4]; + a[1] = s.length(); + System.arraycopy(a, 0, b, 0, 4); + if (b[1] > 2) System.out.println("long"); + break; + } + default: + break; + } + } +} diff --git a/symbolic-executor/src/test/resources/targets/entry/clinit/Main.java b/symbolic-executor/src/test/resources/targets/entry/clinit/Main.java new file mode 100644 index 00000000..9b2d9868 --- /dev/null +++ b/symbolic-executor/src/test/resources/targets/entry/clinit/Main.java @@ -0,0 +1,12 @@ +import org.sosy_lab.sv_benchmarks.Verifier; + +/** Agent test target: the input is read by the static initializer of the entry class. */ +public class Main { + static int x = Verifier.nondetInt(); + + public static void main(String[] args) { + if (x > 5) { + assert false : "clinit target"; + } + } +} diff --git a/symbolic-executor/src/test/resources/targets/entry/initexception/Main.java b/symbolic-executor/src/test/resources/targets/entry/initexception/Main.java new file mode 100644 index 00000000..c4966ca5 --- /dev/null +++ b/symbolic-executor/src/test/resources/targets/entry/initexception/Main.java @@ -0,0 +1,27 @@ +import org.sosy_lab.sv_benchmarks.Verifier; + +/** Agent test target: an exception leaves a superclass constructor called by super(...). */ +public class Main { + public static void main(String[] args) { + int v = Verifier.nondetInt(); + try { + new Derived(v); + } catch (IllegalStateException e) { + assert false : "init target"; + } + } +} + +class Base { + Base(int v) { + if (v == 3) { + throw new IllegalStateException("bad"); + } + } +} + +class Derived extends Base { + Derived(int v) { + super(v); + } +} diff --git a/symbolic-executor/src/test/resources/targets/entry/nested/Main.java b/symbolic-executor/src/test/resources/targets/entry/nested/Main.java new file mode 100644 index 00000000..425e0bb5 --- /dev/null +++ b/symbolic-executor/src/test/resources/targets/entry/nested/Main.java @@ -0,0 +1,23 @@ +import org.sosy_lab.sv_benchmarks.Verifier; + +/** Agent test target: the entry method calls another main method. */ +public class Main { + public static void main(String[] args) { + Harness.main(new String[] {"a"}); + int y = Verifier.nondetInt(); + if (y == 2) { + assert false : "nested target"; + } + } +} + +class Harness { + static int calls = 0; + + public static void main(String[] args) { + int z = Verifier.nondetInt(); + if (z > 1 && args.length == 1) { + calls++; + } + } +} diff --git a/symbolic-executor/src/test/resources/targets/objectarray/Main.java b/symbolic-executor/src/test/resources/targets/objectarray/Main.java new file mode 100644 index 00000000..0e9aac51 --- /dev/null +++ b/symbolic-executor/src/test/resources/targets/objectarray/Main.java @@ -0,0 +1,21 @@ +import org.sosy_lab.sv_benchmarks.Verifier; + +public class Main { + public static void main(String[] args) { + Object[] small = new Object[10]; + small[7] = new Object(); + int i = Verifier.nondetInt(); + if (i >= 0 && i < 10) { + Object o = small[i]; + small[i] = null; + } + Object[] one = new Object[1]; + if (i >= 0 && i < 10) { + one[i - 7] = null; + } + if (args.length > 0 && i >= 0 && i < 10) { + Object[] large = new Object[(1 << 16) + 1]; + large[i] = null; + } + } +} diff --git a/symbolic-executor/src/test/resources/targets/radix/Main.java b/symbolic-executor/src/test/resources/targets/radix/Main.java new file mode 100644 index 00000000..f6dfe50e --- /dev/null +++ b/symbolic-executor/src/test/resources/targets/radix/Main.java @@ -0,0 +1,13 @@ +import org.sosy_lab.sv_benchmarks.Verifier; + +/** Integer.toString(int, int) with a symbolic number and a symbolic radix (see RadixChoiceAgentSpec). */ +public class Main { + public static void main(String[] args) { + int x = Verifier.nondetInt(); + int radix = Verifier.nondetInt(); + String s = Integer.toString(x, radix); + if (s.equals("-zz")) { + System.out.println("found"); + } + } +} diff --git a/symbolic-executor/src/test/resources/targets/stackoverflow/Main.java b/symbolic-executor/src/test/resources/targets/stackoverflow/Main.java new file mode 100644 index 00000000..6f36def0 --- /dev/null +++ b/symbolic-executor/src/test/resources/targets/stackoverflow/Main.java @@ -0,0 +1,31 @@ +import org.sosy_lab.sv_benchmarks.Verifier; + +/** Recursion as deep as the first input; the second input selects whether main catches an overflow. */ +public class Main { + static int depth(int n) { + if (n == 0) { + return 0; + } + return 1 + depth(n - 1); + } + + public static void main(String[] args) { + int n = Verifier.nondetInt(); + boolean catchOverflow = Verifier.nondetInt() == 0; + if (n < 0) { + return; + } + if (!catchOverflow) { + System.out.println("result " + depth(n)); + return; + } + int r; + try { + r = depth(n); + } catch (StackOverflowError e) { + r = -1; + } + System.out.println("result " + r); + assert r == n; + } +} diff --git a/symbolic-executor/src/test/resources/targets/stackoverflow/org/sosy_lab/sv_benchmarks/Verifier.java b/symbolic-executor/src/test/resources/targets/stackoverflow/org/sosy_lab/sv_benchmarks/Verifier.java new file mode 100644 index 00000000..2dcbc9ac --- /dev/null +++ b/symbolic-executor/src/test/resources/targets/stackoverflow/org/sosy_lab/sv_benchmarks/Verifier.java @@ -0,0 +1,46 @@ +package org.sosy_lab.sv_benchmarks; + +/** Minimal stand-in for the sv-benchmarks Verifier; the SV-COMP transformer redirects the calls. */ +public final class Verifier { + public static void assume(boolean condition) { + if (!condition) { + Runtime.getRuntime().halt(1); + } + } + + public static boolean nondetBoolean() { + return false; + } + + public static byte nondetByte() { + return 0; + } + + public static char nondetChar() { + return 0; + } + + public static short nondetShort() { + return 0; + } + + public static int nondetInt() { + return 0; + } + + public static long nondetLong() { + return 0; + } + + public static float nondetFloat() { + return 0; + } + + public static double nondetDouble() { + return 0; + } + + public static String nondetString() { + return ""; + } +} diff --git a/symbolic-executor/src/test/resources/targets/svcomp-exit/CleanExit.java b/symbolic-executor/src/test/resources/targets/svcomp-exit/CleanExit.java new file mode 100644 index 00000000..6999e760 --- /dev/null +++ b/symbolic-executor/src/test/resources/targets/svcomp-exit/CleanExit.java @@ -0,0 +1,10 @@ +import org.sosy_lab.sv_benchmarks.Verifier; + +/** Ends the program with System.exit before an assertion that would fail. */ +public class CleanExit { + public static void main(String[] args) { + int x = Verifier.nondetInt(); + if (x == 7) System.exit(0); + assert x != 7; + } +} diff --git a/symbolic-executor/src/test/resources/targets/svcomp-exit/ExitInFinally.java b/symbolic-executor/src/test/resources/targets/svcomp-exit/ExitInFinally.java new file mode 100644 index 00000000..6abec3a7 --- /dev/null +++ b/symbolic-executor/src/test/resources/targets/svcomp-exit/ExitInFinally.java @@ -0,0 +1,13 @@ +import org.sosy_lab.sv_benchmarks.Verifier; + +/** The assertion fails, then the finally ends the program before the AssertionError escapes. */ +public class ExitInFinally { + public static void main(String[] args) { + int x = Verifier.nondetInt(); + try { + assert x != 7; + } finally { + if (x == 7) System.exit(3); + } + } +} diff --git a/symbolic-executor/src/test/resources/targets/svcomp-exit/HaltInFinally.java b/symbolic-executor/src/test/resources/targets/svcomp-exit/HaltInFinally.java new file mode 100644 index 00000000..4e2a4963 --- /dev/null +++ b/symbolic-executor/src/test/resources/targets/svcomp-exit/HaltInFinally.java @@ -0,0 +1,13 @@ +import org.sosy_lab.sv_benchmarks.Verifier; + +/** Like ExitInFinally, with Runtime.halt (which skips the shutdown hooks). */ +public class HaltInFinally { + public static void main(String[] args) { + int x = Verifier.nondetInt(); + try { + assert x != 7; + } finally { + if (x == 7) Runtime.getRuntime().halt(3); + } + } +} diff --git a/symbolic-executor/src/test/resources/targets/svcomp-objects/Main.java b/symbolic-executor/src/test/resources/targets/svcomp-objects/Main.java new file mode 100644 index 00000000..f54cdaca --- /dev/null +++ b/symbolic-executor/src/test/resources/targets/svcomp-objects/Main.java @@ -0,0 +1,13 @@ +import objects.Box; +import objects.BoxFactory; +import org.sosy_lab.sv_benchmarks.Verifier; + +/** SV-COMP style target for nondetObject: the factory makes the choices with nondet calls. */ +public class Main { + public static void main(String[] args) { + Box b = Verifier.nondetObject(Box.class, new BoxFactory()); + if (b != null && b.v == 42) { + assert false : "nondet object"; + } + } +} diff --git a/symbolic-executor/src/test/resources/targets/svcomp-objects/PlainMain.java b/symbolic-executor/src/test/resources/targets/svcomp-objects/PlainMain.java new file mode 100644 index 00000000..a787b61d --- /dev/null +++ b/symbolic-executor/src/test/resources/targets/svcomp-objects/PlainMain.java @@ -0,0 +1,13 @@ +import objects.Box; +import org.sosy_lab.fixtures.PlainBoxFactory; +import org.sosy_lab.sv_benchmarks.Verifier; + +/** nondetObject with a factory that is not instrumented. */ +public class PlainMain { + public static void main(String[] args) { + Box b = Verifier.nondetObject(Box.class, new PlainBoxFactory()); + if (b != null) { + assert false : "nondet object"; + } + } +} diff --git a/symbolic-executor/src/test/resources/targets/svcomp-objects/objects/Box.java b/symbolic-executor/src/test/resources/targets/svcomp-objects/objects/Box.java new file mode 100644 index 00000000..5ef1e901 --- /dev/null +++ b/symbolic-executor/src/test/resources/targets/svcomp-objects/objects/Box.java @@ -0,0 +1,9 @@ +package objects; + +public class Box { + public final int v; + + public Box(int v) { + this.v = v; + } +} diff --git a/symbolic-executor/src/test/resources/targets/svcomp-objects/objects/BoxFactory.java b/symbolic-executor/src/test/resources/targets/svcomp-objects/objects/BoxFactory.java new file mode 100644 index 00000000..4d8959b3 --- /dev/null +++ b/symbolic-executor/src/test/resources/targets/svcomp-objects/objects/BoxFactory.java @@ -0,0 +1,16 @@ +package objects; + +import org.sosy_lab.sv_benchmarks.ObjectFactory; +import org.sosy_lab.sv_benchmarks.Verifier; + +public class BoxFactory implements ObjectFactory { + @Override + public Box createObject() { + switch (Verifier.nondetInt()) { + case 1: + return new Box(Verifier.nondetInt()); + default: + return null; + } + } +} diff --git a/symbolic-executor/src/test/resources/targets/svcomp-objects/org/sosy_lab/fixtures/PlainBoxFactory.java b/symbolic-executor/src/test/resources/targets/svcomp-objects/org/sosy_lab/fixtures/PlainBoxFactory.java new file mode 100644 index 00000000..aacb9c6a --- /dev/null +++ b/symbolic-executor/src/test/resources/targets/svcomp-objects/org/sosy_lab/fixtures/PlainBoxFactory.java @@ -0,0 +1,13 @@ +package org.sosy_lab.fixtures; + +import objects.Box; +import org.sosy_lab.sv_benchmarks.ObjectFactory; +import org.sosy_lab.sv_benchmarks.Verifier; + +/** A factory in an excluded package: it is not instrumented, so its nondet choices are concrete. */ +public class PlainBoxFactory implements ObjectFactory { + @Override + public Box createObject() { + return Verifier.nondetInt() == 1 ? new Box(7) : null; + } +} diff --git a/symbolic-executor/src/test/resources/targets/svcomp-objects/org/sosy_lab/sv_benchmarks/ObjectFactory.java b/symbolic-executor/src/test/resources/targets/svcomp-objects/org/sosy_lab/sv_benchmarks/ObjectFactory.java new file mode 100644 index 00000000..5236ba02 --- /dev/null +++ b/symbolic-executor/src/test/resources/targets/svcomp-objects/org/sosy_lab/sv_benchmarks/ObjectFactory.java @@ -0,0 +1,5 @@ +package org.sosy_lab.sv_benchmarks; + +public interface ObjectFactory { + T createObject(); +} diff --git a/symbolic-executor/src/test/resources/targets/svcomp-objects/org/sosy_lab/sv_benchmarks/Verifier.java b/symbolic-executor/src/test/resources/targets/svcomp-objects/org/sosy_lab/sv_benchmarks/Verifier.java new file mode 100644 index 00000000..cfe4b31f --- /dev/null +++ b/symbolic-executor/src/test/resources/targets/svcomp-objects/org/sosy_lab/sv_benchmarks/Verifier.java @@ -0,0 +1,12 @@ +package org.sosy_lab.sv_benchmarks; + +/** Minimal stand-in for the sv-benchmarks Verifier; the SV-COMP transformer redirects the calls. */ +public final class Verifier { + public static int nondetInt() { + return 0; + } + + public static T nondetObject(Class type, ObjectFactory factory) { + return factory.createObject(); + } +} diff --git a/symbolic-executor/src/test/resources/targets/svcomp-refequals/Main.java b/symbolic-executor/src/test/resources/targets/svcomp-refequals/Main.java new file mode 100644 index 00000000..62ab227d --- /dev/null +++ b/symbolic-executor/src/test/resources/targets/svcomp-refequals/Main.java @@ -0,0 +1,15 @@ +import org.sosy_lab.sv_benchmarks.Verifier; + +/** + * SV-COMP style target for the agent test of reference comparisons: SWAT rewrites a == b into a + * call to UtilInstrumented.refEquals, whose branches the static pre-analysis graph does not have. + */ +public class Main { + public static void main(String[] args) { + Object a = new Object(); + Object b = Verifier.nondetBoolean() ? a : new Object(); + if (a == b) { + assert b != null; + } + } +} diff --git a/symbolic-executor/src/test/resources/targets/svcomp-strings/Main.java b/symbolic-executor/src/test/resources/targets/svcomp-strings/Main.java new file mode 100644 index 00000000..c1d56f9a --- /dev/null +++ b/symbolic-executor/src/test/resources/targets/svcomp-strings/Main.java @@ -0,0 +1,17 @@ +import org.sosy_lab.sv_benchmarks.Verifier; + +/** + * SV-COMP style target for the agent test of the index checks of String methods: charAt throws + * for a nondet string shorter than 6 chars, and the exception is caught. + */ +public class Main { + public static void main(String[] args) { + String s = Verifier.nondetString(); + try { + char c = s.charAt(5); + assert c != 'x'; + } catch (StringIndexOutOfBoundsException e) { + // too short + } + } +} diff --git a/symbolic-executor/src/test/resources/targets/svcomp/Main.java b/symbolic-executor/src/test/resources/targets/svcomp/Main.java new file mode 100644 index 00000000..0356b322 --- /dev/null +++ b/symbolic-executor/src/test/resources/targets/svcomp/Main.java @@ -0,0 +1,21 @@ +import org.sosy_lab.sv_benchmarks.Verifier; + +/** + * SV-COMP style target for the agent test of the witness data in the trace: nondet calls of + * several types, two of them on one line, and an assertion that fails in a helper method. + */ +public class Main { + static int check(int a, int b) { + assert a + b != 7 : "witness target"; + return a + b; + } + + public static void main(String[] args) { + int a = Verifier.nondetInt(), b = Verifier.nondetInt(); + double d = Verifier.nondetDouble(); + String s = Verifier.nondetString(); + if (d < 0.5 && s.length() > 3) { + check(a, b); + } + } +} diff --git a/symbolic-executor/src/test/resources/targets/svcomp/org/sosy_lab/sv_benchmarks/Verifier.java b/symbolic-executor/src/test/resources/targets/svcomp/org/sosy_lab/sv_benchmarks/Verifier.java new file mode 100644 index 00000000..2dcbc9ac --- /dev/null +++ b/symbolic-executor/src/test/resources/targets/svcomp/org/sosy_lab/sv_benchmarks/Verifier.java @@ -0,0 +1,46 @@ +package org.sosy_lab.sv_benchmarks; + +/** Minimal stand-in for the sv-benchmarks Verifier; the SV-COMP transformer redirects the calls. */ +public final class Verifier { + public static void assume(boolean condition) { + if (!condition) { + Runtime.getRuntime().halt(1); + } + } + + public static boolean nondetBoolean() { + return false; + } + + public static byte nondetByte() { + return 0; + } + + public static char nondetChar() { + return 0; + } + + public static short nondetShort() { + return 0; + } + + public static int nondetInt() { + return 0; + } + + public static long nondetLong() { + return 0; + } + + public static float nondetFloat() { + return 0; + } + + public static double nondetDouble() { + return 0; + } + + public static String nondetString() { + return ""; + } +} diff --git a/symbolic-explorer/SymbolicExplorer.py b/symbolic-explorer/SymbolicExplorer.py index c2bdf135..bb4491d7 100755 --- a/symbolic-explorer/SymbolicExplorer.py +++ b/symbolic-explorer/SymbolicExplorer.py @@ -1,4 +1,5 @@ import argparse +import os import sys import threading from urllib.request import Request @@ -23,7 +24,7 @@ from driver.SimpleDriver import SimpleDriver sys.setrecursionlimit(10000000) -logging.basicConfig(level=logging.DEBUG) +logging.basicConfig(level=logging.INFO) class SymbolicExplorer: @@ -59,7 +60,17 @@ def run_passive(self): def run_sv_comp(self): driver = SVCompDriver(args) - driver.run() + try: + driver.run() + except BaseException: + # The driver reports UNKNOWN for a failed exploration itself; this catches what fails + # around it. Without it the HTTP server would keep the process alive until the time limit. + log.get_logger().exception('[SVCOMP] The SV-COMP driver failed') + if not driver.verdict_reported: + log.get_verdict_logger().info(f'[VERDICT {driver.verification_category.value}] == DONT-KNOW') + for handler in log.get_logger().handlers: + handler.flush() + os._exit(1) def run_http(self): driver = HTTPDriver(self.args) @@ -117,7 +128,7 @@ async def http_exception_handler(request: Request, exc: StarletteHTTPException): def init_args(parser: argparse.ArgumentParser): parser.add_argument("-m", "--mode", choices=['passive', 'annotation', 'args', 'sv-comp', 'http', 'simple'], default='annotation', help="Choose the desired mode") - parser.add_argument("-t", "--target", help="Full path to the target JAR file") + parser.add_argument("-t", "--target", help="Full path to the target JAR or class name") parser.add_argument("-prp", "--property", help="Which property to verify") parser.add_argument("-s", "--symbolicvars", nargs='+', help="The types and amount of the symbolic " "variables") @@ -130,13 +141,56 @@ def init_args(parser: argparse.ArgumentParser): parser.add_argument("-p", "--port", help="The port to use for the server", default=8078) parser.add_argument("-i", "--ip_addr", help="IP address to Listen on", default="127.0.0.1") parser.add_argument("--optimize", action="store_true", help="Enable Z3 optimizer to minimize variable values (slower but may produce simpler solutions)", default=False) + parser.add_argument("--no-fp-tactic", action="store_true", help="Solve queries with floating-point terms with Z3's default solver only, instead of its qffp tactic (eager bit-blasting of floating point) first", default=False) parser.add_argument("--log-smt-formulas", action="store_true", help="Log SMT formulas and models to disk for debugging", default=False) + parser.add_argument("--sa-file", help="Path to a file with static pre-analysis results for informing branch exploration (optional)") + parser.add_argument("--sa-path", help="Path to the static pre-analysis tool for informing branch exploration (optional): a cfg-extraction checkout, whose build/libs/cfg-extractor-1.0-SNAPSHOT-all.jar is run") + parser.add_argument("--sa-jar", help="The static pre-analysis tool's jar itself (cfg-extraction's fat jar, as in the SV-COMP archive), instead of --sa-path (optional; sv-comp and http mode)") + parser.add_argument("--sa-no-callback-targets", action="store_true", help="Do not count library calls as possible asserts when application code that library code may call back (overrides of library methods, lambdas) can reach an assert. Unsound: an assert reached only through such a callback can then give a false SAFE", default=False) + parser.add_argument("--console-log-level", choices=["DEBUG", "INFO", "WARNING", "ERROR"], default="INFO", help="Lowest level of the explorer's log lines on the console (explorer.log and verdict.log always get DEBUG and up)") + parser.add_argument("--prefer-short-strings", action="store_true", default=False, help="Shrink each string input of a solution to the shortest length the query allows") + parser.add_argument("--search-order", choices=["dfs", "shallow", "coverage", "coverage-dfs", "generational"], default="coverage", help="Order in which the open branches are tried (default: coverage): dfs (pre-order of the execution tree), shallow (closest to the root first), coverage (sides no trace took yet first, then shallow), coverage-dfs (the same, then dfs), generational (coverage, then the branches of the traces that covered the most first)") + parser.add_argument("--sa-rank-distance", action="store_true", help="Try branches closest to an assert (by static pre-analysis graph distance) first, instead of in plain DFS order", default=False) + parser.add_argument("--no-executor-cds", action="store_true", help="Do not share the classes the executor JVMs load through a class data sharing (AppCDS) archive dumped during the run (sv-comp mode)", default=False) + parser.add_argument("--no-prefer-small-models", action="store_true", help="Use a solver's solution as it is, also if it has extreme integral inputs (by default such a solution is re-solved with the inputs bounded first)", default=False) + parser.add_argument("--no-loop-summaries", action="store_true", help="Do not let the executor merge diamonds and counting loops into formulas (loop summaries; sets -DloopSummaries=false)", default=False) + parser.add_argument("--no-sa-slicing", action="store_true", help="Do not use the static pre-analysis' relevance analysis: flip branches that cannot affect a target too, and keep their constraints in every query", default=False) + parser.add_argument("--no-loop-state-matching", action="store_true", help="Flip branches below a loop-head state that was already reached at a smaller position too (the executor reports loop-head states if its config sets loopStateMatching)", default=False) + parser.add_argument("--solver-rlimit", type=int, default=0, help="Z3 resource limit (rlimit) for the first query of each branch in sv-comp mode; a branch that runs out is retried later with a growing limit. 0 (default): unlimited") + parser.add_argument("--precheck-jar", default=None, help="cfg-extraction's cfg-extractor-precheck.jar for the property pre-check (default: next to symbolic-explorer in the SV-COMP archive, else ../cfg-extraction/build/libs/)") + parser.add_argument("--no-property-precheck", action="store_true", help="Do not run the static property pre-check (sv-comp mode), which can prove no-runtime-exception (no uncaught RuntimeException is possible) or valid-assert (no AssertionError is ever created) for the whole program", default=False) + parser.add_argument("--divergence-retries", type=int, default=3, help="How often a branch is re-solved (with the failed solutions excluded) when the run of its solution does not reach it, before it is given up on, which rules out SAFE (sv-comp mode)") + parser.add_argument("--approximate-rlimit", type=int, default=250_000_000, help="Z3 resource limit for a query over an approximate Math model in sv-comp mode (about one to one and a half minutes); one that runs out is given up on, which rules out SAFE as the model does anyway. 0: no limit beyond --solver-rlimit") + parser.add_argument("--parallel-sa", action="store_true", help="Run the static pre-analysis in the background and use it once it is ready (sv-comp mode). By default exploration waits for it: running it in parallel makes runs nondeterministic", default=False) + + # SV-COMP resource budget, shared by the explorer and all executor JVMs (sv-comp mode). The + # defaults are the competition's limits (bench-defs swat.xml); a lower cgroup memory limit wins. + parser.add_argument("--memory-limit", type=int, default=15000, + help="Memory limit of the whole run in MB (10^6 bytes); the executor heap is derived from it") + parser.add_argument("--cpu-limit", type=float, default=900, + help="CPU time limit of the whole run in seconds; exploration stops with UNKNOWN shortly before it") + parser.add_argument("--cpu-used", type=float, default=0.0, + help="CPU seconds the run used before the explorer started (e.g. compiling the task)") + + # SV-COMP witness arguments: the explorer writes the violation witness from the trace of the + # violating execution (needs the task's input files and property file as given to the verifier) + parser.add_argument("--witness-dir", default="witness", + help="Directory for witness.yml/witness.graphml, relative to the working directory (sv-comp mode)") + parser.add_argument("--witness-input", nargs='+', default=[], + help="The task's input files/directories exactly as given to the verifier") + parser.add_argument("--witness-property", help="Path to the property file of the task") + parser.add_argument("--witness-base-dir", + help="Directory relative witness input paths are resolved against (default: the working directory)") + parser.add_argument("--producer-version", default="unknown", help="Producer version recorded in the witness") # HTTP-specific arguments parser.add_argument("--host", help="HTTP target host", default="localhost") parser.add_argument("--http-port", help="HTTP target port", type=int, default=8080) parser.add_argument("-u", "--url-template", help="URL template with symbolic variables (e.g., /api/path/?x=)") parser.add_argument("--http-method", choices=['GET', 'POST', 'PUT', 'DELETE'], default='GET', help="HTTP method") + parser.add_argument("--sa-entry", help="The method the static pre-analysis graph starts at, as pkg.Class#method (http mode: the endpoint method; default: main of --target)") + parser.add_argument("--time-limit", type=float, default=0, help="Wall-clock limit in seconds for the exploration (http mode); 0: none") + parser.add_argument("--stop-on-violation", action="store_true", default=False, help="End the exploration at the first violation (http mode)") if __name__ == '__main__': @@ -147,12 +201,19 @@ def init_args(parser: argparse.ArgumentParser): args = parser.parse_args() # Initialize loggers - log.initialize_loggers(args.logdir) + log.initialize_loggers(args.logdir, console_level=getattr(logging, args.console_log_level)) + if args.mode == 'sv-comp': + # The module loggers' DEBUG/INFO lines on stdout (several per branch and query) were 500 MB + # in 900 s on MinePump: CPU time, and memory in a BenchExec container. The explorer's own + # log (explorer.log) is not affected. + logging.getLogger().setLevel(logging.WARNING) # Set configuration flags in Database from data.Database import Database Database.instance().optimize_solutions = args.optimize Database.instance().log_smt_formulas = args.log_smt_formulas + Database.instance().fp_tactic = not args.no_fp_tactic + Database.instance().prefer_short_strings = args.prefer_short_strings Database.instance().args = args # Store args for access to logdir s = SymbolicExplorer(args) diff --git a/symbolic-explorer/constraint/ConstraintController.py b/symbolic-explorer/constraint/ConstraintController.py index afe20f72..29e7b7aa 100755 --- a/symbolic-explorer/constraint/ConstraintController.py +++ b/symbolic-explorer/constraint/ConstraintController.py @@ -38,9 +38,14 @@ def post(request: ConstraintRequest, endpointID: str = Query(...), traceID: str trace = request.trace inputs = request.inputs ufs = request.ufs + missingInvocations = request.missingInvocations symbolicContextLoss = request.symbolicContextLoss symbolicPrecisionLoss = request.symbolicPrecisionLoss referenceSemanticChange = request.referenceSemanticChange + nondetReturns = request.nondetReturns + violation = request.violation + approximateModel = request.approximateModel + stackOverflow = request.stackOverflow # Start a new thread to add constraints thread = threading.Thread(target=ConstraintService.add_constraints, kwargs={ @@ -49,9 +54,14 @@ def post(request: ConstraintRequest, endpointID: str = Query(...), traceID: str 'trace': trace, 'inputs': inputs, 'ufs': ufs, + 'missing_invocations': missingInvocations, 'symbolic_context_loss': symbolicContextLoss, 'symbolic_precision_loss': symbolicPrecisionLoss, - 'reference_semantic_change': referenceSemanticChange}) + 'reference_semantic_change': referenceSemanticChange, + 'nondet_returns': nondetReturns, + 'violation': violation, + 'approximate_model': approximateModel, + 'stack_overflow': stackOverflow}) thread.start() thread.join() # To ensure trace is added in SV-Comp mode # Return a response indicating that the request has been accepted diff --git a/symbolic-explorer/constraint/ConstraintService.py b/symbolic-explorer/constraint/ConstraintService.py index 89831e6e..a124eaf6 100755 --- a/symbolic-explorer/constraint/ConstraintService.py +++ b/symbolic-explorer/constraint/ConstraintService.py @@ -1,14 +1,14 @@ -from typing import List +from typing import List, Optional from data.Database import Database from parse.TraceParser import Parser from data.trace.Input import Input from data.trace.UF import UF -from parse.DataTransferObjects import TraceItem, InputItem, UFItem +from parse.DataTransferObjects import TraceItem, InputItem, UFItem, InvocationItem, NondetReturnItem, ViolationItem from data.trace.Special import Special from data.trace.Branch import Branch -import log +import log, logging logger = log.get_logger() class ConstraintService: @@ -23,7 +23,12 @@ class ConstraintService: @staticmethod def add_constraints(endpoint_id: str, trace_id: str, trace: List[TraceItem], inputs: List[InputItem], ufs: List[UFItem], symbolic_context_loss: bool, symbolic_precision_loss: bool, - reference_semantic_change: bool = False): + reference_semantic_change: bool = False, + missing_invocations: List[InvocationItem] = None, + nondet_returns: List[NondetReturnItem] = None, + violation: Optional[ViolationItem] = None, + approximate_model: bool = False, + stack_overflow: bool = False): """ Adds constraints to the database. @@ -40,16 +45,42 @@ def add_constraints(endpoint_id: str, trace_id: str, trace: List[TraceItem], inp symbolic_context_loss (bool): A flag indicating whether the symbolic context was lost. symbolic_precision_loss (bool): A flag indicating whether the symbolic precision was lost (UFs introduced). reference_semantic_change (bool): A flag indicating whether reference equality semantics changed. + missing_invocations (list): Methods that could not be modelled symbolically during this trace. + nondet_returns (list): The values returned by Verifier.nondet*() calls with their call locations. + violation (ViolationItem): The throwable that escaped the entry method, if any. + approximate_model (bool): Whether an approximate model (a sampled Math function) was used, + so that solutions built from this trace are only guesses. + stack_overflow (bool): Whether a StackOverflowError reached the target's code, so that a + violation of this execution depends on the JVM's stack size. Returns: None: The result is the side effect of adding data to the database. """ - # logger.info(f'[CONSTRAINT SERVICE] Received trace: {[t.__str__() for t in trace]}') - trace_parsed: List[Branch | Special] = Parser.parse_trace(trace, trace_id=trace_id) - # logger.info(f'[CONSTRAINT SERVICE] Parsed trace: {[t.__str__() for t in trace_parsed]}') - inputs_parsed: List[Input] = Parser.parse_inputs(inputs) - ufs_parsed: List[UF] = Parser.parse_ufs(ufs) - # Adding the trace and inputs to the database for the specified endpoint. - Database.instance().add_trace(endpoint_id, trace_id, trace_parsed, inputs_parsed, ufs_parsed, symbolic_context_loss, symbolic_precision_loss, reference_semantic_change) - logger.info(f'[CONSTRAINT SERVICE] Added trace {trace_id} to endpoint {endpoint_id}') + try: + # logger.debug(f'[CONSTRAINT SERVICE] Received trace: {[t.__str__() for t in trace]}') + trace_parsed: List[Branch | Special] = Parser.parse_trace(trace, trace_id=trace_id) + if logger.isEnabledFor(logging.DEBUG): + logger.debug(f'[CONSTRAINT SERVICE] Parsed trace: {[t.__str__() for t in trace_parsed]}') + logger.debug(f'[CONSTRAINT SERVICE] Parsed trace branches: {[t.__str__() for t in trace_parsed if isinstance(t, Branch)]}') + inputs_parsed: List[Input] = Parser.parse_inputs(inputs) + # logger.debug(f'[CONSTRAINT SERVICE] Parsed inputs: {[i.__str__() for i in inputs_parsed]}') + ufs_parsed: List[UF] = Parser.parse_ufs(ufs) + # Flatten the missing-invocation DTOs into plain dicts so the Database/Tree stay decoupled + # from the pydantic request models. + missing_invocations_data = [item.model_dump() for item in (missing_invocations or [])] + # The witness data of this execution (plain dicts as well); the Database keeps the latest. + witness_data = { + 'trace_id': trace_id, + 'nondet_returns': [item.model_dump() for item in (nondet_returns or [])], + 'violation': violation.model_dump() if violation is not None else None, + } + # Adding the trace and inputs to the database for the specified endpoint. + Database.instance().add_trace(endpoint_id, trace_id, trace_parsed, inputs_parsed, ufs_parsed, symbolic_context_loss, symbolic_precision_loss, reference_semantic_change, missing_invocations_data, witness_data, approximate_model, stack_overflow) + logger.info(f'[CONSTRAINT SERVICE] Added trace {trace_id} to endpoint {endpoint_id}') + except Exception as e: + # A trace that cannot be added must not end the run silently, nor let it go on as if the + # tree were complete: the driver reports UNKNOWN for it (SVCompDriver). + logger.exception(f'[CONSTRAINT SERVICE] Could not add trace {trace_id} to endpoint {endpoint_id}') + Database.instance().record_trace_ingest_failure(endpoint_id, f'trace {trace_id}: {type(e).__name__}: {e}', + reference_semantic_change, trace_id, stack_overflow) diff --git a/symbolic-explorer/data/BinaryExecutionTree/Leaf.py b/symbolic-explorer/data/BinaryExecutionTree/Leaf.py index 517b3ef0..b87459e5 100755 --- a/symbolic-explorer/data/BinaryExecutionTree/Leaf.py +++ b/symbolic-explorer/data/BinaryExecutionTree/Leaf.py @@ -1,6 +1,13 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + from data.trace.Input import Input from data.trace.UF import UF +if TYPE_CHECKING: + from data.BinaryExecutionTree.Node import Node + GLOBAL_IID = -1 class Leaf: @@ -14,7 +21,11 @@ class Leaf: gid (int): A unique global identifier for this leaf node. """ - def __init__(self, parent: 'Node', inputs: list[Input], ufs: list[UF]) -> None: + # Whether a run ended here with an uncaught exception (see dfs: it makes the sliced branches + # whose regions it ended in relevant). A class default, also for leaves built with __new__. + abnormal: bool = False + + def __init__(self, parent: Node | None, inputs: list[Input], ufs: list[UF]) -> None: """ Initializes a new instance of the Leaf class. @@ -26,10 +37,10 @@ def __init__(self, parent: 'Node', inputs: list[Input], ufs: list[UF]) -> None: None """ global GLOBAL_IID - self.parent = parent - self.inputs = inputs - self.ufs = ufs - self.gid = GLOBAL_IID - self.id = -1 + self.parent: Node | None = parent + self.inputs: list[Input] = inputs + self.ufs: list[UF] = ufs + self.gid: int = GLOBAL_IID + self.id: int = -1 GLOBAL_IID -= 1 diff --git a/symbolic-explorer/data/BinaryExecutionTree/Node.py b/symbolic-explorer/data/BinaryExecutionTree/Node.py index d7330be6..e36200f2 100755 --- a/symbolic-explorer/data/BinaryExecutionTree/Node.py +++ b/symbolic-explorer/data/BinaryExecutionTree/Node.py @@ -1,14 +1,20 @@ -from data.BinaryExecutionTree.Leaf import Leaf -import logging as log -import json -from typing import List, Optional +from __future__ import annotations + +from typing import Literal, Union +from data.BinaryExecutionTree.Leaf import Leaf +from data.trace.Branch import Branch, PositionedState from data.trace.Input import Input -from data.trace.Special import Special +from data.trace.Special import Special, CLINIT, CLINIT_END from data.trace.UF import UF GLOBAL_IID = 0 # Global unique identifier for nodes +# How dfs names a branch in a candidate's identity: its key, or ('id', iid) if it has none. +BranchName = Union[str, tuple[str, int]] +# A candidate's identity (see dfs): the sig of the relevant decisions above it, and its name. +Ident = tuple[int, BranchName] + class Node: """ Represents a node in a binary execution tree. @@ -28,62 +34,107 @@ class Node: id (any): The ID of the branch. trace_id (any): The trace ID of the branch. kind (str): The type of the node, either 'Special' or 'Branch'. + inst (str): For 'Special' nodes, the instruction that produced the marker + (e.g. '...instruction.CLINIT'); None for 'Branch' nodes. Raises: - ValueError: If the trace argument is None or empty, indicating that the node cannot be created. + ValueError: If no trace element is given, indicating that the node cannot be created. """ - def __init__(self, parent: Optional['Node'], trace: list, inputs: List[Input], ufs: List[UF]): + # Defaults of the per-node flags, also for nodes built with Node.__new__ (tests). + kind: Literal["Branch", "Special"] = "Branch" + inst: str | None = None # only for 'Special' nodes: the marker's instruction (e.g. CLINIT) + concrete_only: bool = False # recorded only to keep the SA walk in step, see Branch + # For 'Branch' nodes, from the trace (the same on every trace through the node, since its path + # is the same): its key, the frame stack change since the previous branch, and the loop-head + # states reached since then with their positions (Tree.register_loop_states). + key: str | None = None + pops: int = 0 + pushes: tuple[str | None, ...] = () + loop_states: tuple[PositionedState, ...] = () + # In a callback from library code, or in SWAT's own helper code: no counterpart in the SA graph + callback: bool = False + choice: bool = False # added by the executor to enumerate an operand's values: not in the SA graph + merged: bool = False # merged by a loop summary on every trace through it: not a decision + merged_conditional: bool = False # ... only if the loop's other effects are irrelevant (see dfs) + # Whether some trace threw an exception the SA graph does not model right before this node + # (an UNMODELLED_EXCEPTION marker, which is not a node of its own, see Tree.structural). + unmodelled_exception: bool = False + # Set by dfs (see there): whether the last walk took this branch for no decision (sliced or + # merged), whether a sliced branch turned out relevant for good, and the candidate identity + # (the sig of the relevant decisions above it, and its key). + sliced: bool = False + poisoned: bool = False + ident: Ident | None = None + # Whether a run below this node ended with an uncaught exception (Database.mark_last_leaf_abnormal) + abnormal_below: bool = False + # Set by StrategyService.collect_query: whether the query for this branch left out the + # constraints of sliced branches. + sliced_query: bool = False + # The number of branches above this node on its path, once StrategyService.depth computed it + depth_cache: int | None = None + + def __init__(self, parent: Node | None, branch: Branch | Special, inputs: list[Input], ufs: list[UF]) -> None: """ - Initializes a new instance of the Node class. + Initializes a new instance of the Node class for one trace element. Its children are left + unset; Tree links the nodes of a trace into a path. Args: parent (Node): The parent node of this node. - trace (list): A list of trace elements defining the execution path. + branch (Branch | Special): The trace element this node stands for. inputs (any): The inputs associated with this node. ufs (any): The UFs associated with this node. """ global GLOBAL_IID - self.parent = parent - self.branched = None - self.skipped = None - self.inputs = inputs - self.ufs = ufs - self.gid = GLOBAL_IID # Assign a unique global identifier - GLOBAL_IID += 1 - self.constraint = {} - - # Validate trace argument - if trace is None or len(trace) == 0: + if branch is None: raise ValueError(f'Cannot create Node with no branch!') + self.parent: Node | None = parent + # The subtrees after the branch was taken and not taken; None: that side is unexplored. + self.branched: Node | Leaf | None = None + self.skipped: Node | Leaf | None = None + self.inputs: list[Input] = inputs + self.ufs: list[UF] = ufs + self.gid: int = GLOBAL_IID # Assign a unique global identifier + GLOBAL_IID += 1 + # The branch's constraint (SMT-LIB) per trace id that went through it + self.constraint: dict[str, str] = {} + self.id: int = branch.id + self.trace_id: str = branch.trace_id - # Process the first element of the trace to determine the branch - branch = trace.pop(0) - - # Create a child node based on remaining trace elements - if len(trace) == 0: - child = Leaf(parent=self, inputs=inputs, ufs=ufs) - else: - child = Node(parent=self, trace=trace, inputs=inputs, ufs=ufs) - - # Assign branch details to the node - self.id = branch.id - self.trace_id = branch.trace_id - - # Check if the branch is of type Special and set properties accordingly if isinstance(branch, Special): self.kind = "Special" - self.branched = child + self.inst = branch.inst else: - self.kind = "Branch" + self.concrete_only = branch.concrete_only + self.key = branch.key + self.pops = branch.pops + self.pushes = branch.pushes + self.callback = branch.callback + self.choice = branch.choice + self.merged = branch.merged + self.merged_conditional = branch.merged_conditional + self.loop_states = branch.positioned_loop_states self.constraint[branch.trace_id] = branch.constraint - # Determine whether the branch was taken or skipped and assign child - if branch.has_branched: - self.branched = child - else: - self.skipped = child - def __str__(self): + def is_clinit_start(self) -> bool: + """ + Checks whether this node marks the beginning of a static initializer (). + + Returns: + bool: True if this is the CLINIT marker of a static initializer. + """ + return self.kind == "Special" and self.inst == CLINIT + + def is_clinit_end(self) -> bool: + """ + Checks whether this node marks the end of a static initializer (). + + Returns: + bool: True if this is the INVOKECLINIT_END marker of a static initializer. + """ + return self.kind == "Special" and self.inst == CLINIT_END + + def __str__(self) -> str: """ Returns a string representation of the node. diff --git a/symbolic-explorer/data/BinaryExecutionTree/Tree.py b/symbolic-explorer/data/BinaryExecutionTree/Tree.py index 6636395e..1dde6926 100755 --- a/symbolic-explorer/data/BinaryExecutionTree/Tree.py +++ b/symbolic-explorer/data/BinaryExecutionTree/Tree.py @@ -1,16 +1,21 @@ -from typing import List, Optional, Set, Union -from data.trace.Branch import Branch -from data.trace.Special import Special +from __future__ import annotations + +from collections import Counter +from typing import Any, Optional, Union +from data.trace.Branch import Branch, Position, PositionedState +from data.trace.Special import Special, UNMODELLED_EXCEPTION from data.BinaryExecutionTree.Leaf import Leaf -from data.BinaryExecutionTree.Node import Node +from data.BinaryExecutionTree.Node import Ident, Node from data.trace.Input import Input from data.trace.UF import UF import log logger = log.get_logger() -#import pygraphviz as pgv class Tree: + # Whether dfs() leaves out the branches below a repeated loop-head state (--no-loop-state-matching). + LOOP_STATE_MATCHING = True + """ Represents a binary execution tree used to model decision points in an execution flow. @@ -22,24 +27,60 @@ class Tree: endpoint_id (any): An identifier for the endpoint associated with this tree. """ - def __init__(self, endpoint_id: Union[str, int]): + def __init__(self, endpoint_id: Union[str, int, None] = None) -> None: """ Initializes a new instance of the Tree class. Args: endpoint_id (any): An identifier for the endpoint this tree is associated with. """ - self.root = None + self.root: Node | Leaf | None = None self.endpoint_id = endpoint_id self.symbolic_context_loss = False self.symbolic_precision_loss = False self.reference_semantic_change = False + # Traces that used an approximate model (TraceDTO.approximateModel). A solution built from one + # is a guess, so the next run may take another path than predicted without anything being wrong. + self.approximate_traces: set[str] = set() + # Traces whose execution had a StackOverflowError reach the target's code: their outcome + # depends on the JVM's stack size, so a violation they show is not a verdict. + self.stack_overflow_traces: set[str] = set() + # How often the next run did take another path than such a guessed solution predicted. + self.expected_divergences: int = 0 self.uncaught_exceptions: int = 0 - self.symbolic_vars: Set = set() - self.ufs: Set = set() + self.symbolic_vars: set[str] = set() # the names of the inputs + self.ufs: set[str] = set() # the definitions of the uninterpreted functions + # Missing invocations accumulated across all traces of this testcase, keyed by + # (owner, name, desc, isInstance, isSymbolic). Each value aggregates count and context_loss. + self.missing_invocations: dict[tuple[str, str, str, bool, bool], dict[str, Any]] = {} + # Execution errors detected in the executor's stdout (internal SWAT assertions or + # [SWAT Exception]s) that make the verdict untrustworthy. These cannot ride the trace + # because they halt the executor JVM before it sends one, so they are surfaced here. + # Each is {'kind': ..., 'message': ...} (see record_execution_error). + self.execution_errors: list[dict[str, str]] = [] + # Loop-head state matching: for each state hash, the smallest position at which an + # executed path reached it (see register_loop_states). + self.loop_state_first: dict[str, Position] = {} + # Relevance (see dfs): the interned sigs of sequences of relevant decisions, the sides the + # last walk found covered (explored, solved or infeasible) per (sig, branch key), and how + # often a walk made a sliced branch relevant again (poisoned it). + self.sig_ids: dict[tuple[Ident, bool], int] = {} + self.covered: set[tuple[Ident, bool]] = set() + self.poisoned_total = 0 + self.begin_walk() + # The leaf the last added trace ended at, so that the run's end can be recorded on it + self.last_leaf: Optional[Leaf] = None + + def begin_walk(self) -> None: + """Resets the counts of the last dfs() walk (see dfs and DFS.describe_walk): its branches + by how they were paired with the SA graph, pruned sides, sliced and merged branches, + candidates covered by a path with the same relevant decisions, sliced branches it found + relevant (poisoned: then the caller walks again), desyncs, and subtrees left out below a + repeated loop-head state.""" + self.walk: Counter[str] = Counter() - def record_inputs(self, inputs: List[Input]): + def record_inputs(self, inputs: list[Input]) -> None: """ Records the inputs associated with the tree. @@ -49,7 +90,7 @@ def record_inputs(self, inputs: List[Input]): for input in inputs: self.symbolic_vars.add(input.name) - def record_ufs(self, ufs: List[UF]): + def record_ufs(self, ufs: list[UF]) -> None: """ Records the UFs associated with the tree. @@ -60,78 +101,231 @@ def record_ufs(self, ufs: List[UF]): self.ufs.add(uf.definition) - def record_context_loss(self): + def record_missing_invocations(self, invocations: Optional[list[dict[str, Any]]]) -> None: + """ + Accumulates missing invocations across all traces of this testcase. + + Args: + invocations (list): Dicts with keys owner, name, desc, isInstance, isSymbolic, + contextLoss, count. Entries are deduped by + (owner, name, desc, isInstance, isSymbolic); counts are summed and contextLoss + is OR-ed across traces. The full set is the superset of missing invocations; + entries with context_loss=True form the context-loss subset. + """ + if not invocations: + return + for inv in invocations: + key = (inv['owner'], inv['name'], inv['desc'], inv['isInstance'], inv['isSymbolic']) + count = int(inv.get('count', 1)) + context_loss = bool(inv.get('contextLoss', False)) + existing = self.missing_invocations.get(key) + if existing is None: + self.missing_invocations[key] = { + 'owner': inv['owner'], + 'name': inv['name'], + 'desc': inv['desc'], + 'isInstance': inv['isInstance'], + 'isSymbolic': inv['isSymbolic'], + 'context_loss': context_loss, + 'count': count, + } + else: + existing['count'] += count + existing['context_loss'] = existing['context_loss'] or context_loss + + def record_execution_error(self, kind: str, message: str) -> None: + """ + Records an execution error surfaced from the executor's stdout. + + Deduplicated by (kind, message) so repeated exploration rounds don't inflate the list. + + Args: + kind (str): A short category, e.g. "swat_assertion" or "swat_exception". + message (str): The offending output line. + """ + entry = {'kind': kind, 'message': message} + if entry not in self.execution_errors: + self.execution_errors.append(entry) + + def record_context_loss(self) -> None: logger.warning("Context loss recorded!") self.symbolic_context_loss = True - def record_precision_loss(self): + def record_precision_loss(self) -> None: logger.warning("Precision loss recorded!") self.symbolic_precision_loss = True - def record_reference_semantic_change(self): + def record_reference_semantic_change(self) -> None: logger.warning("Reference semantic change recorded: user-de-interned strings compared via Objects.equals") self.reference_semantic_change = True - def add(self, trace: list[Branch | Special], inputs: List[Input], ufs: List[UF]): + @staticmethod + def structural(trace: list[Branch | Special]) -> list[tuple[Branch | Special, bool]]: + """ + The elements of a trace that are levels of the tree, each with whether an + UNMODELLED_EXCEPTION marker came right before it. + + The marker is only in the trace of a run that throws, so it cannot be a level of its own: + a run that does not throw has some other element at that depth. It is kept on the node + after it instead, for the SA walk (see dfs). """ - Adds a branch to the tree based on the provided trace and inputs. + elements = [] + marked = False + for element in trace: + if getattr(element, 'inst', None) == UNMODELLED_EXCEPTION: + marked = True + continue + elements.append((element, marked)) + marked = False + return elements + + def add(self, trace: list[Branch | Special], inputs: list[Input], ufs: list[UF]) -> bool: + """ + Adds a trace to the tree: follows its path as far as the tree has it already, and appends + the rest as a new path. + + If the trace has another element than the tree at some depth, the two runs did the same + up to there and then differently without a branch to tell them apart. The tree keeps what + it has, the rest of the trace is dropped, and a 'tree_mismatch' execution error records + that the search is no longer complete. Args: trace (list): A list of trace elements defining the execution path. inputs (any): The inputs associated with the branch being added. ufs (any): The UFs associated with the branch being added. - """ - self.root = self.add_recursive(None, self.root, trace.copy(), inputs, ufs) - def add_recursive(self, parent: Optional[Node], node: Optional[Union[Node, Leaf]], trace: list[Branch | Special], inputs: List[Input], ufs: List[UF]): + Returns: + bool: Whether the whole trace fitted the tree. """ - Recursively adds nodes or leaves to the tree. + self.register_loop_states(trace) + elements = self.structural(trace) + parent: Optional[Node] = None + branched = False # the side of parent that node is on + node = self.root + i = 0 + while i < len(elements) and isinstance(node, Node): + element, marked = elements[i] + if node.id != element.id or (node.kind == "Special") != isinstance(element, Special): + message = f'depth {i}: tree has {node.kind} {node.id}, trace {element}' + logger.warning(f"Trace {getattr(element, 'trace_id', '?')} does not fit the tree ({message}), dropping its rest") + self.record_execution_error('tree_mismatch', message) + return False + if marked: + node.unmodelled_exception = True + if isinstance(element, Branch): + node.constraint[element.trace_id] = element.constraint + # Merged only if every trace through it merged it (a summary the executor dropped + # leaves the constraint in force) + if not element.merged: + node.merged = False + elif element.merged_conditional: + node.merged_conditional = True + if element.has_branched: + if node.branched is None and node.skipped is not None: + logger.info(f"New branch from node {node.id} (branched)") + else: + if node.skipped is None and node.branched is not None: + logger.info(f"New branch from node {node.id} (skipped)") + parent, branched = node, element.has_branched + node = node.branched if branched else node.skipped + i += 1 - Args: - parent (Node): The parent node of the current node. - node (Node/Leaf): The current node to add to. - trace (list): The remaining trace elements. - inputs (any): The inputs associated with the current node. - ufs (any): The UFs associated with the current node. + if i == len(elements) and node is not None: + if isinstance(node, Leaf): + self.last_leaf = node + return True # the path is in the tree already - Returns: - Node/Leaf: The newly added or modified node or leaf. + # node is None or a Leaf: the rest of the trace is a new path, ending in a Leaf + for element, marked in elements[i:]: + new = Node(parent, element, inputs, ufs) + new.unmodelled_exception = marked + self._attach(parent, branched, new) + parent, branched = new, element.has_branched + leaf = Leaf(parent, inputs, ufs) + self._attach(parent, branched, leaf) + self.last_leaf = leaf + return True - Raises: - ValueError: If branch IDs in the trace don't match the node's ID. + def register_loop_states(self, trace: list[Branch | Special]) -> None: """ - if node is None: - # Create a new Node or Leaf if the current node is None - return Node(parent, trace, inputs, ufs) if len(trace) > 0 else Leaf(parent, inputs, ufs) + Positions the loop-head states of a trace and records the smallest position of each. - if len(trace) > 0: + The position of a state is (d, k, m): d branches precede it, k is the outcomes of those + branches read as a binary number (taken = 1, the first branch the most significant bit), and + m states precede it. Positions compare lexicographically. The state goes with the branch + that follows it: that branch, and every branch below it, needs no flip if the same state was + reached at a smaller position (see dominated_by_loop_state and the executor's + LoopStateMatcher for why). Two paths through the same tree node have the same prefix, so + the node's positions do not depend on the trace that created it. + """ + d, k, m = 0, 0, 0 + for element in trace: + if not isinstance(element, Branch): + continue + states = element.loop_states + if states: + positioned: list[PositionedState] = [] + for state in states: + position = (d, k, m) + m += 1 + positioned.append((state, position)) + first = self.loop_state_first.get(state) + if first is None or position < first: + self.loop_state_first[state] = position + element.positioned_loop_states = tuple(positioned) + d += 1 + k = 2 * k + (1 if element.has_branched else 0) + + def dominated_by_loop_state(self, node: Node) -> bool: + """Whether a state reached before this branch was reached at a smaller position already.""" + if not Tree.LOOP_STATE_MATCHING: + return False + for state, position in node.loop_states: + if self.loop_state_first.get(state, position) < position: + return True + return False + + def _attach(self, parent: Optional[Node], branched: bool, child: Union[Node, Leaf]) -> None: + """Makes child the root, or the given side of parent.""" + if parent is None: + self.root = child + elif branched: + parent.branched = child + else: + parent.skipped = child + + def __deepcopy__(self, memo: dict[int, Any]) -> Tree: + """ + Copies the tree without recursing along its paths, which can be longer than the + interpreter's recursion limit allows. + """ + import copy + cls = self.__class__ + result = cls.__new__(cls) + memo[id(self)] = result + # Copy the nodes shallowly first, so that copying their other attributes below does not + # descend into the tree. + nodes: list[Node | Leaf] = [] + work: list[Node | Leaf | None] = [self.root] + while work: + node = work.pop() + if node is None or id(node) in memo: + continue + memo[id(node)] = copy.copy(node) + nodes.append(node) if isinstance(node, Node): - new_node = trace.pop(0) - - # Ensure branch IDs match - if node.id != new_node.id: - raise ValueError(f'Branch id\'s dont match: {node.id} : {new_node.id}') - - # Update constraints for the node - if isinstance(new_node, Branch): - node.constraint[new_node.trace_id] = new_node.constraint - - # Recurse into the correct branch based on the trace - if new_node.has_branched: - if node.branched is None and node.skipped is not None: - logger.info(f"New branch from node {node.id} (branched)") - node.branched = self.add_recursive(node, node.branched, trace, inputs, ufs) - else: - if node.skipped is None and node.branched is not None: - logger.info(f"New branch from node {node.id} (skipped)") - node.skipped = self.add_recursive(node, node.skipped, trace, inputs, ufs) - else: - # Create a new Node or Leaf if the current node is a Leaf - return Node(parent, trace, inputs, ufs) if len(trace) > 0 else Leaf(parent, inputs, ufs) + work.append(node.skipped) + work.append(node.branched) + for node in nodes: + new = memo[id(node)] + for name, value in node.__dict__.items(): + new.__dict__[name] = copy.deepcopy(value, memo) + for name, value in self.__dict__.items(): + result.__dict__[name] = copy.deepcopy(value, memo) + return result - return node - def get_constraint_label(self, parent: Node, node: Union[Node, Leaf]): - return None +# VISUALIZATIONS + def get_constraint_label(self, parent: Node, node: Union[Node, Leaf]) -> str: """Get the constraint label for an edge.""" if isinstance(node, Leaf): return "" @@ -139,10 +333,13 @@ def get_constraint_label(self, parent: Node, node: Union[Node, Leaf]): return str(parent.constraint[node.trace_id]) return "" - def add_to_dot(self, node: Optional[Union[Node, Leaf]], graph, parent: Optional[Node] = None): - return None - """Recursively add nodes and edges to the DOT graph.""" - if node is not None: + def add_to_dot(self, node: Optional[Union[Node, Leaf]], graph: Any, parent: Optional[Node] = None) -> None: + """Add the nodes and edges below node to the DOT graph (a pygraphviz AGraph).""" + work: list[tuple[Node | Leaf | None, Node | None]] = [(node, parent)] + while work: + node, parent = work.pop() + if node is None: + continue graph.add_node(node.gid, label=str(node.gid) + ':' + str(node.id)) if parent is not None: # Add an edge with constraint as a label @@ -150,14 +347,14 @@ def add_to_dot(self, node: Optional[Union[Node, Leaf]], graph, parent: Optional[ graph.add_edge(parent.gid, node.gid, label=constraint_label) if isinstance(node, Node): - self.add_to_dot(node.branched, graph, node) - self.add_to_dot(node.skipped, graph, node) - -# def plot_tree(self, idx): -# return None -# """Plot the tree using Graphviz and save to a file.""" -# #log.info(self.to_string()) -# G = pgv.AGraph(directed=True, strict=True, rankdir='TB') -# self.add_to_dot(self.root, G) -# G.layout(prog="dot") -# G.draw(f"tree_{idx}.png") + work.append((node.skipped, node)) + work.append((node.branched, node)) + + def plot_tree(self, idx: int) -> None: + """Plot the tree using Graphviz and save to a file.""" + #log.info(self.to_string()) + import pygraphviz as pgv + G = pgv.AGraph(directed=True, strict=True, rankdir='TB') + self.add_to_dot(self.root, G) + G.layout(prog="dot") + G.draw(f"tree_{idx}.png") diff --git a/symbolic-explorer/data/Database.py b/symbolic-explorer/data/Database.py index e4866a8c..fa334542 100755 --- a/symbolic-explorer/data/Database.py +++ b/symbolic-explorer/data/Database.py @@ -1,7 +1,8 @@ import threading import copy -from typing import Any, List, Union +from typing import AbstractSet, Any, Callable, List, TypeVar, Union +from data.BinaryExecutionTree.Node import Ident from data.BinaryExecutionTree.Tree import Tree from data.Solution.Solution import Solution from driver.SymbolicStorage import SymbolicVar @@ -10,6 +11,8 @@ from data.trace.Special import Special from data.trace.Branch import Branch +T = TypeVar('T') + lock = threading.Lock() @@ -26,59 +29,62 @@ def instance(): def __init__(self): - lock.acquire() - - if Database.__instance: - lock.release() - raise Exception("Attempting to create a new singleton instance.") - - self.tree = {} - self.constraints = {} - self.solutions = {} - self.new_solutions = [] - self.unsat_branches = [] - self.endpoints = list() - self.traces: list[list[Branch | Special]] = list() - self.instr_coverage = {'total_executed': {}, 'total_instr_count': 0, 'endpoints': {}} - self.branch_coverage = {'total_executed': {}, 'total_branch_instr_count': 0} - self.violations = {} - self.optimize_solutions = False # Whether to use Z3 Optimize() to minimize values - self.log_smt_formulas = False # Whether to log SMT formulas and models to disk for debugging - - lock.release() + with lock: + if Database.__instance: + raise Exception("Attempting to create a new singleton instance.") + + self.tree = {} + self.constraints = {} + self.solutions = {} + self.new_solutions = [] + self.unsat_branches = [] + # Branches whose solutions repeatedly did not lead the run to them, given up on (by gid) + self.retired_branches = set() + self.endpoints = list() + self.traces: list[list[Branch | Special]] = list() + self.instr_coverage = {'total_executed': {}, 'total_instr_count': 0, 'endpoints': {}} + self.branch_coverage = {'total_executed': {}, 'total_branch_instr_count': 0} + self.violations = {} + # Witness data (nondet returns, uncaught throwable) of the latest trace per endpoint + self.witness_data = {} + self.optimize_solutions = False # Whether to use Z3 Optimize() to minimize values + self.log_smt_formulas = False # Whether to log SMT formulas and models to disk for debugging + # Whether queries with floating-point terms go to Z3's qffp tactic first (see Z3Handler.check_query) + self.fp_tactic = True + self.prefer_short_strings = False # Whether to shrink the string inputs of a model (Z3Handler.shorten_strings) + # A long-running target (http mode): static initializers are taken out of the traces, + # see data.trace.StaticInitializers + self.strip_static_initializers = False def reset(self): - lock.acquire() - - self.tree = {} - self.constraints = {} - self.solutions = {} - self.new_solutions = [] - self.unsat_branches = [] - self.endpoints = list() - self.traces: list[list[Branch | Special]] = list() - self.instr_coverage = {'total_executed': {}, 'total_instr_count': 0, 'endpoints': {}} - self.branch_coverage = {'total_executed': {}, 'total_branch_instr_count': 0} - self.violations = {} - - lock.release() + with lock: + self.tree = {} + self.constraints = {} + self.solutions = {} + self.new_solutions = [] + self.unsat_branches = [] + # Branches whose solutions repeatedly did not lead the run to them, given up on (by gid) + self.retired_branches = set() + self.endpoints = list() + self.traces: list[list[Branch | Special]] = list() + self.instr_coverage = {'total_executed': {}, 'total_instr_count': 0, 'endpoints': {}} + self.branch_coverage = {'total_executed': {}, 'total_branch_instr_count': 0} + self.violations = {} + self.witness_data = {} def add_constraints(self, trace_id: str, constraints: dict): - lock.acquire() - self.constraints[trace_id] = constraints - lock.release() + with lock: + self.constraints[trace_id] = constraints def get_constraints(self, trace_id: str): - lock.acquire() - ret = copy.deepcopy(self.constraints[trace_id]) - lock.release() + with lock: + ret = copy.deepcopy(self.constraints[trace_id]) return ret def add_violation(self, endpoint_id: int, sym_vars: List[SymbolicVar]): - lock.acquire() - self._add_violation(endpoint_id, sym_vars) - lock.release() + with lock: + self._add_violation(endpoint_id, sym_vars) def _add_violation(self, endpoint_id: str | int, sym_vars: List[SymbolicVar]): endpoint_id = str(endpoint_id) @@ -88,17 +94,15 @@ def _add_violation(self, endpoint_id: str | int, sym_vars: List[SymbolicVar]): self.violations[endpoint_id].append(sym_vars) def get_violations(self, endpoint_id: int): - lock.acquire() - if endpoint_id not in self.violations: - return [] - violations = copy.deepcopy(self.violations[endpoint_id]) - lock.release() + with lock: + if endpoint_id not in self.violations: + return [] + violations = copy.deepcopy(self.violations[endpoint_id]) return violations def add_endpoint(self, endpoint_id: Union[str, int]): - lock.acquire() - self._add_endpoint(endpoint_id) - lock.release() + with lock: + self._add_endpoint(endpoint_id) def _add_endpoint(self, endpoint_id: Union[str, int]): endpoint_id = str(endpoint_id) @@ -107,168 +111,280 @@ def _add_endpoint(self, endpoint_id: Union[str, int]): self.tree[endpoint_id] = Tree(endpoint_id=endpoint_id) def get_endpoints(self): - lock.acquire() - endpoints = copy.deepcopy(self.endpoints) - lock.release() + with lock: + endpoints = copy.deepcopy(self.endpoints) return endpoints def add_trace(self, endpoint_id: Union[str, int], trace_id: str, trace: list[Branch | Special], inputs: List[Input], ufs: List[UF], symbolic_context_loss: bool, symbolic_precision_loss: bool, - reference_semantic_change: bool = False): + reference_semantic_change: bool = False, missing_invocations: list = None, + witness_data: dict = None, + approximate_model: bool = False, + stack_overflow: bool = False): endpoint_id = str(endpoint_id) - - lock.acquire() - - self._add_endpoint(endpoint_id) - self.traces.append(trace) - self.tree[endpoint_id].add(trace, inputs, ufs) - self.tree[endpoint_id].record_inputs(inputs) - self.tree[endpoint_id].record_ufs(ufs) - self.tree[endpoint_id].record_context_loss() if symbolic_context_loss else None - self.tree[endpoint_id].record_precision_loss() if symbolic_precision_loss else None - self.tree[endpoint_id].record_reference_semantic_change() if reference_semantic_change else None - - lock.release() + if self.strip_static_initializers: + from data.trace.StaticInitializers import strip_static_initializers + trace = strip_static_initializers(trace) + + with lock: + self._add_endpoint(endpoint_id) + tree = self.tree[endpoint_id] + # The soundness flags go first: they must hold even if adding the trace to the tree fails. + tree.record_context_loss() if symbolic_context_loss else None + tree.record_precision_loss() if symbolic_precision_loss else None + tree.record_reference_semantic_change() if reference_semantic_change else None + tree.approximate_traces.add(trace_id) if approximate_model else None + tree.stack_overflow_traces.add(trace_id) if stack_overflow else None + tree.record_missing_invocations(missing_invocations) + tree.record_inputs(inputs) + tree.record_ufs(ufs) + self.traces.append(trace) + if witness_data is not None: + self.witness_data[endpoint_id] = witness_data + tree.add(trace, inputs, ufs) + + def record_trace_ingest_failure(self, endpoint_id: Union[str, int], message: str, + reference_semantic_change: bool = False, trace_id: str = None, + stack_overflow: bool = False): + """Records that a trace the executor sent could not be added: the tree lacks its path, so + the search on it is incomplete. A reference semantic change it reported still counts, and so + does a stack overflow: the trace becomes the latest one, so that the driver does not report + a violation of that execution (see SVCompDriver.round_has_stack_overflow).""" + endpoint_id = str(endpoint_id) + with lock: + self._add_endpoint(endpoint_id) + self.tree[endpoint_id].record_execution_error('trace_ingest_failed', message) + if reference_semantic_change: + self.tree[endpoint_id].record_reference_semantic_change() + if stack_overflow and trace_id is not None: + self.tree[endpoint_id].stack_overflow_traces.add(trace_id) + self.witness_data[endpoint_id] = {'trace_id': trace_id, 'nondet_returns': [], 'violation': None} + + def trace_count(self) -> int: + """How many traces arrived so far.""" + with lock: + return len(self.traces) def get_traces(self): + with lock: + ret = copy.deepcopy(self.traces) + return ret + + def trace_count(self) -> int: + """The number of traces received so far, over all endpoints.""" lock.acquire() - ret = copy.deepcopy(self.traces) + ret = len(self.traces) lock.release() return ret def get_trace(self, idx: int): - lock.acquire() - ret = copy.deepcopy(self.traces[idx]) - lock.release() + with lock: + ret = copy.deepcopy(self.traces[idx]) return ret def get_tree(self, endpoint_id: Union[str, int]) -> Tree: endpoint_id = str(endpoint_id) - lock.acquire() - ret = copy.deepcopy(self.tree[endpoint_id]) - lock.release() + with lock: + ret = copy.deepcopy(self.tree[endpoint_id]) return ret + def visit_tree(self, endpoint_id: Union[str, int], visit: Callable[[Tree, AbstractSet[int], AbstractSet[int]], T]) -> T: + """Returns visit(tree, solved, unsat) for the endpoint's tree, with the gids of the solved + (or retired) and of the unsat branches, all under the lock and without copying: a copy of + the whole tree in every round costs more than the walk itself. visit must not keep the + sets, and must not call back into the Database (the lock is held).""" + endpoint_id = str(endpoint_id) + with lock: + return visit(self.tree[endpoint_id], self.solutions.keys() | self.retired_branches, + set(self.unsat_branches)) + + def count_symbolic_vars(self, endpoint_id: Union[str, int]) -> int: + endpoint_id = str(endpoint_id) + with lock: + ret = len(self.tree[endpoint_id].symbolic_vars) + return ret + + def get_execution_errors(self, endpoint_id: Union[str, int], kind: str) -> list[dict]: + """The execution errors of the endpoint's tree of the given kind, without copying the tree.""" + endpoint_id = str(endpoint_id) + with lock: + ret = [dict(e) for e in self.tree[endpoint_id].execution_errors if e.get('kind') == kind] + return ret + + def get_witness_data(self, endpoint_id: Union[str, int]) -> dict | None: + """The witness data of the latest trace of the endpoint (None if no trace arrived since the last clear).""" + endpoint_id = str(endpoint_id) + with lock: + ret = copy.deepcopy(self.witness_data.get(endpoint_id)) + return ret + + def clear_witness_data(self, endpoint_id: Union[str, int]): + """Forgets the witness data of the endpoint, so that only a trace of the next execution can provide it.""" + endpoint_id = str(endpoint_id) + with lock: + self.witness_data.pop(endpoint_id, None) + def record_uncaught_exception(self, endpoint_id: Union[str, int]): endpoint_id = str(endpoint_id) - lock.acquire() - self.tree[endpoint_id].uncaught_exceptions += 1 - lock.release() + with lock: + self.tree[endpoint_id].uncaught_exceptions += 1 + + def mark_last_leaf_abnormal(self, endpoint_id: Union[str, int]) -> None: + """Records that the run of the last added trace ended with an uncaught exception.""" + endpoint_id = str(endpoint_id) + with lock: + leaf = self.tree[endpoint_id].last_leaf + if leaf is not None: + leaf.abnormal = True + # So that dfs finds it also below a side it does not walk otherwise + node = leaf.parent + while node is not None and not node.abnormal_below: + node.abnormal_below = True + node = node.parent + + def projected_covered(self, endpoint_id: Union[str, int], ident: Ident, branched: bool) -> bool: + """Whether the last walk found the side of the branch `ident` (a candidate's sig and key, + see dfs) explored by some path with the same relevant decisions.""" + endpoint_id = str(endpoint_id) + with lock: + return (ident, branched) in self.tree[endpoint_id].covered + + def is_approximate_trace(self, endpoint_id: Union[str, int], trace_id: str) -> bool: + """Whether the trace used an approximate model, so solutions built from it are guesses.""" + endpoint_id = str(endpoint_id) + with lock: + ret = trace_id in self.tree[endpoint_id].approximate_traces + return ret + + def is_stack_overflow_trace(self, endpoint_id: Union[str, int], trace_id: str) -> bool: + """Whether a StackOverflowError reached the target's code in the execution of the trace.""" + endpoint_id = str(endpoint_id) + with lock: + ret = trace_id in self.tree[endpoint_id].stack_overflow_traces + return ret + + def record_expected_divergence(self, endpoint_id: Union[str, int]): + endpoint_id = str(endpoint_id) + with lock: + self.tree[endpoint_id].expected_divergences += 1 + + def record_execution_error(self, endpoint_id: Union[str, int], kind: str, message: str) -> None: + endpoint_id = str(endpoint_id) + with lock: + self._add_endpoint(endpoint_id) + self.tree[endpoint_id].record_execution_error(kind, message) def add_solution(self, branch_id: int, sol: Any, inputs: List[Input], endpoint_id: Union[str, int]): - lock.acquire() - self.solutions[branch_id] = Solution(sol=sol, inputs=inputs, endpoint_id=endpoint_id) - self.new_solutions.append(branch_id) - lock.release() + with lock: + self.solutions[branch_id] = Solution(sol=sol, inputs=inputs, endpoint_id=endpoint_id) + self.new_solutions.append(branch_id) def get_solutions(self): - lock.acquire() - ret = copy.deepcopy(self.solutions) - lock.release() + with lock: + ret = copy.deepcopy(self.solutions) return ret def consume_new_solution(self, branch_id: int): # raise Exception("CAREFUL! Switched from node.id to node.gid, untested here") - lock.acquire() - - solution = copy.deepcopy(self.solutions[branch_id]) - self.new_solutions.remove(branch_id) + with lock: + solution = copy.deepcopy(self.solutions[branch_id]) + self.new_solutions.remove(branch_id) - lock.release() return solution def get_new_solutions(self): - lock.acquire() - ret = copy.deepcopy(self.new_solutions) - lock.release() + with lock: + ret = copy.deepcopy(self.new_solutions) return ret def clear_new_solutions(self): - lock.acquire() - self.new_solutions.clear() - lock.release() + with lock: + self.new_solutions.clear() + + def drop_solution(self, branch_id: int): + """Forgets the solution of the branch, so that it becomes a candidate again.""" + with lock: + self.solutions.pop(branch_id, None) + if branch_id in self.new_solutions: + self.new_solutions.remove(branch_id) + + def retire_branch(self, branch_id: int): + """Takes the branch out of the candidates for good, without claiming it is infeasible.""" + with lock: + self.retired_branches.add(branch_id) + + def get_retired_branches(self): + with lock: + ret = set(self.retired_branches) + return ret def add_unsat_branch(self, branch_id: int): - lock.acquire() - self.unsat_branches.append(branch_id) - lock.release() + with lock: + self.unsat_branches.append(branch_id) def get_unsat_branches(self): - lock.acquire() - ret = copy.deepcopy(self.unsat_branches) - lock.release() + with lock: + ret = copy.deepcopy(self.unsat_branches) return ret def add_branch_coverage(self, ids: List[int], total_branch_instr_count: int): - lock.acquire() + with lock: + for _id in ids: + if _id in self.branch_coverage['total_executed']: + self.branch_coverage['total_executed'][_id] += 1 + else: + self.branch_coverage['total_executed'][_id] = 1 - for _id in ids: - if _id in self.branch_coverage['total_executed']: - self.branch_coverage['total_executed'][_id] += 1 - else: - self.branch_coverage['total_executed'][_id] = 1 - - self.branch_coverage['total_branch_instr_count'] = total_branch_instr_count - - lock.release() + self.branch_coverage['total_branch_instr_count'] = total_branch_instr_count def get_branch_coverage(self): - lock.acquire() - - ret = { - 'executed_branches': len(self.branch_coverage['total_executed']), - 'total_branch_count': self.branch_coverage['total_branch_instr_count'] - } + with lock: + ret = { + 'executed_branches': len(self.branch_coverage['total_executed']), + 'total_branch_count': self.branch_coverage['total_branch_instr_count'] + } - lock.release() return ret def add_instr_coverage(self, endpoint_id: Union[str, int], ids: List[int], total_instr_count: int): - lock.acquire() + with lock: + if endpoint_id not in self.instr_coverage['endpoints'].keys(): + self.instr_coverage['endpoints'][endpoint_id] = {} - if endpoint_id not in self.instr_coverage['endpoints'].keys(): - self.instr_coverage['endpoints'][endpoint_id] = {} + for _id in ids: + if _id in self.instr_coverage['total_executed']: + self.instr_coverage['total_executed'][_id] += 1 + else: + self.instr_coverage['total_executed'][_id] = 1 - for _id in ids: - if _id in self.instr_coverage['total_executed']: - self.instr_coverage['total_executed'][_id] += 1 - else: - self.instr_coverage['total_executed'][_id] = 1 + if _id in self.instr_coverage: + self.instr_coverage['endpoints'][endpoint_id][_id] += 1 + else: + self.instr_coverage['endpoints'][endpoint_id][_id] = 1 - if _id in self.instr_coverage: - self.instr_coverage['endpoints'][endpoint_id][_id] += 1 - else: - self.instr_coverage['endpoints'][endpoint_id][_id] = 1 - - self.instr_coverage['total_instr_count'] = total_instr_count - - lock.release() + self.instr_coverage['total_instr_count'] = total_instr_count def get_total_instr_coverage(self): - lock.acquire() - - ret = { - 'executed_instr': len(self.instr_coverage['total_executed']), - 'total_instr_count': self.instr_coverage['total_instr_count'] - } + with lock: + ret = { + 'executed_instr': len(self.instr_coverage['total_executed']), + 'total_instr_count': self.instr_coverage['total_instr_count'] + } - lock.release() return ret def get_endpoint_instr_coverage(self, endpoint_id: Union[str, int]): - lock.acquire() - - if endpoint_id in self.instr_coverage['endpoints'].keys(): - ret = len(self.instr_coverage['endpoints'][endpoint_id]) - else: - ret = 0 + with lock: + if endpoint_id in self.instr_coverage['endpoints'].keys(): + ret = len(self.instr_coverage['endpoints'][endpoint_id]) + else: + ret = 0 - lock.release() return ret diff --git a/symbolic-explorer/data/StaticAnalysisGraph/Relevance.py b/symbolic-explorer/data/StaticAnalysisGraph/Relevance.py new file mode 100644 index 00000000..41ad9972 --- /dev/null +++ b/symbolic-explorer/data/StaticAnalysisGraph/Relevance.py @@ -0,0 +1,490 @@ +""" +Relevance: which branches of the SA graph can affect a target point (a static slice). + +A branch is *irrelevant* if neither of its outcomes can change whether a target point is reached, +nor any value that a target point or a relevant branch reads. Then flipping it cannot lead to a +violation that keeping it cannot, and its constraint does not have to hold for one either: SWAT +does not flip it, leaves its constraint out of every query (StrategyService.collect_path_constrains) +and identifies explored paths by their relevant decisions only (dfs). This is path slicing (Jhala +and Majumdar, PLDI 2005) with the slice computed statically. + +What "cannot change" means here: both outcomes lead, within the branch's method, to its immediate +post-dominator, and the code in between (its *region*) contains no target point, cannot end the +program, writes no relevant location and contains no relevant branch. Then every execution leaves +the region with the same relevant state either way, and the rest runs the same. + +Over-approximations that keep this sound: + +* Locations come from the extractor's data-flow facts (cfg-extraction `DataflowFacts`): locals per + method, fields by name and type (no points-to analysis), array elements by element kind, global + state such as the input cursor and library state. The slice is flow- and context-insensitive. +* Post-dominance ignores paths that cannot end normally: an exception that no handler can catch + harmfully (one that can reach a target, return normally, end the program or rethrow something + harmful) ends the program with an uncaught exception, which SWAT reports, and which rules out + SAFE (SVCompDriver). A harmful exception that may leave the method makes the branch relevant. +* A region whose code does not terminate makes its executions time out, which rules out SAFE too. + +What the static analysis cannot see -- a callback from library code, a static initializer, a branch +the executor adds (choice branches), an exception the graph does not model -- is caught in dfs, +which makes an irrelevant branch relevant again (poisons it) when such a thing happens inside its +region on some path, or when a path ends abnormally inside it. +""" +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any, Final, Iterable, Iterator, Literal, Optional, Union + +from data.StaticAnalysisGraph.SAGraph import CallStack, ExceptionChain, Location, SANode, is_pseudo + +ANY: Final = "*" + +# Virtual nodes of a method's control-flow graph (see _postdominators): its normal exit, its +# harmful exceptional exit, and the end both lead to. +EXIT: Final = "EXIT" +EXIT_EXC: Final = "EXIT_EXC" +END: Final = "END" +# A node of a method's control-flow graph: a graph node, or a virtual one. +CfgNode = Union[SANode, Literal["EXIT", "EXIT_EXC", "END"]] +# A method by its signature; None for nodes without one (SANode.method). +Method = Optional[str] + + +def is_local(location: Location) -> bool: + return location.startswith("l:") or location.startswith("p:") or location == "ret" + + +@dataclass(eq=False) +class RegionFacts: + """What the region of one branch contains (see the module comment).""" + nodes: frozenset[SANode] # the nodes of the region, in the branch's own frame + writes: frozenset[Location] # locations it may write (scoped), callees included + reaches_target: bool # contains a target point, or calls code that may reach one + terminates: bool # may end the program + branches: tuple[SANode, ...] # the branch nodes in the region (same frame) + callee_methods: frozenset[Method] # the methods called in the region + + +@dataclass +class RelevanceStats: + branches: int = 0 + irrelevant: int = 0 + no_postdominator: int = 0 + all_relevant: bool = False + iterations: int = 0 + + def as_dict(self) -> dict[str, Any]: + return dict(branches=self.branches, irrelevant=self.irrelevant, + no_postdominator=self.no_postdominator, all_relevant=self.all_relevant, + iterations=self.iterations) + + +class Relevance: + """Computes SANode.irrelevant, summary_ok and region for every branch of a loaded graph.""" + + def __init__(self, nodes: Iterable[SANode]) -> None: + self.nodes = list(nodes) + self.stats = RelevanceStats() + self.by_method: dict[str, list[SANode]] = {} + for n in self.nodes: + if n.method is not None: + self.by_method.setdefault(n.method, []).append(n) + self.call_sites = [n for n in self.nodes if n.is_call_site()] + + # --- scoping ----------------------------------------------------------------------------- + + @staticmethod + def scoped(method: str | None, locations: Iterable[Location]) -> set[Location]: + """The locations, with those local to a method (see is_local) named `|`.""" + return {f"{method}|{loc}" if is_local(loc) else loc for loc in locations} + + # --- exceptions ---------------------------------------------------------------------------- + + def _compute_harmful(self) -> None: + """ + The exception chains (and pseudo types) that some handler may catch harmfully: the handler + may reach a target, return normally, end the program, or let a harmful exception go on. + Anything else ends with an uncaught exception, or never ends. + """ + handlers: dict[ExceptionChain, set[SANode]] = {} + chains: set[ExceptionChain] = set() + for n in self.nodes: + chains |= set(n.escapes) + if n.exceptionChain is not None: + chains.add(n.exceptionChain) + for chain in chains: + found: set[SANode] = set() + for cs in self.call_sites: + if is_pseudo(chain): + found.update(cs.unknown_handlers.get(chain[0], [])) + else: + h = cs.handler_for(chain) + if h is not None: + found.add(h) + handlers[chain] = found + harmful: set[ExceptionChain] = set() + changed = True + while changed: + changed = False + for chain, hs in handlers.items(): + if chain in harmful: + continue + for h in hs: + if (h.reachesTargetSameLevel or h.canReturn or self._method_terminates.get(h.method, True) + or any(c in harmful for c in h.escapes)): + harmful.add(chain) + changed = True + break + self.harmful = harmful + + # --- the per-method control-flow graph ---------------------------------------------------- + + def _successors(self, n: SANode) -> list[CfgNode]: + succ: list[CfgNode] = [] + if n.is_call_site(): + callees = n.callees + if any(e.canReturn for e in callees): + succ.append(n.resume_site()) + for e in callees: + for chain in e.escapes: + if is_pseudo(chain): + succ.extend(n.unknown_handlers.get(chain[0], [])) + if chain in self.harmful: + succ.append(EXIT_EXC) + continue + handler = n.handler_for(chain) + if handler is not None: + succ.append(handler) + elif chain in self.harmful: + succ.append(EXIT_EXC) + succ.extend(n.next_exceptional) + return succ + if n.next_fallthrough is not None: + succ.append(n.next_fallthrough) + if n.next_branched is not None: + succ.append(n.next_branched) + succ.extend(n.next_exceptional) + if n.isExit: + succ.append(EXIT) + if n.exceptionChain is not None and n.exceptionChain in self.harmful: + succ.append(EXIT_EXC) + return succ + + def _postdominators(self, method_nodes: list[SANode]) -> tuple[dict[CfgNode, CfgNode], dict[CfgNode, list[CfgNode]]]: + """ + Immediate post-dominators within a method (Cooper, Harvey and Kennedy's iterative + algorithm on the reverse graph), over the nodes that can reach END. Returns (ipdom, succ): + ipdom maps each such node to its immediate post-dominator, succ every node to its + successors in the method (virtual nodes included). + """ + own = set(method_nodes) + succ: dict[CfgNode, list[CfgNode]] = {} + for n in method_nodes: + # Only successors in the same method (or virtual): a call's callee is not, nor is a + # handler in another frame (those are reached through the call site's own edges). + succ[n] = [s for s in self._successors(n) if s in own or isinstance(s, str)] + succ[EXIT] = [END] + succ[EXIT_EXC] = [END] + succ[END] = [] + preds: dict[CfgNode, list[CfgNode]] = {n: [] for n in succ} + for x, ss in succ.items(): + for s in ss: + preds[s].append(x) + # Post-order of a DFS from END over predecessors (the reverse graph). + order: list[CfgNode] = [] + seen: set[CfgNode] = {END} + stack: list[tuple[CfgNode, Iterator[CfgNode]]] = [(END, iter(preds[END]))] + while stack: + node, it = stack[-1] + advanced = False + for p in it: + if p not in seen: + seen.add(p) + stack.append((p, iter(preds[p]))) + advanced = True + break + if not advanced: + stack.pop() + order.append(node) + index = {n: i for i, n in enumerate(order)} # END has the highest + ipdom: dict[CfgNode, CfgNode] = {END: END} + rpo = list(reversed(order)) + changed = True + while changed: + changed = False + for x in rpo: + if x is END: + continue + new: CfgNode | None = None + for s in succ[x]: + if s in ipdom: + if new is None: + new = s + else: + a, b = s, new + while a is not b: + while index[a] < index[b]: + a = ipdom[a] + while index[b] < index[a]: + b = ipdom[b] + new = a + if new is not None and ipdom.get(x) is not new: + ipdom[x] = new + changed = True + return ipdom, succ + + # --- the analysis --------------------------------------------------------------------------- + + def compute(self) -> RelevanceStats: + nodes = self.nodes + # Which methods may end the program, and which global locations they may write, callees + # included (least fixpoints over the call graph). + callees_of: dict[Method, set[Method]] = {m: set() for m in self.by_method} + for cs in self.call_sites: + for e in cs.callees: + if cs.method is not None and e.method is not None: + callees_of.setdefault(cs.method, set()).add(e.method) + terminates: dict[Method, bool] = {m: any(n.terminates for n in ns) for m, ns in self.by_method.items()} + global_writes: dict[Method, set[Location]] = {m: set() for m in self.by_method} + for m, ns in self.by_method.items(): + for n in ns: + for written, _ in n.dataflow: + global_writes[m].update(w for w in written if not is_local(w)) + changed = True + while changed: + changed = False + for caller, called in callees_of.items(): + for c in called: + if terminates.get(c) and not terminates.get(caller): + terminates[caller] = True + changed = True + extra = global_writes.get(c, set()) - global_writes.setdefault(caller, set()) + if extra: + global_writes[caller] |= extra + changed = True + self._method_terminates = terminates + self._compute_harmful() + + # The data dependencies: each written location on what it may be computed from. + deps: dict[Location, set[Location]] = {} + star_reads: set[Location] = set() + + def depend(written: set[Location], read: set[Location]) -> None: + if ANY in written: + star_reads.update(read) + for w in written: + deps.setdefault(w, set()).update(read) + + for n in nodes: + method = n.method + for written, read in n.dataflow: + depend(self.scoped(method, written), self.scoped(method, read)) + if n.call is not None and n.is_call_site(): + args = n.call.get("args", []) + base = n.call.get("base", []) + result = n.call.get("result") + for e in n.callees: + em = e.method + for i, arg in enumerate(args): + depend({f"{em}|p:{i}"}, self.scoped(method, arg)) + depend({f"{em}|p:this"}, self.scoped(method, base)) + if result is not None: + depend(self.scoped(method, [result]), {f"{em}|ret"} + | self.scoped(method, n.call.get("resultAddress", []))) + + # Regions. + regions: dict[SANode, RegionFacts] = {} + branches = [n for n in nodes if n.is_branch()] + self.stats.branches = len(branches) + no_ipdom: set[SANode] = set() + # Statements that may throw to somewhere that matters (a local handler, or a harmful + # escape) are implicit branches: whether they throw depends on what they read (a null + # receiver, a cast's operand, a callee's arguments), so they get a region too, and their + # reads become relevant when it is. They are no tree branches: nothing flips them. + throwers: list[SANode] = [] + for m, method_nodes in self.by_method.items(): + mbranches = [n for n in method_nodes if n.is_branch()] + if not mbranches and not any(n.next_exceptional or n.is_call_site() or n.exceptionChain + for n in method_nodes): + continue + ipdom, succ = self._postdominators(method_nodes) + mthrowers = [n for n in method_nodes if not n.is_branch() and len(set(succ[n])) >= 2] + throwers.extend(mthrowers) + for b in mbranches + mthrowers: + stop = ipdom.get(b) + if stop is None or stop == END: + # No post-dominator (no path to the end), or the outcomes leave the method + # differently: either may matter. + no_ipdom.add(b) + continue + seen: set[CfgNode] = set() + work = [s for s in succ[b] if s is not stop] + while work: + x = work.pop() + if x in seen or x is stop: + continue + seen.add(x) + work.extend(succ.get(x, ())) + region_nodes = frozenset(x for x in seen if not isinstance(x, str)) + if EXIT_EXC in seen or EXIT in seen: + no_ipdom.add(b) # cannot happen below END, but be safe + continue + writes: set[Location] = set() + reaches_target = False + term = False + rbranches: list[SANode] = [] + cmethods: set[Method] = set() + for x in region_nodes: + for written, _ in x.dataflow: + writes |= self.scoped(m, written) + if x.isTarget or x.isIncomplete: + reaches_target = True + if x.terminates: + term = True + if x.is_branch(): + rbranches.append(x) + if x.is_call_site(): + for e in x.callees: + cmethods.add(e.method) + writes |= global_writes.get(e.method, set()) + if e.reachesTargetSameLevel: + reaches_target = True + if terminates.get(e.method): + term = True + if x.call is not None and x.call.get("result") is not None: + writes |= self.scoped(m, [x.call["result"]]) + regions[b] = RegionFacts(region_nodes, frozenset(writes), reaches_target, term, + tuple(rbranches), frozenset(cmethods)) + self.stats.no_postdominator = sum(1 for b in no_ipdom if b.is_branch()) + + # The least fixpoint: relevant locations and relevant branches. + relevant_locations: set[Location] = set() + everything = False + + def add(locations: Iterable[Location]) -> None: + nonlocal everything + work = [l for l in locations if l not in relevant_locations] + first = not relevant_locations + while work: + l = work.pop() + if l in relevant_locations: + continue + if l == ANY: + everything = True + relevant_locations.add(l) + work.extend(x for x in deps.get(l, ()) if x not in relevant_locations) + if first and relevant_locations and star_reads: + add(star_reads) # a write to anything may write a relevant location + + for n in nodes: + if n.isTarget or n.isIncomplete: + for _, read in n.dataflow: + add(self.scoped(n.method, read)) + add(self.scoped(n.method, n.control)) + if n.is_call_site() and n.call is not None: + for arg in n.call.get("args", []): + add(self.scoped(n.method, arg)) + add(self.scoped(n.method, n.call.get("base", []))) + if n.terminates: + add(self.scoped(n.method, n.control)) + + def reads_of(n: SANode) -> set[Location]: + """What a branch's decision depends on; for an implicit branch (a thrower), everything + it reads.""" + if n.is_branch(): + return self.scoped(n.method, n.control) + read = set(self.scoped(n.method, n.control)) + for _, r in n.dataflow: + read |= self.scoped(n.method, r) + if n.call is not None: + for arg in n.call.get("args", []): + read |= self.scoped(n.method, arg) + read |= self.scoped(n.method, n.call.get("base", [])) + return read + + relevant = set(no_ipdom) + for b in no_ipdom: + add(reads_of(b)) + candidates = [b for b in branches + throwers if b in regions] + callers_of: dict[Method, set[Method]] = {} + for caller, called in callees_of.items(): + for c in called: + callers_of.setdefault(c, set()).add(caller) + + def methods_running(relevant: set[SANode]) -> set[Method]: + """The methods that run a relevant branch, callees included.""" + methods = {b.method for b in relevant if b.is_branch()} + work = list(methods) + while work: + c = work.pop() + for m in callers_of.get(c, ()): + if m not in methods: + methods.add(m) + work.append(m) + return methods + + changed = True + while changed and not everything: + self.stats.iterations += 1 + changed = False + relevant_methods = methods_running(relevant) + for b in candidates: + if b in relevant: + continue + r = regions[b] + if (r.reaches_target or r.terminates + or not r.writes.isdisjoint(relevant_locations) + or (ANY in r.writes and relevant_locations) + or any(x in relevant for x in r.branches) + or not r.callee_methods.isdisjoint(relevant_methods)): + relevant.add(b) + add(reads_of(b)) + changed = True + self.stats.all_relevant = everything + self.relevant_locations = relevant_locations + + # Branches whose regions change nothing relevant but locals of their own method: a loop head + # whose loop the executor summarized (closed forms of its locals) while its body also does + # other things, which must not matter (see dfs and the executor's LoopSummaries). + relevant_methods = methods_running(relevant) + + for b in branches: + irrelevant = not everything and b not in relevant and b in regions + b.irrelevant = irrelevant + b.region = regions[b].nodes if irrelevant else None + b.summary_ok = False + if everything or b not in regions: + continue + r = regions[b] + if (r.reaches_target or r.terminates or ANY in r.writes + or any('|' not in w and w in relevant_locations for w in r.writes) + or any(x in relevant and x is not b for x in r.nodes) + or not r.callee_methods.isdisjoint(relevant_methods)): + continue + b.summary_ok = True + b.region = r.nodes + self.stats.irrelevant = sum(1 for b in branches if b.irrelevant) + return self.stats + + +def in_region(region_owner: SANode, owner_stack: CallStack, node: SANode, stack: CallStack) -> bool: + """ + Whether the walk's position (`node` under call stack `stack`) is still inside the region of the + sliced branch `region_owner`, reached with `owner_stack`: in the region in the owner's own + frame, or in a frame called from a call site in the region. + """ + d = 0 if owner_stack is None else owner_stack[2] + depth = 0 if stack is None else stack[2] + if depth < d: + return False + region = region_owner.region + assert region is not None, f"{region_owner.id} has no region" + cell = stack + inner_site: SANode | None = None + while cell is not None and cell[2] > d: + inner_site = cell[0] + cell = cell[1] + if cell is not owner_stack: + return False # the owner's frame is gone + if inner_site is None: + return node in region + return inner_site in region diff --git a/symbolic-explorer/data/StaticAnalysisGraph/SAGraph.py b/symbolic-explorer/data/StaticAnalysisGraph/SAGraph.py new file mode 100644 index 00000000..121c05d1 --- /dev/null +++ b/symbolic-explorer/data/StaticAnalysisGraph/SAGraph.py @@ -0,0 +1,719 @@ +""" +The static pre-analysis (SA) graph: the extractor's interprocedural control-flow graph +(cfg-extraction, `_main_interprocedural.json`), and what dfs needs to walk it in step with +the execution tree. + +Terms used here, in dfs and in Relevance: + +* **target point**: a node whose execution may violate the property being checked: an assertion + point under valid-assert, a site that may raise a RuntimeException under no-runtime-exception + (later also shell calls, SQL statements). `SANode.isTarget`. +* **interesting**: a target point is reachable from a node, given the walk's call stack + (`is_interesting`). dfs prunes the sides of branches that are not interesting. +* **call stack** (`CallStack`): the call sites the walk entered to get where it is. +* **trace stack**: the call stack as the trace reports it (the frames popped and pushed before + each branch), which the walk is checked against (`check_branch`) and rebuilt from (`resync`). +* **key**: `owner.name(desc)|line|kind|ordinal`, the name of a branch or call site that both the + executor and the extractor compute (their `BranchKeys`). +* **lost**: the walk has no graph node (None). Then nothing is pruned, which is always safe. +""" +from __future__ import annotations +from dataclasses import dataclass, field +from typing import TYPE_CHECKING, Any, Final, Literal, Optional, Tuple, TypedDict, Union, cast +from collections import deque +from enum import Enum +import json +import math +import re + +if TYPE_CHECKING: + from data.StaticAnalysisGraph.Relevance import RelevanceStats + +# An exception type followed by its superclasses up to java.lang.Throwable, e.g. +# ("java.lang.ArithmeticException", "java.lang.RuntimeException", ..., "java.lang.Throwable"); or +# a pseudo type alone, e.g. ("?Exception",) (see is_pseudo). +ExceptionChain = Tuple[str, ...] + +# An abstract location of the data-flow facts (cfg-extraction DataflowFacts): a local `l:`, a +# parameter `p:` or `p:this`, the return value `ret` (these three are per method: Relevance +# scopes them as `|`), a field `f::`, the elements of arrays +# `a:`, global state `g:` (the input cursor, library state, output, the +# exception in flight), or `*`, which stands for every location. +Location = str +# What a statement does to locations: those it may write, and those it reads to compute them. +Effect = Tuple[Tuple[Location, ...], Tuple[Location, ...]] + + +class CallFacts(TypedDict, total=False): + """The data-flow facts of an expanded call site (the extractor's `call` field).""" + args: list[list[Location]] # per argument, the locations its value comes from + base: list[Location] # the receiver's locations (empty for a static call) + result: Location # where the result goes, if it is used + resultAddress: list[Location] # what the result's destination is addressed by (an array index, ...) + + +# The walk's call stack: a persistent linked-list stack of call-site nodes, innermost call on top, each +# cell also carrying the stack's depth. `None` is the empty stack, i.e. the walk is in main's +# frame. Every dfs branch extends the stack it was handed without mutating it, so sibling branches +# never see each other's frames. Build and take apart with push() / pop(). +CallStack = Optional[Tuple["SANode", "CallStack", int]] + +# Bounds on a single walk_till_branch() call. A branch-free infinite loop, or unbounded recursion +# with no branch before the recursive call, would otherwise never reach a branch. Hitting either +# bound loses the walk, which is always safe. +MAX_WALK_STEPS = 100_000 +MAX_STACK_DEPTH = 10_000 + + +@dataclass(eq=False) +class SANode: + id: str + # What a target point is reachable from here by, evaluated against the walk's call stack in + # is_interesting(). Computed by compute_summaries(); see there for the exact definitions. + # reachesTargetSameLevel: without leaving this node's frame (calls in and back out allowed) + # canReturn: this method's normal exit is reachable, likewise + # escapes: the exceptions (as superclass chains) that can leave this frame + reachesTargetSameLevel: bool = False + canReturn: bool = False + escapes: frozenset[ExceptionChain] = frozenset() + # A target point of the property being checked (see the module comment). Incomplete nodes are + # target points too. + isTarget: bool = False + # The extractor could not expand something here (a call whose target it could not pin down, or + # a throw whose destination it could not), so whatever was left out is missing from the graph + # and the walk cannot pair the branches that follow. walk_till_branch() stops here (the walk is + # lost), and the node counts as a target point, so the paths leading to it stay interesting. + isIncomplete: bool = False + # A method exit (a return). The walk resumes at the return site of the frame on top of its + # call stack. + isExit: bool = False + # Only for an uncaught[T] node, where exception T leaves its method: T followed by its + # superclasses up to java.lang.Throwable. The walk unwinds its call stack to the first frame + # with a handler for one of these, most specific first. + exceptionChain: ExceptionChain | None = None + # Predecessors along every edge kind (calls, RETURN and EXCEPTIONAL_RETURN edges included), for + # the target distances. + prev: list[SANode] = field(default_factory=list, repr=False) + # The successors within the method: a branch has both, other nodes at most next_fallthrough, + # a call site none (see callees). + next_fallthrough: SANode | None = None + next_branched: SANode | None = None + # EXCEPTION edges, inferred by SootUp. The walk never follows them; the summaries count them. + next_exceptional: list[SANode] = field(default_factory=list, repr=False) + # Only for a call site (a node with CALL edges): the entries of the methods it may call (one, + # or several known overrides), where the caller resumes after the callee returns, and the + # handler each caught exception type resumes at if the callee throws. + callees: list[SANode] = field(default_factory=list, repr=False) + return_site: SANode | None = field(default=None, repr=False) + handlers: dict[str, SANode] = field(default_factory=dict, repr=False) + # Only for a call site: for each pseudo exception type (see is_pseudo), the handlers of the call + # site that might catch it. An exception of a pseudo type is not known precisely, so it may be + # caught by any of them or by none and unwind further. + unknown_handlers: dict[str, list[SANode]] = field(default_factory=dict, repr=False) + # The method this node belongs to (the extractor's "method: " metadata). + method: str | None = None + # For a branch or a call site, its key (see check_branch); and the graph's keys and root + # method, shared by all nodes of a graph. + key: str | None = None + graph_index: GraphIndex | None = field(default=None, repr=False) + # How many branches must still be decided on the shortest path from here to a target point, + # counting this node if it is a branch. Computed by compute_target_distances(). Context- + # insensitive, so only a heuristic for ordering the search (--sa-rank-distance); pruning never + # depends on it. + targetDistance: float = math.inf + # The extractor's data-flow facts (cfg-extraction DataflowFacts), for the relevance analysis: + # (written, read) location pairs, what the node's control decision reads, whether it may end + # the program, and for an expanded call site its arguments, receiver and result. + dataflow: tuple[Effect, ...] = () + control: tuple[Location, ...] = () + terminates: bool = False + call: CallFacts | None = field(default=None, repr=False) + # Set by Relevance, for a branch. irrelevant: it cannot affect the targets, so dfs slices it. + # summary_ok: its region changes nothing relevant except locals of its own method, so dfs may + # honor a conditional loop summary of it. region: for either, the nodes of its own frame up to + # its immediate post-dominator. + irrelevant: bool = False + summary_ok: bool = False + region: frozenset[SANode] | None = field(default=None, repr=False) + + def is_branch(self) -> bool: + return self.next_branched is not None + + def sides(self) -> tuple[SANode, SANode]: + """A branch's successors: (fall-through, branched).""" + assert self.next_fallthrough is not None and self.next_branched is not None, f"{self.id} is no branch" + return self.next_fallthrough, self.next_branched + + def resume_site(self) -> SANode: + """A call site's return site, where its frame resumes when the callee returns.""" + assert self.return_site is not None, f"{self.id} is no call site" + return self.return_site + + def is_call_site(self) -> bool: + return self.return_site is not None + + def handler_for(self, chain: ExceptionChain) -> SANode | None: + """At a call site: the handler that catches an exception with this superclass chain, the + most specific one first (the rule the extractor wires its EXCEPTIONAL_RETURN edges with).""" + for exception_type in chain: + handler = self.handlers.get(exception_type) + if handler is not None: + return handler + return None + + def walk_till_branch(self, stack: CallStack = None) -> tuple[SANode, CallStack] | None: + """ + Follows the graph from this node to the next branch, and returns it with the call stack + in effect there -- or None (lost) if the graph cannot say where the execution goes. + + Crosses method boundaries on the way: a call into a branch-free method goes in and back + out within one invocation, so the stack is pushed and popped here rather than in dfs. + Idempotent on a branch node. + """ + node = self + for _ in range(MAX_WALK_STEPS): + if node.isIncomplete: # the graph stops being faithful here + return None + if node.is_branch(): + return node, stack + if node.is_call_site(): + if len(node.callees) != 1: + # Several possible callees: which one runs only the trace can say, so the walk + # stops here and finds its place again inside the callee (resync). + return None + if depth(stack) >= MAX_STACK_DEPTH: + return None + stack = push(stack, node) + node = node.callees[0] + continue + if node.next_fallthrough is not None: + node = node.next_fallthrough + continue + if node.isExit: + if stack is None: # returning from main, or from a frame we never saw entered + return None + frame, stack = pop(stack) + node = frame.resume_site() + continue + if node.exceptionChain is not None: + if is_pseudo(node.exceptionChain): + return None # where an exception of unknown type goes is not known + resumed = unwind(node.exceptionChain, stack) + if resumed is None: # the exception leaves main + return None + node, stack = resumed + continue + return None # a dead end the graph has no continuation for + return None + + +# --- call stacks and exceptions ---------------------------------------------------------------- + +def push(stack: CallStack, call_site: SANode) -> CallStack: + return (call_site, stack, depth(stack) + 1) + + +def pop(stack: CallStack) -> tuple[SANode, CallStack]: + assert stack is not None, "pop from an empty call stack" + return stack[0], stack[1] + + +def depth(stack: CallStack | TraceFrames) -> int: + return 0 if stack is None else stack[2] + + +def unwind(exception_chain: ExceptionChain, stack: CallStack) -> tuple[SANode, CallStack] | None: + """ + Pops frames off `stack` until one catches an exception with the given superclass chain. + + Returns the handler to resume at and the stack below the catching frame, or None if no frame + catches it and the exception leaves main. + """ + while stack is not None: + frame, stack = pop(stack) + handler = frame.handler_for(exception_chain) + if handler is not None: + return handler, stack + return None + + +def is_pseudo(exception_chain: ExceptionChain) -> bool: + """ + Whether an escape is of a pseudo exception type: an exception the graph knows may occur but not + its type -- "?Exception" (unguarded: a null dereference, a cast, anything a library call + throws) and "?Error". The extractor lists which handlers of each call site might catch one + (SANode.unknown_handlers). It may be caught by any of them, or by none. + """ + return exception_chain[0].startswith("?") + + +def unwind_pseudo(pseudo_type: str, stack: CallStack) -> list[tuple[SANode, CallStack]]: + """Every handler an exception of a pseudo type may resume at, with the stack below its frame.""" + resumed: list[tuple[SANode, CallStack]] = [] + while stack is not None: + frame, stack = pop(stack) + resumed.extend((handler, stack) for handler in frame.unknown_handlers.get(pseudo_type, [])) + return resumed + + +# --- keys: checking the walk against the trace, and finding the graph again -------------------- + +@dataclass(eq=False) +class GraphIndex: + """The nodes of a graph by key (branches and call sites; see check_branch), and main's method.""" + keys: dict[str, SANode] + root_method: str | None + # Application code that library code may call back can reach an assert (the extractor's + # callbacksReachingAssert). Such a callback may also run once main's frame is gone (a shutdown + # hook, a finalizer, a thread still running), so leaving main is then interesting too. + callbacks_reach_assert: bool = False + + +# The trace stack: the executor's view of the call stack at a branch, as the walk tracks it from the +# trace (each branch carries the frames popped and pushed since the previous one). Its frames are +# cells like a CallStack's, but hold the graph's call site for each frame, or None for a frame the +# graph has no call site for (a library call, a call the extractor gave no key); a CallStack is a +# TraceFrames whose frames are all known (see dfs, which shares the cells of the two). The trace +# stack is LOST once the trace popped more frames than are known, e.g. returning from a frame +# entered before tracing began. +class _Lost(Enum): + LOST = "LOST" + + +LOST: Final = _Lost.LOST +TraceFrames = Optional[Tuple[Optional["SANode"], "TraceFrames", int]] +TraceStack = Union[TraceFrames, Literal[_Lost.LOST]] + + +def apply_stack_delta(stack: TraceStack, pops: int, pushes: tuple[str | None, ...], + index: GraphIndex | None) -> TraceStack: + """The trace stack `stack` after `pops` frames were popped and `pushes` pushed (the keys of + the call sites that pushed them, None where the executor could not name one).""" + if stack is LOST: + return LOST + for _ in range(pops): + if stack is None: + return LOST + stack = stack[1] + for key in pushes: + call_site = index.keys.get(key) if index is not None and key is not None else None + if call_site is not None and not call_site.is_call_site(): + call_site = None + stack = (call_site, stack, depth(stack) + 1) + return stack + + +def same_stacks(walk: CallStack, trace: TraceStack) -> bool | None: + """ + Whether the walk's call stack is the one the trace reports: True, False, or None where the + trace stack has frames without a call site (and agrees everywhere else). Compares from the top + down to the first cell the two share, so after the walk's stack has been adopted for the trace + (see dfs) this costs only the frames changed since. + """ + if trace is LOST: + return None + known = True + while walk is not trace: + if walk is None or trace is None or walk[2] != trace[2]: + return False + if trace[0] is None: + known = False + elif trace[0] is not walk[0]: + return False + walk, trace = walk[1], trace[1] + return True if known else None + + +def keys_match(graph_key: str, trace_key: str) -> bool: + """ + Whether a graph node's key names the trace's instruction. A graph key may leave the ordinal + open (`...|kind|*`, where the extractor could not tell same-kind instructions on a line apart): + it then matches any ordinal. + """ + if "|*" not in graph_key: + return graph_key == trace_key + prefix, rest = graph_key.split("|*", 1) + if not trace_key.startswith(prefix + "|"): + return False + tail = trace_key[len(prefix) + 1:] + digits = len(tail) - len(tail.lstrip("0123456789")) + return digits > 0 and tail[digits:] == rest + + +def check_branch(node: SANode, stack: CallStack, key: str | None, trace_stack: TraceStack) -> bool | None: + """ + Whether the walk is still in step with the execution at a branch: the graph's branch `node`, + reached with call stack `stack`, against the branch the trace reports there (its `key`) and the + trace stack. False when they provably differ (a desync); None when it cannot be told (a key is + missing on either side, or a frame has no call site); True otherwise. + + Keys name a branch by method, source line, kind and position on that line (see the executor's + and the extractor's BranchKeys), so a pairing that slipped by one branch is caught at that very + branch, before its pruning decision is made. + """ + if key is None: + return None # the executor could not name the branch + if node.key is not None and not keys_match(node.key, key): + return False + stacks = same_stacks(stack, trace_stack) + if stacks is False: + return False + if node.key is None or stacks is None: + return None + return True + + +def resync(key: str | None, trace_stack: TraceStack, index: GraphIndex | None) -> tuple[SANode, CallStack] | None: + """ + Where a lost walk resumes, at a branch: the graph's branch with the trace's `key`, under the + trace stack -- or None unless every frame of that stack maps to a call site and each one, from + main up, calls the method the next one is in. + """ + if key is None or index is None or trace_stack is LOST: + return None + node = index.keys.get(key) + if node is None or not node.is_branch(): + return None + frames: list[SANode] = [] + cell: TraceFrames = trace_stack + while cell is not None: + if cell[0] is None: + return None + frames.append(cell[0]) + cell = cell[1] + methods = {index.root_method} + for call_site in reversed(frames): + if call_site.method is None or call_site.method not in methods: + return None + methods = {callee.method for callee in call_site.callees} + if node.method is None or node.method not in methods: + return None + return node, cast(CallStack, trace_stack) # every frame is known (checked above) + + +# --- which nodes are interesting --------------------------------------------------------------- + +def is_interesting(node: SANode, stack: CallStack) -> bool: + """ + Whether a target point is reachable from `node` when the walk is there with `stack`. + + Exact for realizable paths, i.e. those on which every return and every unwinding exception + goes where the call stack says: a target point is either reachable within the current frame, + or after returning -- normally or by an exception -- into a frame further down the stack, + where the same question is asked again. Costs O(stack depth) per exception type the frame can + throw. + """ + worklist: list[tuple[SANode, CallStack]] = [(node, stack)] + # The stack cells are shared with the caller's stack and alive for the whole query, so their + # identities are stable keys. + seen = set() + while worklist: + current, current_stack = worklist.pop() + key = (id(current), id(current_stack)) + if key in seen: + continue + seen.add(key) + if current.reachesTargetSameLevel: + return True + if current_stack is None: # in main's frame: nothing to return into + index = current.graph_index + if index is not None and index.callbacks_reach_assert and (current.canReturn or current.escapes): + return True # a callback may still run after main (see GraphIndex) + continue + frame, below = pop(current_stack) + if current.canReturn: + worklist.append((frame.resume_site(), below)) + for chain in current.escapes: + if is_pseudo(chain): + worklist.extend(unwind_pseudo(chain[0], current_stack)) + continue + resumed = unwind(chain, current_stack) + if resumed is not None: + worklist.append(resumed) + return False + + +def compute_summaries(nodes: list[SANode]) -> None: + """ + Computes each node's reachesTargetSameLevel, canReturn and escapes, as the least fixpoint of + + reachesTargetSameLevel(N) = isTarget(N) + or N is a call site into E, returning to R: + reachesTargetSameLevel(E) + or canReturn(E) and reachesTargetSameLevel(R) + or reachesTargetSameLevel(h) for each h at N catching an exception in escapes(E) + or otherwise: any intraprocedural successor + canReturn(N) = N is an exit, or likewise with canReturn in place of reachesTarget... + escapes(N) = {T} at an uncaught[T] node; at a call site the exceptions in escapes(E) no + handler of N catches, plus escapes(R) if canReturn(E), plus escapes(h) of + each handler reached; otherwise the union over intraprocedural successors. + A pseudo type (is_pseudo) reaches every handler that might catch it and + always escapes further too. + + (Reps-Horwitz-Sagiv same-level realizable paths.) The call summaries make it mutually + recursive through callee entries, so recursion makes it cyclic; it is solved with a worklist, + and since every value only ever grows within a finite domain it converges. + + An incomplete node might hide anything and forces both booleans true. A throw leaving the + method does not count towards canReturn: its uncaught[T] node has no normal continuation. + + Intraprocedural successors include the SootUp-inferred EXCEPTION edges. The walk does not + follow those, but counting them can only make more paths interesting. + """ + dependents: dict[int, list[SANode]] = {id(n): [] for n in nodes} + + def successors(n: SANode) -> list[SANode]: + succ = list(n.next_exceptional) + if n.next_branched is not None: + succ.append(n.next_branched) + if n.next_fallthrough is not None: + succ.append(n.next_fallthrough) + return succ + + for n in nodes: + for s in successors(n): + dependents[id(s)].append(n) + if n.is_call_site(): + unknown = [h for hs in n.unknown_handlers.values() for h in hs] + for s in [*n.callees, n.resume_site(), *n.handlers.values(), *unknown]: + dependents[id(s)].append(n) + + def evaluate(n: SANode) -> tuple[bool, bool, frozenset[ExceptionChain]]: + if n.isIncomplete: + return True, True, frozenset() + rts = n.isTarget + cr = n.isExit + esc: set[ExceptionChain] = set() + if n.exceptionChain is not None: + esc.add(n.exceptionChain) + for s in successors(n): + rts = rts or s.reachesTargetSameLevel + cr = cr or s.canReturn + esc |= s.escapes + if n.is_call_site(): + # Where the caller may resume after any of the callees: at the return site, and at the + # handlers of what they throw (an exception of a pseudo type may also be caught by none). + resumes: list[SANode] = [] + for entry in n.callees: + rts = rts or entry.reachesTargetSameLevel + if entry.canReturn: + resumes.append(n.resume_site()) + for chain in entry.escapes: + if is_pseudo(chain): + resumes.extend(n.unknown_handlers.get(chain[0], [])) + esc.add(chain) + continue + handler = n.handler_for(chain) + if handler is None: + esc.add(chain) + else: + resumes.append(handler) + for r in resumes: + rts = rts or r.reachesTargetSameLevel + cr = cr or r.canReturn + esc |= r.escapes + return rts, cr, frozenset(esc) + + worklist = list(nodes) + queued = {id(n) for n in nodes} + while worklist: + n = worklist.pop() + queued.discard(id(n)) + rts, cr, esc = evaluate(n) + if (rts, cr, esc) != (n.reachesTargetSameLevel, n.canReturn, n.escapes): + n.reachesTargetSameLevel, n.canReturn, n.escapes = rts, cr, esc + for d in dependents[id(n)]: + if id(d) not in queued: + queued.add(id(d)) + worklist.append(d) + + +def compute_target_distances(nodes: list[SANode]) -> None: + """ + Sets each node's targetDistance: the fewest branches on any path from the node to a target + point, following every edge kind (calls, returns and exceptional edges included) and ignoring + the call stack. A 0-1 BFS backwards over `prev`: leaving a branch node costs 1, anything else 0. + """ + worklist: deque[SANode] = deque() + for n in nodes: + n.targetDistance = math.inf + if n.isTarget: + n.targetDistance = 0 + worklist.append(n) + while worklist: + current = worklist.popleft() + for pred in current.prev: + cost = 1 if pred.is_branch() else 0 + d = current.targetDistance + cost + if d < pred.targetDistance: + pred.targetDistance = d + if cost == 0: + worklist.appendleft(pred) + else: + worklist.append(pred) + + +# --- application code that runs outside main's call tree --------------------------------------- + +# Library methods that run application code outside the caller's call tree: on another thread, +# after main (shutdown hooks), or when an exception leaves a thread (uncaught-exception handlers). +# Matched by name at library call sites only, so an application method of the same name, which +# the graph expands, does not count. +ASYNC_ENTRY_METHODS = { + "addShutdownHook", "setUncaughtExceptionHandler", "setDefaultUncaughtExceptionHandler", + "execute", "submit", "invokeAll", "invokeAny", "schedule", "scheduleAtFixedRate", + "scheduleWithFixedDelay", "invokeLater", "invokeAndWait", "startVirtualThread", +} +# `` in a Jimple invoke statement. +INVOKED_METHOD = re.compile(r"<([^:<>]+): ([^ <>]+) ([^ (<>]+)\(([^)]*)\)>") + + +def is_async_entry(declaring_class: str, return_type: str, name: str, params: str) -> bool: + if name in ASYNC_ENTRY_METHODS or name.endswith("Async"): # CompletableFuture.runAsync etc. + return True + if name == "start": # Thread.start, also through a subclass or a Thread.Builder + return (return_type == "void" and params == "") or declaring_class.startswith("java.lang.Thread") + return declaring_class == "java.lang.ref.Cleaner" and name == "register" + + +# --- loading ----------------------------------------------------------------------------------- + +# Edge types that continue within the method along the one fall-through slot. THROW is an explicit +# throw to where it provably goes; SWAT reports no branch for an ATHROW, so it walks like NORMAL. +FALLTHROUGH_EDGES = {"NORMAL", "FALSE_BRANCH", "PHANTOM_FALSE_BRANCH", "THROW"} +BRANCHED_EDGES = {"TRUE_BRANCH", "PHANTOM_TRUE_BRANCH"} +# Edges only recorded in `prev` (for the target distances): which RETURN or EXCEPTIONAL_RETURN edge +# applies depends on the call stack, so the walk and the summaries use the call sites' return_site +# and handlers instead. +RETURN_EDGES = {"RETURN", "EXCEPTIONAL_RETURN"} + + +class SAGraph: + def __init__(self) -> None: + self.json_graph: dict[str, Any] = {} # the extractor's JSON, as loaded + self.nodes: dict[str, SANode] = {} + self.entry_node: SANode | None = None + # The relevance analysis' statistics, None if it did not run (see load_json_graph). + self.relevance_stats: RelevanceStats | None = None + + def async_entry_sites(self) -> list[str]: + """ + The library call sites (as `node id: method`) that may run application code outside main's + call tree, see ASYNC_ENTRY_METHODS. The walk only follows main's call tree, so a target + point reached from such code is on no path it knows of. + """ + expanded = {e["source"] for e in self.json_graph.get("edges", []) if e["type"] == "CALL"} + sites = [] + for json_node in self.json_graph.get("nodes", []): + if json_node["id"] in expanded: + continue + match = INVOKED_METHOD.search(json_node.get("statement") or "") + if match is not None and is_async_entry(*match.groups()): + sites.append(f'{json_node["id"]}: {match.group(1)}.{match.group(3)}') + return sites + + def load_json_graph(self, path: str, targets: str = "assertionPointIds", slicing: bool = True) -> None: + """ + Loads the extractor's graph. `targets` names the metadata field with the target points of + the property being checked: assertionPointIds (valid-assert) or runtimeExceptionPointIds + (no-runtime-exception). A graph without it cannot be used for that property (KeyError). + + With `slicing`, and if the graph carries data-flow facts, also computes which branches are + irrelevant to those targets (see Relevance). + """ + with open(path, "r") as f: + self.json_graph = json.load(f) + metadata = self.json_graph["metadata"] + if targets not in metadata: + raise KeyError(f"the graph has no {targets}: it was made by an extractor that does not know this property") + + for json_node in self.json_graph["nodes"]: + chain = json_node.get("exceptionTypeChain") + method = json_node.get("metadata") + node = SANode(json_node["id"], + isIncomplete=json_node.get("isIncomplete", False), + exceptionChain=tuple(chain) if chain is not None else None, + method=method[len("method: "):] if isinstance(method, str) and method.startswith("method: ") else None, + key=json_node.get("key"), + dataflow=tuple((tuple(w), tuple(r)) for w, r in json_node.get("dataflow", ())), + control=tuple(json_node.get("control", ())), + terminates=json_node.get("terminates", False) is True, + call=json_node.get("call")) + self.nodes[node.id] = node + for exit_id in self.json_graph.get("exitNodeIds", []): + self.nodes[exit_id].isExit = True + # Phantom guards whose implicit exception the property pre-check proves cannot happen + # (the extractor's cannotRaise, see RaiseSites in cfg-extraction): their exception side + # leads nowhere, below. + cannot_raise = {n["id"] for n in self.json_graph["nodes"] if n.get("cannotRaise") is True} + + for json_edge in self.json_graph["edges"]: + source = self.nodes[json_edge["source"]] + target = self.nodes[json_edge["target"]] + etype = json_edge["type"] + if etype == "PHANTOM_FALSE_BRANCH" and source.id in cannot_raise: + # The exception side of a guard that cannot raise: a dead end, from which nothing + # is reachable. Should an execution take it after all, the walk is lost there, + # which prunes nothing. + target = SANode(f"dead[{source.id}]") + self.nodes[target.id] = target + if etype in FALLTHROUGH_EDGES: + assert source.next_fallthrough is None, f"SANode {source.id} has multiple fallthrough edges!" + source.next_fallthrough = target + elif etype in BRANCHED_EDGES: + assert source.next_branched is None, f"SANode {source.id} has multiple branched edges!" + source.next_branched = target + elif etype == "EXCEPTION": + source.next_exceptional.append(target) + elif etype == "CALL": + source.callees.append(target) + source.return_site = self.nodes[json_edge["returnSite"]] + source.handlers = {t: self.nodes[h] for t, h in json_edge.get("handlers", {}).items()} + source.unknown_handlers = {t: [self.nodes[h] for h in hs] + for t, hs in json_edge.get("unknownHandlers", {}).items()} + elif etype not in RETURN_EDGES: + raise ValueError(f"Unknown edge type {etype} from {source.id} to {target.id}") + target.prev.append(source) + for n in self.nodes.values(): + assert n.next_branched is None or n.next_fallthrough is not None, f"branch {n.id} has no fallthrough edge" + assert not (n.is_call_site() and n.next_fallthrough is not None), f"call site {n.id} has a fallthrough edge" + + keys: dict[str, SANode] = {} + duplicates = set() + for n in self.nodes.values(): + if n.key is not None and "|*" not in n.key: # an open ordinal names no one node + if n.key in keys: + duplicates.add(n.key) + keys[n.key] = n + for k in duplicates: # cannot tell which one is meant + del keys[k] + + self.entry_node = self.nodes[self.json_graph["entryNodeId"]] + # Callbacks are only an extra concern for asserts: for runtime exceptions, every library call + # that may call back into the application is a target point itself (see the extractor). + graph_index = GraphIndex(keys, self.entry_node.method, + callbacks_reach_assert=targets == "assertionPointIds" + and bool(metadata.get("callbacksReachingAssert"))) + for n in self.nodes.values(): + n.graph_index = graph_index + + for target_id in metadata[targets]: + self.nodes[target_id].isTarget = True + # The extractor already reports incomplete nodes as target points; marking them here too + # means a graph that ever forgets one still errs on the safe side. + for node in self.nodes.values(): + if node.isIncomplete: + node.isTarget = True + + compute_summaries(list(self.nodes.values())) + if slicing and metadata.get("dataflowFacts"): + from data.StaticAnalysisGraph.Relevance import Relevance + self.relevance_stats = Relevance(self.nodes.values()).compute() + compute_target_distances(list(self.nodes.values())) + + +if __name__ == "__main__": + import sys + graph = SAGraph() + graph.load_json_graph(sys.argv[1]) + print("not reachesTargetSameLevel:", [n.id for n in graph.nodes.values() if not n.reachesTargetSameLevel]) diff --git a/symbolic-explorer/data/trace/Branch.py b/symbolic-explorer/data/trace/Branch.py index 9b705c69..91a26749 100755 --- a/symbolic-explorer/data/trace/Branch.py +++ b/symbolic-explorer/data/trace/Branch.py @@ -1,3 +1,10 @@ +# Where a trace reached a loop-head state (see Tree.register_loop_states): (d, k, m), the number of +# branches before it, their outcomes read as a binary number, and the number of states before it. +Position = tuple[int, int, int] +# A loop-head state (the executor's hash of it) with its position. +PositionedState = tuple[str, Position] + + class Branch: """ Represents a branch in an execution trace, capturing the details of a decision point. @@ -13,7 +20,10 @@ class Branch: constraint (any): The constraint or condition associated with the branch. """ - def __init__(self, id: int, trace_id: str, has_branched: bool, constraint: str): + def __init__(self, id: int, trace_id: str, has_branched: bool, constraint: str, concrete_only: bool = False, + key: str | None = None, pops: int = 0, pushes: tuple[str | None, ...] = (), callback: bool = False, + choice: bool = False, loop_states: tuple[str, ...] = (), merged: bool = False, + merged_conditional: bool = False) -> None: """ Initializes a new instance of the Branch class. @@ -27,6 +37,25 @@ def __init__(self, id: int, trace_id: str, has_branched: bool, constraint: str): self.trace_id = trace_id self.has_branched = has_branched self.constraint = constraint + # Recorded only to keep the SA walk in step (e.g. the bounds check of an untracked array): + # it has no symbolic constraint and is never worth solving. + self.concrete_only = concrete_only + # Where the branch is, to check the SA walk against (see TraceItem). + self.key = key + self.pops = pops + self.pushes = pushes + # In application code that library code called back, which the SA graph does not have. + self.callback = callback + # Added by the executor to enumerate the values of a symbolic operand; not in the SA graph. + self.choice = choice + # Merged by a loop summary: not a decision (see TraceItem). + self.merged = merged + # ... only if the loop's other effects cannot matter (see dfs) + self.merged_conditional = merged_conditional + # The loop-head states reached since the previous branch (see Tree.register_loop_states). + self.loop_states = loop_states + # The same with their positions, set by Tree.register_loop_states. + self.positioned_loop_states: tuple[PositionedState, ...] = () - def __str__(self): + def __str__(self) -> str: return f'[(B) - {self.id} - ({"T" if self.has_branched else "F"})]' \ No newline at end of file diff --git a/symbolic-explorer/data/trace/Special.py b/symbolic-explorer/data/trace/Special.py index 89f76509..6db5eecb 100755 --- a/symbolic-explorer/data/trace/Special.py +++ b/symbolic-explorer/data/trace/Special.py @@ -1,5 +1,19 @@ from typing import Optional +# Fully qualified names of the instructions that delimit a static initializer in the trace. The +# executor emits CLINIT when it enters a and INVOKECLINIT_END when it leaves it again. +CLINIT = 'de.uzl.its.swat.symbolic.instruction.CLINIT' +CLINIT_END = 'de.uzl.its.swat.symbolic.instruction.INVOKECLINIT_END' +# The executor's marker for an exception the SA graph does not model +# (SymbolicInstructionVisitor.UNMODELLED_EXCEPTION). Unlike the other elements, it is only in the +# trace of a run that throws, so two traces with the same branches can differ by it. +UNMODELLED_EXCEPTION = 'UNMODELLED_EXCEPTION' + + +def short_inst(inst: Optional[str]) -> str: + """Strips the package from an instruction name for logging, e.g. '...instruction.CLINIT' -> 'CLINIT'.""" + return inst.rsplit('.', 1)[-1] if inst else '?' + class Special: def __init__(self, id: int, trace_id: str, has_branched: bool, inst: Optional[str]): @@ -9,4 +23,4 @@ def __init__(self, id: int, trace_id: str, has_branched: bool, inst: Optional[st self.inst = inst def __str__(self): - return f'[(S) - {self.id} - ({"T" if self.has_branched else "F"})]' \ No newline at end of file + return f'[(S) - {self.id} - ({"T" if self.has_branched else "F"}) - {short_inst(self.inst)}]' diff --git a/symbolic-explorer/data/trace/StaticInitializers.py b/symbolic-explorer/data/trace/StaticInitializers.py new file mode 100644 index 00000000..0dd8a9b6 --- /dev/null +++ b/symbolic-explorer/data/trace/StaticInitializers.py @@ -0,0 +1,50 @@ +""" +Static initializers in the traces of a long-running target. + +A JVM runs a class's once, so in a target that serves many requests (http mode) only the +first trace to use a class contains its static initializer (CLINIT ... INVOKECLINIT_END), and no +later run can repeat it. Left in, the traces disagree at that depth without a branch to tell them +apart, so the tree drops the rest of every later trace there (Tree.add, tree_mismatch). The +initializer's branches cannot be explored by re-running either, and the SA graph does not have them +(dfs masks them), so they are taken out of the trace. +""" + +from data.trace.Branch import Branch +from data.trace.Special import Special, CLINIT, CLINIT_END + + +def compose_deltas(first: tuple[int, tuple], second: tuple[int, tuple]) -> tuple[int, tuple]: + """The stack change (frames popped, keys pushed) of `first` followed by `second`.""" + pops1, pushes1 = first + pops2, pushes2 = second + kept = len(pushes1) - min(pops2, len(pushes1)) + return pops1 + max(0, pops2 - len(pushes1)), tuple(pushes1[:kept]) + tuple(pushes2) + + +def strip_static_initializers(trace: list) -> list: + """ + The trace without its static initializers (nested ones included). The stack change of the + branches taken out is carried over to the next branch kept, so that the call stack the SA walk + rebuilds from the deltas stays right. A left by an exception has no end marker; its + rest is left out up to the end of the trace. + """ + out = [] + depth = 0 + carried = (0, ()) # stack change of the branches left out since the last kept one + for element in trace: + inst = getattr(element, 'inst', None) if isinstance(element, Special) else None + if inst == CLINIT: + depth += 1 + continue + if inst == CLINIT_END and depth > 0: + depth -= 1 + continue + if depth > 0: + if isinstance(element, Branch): + carried = compose_deltas(carried, (element.pops or 0, tuple(element.pushes or ()))) + continue + if isinstance(element, Branch) and carried != (0, ()): + element.pops, element.pushes = compose_deltas(carried, (element.pops or 0, tuple(element.pushes or ()))) + carried = (0, ()) + out.append(element) + return out diff --git a/symbolic-explorer/driver/HTTPDriver.py b/symbolic-explorer/driver/HTTPDriver.py index 1ca365da..9d2234eb 100644 --- a/symbolic-explorer/driver/HTTPDriver.py +++ b/symbolic-explorer/driver/HTTPDriver.py @@ -3,7 +3,9 @@ import json import copy import signal +import time import requests +import z3 from enum import Enum from typing import Dict, List, Tuple, Optional, Any @@ -11,6 +13,8 @@ from driver.SymbolicStorage import SymbolicStorage, DataTypes from solver.SolverHandler import SATResult from strategy.StrategyService import StrategyService +from data.StaticAnalysisGraph.SAGraph import SAGraph +from svcomp.StaticPreAnalysis import StaticPreAnalysis import log logger = log.get_logger() @@ -50,6 +54,19 @@ def __init__(self, args): self.endpoint_id = None self.args = args self.base_url = f"http://{args.host}:{args.http_port}" + self.last_trace_id = None # of the latest trace whose violation was checked + self.start_time = time.perf_counter() + + # Static pre-analysis of the endpoint method (--sa-entry with --sa-path or --sa-file); the + # graph stays empty, which prunes nothing, unless it is loaded. + if not getattr(args, 'property', None): + args.property = 'valid-assert.prp' + self.sa = StaticPreAnalysis(args) + # The target serves every request in one JVM, so a static initializer is in one trace only + Database.instance().strip_static_initializers = True + self.sa_graph = SAGraph() + self.stats = {'requests': 0, 'solver_calls': 0, 'violations': 0, 'first_violation_s': None, + 'first_violation_request': None, 'server_errors': 0, 'exhausted': False, 'timed_out': False} # Parse the target specification (METHOD|URL|BODY) self._parse_target_specification(args.url_template) @@ -563,6 +580,13 @@ def send_http_request(self, timeout: int = 60) -> Tuple[ExecutionStatus, Dict]: try: # Prepare headers headers = {} + if isinstance(json_body, str): + # A body template that is a JSON string ("") is the raw request body, + # e.g. for a @RequestBody String parameter. + headers['Content-Type'] = 'text/plain; charset=utf-8' + response = requests.request(self.http_method, url, data=json_body.encode('utf-8'), + timeout=timeout, headers=headers) + return self._response_status(response) if json_body: headers['Content-Type'] = 'application/json' @@ -593,29 +617,8 @@ def send_http_request(self, timeout: int = 60) -> Tuple[ExecutionStatus, Dict]: logger.error(f'[HTTP DRIVER] Unsupported HTTP method: {self.http_method}') return ExecutionStatus.ERROR, {'error': f'Unsupported method: {self.http_method}'} - logger.info(f'[HTTP DRIVER] Response status: {response.status_code}') + return self._response_status(response) - response_data = { - 'status_code': response.status_code, - 'headers': dict(response.headers), - 'url': response.url, - 'text': response.text[:1000] # Limit response text for logging - } - - # Only treat 5xx server errors as ERROR status - # 4xx client errors are valid responses from the target and should be treated as SUCCESS - if response.status_code >= 500: - logger.warning(f'[HTTP DRIVER] Server error: {response.status_code}') - return ExecutionStatus.ERROR, response_data - - # All other responses (1xx, 2xx, 3xx, 4xx) are considered successful communication with target - if response.status_code >= 400: - logger.info(f'[HTTP DRIVER] Client error response: {response.status_code} (valid target response)') - else: - logger.info(f'[HTTP DRIVER] Successful response: {response.status_code}') - - return ExecutionStatus.SUCCESS, response_data - except requests.exceptions.Timeout: logger.warning(f'[HTTP DRIVER] Request timeout after {timeout}s') return ExecutionStatus.TIMEOUT, {'error': 'timeout'} @@ -626,72 +629,135 @@ def send_http_request(self, timeout: int = 60) -> Tuple[ExecutionStatus, Dict]: logger.critical(f'[HTTP DRIVER] Exception during request: {e}') return ExecutionStatus.CRASH, {'error': str(e)} + def _response_status(self, response) -> Tuple[ExecutionStatus, Dict]: + """The execution status of a request from its response.""" + logger.info(f'[HTTP DRIVER] Response status: {response.status_code}') + + response_data = { + 'status_code': response.status_code, + 'headers': dict(response.headers), + 'url': response.url, + 'text': response.text[:1000] # Limit response text for logging + } + + # Only treat 5xx server errors as ERROR status + # 4xx client errors are valid responses from the target and should be treated as SUCCESS + if response.status_code >= 500: + logger.warning(f'[HTTP DRIVER] Server error: {response.status_code}') + return ExecutionStatus.ERROR, response_data + + # All other responses (1xx, 2xx, 3xx, 4xx) are considered successful communication with target + if response.status_code >= 400: + logger.info(f'[HTTP DRIVER] Client error response: {response.status_code} (valid target response)') + else: + logger.info(f'[HTTP DRIVER] Successful response: {response.status_code}') + + return ExecutionStatus.SUCCESS, response_data + def record_violation(self): """Records the violation in the database.""" db = Database.instance() db.add_violation(endpoint_id=self.endpoint_id, sym_vars=list(self.sym_storage.vars.values())) + self.stats['violations'] += 1 + if self.stats['first_violation_s'] is None: + self.stats['first_violation_s'] = round(time.perf_counter() - self.start_time, 2) + self.stats['first_violation_request'] = self.stats['requests'] + + def new_trace_violation(self) -> tuple[bool, dict | None]: + """ + Whether a trace of this endpoint arrived since the previous request, and the violation it + reports (the throwable that escaped the endpoint, see the executor's Witness), if any. The + executor sends the trace before the endpoint returns or throws, so it is in by now. + """ + if self.endpoint_id is None: + return False, None + witness = Database.instance().get_witness_data(self.endpoint_id) + if witness is None or witness.get('trace_id') == self.last_trace_id: + return False, None + self.last_trace_id = witness.get('trace_id') + return True, witness.get('violation') + + def is_target_violation(self, violation: dict | None) -> bool: + """Whether the escaped throwable violates the checked property (only valid-assert so far).""" + return violation is not None and violation.get('throwable') == 'java.lang.AssertionError' def determine_next_step(self, status: ExecutionStatus, response: Dict) -> Action: - """Determines the next step based on the HTTP response.""" - match status: - case ExecutionStatus.SUCCESS: - # Check response for assertion violations or error indicators - response_text = response.get('text', '') - if 'java.lang.AssertionError' in response_text: - logger.info(f'[HTTP DRIVER] Assertion error detected in response') - self.record_violation() - return Action.SYMBOLICNEXT - elif 'VIOLATION' in response_text or 'ERROR' in response_text: - logger.info(f'[HTTP DRIVER] Violation detected in response') - self.record_violation() - return Action.SYMBOLICNEXT - return Action.SYMBOLICNEXT - case ExecutionStatus.VIOLATION: + """Determines the next step based on the HTTP response and the trace it produced.""" + new_trace, violation = self.new_trace_violation() + if status in (ExecutionStatus.SUCCESS, ExecutionStatus.ERROR): + response_text = response.get('text', '') + if self.is_target_violation(violation) or 'java.lang.AssertionError' in response_text: + frames = (violation or {}).get('frames') or [{}] + logger.info(f'[HTTP DRIVER] Assertion violation detected (at {frames[0].get("className")}:{frames[0].get("line")})') self.record_violation() - logger.info(f'[HTTP DRIVER] Violation recorded!') + self.state.verdict = Verdict.VIOLATION + if self.args.stop_on_violation: + return Action.REPORTVERDICT return Action.SYMBOLICNEXT + if status == ExecutionStatus.ERROR: + # A server error is a valid outcome of a request (an exception other than the + # violation escaped the endpoint) as long as its trace arrived. + self.stats['server_errors'] += 1 + if not new_trace: + logger.info(f'[HTTP DRIVER] Server error without a trace') + if self.state.verdict != Verdict.VIOLATION: + self.state.verdict = Verdict.UNKNOWN + return Action.REPORTVERDICT + logger.info(f'[HTTP DRIVER] Server error (exception {violation and violation.get("throwable")}), continuing') + return Action.SYMBOLICNEXT + match status: case ExecutionStatus.TIMEOUT: logger.info(f'[HTTP DRIVER] Timeout!') - self.state.verdict = Verdict.UNKNOWN + if self.state.verdict != Verdict.VIOLATION: + self.state.verdict = Verdict.UNKNOWN return Action.REPORTVERDICT case ExecutionStatus.CRASH: logger.info(f'[HTTP DRIVER] Crash!') - self.state.verdict = Verdict.UNKNOWN - return Action.REPORTVERDICT - case ExecutionStatus.ERROR: - logger.info(f'[HTTP DRIVER] Error!') - self.state.verdict = Verdict.UNKNOWN + if self.state.verdict != Verdict.VIOLATION: + self.state.verdict = Verdict.UNKNOWN return Action.REPORTVERDICT raise Exception(f'Unknown execution status: {status}') def retrieve_solution(self): """Retrieve next solution using symbolic exploration.""" - possible_branches = StrategyService.select_branch(endpoint_id=self.endpoint_id) + possible_branches = StrategyService.select_branch(endpoint_id=self.endpoint_id, sa_node=self.sa_graph.entry_node, + rank_by_distance=self.args.sa_rank_distance, + search_order=self.args.search_order) logger.info(f'[HTTP DRIVER] Found {len(possible_branches)} possible branches') - - symbolic_vars = None - sat = None - branch_found = False - + + sol = None + gave_up = False for branch in possible_branches: if not StrategyService.is_symbolic_branch(branch): continue - branch_found = True + if self.out_of_time(): + gave_up = True + sol = None + break sat, sol = StrategyService.solve_branch(branch) - + self.stats['solver_calls'] += 1 + if sat == SATResult.SAT and self.has_surrogate(sol): + # Rarely needed, and UNSAT under the restriction says nothing about the branch, so + # it is not recorded: the branch would be re-solved in every round. + sat, sol = StrategyService.solve_branch(branch, extra_exprs=self.sendable_strings(branch)) + self.stats['solver_calls'] += 1 if sat == SATResult.SAT: - symbolic_vars = branch.inputs break - - if not branch_found or sat == SATResult.UNSAT: - self.state.verdict = Verdict.SAFE - logger.info(f'[HTTP DRIVER] No symbolic branch found or UNSAT') - return Action.REPORTVERDICT - - if sat == SATResult.UNKNOWN: - logger.info(f'[HTTP DRIVER] SAT result is UNKNOWN') - self.state.verdict = Verdict.UNKNOWN + if sat != SATResult.UNSAT: + gave_up = True + sol = None + + if sol is None: + if gave_up: + logger.info(f'[HTTP DRIVER] No solution found, but the solver gave up on some branch') + if self.state.verdict != Verdict.VIOLATION: + self.state.verdict = Verdict.UNKNOWN + else: + logger.info(f'[HTTP DRIVER] No symbolic branch found or UNSAT') + if self.state.verdict != Verdict.VIOLATION: + self.state.verdict = Verdict.SAFE + self.stats['exhausted'] = not gave_up return Action.REPORTVERDICT sol_viz = [f'{key}: {val["plain_value"]}' for key, val in sol.items()] @@ -702,25 +768,104 @@ def retrieve_solution(self): def run(self): """Main execution method.""" - verdict = self.exec() + try: + verdict = self.exec() + except BaseException: + # Without this, the HTTP server would keep the explorer alive with no driver. + logger.exception('[HTTP DRIVER] The exploration failed') + verdict = Verdict.VIOLATION if self.state.verdict == Verdict.VIOLATION else Verdict.UNKNOWN + self.stats['crashed'] = True + self.write_stats(verdict) logger.info(f'[HTTP DRIVER] Verdict: {verdict}') self.kill_current_process() + @staticmethod + def sendable_strings(branch) -> list: + """ + Restricts the string inputs to chars that can be sent: a request is UTF-8, so a lone UTF-16 + surrogate (which Java strings, and so the solver's, may hold) cannot be part of it. + UNSAT under this restriction is not recorded as the branch being infeasible. + """ + from solver.ConstraintCache import get_constraint_cache + ctx = get_constraint_cache().shared_context + no_surrogates = z3.Star(z3.Union(z3.Range('\u0000', '\ud7ff', ctx=ctx), z3.Range('\ue000', '\uffff', ctx=ctx))) + names = {i.name for i in branch.inputs if i.name.startswith(DataTypes.STRING.value + '_') + or i.name.startswith('List_') and DataTypes.STRING.value in i.name} + return [z3.InRe(z3.String(name, ctx=ctx), no_surrogates) for name in sorted(names)] + + @staticmethod + def has_surrogate(sol: dict) -> bool: + """Whether a string of the solution holds a UTF-16 surrogate, see sendable_strings.""" + for val in sol.values(): + v = val.get('plain_value') + if z3.is_expr(v) and z3.is_string_value(v): + text = v.as_string() + if re.search(r'\\u\{d[89a-f][0-9a-f]{2}\}', text, re.IGNORECASE) \ + or any('\ud800' <= c <= '\udfff' for c in text): + return True + return False + + def out_of_time(self) -> bool: + return bool(self.args.time_limit) and time.perf_counter() - self.start_time > self.args.time_limit + + def load_sa_graph(self): + """Runs the static pre-analysis for the endpoint (--sa-entry) and adopts its graph.""" + self.sa.start() + self.sa.wait() + graph = self.sa.take_graph() + if graph is not None: + self.sa_graph = graph + self.stats['sa'] = self.sa.stats(0 if graph is not None else None) + + def final_verdict(self) -> Verdict: + """The verdict, with SAFE downgraded wherever the exploration could not back it.""" + verdict = self.state.verdict + if verdict != Verdict.SAFE or self.endpoint_id is None: + return verdict + tree = Database.instance().get_tree(self.endpoint_id) + if any(e.get('kind') == 'sa_desync' for e in tree.execution_errors): + logger.warning(f'[HTTP DRIVER] The SA walk fell out of step with the execution (see [DFS] SA desync)') + return Verdict.UNKNOWN + if tree.symbolic_context_loss or tree.symbolic_precision_loss: + logger.warning(f'[HTTP DRIVER] Found symbolic context or precision loss') + return Verdict.UNKNOWN + return verdict + + def write_stats(self, verdict: Verdict): + self.stats['verdict'] = verdict.name + self.stats['wall_s'] = round(time.perf_counter() - self.start_time, 2) + if self.endpoint_id is not None: + tree = Database.instance().get_tree(self.endpoint_id) + self.stats['sa_desyncs'] = sum(1 for e in tree.execution_errors if e.get('kind') == 'sa_desync') + self.stats['last_walk'] = dict(StrategyService.walk_stats) # see DFS.describe_walk + self.stats['symbolic_context_loss'] = tree.symbolic_context_loss + self.stats['symbolic_precision_loss'] = tree.symbolic_precision_loss + with open(os.path.join(self.args.logdir, 'http_stats.json'), 'w') as f: + json.dump(self.stats, f, indent=2) + def exec(self): """Runs the symbolic execution using HTTP requests.""" logger.info(f'[HTTP DRIVER] Beginning HTTP symbolic execution') - + self.start_time = time.perf_counter() + # Register symbolic variables from URL template self._register_symbolic_vars() - + + self.load_sa_graph() + # Main execution loop while True: + if self.out_of_time(): + logger.info(f'[HTTP DRIVER] Time limit of {self.args.time_limit}s reached') + if self.state.verdict != Verdict.VIOLATION: + self.state.verdict = Verdict.UNKNOWN + self.stats['timed_out'] = True + break + # Send HTTP request status, response = self.send_http_request() - - # Determine the next step - next_step = self.determine_next_step(status, response) - + self.stats['requests'] += 1 + # Select the endpoint (assuming single endpoint for HTTP targets) endpoints = Database.instance().get_endpoints() if len(endpoints) == 0: @@ -730,15 +875,21 @@ def exec(self): else: assert len(endpoints) == 1 self.endpoint_id = endpoints[0] - + + # Determine the next step + next_step = self.determine_next_step(status, response) + if next_step == Action.REPORTVERDICT: break if next_step == Action.SYMBOLICNEXT: logger.info(f'[HTTP DRIVER] Next step: SYMBOLIC EXPLORATION') - + next_step = self.retrieve_solution() if next_step == Action.REPORTVERDICT: + if self.out_of_time(): + logger.info(f'[HTTP DRIVER] Time limit of {self.args.time_limit}s reached') + self.stats['timed_out'] = True break logger.info(f'[HTTP DRIVER] Symbolic execution terminated') @@ -748,7 +899,9 @@ def exec(self): for v in violations: logger.info(f'[HTTP DRIVER] Violation: {[vv.__str__() for vv in v]}') - return self.state.verdict + verdict = self.final_verdict() + self.write_stats(verdict) + return verdict def kill_current_process(self): """Terminate the current process.""" diff --git a/symbolic-explorer/driver/SVCompDriver.py b/symbolic-explorer/driver/SVCompDriver.py index a0081e89..fa017db0 100644 --- a/symbolic-explorer/driver/SVCompDriver.py +++ b/symbolic-explorer/driver/SVCompDriver.py @@ -1,22 +1,36 @@ +import copy import glob +import sys import signal import subprocess import os import time from contextlib import contextmanager -from typing import List, Tuple +from typing import Any, List, Tuple from pathlib import Path -from solver.SolverHandler import SATResult +from solver.SolverHandler import SATResult, Z3Handler +from solver.ConstraintCache import get_constraint_cache from data.Database import Database from data.BinaryExecutionTree.Node import Node +from data.BinaryExecutionTree.Tree import Tree +from data.trace.Branch import Branch +from data.trace.Special import Special from strategy.StrategyService import StrategyService +from strategy import DFS +from data.StaticAnalysisGraph.SAGraph import SAGraph +from svcomp.StaticPreAnalysis import StaticPreAnalysis +from svcomp.PropertyPreCheck import PropertyPreCheck from enum import Enum -from svcomp.SymbolicStorage import SymbolicStorage +from svcomp.SymbolicStorage import SymbolicStorage, DataTypes +from svcomp.TestcaseStats import write_testcase_stats +from svcomp.WitnessWriter import write_violation_witness +from svcomp.ResourceBudget import ResourceBudget, DEFAULT_MEMORY_LIMIT_MB, DEFAULT_CPU_LIMIT_S, descendants +from svcomp.ExecutorCDS import ExecutorCDS from timing.TimingManager import TimingManager @@ -24,7 +38,46 @@ logger = log.get_logger() verdict_logger = log.get_verdict_logger() import platform - +import threading + + +# With --solver-rlimit, each time a branch's query runs out of its resource limit, the limit for +# that branch grows by this factor, up to MAX_RLIMIT (the largest value Z3 accepts). +RLIMIT_GROWTH = 5 +MAX_RLIMIT = 2**32 - 1 + +# Execution errors after which the tree does not cover every path the runs could take, so that an +# exhausted search proves nothing (see Tree.add, ConstraintService, run_testcase and run). +INCOMPLETE_SEARCH_ERRORS = ('tree_mismatch', 'trace_ingest_failed', 'no_trace', 'explorer_crash') +# A solution whose run does not reach the side of the branch it was solved for is re-solved up to +# this many times (by default), each time with the solutions that failed so far excluded. Only if +# all of them fail too is the branch given up on, which rules out SAFE (see run()). +DIVERGENCE_RETRIES = 3 +# A retry first looks for small values: every numeric input (integral or floating-point) is bounded +# by each of these magnitudes in turn, then left unbounded. The solver likes extreme values such as +# 2^31-1 for a size, and then the allocation fails (OutOfMemoryError) before the run gets to the +# branch; or a double of 2^67 where an approximate Math model is only right for small arguments. +RETRY_BOUNDS = (16, 256, 65536) +# Resource limit of each bounded query: BOUNDED_RLIMIT_FACTOR times what the branch's unbounded query +# used, but at least BOUNDED_RLIMIT (about 4 s). They only look for a nicer solution, so they must not +# use up the budget: bounding floating-point inputs can make a query far harder than the unbounded +# one (ReverseInterpolator_false: 261 s instead of 2 s). A fixed limit is too small where the query +# is expensive anyway (jbmc-regression/calc: 22M units unbounded, 25M bounded; without the bounded +# solution the run allocates an array of 2^31 - 1 and runs out of memory). +BOUNDED_RLIMIT = 10_000_000 +BOUNDED_RLIMIT_FACTOR = 2 +# A solution with a numeric input beyond this magnitude (or infinite) is re-solved with the inputs +# bounded by RETRY_BOUNDS first (see prefer_small): solvers like extreme values, and a loop bound of +# 2^31 - 1 makes the next run never end. +LARGE_INPUT = 65536 +# Execution errors after which an exhausted search proves nothing: a run was cut short, so the +# rest of its path was never explored. +CUT_SHORT_ERRORS = ('resource_limit', 'out_of_memory') + +# Executor JVM options besides the heap size. Every round starts a new, short-lived JVM, in which +# the C2 compiler spends more CPU time than it ever saves (more than a third of an executor's CPU +# time on MinePump/TCAS); C1 alone compiles fast enough. +EXECUTOR_JVM_OPTIONS = ['-XX:TieredStopAtLevel=1'] # The (unique) endpoint ID for the SV-COMP target. As each target is handled separately, this ID is always 0. ENDPOINT_ID = 0 @@ -34,6 +87,8 @@ class ExecutionStatus(Enum): SUCCESS = 1 ERROR = 2 TIMEOUT = 3 + # Stopped by the resource budget before it ended (see svcomp/ResourceBudget.py) + RESOURCE_LIMIT = 4 class Verdict(Enum): VIOLATION = "== ERROR" @@ -62,15 +117,67 @@ class State: def __init__(self): self.verdict = Verdict.UNKNOWN self.branch_to_explore: Node | None = None + # The solution the next run executes (for branch_to_explore) + self.solution: dict[str, Any] | None = None # see Z3Handler.solve + # Whether the solution for branch_to_explore came from a trace with an approximate model, + # so that the next run may legitimately diverge from it. + self.solution_is_guess: bool = False +def escape_java_string(s: str) -> str: + """Escapes a String input for the command line, undone by the executor's Verifier.nondetString: + a backslash as \\\\, and every character outside printable ASCII as \\uXXXX, so the value does + not depend on how the JVM decodes its arguments.""" + return ''.join('\\\\' if c == '\\' else c if ' ' <= c <= '~' else f'\\u{ord(c):04x}' for c in s) + + class SVCompDriver: + # Disabled default for drivers built without __init__ (tests) + cds = ExecutorCDS(enabled=False) + def __init__(self, args): self.args = args self.state = State() self.symbolicStorage = SymbolicStorage() self.shutdown_flag = False self.verification_category = VerificationCategory(self.args.property) + self.sa_graph: SAGraph = SAGraph() # static analysis graph, empty until the pre-analysis is adopted + self.sa = StaticPreAnalysis(args) + self.precheck = PropertyPreCheck(args, self.verification_category.value) + self.sa_adopted_round: int | None = None + self.round_idx = 0 + self.nr_solver_calls = 0 + self.solver_limit_hits = 0 # queries that ran out of their resource limit + # How often each branch (by gid) ran out of its resource limit; each time multiplies it. + self.rlimit_level: dict[int, int] = {} + self.divergence_retries = getattr(args, 'divergence_retries', DIVERGENCE_RETRIES) + # The solutions of each branch (by gid) whose runs did not reach it + self.failed_solutions: dict[int, list[dict[str, Any]]] = {} + # Branches given up on after their solutions kept failing, with the side that was not reached + # (True: branched), unless the solutions were only guesses (see check_divergence) + self.given_up: list[tuple[Node, bool]] = [] + self.divergence_retry_count = 0 # branches scheduled to be re-solved + # Resource units the unbounded query of each branch (by gid) used, see bounded_rlimit + self.query_cost: dict[int, int] = {} + # A run whose solution left irrelevant constraints out (see check_divergence), to be + # checked against the next walk: (target, solution, guess, reason) + self.pending_projected: tuple[Node, dict[str, Any] | None, bool, str] | None = None + self.projected_reached = 0 # such runs that reached the target on another path + DFS.SLICING = not getattr(args, 'no_sa_slicing', False) + self.budget = ResourceBudget(memory_limit_mb=getattr(args, 'memory_limit', None) or DEFAULT_MEMORY_LIMIT_MB, + cpu_limit_s=getattr(args, 'cpu_limit', None) or DEFAULT_CPU_LIMIT_S, + cpu_used_s=getattr(args, 'cpu_used', None) or 0.0) + self.budget.on_emergency = self.end_run_now + # Only one verdict is reported, by the main thread or, in an emergency, by the monitor + self.verdict_lock = threading.Lock() + self.verdict_reported = False + self.stopped_by: str | None = None # why the budget stopped the last executor run, if it did + Tree.LOOP_STATE_MATCHING = not getattr(args, 'no_loop_state_matching', False) + # AppCDS archive of the classes every executor JVM loads (see svcomp/ExecutorCDS.py) + # In the log directory, so that a run that is killed leaves no files behind elsewhere + logdir = getattr(args, 'logdir', None) + self.cds = ExecutorCDS(enabled=not getattr(args, 'no_executor_cds', False), + base_dir=logdir if logdir and os.path.isdir(logdir) else None) @contextmanager def pushd(self, dirname): @@ -82,19 +189,28 @@ def pushd(self, dirname): finally: os.chdir(original_dir) - def build_command(self,javaagent:str, config:str, Z3_DIR: str, port, cp, java_path:str = 'java', mem:int=32) -> List[str]: - """Builds the Java command list with given parameters.""" + def build_command(self,javaagent:str, config:str, Z3_DIR: str, port, cp, java_path:str = 'java', heap_mib: int | None = None) -> List[str]: + """Builds the Java command list with given parameters. The heap size defaults to the + executor's share of the memory budget.""" classpath_separator = ";" if platform.system() == "Windows" else ":" + class_path = list(cp) + [Z3_DIR] + cds_options = [] + if classpath_separator == ':': + class_path = self.cds.class_path(class_path, javaagent) + cds_options = self.cds.jvm_options(self.round_idx) cmd = [ java_path, - f'-Xmx{mem}g', + f'-Xmx{heap_mib or self.budget.heap_mib}m', + *EXECUTOR_JVM_OPTIONS, + *cds_options, f'-Dconfig.path={config}', + *(['-DloopSummaries=false'] if getattr(self.args, 'no_loop_summaries', False) else []), f'-Dexplorer.port={port}', f'-javaagent:{javaagent}', f"-Djava.library.path={Z3_DIR}", '-cp', - f'{classpath_separator.join(cp)}{classpath_separator}{Z3_DIR}', + classpath_separator.join(class_path), '-ea', 'Main' ] @@ -103,21 +219,30 @@ def build_command(self,javaagent:str, config:str, Z3_DIR: str, port, cp, java_p sym_vars = [] for var in self.symbolicStorage.vars.values(): val = var.newValue if var.newValue is not None else var.value + if var.dType == DataTypes.STRING and val is not None: + val = escape_java_string(val) sym_vars.append(f'-Dswat.input.{var.dType.value}_{var.idx}={val}') - cmd[3:3] = sym_vars + at = cmd.index(f'-Dconfig.path={config}') + 1 + cmd[at:at] = sym_vars return cmd def run_command_with_timeout(self, cmd: List[str]) -> Tuple[ExecutionStatus, List[str]]: """ Executes the given command and returns output from both STDOUT and STDERR. - Todo: What about timeouts? + There is no timeout of its own: the resource budget stops the run when the memory or + CPU time of the whole verification run gets close to its limit (RESOURCE_LIMIT). """ logger.info(f'[SVCOMP] Executing target') output = [] with subprocess.Popen(cmd, stdout=subprocess.PIPE,stderr=subprocess.STDOUT, bufsize=1, universal_newlines=True) as proc: try: - stdout, _ = proc.communicate() #timeout=timeout + with self.budget.executor(proc) as stopped_by: + stdout, _ = proc.communicate() output = stdout.splitlines() + self.stopped_by = stopped_by() + if self.stopped_by: + logger.warning(f'[SVCOMP] Executor stopped by the resource budget ({self.stopped_by})') + return ExecutionStatus.RESOURCE_LIMIT, output return ExecutionStatus.SUCCESS, output except subprocess.TimeoutExpired: @@ -132,15 +257,37 @@ def run_command_with_timeout(self, cmd: List[str]) -> Tuple[ExecutionStatus, Lis return ExecutionStatus.ERROR, output + [str(e)] - def determine_next_step(self, status: ExecutionStatus, output: List[str] ) -> Action: - """Determines the next step based on the execution status.""" - if status == ExecutionStatus.SUCCESS: + def determine_next_step(self, status: ExecutionStatus, output: List[str], received_trace: bool = True) -> Action: + """Determines the next step based on the execution status. - + received_trace says whether the run sent a trace. The executor sends one whenever the entry + method was entered (also when it ends in an exception or System.exit), so a run without one + never reached it, e.g. because the JVM failed to start (VerifyError, missing main class) or + the agent failed: such a run proves nothing. + + A run the resource budget stopped (RESOURCE_LIMIT) still reports a violation it printed + before. Otherwise it ended without a trace, so its path stays unexplored: on a CPU stop the + verdict is UNKNOWN, on a memory stop exploration goes on with other branches. + """ + if status == ExecutionStatus.SUCCESS or status == ExecutionStatus.RESOURCE_LIMIT: + + # A StackOverflowError reached the target in this execution: its outcome depends on the + # JVM's stack size, which the task does not fix, so a violation it shows is not reported. + # The trace carries context loss, so the search goes on but cannot end in SAFE. + stack_overflow = self.round_has_stack_overflow() + if stack_overflow: + output = self.drop_violations(output) + out_of_memory_recorded = False for l in output: + if "java.lang.OutOfMemoryError" in l and not out_of_memory_recorded: + # The run was cut short by the JVM's memory limit, which the task does not have + logger.warning(f'[SVCOMP] Executor ran out of memory: {l.strip()}') + Database.instance().record_execution_error(ENDPOINT_ID, 'out_of_memory', l.strip()) + out_of_memory_recorded = True if "java.lang.AssertionError: [SWAT]" in l: - # Internal assertion error in the DSE + # Internal assertion error in the DSE logger.critical(f'[SVCOMP] SWAT Assertion failed: {l}') + Database.instance().record_execution_error(ENDPOINT_ID, 'swat_assertion', l.strip()) self.state.verdict = Verdict.UNKNOWN return Action.REPORTVERDICT if "java.lang.AssertionError" in l and self.verification_category == VerificationCategory.VALID_ASSERT_PRP: @@ -154,19 +301,54 @@ def determine_next_step(self, status: ExecutionStatus, output: List[str] ) -> Ac if ("[SWAT] Uncaught top-level RuntimeException in symbolic execution" in l or "[SWAT] Uncaught top-level checked exception in symbolic execution" in l) and self.verification_category == VerificationCategory.VALID_ASSERT_PRP: logger.info(f'[SVCOMP] Uncaught Exception: {l}') Database.instance().record_uncaught_exception(ENDPOINT_ID) + if received_trace: + # The path ended here (dfs: an irrelevant branch whose region it ended in + # is relevant after all) + Database.instance().mark_last_leaf_abnormal(ENDPOINT_ID) + if "[SWAT] Deadlock:" in l: + # The executor ended a run whose threads were deadlocked (DeadlockWatchdog): the + # execution can do nothing more, so its trace is complete. + logger.info(f'[SVCOMP] {l.strip()}') if "[SWAT Exception]:" in l: logger.info(f'[SVCOMP] SWAT Exception: {l}') + Database.instance().record_execution_error(ENDPOINT_ID, 'swat_exception', l.strip()) self.state.verdict = Verdict.UNKNOWN return Action.REPORTVERDICT + if status == ExecutionStatus.RESOURCE_LIMIT: + Database.instance().record_execution_error(ENDPOINT_ID, 'resource_limit', + f'round {self.round_idx}: executor stopped ({self.stopped_by})') + if self.stopped_by == 'cpu' or Database.instance().count_symbolic_vars(ENDPOINT_ID) == 0: + self.state.verdict = Verdict.UNKNOWN + return Action.REPORTVERDICT + return Action.SYMBOLICNEXT - if len(Database.instance().get_tree(ENDPOINT_ID).symbolic_vars) == 0: + if not received_trace: + logger.warning(f'[SVCOMP] The target sent no trace: it did not reach the entry method') + Database.instance().record_execution_error(ENDPOINT_ID, 'no_trace', 'the target sent no trace' + + (f' (branch {self.no_trace_branch})' if getattr(self, 'no_trace_branch', None) is not None else '')) + self.state.verdict = Verdict.UNKNOWN + return Action.REPORTVERDICT + + ingest_failures = Database.instance().get_execution_errors(ENDPOINT_ID, 'trace_ingest_failed') + if ingest_failures: + # The tree lacks (part of) a trace, so searching on it would be incomplete. + logger.critical(f'[SVCOMP] A trace could not be added to the tree: {ingest_failures[0]["message"]}') + self.state.verdict = Verdict.UNKNOWN + return Action.REPORTVERDICT + + if Database.instance().count_symbolic_vars(ENDPOINT_ID) == 0: + if stack_overflow: + # The only execution there is overflowed the stack: its outcome is not a verdict + logger.info(f'[SVCOMP] No symbolic vars found, but the execution overflowed the stack') + self.state.verdict = Verdict.UNKNOWN + return Action.REPORTVERDICT # No symbolic vars found, safe to assume no violation logger.info(f'[SVCOMP] No symbolic vars found') self.state.verdict = Verdict.NO_SYMBOLIC_VARS return Action.REPORTVERDICT - logger.info(f'[SVCOMP] Found {len(Database.instance().get_tree(ENDPOINT_ID).symbolic_vars)} symbolic vars') + logger.info(f'[SVCOMP] Found {Database.instance().count_symbolic_vars(ENDPOINT_ID)} symbolic vars') # No assertion error found but symbolic variables present, continue with symbolic exploration return Action.SYMBOLICNEXT @@ -177,23 +359,64 @@ def determine_next_step(self, status: ExecutionStatus, output: List[str] ) -> Ac return Action.REPORTVERDICT + def is_violation_line(self, line: str) -> bool: + """Whether the executor output line reports a violation of the checked property.""" + if self.verification_category == VerificationCategory.VALID_ASSERT_PRP: + return "java.lang.AssertionError" in line and "java.lang.AssertionError: [SWAT]" not in line + if self.verification_category == VerificationCategory.NO_RUNTIME_EXCEPTION_PRP: + return "[SWAT] Uncaught top-level RuntimeException in symbolic execution" in line + return False + + def drop_violations(self, output: List[str]) -> List[str]: + """Removes the violation lines of an execution whose outcome is not a verdict.""" + violations = [l for l in output if self.is_violation_line(l)] + if violations: + logger.warning(f'[SVCOMP] Violation after a stack overflow, not reported: {violations[0].strip()}') + Database.instance().record_execution_error(ENDPOINT_ID, 'stack_overflow_violation', violations[0].strip()) + return [l for l in output if not self.is_violation_line(l)] + + def round_has_stack_overflow(self) -> bool: + """Whether the trace of this round (if one arrived) reports a stack overflow in the target.""" + witness_data = Database.instance().get_witness_data(ENDPOINT_ID) + return witness_data is not None and Database.instance().is_stack_overflow_trace( + ENDPOINT_ID, witness_data['trace_id']) + def log_output(self, output: List[str]): logger.info(f'[SVCOMP] Java Output Begin') for line in output: - logger.info(f'[SWAT] --> {line.strip()}') + logger.info(f'[SWAT] --> {line.strip()}', extra=log.RELAYED) logger.info(f'[SVCOMP] Java Output End') def run_testcase(self, java_path, agentpath: str, configpath: str, z3path, port, cp) -> Verdict: """Runs the testcase using the constructed Java command.""" + # Static property pre-check: if it proves the property, round 0 only cross-checks it. + self.precheck.run() + + # Static pre-analysis: an external Java subprocess plus loading its graph. By default + # exploration waits for it, and the wait is its own timing stage (otherwise it would be + # folded into the symbolic explorer residual). With --parallel-sa it runs in the background + # while exploration already starts without pruning, and the graph is adopted in + # retrieve_solution() once it is ready. + self.sa.start() + if not self.args.parallel_sa: + self.sa.wait() + self.adopt_sa_graph() + next_step = Action.RANDOMNEXT - round_idx = 0 + self.round_idx = 0 while True: logger.info(f'[SVCOMP] {"="*73}') - logger.info(f'[SVCOMP] {"="*30} ROUND ({round_idx:03}) {"="*30}') + logger.info(f'[SVCOMP] {"="*30} ROUND ({self.round_idx:03}) {"="*30}') logger.info(f'[SVCOMP] {"="*73}') + # Only a trace of this round may provide the witness data of the violating execution + Database.instance().clear_witness_data(ENDPOINT_ID) + + # Dump the executor's class data sharing archive once the run proves long enough + self.cds.prepare_round(self.round_idx, java_path, lambda: [f'-Xmx{self.budget.heap_mib}m', *EXECUTOR_JVM_OPTIONS]) + # Build the java command to run DSE command: List[str] = self.build_command(javaagent=agentpath, config=configpath, Z3_DIR=z3path, port=port, cp=cp, java_path=java_path) @@ -202,27 +425,41 @@ def run_testcase(self, java_path, agentpath: str, configpath: str, z3path, port, # Run the target with DSE - Time the symbolic executor status: ExecutionStatus output: List[str] + traces_before = Database.instance().trace_count() executor_start = time.perf_counter() status, output = self.run_command_with_timeout(cmd=command) executor_duration = time.perf_counter() - executor_start TimingManager.instance().record_executor_time(executor_duration) + # A run stopped by the resource budget sends no trace; any other run without one is + # handled by determine_next_step (no_trace). + received_trace = Database.instance().trace_count() > traces_before + self.no_trace_branch = None + # check if the target explored the branch as expected - if self.state.branch_to_explore: - for branch in Database.instance().get_trace(-1): # most recent trace - if branch.id == self.state.branch_to_explore.id: - if branch.has_branched == (self.state.branch_to_explore.branched is not None): - logger.error(f'[SVCOMP] SWAT Assertion failed: Target did not explore branch {branch.id} as expected! Branch unexpectedly taken/skipped.') - break + if self.state.branch_to_explore and status != ExecutionStatus.RESOURCE_LIMIT: + if received_trace: + self.check_divergence(Database.instance().get_trace(-1)) # most recent trace else: - logger.error(f'[SVCOMP] SWAT Assertion failed: Target did not explore branch {self.state.branch_to_explore.id} as expected! Branch does not appear in trace.') + # Whether the run took the side its solution was solved for is unknown; the + # run ends UNKNOWN (determine_next_step) + self.no_trace_branch = self.state.branch_to_explore.id self.state.branch_to_explore = None + self.state.solution = None + self.state.solution_is_guess = False self.log_output(output) logger.info(f'[STATUS] {status}') - next_step: Action = self.determine_next_step(status, output) + next_step: Action = self.determine_next_step(status, output, received_trace) + # A run that sent no trace never reached main: the proof says nothing about it + if self.precheck.proven and received_trace: + self.round_idx += 1 + return self.verdict_with_proof(next_step) - round_idx += 1 + # Visualize DB tree + # print("Plotting DB Tree...", flush=True) + # Database.instance().get_tree(0).plot_tree(self.round_idx) + self.round_idx += 1 if next_step == Action.REPORTVERDICT: logger.info(f'[SVCOMP] Finised testcase analysis, reporting verdict') @@ -235,6 +472,11 @@ def run_testcase(self, java_path, agentpath: str, configpath: str, z3path, port, # Explorer time is computed as residual in TimingManager.get_aggregates() # (total_time - executor_time - solver_time - witness_times) next_step = self.retrieve_solution() + if self.budget.cpu_exceeded.is_set() and next_step != Action.REPORTVERDICT: + logger.warning('[SVCOMP] CPU time deadline reached, stopping exploration') + self.state.verdict = Verdict.UNKNOWN + return self.state.verdict + if next_step == Action.REPORTVERDICT: return self.state.verdict @@ -244,44 +486,313 @@ def run_testcase(self, java_path, agentpath: str, configpath: str, z3path, port, - def retrieve_solution(self): - possible_branches = StrategyService.select_branch(endpoint_id=ENDPOINT_ID) - logger.info(f'[SYMBOLIC EXPLORATION] Found {len(possible_branches)} possible branches') - logger.info(f'[SYMBOLIC EXPLORATION] Possible branch IDs: {[b.id for b in possible_branches]}') - symbolic_vars = None - sat = None - branch_found = False - for branch in possible_branches: - #logger.info(f'[SYMBOLIC EXPLORATION] Checking branch {branch.id}, kind={branch.kind}') - if not StrategyService.is_symbolic_branch(branch): - #logger.info(f'[SYMBOLIC EXPLORATION] Skipping non-symbolic branch {branch.id}, constraint has no symbolic vars') + def verdict_with_proof(self, next_step: Action) -> Verdict: + """The verdict after round 0 once the pre-check proved the property: SAFE, as no execution + can violate it, whatever the exploration could model. A violation found in round 0 + contradicts the proof, and then neither is trusted.""" + if next_step == Action.REPORTVERDICT and self.state.verdict == Verdict.VIOLATION: + logger.error(f'[PRECHECK] Round 0 found a violation of a property the pre-check proved; reporting UNKNOWN') + Database.instance().record_execution_error(ENDPOINT_ID, 'precheck_contradiction', self.precheck.reason or '') + self.state.verdict = Verdict.UNKNOWN + else: + logger.info(f'[PRECHECK] Property proven statically: {self.precheck.reason}') + self.state.verdict = Verdict.SAFE + return self.state.verdict + + def check_divergence(self, trace: list[Branch | Special]) -> None: + """Checks that the last run took the side of branch_to_explore its solution was solved for. + + If not, the branch is re-solved with the failed solutions excluded (see solve_retry), up to + divergence_retries times; only then is it given up on (give_up_branch). + """ + target = self.state.branch_to_explore + assert target is not None, "check_divergence without a branch to explore" + # The tree has one level per trace element (but for UNMODELLED_EXCEPTION markers, see + # Tree.structural), so the target is at its depth in the trace. + # Matching by id alone would be wrong: ids repeat within a trace (all keys of a + # LOOKUPSWITCH share one, TABLESWITCH case numbers wrap at 16, loops, recursion). + trace = [element for element, _ in Tree.structural(trace)] + depth = 0 + node = getattr(target, 'parent', None) + while node is not None: + depth += 1 + node = node.parent + if depth < len(trace) and trace[depth].id == target.id: + if trace[depth].has_branched != (target.branched is not None): + return + reason = 'Branch unexpectedly taken/skipped.' + else: + reason = 'Branch does not appear in trace.' + if target.sliced_query: + # The query left out the constraints of irrelevant branches, so the run may well have + # taken other irrelevant decisions and reached the branch on another path. Whether it + # did is known after the next walk, which identifies paths by their relevant decisions. + self.pending_projected = (target, self.state.solution, self.state.solution_is_guess, reason) + return + self.divergence_failed(target, self.state.solution, self.state.solution_is_guess, reason) + + def check_projected_divergence(self) -> bool: + """After a walk: whether the run of a solution whose query left out irrelevant constraints + reached its branch's side on some path with the same relevant decisions. If not, it is a + divergence like any other (divergence_failed). Returns whether a divergence was handled, + which changes the candidates.""" + if self.pending_projected is None: + return False + target, solution, guess, reason = self.pending_projected + self.pending_projected = None + ident = target.ident + if ident is not None and Database.instance().projected_covered(ENDPOINT_ID, ident, target.branched is None): + self.projected_reached += 1 + logger.info(f'[SVCOMP] Branch {target.id} reached on another path with the same relevant decisions') + return False + self.divergence_failed(target, solution, guess, reason + ' (sliced query, not reached with the same relevant decisions either)') + return True + + def divergence_failed(self, target: Node, solution: dict[str, Any] | None, guess: bool, reason: str) -> None: + """A run did not reach the side of `target` its solution was solved for: re-solve it without + the failed solutions, or give up on it after divergence_retries of them.""" + if guess: + Database.instance().record_expected_divergence(ENDPOINT_ID) + failed = self.failed_solutions.setdefault(target.gid, []) + failed.append(solution or {}) + if len(failed) <= self.divergence_retries: + logger.info(f'[SVCOMP] Diverged from branch {target.id}{" (approximate model)" if guess else ""}: {reason} ' + f'Re-solving it without the failed solutions ({len(failed)}/{self.divergence_retries}).') + Database.instance().drop_solution(target.gid) + self.divergence_retry_count += 1 + return + self.give_up_branch(target, guess, reason) + + def give_up_branch(self, target: Node, guess: bool, reason: str) -> None: + """Takes a branch whose solutions kept missing it out of the search. + + A divergence is a bug, unless the solution relied on an approximate model: then it was only a + guess, and the divergence is expected (that run has precision loss, so it cannot end SAFE + anyway). That is logged without the "SWAT Assertion failed" marker the harness counts as an + internal error. + """ + Database.instance().retire_branch(target.gid) + tries = len(self.failed_solutions.get(target.gid, [])) + if guess: + logger.info(f'[SVCOMP] Diverged from branch {target.id}: its solution relied on an approximate model (expected). {reason} Giving up after {tries} solutions.') + return + logger.error(f'[SVCOMP] SWAT Assertion failed: Target did not explore branch {target.id} as expected! {reason} Giving up after {tries} solutions.') + # Its unexplored side is never tried again: an exhausted search no longer proves anything, + # unless some other run reaches that side after all (see run()). + Database.instance().record_execution_error(ENDPOINT_ID, 'unexpected_divergence', f'branch {target.id}: {reason}') + # The side open when the branch was solved, on the tree's own node, which later runs extend + self.given_up.append((getattr(target, 'live', target), target.branched is None)) + + def unreached_given_up_branches(self) -> list[Node]: + """The given-up branches whose unexplored side no run has reached by now. A side reached + later is in the tree, so the search covers what lies behind it like any other; so is one + reached on a path with the same relevant decisions (see dfs).""" + return [n for n, branched in self.given_up if (n.branched if branched else n.skipped) is None + and not (n.ident is not None + and Database.instance().projected_covered(ENDPOINT_ID, n.ident, branched))] + + @staticmethod + def is_large(sol: dict) -> bool: + """Whether a solution has a numeric input beyond LARGE_INPUT in magnitude: an integral one, or + a floating-point one (infinities included, NaN not).""" + import z3 + values = [v.get('plain_value') for v in sol.values() if isinstance(v, dict)] if isinstance(sol, dict) else [] + for value in values: + if not z3.is_expr(value): + continue + if z3.is_bv_value(value): + n = value.as_signed_long() + elif z3.is_int_value(value): + n = value.as_long() + elif z3.is_fp_value(value): + # Infinities count as large, NaN does not. (The FPRef operators use value's context.) + hi = z3.FPVal(LARGE_INPUT, fps=value.sort(), ctx=value.ctx) + lo = z3.FPVal(-LARGE_INPUT, fps=value.sort(), ctx=value.ctx) + if z3.is_true(z3.simplify(z3.Or(value > hi, value < lo))): + return True continue - branch_found = True - #logger.info(f'[SYMBOLIC EXPLORATION] Solving for branch {branch.id}') - sat, sol = StrategyService.solve_branch(branch) - + else: + continue + if abs(n) > LARGE_INPUT: + return True + return False + + def bounded_rlimit(self, branch: Node, rlimit: int | None) -> int: + """The resource limit of a query for the branch with bounded inputs (see BOUNDED_RLIMIT).""" + cap = max(BOUNDED_RLIMIT, BOUNDED_RLIMIT_FACTOR * self.query_cost.get(branch.gid, 0)) + return cap if not rlimit else min(rlimit, cap) + + def prefer_small(self, branch: Node, sol: dict, rlimit: int | None) -> dict: + """A solution for the branch with small inputs if there is one, else `sol`. + + Re-solves the same query with every numeric input bounded by RETRY_BOUNDS in turn; any + model of it is one of the query, so this only picks another execution of the same path + prefix, one that is cheaper to run. Bounded queries without a model prove nothing and are + not recorded (see solve_branch).""" + if not self.is_large(sol): + return sol + for bound in RETRY_BOUNDS: + extra = Z3Handler.bound_constraints(sol, bound) + if not extra: + return sol + sat, small = StrategyService.solve_branch(branch, solver_timeout_ms=None, solver_rlimit=self.bounded_rlimit(branch, rlimit), extra_exprs=extra, + approximate_rlimit=getattr(self.args, 'approximate_rlimit', None)) + self.nr_solver_calls += 1 if sat == SATResult.SAT: - logger.info(f'[SYMBOLIC EXPLORATION] Found solution for branch {branch.id} {"skipped" if branch.skipped is None else "branched"}') - symbolic_vars = branch.inputs - # remember which branch we want to explore - self.state.branch_to_explore = branch + logger.info(f'[SYMBOLIC EXPLORATION] Branch {branch.id}: smaller solution with inputs bounded by {bound}') + return small + return sol # still the recorded one: bounded queries without a model record nothing + + def solve_retry(self, branch: Node, rlimit: int | None) -> tuple[SATResult, dict]: + """Re-solves a branch whose solutions so far did not lead the run to it, excluding them. + + Small values are tried first (RETRY_BOUNDS), then any value. A bounded query that fails in + any way only moves on to the next bound. UNSAT without a bound means that no other solution + is left: the branch is given up on, as the encoding was evidently imprecise for it, the + branch is not claimed infeasible. + """ + failed = self.failed_solutions[branch.gid] + for bound in RETRY_BOUNDS + (None,): + extra = Z3Handler.retry_constraints(failed, bound) + sat, sol = StrategyService.solve_branch(branch, solver_timeout_ms=None, + solver_rlimit=rlimit if bound is None else self.bounded_rlimit(branch, rlimit), extra_exprs=extra, + approximate_rlimit=getattr(self.args, 'approximate_rlimit', None)) + self.nr_solver_calls += 1 + if sat == SATResult.SAT or bound is None: break - logger.debug(f'[SYMBOLIC EXPLORATION] No solution ({sat}) found for branch {branch.id}') - - if not branch_found or sat == SATResult.UNSAT: - self.state.verdict = Verdict.SAFE - logger.info(f'[SYMBOLIC EXPLORATION] No symbolic branch found or UNSAT') - return Action.REPORTVERDICT - - if sat == SATResult.UNKNOWN: - logger.info(f'[SYMBOLIC EXPLORATION] SAT result is UNKNOWN') - self.state.verdict = Verdict.UNKNOWN + logger.debug(f'[SYMBOLIC EXPLORATION] Retry of branch {branch.id}: no solution with inputs bounded by {bound} ({sat})') + if sat == SATResult.UNSAT: + self.give_up_branch(branch, Database.instance().is_approximate_trace(ENDPOINT_ID, branch.trace_id), + 'No other solution left.') + return sat, sol + + def adopt_sa_graph(self) -> None: + """Switches branch selection to the pre-analysis graph once it is ready (at most once). + + Switching mid-exploration is safe: every selection walks the tree from its root, and the + rounds before only explored more than pruning would have. + """ + graph = self.sa.take_graph() + if graph is not None: + logger.info(f'[EXPLORER] Using static pre-analysis graph from round {self.round_idx} on.') + self.sa_graph = graph + self.sa_adopted_round = self.round_idx + + def solver_rlimit_for(self, branch: Node) -> int | None: + """The resource limit for the next query of this branch: the base limit, grown by + RLIMIT_GROWTH for each time the branch already ran out of it (None: unlimited).""" + if not self.args.solver_rlimit: + return None + return min(self.args.solver_rlimit * RLIMIT_GROWTH ** self.rlimit_level.get(branch.gid, 0), MAX_RLIMIT) + + def solve_candidates(self, candidates: list[Node]) -> tuple[Node | None, dict, bool]: + """Solves the candidate branches until one is SAT. + + Returns the branch and its solution, or (None, {}, gave_up) if none is SAT, where gave_up + says whether the solver failed to decide some branch, so that no SAFE verdict may follow. + + With a solver rlimit, a branch that runs out of it is skipped for now and retried with a + larger limit once every cheaper query has been tried: branches are taken in order of how + often they ran out already (a stable sort, so DFS order otherwise), and after a full pass + the ones that ran out are tried again, until one is SAT or all are decided. A branch that + runs out even at MAX_RLIMIT is given up on. + + Choice branches (which the executor adds to enumerate the values of a symbolic operand, + e.g. a radix) go after the program's own branches of the same level: a program branch + leads somewhere new on the path it is on, while a choice branch starts over with another + value of the operand. + + The limit only serves to let other branches go first. So once a retry pass is left with a + single branch, that one is solved without a limit: escalating it step by step would only + redo the same work at each step. + """ + gave_up = False + pending = sorted(candidates, key=lambda b: (self.rlimit_level.get(b.gid, 0), b.choice)) + retry = False + while pending: + limited = [] + alone = retry and len(pending) == 1 + for branch in pending: + if self.budget.cpu_exceeded.is_set(): + return None, {}, True + rlimit = None if alone else self.solver_rlimit_for(branch) + with self.budget.solver(get_constraint_cache().shared_context) as interrupted_by: + if branch.gid in self.failed_solutions: + sat, sol = self.solve_retry(branch, rlimit) + else: + sat, sol = StrategyService.solve_branch(branch, solver_timeout_ms=None, solver_rlimit=rlimit, + approximate_rlimit=getattr(self.args, 'approximate_rlimit', None)) + self.nr_solver_calls += 1 + self.query_cost[branch.gid] = Z3Handler.last_rlimit_used + if interrupted_by(): + logger.info(f'[SYMBOLIC EXPLORATION] Query for branch {branch.id} stopped by the resource budget ({interrupted_by()})') + gave_up = True + continue + if sat == SATResult.SAT: + if not getattr(self.args, 'no_prefer_small_models', False): + with self.budget.solver(get_constraint_cache().shared_context): + sol = self.prefer_small(branch, sol, rlimit) + return branch, sol, gave_up + if sat == SATResult.LIMIT: + self.solver_limit_hits += 1 + if rlimit is None or rlimit >= MAX_RLIMIT: + logger.info(f'[SYMBOLIC EXPLORATION] Branch {branch.id} exceeds even the largest resource limit, giving up on it') + gave_up = True + continue + self.rlimit_level[branch.gid] = self.rlimit_level.get(branch.gid, 0) + 1 + logger.info(f'[SYMBOLIC EXPLORATION] Branch {branch.id} ran out of resource limit {rlimit}, retrying later with {self.solver_rlimit_for(branch)}') + limited.append(branch) + elif sat == SATResult.UNKNOWN: + logger.info(f'[SYMBOLIC EXPLORATION] Solver could not decide branch {branch.id}') + gave_up = True + else: + logger.debug(f'[SYMBOLIC EXPLORATION] No solution ({sat}) found for branch {branch.id}') + pending = limited + retry = True + return None, {}, gave_up + + def retrieve_solution(self): + self.adopt_sa_graph() + possible_branches = StrategyService.select_branch(endpoint_id=ENDPOINT_ID, sa_node=self.sa_graph.entry_node, rank_by_distance=self.args.sa_rank_distance, + search_order=getattr(self.args, 'search_order', 'coverage')) + if self.check_projected_divergence(): + # The branch is a candidate again (or given up on): walk again. + possible_branches = StrategyService.select_branch(endpoint_id=ENDPOINT_ID, sa_node=self.sa_graph.entry_node, rank_by_distance=self.args.sa_rank_distance, + search_order=getattr(self.args, 'search_order', 'coverage')) + logger.info(f'[SYMBOLIC EXPLORATION] Found {len(possible_branches)} possible branches') + logger.debug(f'[SYMBOLIC EXPLORATION] Possible branch IDs: {[b.id for b in possible_branches]}') + candidates = [b for b in possible_branches if StrategyService.is_symbolic_branch(b)] + branch, sol, solver_gave_up = self.solve_candidates(candidates) + + if branch is None: + if solver_gave_up: + # Some branch might still be feasible, the solver just could not tell. + logger.info(f'[SYMBOLIC EXPLORATION] No solution found, but the solver gave up on some branch') + self.state.verdict = Verdict.UNKNOWN + else: + logger.info(f'[SYMBOLIC EXPLORATION] No symbolic branch found or UNSAT') + self.state.verdict = Verdict.SAFE return Action.REPORTVERDICT - + + logger.info(f'[SYMBOLIC EXPLORATION] Found solution for branch {branch.id} {"skipped" if branch.skipped is None else "branched"}') + # remember which branch we want to explore, as it is now: the node itself is the tree's + # and gets its other side once the next trace arrives (check_divergence needs this one) + self.state.branch_to_explore = copy.copy(branch) + # the tree's own node, through which a given-up branch's side reached later is seen (run()) + self.state.branch_to_explore.live = branch + self.state.solution = sol + # A solution is a guess if its trace used an approximate model, which is precision loss, or + # if its query is over a sampled Math function: that function is in a branch constraint of + # the trace, so the trace is precision loss as well. In both cases a divergence cannot lead + # to a false SAFE. A solution from a trace whose Math results never reach the query is + # exact, and a divergence from it is an error. + self.state.solution_is_guess = (Database.instance().is_approximate_trace(ENDPOINT_ID, branch.trace_id) + or StrategyService.is_approximate_query(branch)) + sol_viz = [f'{key}: {val["plain_value"]}' for key, val in sol.items()] logger.info(f'[SYMBOLIC EXPLORATION] SAT solution: {sol}') logger.info(f'[SYMBOLIC EXPLORATION] Found new solution: {sol_viz}') - self.symbolicStorage.register_vars(symbolic_vars) + self.symbolicStorage.register_vars(branch.inputs) self.symbolicStorage.store_solution(sol) return Action.SYMBOLICNEXT @@ -298,28 +809,81 @@ def run(self): # Start total timing TimingManager.instance().start_total_timer() + self.budget.start() - verdict = self.run_testcase(java_path=self.args.java_path, agentpath=self.args.agent, configpath=self.args.config, z3path=self.args.z3dir, port=self.args.port, cp=self.args.classpath) + try: + verdict = self.run_testcase(java_path=self.args.java_path, agentpath=self.args.agent, configpath=self.args.config, z3path=self.args.z3dir, port=self.args.port, cp=self.args.classpath) + except Exception as e: + # The exploration is incomplete: report UNKNOWN now, instead of nothing at all. + logger.exception(f'[SVCOMP] Exploration failed') + Database.instance().record_execution_error(ENDPOINT_ID, 'explorer_crash', f'{type(e).__name__}: {e}') + verdict = Verdict.UNKNOWN + finally: + self.cds.cleanup() + # Exploration can finish before the pre-analysis: stop it, so no extractor outlives us. + self.sa.cancel() + if self.sa.duration is not None: + TimingManager.instance().record_static_analysis_time(self.sa.duration, blocking=not self.args.parallel_sa) - if (verdict == Verdict.SAFE) and Database.instance().get_tree(ENDPOINT_ID).symbolic_context_loss: + # The soundness downgrades below guard a SAFE that rests on the exploration. A SAFE from the + # property pre-check rests on a static proof for all executions instead, so they do not apply. + downgrade = not (verdict == Verdict.SAFE and self.precheck.proven) + + if downgrade and (verdict == Verdict.SAFE) and Database.instance().get_tree(ENDPOINT_ID).symbolic_context_loss: logger.warning(f'[SVCOMP] Found symbolic context loss') verdict = Verdict.UNKNOWN - if (verdict == Verdict.SAFE) and Database.instance().get_tree(ENDPOINT_ID).symbolic_precision_loss: + if downgrade and (verdict == Verdict.SAFE) and Database.instance().get_tree(ENDPOINT_ID).symbolic_precision_loss: logger.warning(f'[SVCOMP] Found symbolic precision loss') verdict = Verdict.UNKNOWN - if (verdict == Verdict.SAFE) and Database.instance().get_tree(ENDPOINT_ID).uncaught_exceptions > 0: + if self.given_up: + unreached = self.unreached_given_up_branches() + if downgrade and (verdict == Verdict.SAFE) and unreached: + logger.warning(f'[SVCOMP] No solution reached the side of branch(es) {[n.id for n in unreached]} it was solved for') + verdict = Verdict.UNKNOWN + elif not unreached: + logger.info(f'[SVCOMP] Other runs reached the sides of all given-up branches') + + if downgrade and (verdict == Verdict.SAFE) and any(e.get('kind') == 'sa_desync' + for e in Database.instance().get_tree(ENDPOINT_ID).execution_errors): + logger.warning(f'[SVCOMP] The SA walk fell out of step with the execution (see [DFS] SA desync)') + verdict = Verdict.UNKNOWN + + # An incomplete search rules out SAFE even after the pre-check's proof: the run that the + # proof's round-0 contradiction check rests on may not have been fully ingested. + incomplete = [e for e in Database.instance().get_tree(ENDPOINT_ID).execution_errors + if e.get('kind') in INCOMPLETE_SEARCH_ERRORS] + if (verdict == Verdict.SAFE) and incomplete: + logger.warning(f'[SVCOMP] The search is incomplete ({incomplete[0]["kind"]}: {incomplete[0]["message"]})') + verdict = Verdict.UNKNOWN + + if (verdict == Verdict.SAFE) and any(e.get('kind') in CUT_SHORT_ERRORS + for e in Database.instance().get_tree(ENDPOINT_ID).execution_errors): + logger.warning(f'[SVCOMP] An execution was cut short by a memory limit, its path is unexplored') + verdict = Verdict.UNKNOWN + + if downgrade and (verdict == Verdict.SAFE) and Database.instance().get_tree(ENDPOINT_ID).uncaught_exceptions > 0: logger.warning(f'[SVCOMP] Found uncaught exceptions during symbolic execution') verdict = Verdict.UNKNOWN - if (verdict == Verdict.VIOLATION) and Database.instance().get_tree(ENDPOINT_ID).reference_semantic_change: - logger.warning(f'[SVCOMP] Found reference semantic change (user-de-interned strings compared via Objects.equals) - downgrading VIOLATION to UNKNOWN') + if (verdict in (Verdict.SAFE, Verdict.VIOLATION)) and Database.instance().get_tree(ENDPOINT_ID).reference_semantic_change: + logger.warning(f'[SVCOMP] Found reference semantic change (reference comparison decided by symbolic values SWAT does not encode) - downgrading {verdict.name} to UNKNOWN') verdict = Verdict.UNKNOWN if verdict == Verdict.NO_SYMBOLIC_VARS: verdict = Verdict.SAFE - verdict_logger.info(f'[VERDICT {self.verification_category.value}] {verdict.value}') + with self.verdict_lock: + if self.verdict_reported: + return + self.verdict_reported = True + verdict_logger.info(f'[VERDICT {self.verification_category.value}] {verdict.value}') + self.budget.stop() + logger.info(f'[EXPLORER] Symbolic execution terminated after {self.round_idx} iterations') + + # The witness is written after the verdict and never affects it + if verdict == Verdict.VIOLATION: + self.write_witness() # Stop total timing (don't print summary here - execution.py will print complete summary with witness timing) TimingManager.instance().stop_total_timer() @@ -329,9 +893,71 @@ def run(self): timing_file = os.path.join(log_dir, 'timing_data.json') TimingManager.instance().save_to_file(Path(timing_file)) + # Write the consolidated per-testcase statistics (verdict, soundness flags, missing + # invocations and the context-loss subset) so the analysis can rely on structured data. + self.write_stats(verdict) + self.kill_current_process() + + def sa_stats(self) -> dict[str, Any]: + """The pre-analysis' statistics, with the counts of the last walk (Tree.walk) and what the + relevance analysis did in the walks.""" + stats = self.sa.stats(self.sa_adopted_round) + stats['last_walk'] = dict(StrategyService.walk_stats) + if self.sa.slicing and stats.get('relevance') is not None: + tree = Database.instance().get_tree(ENDPOINT_ID) + stats['relevance'] = dict(stats['relevance'], + poisoned=tree.poisoned_total, + reached_on_other_path=self.projected_reached) + return stats + + def write_stats(self, verdict): + stats_file = os.path.join(self.args.logdir if hasattr(self.args, 'logdir') else 'logs', 'stats.json') + write_testcase_stats(Path(stats_file), verdict, self.verification_category, Database.instance().get_tree(ENDPOINT_ID), + self.round_idx, self.nr_solver_calls, self.sa_stats(), + self.solver_limit_hits, self.precheck.stats(), self.divergence_retry_count, + resources=self.budget.stats()) + + def end_run_now(self, why: str): + """Called by the resource budget's monitor when the run must end at once: reports UNKNOWN + (unless the main thread reported a verdict already) and exits. The main thread is stuck in + work the budget cannot stop, or the explorer alone is too large.""" + with self.verdict_lock: + if self.verdict_reported: + return + self.verdict_reported = True + logger.warning(f'[SVCOMP] Ending the run with UNKNOWN: {why}') + verdict_logger.info(f'[VERDICT {self.verification_category.value}] {Verdict.UNKNOWN.value}') + try: + self.write_stats(Verdict.UNKNOWN) + except Exception as e: # the tree may be changing under us + logger.error(f'[SVCOMP] Could not write the statistics: {e}') + for handler in logger.handlers + verdict_logger.handlers: + handler.flush() + sys.stdout.flush() + for pid in descendants(os.getpid()): # the executor or the pre-analysis, if one is running + try: + os.kill(pid, signal.SIGKILL) + except OSError: + pass + os._exit(0) + def write_witness(self): + """Writes witness.yml/witness.graphml for the VIOLATION verdict from the trace of the violating round.""" + input_files = getattr(self.args, 'witness_input', None) or [] + property_file = getattr(self.args, 'witness_property', None) + if not input_files or not property_file: + logger.warning('[WITNESS] No task input files/property file given (--witness-input/--witness-property), no witness is written') + return + write_violation_witness( + Database.instance().get_witness_data(ENDPOINT_ID), + input_files=input_files, + property_file=property_file, + out_dir=getattr(self.args, 'witness_dir', None) or 'witness', + producer_version=getattr(self.args, 'producer_version', None) or 'unknown', + base_dir=getattr(self.args, 'witness_base_dir', None) or None) + def kill_current_process(self): pid = os.getpid() os.kill(pid, signal.SIGTERM) # Send termination signal diff --git a/symbolic-explorer/driver/SymbolicStorage.py b/symbolic-explorer/driver/SymbolicStorage.py index 8ea14fcf..9b802c64 100755 --- a/symbolic-explorer/driver/SymbolicStorage.py +++ b/symbolic-explorer/driver/SymbolicStorage.py @@ -55,6 +55,35 @@ def register_vars(self, sym_vars: [str], indices: [int] = None) -> None: self.vars[int(v.idx)] = v logger.info(f'[EXPLORER] Registered symbolic variable {v.dType.name}_{v.idx}') + def register_inputs(self, inputs) -> None: + """Register symbolic variables from the inputs recorded in a trace. + + Unlike register_vars(), this keeps the concrete value observed during the + execution as fallback and uses the variable's real DSE index (parsed from + its name). If the solver's model omits a variable (e.g. it is unconstrained + on the solved path), the fallback value is used instead of emitting a + literal 'None' assignment, which would crash the executor. + Mirrors svcomp/SymbolicStorage.register_vars(). + + Args: + inputs: List of data.trace.Input objects (e.g. a branch node's inputs). + """ + for input in inputs: + dtype, _, idx = input.name.rpartition('_') + if not idx.isdigit(): + # Auxiliary inputs such as array-length variables ("[I_0_length") + # are not assignable and carry no DSE index. + logger.debug(f'[EXPLORER] Skipping auxiliary input {input.name}') + continue + try: + v = SymbolicVar(dtype=DataTypes(dtype), idx=int(idx)) + except ValueError: + logger.warning(f'[EXPLORER] Skipping input {input.name} with unknown type {dtype}') + continue + v.value = input.value + self.vars[v.idx] = v + logger.info(f'[EXPLORER] Registered symbolic variable {v.dType.name}_{v.idx} (trace value: {v.value})') + def init_values(self): for var in self.vars.values(): match var.dType: diff --git a/symbolic-explorer/driver/TargetDriver.py b/symbolic-explorer/driver/TargetDriver.py index 6549de1f..4592898d 100644 --- a/symbolic-explorer/driver/TargetDriver.py +++ b/symbolic-explorer/driver/TargetDriver.py @@ -6,7 +6,7 @@ from data.Database import Database from driver.SymbolicStorage import SymbolicStorage -# import logging +from data.StaticAnalysisGraph.SAGraph import SAGraph import log logger = log.get_logger() @@ -43,7 +43,7 @@ class INPUTTYPE(Enum): class State: def __init__(self): - self.verdict = Verdict.UNKNOWN + self.verdict = Verdict.SAFE class TargetDriver: @@ -52,8 +52,10 @@ def __init__(self, args): self.sym_storage = SymbolicStorage() self.endpoint_id = None self.args = args + self.branch_log = None # Log file for branches + self.sa_graph = SAGraph() # static analysis graph - def build_command(self, mem: int = 32) -> [str]: + def build_command(self, mem: int = 32) -> list[str]: """Builds the Java command list with given parameters.""" cmd = [ 'java', @@ -63,13 +65,23 @@ def build_command(self, mem: int = 32) -> [str]: f"-Djava.library.path={self.args.z3dir}", '-Dsolver.mode=HTTP', '-Dagent.logging.level=DEBUG', - '-ea', - '-jar' if ".jar" in self.args.target else '', - self.args.target + '-ea', # Enable assertions ] + + # Add target + if self.args.target.endswith('.jar'): + cmd.extend(['-jar', self.args.target]) + else: + # Assume target is a class name with classpath provided + if self.args.classpath: + cmd.extend(['-cp', ':'.join(self.args.classpath), self.args.target]) + else: + logger.error('[EXPLORE] No classpath provided for class target') + cmd.extend([self.args.target]) + return cmd - def add_values(self, cmd: [str]) -> [str]: + def add_values(self, cmd: list[str]) -> list[str]: cmd = cmd.copy() """Adds the symbolic values to the Java command.""" for var in self.sym_storage.vars.values(): @@ -78,22 +90,29 @@ def add_values(self, cmd: [str]) -> [str]: else: val = var.newValue var.value = var.newValue + if val is None: + # Neither a solver value nor a concrete fallback is known; skipping the + # assignment lets the executor keep the target's original value instead + # of crashing on parsing a literal 'None'. + logger.error(f'[EXPLORER] No value known for {var.dType.value}_{var.idx}, skipping assignment') + continue if self.args.mode == "args": cmd.append(f'{val}') else: cmd.insert(1, f'-Dswat.assignment.{var.dType.value}_{var.idx}={val}') return cmd - def run_command_with_timeout(self, cmd: [str], timeout: int = 60) -> (ExecutionStatus, dict): + def run_command_with_timeout(self, cmd: list[str], timeout: int = 60) -> tuple[ExecutionStatus, dict]: """Executes the given command and returns the status and message.""" + logger.debug(f'[EXPLORER] Running command: {" ".join(cmd)}') logger.info(f'[EXPLORER] Java Output Begin') try: stdout = [] with subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, bufsize=1, universal_newlines=True) as proc: - for line in proc.stdout: - logger.info(f'[EXECUTOR] --> {line.strip()}') + for line in proc.stdout or []: + logger.info(f'[EXECUTOR] --> {line.strip()}', extra=log.RELAYED) stdout.append(line) logger.info(f'[EXPLORER] Java Output End') for l in stdout: @@ -117,7 +136,7 @@ def record_violation(self): db = Database.instance() db.add_violation(endpoint_id=self.endpoint_id, sym_vars=list(self.sym_storage.vars.values())) - def determine_next_step(self, status: ExecutionStatus, stdout: [str]) -> Action: + def determine_next_step(self, status: ExecutionStatus, stdout: list[str]) -> Action: """Determines the next step based on the execution status.""" match status: case ExecutionStatus.SUCCESS: @@ -125,62 +144,75 @@ def determine_next_step(self, status: ExecutionStatus, stdout: [str]) -> Action: case ExecutionStatus.VIOLATION: self.record_violation() logger.info(f'[EXPLORER] Violation recorded!') - return Action.SYMBOLICNEXT + self.state.verdict = Verdict.VIOLATION + # return Action.SYMBOLICNEXT + return Action.REPORTVERDICT case ExecutionStatus.TIMEOUT: logger.info(f'[EXPLORER] Timeout!') - self.state.verdict = Verdict.UNKNOWN + if self.state.verdict != Verdict.VIOLATION: + self.state.verdict = Verdict.UNKNOWN return Action.REPORTVERDICT case ExecutionStatus.CRASH: logger.info(f'[EXPLORER] Crash!') - self.state.verdict = Verdict.UNKNOWN + if self.state.verdict != Verdict.VIOLATION: + self.state.verdict = Verdict.UNKNOWN return Action.REPORTVERDICT case ExecutionStatus.ERROR: logger.info(f'[EXPLORER] Error!') - self.state.verdict = Verdict.UNKNOWN + if self.state.verdict != Verdict.VIOLATION: + self.state.verdict = Verdict.UNKNOWN return Action.REPORTVERDICT raise Exception(f'Unknown execution status: {status}') def retrieve_solution(self): - possible_branches = StrategyService.select_branch(endpoint_id=self.endpoint_id) + possible_branches = StrategyService.select_branch(endpoint_id=self.endpoint_id, sa_node=self.sa_graph.entry_node, rank_by_distance=self.args.sa_rank_distance) + # possible_branches = possible_branches[::-1] # Reverse the order to prioritize deeper branches logger.info(f'[EXPLORER] Found {len(possible_branches)} possible branches') - symbolic_vars = None sat = None branch_found = False for branch in possible_branches: if not StrategyService.is_symbolic_branch(branch): continue branch_found = True + sat, sol = StrategyService.solve_branch(branch) + if self.branch_log is None: + self.branch_log = open("branches.log", "w") + branch_id = branch.id & 0xFFFFFFFFFFFFFFFF + print(f"Branch 0x{branch_id:016x} ({branch_id}) {sat}", flush=True, file=self.branch_log) + if sat == SATResult.SAT: - symbolic_vars = branch.inputs - break + sol_viz = {key: val.get('plain_value', val.get('encoded_value')) + for key, val in sol.items()} + logger.info(f'[EXPLORER] Solved branch {branch.id}') + logger.info(f'[EXPLORER] Solution: {sol_viz}') + + # Register the solution in symbolic storage + self.sym_storage.register_inputs(branch.inputs) + self.sym_storage.store_solution(sol) + return Action.SYMBOLICNEXT if not branch_found or sat == SATResult.UNSAT: - self.state.verdict = Verdict.SAFE + # self.state.verdict = Verdict.SAFE logger.info(f'[EXPLORER] No symbolic branch found or UNSAT') return Action.REPORTVERDICT if sat == SATResult.UNKNOWN: logger.info(f'[EXPLORER] SAT result is UNKNOWN') - self.state.verdict = Verdict.UNKNOWN + if self.state.verdict != Verdict.VIOLATION: + self.state.verdict = Verdict.UNKNOWN return Action.REPORTVERDICT - sol_viz = [f'{key}: {val["plain_value"]}' for key, val in sol.items()] - logger.info(f'[EXPLORER] Found new solution: {sol_viz}') - self.sym_storage.register_vars([var.name.split('_')[0] for var in symbolic_vars]) - self.sym_storage.store_solution(sol) - return Action.SYMBOLICNEXT - def run(self): verdict = self.exec() logger.info(f'[EXPLORER] Verdict: {verdict}') self.kill_current_process() def exec(self): - """Runs the symbolic execution on the given testcase.""" + logger.info(f'[EXPLORER] Beginning testcase analysis') # Register symbolic variables (in case of cmd line argument mode) if self.args.mode == "args": @@ -188,8 +220,31 @@ def exec(self): self.sym_storage.init_values() # Build the command to execute target base_cmd = self.build_command() + + # Get static pre-analysis information if provided + try: + if self.args.sa_file: + self.sa_graph.load_json_graph(self.args.sa_file) + elif self.args.sa_path: + logger.info(f'[EXPLORER] Running static pre-analysis...') + subprocess.run(["java", "-jar", os.path.join(self.args.sa_path, "build", "libs", "cfg-extractor-1.0-SNAPSHOT-all.jar"), + ':'.join(os.path.abspath(p) for p in self.args.classpath), self.args.logdir, self.args.target, "main", "inter"], + check=True, timeout=5) + + self.sa_graph.load_json_graph(os.path.join(self.args.logdir, f"{self.args.target}_main_interprocedural.json")) + logger.info(f'[EXPLORER] Loaded static pre-analysis graph.') + except Exception as e: + logger.error(f'[EXPLORER] Failed to get static pre-analysis information. Exception: {e}') + + # Main execution loop - while True: + iteration = 1 + while iteration <= 150: + logger.info('') + logger.info('='*70) + logger.info(f'ITERATION {iteration}') + logger.info('='*70) + # Add the symbolic values cmd = self.add_values(base_cmd) # Run the command @@ -199,6 +254,13 @@ def exec(self): # Select the (only!) endpoint assert len(Database.instance().get_endpoints()) == 1 self.endpoint_id = Database.instance().get_endpoints()[0] + + # Visualize DB tree + # print("Plotting DB Tree...", flush=True) + # Database.instance().get_tree(self.endpoint_id).plot_tree(iteration) + + iteration += 1 + if next_step == Action.REPORTVERDICT: break @@ -209,12 +271,14 @@ def exec(self): if next_step == Action.REPORTVERDICT: break - logger.info(f'[EXPLORER] Symbolic execution terminated') + logger.info(f'[EXPLORER] Symbolic execution terminated after {iteration-1} iterations') violations = Database.instance().get_violations(self.endpoint_id) logger.info(f'[EXPLORER] Found {len(violations)} violations') if len(violations) > 0: for v in violations: logger.info(f'[EXPLORER] Violation: {[vv.__str__() for vv in v]}') + + return self.state.verdict def kill_current_process(self): pid = os.getpid() diff --git a/symbolic-explorer/log.py b/symbolic-explorer/log.py index 658238fa..14ed32e7 100755 --- a/symbolic-explorer/log.py +++ b/symbolic-explorer/log.py @@ -10,6 +10,20 @@ verdict_logger = logging.getLogger('verdict.log') +# Pass as `extra` for lines relayed from another program (e.g. the executor's output): they carry +# their own timestamp and level, so they are logged as they are. +RELAYED = {'relayed': True} + + +class _Formatter(logging.Formatter): + """The explorer's format, except for relayed lines (see RELAYED), which are left as they are.""" + + def format(self, record): + if getattr(record, 'relayed', False): + return record.getMessage() + return super().format(record) + + class LoggerNotInitializedError(Exception): """Exception raised when loggers are not initialized.""" pass @@ -22,17 +36,18 @@ def get_logger(): return logger def get_verdict_logger(): - global logger - if logger is None: + global verdict_logger + if verdict_logger is None: raise LoggerNotInitializedError("Logger has not been initialized.") - return logger + return verdict_logger -def setup_logger(_logger, log_file, level=logging.INFO, use_console_handler=True): - """Sets up a logger with both file and optional console handlers.""" +def setup_logger(_logger, log_file, level=logging.INFO, use_console_handler=True, console_level=None): + """Sets up a logger with both file and optional console handlers. The console handler logs + from console_level on (default: level).""" global _interal_logger - formatter = logging.Formatter( + formatter = _Formatter( "%(asctime)s [%(threadName)-20.20s] [%(levelname)-5.5s] %(message)s" ) _interal_logger.debug(f"Setting up logger {_logger} with file {log_file}") @@ -50,7 +65,7 @@ def setup_logger(_logger, log_file, level=logging.INFO, use_console_handler=True # Optional console handler if use_console_handler: console_handler = logging.StreamHandler() - console_handler.setLevel(level) # Ensure consistent levels + console_handler.setLevel(level if console_level is None else console_level) console_handler.setFormatter(formatter) _logger.addHandler(console_handler) @@ -59,11 +74,17 @@ def setup_logger(_logger, log_file, level=logging.INFO, use_console_handler=True return _logger -def initialize_loggers(log_dir, level=logging.DEBUG): - """Initializes global loggers based on the provided log directory.""" +def initialize_loggers(log_dir, level=logging.DEBUG, console_level=logging.INFO): + """Initializes global loggers based on the provided log directory: the log files get + everything from level on, the console from console_level on.""" global logger, verdict_logger os.makedirs(log_dir, exist_ok=True) - - logger = setup_logger(logger, os.path.join(log_dir, 'explorer.log'), level=level) - verdict_logger = setup_logger(verdict_logger, os.path.join(log_dir, 'verdict.log'),level=level) + + logger = setup_logger(logger, os.path.join(log_dir, 'explorer.log'), level=level, console_level=console_level) + verdict_logger = setup_logger(verdict_logger, os.path.join(log_dir, 'verdict.log'), level=level, + console_level=console_level) + # The verdict also goes to explorer.log, so that it reads as the whole run + for handler in logger.handlers: + if isinstance(handler, logging.FileHandler): + verdict_logger.addHandler(handler) diff --git a/symbolic-explorer/parse/DataTransferObjects.py b/symbolic-explorer/parse/DataTransferObjects.py index be30d859..e52372ec 100644 --- a/symbolic-explorer/parse/DataTransferObjects.py +++ b/symbolic-explorer/parse/DataTransferObjects.py @@ -10,6 +10,25 @@ class TraceItem(BaseModel): branched: bool type: str inst: Optional[str] = None + # A branch the executor records only to mirror a guard of the SA graph; never a solver target. + concreteOnly: bool = False + # Where the branch is (see the executor's BranchKeys): its key, and how the frame stack changed + # since the previous branch -- frames popped, then the call sites (keys) of the frames pushed. + key: Optional[str] = None + pops: int = 0 + pushes: list[Optional[str]] = [] + # Recorded in application code that library code called back: not in the SA graph. + callback: bool = False + # A branch the executor adds to enumerate the values of a symbolic operand (e.g. the element a + # symbolic array index selects): flipped like any other, but not in the SA graph. + choice: bool = False + # Merged by a loop summary (the executor's symbolic/summary): both outcomes lead to the same, + # merged state, so it is neither flipped nor is its constraint kept. + merged: bool = False + # Merged only if the loop's other effects are irrelevant (decided with the SA graph, see dfs). + mergedConditional: bool = False + # Hashes of the loop-head states the execution reached since the previous branch, in order. + loopStates: list[str] = [] class UFItem(BaseModel): definition: str @@ -22,13 +41,72 @@ class InputItem(BaseModel): upperBound: str +class InvocationItem(BaseModel): + """A method invocation that could not be modelled symbolically during execution. + + The full list is the superset of missing invocations; entries with ``contextLoss`` set are the + dangerous subset that received symbolic arguments and caused symbolic context loss. + """ + owner: str + name: str + desc: str + isInstance: bool + isSymbolic: bool + contextLoss: bool + count: int + + +class NondetReturnItem(BaseModel): + """A value returned by a ``Verifier.nondet*()`` call and the location of the call. + + ``value`` is the witness string form (integers decimal, char as code point, float/double as + Java's ``Float/Double.toString``, boolean ``true``/``false``, String raw); ``bits`` carries the + exact IEEE-754 bits of a float/double as ``0x``-prefixed hex. ``callIndex`` is the 0-based + index among the nondet calls on that source line of that class, in evaluation order; + ``method`` is the calling method (name + descriptor). + """ + function: str + type: str + value: str + bits: Optional[str] = None + className: str + # The calling method as name + descriptor, e.g. main([Ljava/lang/String;)V + method: Optional[str] = None + sourceFile: str = "" + line: int + callIndex: int = 0 + + +class FrameItem(BaseModel): + """A stack frame of the throwable that escaped the entry method.""" + className: str + sourceFile: str = "" + line: int + + +class ViolationItem(BaseModel): + """The throwable that escaped the entry method, with its frames innermost first.""" + throwable: str + frames: List[FrameItem] = [] + + class ConstraintRequest(BaseModel): trace: List[TraceItem] inputs: List[InputItem] ufs: List[UFItem] + missingInvocations: List[InvocationItem] = [] + # Witness data: the nondet return values of the execution and the uncaught throwable (if any) + nondetReturns: List[NondetReturnItem] = [] + violation: Optional[ViolationItem] = None symbolicContextLoss: bool symbolicPrecisionLoss: bool referenceSemanticChange: bool = False + # An approximate model was used that is precision loss wherever it is used: solutions from this + # trace are guesses. Queries over sampled Math functions are recognised by their function names. + approximateModel: bool = False + # A StackOverflowError reached the target's code: the path depends on the JVM's stack size, so a + # violation of this execution is not a verdict (the executor also reports context loss). + stackOverflow: bool = False class CoverageRequest(BaseModel): diff --git a/symbolic-explorer/parse/TraceParser.py b/symbolic-explorer/parse/TraceParser.py index 38c627fa..53047c89 100755 --- a/symbolic-explorer/parse/TraceParser.py +++ b/symbolic-explorer/parse/TraceParser.py @@ -18,7 +18,16 @@ def parse_trace(trace: List[TraceItem], trace_id: str) -> List[Branch | Special] _trace.append(Branch(id=branch.iid, trace_id=trace_id, has_branched=branch.branched, - constraint=sanitized_constraint)) + constraint=sanitized_constraint, + concrete_only=branch.concreteOnly, + key=branch.key, + pops=branch.pops, + pushes=tuple(branch.pushes), + callback=branch.callback, + choice=branch.choice, + merged=branch.merged, + merged_conditional=branch.mergedConditional, + loop_states=tuple(branch.loopStates))) else: _trace.append(Special(id=branch.iid, trace_id=trace_id, diff --git a/symbolic-explorer/requirements.txt b/symbolic-explorer/requirements.txt index fec927c0..894089a5 100644 --- a/symbolic-explorer/requirements.txt +++ b/symbolic-explorer/requirements.txt @@ -1,21 +1,22 @@ -annotated-doc==0.0.4 -annotated-types==0.7.0 -anyio==4.12.1 -certifi==2026.1.4 -charset-normalizer==3.4.4 -click==8.3.1 +annotated-doc==0.0.5 +annotated-types==0.8.0 +anyio==4.15.1 +certifi==2026.10.19 +charset-normalizer==3.5.2 +click==8.5.0 exceptiongroup==1.3.1 -fastapi==0.128.0 +fastapi==0.151.0 h11==0.16.0 -idna==3.11 +idna==3.20 javaproperties==0.8.2 -pydantic==2.12.5 -pydantic_core==2.41.5 -requests==2.32.5 -sniffio==1.3.1 -starlette==0.50.0 -typing-inspection==0.4.2 -typing_extensions==4.15.0 -urllib3==2.6.3 -uvicorn==0.40.0 -z3-solver==4.15.4.0 +opentelemetry-api==1.45.1 +pydantic==2.14.1 +pydantic_core==2.50.1 +requests==2.34.2 +starlette==1.9.0 +typing-inspection==0.4.4 +typing_extensions==4.16.0 +urllib3==2.8.0 +uvicorn==0.54.0 +witnesscreator @ git+https://github.com/SWAT-project/WitnessCreator@v1.0.0 +z3-solver==5.3.1.0 diff --git a/symbolic-explorer/solver/ConstraintCache.py b/symbolic-explorer/solver/ConstraintCache.py index 258dc345..b0a6aeda 100644 --- a/symbolic-explorer/solver/ConstraintCache.py +++ b/symbolic-explorer/solver/ConstraintCache.py @@ -66,7 +66,10 @@ def get_node_constraint(self, node: Any) -> Dict[str, Any]: - 'variables': Set of variable names found in constraint - 'smt': Original SMT-LIB string """ - key = (node.id, node.trace_id) + # By tree node, not by iid: two branches of one trace can share an iid (a switch's case + # index wraps at 16, a loop counter at 64), and the cache must not hand one the other's + # constraint. + key = (node.gid, node.trace_id) if key not in self._node_cache: constraint_str = node.constraint[node.trace_id] diff --git a/symbolic-explorer/solver/ConstraintManager.py b/symbolic-explorer/solver/ConstraintManager.py index ba087d75..592b513f 100644 --- a/symbolic-explorer/solver/ConstraintManager.py +++ b/symbolic-explorer/solver/ConstraintManager.py @@ -202,21 +202,24 @@ def _extract_variables(self, expr: ExprRef) -> Set[str]: Set of variable names """ variables = set() - - def traverse(e): - """Recursively traverse the AST.""" + # Iterative, and each shared subterm once: an expression is a DAG that can be deeper than + # the recursion limit, and exponentially larger as a tree. + seen = set() + work = [expr] + while work: + e = work.pop() + key = e.get_id() + if key in seen: + continue + seen.add(key) if is_const(e) and e.decl().kind() == Z3_OP_UNINTERPRETED: # This is a variable (uninterpreted constant) variables.add(str(e)) elif is_app(e): - # Traverse all arguments - for i in range(e.num_args()): - traverse(e.arg(i)) + work.extend(e.arg(i) for i in range(e.num_args())) elif is_quantifier(e): - # Traverse quantifier body - traverse(e.body()) + work.append(e.body()) - traverse(expr) return variables def _classify_constraint(self, expr: ExprRef) -> str: diff --git a/symbolic-explorer/solver/Int2BvLifting.py b/symbolic-explorer/solver/Int2BvLifting.py new file mode 100644 index 00000000..54274fe6 --- /dev/null +++ b/symbolic-explorer/solver/Int2BvLifting.py @@ -0,0 +1,508 @@ +""" +Rewrites bit-vector comparisons over int_to_bv of bounded Integer terms into Integer comparisons. + +The executor encodes Java ints, chars, ... as bit-vectors, and String lengths, indices and char +codes come from the string theory as Integers, so a query such as `s.length() > 2` reads +`(bvsgt ((_ int_to_bv 32) (str.len s)) (_ bv2 32))`. Z3 decides such mixed queries badly: trivially +satisfiable ones with a few chars and lengths run for minutes. When the Integer term is known to lie +in the range of the bit-vector, int_to_bv is a bijection there and the comparison can be made over +the Integers instead, where Z3 is fast. + +`lift(e, signed)` finds an Integer term t with e == int_to_bv(t) whose value lies in the signed +(or unsigned) range of e's width, built from: +- bit-vector numerals, +- int_to_bv of an Integer term with a known range (numerals, str.len, str.indexof, str.to_code, + ubv_to_int, + - * and ite of those), +- bvadd/bvsub/bvneg/bvmul/ite of liftable terms, as long as the range still fits, +- zero/sign extension of liftable terms. +A char code (str.to_code) of an SMT string lies in [-1, 0x10FFFF], which does not fit 16 bits: it is +lifted as ite(t < 0, t + 2^16, ite(t < 2^16, t, t mod 2^16)), which is exact, but the mod makes +Z3 slow. The executor encodes a Java string with one SMT char per UTF-16 char, so in a query in +which every char comes from a Java char, a char code lies in [-1, 0xFFFF] and is lifted as +ite(t < 0, t + 2^16, t) (Int2BvLifting(java_chars=True), for queries that `java_chars_only` accepts). + +`rewrite(e)` replaces every comparison (=, distinct, bvs*, bvu*) between two liftable bit-vectors that +involves int_to_bv by the Integer comparison of their lifts, and ubv_to_int of a liftable term by its +lift. Everything else is left as it is. + +The ranges of str.len and str.indexof assume that every string has at most 2^31 - 1 chars, as Java +strings do, and with java_chars=True the range of str.to_code assumes that every char is a Java +char. `rewrite` records the strings whose length and the char codes whose range it relied on; +`lift_query` adds the bound for each of them to the query, so that the rewritten query is +equivalent to the original one under these bounds in every model (and its models are models of +the original query). + +The char code bounds hold in every model in which each string variable is a Java string, as long as +every char that the query itself puts into a string is a Java char: a string constant, and the +argument of str.from_code (taking the range of str.to_code as [-1, 0xFFFF], which holds for the +strings below it by induction over the term). `java_chars_only` checks that; other queries are +lifted with the full range (java_chars=False). +""" + +import ctypes + +import z3 + +JAVA_MAX_STRING_LENGTH = 2 ** 31 - 1 +# Upper bound of str.to_code; Z3's char sort is never larger than Unicode. +MAX_CODE_POINT = 0x10FFFF +# Upper bound of a Java char (one UTF-16 code unit). +MAX_JAVA_CHAR = 0xFFFF + +_SIGNED_CMP = {z3.Z3_OP_SLEQ, z3.Z3_OP_SLT, z3.Z3_OP_SGEQ, z3.Z3_OP_SGT} +_UNSIGNED_CMP = {z3.Z3_OP_ULEQ, z3.Z3_OP_ULT, z3.Z3_OP_UGEQ, z3.Z3_OP_UGT} +_INT_CMP = { + z3.Z3_OP_SLEQ: lambda a, b: a <= b, z3.Z3_OP_ULEQ: lambda a, b: a <= b, + z3.Z3_OP_SLT: lambda a, b: a < b, z3.Z3_OP_ULT: lambda a, b: a < b, + z3.Z3_OP_SGEQ: lambda a, b: a >= b, z3.Z3_OP_UGEQ: lambda a, b: a >= b, + z3.Z3_OP_SGT: lambda a, b: a > b, z3.Z3_OP_UGT: lambda a, b: a > b, +} + + +def _kind(e): + return e.decl().kind() if z3.is_app(e) else None + + +def _view_range(width, signed): + return (-(2 ** (width - 1)), 2 ** (width - 1) - 1) if signed else (0, 2 ** width - 1) + + +class Int2BvLifting: + """Rewrites expressions of one Z3 context; caches results across queries. + + The caches are keyed by AST id. Z3 reuses the id of an AST that has been freed, so every + expression that serves as a key is kept alive in self._alive. + """ + + def __init__(self, java_chars=False): + """java_chars: take the range of str.to_code as [-1, 0xFFFF] and record each char code + that relies on it, for queries that java_chars_only accepts.""" + self.java_chars = java_chars + self._alive = [] + self._rewritten = {} + self._lifted = {} + self._ranges = {} + self._mentions_int2bv = {} + # Strings whose length bound (and char codes whose Java char bound) a rewrite relied on, + # by expression id + self._bounded = {} + self._needs = {} + self._java_chars_only = {} + # Integer stand-ins for bit-vector inputs (see lift_inputs): their range, by expression id + self._input_ranges = {} + # The inputs under ubv_to_int in an assertion, by expression id + self._bv2int_inputs = {} + + # Integer ranges + + def int_range(self, t): + """(lo, hi) such that lo <= t <= hi in every model (under the string length bound), or None. + Records the strings whose length bound the range relies on in self._needs[t].""" + key = t.get_id() + if key in self._ranges: + return self._ranges[key] + self._alive.append(t) + r, needs = self._int_range(t) + self._ranges[key] = r + self._needs[key] = needs if r is not None else () + return r + + def _needs_of(self, *terms): + out = [] + for t in terms: + out.extend(self._needs.get(t.get_id(), ())) + return tuple(out) + + def _int_range(self, t): + v = _int_const(t) + if v is not None: + return (v, v), () + if t.get_id() in self._input_ranges: + return self._input_ranges[t.get_id()], () + k = _kind(t) + args = t.children() if z3.is_app(t) else [] + if k == z3.Z3_OP_SEQ_LENGTH: + return (0, JAVA_MAX_STRING_LENGTH), (args[0],) + if k == z3.Z3_OP_SEQ_INDEX: + return (-1, JAVA_MAX_STRING_LENGTH), (args[0],) + if k == z3.Z3_OP_STR_TO_CODE: + if self.java_chars: + return (-1, MAX_JAVA_CHAR), (t,) + return (-1, MAX_CODE_POINT), () + if k == z3.Z3_OP_BV2INT: + if z3.is_bv_value(args[0]): + return (args[0].as_long(), args[0].as_long()), () + return (0, 2 ** args[0].size() - 1), () + if k == z3.Z3_OP_SBV2INT: + return _view_range(args[0].size(), True), () + if k in (z3.Z3_OP_ADD, z3.Z3_OP_SUB, z3.Z3_OP_MUL) and args: + ranges = [self.int_range(a) for a in args] + if any(r is None for r in ranges): + return None, () + lo, hi = ranges[0] + for (l2, h2) in ranges[1:]: + if k == z3.Z3_OP_ADD: + lo, hi = lo + l2, hi + h2 + elif k == z3.Z3_OP_SUB: + lo, hi = lo - h2, hi - l2 + else: + products = [lo * l2, lo * h2, hi * l2, hi * h2] + lo, hi = min(products), max(products) + return (lo, hi), self._needs_of(*args) + if k == z3.Z3_OP_UMINUS: + r = self.int_range(args[0]) + return (None, ()) if r is None else ((-r[1], -r[0]), self._needs_of(args[0])) + if k == z3.Z3_OP_ITE: + ra, rb = self.int_range(args[1]), self.int_range(args[2]) + if ra is None or rb is None: + return None, () + return (min(ra[0], rb[0]), max(ra[1], rb[1])), self._needs_of(args[1], args[2]) + return None, () + + def java_chars_only(self, e): + """Whether every char that e puts into a string is a Java char (at most 0xFFFF): no string + constant has a larger char, every str.from_code argument is at most 0xFFFF (by its range, + with str.to_code in [-1, 0xFFFF]) and there is no Char-sorted term. Needs java_chars=True.""" + assert self.java_chars + memo = self._java_chars_only + # Iterative post-order: string concatenations can be deeper than Python's recursion limit. + stack = [(e, False)] + while stack: + t, expanded = stack.pop() + key = t.get_id() + if key in memo: + continue + children = t.children() if z3.is_app(t) else [] + if not expanded and children: + stack.append((t, True)) + stack.extend((c, False) for c in children if c.get_id() not in memo) + continue + self._alive.append(t) + if t.sort().kind() == z3.Z3_CHAR_SORT: + ok = False + elif z3.is_string_value(t): + ok = all(c <= MAX_JAVA_CHAR for c in _string_codes(t)) + elif z3.is_app(t): + ok = all(memo[c.get_id()] for c in children) + if ok and _kind(t) == z3.Z3_OP_STR_FROM_CODE: + r = self.int_range(t.arg(0)) + ok = r is not None and r[1] <= MAX_JAVA_CHAR + else: + # quantifiers and bound variables: the executor builds none + ok = False + memo[key] = ok + return memo[e.get_id()] + + # Bit-vector lifting + + def lift(self, e, signed): + """(t, needs): an Integer term with e == int_to_bv(t) and t in the signed/unsigned range of + e's width, and the strings whose length bound that relies on; or None.""" + key = (e.get_id(), signed) + if key not in self._lifted: + self._alive.append(e) + self._lifted[key] = self._lift(e, signed) + return self._lifted[key] + + def _lift(self, e, signed): + width = e.size() + lo_view, hi_view = _view_range(width, signed) + if z3.is_bv_value(e): + return (z3.IntVal(e.as_signed_long() if signed else e.as_long(), e.ctx), ()) + k = _kind(e) + args = e.children() if z3.is_app(e) else [] + if k == z3.Z3_OP_INT2BV: + t = self.rewrite(args[0]) + r = self.int_range(args[0]) + if r is None: + return None + needs = self._needs.get(args[0].get_id(), ()) + self.needs(args[0]) + wrapped = _wrap(t, r, lo_view, hi_view, 2 ** width) + return None if wrapped is None else (wrapped, needs) + if k in (z3.Z3_OP_BADD, z3.Z3_OP_BSUB, z3.Z3_OP_BMUL) and args: + parts = [self._lift_ranged(a, signed) for a in args] + if any(p is None for p in parts): + return None + t, (lo, hi), needs = parts[0] + for (t2, (l2, h2), n2) in parts[1:]: + if k == z3.Z3_OP_BADD: + t, lo, hi = t + t2, lo + l2, hi + h2 + elif k == z3.Z3_OP_BSUB: + t, lo, hi = t - t2, lo - h2, hi - l2 + else: + products = [lo * l2, lo * h2, hi * l2, hi * h2] + t, lo, hi = t * t2, min(products), max(products) + needs = needs + n2 + wrapped = _wrap(t, (lo, hi), lo_view, hi_view, 2 ** width) + return None if wrapped is None else (wrapped, needs) + if k == z3.Z3_OP_BNEG: + p = self._lift_ranged(args[0], signed) + if p is None: + return None + t, (lo, hi), needs = p + wrapped = _wrap(-t, (-hi, -lo), lo_view, hi_view, 2 ** width) + return None if wrapped is None else (wrapped, needs) + if k == z3.Z3_OP_ITE: + a, b = self.lift(args[1], signed), self.lift(args[2], signed) + if a is None or b is None: + return None + return z3.If(self.rewrite(args[0]), a[0], b[0]), a[1] + b[1] + self.needs(args[0]) + if k in (z3.Z3_OP_ZERO_EXT, z3.Z3_OP_SIGN_EXT): + inner = self.lift(args[0], k == z3.Z3_OP_SIGN_EXT) + if inner is None: + return None + # The inner value fits the inner width's range, so it fits both views of the + # extended width, except a negative value in the unsigned view. + if not signed and k == z3.Z3_OP_SIGN_EXT: + return None + return inner + if k == z3.Z3_OP_CONCAT and len(args) == 2 and z3.is_bv_value(args[0]) and args[0].as_long() == 0: + # A zero extension, as the executor widens a char to an int + return self.lift(args[1], False) + return None + + def _lift_ranged(self, e, signed): + """(t, (lo, hi), needs) for a liftable e, with the range of its lift.""" + lifted = self.lift(e, signed) + if lifted is None: + return None + t, needs = lifted + # The lift lies in the view's range by construction; its own range may be tighter. + lo, hi = _view_range(e.size(), signed) + r = self.int_range(t) + if r is not None: + lo, hi = max(lo, r[0]), min(hi, r[1]) + needs = needs + self._needs.get(t.get_id(), ()) + return t, (lo, hi), needs + + # Rewriting + + def mentions_int2bv(self, e): + key = e.get_id() + if key not in self._mentions_int2bv: + self._alive.append(e) + self._mentions_int2bv[key] = z3.is_app(e) and ( + _kind(e) == z3.Z3_OP_INT2BV or any(self.mentions_int2bv(c) for c in e.children())) + return self._mentions_int2bv[key] + + def rewrite(self, e): + """e with comparisons over int_to_bv of bounded terms made over the Integers.""" + key = e.get_id() + if key in self._rewritten: + return self._rewritten[key] + self._alive.append(e) + result, needs = e, () + if z3.is_app(e) and self.mentions_int2bv(e): + result, needs = self._rewrite_app(e) + self._rewritten[key] = result + self._bounded[key] = needs + return result + + def needs(self, e): + """The strings whose length bound (and char codes whose Java char bound) rewrite(e) relies on.""" + self.rewrite(e) + return self._bounded[e.get_id()] + + def _rewrite_app(self, e): + k = _kind(e) + args = e.children() + equality = k in (z3.Z3_OP_EQ, z3.Z3_OP_DISTINCT) + if len(args) == 2 and z3.is_bv(args[0]) and (equality or k in _SIGNED_CMP or k in _UNSIGNED_CMP): + views = [True, False] if equality else [k in _SIGNED_CMP] + for signed in views: + a, b = self.lift(args[0], signed), self.lift(args[1], signed) + if a is not None and b is not None: + if equality: + cmp = (lambda x, y: x == y) if k == z3.Z3_OP_EQ else (lambda x, y: x != y) + else: + cmp = _INT_CMP[k] + return cmp(a[0], b[0]), a[1] + b[1] + if k == z3.Z3_OP_BV2INT: + a = self.lift(args[0], False) + if a is not None: + return a + if k == z3.Z3_OP_SBV2INT: + a = self.lift(args[0], True) + if a is not None: + return a + if k == z3.Z3_OP_ITE: + x = _signed_bv2int_operand(e) + if x is not None: + a = self.lift(x, True) + if a is not None: + return a + children = [self.rewrite(c) for c in args] + needs = tuple(n for c in args for n in self._bounded[c.get_id()]) + if all(c.eq(o) for c, o in zip(children, args)): + return e, needs + return e.decl()(*children), needs + + +def _wrap(t, r, lo_view, hi_view, m): + """The value of int_to_bv(t) in the view [lo_view, hi_view] (of size m), for t in r = (lo, hi): + t itself where it lies in the view, else t shifted by m (or reduced modulo m, if the range is + wider than that), as an ite with only the cases r allows. None if t can lie below the view by + more than m.""" + lo, hi = r + if lo < lo_view - m: + return None + result = t + if hi > hi_view: + high = t - m if hi <= hi_view + m else (t - lo_view) % m + lo_view + result = z3.If(t <= hi_view, t, high) + if lo < lo_view: + result = z3.If(t < lo_view, t + m, result) + return result + + +def _string_codes(e): + """The char codes of a string constant.""" + n = z3.Z3_get_string_length(e.ctx_ref(), e.as_ast()) + buf = (ctypes.c_uint * n)() + z3.Z3_get_string_contents(e.ctx_ref(), e.as_ast(), n, buf) + return list(buf) + + +def _int_const(e): + """The value of an Integer numeral, also when written as (- n); else None.""" + if z3.is_int_value(e): + return e.as_long() + if _kind(e) == z3.Z3_OP_UMINUS and z3.is_int_value(e.arg(0)): + return -e.arg(0).as_long() + return None + + +def _normal(e): + """e simplified to a fixpoint (one simplify of ubv_to_int(concat ...) leaves 2^k * ubv_to_int(0)).""" + for _ in range(4): + n = z3.simplify(e) + if n.eq(e): + break + e = n + return e + + +def _signed_bv2int_operand(e): + """x if e is the signed bv2int ite(bvsle(0, x), ubv_to_int(x), ubv_to_int(x) - 2^w) (in the + forms JavaSMT builds it, or with ubv_to_int(x) expanded, as Z3 does for a concat), else None.""" + cond, then, other = e.children() + if _kind(cond) != z3.Z3_OP_SLEQ: + return None + zero, x = cond.children() + if not (z3.is_bv_value(zero) and zero.as_long() == 0): + return None + m = 2 ** x.size() + if _kind(then) != z3.Z3_OP_BV2INT or not then.arg(0).eq(x): + unsigned = z3.BV2Int(x) + if _normal(then).eq(_normal(unsigned)) and _normal(other).eq(_normal(unsigned - m)): + return x + return None + if _kind(other) == z3.Z3_OP_ADD and other.num_args() == 2: + a, b = other.children() + for u, c in ((a, b), (b, a)): + if u.eq(then) and _int_const(c) == -m: + return x + if _kind(other) == z3.Z3_OP_SUB and other.num_args() == 2: + a, b = other.children() + if a.eq(then) and _int_const(b) == m: + return x + return None + + +def lift_query(lifting, exprs): + """The rewritten expressions, plus the length bounds of the strings and the Java char bounds of + the char codes they rely on.""" + out = [lifting.rewrite(x) for x in exprs] + needed = {} + for x in exprs: + for s in lifting.needs(x): + needed[s.get_id()] = s + for s in needed.values(): + # (A char code's index may itself be lifted.) + out.append(z3.Length(s) <= JAVA_MAX_STRING_LENGTH if z3.is_string(s) else lifting.rewrite(s) <= MAX_JAVA_CHAR) + return out + + +def _bv2int_inputs(lifting, exprs): + """The bit-vector variables that occur as ubv_to_int(v) in exprs (cached per expression: the + path constraints recur in every query below them).""" + found = {} + for e in exprs: + key = e.get_id() + if key not in lifting._bv2int_inputs: + lifting._alive.append(e) + lifting._bv2int_inputs[key] = _bv2int_inputs_of(e) + for v in lifting._bv2int_inputs[key]: + found[v.get_id()] = v + return list(found.values()) + + +def _bv2int_inputs_of(expr): + found, seen = {}, set() + stack = [expr] + while stack: + e = stack.pop() + if e.get_id() in seen: + continue + seen.add(e.get_id()) + if _kind(e) == z3.Z3_OP_BV2INT: + v = e.arg(0) + if z3.is_const(v) and v.decl().kind() == z3.Z3_OP_UNINTERPRETED: + found[v.get_id()] = v + if z3.is_app(e): + stack.extend(e.children()) + return list(found.values()) + + +def _remaining_int2bv_args(exprs): + """The ids of the terms t that still occur as int_to_bv(t) in exprs.""" + found, seen = set(), set() + stack = list(exprs) + while stack: + e = stack.pop() + if e.get_id() in seen: + continue + seen.add(e.get_id()) + if _kind(e) == z3.Z3_OP_INT2BV: + found.add(e.arg(0).get_id()) + if z3.is_app(e): + stack.extend(e.children()) + return found + + +def input_stand_in(v): + """The Integer variable that stands in for the bit-vector input v (named so that it is not + taken for an input itself).""" + return z3.Int(f'int!{v.decl().name()}', v.ctx) + + +def lift_inputs(lifting, exprs): + """(out, inputs): lift_query of exprs, with bit-vector inputs that are only used as numbers + solved over the Integers. + + The executor's ints are bit-vectors, but a string index is an Integer: s.charAt(i) reads + (str.at s (ubv_to_int i)), with the signed conversion around it. Z3 decides such mixed queries + badly. Every input v that occurs under ubv_to_int is replaced by int_to_bv(x), for a new + Integer x in [0, 2^w) (exact: that is a bijection), and lifted as above. Where an occurrence + cannot be lifted (bit operations, extracts, ...), int_to_bv(x) would remain; such inputs are + put back as they were. `inputs` maps each lifted x to its v: a model gives v = x.""" + candidates = _bv2int_inputs(lifting, exprs) + if not candidates and not any(lifting.mentions_int2bv(e) for e in exprs): + return list(exprs), {} + while candidates: + stand_ins = {} + for v in candidates: + x = input_stand_in(v) + lifting._alive.append(x) + lifting._input_ranges[x.get_id()] = (0, 2 ** v.size() - 1) + stand_ins[x.get_id()] = (x, v) + subs = [(v, z3.Int2BV(x, v.size())) for x, v in stand_ins.values()] + out = lift_query(lifting, [z3.substitute(e, *subs) for e in exprs]) + remaining = _remaining_int2bv_args(out) + failed = [v for xid, (x, v) in stand_ins.items() if xid in remaining] + if not failed: + out.extend(z3.And(x >= 0, x <= 2 ** v.size() - 1) for x, v in stand_ins.values()) + return out, {x.get_id(): (x, v) for x, v in stand_ins.values()} + failed_ids = {v.get_id() for v in failed} + candidates = [v for v in candidates if v.get_id() not in failed_ids] + return lift_query(lifting, exprs), {} diff --git a/symbolic-explorer/solver/SolverHandler.py b/symbolic-explorer/solver/SolverHandler.py index dd9a0eec..03f298a9 100755 --- a/symbolic-explorer/solver/SolverHandler.py +++ b/symbolic-explorer/solver/SolverHandler.py @@ -1,6 +1,8 @@ +import copy import re import time from svcomp.SymbolicStorage import DataTypes +import z3 from z3 import Solver, is_true, Not, Optimize, Z3_OP_UNINTERPRETED, Abs from enum import Enum from typing import Union, Dict, Tuple, Any @@ -12,15 +14,99 @@ logger = logging.getLogger("explorer.log") +# Z3 5.1 made a new regex solver the default (smt.seq.regex_monadic). On the string queries of +# String/Character method models (str.in_re over re.range with many char bounds) it is much +# slower than the previous one (e.g. jbmc-regression/StaticCharMethods02/03: seconds before, +# minutes or no answer after), so keep the previous solver. Older Z3 has no such parameter. +try: + z3.set_param("smt.seq.regex_monadic", False) +except z3.Z3Exception: + pass + class SATResult(Enum): SAT = 'sat' UNSAT = 'unsat' UNKNOWN = 'unknown' + # The solver ran out of the resource limit (rlimit) it was given. Unlike UNKNOWN, a larger + # limit may still decide the query. Only returned when solve() is given an rlimit. + LIMIT = 'limit' +# The shared Z3 context's cumulative resource count after the last check. Checks run one at a time, +# so the difference to the count after the next one is what that check used (see _rlimit_used). +_last_rlimit_count = 0 + + +def _rlimit_used(solver) -> int: + """The resources (Z3 rlimit units) the check just run on `solver` used.""" + global _last_rlimit_count + stats = solver.statistics() + count = next((stats.get_key_value(k) for k in stats.keys() if k == 'rlimit count'), _last_rlimit_count) + used, _last_rlimit_count = count - _last_rlimit_count, count + return used + + +def _has_fp(exprs) -> bool: + """Whether any of the expressions contains a floating-point or rounding-mode sorted term. + + Walks the term DAG through the C API (about 3x faster than the z3py wrappers, which build a + Python object per child), with a memo of the AST ids seen in this call only: an id is unique + only among live ASTs of one context, and these are all kept alive by `exprs` while it runs.""" + exprs = list(exprs) + if not exprs: + return False + c = exprs[0].ctx.ref() + seen = set() + stack = [e.as_ast() for e in exprs] + while stack: + a = stack.pop() + k = z3.Z3_get_ast_id(c, a) + if k in seen: + continue + seen.add(k) + kind = z3.Z3_get_ast_kind(c, a) + if kind != z3.Z3_APP_AST and kind != z3.Z3_NUMERAL_AST: + continue + sort_kind = z3.Z3_get_sort_kind(c, z3.Z3_get_sort(c, a)) + if sort_kind == z3.Z3_FLOATING_POINT_SORT or sort_kind == z3.Z3_ROUNDING_MODE_SORT: + return True + if kind == z3.Z3_APP_AST: + app = z3.Z3_to_app(c, a) + stack.extend(z3.Z3_get_app_arg(c, app, i) for i in range(z3.Z3_get_app_num_args(c, app))) + return False + + +def _entry_has_fp(entry: dict) -> bool: + """_has_fp of a ConstraintCache entry's expressions, computed once and kept in the entry + (which lives as long as its expressions, so the flag cannot go stale).""" + flag = entry.get('has_fp') + if flag is None: + flag = entry['has_fp'] = _has_fp(entry['exprs']) + return flag + + +def _cut_short(reason: str) -> bool: + """Whether a check that ended in unknown was stopped from outside rather than given up: its + rlimit ran out ("canceled"; older Z3: "max. resource limit exceeded"), its timeout passed, or + the context was interrupted (ResourceBudget). The default solver and the qffp tactic solver + report the same strings.""" + return any(s in reason for s in ('canceled', 'resource', 'timeout', 'interrupted')) + + +def _satisfies(model, assertions) -> bool: + """Whether every assertion is true in the model. Evaluates on a copy, as model completion + adds interpretations to the model it evaluates in, which would change the inputs that + extract_and_encode_values() reports.""" + completed = copy.copy(model) + return all(is_true(completed.evaluate(a, model_completion=True)) for a in assertions) + + class Z3Handler: """Class to handle symbolic solving and related tasks.""" + # The resource units the last query (solve) used, so that a caller can size follow-up queries. + last_rlimit_used: int = 0 + @staticmethod def encode_array(model, array_var, element_type_prefix: str, length: int) -> str: """ @@ -176,7 +262,11 @@ def to_signed(unsigned_value, bit_width): return str(encoded) elif prefix == DataTypes.STRING.value: - return value.as_string() + # as_string() escapes some characters as \u{...} but prints a backslash as is, so its + # output is ambiguous. Read the code points instead. + n = z3.simplify(z3.Length(value)).as_long() + return ''.join(chr(z3.simplify(z3.StrToCode(z3.SubString(value, i, 1))).as_long()) + for i in range(n)) else: raise ValueError(f"Unsupported prefix {prefix} for value {value}") @@ -238,6 +328,9 @@ def extract_and_encode_values(model: Any) -> Dict[str, Dict[str, Union[str, int] for var_decl in model.decls(): # Extract the variable (as an expression) and its value name = str(var_decl.name()) + # Functions (e.g. the executor's Math models, math_tan_3) are not inputs + if var_decl.arity() > 0: + continue # Updated regex to match array names like "[I_0" as well as regular names if not re.match(r'(\[?[\w\/]+)_(\d+)', name): continue @@ -358,7 +451,81 @@ def write_optimizer_to_file(optimizer: Optimize, output_dir: str = "smt_files") return filepath @staticmethod - def solve(node: Any, path_constraints: list) -> Tuple[SATResult, Dict[str, Any]]: + def lift_int2bv(cache, assertions: list) -> Tuple[list, dict]: + """(assertions, inputs): the assertions with comparisons over int_to_bv of string lengths, + indices and char codes made over the Integers, and bit-vector inputs used only as numbers + replaced by Integers (see Int2BvLifting.lift_inputs), which Z3 decides much faster. + Equivalent for Java strings (at most 2^31 - 1 chars of at most 0xFFFF each); `inputs` maps + each Integer stand-in to its input (see map_lifted_inputs). On any error, the assertions as + they are.""" + from solver.Int2BvLifting import Int2BvLifting, lift_inputs + try: + # One lifting per char code range, as each caches its results across queries. + lifting = getattr(cache, 'int2bv_lifting', None) + if lifting is None: + lifting = cache.int2bv_lifting = Int2BvLifting(java_chars=True) + if not all(lifting.java_chars_only(a) for a in assertions): + # The query itself puts a char above 0xFFFF (or one it cannot bound) into a string. + lifting = getattr(cache, 'int2bv_lifting_unicode', None) + if lifting is None: + lifting = cache.int2bv_lifting_unicode = Int2BvLifting() + return lift_inputs(lifting, assertions) + except Exception as e: + logger.warning(f"[SOLVER] int_to_bv lifting failed, solving the query as it is: {e}") + return assertions, {} + + @staticmethod + def map_lifted_inputs(model, inputs: dict, encoded: Dict[str, Any]) -> None: + """Adds the values of the bit-vector inputs that the query had replaced by Integer + stand-ins (see lift_int2bv) to the encoded solution: v = x mod 2^w.""" + for x, v in inputs.values(): + value = model.eval(x, model_completion=True) + if not z3.is_int_value(value): + continue + bv = z3.BitVecVal(value.as_long() % 2 ** v.size(), v.size(), v.ctx) + name = str(v.decl().name()) + prefix, _, idx = name.partition('_') + encoded[name] = { + 'encoded_value': Z3Handler.encode_value(prefix, bv), + 'plain_value': bv, + 'index': idx, + } + + @staticmethod + def shorten_strings(solver, model, t_check: float): + """A model of the solver's assertions in which each string input is as short as they allow + (shortest first for the first input, then the next, ...), or `model` where finding one + takes too long. Z3 fills the chars a query leaves open with whatever it likes, often more of + them than needed; in a parser, every extra char is one more step down an error path. + Each check gets a short timeout (twice the first check's time, at least 0.2 s).""" + solver.set("timeout", max(200, int(2000 * t_check))) + for d in model.decls(): + if d.arity() > 0 or not z3.is_string(d()) or not re.match(r'[\w/]+_\d+$', d.name()): + continue + s = d() + lo, hi = 0, model.eval(z3.Length(s), model_completion=True).as_long() + while lo < hi: + mid = (lo + hi) // 2 + solver.push() + solver.add(z3.Length(s) <= mid) + res = solver.check() + if res == z3.sat: + model = solver.model() + hi = model.eval(z3.Length(s), model_completion=True).as_long() + solver.pop() + elif res == z3.unsat: + solver.pop() + lo = mid + 1 + else: + solver.pop() + return model + # Keep this input at its length while shrinking the next ones. + solver.add(z3.Length(s) == hi) + return model + + @staticmethod + def solve(node: Any, path_constraints: list, timeout_ms: int | None = 60 * 1000, rlimit: int | None = None, + extra_exprs: list | None = None) -> Tuple[SATResult, Dict[str, Any]]: """ Solve for the given node and path constraints using ConstraintCache (modern approach). @@ -368,14 +535,18 @@ def solve(node: Any, path_constraints: list) -> Tuple[SATResult, Dict[str, Any]] Args: - node (Any): The node for which to solve. - path_constraints (list): List of path constraint SMT strings. + - timeout_ms: Wall-clock limit for the check, or None. + - rlimit: Z3 resource limit for the check, or None. Unlike a timeout it is deterministic: + the same query gets as far regardless of machine load. Running out of it gives LIMIT. + - extra_exprs: Further Z3 constraints (in the cache's shared context) on the inputs, e.g. + from retry_constraints(). Returns: - Tuple[SATResult, Dict[str, Any]]: Tuple containing SAT result and solution dictionary. """ - import time from solver.ConstraintCache import get_constraint_cache - t_start = time.time() + t_start = time.perf_counter() # Get the global constraint cache (with shared context) @@ -388,27 +559,43 @@ def solve(node: Any, path_constraints: list) -> Tuple[SATResult, Dict[str, Any]] # Get cached path constraints all_exprs = list(node_exprs) # Start with node constraint + entries = [node_cached] for i, path_constraint_str in enumerate(path_constraints): path_cached = cache.get_smt_constraint(path_constraint_str, source=f"path_{i}") all_exprs.extend(path_cached['exprs']) + entries.append(path_cached) + + # logger.debug(f"[SOLVER] all_exprs: {all_exprs}") - # Create solver with shared context from cache - solver = Solver(ctx=cache.shared_context) - # The FIRST set of expressions are from the node constraint - we need to NEGATE them # (We're looking for a solution that takes a different path) + original, query, lifted_inputs = [], [], {} if len(node_exprs) > 0: - # Negate all node constraint expressions and add them - for expr in node_exprs: - negated = Not(expr) - solver.add(negated) + # Negate all node constraint expressions, then add all path constraint expressions + # (already parsed and cached) + # (The extra constraints restrict inputs too, so they are lifted with the rest.) + original = [Not(expr) for expr in node_exprs] + all_exprs[len(node_exprs):] + list(extra_exprs or []) + query, lifted_inputs = Z3Handler.lift_int2bv(cache, original) - # Add all path constraint expressions (already parsed and cached) - for i, path_expr in enumerate(all_exprs[len(node_exprs):]): - solver.add(path_expr) - - t_path = time.time() - t_start + # Create solver with shared context from cache + solver = Solver(ctx=cache.shared_context) + solver.add(*query) + # The lifting neither adds nor removes floating-point terms, so whether the query has + # any is known from the cached constraints it is built from (each checked once). + fp_tactic = (bool(query) and getattr(Database.instance(), 'fp_tactic', True) + and (any(_entry_has_fp(e) for e in entries) or _has_fp(extra_exprs or []))) + if fp_tactic and lifted_inputs: + # The tactic's model is checked against the original assertions too; it gives the + # Integer stand-ins, not the inputs they replace. + subs = [(v, z3.Int2BV(x, v.size())) for x, v in lifted_inputs.values()] + original = [z3.substitute(a, *subs) for a in original] + + # Building the solver is Z3 work (SMT-LIB parsing on cache misses, assertion + # construction), so it counts towards the SMT solver stage rather than the + # explorer residual. It is not a query, hence count=False. + t_path = time.perf_counter() - t_start + TimingManager.instance().record_solver_time(t_path, count=False) # Array length constraints are now added in the Java symbolic executor # when the array length is made symbolic (InternalInvocation.java) @@ -432,15 +619,14 @@ def solve(node: Any, path_constraints: list) -> Tuple[SATResult, Dict[str, Any]] except Exception as e: logger.warning(f"[SOLVER] Failed to save SMT formula: {e}") - solver.set("timeout", 60 * 1000) - t_start = time.time() - res = solver.check() - t_check = time.time() - t_start - + res, sol, solver, t_check, rlimit_used = Z3Handler.check_query( + solver, query, original, timeout_ms=timeout_ms, rlimit=rlimit, fp_tactic=fp_tactic) + Z3Handler.last_rlimit_used = rlimit_used - if str(res) == SATResult.SAT.value: - t_start = time.time() - sol = solver.model() + if res == SATResult.SAT: + t_start = time.perf_counter() + if Database.instance().prefer_short_strings: + sol = Z3Handler.shorten_strings(solver, sol, t_check) # Log model to disk for debugging (if enabled) if Database.instance().log_smt_formulas: @@ -459,33 +645,180 @@ def solve(node: Any, path_constraints: list) -> Tuple[SATResult, Dict[str, Any]] except Exception as e: logger.warning(f"[SOLVER] Failed to save model: {e}") - # DEBUG: Print the full Z3 model - for decl in sol.decls(): - var_name = decl.name() - var_value = sol[decl] - - # For arrays, show first few elements - if '[' in str(var_name): - from z3 import Int as Z3Int - try: - # Get context from the array to avoid context mismatch - ctx = var_value.ctx if hasattr(var_value, 'ctx') else None - for i in range(min(5, 10)): # Show first 5 elements - idx = Z3Int(i, ctx=ctx) if ctx is not None else Z3Int(i) - elem_val = sol.eval(var_value[idx], model_completion=True) - except: - pass - - # DEBUG: Verify model satisfies constraints - for i, constraint in enumerate(solver.assertions()): - satisfied = sol.evaluate(constraint, model_completion=True) - status = "✓" if satisfied else "✗" + # Sanity check: every assertion must hold under the returned model. + # Evaluating all assertions is real Z3 work, so only do it when DEBUG + # logging is actually enabled. (A model of the qffp tactic is checked always, in + # check_query.) This evaluates in the model itself, so model completion adds the + # inputs the model left out, which extract_and_encode_values() then reports. + if logger.isEnabledFor(logging.DEBUG): + for constraint in solver.assertions(): + if not is_true(sol.evaluate(constraint, model_completion=True)): + logger.warning(f"[SOLVER] Model does not satisfy assertion: {constraint}") encoded_sol = Z3Handler.extract_and_encode_values(sol) - t_encode = time.time() - t_start + Z3Handler.map_lifted_inputs(sol, lifted_inputs, encoded_sol) + t_encode = time.perf_counter() - t_start + TimingManager.instance().record_solver_time(t_encode, count=False) + logger.debug(f"[SOLVER] build={t_path:.3f}s check={t_check:.3f}s model={t_encode:.3f}s rlimit={rlimit_used}") return SATResult.SAT, encoded_sol - else: - return SATResult.UNSAT if str(res) == SATResult.UNSAT.value else SATResult.UNKNOWN, {} + logger.debug(f"[SOLVER] build={t_path:.3f}s check={t_check:.3f}s rlimit={rlimit_used} ({res.value})") + return res, {} + + @staticmethod + def check_query(solver, query: list, original: list | None = None, timeout_ms: int | None = None, + rlimit: int | None = None, fp_tactic: bool = False): + """Checks a query: `solver` (a default z3.Solver) has `query` asserted already. + + With fp_tactic, the query is first given to the qffp tactic (Tactic('qffp').solver()), + which bit-blasts floating-point terms eagerly (fpa2bv). The default solver only does + that for pure QF_FP/QF_FPBV; an uninterpreted function (the SampledMath models) or a + string term (parseFloat) in the query makes it use the lazy theory_fpa of the SMT core, + which is far slower on these queries. Whatever the tactic leaves after bit-blasting + (strings, Int arithmetic, arrays, UFs) goes to the SMT core at the end of its pipeline, + so it takes every query the default solver does. + + - SAT from the tactic counts only if its model satisfies every assertion of `query` and + of `original` (the assertions before the int_to_bv lifting); otherwise the default + solver decides. + - UNSAT from the tactic is trusted as from the default solver. The tactic pipeline + (simplify, propagate-values, fpa2bv, bit-blast, SAT/SMT) is a satisfiability-preserving + transformation by Z3's own rewriters, the same fpa2bv that the default solver applies + to pure QF_FP queries, so its UNSAT is not less reliable than the default solver's. + There is no cheap independent check of UNSAT. On a corpus of 748 queries from SV-COMP + runs (248 of them with FP terms), the two never disagreed on SAT vs UNSAT. + - An unknown because the check was cut short (rlimit ran out, timeout passed, context + interrupted by the ResourceBudget) is the result, as from the default solver: falling + back would give the query its budget twice, and an interrupted check must stay + stopped. An unknown for any other reason, or an exception, falls back to the default + solver with what is left of the timeout and rlimit. + + The rlimit counts the same Z3 resource units for both, but the tactic's work per unit + differs from the SMT core's: on FP queries it decides with fewer units, so a fixed + rlimit (e.g. --approximate-rlimit for queries over approximate Math models) reaches + further with the tactic. + + Returns (result, model or None, the solver that decided or None, check seconds, rlimit + units used).""" + timing = TimingManager.instance() + t_check = 0.0 + rlimit_used = 0 + checks = 0 + + def run(s, timeout, limit): + nonlocal t_check, rlimit_used, checks + if timeout is not None: + s.set("timeout", timeout) + if limit is not None: + # Z3 gives each check this much on top of what the (shared) context has used already. + s.set("rlimit", limit) + t = time.perf_counter() + try: + return s.check() + finally: + dt = time.perf_counter() - t + t_check += dt + # A fallback check is the same query again: one query for the timing stats + timing.record_solver_time(dt, count=checks == 0) + checks += 1 + rlimit_used += _rlimit_used(s) + + timeout_left, rlimit_left = timeout_ms, rlimit + if fp_tactic: + try: + tactic_solver = z3.Tactic('qffp', ctx=solver.ctx).solver() + tactic_solver.add(*query) + res = run(tactic_solver, timeout_ms, rlimit) + if res == z3.unsat: + return SATResult.UNSAT, None, tactic_solver, t_check, rlimit_used + if res == z3.sat: + model = tactic_solver.model() + # by id: most of the original assertions are the very terms of the query + check = list({a.get_id(): a for a in query + list(original or [])}.values()) + if _satisfies(model, check): + return SATResult.SAT, model, tactic_solver, t_check, rlimit_used + logger.warning("[SOLVER] qffp tactic model does not satisfy the query, using the default solver") + else: + reason = tactic_solver.reason_unknown() + if _cut_short(reason): + return Z3Handler._unknown(reason, rlimit, timeout_ms, t_check), None, tactic_solver, t_check, rlimit_used + logger.debug(f"[SOLVER] qffp tactic unknown ({reason}), using the default solver") + except z3.Z3Exception as e: + logger.warning(f"[SOLVER] qffp tactic failed ({e}), using the default solver") + # What is left of the budget for the default solver + if timeout_ms is not None: + timeout_left = int(timeout_ms - t_check * 1000) + if timeout_left <= 0: + return SATResult.UNKNOWN, None, None, t_check, rlimit_used + if rlimit is not None: + rlimit_left = rlimit - rlimit_used + if rlimit_left <= 0: + return Z3Handler._unknown('canceled', rlimit, timeout_ms, t_check), None, None, t_check, rlimit_used + + res = run(solver, timeout_left, rlimit_left) + if res == z3.sat: + return SATResult.SAT, solver.model(), solver, t_check, rlimit_used + if res == z3.unsat: + return SATResult.UNSAT, None, solver, t_check, rlimit_used + return Z3Handler._unknown(solver.reason_unknown(), rlimit, timeout_ms, t_check), None, solver, t_check, rlimit_used + + @staticmethod + def _unknown(reason: str, rlimit: int | None, timeout_ms: int | None, t_check: float) -> SATResult: + """LIMIT or UNKNOWN for a check that ended in unknown for `reason`.""" + # With only an rlimit set, a check cut short is reported as "canceled" (older Z3: + # "max. resource limit exceeded"); anything else is the solver giving up for good. + if rlimit is not None and timeout_ms is None and ('canceled' in reason or 'resource' in reason): + logger.debug(f"[SOLVER] resource limit {rlimit} exhausted after {t_check:.3f}s") + return SATResult.LIMIT + logger.debug(f"[SOLVER] unknown: {reason}") + return SATResult.UNKNOWN + + @staticmethod + def solution_values(sol: Dict[str, Any]) -> Dict[str, Any]: + """The scalar input values of a solution (from extract_and_encode_values) as Z3 values, + by name. Arrays are left out: their plain value is only a description.""" + return {name: val['plain_value'] for name, val in sol.items() if z3.is_expr(val.get('plain_value'))} + + @staticmethod + def bound_constraints(sol: dict, bound: int, ctx=None) -> list: + """Constraints restricting every numeric input of the solution `sol` to [-bound, bound] + (chars: [0, bound]), without excluding `sol` itself (see retry_constraints).""" + return Z3Handler.retry_constraints([sol], bound, ctx)[1:] + + @staticmethod + def retry_constraints(failed: list, bound: int | None = None, ctx=None) -> list: + """Constraints for re-solving a branch whose earlier solutions `failed` did not lead the + run to it: each of them is excluded (by its scalar inputs), and with `bound`, every + numeric input is restricted to [-bound, bound] (chars: [0, bound]; floating-point inputs + also to neither NaN nor infinite). + + Both only remove models, so UNSAT under them proves nothing about the branch. + """ + if ctx is None: + from solver.ConstraintCache import get_constraint_cache + ctx = get_constraint_cache().shared_context + exprs = [] + consts = {} + for sol in failed: + values = Z3Handler.solution_values(sol) + pairs = [(z3.Const(name, v.sort()), v) for name, v in values.items()] + consts.update((name, c) for (c, _), name in zip(pairs, values)) + # With no scalar input to tell models apart, the failed one cannot be excluded alone. + exprs.append(z3.Or([c != v for c, v in pairs]) if pairs else z3.BoolVal(False, ctx=ctx)) + if bound is not None: + for name, c in consts.items(): + if z3.is_bv(c): + width = c.size() + if name.startswith(DataTypes.CHAR.value + '_'): + exprs.append(z3.ULE(c, z3.BitVecVal(min(bound, 2**width - 1), width, ctx=c.ctx))) + elif bound < 2**(width - 1): + exprs.append(z3.And(c >= -bound, c <= bound)) + elif z3.is_int(c): + exprs.append(z3.And(c >= -bound, c <= bound)) + elif z3.is_fp(c): + # (The FPRef operators use c's context; z3.fpGEQ etc. would use the global one.) + lo, hi = z3.FPVal(-bound, fps=c.sort(), ctx=c.ctx), z3.FPVal(bound, fps=c.sort(), ctx=c.ctx) + exprs.append(z3.And(c >= lo, c <= hi)) + return exprs @staticmethod def solve_with_optimization(node: Any, path_constraints: list) -> Tuple[SATResult, Dict[str, Any]]: @@ -499,6 +832,7 @@ def solve_with_optimization(node: Any, path_constraints: list) -> Tuple[SATResul Returns: - Tuple[SATResult, Dict[str, Any]]: Tuple containing SAT result and solution dictionary. """ + build_start = time.perf_counter() c = Not(Z3Handler.string_to_expr(node.constraint[node.trace_id])) optimizer = Optimize() optimizer.add(c) @@ -521,6 +855,10 @@ def solve_with_optimization(node: Any, path_constraints: list) -> Tuple[SATResul optimizer.minimize(Abs(var)) optimizer.set("timeout", 60 * 1000) + # Same accounting as Z3Handler.solve(): building the optimizer is solver work + # but not a query, so it is recorded with count=False. + TimingManager.instance().record_solver_time(time.perf_counter() - build_start, count=False) + smt_file = Z3Handler.write_optimizer_to_file(optimizer) print(f"Saved SMT-LIB file to: {smt_file}") @@ -533,8 +871,10 @@ def solve_with_optimization(node: Any, path_constraints: list) -> Tuple[SATResul TimingManager.instance().record_solver_time(solver_duration) if str(res) == SATResult.SAT.value: + model_start = time.perf_counter() sol = optimizer.model() encoded_sol = Z3Handler.extract_and_encode_values(sol) + TimingManager.instance().record_solver_time(time.perf_counter() - model_start, count=False) return SATResult.SAT, encoded_sol else: return SATResult.UNSAT if str(res) == SATResult.UNSAT.value else SATResult.UNKNOWN, {} diff --git a/symbolic-explorer/strategy/DFS.py b/symbolic-explorer/strategy/DFS.py index 407057e3..2fec416e 100755 --- a/symbolic-explorer/strategy/DFS.py +++ b/symbolic-explorer/strategy/DFS.py @@ -1,19 +1,354 @@ -from typing import List, Set - -from data.BinaryExecutionTree.Node import Node - - -def dfs(visited: Set[Node], tree, node: Node, solved_branches: Set[int], unsat_branch_ids: Set[int]) -> List[Node]: - possible_nodes = list() - if node is not None \ - and node not in visited \ - and isinstance(node, Node): - if (node.skipped is None or node.branched is None) \ - and node.gid not in solved_branches \ - and node.gid not in unsat_branch_ids \ - and node.kind != "Special": - possible_nodes.append(node) - visited.add(node) - possible_nodes.extend(dfs(visited, tree, node.skipped, solved_branches, unsat_branch_ids)) - possible_nodes.extend(dfs(visited, tree, node.branched, solved_branches, unsat_branch_ids)) - return possible_nodes +""" +The walk that finds the branches worth flipping. + +dfs() walks the execution tree and, if there is one, the static pre-analysis (SA) graph in step, +pairing branch for branch by position (see SAGraph for the terms). At each branch of the tree it + +1. **pairs** the branch with a graph branch (`_pair`): it follows the graph to its next branch and + checks that branch's key and call stack against the trace. A mismatch is a *desync*: it is + recorded (which rules out SAFE) and the walk is lost. A lost walk tries to find the graph again + by the trace's key (*resync*). A branch the graph has no node for (in a static initializer, in a + callback from library code, a choice branch) is *masked*: the walk holds its place. Only a + *verified* pairing (checked by key and stack, or found by resync) prunes or slices anything. +2. **prunes**: a side from which no target point is interesting is neither a candidate nor walked. +3. **slices** (`_classify`): a branch the relevance analysis finds cannot affect the targets is no + decision: it is not flipped and its constraint is left out of every query + (StrategyService.collect_path_constrains). Nor is a branch a loop summary merged. Candidates are + identified by the path's relevant decisions (its *sig*) and their key, so paths that differ only + in other decisions are one path for the search. + +Slicing trusts the graph about the region of a sliced branch (Relevance). Each path carries the +regions it is in (its *open regions*); whatever the graph cannot account for inside one -- a branch +it has no node for, a relevant branch, an uncaught exception that ends the path -- makes those +branches relevant for good (*poisons* them, Node.poisoned), and the caller walks again. + +A lost or unverified walk prunes and slices nothing, which is always safe. +""" +from __future__ import annotations + +from collections import Counter +from typing import AbstractSet, Final, Literal, NamedTuple, Optional, Tuple, Union + +from data.BinaryExecutionTree.Node import Ident, Node +from data.BinaryExecutionTree.Leaf import Leaf +from data.BinaryExecutionTree.Tree import Tree +from data.StaticAnalysisGraph.SAGraph import SANode, CallStack, GraphIndex, TraceStack, is_interesting, \ + apply_stack_delta, check_branch, resync +from data.StaticAnalysisGraph.Relevance import in_region + +import logging + +import log +logger = log.get_logger() +# Log level of the per-node trace of the walk (enable with logger.setLevel(TRACE)) +TRACE = logging.DEBUG - 5 + +# Whether dfs uses the graph's relevance analysis (see Relevance): set from --no-sa-slicing. +SLICING = True + +# How the walk paired a branch with the graph (see _pair). Only VERIFIED and RESYNCED pairings prune +# or slice anything. +VERIFIED: Final = "verified" # the graph's branch, checked by key and call stack +RESYNCED: Final = "resynced" # found again by key, under the trace's call stack, after the walk was lost +UNVERIFIED: Final = "unverified" # followed, but a key or a call site is missing to check it +LOST: Final = "lost" # the graph cannot say where the execution is +MASKED: Final = "masked" # a branch the graph has no node for: the walk holds its place +Pairing = Literal["verified", "resynced", "unverified", "lost", "masked"] + +# What a branch is for the search (see _classify). +DECISION: Final = "decision" # flipped, its constraint kept, its outcome part of the path's sig +SLICED: Final = "sliced" # irrelevant, or a summarized loop whose other effects are: opens a region +MERGED: Final = "merged" # merged by a loop summary with only local effects: needs no region +Role = Literal["decision", "sliced", "merged"] + +# The regions of the sliced branches a path is in, innermost first, as a linked list: the sliced +# branch's tree node, its graph node and the call stack there, and the regions outside it. +OpenRegions = Optional[Tuple[Tuple[Node, SANode, CallStack], "OpenRegions"]] + + +class _At(NamedTuple): + """The walk's state on reaching a tree node.""" + node: Node | Leaf | None # the tree node (None: an unexplored side) + sa_node: SANode | None # where the graph walk stands; None: lost + sa_stack: CallStack # the graph walk's call stack + trace_stack: TraceStack # the call stack as the trace reports it (SAGraph.apply_stack_delta) + clinit_depth: int # how many static initializers the path is in + sig: int # the path's relevant decisions so far (see _sig) + open_regions: OpenRegions # the regions of the sliced branches the path is in + walk_only: bool # below a side that cannot reach a target, walked only because a + # path there ended abnormally (_abnormal_below): yields no candidates + + +def dfs(tree: Tree | None, root: Node | Leaf | None, solved_branches: AbstractSet[int], + unsat_branch_ids: AbstractSet[int], sa_entry: SANode | None = None, + distances: dict[Node, float] | None = None) -> list[Node]: + """ + The branches below `root` worth flipping: those with an unexplored side that is interesting, + that are no sliced or merged branch, not solved or found infeasible already, and not covered + by another path with the same relevant decisions. + + `sa_entry` is the graph's entry node (None: no graph). If `distances` is given, it receives + each returned node's SA distance to a target point along its unexplored side; nodes without SA + information are left out. The walk records its counts, desyncs and covered sides on `tree`. + """ + if tree is None: + tree = Tree() + tree.begin_walk() + counts = tree.walk + index: GraphIndex | None = sa_entry.graph_index if sa_entry is not None else None + # The candidates with their open side (see side below), in the walk's order. + possible_nodes: list[tuple[Node, tuple[Ident, bool]]] = [] + # The (candidate identity, side) pairs explored, solved or found infeasible on some path. + covered: set[tuple[Ident, bool]] = set() + + # A pre-order over the tree: a node, then the subtree of its skipped side, then that of its + # branched side. It keeps its own stack, since paths can be longer than the interpreter's + # recursion limit allows. + work = [_At(root, sa_entry, None, None, 0, 0, None, False)] + while work: + at = work.pop() + node, sa_node, sa_stack, trace_stack, clinit_depth, sig, open_regions, walk_only = at + if isinstance(node, Leaf) and open_regions is not None and node.abnormal: + # The path ended with an uncaught exception inside the region of a sliced branch: the + # other outcome might have gone on to a target. + _poison(tree, open_regions, "path ended abnormally") + if not isinstance(node, Node): + continue + + if node.unmodelled_exception: + # Some trace threw an exception the graph does not model (a null dereference, a cast, a + # library call that threw, an Error) right before this node: the graph cannot say + # where execution went, so the walk is lost -- until it finds the graph again by key + # (resync, in _pair). The UNMODELLED_EXCEPTION marker itself is no tree node + # (Tree.structural), so that runs which do and do not throw share the tree. + sa_node, sa_stack = None, None + + if node.kind == "Special": + # Static initializers delimit themselves with CLINIT / INVOKECLINIT_END markers. One left + # by an exception emits no end marker: the rest of the path then counts as inside it. + if node.is_clinit_start(): + clinit_depth += 1 + elif node.is_clinit_end(): + clinit_depth -= 1 + assert clinit_depth >= 0 + work.append(at._replace(node=node.branched, sa_node=sa_node, sa_stack=sa_stack, + clinit_depth=clinit_depth)) + continue + + # A state this path reached before the branch was reached at a smaller position already: + # nothing below can be reached only from here (Tree.register_loop_states). + if node.loop_states and tree.dominated_by_loop_state(node): + counts['loop_state_pruned'] += 1 + continue + counts['branches'] += 1 + + # 1. Pair the branch with a graph branch. + if index is not None: + # The frames entered and left since the previous branch, which happened before this one. + trace_stack = apply_stack_delta(trace_stack, node.pops, node.pushes, index) + # The graph has no node for a branch in a static initializer, in application code that + # library code called back (the graph steps over the library call), or for a choice branch + # (the executor's search for the element a symbolic index selects): the walk holds its place + # until a branch it does have a node for. + masked = clinit_depth > 0 or node.callback or node.choice + pairing: Pairing + if masked: + pairing = MASKED + else: + sa_node, sa_stack, trace_stack, pairing = _pair(tree, node, sa_node, sa_stack, trace_stack, index) + counts[pairing] += 1 + # The graph's branch, if the pairing was verified: only then does the graph prune or slice. + verified = sa_node if pairing in (VERIFIED, RESYNCED) else None + + # 2. Prune: which sides can still reach a target point. + if verified is not None: + fallthrough, branched = verified.sides() + skip_is_interesting = is_interesting(fallthrough, sa_stack) + branch_is_interesting = is_interesting(branched, sa_stack) + counts['pruned_sides'] += (not skip_is_interesting) + (not branch_is_interesting) + else: + skip_is_interesting = branch_is_interesting = True + + # 3. Slice: close the regions the branch is outside of, then see what it is for the search. + if verified is not None: + while open_regions is not None: + _, owner_sa, owner_stack = open_regions[0] + if in_region(owner_sa, owner_stack, verified, sa_stack): + break + open_regions = open_regions[1] + role = _classify(node, verified) + node.sliced = role != DECISION + ident: Ident = (sig, node.key if node.key is not None else ('id', node.id)) + node.ident = ident + if role == DECISION: + if open_regions is not None: + _poison(tree, open_regions, f"branch {node.key or node.id} in its region" + + ("" if verified is not None else " (not paired with the graph)")) + sig_skipped = _sig(tree.sig_ids, ident, False) + sig_branched = _sig(tree.sig_ids, ident, True) + if node.skipped is not None: + covered.add((ident, False)) + if node.branched is not None: + covered.add((ident, True)) + if node.gid in solved_branches or node.gid in unsat_branch_ids: + covered.add((ident, node.branched is None)) # its open side + else: + counts[role] += 1 + sig_skipped = sig_branched = sig + if role == SLICED: + assert verified is not None # (see _classify) + open_regions = ((node, verified, sa_stack), open_regions) + + # One line per branch node and round: at INFO this was most of the log (270 MB in 900 s on + # MinePump) and much of the explorer's own CPU time, so it is below DEBUG. + if logger.isEnabledFor(TRACE): + place = ("CALLBACK" if node.callback else "CHOICE" if node.choice else "CLINIT") if masked \ + else sa_node.id if sa_node is not None else "-" + logger.log(TRACE, f"[DFS] @{node.id}/{place} {pairing} " + f"({'branched' if node.branched else ''}{'skipped' if node.skipped else ''}, {role}): " + f"skip_is_interesting={skip_is_interesting}, branch_is_interesting={branch_is_interesting}") + + # The node itself, if its open side is worth flipping. + if role == DECISION and not walk_only and not node.concrete_only \ + and ((node.skipped is None and skip_is_interesting) or (node.branched is None and branch_is_interesting)) \ + and node.gid not in solved_branches and node.gid not in unsat_branch_ids: + possible_nodes.append((node, (ident, node.branched is None))) # (True: the branched side is open) + if distances is not None and sa_node is not None and not masked: + unexplored = sa_node.sides()[0] if node.skipped is None else sa_node.sides()[1] + distances[node] = unexplored.targetDistance + + # Walk on along both sides, each with its own successor in the graph. The branched side goes + # on the stack first, so that the skipped side's subtree is walked before it. A side that + # cannot reach a target is still walked (for the regions only) if a path below it ended + # abnormally inside an open region. + sa_skipped: SANode | None + sa_branched: SANode | None + if sa_node is not None and not masked: + sa_skipped, sa_branched = sa_node.sides() + else: + sa_skipped = sa_branched = sa_node + for child, sa_child, interesting, child_sig in ((node.branched, sa_branched, branch_is_interesting, sig_branched), + (node.skipped, sa_skipped, skip_is_interesting, sig_skipped)): + if interesting and not walk_only: + child_walk_only = False + elif open_regions is not None and _abnormal_below(child): + child_walk_only = True + else: + continue + work.append(_At(child, sa_child, sa_stack, trace_stack, clinit_depth, child_sig, open_regions, + child_walk_only)) + + # Candidates whose side a path with the same relevant decisions has covered already, and + # repeats of the same candidate, are left out. + tree.covered = covered + kept: list[Node] = [] + seen: set[tuple[Ident, bool]] = set() + for n, side in possible_nodes: + if side in covered or side in seen: + counts['covered'] += 1 + if distances is not None: + distances.pop(n, None) + continue + seen.add(side) + kept.append(n) + return kept + + +def _pair(tree: Tree, node: Node, sa_node: SANode | None, sa_stack: CallStack, trace_stack: TraceStack, + index: GraphIndex | None) -> tuple[SANode | None, CallStack, TraceStack, Pairing]: + """ + Pairs the tree's branch `node` with a graph branch: follows the graph from `sa_node` to its next + branch and checks that against the trace; if the walk is lost (or just found out of step), + finds the graph again by the trace's key. Returns (sa_node, sa_stack, trace_stack, pairing): + the graph branch and its call stack (None: lost), the trace stack, and how the branch was + paired (VERIFIED, RESYNCED, UNVERIFIED or LOST). A pairing that cannot be checked (a key or a + call site missing) is still followed, so that a later branch can be checked against the walk, + but it is UNVERIFIED. + """ + if sa_node is not None: + walked = sa_node.walk_till_branch(sa_stack) # idempotent + sa_node, sa_stack = walked if walked is not None else (None, None) + if sa_node is not None: + in_step = check_branch(sa_node, sa_stack, node.key, trace_stack) + if in_step is True: + # The same frames: sharing the cells keeps later checks cheap (SAGraph.same_stacks). + return sa_node, sa_stack, sa_stack, VERIFIED + if in_step is None: + return sa_node, sa_stack, trace_stack, UNVERIFIED + # The walk paired this branch with the wrong graph node: its pruning so far on this path + # may be wrong too where a branch could not be checked, so the verdict cannot be SAFE + # (SVCompDriver). + logger.warning(f"[DFS] SA desync @{node.id}: trace {node.key}, graph {sa_node.id} {sa_node.key}") + tree.record_execution_error('sa_desync', f'trace {node.key}, graph {sa_node.key}') + tree.walk['desyncs'] += 1 + resumed = resync(node.key, trace_stack, index) + if resumed is None: + return None, None, trace_stack, LOST + # Found by the trace's key under the trace's own call stack. + return resumed[0], resumed[1], trace_stack, RESYNCED + + +def _classify(node: Node, verified: SANode | None) -> Role: + """ + What the branch is for the search: a DECISION, SLICED or MERGED. `verified` is the graph's + branch it was paired with, if the pairing was verified (only then can it be SLICED). + + A loop summary's branch (Node.merged) is no decision: both outcomes lead to the merged state. + If the executor merged a loop whose body does other things too (a call, a field or array + access; Node.merged_conditional), that holds only if the graph shows that those cannot matter + (Relevance: summary_ok), and like a sliced branch it then opens a region. Unhonored, the closed + forms with the kept constraints describe the same path: sound. + """ + if node.merged and not node.merged_conditional: + return MERGED + if SLICING and verified is not None and not node.poisoned \ + and (verified.irrelevant or (node.merged and verified.summary_ok)): + return SLICED + return DECISION + + +def _abnormal_below(child: Node | Leaf | None) -> bool: + """Whether a path through `child` ended with an uncaught exception.""" + if isinstance(child, Leaf): + return child.abnormal + return child is not None and child.abnormal_below + + +def _sig(sig_ids: dict[tuple[Ident, bool], int], ident: Ident, side: bool) -> int: + """The sig of a path that has the relevant decisions of sig `ident[0]`, then took `side` at + the branch `ident[1]`: a number for the sequence of relevant decisions, interned on the tree so + that it is the same in every walk.""" + key = (ident, side) + s = sig_ids.get(key) + if s is None: + s = len(sig_ids) + 1 + sig_ids[key] = s + return s + + +def _poison(tree: Tree, open_regions: OpenRegions, why: str) -> None: + """Makes the sliced branches whose regions are open relevant again, for good.""" + while open_regions is not None: + owner = open_regions[0][0] + if not owner.poisoned: + owner.poisoned = True + tree.walk['poisoned'] += 1 + tree.poisoned_total += 1 + logger.debug(f"[DFS] Sliced branch {owner.key or owner.id} is relevant after all: {why}") + open_regions = open_regions[1] + + +def describe_walk(counts: Counter[str]) -> str: + """One line on what a walk did, from its counts (Tree.walk), in the terms of the module comment: + how its branches were paired, sides pruned, and (with slicing) branches sliced or merged, + candidates covered by a path with the same relevant decisions, and sliced branches poisoned.""" + c = counts + line = (f"{c['branches']} branches ({c[VERIFIED]} verified, {c[RESYNCED]} resynced, {c[UNVERIFIED]} unverified, " + f"{c[LOST]} lost, {c[MASKED]} masked), {c['pruned_sides']} sides pruned") + if c[SLICED] or c[MERGED] or c['covered'] or c['poisoned']: + line += f"; {c[SLICED]} sliced, {c[MERGED]} merged, {c['covered']} covered, {c['poisoned']} poisoned" + if c['desyncs']: + line += f"; {c['desyncs']} desyncs" + if c['loop_state_pruned']: + line += f"; {c['loop_state_pruned']} subtrees below a repeated loop-head state" + return line diff --git a/symbolic-explorer/strategy/StrategyService.py b/symbolic-explorer/strategy/StrategyService.py index 5d97d725..4aad0c92 100755 --- a/symbolic-explorer/strategy/StrategyService.py +++ b/symbolic-explorer/strategy/StrategyService.py @@ -1,31 +1,173 @@ +from __future__ import annotations + +import functools +from collections import Counter import json -import logging +import math +import re +import time +from typing import AbstractSet, Any, Iterable +from data.BinaryExecutionTree.Leaf import Leaf from data.BinaryExecutionTree.Node import Node +from data.BinaryExecutionTree.Tree import Tree +from data.StaticAnalysisGraph.SAGraph import SANode from data.Database import Database -from strategy.DFS import dfs +from strategy.DFS import dfs, describe_walk from solver.SolverHandler import SATResult, Z3Handler - -logger = logging.getLogger(__name__) +from timing.TimingManager import TimingManager + +import log +# The explorer's logger: a module logger would reach neither explorer.log nor the console. +logger = log.get_logger() + +# A declaration of a function with at least one argument, i.e. an uninterpreted function rather +# than a variable: "(declare-fun f ( ...) )". +_UF_DECLARATION = re.compile(r'\(declare-fun\s+(\|[^|]*\||[^\s()]+)\s+\(\s*[^\s)]') + +# Prefix of the functions the executor builds approximate Math models from (SampledMath.UF_PREFIX). +APPROXIMATE_UF_PREFIX = 'math_' + + +@functools.lru_cache(maxsize=4096) +def declared_ufs(smt: str) -> frozenset[str]: + """The uninterpreted functions an SMT-LIB string declares.""" + return frozenset(name.strip("|") for name in _UF_DECLARATION.findall(smt)) + + +# Search orders for the branches to flip (--search-order): "dfs" is the walk's own pre-order, +# "shallow" takes the branches closest to the root first, "coverage" first those whose open side no +# trace has taken at any position (then the shallowest), "coverage-dfs" the same with DFS order. +# "generational" is "coverage", but among the branches that reach nothing new, it first takes those +# of the traces that covered the most sides first (as SAGE's generational search): a trace that +# found something new is where combinations with it are found. +# "coverage" is the default: on sv-benchmarks and swat-benchmarks it finds more violations than +# "dfs" in the same time (2026-11-03, see CLAUDE.md), and the order cannot change a SAFE verdict. +SEARCH_ORDERS = ("dfs", "shallow", "coverage", "coverage-dfs", "generational") + +# The loop count in the top 6 bits of an iid; without it, an iid names the branch instruction +# (and switch case) wherever a loop runs it. +_STATIC_IID_MASK = (1 << 58) - 1 + + +def depth(node: Node) -> int: + """The number of branches above node on its path (cached on the node, Node.depth_cache).""" + d = node.depth_cache + if d is None: + chain = [] + n: Node | None = node + while n is not None and n.depth_cache is None: + chain.append(n) + n = n.parent + d = -1 if n is None else n.depth_cache + assert d is not None + for m in reversed(chain): + d += 1 + m.depth_cache = d + return d + + +def open_side(node: Node) -> tuple[int, bool]: + """The branch instruction and side that flipping node would take.""" + return node.id & _STATIC_IID_MASK, node.skipped is not None + + +def covered_sides(root: Node | Leaf | None) -> set[tuple[int, bool]]: + """The (instruction, side) pairs some trace in the tree took.""" + return set(first_coverers(root)) + + +def first_coverers(root: Node | Leaf | None) -> dict[tuple[int, bool], Node]: + """For each (instruction, side) pair some trace in the tree took, the node through which it + was taken first (nodes are numbered in the order they were added, by gid).""" + first: dict[tuple[int, bool], Node] = {} + stack: list[Node | Leaf | None] = [root] + while stack: + n = stack.pop() + if not isinstance(n, Node): + continue + if n.kind == "Branch": + for side, child in ((True, n.branched), (False, n.skipped)): + if child is not None: + pair = (n.id & _STATIC_IID_MASK, side) + if pair not in first or n.gid < first[pair].gid: + first[pair] = n + stack.append(n.branched) + stack.append(n.skipped) + return first + + +def trace_scores(first: dict[tuple[int, bool], Node]) -> dict[str, int]: + """How many (instruction, side) pairs each trace took first, by trace id.""" + scores: dict[str, int] = {} + for n in first.values(): + scores[n.trace_id] = scores.get(n.trace_id, 0) + 1 + return scores class StrategyService: + # is_symbolic_branch per (node gid, trace id) + _symbolic_branches: dict[tuple[int, str], bool] = {} @staticmethod - def select_branch(**kwargs): - endpoint_id = kwargs.get('endpoint_id', None) + def select_branch(endpoint_id: str | int | None, sa_node: SANode | None = None, + rank_by_distance: bool = False, search_order: str = "coverage") -> list[Node]: + """The branches worth flipping on the endpoint's tree (see dfs), in the order to try them. + `sa_node` is the SA graph's entry node (None: no graph); `search_order` is one of + SEARCH_ORDERS.""" db = Database.instance() - tree = db.get_tree(endpoint_id) - visited = set() - possible_branches = dfs(visited, tree, tree.root, db.get_solutions(), db.get_unsat_branches()) + # With rank_by_distance, the branches are tried closest-to-a-target first. Only the order + # changes, so which branches exist, and hence the verdict, does not. The sort is stable: + # ties, and branches without SA information (ranked last), keep their DFS order. + distances: dict[Node, float] | None = {} if rank_by_distance else None + order = search_order + covered: set[tuple[int, bool]] = set() + scores: dict[str, int] = {} + # The walk is on the Database's tree itself, so dfs records where the SA walk fell out of + # step there directly. The returned nodes are live: they gain children as traces arrive. + def walk(tree: Tree, solved: AbstractSet[int], unsat: AbstractSet[int]) -> tuple[list[Node], dict[str, int]]: + nonlocal covered, scores, distances + sa_entry = sa_node + found = dfs(tree, tree.root, solved, unsat, sa_entry, distances=distances) + # A sliced branch that turned out relevant (dfs poisons it) changes which constraints + # and decisions count on every path below it: walk again. Poisoning is for good, so + # this ends. + while tree.walk['poisoned']: + distances = {} if distances is not None else None + found = dfs(tree, tree.root, solved, unsat, sa_entry, distances=distances) + if order.startswith("coverage") or order == "generational": + first = first_coverers(tree.root) + covered = set(first) + scores = trace_scores(first) + return found, dict(tree.walk) + possible_branches, counts = db.visit_tree(endpoint_id, walk) + logger.info(f'[DFS] Walk: {describe_walk(Counter(counts))}') + StrategyService.walk_stats = counts + # All sorts are stable: ties keep their DFS order. + if order == "shallow": + possible_branches.sort(key=depth) + elif order == "coverage": + possible_branches.sort(key=lambda n: (open_side(n) in covered, depth(n))) + elif order == "coverage-dfs": + possible_branches.sort(key=lambda n: open_side(n) in covered) + elif order == "generational": + possible_branches.sort(key=lambda n: (open_side(n) in covered, -scores.get(n.trace_id, 0), depth(n))) + if distances: + possible_branches.sort(key=lambda n: distances.get(n, math.inf)) return possible_branches + # The counts of the last select_branch walk (Tree.walk), for stats.json. + walk_stats: dict[str, int] = {} + @staticmethod - def collect_path_constrains(node: Node) -> list(): - path_constraints = list() + def collect_path_constrains(node: Node, dropped: list[Node] | None = None, full: bool = False) -> list[str]: + """The constraints of the branches above node on its path, except (unless `full`) those of + the branches the last walk sliced (dfs: irrelevant to the targets), which are counted in + `dropped`.""" + path_constraints: list[str] = [] trace_id = node.trace_id while node.parent is not None: @@ -34,13 +176,18 @@ def collect_path_constrains(node: Node) -> list(): if trace_id in node.constraint: c = node.constraint[trace_id] if "declare-fun" in c: + if not full and node.sliced: + if dropped is not None: + dropped.append(node) + continue path_constraints.append(c) return path_constraints @staticmethod - def collect_input_constrains(node: Node) -> list(): - path_constraints = list() + def collect_input_constrains(node: Node) -> list[str]: + """The inputs' bounds (SMT-LIB assertions).""" + path_constraints: list[str] = [] for input in node.inputs: path_constraints.append(input.lower_bound) @@ -49,7 +196,7 @@ def collect_input_constrains(node: Node) -> list(): return path_constraints @staticmethod - def collect_array_length_constraints(node: Node, max_length: int) -> list(): + def collect_array_length_constraints(node: Node, max_length: int) -> list[str]: """ Add constraints limiting array lengths to a maximum value. This prevents Z3 from generating solutions with unreasonably large arrays. @@ -75,30 +222,126 @@ def collect_array_length_constraints(node: Node, max_length: int) -> list(): return constraints @staticmethod - def collect_uf_definitions(node: Node) -> list(): - uf_definitions = list() + def collect_uf_definitions(node: Node) -> list[str]: + uf_definitions: list[str] = [] for uf in node.ufs: uf_definitions.append(uf.definition) return uf_definitions @staticmethod - def solve_branch(possible_branch: Node, endpoint_id=None): - db = Database.instance() + def slice_uf_definitions(definitions: list[str], query: list[str]) -> tuple[list[str], set[str]]: + """The definitions the query needs, and the uninterpreted functions it then contains. + + A definition is needed if it mentions a function the query or another needed definition + contains, or no function at all. The others only constrain functions that occur nowhere + else in the query; the executor gives every model call its own function, so each of them + can be satisfied by its function's value whatever the inputs, and leaving it out changes + neither satisfiability nor the inputs of any model. + """ + needed: set[str] = set() + for smt in query: + needed |= declared_ufs(smt) + pending = [(d, declared_ufs(d)) for d in definitions] + sliced = [] + changed = True + while changed: + changed = False + rest = [] + for d, ufs in pending: + if not ufs or ufs & needed: + sliced.append(d) + needed |= ufs + changed = True + else: + rest.append((d, ufs)) + pending = rest + # Keep the order in which the executor recorded them. + order = {d: i for i, d in enumerate(definitions)} + sliced.sort(key=lambda d: order[d]) + return sliced, needed - path_constraints = StrategyService.collect_path_constrains(possible_branch) + @staticmethod + def collect_query(node: Node, full: bool = False) -> tuple[list[str], set[str]]: + """The constraints of the query for the unexplored side of the branch, besides its own + constraint, and the uninterpreted functions the query contains; with `full`, also those of + the sliced branches.""" + dropped: list[Node] = [] + path_constraints = StrategyService.collect_path_constrains(node, dropped, full) + if not full: + # Whether the query left out a constraint: the run of its solution may then take other + # irrelevant decisions and reach the branch on another path (see SVCompDriver). + node.sliced_query = bool(dropped) + path_constraints.extend(StrategyService.collect_input_constrains(node)) + node_constraint = node.constraint.get(node.trace_id, '') + definitions, ufs = StrategyService.slice_uf_definitions( + StrategyService.collect_uf_definitions(node), path_constraints + [node_constraint]) + path_constraints.extend(definitions) + return path_constraints, ufs - path_constraints.extend(StrategyService.collect_input_constrains(possible_branch)) + @staticmethod + def is_approximate(ufs: Iterable[str]) -> bool: + """Whether a query with these functions depends on an approximate Math model. Only a branch + constraint brings one in, and the executor reports such a trace as precision loss.""" + return any(uf.startswith(APPROXIMATE_UF_PREFIX) for uf in ufs) - path_constraints.extend(StrategyService.collect_uf_definitions(possible_branch)) + @staticmethod + def is_approximate_query(node: Node) -> bool: + """Whether the query for the unexplored side of the branch depends on an approximate Math model.""" + return StrategyService.is_approximate(StrategyService.collect_query(node, full=True)[1]) + + @staticmethod + def solve_branch(possible_branch: Node, endpoint_id: str | int | None = None, solver_timeout_ms: int | None = 60 * 1000, + solver_rlimit: int | None = None, extra_exprs: list[Any] | None = None, + approximate_rlimit: int | None = None) -> tuple[SATResult, dict[str, Any]]: + """Solves for the unexplored side of the branch. LIMIT (the rlimit ran out) records nothing, + so the branch stays a candidate and can be retried with a larger limit. + + extra_exprs further restrict the inputs (see Z3Handler.retry_constraints). UNSAT under them + does not make the branch infeasible, so it is not recorded either. + + A query over an approximate Math model can only give a guess, and a branch whose search + depends on one never ends in SAFE (the executor reports precision loss). With + approximate_rlimit, such a query gets at most that resource limit, and running out of it is + UNKNOWN (given up), not LIMIT.""" + db = Database.instance() + + path_constraints, ufs = StrategyService.collect_query(possible_branch) inputs = possible_branch.inputs - sat, sol = Z3Handler.solve(possible_branch, path_constraints) + capped = False + if approximate_rlimit and StrategyService.is_approximate(ufs): + if solver_rlimit is None or solver_rlimit >= approximate_rlimit: + solver_rlimit = approximate_rlimit + capped = True + + sat = None + if possible_branch.sliced_query: + # The full query first: a model of it is one of the sliced query too, and its run stays + # on the path the tree knows. The sliced query decides only where the full one has no + # model (or the solver cannot tell), which is where leaving constraints out matters. + # (A solver is free to pick any model of the sliced query, also one far from the path, + # e.g. an array size of 2^30 the executor cannot allocate.) + full_constraints, full_ufs = StrategyService.collect_query(possible_branch, full=True) + if not (capped or (approximate_rlimit and StrategyService.is_approximate(full_ufs))): + sat, sol = Z3Handler.solve(possible_branch, full_constraints, timeout_ms=solver_timeout_ms, + rlimit=solver_rlimit, extra_exprs=extra_exprs) + if sat == SATResult.SAT: + possible_branch.sliced_query = False + else: + sat = None + if sat is None: + sat, sol = Z3Handler.solve(possible_branch, path_constraints, timeout_ms=solver_timeout_ms, rlimit=solver_rlimit, + extra_exprs=extra_exprs) + + if sat == SATResult.LIMIT and capped: + logger.info(f'[SOLVER] Query over an approximate model for branch {possible_branch.id} ran out of resource limit {solver_rlimit}, giving up on it') + sat = SATResult.UNKNOWN if sat == SATResult.SAT: db.add_solution(branch_id=possible_branch.gid, sol=sol, inputs=inputs, endpoint_id=endpoint_id) - elif sat == SATResult.UNSAT: + elif sat == SATResult.UNSAT and not extra_exprs: db.add_unsat_branch(possible_branch.gid) return sat, sol @@ -122,16 +365,26 @@ def is_symbolic_branch(node: Node) -> bool: """ from solver.ConstraintCache import get_constraint_cache + # The answer depends only on the node's constraint and inputs, which are fixed when the + # node is created; a branch not taken stays a candidate, so it is asked again every round. + key = (node.gid, node.trace_id) + known = StrategyService._symbolic_branches.get(key) + if known is not None: + return known + try: - # Get cached parsed constraint (parse once, reuse many times) + # Get cached parsed constraint (parse once, reuse many times). + # On a cache miss this parses SMT-LIB via Z3 and walks the AST, which is + # solver work and must not be charged to the explorer residual. It is not + # a solver query, hence count=False. cache = get_constraint_cache() + t_parse = time.perf_counter() cached_data = cache.get_node_constraint(node) + TimingManager.instance().record_solver_time(time.perf_counter() - t_parse, count=False) # Extract variables from the parsed constraint constraint_vars = cached_data['variables'] - logger.info(f"[is_symbolic_branch] Branch {node.id}: found {len(constraint_vars)} variables in constraint") - logger.debug(f"[is_symbolic_branch] Variables: {constraint_vars}") # Get input variable names (symbolic variables we're tracking) input_vars = set() @@ -146,13 +399,14 @@ def is_symbolic_branch(node: Node) -> bool: input_vars.add(f"{inp.name}_length") input_vars.add(f"|{inp.name}_length|") - logger.debug(f"[is_symbolic_branch] Input variables: {input_vars}") # Check if any constraint variable matches an input variable is_symbolic = bool(constraint_vars & input_vars) - logger.info(f"[is_symbolic_branch] Branch {node.id} is_symbolic: {is_symbolic}") + logger.debug(f"[is_symbolic_branch] Branch {node.id} is_symbolic: {is_symbolic} " + f"(constraint variables {sorted(constraint_vars)}, inputs {sorted(input_vars)})") + StrategyService._symbolic_branches[key] = is_symbolic return is_symbolic except Exception as e: diff --git a/symbolic-explorer/svcomp/ExecutorCDS.py b/symbolic-explorer/svcomp/ExecutorCDS.py new file mode 100644 index 00000000..97c6bb14 --- /dev/null +++ b/symbolic-explorer/svcomp/ExecutorCDS.py @@ -0,0 +1,180 @@ +"""Class data sharing (AppCDS) archive for the executor JVMs of one verification run. + +Every exploration round starts a new executor JVM, which loads, parses and verifies the same few +thousand classes again: those of the agent jar (ASM, Jackson, logback, the executor itself) and the +JDK classes outside the JDK's default CDS archive. A static AppCDS archive of these classes, dumped +once per run, lets each later JVM map them instead (about a quarter of a round's CPU time). + +Only how classes are loaded changes, never which bytes: the JVM still offers every archived class +to the agent's ClassFileTransformers, and a class a transformer changes is parsed from the new +bytes as before. Task classes come from directories, which CDS does not archive. + +Layout: CDS archives classes from the leading jar entries of the class path only (a class path +directory before them that classes are loaded from makes the dump fail). The class path therefore +gets a prefix of all its jars plus the agent jar, in their order, before the original class path +(the agent jar is otherwise appended by the JVM behind it). This is done only when no file of a +moved jar also exists in an entry that preceded it, so that every class and resource name still +resolves to the same file. The prefix is used from round 0 on, so all rounds load classes alike. + +Steps: round 0 writes the list of loaded classes (-XX:DumpLoadedClassList); before round +DUMP_AT_ROUND the archive is dumped (-Xshare:dump, about one and a half rounds of CPU time, so +short runs do not pay for it); later rounds use it (-XX:SharedArchiveFile). If any step fails the +run goes on without the archive; a JVM that cannot use an archive (e.g. a jar changed) silently +runs without it (-Xshare:auto, the default). +""" +import os +import shutil +import subprocess +import tempfile +import time +import zipfile + +import log + +logger = log.get_logger() + +# The round before which the archive is dumped: runs of fewer rounds do not pay for the dump +DUMP_AT_ROUND = 3 +# Do not scan class path directories with more files than this for name clashes +MAX_SCAN_FILES = 50_000 +DUMP_TIMEOUT_S = 120 + + +def _names(entry: str) -> set[str] | None: + """The file names (as class path resource names) of a class path entry; None if unknown.""" + if os.path.isdir(entry): + names = set() + for root, _, files in os.walk(entry): + rel = os.path.relpath(root, entry) + for f in files: + names.add(f if rel == '.' else f'{rel}/{f}'.replace(os.sep, '/')) + if len(names) > MAX_SCAN_FILES: + return None + return names + if os.path.isfile(entry): + try: + with zipfile.ZipFile(entry) as z: + return {n for n in z.namelist() if not n.endswith('/')} + except (zipfile.BadZipFile, OSError): + return None + return set() # a missing entry contributes nothing + + +def class_path_layout(cp: list[str], agent: str) -> list[str] | None: + """The class path with the jar prefix (see the module doc), or None if moving the jars could + change which file a class or resource name resolves to.""" + effective = list(cp) + [agent] + prefix: list[str] = [] + for e in effective: + if e.endswith('.jar') and os.path.isfile(e) and e not in prefix: + prefix.append(e) + if not prefix: + return None + cache: dict[str, set[str] | None] = {} + + def names(e): + if e not in cache: + cache[e] = _names(e) + return cache[e] + + for k, jar in enumerate(prefix): + moved = names(jar) + if moved is None: + return None + # Entries that came before the jar and do not now (all but the earlier prefix jars) + for e in effective[:effective.index(jar)]: + if e in prefix[:k]: + continue + other = names(e) + if other is None or moved & other: + return None + return prefix + list(cp) + + +class ExecutorCDS: + def __init__(self, enabled: bool, base_dir: str | None = None, dump_at_round: int = DUMP_AT_ROUND): + self.enabled = enabled + self.base_dir = base_dir + self.dump_at_round = dump_at_round + self.dir: str | None = None + self.layout: list[str] | None = None + self.archive: str | None = None + self.classlist: str | None = None + self.dumped = False + self.dump_cpu_s = 0.0 + self.dump_wall_s = 0.0 + self._layout_done = False + + def class_path(self, cp: list[str], agent: str) -> list[str]: + """The class path for the executor JVM (computed once per run; the same in every round, + also after a failed dump).""" + if self._layout_done or not self.enabled: + return self.layout or cp + if not self._layout_done: + self._layout_done = True + try: + self.layout = class_path_layout(cp, agent) + except Exception as e: # never fail a run over an optimisation + logger.warning(f'[CDS] class path layout failed: {e}') + self.layout = None + if self.layout is None: + logger.info('[CDS] jars cannot be moved to the front of the class path, CDS is off') + self.enabled = False + return cp + try: + self.dir = tempfile.mkdtemp(prefix='swat-cds-', dir=self.base_dir) + except OSError as e: + logger.warning(f'[CDS] no directory for the archive: {e}') + self.enabled = False + self.layout = None + return cp + self.classlist = os.path.join(self.dir, 'classes.lst') + self.archive = os.path.join(self.dir, 'executor.jsa') + return self.layout if self.layout is not None else cp + + def jvm_options(self, round_idx: int) -> list[str]: + """The CDS options of the executor JVM of the given round.""" + if not self.enabled or self.dir is None: + return [] + if round_idx == 0: + return [f'-XX:DumpLoadedClassList={self.classlist}'] + if self.dumped: + return [f'-XX:SharedArchiveFile={self.archive}'] + return [] + + def prepare_round(self, round_idx: int, java_path: str, jvm_options): + """Dumps the archive before round dump_at_round; jvm_options() gives the executor's heap and + other JVM options (the dump has to use the same heap layout, e.g. compressed oops).""" + if not self.enabled or self.dumped or self.dir is None or round_idx < self.dump_at_round: + return + self.enabled = False # one attempt; set again below on success + if not (self.classlist and os.path.isfile(self.classlist) and os.path.getsize(self.classlist) > 0): + logger.info('[CDS] no class list from round 0, CDS is off') + return + prefix = [e for e in self.layout if e.endswith('.jar')] + prefix = prefix[:next((i for i, e in enumerate(self.layout) if not e.endswith('.jar')), len(prefix))] + cmd = [java_path, *jvm_options(), '-Xshare:dump', f'-XX:SharedClassListFile={self.classlist}', + f'-XX:SharedArchiveFile={self.archive}', '-cp', ':'.join(prefix)] + before = os.times() + start = time.perf_counter() + try: + with open(os.path.join(self.dir, 'dump.log'), 'w') as out: + p = subprocess.run(cmd, stdout=out, stderr=subprocess.STDOUT, timeout=DUMP_TIMEOUT_S) + ok = p.returncode == 0 and os.path.isfile(self.archive) + except (OSError, subprocess.SubprocessError) as e: + logger.warning(f'[CDS] archive dump failed: {e}') + ok = False + after = os.times() + self.dump_wall_s = time.perf_counter() - start + self.dump_cpu_s = (after.children_user + after.children_system) - (before.children_user + before.children_system) + if ok: + self.enabled = self.dumped = True + logger.info(f'[CDS] archive dumped before round {round_idx} ' + f'({os.path.getsize(self.archive) >> 20} MiB, {self.dump_cpu_s:.2f} s CPU)') + else: + logger.warning(f'[CDS] archive dump failed (see {self.dir}/dump.log), CDS is off') + + def cleanup(self): + if self.dir is not None: + shutil.rmtree(self.dir, ignore_errors=True) + self.dir = None diff --git a/symbolic-explorer/svcomp/PropertyPreCheck.py b/symbolic-explorer/svcomp/PropertyPreCheck.py new file mode 100644 index 00000000..430d861d --- /dev/null +++ b/symbolic-explorer/svcomp/PropertyPreCheck.py @@ -0,0 +1,107 @@ +""" +Static property pre-check for the SV-COMP driver. + +A conservative bytecode analysis of the whole program on the classpath, part of the static +pre-analysis in cfg-extraction (de.uzl.its.cfg.staticcheck.ExceptionFreedomCheck, built as +cfg-extractor-precheck.jar). It proves, or fails to prove: + +- for no-runtime-exception: no RuntimeException can escape Main.main or a static initializer; +- for valid-assert: no AssertionError is ever created, so no assert can fail. + +A proven property holds on every execution, independently of what the symbolic exploration can +model, so the driver may answer SAFE even where context loss, precision loss, uncaught exceptions +or an unexhausted path space would otherwise prevent it. A failed, crashed or timed-out check +proves nothing, and the driver proceeds as without it. + +The check prints one JSON line: tool and version (cfg-extraction's git describe), and per property +whether it is proven and why (not). +""" + +import json +import os +import subprocess +import time + +import log +logger = log.get_logger() + +CHECK_CLASS = "de.uzl.its.cfg.staticcheck.ExceptionFreedomCheck" +CHECK_TIMEOUT_S = 60 +JAR_NAME = "cfg-extractor-precheck.jar" +_EXPLORER_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +# Where the jar is found without --precheck-jar: next to symbolic-explorer in the SV-COMP archive, +# and in the build output of the cfg-extraction checkout next to SWAT's in the workspace. +DEFAULT_JARS = ( + os.path.join(_EXPLORER_DIR, "..", "cfg-extraction", JAR_NAME), + os.path.join(_EXPLORER_DIR, "..", "..", "cfg-extraction", "build", "libs", JAR_NAME), +) + +# The property file name -> the field of the check's result that proves it. +PROVES = { + "no-runtime-exception.prp": ("exception_free", "reason"), + "valid-assert.prp": ("assertion_free", "assertion_reason"), +} + + +class PropertyPreCheck: + def __init__(self, args, prop: str): + self.args = args + self.prop = prop + self.enabled = prop in PROVES and not getattr(args, "no_property_precheck", False) + self.status = "disabled" if not self.enabled else "not_started" + self.proven = False + self.reason: str | None = None + self.duration: float | None = None + self.version: str | None = None + + @staticmethod + def _is_swat_library(path: str) -> bool: + """Z3's Java bindings, which SWAT itself puts on the target's classpath: not part of the program.""" + return os.path.basename(path) == "com.microsoft.z3.jar" + + def jar(self) -> str | None: + """The check's jar: --precheck-jar, else the first of DEFAULT_JARS that exists.""" + given = getattr(self.args, "precheck_jar", None) + if given: + return given + return next((os.path.normpath(p) for p in DEFAULT_JARS if os.path.isfile(p)), None) + + def command(self) -> list[str]: + classpath = [os.path.abspath(p) for p in self.args.classpath if not self._is_swat_library(p)] + return [self.args.java_path, "-Xmx2g", "-cp", self.jar(), CHECK_CLASS, *classpath] + + def run(self) -> bool: + """Runs the check (blocking) and returns whether it proved the property.""" + if not self.enabled: + return False + start = time.perf_counter() + if self.jar() is None: + logger.error(f'[PRECHECK] No {JAR_NAME} (looked in {", ".join(map(os.path.normpath, DEFAULT_JARS))}); skipping the property pre-check') + self.status = "missing_jar" + return False + try: + proc = subprocess.run(self.command(), capture_output=True, text=True, timeout=CHECK_TIMEOUT_S) + result = json.loads(proc.stdout.strip().splitlines()[-1]) + field, reason_field = PROVES[self.prop] + self.proven = result.get(field) is True + self.reason = result.get(reason_field) + self.version = result.get("version") + self.status = "proven" if self.proven else "not_proven" + except subprocess.TimeoutExpired: + self.status = "timeout" + except Exception as e: + logger.error(f'[PRECHECK] Property pre-check failed: {type(e).__name__}: {e}') + self.status = "failed" + self.duration = time.perf_counter() - start + logger.info(f'[PRECHECK] {self.prop}: {self.status} after {self.duration:.2f}s ({self.reason}; cfg-extraction {self.version})') + return self.proven + + def stats(self) -> dict: + return { + 'enabled': self.enabled, + 'status': self.status, + 'proven': self.proven, + 'reason': self.reason, + 'duration_s': self.duration, + 'version': self.version, + } diff --git a/symbolic-explorer/svcomp/ResourceBudget.py b/symbolic-explorer/svcomp/ResourceBudget.py new file mode 100644 index 00000000..4eaee970 --- /dev/null +++ b/symbolic-explorer/svcomp/ResourceBudget.py @@ -0,0 +1,360 @@ +""" +The resource budget of an SV-COMP run: the memory and CPU time that the explorer and all executor +JVMs share. SV-COMP (bench-defs swat.xml) limits the whole run to 15 GB of memory and 15 min of CPU +time, summed over all processes, on 4 cores. + +A monitor thread polls the resident memory and CPU time of the explorer and its descendants. Close to +a limit it stops the work in progress itself, so that the run still ends with a verdict instead of +being killed from outside: +- memory: the executor run in progress is killed (it ends without a trace, so the path it was to + explore stays unexplored) and exploration goes on with the other branches; a Z3 query is + interrupted (the branch counts as undecided). Either rules out a SAFE verdict. +- CPU time: exploration stops and the run ends with UNKNOWN, unless a violation was found already. +""" +import ctypes +import os +import signal +import threading +import time +from contextlib import contextmanager + +import log + +logger = log.get_logger() + +MB = 1000 * 1000 +MIB = 1024 * 1024 + +# BenchExec counts in SI units: memlimit "15 GB" is 15e9 bytes. +DEFAULT_MEMORY_LIMIT_MB = 15000 +DEFAULT_CPU_LIMIT_S = 900 + +# Kept free below the memory limit: page cache and the files the run writes (which count against +# the limit in a BenchExec container), the wrapper scripts, and growth between two polls (the +# executor JVM was seen to grow by 2 GB/s). +HEADROOM_MB = 2000 +# If the explorer alone is above the limit minus this, nothing can be stopped to make room. +HARD_HEADROOM_MB = 1000 +# How long an interrupted Z3 query may stay over the memory threshold. Z3 does not react to an +# interrupt while it bit-blasts, during which it can allocate gigabytes in seconds. (Z3's own +# memory_max_size is no way out: Z3 then aborts the process in some places.) +INTERRUPT_GRACE_S = 1.0 +# The explorer's share (Python, the execution tree, Z3). The executor JVM gets half of the rest as +# heap; the other half is for its native memory, mostly JavaSMT's Z3 context, which grows with the +# trace about as fast as the heap. +EXPLORER_SHARE_MB = 1500 +MIN_HEAP_MB = 256 + +# Stop this much before the CPU limit: polling lag (up to 4 cores per poll interval), killing the +# executor and writing the verdict, the witness and the statistics. +CPU_MARGIN_S = 20 +POLL_S = 0.2 +# Finding all descendants means reading every process's parent, which is slow on a busy machine; +# in between, only the processes found last time and the executor are measured. +FULL_SCAN_S = 5 +# How long the main thread may take to wind down after the CPU deadline before the monitor ends +# the run itself. +GRACE_S = 10 + +CLK_TCK = os.sysconf('SC_CLK_TCK') +PAGE_SIZE = os.sysconf('SC_PAGE_SIZE') + + +def cgroup_memory_limit(proc_root: str = '/proc', cgroup_root: str = '/sys/fs/cgroup') -> int | None: + """The tightest memory limit (bytes) of this process's cgroup and its ancestors, or None. + + BenchExec (and a systemd scope with MemoryMax) enforce the limit through the cgroup, so it is + the limit that actually applies when it is lower than the configured one. + """ + return limiting_cgroup(proc_root, cgroup_root)[0] + + +def limiting_cgroup(proc_root: str = '/proc', cgroup_root: str = '/sys/fs/cgroup') -> tuple[int | None, str | None]: + """(limit, directory) of the cgroup with the tightest memory limit, see cgroup_memory_limit().""" + try: + lines = open(f'{proc_root}/self/cgroup').read().splitlines() + except OSError: + return None, None + limits = [] + for line in lines: + parts = line.split(':', 2) + if len(parts) != 3: + continue + _, controllers, path = parts + if controllers == '': # cgroup v2 + base, name = cgroup_root, 'memory.max' + elif 'memory' in controllers.split(','): # cgroup v1 + base, name = f'{cgroup_root}/memory', 'memory.limit_in_bytes' + else: + continue + path = path.rstrip('/') + while True: + try: + value = open(f'{base}{path}/{name}').read().strip() + if value.isdigit() and int(value) < 2**60: # v1 reports "no limit" as a huge number + limits.append((int(value), f'{base}{path}')) + except OSError: + pass + if not path: + break + path = path.rsplit('/', 1)[0] + return min(limits) if limits else (None, None) + + +def cgroup_cpu_time(directory: str | None) -> float | None: + """CPU seconds of all processes in a cgroup (v2 cpu.stat), or None.""" + if directory is None: + return None + try: + with open(f'{directory}/cpu.stat') as f: + for line in f: + if line.startswith('usage_usec '): + return int(line.split()[1]) / 1e6 + except (OSError, ValueError): + pass + return None + + +def _stat(pid: int, proc_root: str = '/proc') -> tuple[int, float, int] | None: + """(ppid, CPU seconds, RSS bytes) of a process, or None if it is gone.""" + try: + with open(f'{proc_root}/{pid}/stat') as f: + fields = f.read().rsplit(')', 1)[1].split() + except (OSError, IndexError): + return None + return int(fields[1]), (int(fields[11]) + int(fields[12])) / CLK_TCK, int(fields[21]) * PAGE_SIZE + + +def descendants(pid: int, proc_root: str = '/proc') -> dict[int, tuple[float, int]]: + """{pid: (CPU seconds, RSS bytes)} of the live descendants of a process.""" + stats = {} + for entry in os.listdir(proc_root): + if entry.isdigit(): + s = _stat(int(entry), proc_root) + if s is not None: + stats[int(entry)] = s + children: dict[int, list[int]] = {} + for p, (ppid, _, _) in stats.items(): + children.setdefault(ppid, []).append(p) + result, todo = {}, list(children.get(pid, [])) + while todo: + p = todo.pop() + result[p] = stats[p][1:] + todo.extend(children.get(p, [])) + return result + + +def _malloc_trim(): + """Returns freed heap memory to the system (glibc keeps it otherwise, e.g. after a Z3 query).""" + try: + ctypes.CDLL('libc.so.6').malloc_trim(0) + except (OSError, AttributeError): + pass + + +class ResourceBudget: + + def __init__(self, memory_limit_mb: int = DEFAULT_MEMORY_LIMIT_MB, cpu_limit_s: float = DEFAULT_CPU_LIMIT_S, + cpu_used_s: float = 0.0, cgroup_limit: int | None = None, wall_limit_s: float | None = None): + """cpu_used_s: CPU time the run used before the explorer started (e.g. compiling the task), + which counts against the same limit. cgroup_limit: the cgroup's memory limit; read from the + system unless given. wall_limit_s: the wall time the run may take (default: the CPU limit, + as under BenchExec and the harness); a target that hangs without using CPU (a deadlock) + would otherwise be killed before the explorer reports anything.""" + self.configured_memory_limit = memory_limit_mb * MB + if cgroup_limit is None: + cgroup_limit, self._cgroup_dir = limiting_cgroup() + else: + self._cgroup_dir = None + self.cgroup_memory_limit = cgroup_limit + self.memory_limit = min(self.configured_memory_limit, self.cgroup_memory_limit or self.configured_memory_limit) + self.memory_threshold = self.memory_limit - HEADROOM_MB * MB + self.hard_memory_threshold = self.memory_limit - HARD_HEADROOM_MB * MB + self.heap_mib = max(MIN_HEAP_MB * MB, (self.memory_threshold - EXPLORER_SHARE_MB * MB) // 2) // MIB + self.cpu_limit = cpu_limit_s + self.cpu_used_before = cpu_used_s + self.cpu_deadline = cpu_limit_s - CPU_MARGIN_S + self.wall_limit = wall_limit_s if wall_limit_s is not None else cpu_limit_s + self.wall_deadline = self.wall_limit - CPU_MARGIN_S + self._started_at = time.monotonic() + self.wall_deadline_reached = False + + self.cpu_exceeded = threading.Event() + self.cpu_exceeded_at: float | None = None + self.peak_rss = 0 + self.cpu_used = cpu_used_s + self.memory_hits = 0 # how often work was stopped to stay within the memory limit + self.solver_interruptions = 0 + + self._lock = threading.Lock() + self._executor = None # the running executor process (subprocess.Popen) + self._executor_stopped: str | None = None # why the monitor killed it: 'memory' or 'cpu' + self._solver_context = None # the Z3 context of a running query + self._solver_interrupted: str | None = None + self._solver_interrupted_at = 0.0 + self._tracked: set[int] = set() # descendants found by the last full scan + self._last_scan = 0.0 + self._thread: threading.Thread | None = None + self._stop = threading.Event() + # Called (from the monitor thread) when the run must end now: the explorer alone is over + # the memory limit, or the main thread did not wind down after the CPU deadline. + self.on_emergency = None + + def describe(self) -> str: + cg = f', cgroup limit {self.cgroup_memory_limit // MB} MB' if self.cgroup_memory_limit else '' + return (f'memory limit {self.memory_limit // MB} MB{cg} (executor heap {self.heap_mib} MiB, ' + f'stop at {self.memory_threshold // MB} MB), CPU limit {self.cpu_limit:.0f} s ' + f'(stop at {self.cpu_deadline:.0f} s, {self.cpu_used_before:.1f} s used before)') + + # ---- measuring ---- + + def usage(self) -> tuple[int, float]: + """(RSS bytes, CPU seconds) of the run so far: the explorer and all its descendants, plus the + CPU time used before the explorer started. Where the memory limit comes from a cgroup (as + under BenchExec), the CPU time is at least that of the cgroup, which also counts the wrapper + scripts that started the explorer and keep running next to it.""" + own = _stat(os.getpid()) + rss = own[2] if own else 0 + t = os.times() + cpu = self.cpu_used_before + t.user + t.system + t.children_user + t.children_system + if time.monotonic() - self._last_scan >= FULL_SCAN_S: + self._tracked = set(descendants(os.getpid())) + self._last_scan = time.monotonic() + proc = self._executor + for pid in self._tracked | ({proc.pid} if proc is not None else set()): + st = _stat(pid) + if st is not None: + cpu += st[1] + rss += st[2] + return rss, max(cpu, cgroup_cpu_time(self._cgroup_dir) or 0.0) + + # ---- the work the monitor may stop ---- + + @contextmanager + def executor(self, proc): + """Marks proc as the running executor. Yields a callable that tells, once it ended, whether + the monitor stopped it, and why ('memory' or 'cpu'), or None.""" + with self._lock: + self._executor, self._executor_stopped = proc, None + try: + yield lambda: self._executor_stopped + finally: + with self._lock: + self._executor = None + + @contextmanager + def solver(self, context): + """Marks a Z3 query on context as running. Yields a callable that tells, once it returned, + whether the monitor interrupted it, and why, or None.""" + with self._lock: + self._solver_context, self._solver_interrupted = context, None + try: + yield lambda: self._solver_interrupted + finally: + with self._lock: + self._solver_context = None + if self._solver_interrupted: + _malloc_trim() + + def _stop_work(self, reason: str) -> bool | None: + """Stops the executor run or the Z3 query in progress: True if it was running, None if it + is being stopped already, False if there is none.""" + with self._lock: + proc = self._executor + if proc is not None and proc.poll() is None: + if self._executor_stopped: + return None + self._executor_stopped = reason + # SIGKILL: on SIGTERM the executor's shutdown hook would send the trace cut short + # as if the target had ended there. + for pid in [*descendants(proc.pid), proc.pid]: + try: + os.kill(pid, signal.SIGKILL) + except OSError: + pass + return True + if self._solver_context is not None: + if self._solver_interrupted: + self._solver_context.interrupt() # for a check started since + return None + self._solver_interrupted = reason + self._solver_interrupted_at = time.monotonic() + self.solver_interruptions += 1 + self._solver_context.interrupt() + return True + return False + + # ---- the monitor ---- + + def start(self): + self._started_at = time.monotonic() + logger.info(f'[BUDGET] {self.describe()}') + self._thread = threading.Thread(target=self._monitor, name='resource-budget', daemon=True) + self._thread.start() + + def stop(self): + self._stop.set() + + def _monitor(self): + while not self._stop.wait(POLL_S): + try: + self.check() + except Exception as e: # the monitor must not die + logger.error(f'[BUDGET] Monitor error: {e}') + + def check(self): + """One poll: measure, and act if a limit is near.""" + rss, cpu = self.usage() + self.peak_rss = max(self.peak_rss, rss) + self.cpu_used = cpu + wall = time.monotonic() - self._started_at + self.cpu_used_before + if wall >= self.wall_deadline and not self.cpu_exceeded.is_set(): + # Ends the run like the CPU deadline (cpu_exceeded): UNKNOWN unless a verdict is in + logger.warning(f'[BUDGET] Wall time {wall:.1f} s reached the deadline of {self.wall_deadline:.0f} s ' + f'(CPU {cpu:.1f} s: something waits without working, e.g. a deadlocked target), stopping') + self.wall_deadline_reached = True + self.cpu_exceeded_at = time.monotonic() + self.cpu_exceeded.set() + if cpu >= self.cpu_deadline or self.wall_deadline_reached: + if not self.cpu_exceeded.is_set(): + logger.warning(f'[BUDGET] CPU time {cpu:.1f} s reached the deadline of {self.cpu_deadline:.0f} s, stopping') + self.cpu_exceeded_at = time.monotonic() + self.cpu_exceeded.set() + # Also stops what was started after the deadline, until the main thread has wound down + self._stop_work('cpu') + if time.monotonic() - self.cpu_exceeded_at > GRACE_S: + self._emergency(f'the explorer did not stop within {GRACE_S} s of the CPU deadline') + if rss >= self.memory_threshold: + stopped = self._stop_work('memory') + if stopped: + self.memory_hits += 1 + logger.warning(f'[BUDGET] Memory {rss // MB} MB reached {self.memory_threshold // MB} MB, ' + f'stopped the work in progress') + elif stopped is None and self._solver_context is not None: + # An interrupted query that keeps its memory, or keeps growing + if rss >= self.hard_memory_threshold or time.monotonic() - self._solver_interrupted_at > INTERRUPT_GRACE_S: + self._emergency(f'a Z3 query uses {rss // MB} MB of the {self.memory_limit // MB} MB limit and does not stop') + elif stopped is False: + _malloc_trim() + if self.usage()[0] >= self.hard_memory_threshold: + self._emergency(f'the explorer uses {rss // MB} MB of the {self.memory_limit // MB} MB limit') + + def _emergency(self, why: str): + logger.error(f'[BUDGET] Ending the run: {why}') + if self.on_emergency is not None: + self.on_emergency(why) + + def stats(self) -> dict: + return { + 'memory_limit_mb': self.memory_limit // MB, + 'cgroup_memory_limit_mb': self.cgroup_memory_limit // MB if self.cgroup_memory_limit else None, + 'executor_heap_mib': self.heap_mib, + 'peak_rss_mb': self.peak_rss // MB, + 'memory_hits': self.memory_hits, + 'solver_interruptions': self.solver_interruptions, + 'cpu_limit_s': self.cpu_limit, + 'cpu_deadline_s': self.cpu_deadline, + 'cpu_used_s': round(self.cpu_used, 1), + 'cpu_deadline_reached': self.cpu_exceeded.is_set(), + 'wall_deadline_reached': self.wall_deadline_reached, + } diff --git a/symbolic-explorer/svcomp/SVCompHandler.py b/symbolic-explorer/svcomp/SVCompHandler.py deleted file mode 100755 index 7922fb1f..00000000 --- a/symbolic-explorer/svcomp/SVCompHandler.py +++ /dev/null @@ -1,246 +0,0 @@ -import glob -import re -import signal -import subprocess -import os, sys -from contextlib import contextmanager - - -from solver.SolverHandler import SATResult - -from data.Database import Database - -from strategy.StrategyService import StrategyService - -from enum import Enum -from svcomp.SymbolicStorage import SymbolicStorage -import logging - -import log -logger = log.get_logger() -verdict_logger = log.get_verdict_logger() - - -class ExecutionStatus(Enum): - SUCCESS = 1 - ERROR = 2 - TIMEOUT = 3 - CRASH = 4 - VIOLATION = 5 - -class Verdict(Enum): - VIOLATION = "== ERROR" - SAFE = "== OK" - UNKNOWN = "== DONT-KNOW" - NO_SYMBOLIC_VARS = "== NON-SYMBOLIC" - -class Action(Enum): - RANDOMNEXT = 1 - SYMBOLICNEXT = 2 - REPORTVERDICT = 3 - -class INPUTTYPE(Enum): - RANDOM = 1 - SYMBOLIC = 2 - MAGIC = 3 - - - -class State: - def __init__(self): - self.verdict = Verdict.UNKNOWN - - -class SVCompHandler: - def __init__(self): - self.state = State() - self.symbolicStorage = SymbolicStorage() - self.shutdown_flag = False - - @contextmanager - def pushd(self, dirname): - """Context manager to temporarily change the working directory.""" - original_dir = os.getcwd() - os.chdir(dirname) - try: - yield - finally: - os.chdir(original_dir) - - def build_command(self, javaagent:str, config:str, Z3_DIR: str, mem:int=32) -> [str]: - """Builds the Java command list with given parameters.""" - cmd = [ - 'java', - f'-Xmx{mem}g', - f'-Dconfig.path={config}', - f'-javaagent:{javaagent}', - f"-Djava.library.path={Z3_DIR}", - '-cp', - f'build:{Z3_DIR}', - '-ea', - 'Main' - ] - if self.symbolicStorage.input_type == INPUTTYPE.SYMBOLIC: - - sym_vars = [] - for var in self.symbolicStorage.vars.values(): - val = var.newValue if var.newValue is not None else var.value - sym_vars.append(f'-Dswat.input.{var.dType.value}_{var.idx}={val}') - cmd[3:3] = sym_vars - return cmd - - - - def run_command_with_timeout(self, cmd: [str], timeout: int = 60) -> (ExecutionStatus, dict): - """Executes the given command and returns the status and message.""" - - logger.info(f'[SVCOMP] Java Output Begin') - try: - stdout = [] - with subprocess.Popen(cmd, stdout=subprocess.PIPE,stderr=subprocess.STDOUT, bufsize=1, universal_newlines=True) as proc: - for line in proc.stdout: - logger.info(f'[SWAT] --> {line.strip()}') - stdout.append(line) - logger.info(f'[SVCOMP] Java Output End') - for l in stdout: - if '*** java.lang.instrument ASSERTION FAILED ***' in l: - return ExecutionStatus.ERROR, stdout - if proc.returncode == 0: - return ExecutionStatus.SUCCESS, stdout - else: - for l in stdout: - if "java.lang.AssertionError" in l: - return ExecutionStatus.VIOLATION, stdout - return ExecutionStatus.ERROR, stdout - except subprocess.TimeoutExpired: - return ExecutionStatus.TIMEOUT, stdout - except Exception as e: - logger.critical(f'[SVCOMP] Exception: {e}') - return ExecutionStatus.CRASH, str(e) - - - - - def determine_next_step(self, status: ExecutionStatus, stdout: [str] ) -> Action: - """Determines the next step based on the execution status.""" - if status == ExecutionStatus.SUCCESS: - for line in stdout: - if re.match(r'.*\[DSE\] Initializing symbolic tracking for the following value .*', line): - return Action.SYMBOLICNEXT - - self.state.verdict = Verdict.NO_SYMBOLIC_VARS - - - return Action.REPORTVERDICT - - elif status == ExecutionStatus.VIOLATION: - - self.state.verdict = Verdict.VIOLATION - - return Action.REPORTVERDICT - - elif status == ExecutionStatus.TIMEOUT or status == ExecutionStatus.CRASH or status == ExecutionStatus.ERROR: - self.state.verdict = Verdict.UNKNOWN - return Action.REPORTVERDICT - - else: - self.state.verdict = Verdict.UNKNOWN - return Action.REPORTVERDICT - - def run_testcase(self, base_dir: str, classpath: [str], agentpath: str, configpath: str, z3path) -> Verdict: - """Runs the testcase using the constructed Java command.""" - db = Database.instance() - - next_step = Action.RANDOMNEXT - idx = 0 - while True: - - logger.info(f'[SVCOMP] ============================= ROUND ({idx}) =============================') - idx +=1 - command = self.build_command(javaagent=agentpath, config=configpath, Z3_DIR=z3path) - logger.info(f'[SVCOMP] Execution command: {command}') - - status, output = self.run_command_with_timeout(cmd=command, timeout=60) - logger.info(f'[STATUS] {status}') - next_step = self.determine_next_step(status, output) - - - if next_step == Action.REPORTVERDICT: - logger.info(f'[SVCOMP] Next step: REPORTVERDICT') - return self.state.verdict - - - elif next_step == Action.SYMBOLICNEXT: - logger.info(f'[SVCOMP] Next step: SYMBOLIC EXPLORATION') - - next_step = self.retrieve_solution() - if next_step == Action.REPORTVERDICT: - return self.state.verdict - elif next_step == Action.SYMBOLICNEXT: - self.symbolicStorage.input_type = INPUTTYPE.SYMBOLIC - - - - - def retrieve_solution(self): - possible_branches = StrategyService.select_branch(endpoint_id=0) - logger.info(f'[SYMBOLIC EXPLORATION] Found {len(possible_branches)} possible branches') - symbolic_vars = None - sat = None - branch_found = False - for branch in possible_branches: - if not StrategyService.is_symbolic_branch(branch): - continue - branch_found = True - sat, sol = StrategyService.solve_branch(branch) - - if sat == SATResult.SAT: - symbolic_vars = branch.inputs - break - - if not branch_found or sat == SATResult.UNSAT: - self.state.verdict = Verdict.SAFE - logger.info(f'[SYMBOLIC EXPLORATION] No symbolic branch found or UNSAT') - return Action.REPORTVERDICT - - if sat == SATResult.UNKNOWN: - logger.info(f'[SYMBOLIC EXPLORATION] SAT result is UNKNOWN') - self.state.verdict = Verdict.UNKNOWN - return Action.REPORTVERDICT - - sol_viz = [f'{key}: {val["plain_value"]}' for key, val in sol.items()] - logger.info(f'[SYMBOLIC EXPLORATION] Found new solution: {sol_viz}') - self.symbolicStorage.register_vars(symbolic_vars) - self.symbolicStorage.store_solution(sol) - return Action.SYMBOLICNEXT - - def check_missing_invocations(self): - for l in glob.glob("logs/invocation-*.log"): - with open(l, "r") as f: - if f.read().strip() != "": - return True - return False - - def run(self, basedir, classpath: [str], agentpath: str, configpath:str, z3dir:str): - Database.instance().add_endpoint(0) - logger.info(f'[SVCOMP] Beginning testcase analysis') - verdict = self.run_testcase(basedir, classpath, agentpath, configpath, z3dir) - - if (verdict == Verdict.SAFE) and self.check_missing_invocations(): - verdict = Verdict.UNKNOWN - - if verdict == Verdict.NO_SYMBOLIC_VARS: - verdict = Verdict.SAFE - - verdict_logger.info(f'[VERDICT ASSERT] {verdict.value}') - - - self.kill_current_process() - - - def kill_current_process(self): - pid = os.getpid() - os.kill(pid, signal.SIGTERM) # Send termination signal - # os.kill(pid, signal.SIGKILL) # Use this for a more forceful kill if needed - - diff --git a/symbolic-explorer/svcomp/StaticPreAnalysis.py b/symbolic-explorer/svcomp/StaticPreAnalysis.py new file mode 100644 index 00000000..e9a22086 --- /dev/null +++ b/symbolic-explorer/svcomp/StaticPreAnalysis.py @@ -0,0 +1,268 @@ +""" +Static pre-analysis (SA) for the SV-COMP driver, run in a background thread. + +The extractor is an external Java process. By default the driver waits for it before the first +round. With ``--parallel-sa`` the explorer already explores without pruning while it runs (and +while its graph is loaded), and the driver adopts the graph once it is ready. + +The graph is built in a private ``SAGraph`` and only handed out once it is fully loaded, so the +explorer never sees a half-loaded graph. ``cancel()`` kills the extractor and joins the thread, so +no Java process outlives the explorer when exploration finishes first. + +The graph names the target points of two properties: asserts (``new AssertionError`` sites) for +valid-assert, and the sites that may raise a RuntimeException for no-runtime-exception (found with +the property pre-check's value analysis, see cfg-extraction's RaiseSites). Under any other property +the pre-analysis is not run at all. A graph whose program may run application code outside main's +call tree (another thread, a shutdown hook, an uncaught-exception handler) is not used either, see +``SAGraph.async_entry_sites``. +""" + +import argparse +import os +import subprocess +import threading +import time +import traceback +from enum import Enum +from typing import Any + +from data.StaticAnalysisGraph.SAGraph import SAGraph + +import log +logger = log.get_logger() + +EXTRACTOR_TIMEOUT_S = 120 +# The extractor's fat jar in a cfg-extraction checkout (--sa-path); --sa-jar names the jar itself. +EXTRACTOR_JAR = os.path.join("build", "libs", "cfg-extractor-1.0-SNAPSHOT-all.jar") +# The class the SV-COMP executor runs (SVCompDriver.build_command): the graph's entry without --target. +SVCOMP_MAIN_CLASS = "Main" + +# The properties the graph has target points for, and the metadata field that lists them. +SA_TARGETS = { + "valid-assert.prp": "assertionPointIds", + "no-runtime-exception.prp": "runtimeExceptionPointIds", +} + + +class SAStatus(Enum): + DISABLED = "disabled" + NOT_STARTED = "not_started" + RUNNING = "running" + LOADED = "loaded" + FAILED = "failed" + TIMEOUT = "timeout" + CANCELLED = "cancelled" # exploration finished before the pre-analysis + REJECTED = "rejected" # loaded, but pruning by the graph would not be sound (see rejected_reason) + + +class StaticPreAnalysis: + def __init__(self, args: argparse.Namespace) -> None: + self.args = args + self.status = SAStatus.NOT_STARTED if self.enabled else SAStatus.DISABLED + self.duration: float | None = None # wall time from start until done or cancelled + self.rejected_reason: str | None = None + self._graph: SAGraph | None = None + self._start: float | None = None + self._thread: threading.Thread | None = None + self._proc: subprocess.Popen[bytes] | None = None + self._lock = threading.Lock() # orders starting the extractor against cancel() + self._cancelled = threading.Event() + self._done = threading.Event() + # Whether to compute which branches are irrelevant to the targets (see Relevance) + self.slicing = not getattr(args, "no_sa_slicing", False) + self.relevance_stats: dict[str, Any] | None = None # see RelevanceStats + + @property + def requested(self) -> bool: + return bool(self.args.sa_file or self.args.sa_path or getattr(self.args, "sa_jar", None)) + + @property + def extractor_jar(self) -> str: + """The extractor to run: --sa-jar, else the fat jar in the --sa-path checkout.""" + sa_jar = getattr(self.args, "sa_jar", None) + if sa_jar: + return str(sa_jar) + return os.path.join(self.args.sa_path, EXTRACTOR_JAR) + + @property + def enabled(self) -> bool: + return self.requested and self.property_supported + + @property + def property_supported(self) -> bool: + return self.targets is not None + + @property + def entry(self) -> tuple[str, str]: + """ + The class and method the graph starts at: main of the target class, or the method that + --sa-entry names (`pkg.Class#method`, e.g. an HTTP endpoint, where the executor starts + tracing). The extractor finds the method by name, so it must not be overloaded. + """ + sa_entry = getattr(self.args, "sa_entry", None) + if sa_entry: + entry_class, _, entry_method = sa_entry.partition("#") + return entry_class, entry_method + # The SV-COMP executor always runs Main, and the SV-COMP archive passes no --target. + return self.args.target or SVCOMP_MAIN_CLASS, "main" + + @property + def graph_file(self) -> str: + """Where the extractor writes the graph: named after the entry's simple class name and method.""" + entry_class, entry_method = self.entry + return os.path.join(self.args.logdir, f"{entry_class.rsplit('.', 1)[-1]}_{entry_method}_interprocedural.json") + + @property + def targets(self) -> str | None: + """The graph's metadata field with the target points of the property, or None.""" + return SA_TARGETS.get(os.path.basename(getattr(self.args, "property", None) or "")) + + def start(self) -> None: + if not self.enabled: + if self.requested: + logger.info(f'[EXPLORER] Static pre-analysis is disabled: its graph has no target points ' + f'for property {self.args.property} (only {", ".join(SA_TARGETS)}).') + else: + logger.info(f'[EXPLORER] Static pre-analysis is disabled.') + self._done.set() + return + self._start = time.perf_counter() + self.status = SAStatus.RUNNING + # Daemon, so a graph still being parsed can never keep the process alive. + self._thread = threading.Thread(target=self._run, name='static-pre-analysis', daemon=True) + self._thread.start() + + def is_done(self) -> bool: + return self._done.is_set() + + def wait(self) -> None: + self._done.wait() + + def take_graph(self) -> SAGraph | None: + """The loaded graph, once and only once; None while running, or if SA failed or is disabled.""" + if not self._done.is_set(): + return None + graph, self._graph = self._graph, None + return graph + + def cancel(self, join_timeout_s: float = 10.0) -> None: + """Stop the pre-analysis if it is still running: kill the extractor and join the thread.""" + if self._thread is None or self._done.is_set(): + return + logger.info(f'[EXPLORER] Cancelling static pre-analysis that is still running.') + self._cancelled.set() + with self._lock: + proc = self._proc + if proc is not None and proc.poll() is None: + proc.kill() + self._thread.join(join_timeout_s) + if self._thread.is_alive(): + # Only possible while parsing the graph in Python: no child process is left, and the + # daemon thread ends with the process. + logger.warning(f'[EXPLORER] Static pre-analysis thread did not stop within {join_timeout_s}s.') + self.status = SAStatus.CANCELLED + self.duration = self._elapsed() + + def _elapsed(self) -> float: + """The wall time since start().""" + assert self._start is not None, "not started" + return time.perf_counter() - self._start + + def _run(self) -> None: + targets = self.targets + assert targets is not None # (only started if enabled) + graph = SAGraph() + try: + if self.args.sa_file: + logger.info(f'[EXPLORER] Loading static pre-analysis graph from provided file...') + graph.load_json_graph(self.args.sa_file, targets, slicing=self.slicing) + logger.info(f'[EXPLORER] Loaded static pre-analysis graph from provided file.') + else: + logger.info(f'[EXPLORER] Running static pre-analysis...') + if not self._run_extractor(): + return + logger.info(f'[EXPLORER] Loading static pre-analysis graph...') + graph.load_json_graph(self.graph_file, targets, slicing=self.slicing) + logger.info(f'[EXPLORER] Loaded static pre-analysis graph.') + async_sites = graph.async_entry_sites() + if self._cancelled.is_set(): + self.status = SAStatus.CANCELLED + elif async_sites: + # An assert on another thread or after main's frame is gone is not on any path + # the walk follows, so no branch side could be pruned soundly. + self.rejected_reason = f'application code may run outside main\'s call tree: {", ".join(async_sites[:5])}' + logger.info(f'[EXPLORER] Not using the static pre-analysis graph, {self.rejected_reason}') + self.status = SAStatus.REJECTED + else: + self._graph = graph + self.status = SAStatus.LOADED + if graph.relevance_stats is not None: + self.relevance_stats = graph.relevance_stats.as_dict() + logger.info(f'[EXPLORER] Relevance: {self.relevance_stats}') + except Exception as e: + if self._cancelled.is_set(): + self.status = SAStatus.CANCELLED + else: + logger.error(f'[EXPLORER] Failed to get static pre-analysis information. Exception of type {type(e).__name__}: {e}') + logger.error(traceback.format_exc()) + self.status = SAStatus.FAILED + finally: + # Record on every path: a timed-out, failed or cancelled pre-analysis still costs wall time. + self.duration = self._elapsed() + logger.info(f'[EXPLORER] Static pre-analysis finished with status {self.status.value} after {self.duration:.2f}s.') + self._done.set() + + @staticmethod + def _is_swat_library(path: str) -> bool: + """ + A library SWAT itself puts on the target's classpath (Z3's Java bindings). It is not part of + the program, and the extractor would treat its classes as application code: their + toString() methods, which assert, would count as callbacks that may reach an assert. + """ + return os.path.basename(path) == "com.microsoft.z3.jar" + + def _run_extractor(self) -> bool: + """Runs the extractor; False if it was cancelled or timed out.""" + cmd = ["java", "-jar", self.extractor_jar, + ':'.join(os.path.abspath(p) for p in self.args.classpath if not self._is_swat_library(p)), + self.args.logdir, *self.entry, "inter"] \ + + (["--no-callback-targets"] if getattr(self.args, "sa_no_callback_targets", False) else []) + with self._lock: + if self._cancelled.is_set(): + self.status = SAStatus.CANCELLED + return False + # Same process group as the explorer, so the harness' killpg on a timeout reaches it too. + self._proc = subprocess.Popen(cmd) + try: + returncode = self._proc.wait(timeout=EXTRACTOR_TIMEOUT_S) + except subprocess.TimeoutExpired as e: + self._proc.kill() + self._proc.wait() + logger.error(f'[EXPLORER] Failed to get static pre-analysis information. TimeoutExpired: {e}') + self.status = SAStatus.TIMEOUT + return False + if self._cancelled.is_set(): + self.status = SAStatus.CANCELLED + return False + if returncode != 0: + raise subprocess.CalledProcessError(returncode, cmd) + return True + + def stats(self, adopted_at_round: int | None) -> dict[str, Any]: + return { + 'enabled': self.enabled, + 'requested': self.requested, + 'property_supported': self.property_supported, + 'mode': 'parallel' if getattr(self.args, "parallel_sa", False) else 'sequential', + 'callback_targets': not getattr(self.args, "sa_no_callback_targets", False), + 'slicing': self.slicing, + 'relevance': self.relevance_stats, + 'status': self.status.value, + 'failed': self.status in (SAStatus.FAILED, SAStatus.TIMEOUT), + 'timed_out': self.status == SAStatus.TIMEOUT, + 'cancelled': self.status == SAStatus.CANCELLED, + 'rejected_reason': self.rejected_reason, + 'duration_s': self.duration, + # Round whose branch selection first used the graph; None if it never was. + 'adopted_at_round': adopted_at_round, + } diff --git a/symbolic-explorer/svcomp/SymbolicStorage.py b/symbolic-explorer/svcomp/SymbolicStorage.py index cbb2f1a2..99671435 100755 --- a/symbolic-explorer/svcomp/SymbolicStorage.py +++ b/symbolic-explorer/svcomp/SymbolicStorage.py @@ -21,14 +21,18 @@ def __init__(self) -> None: self.input_type = None def register_vars(self, vars: Dict[str, Input]) -> None: + # Keyed by name: inputs of different types can share an index (I_0 and [I_0). for var in vars: v = SymbolicVar(identifier=var.name, value=var.value) - self.vars[v.idx] = v + self.vars[var.name] = v def store_solution(self, sol: dict): - for s in sol.values(): - self.vars[s['index']].newValue = s['encoded_value'] + for name, s in sol.items(): + # The model also names symbolic values that are no Verifier input, e.g. an array + # ([I_0) the executor made symbolic; those cannot be passed to the program. + if name in self.vars: + self.vars[name].newValue = s['encoded_value'] diff --git a/symbolic-explorer/svcomp/TestcaseStats.py b/symbolic-explorer/svcomp/TestcaseStats.py new file mode 100644 index 00000000..c34f8b82 --- /dev/null +++ b/symbolic-explorer/svcomp/TestcaseStats.py @@ -0,0 +1,103 @@ +""" +Consolidated per-testcase statistics for SV-COMP runs. + +The Symbolic Explorer owns the per-testcase output: after a testcase completes it writes a single +``stats.json`` into the log directory containing the verdict, the soundness-relevant flags, and the +methods that could not be modelled symbolically. This replaces the executor-written ``stats_*.json`` +files and lets the analysis rely on authoritative structured data instead of scraping logs. + +The missing-invocation list is the superset; entries with ``context_loss=True`` are the dangerous +subset that received symbolic arguments and caused symbolic context loss. This makes the +superset/subset relationship explicit (see issue #25), rather than inferring it from regexes. +""" + +import json +from pathlib import Path + +import log + +logger = log.get_logger() + + +def _signature(inv: dict) -> str: + """Render a missing invocation as a stable ``owner/name:desc`` signature.""" + return f"{inv['owner']}/{inv['name']}:{inv['desc']}" + + +def build_testcase_stats(verdict, category, tree, symbolic_exec_iterations: int, nr_solver_calls: int, static_analysis: dict, + solver_limit_hits: int = 0, property_precheck: dict | None = None, divergence_retries: int = 0, + resources: dict | None = None) -> dict: + """ + Assemble the consolidated per-testcase statistics dictionary. + + Args: + verdict: The final (post-downgrade) Verdict enum for the testcase. + category: The VerificationCategory enum for the property. + tree: The per-endpoint Tree holding accumulated metadata for the testcase. + static_analysis: The pre-analysis outcome, see StaticPreAnalysis.stats(). + property_precheck: The property pre-check outcome, see PropertyPreCheck.stats(). + resources: The resource budget and its use, see ResourceBudget.stats(). + + Returns: + A JSON-serializable dict describing the testcase outcome. + """ + # Order: context-loss culprits first, then by signature, for readable output. + missing = sorted( + tree.missing_invocations.values(), + key=lambda i: (not i['context_loss'], i['owner'], i['name'], i['desc']), + ) + context_loss_methods = sorted({_signature(i) for i in missing if i['context_loss']}) + + return { + 'property': category.value, + 'verdict': verdict.name, + 'verdict_raw': verdict.value, + 'soundness': { + 'symbolic_context_loss': tree.symbolic_context_loss, + 'symbolic_precision_loss': tree.symbolic_precision_loss, + 'reference_semantic_change': tree.reference_semantic_change, + 'uncaught_exceptions': tree.uncaught_exceptions, + 'stack_overflow_traces': len(tree.stack_overflow_traces), + }, + 'symbolic_vars': sorted(tree.symbolic_vars), + 'missing_invocations': missing, + 'context_loss_methods': context_loss_methods, + 'execution_errors': list(tree.execution_errors), + 'counts': { + 'missing_invocations': len(missing), + 'context_loss_invocations': sum(1 for i in missing if i['context_loss']), + 'execution_errors': len(tree.execution_errors), + }, + 'performance': { + 'symbolic_exec_iterations': symbolic_exec_iterations, + 'nr_solver_calls': nr_solver_calls, + # Queries that ran out of their resource limit (--solver-rlimit) and were retried later. + 'solver_limit_hits': solver_limit_hits, + # Runs that took another path than a solution based on an approximate model predicted. + 'expected_divergences': tree.expected_divergences, + # Branches re-solved because the run of their previous solution did not reach them. + 'divergence_retries': divergence_retries, + }, + 'static_analysis': static_analysis, + 'property_precheck': property_precheck, + 'resources': resources, + } + + +def write_testcase_stats(filepath: Path, verdict, category, tree, symbolic_exec_iterations: int, nr_solver_calls: int, static_analysis: dict, + solver_limit_hits: int = 0, property_precheck: dict | None = None, divergence_retries: int = 0, + resources: dict | None = None): + """Write the consolidated per-testcase statistics to ``filepath`` as JSON.""" + data = build_testcase_stats(verdict, category, tree, symbolic_exec_iterations, nr_solver_calls, static_analysis, solver_limit_hits, + property_precheck, divergence_retries, resources) + + filepath.parent.mkdir(parents=True, exist_ok=True) + with open(filepath, 'w') as f: + json.dump(data, f, indent=2) + + logger.info( + f"[STATS] Saved testcase stats to {filepath} " + f"({data['counts']['missing_invocations']} missing invocations, " + f"{data['counts']['context_loss_invocations']} context-loss, " + f"{data['counts']['execution_errors']} execution errors)" + ) diff --git a/symbolic-explorer/svcomp/WitnessWriter.py b/symbolic-explorer/svcomp/WitnessWriter.py new file mode 100644 index 00000000..dad58141 --- /dev/null +++ b/symbolic-explorer/svcomp/WitnessWriter.py @@ -0,0 +1,104 @@ +""" +Writes the SV-COMP violation witness of a testcase from the trace of the violating execution. + +The executor ships the witness data in the trace (``nondetReturns``: every value returned by a +``Verifier.nondet*()`` call with the location of the call; ``violation``: the throwable that escaped +the entry method with its stack frames). The Database keeps the data of the latest trace; when the +final verdict is VIOLATION the driver hands it to :func:`write_violation_witness`, which builds the +witness files with the ``witnesscreator`` package (``witness.yml`` in format 2.3 and the legacy +``witness.graphml``). + +Witness generation must never break or delay the verdict: everything here is caught and logged. +""" + +import time +from pathlib import Path +from typing import Dict, List, Optional + +import log +from timing.TimingManager import TimingManager + +logger = log.get_logger() + +# The witness formats written per violation; YAML (2.3) is what SV-COMP 2027 requires, GraphML is +# kept for validators that still read it (e.g. wit4java). +WITNESS_FORMATS = ("yaml", "graphml") +PRODUCER_NAME = "SWAT" + + +def write_violation_witness(witness_data: Optional[dict], input_files: List[str], property_file: str, + out_dir: str, producer_version: str, + base_dir: Optional[str] = None) -> Dict[str, Path]: + """ + Write the violation witness files for the trace data of the violating execution. + + Args: + witness_data: ``{'nondet_returns': [...], 'violation': {...} | None}`` as stored by the + Database for the latest trace, or None if no trace of the violating execution arrived. + input_files: The task's input files/directories exactly as given to the verifier. + property_file: Path to the property file of the task. + out_dir: Directory the witness files are written to (created if needed). + producer_version: The SWAT version recorded in the witness metadata. + base_dir: Directory relative input paths are resolved against (default: the working directory). + + Returns: + The written files per format (``{'yaml': Path, 'graphml': Path}``), empty if nothing was written. + Never raises. + """ + start = time.perf_counter() + try: + return _write(witness_data, input_files, property_file, out_dir, producer_version, base_dir) + except Exception: # noqa: BLE001 - the verdict must not depend on the witness + logger.exception('[WITNESS] Writing the violation witness failed') + return {} + finally: + TimingManager.instance().record_witness_generation_time(time.perf_counter() - start) + + +def _write(witness_data: Optional[dict], input_files: List[str], property_file: str, out_dir: str, + producer_version: str, base_dir: Optional[str]) -> Dict[str, Path]: + try: + from witnesscreator import Frame, Location, NondetReturn, Task, Violation, write_witnesses + except ImportError as e: + logger.error(f'[WITNESS] The witnesscreator package is not available, no witness is written: {e}') + return {} + + if witness_data is None: + logger.warning('[WITNESS] No trace of the violating execution arrived; the witness has no waypoints') + witness_data = {} + + values = [] + for nr in witness_data.get('nondet_returns') or []: + values.append(NondetReturn( + function=nr['function'], + type=nr['type'], + value=nr['value'], + location=Location(class_name=nr['className'], source_file=nr.get('sourceFile') or None, + line=nr['line'], call_index=nr.get('callIndex', 0), + method=nr.get('method')), + bits=nr.get('bits'), + )) + + violation = None + v = witness_data.get('violation') + if v is not None: + violation = Violation( + throwable=v['throwable'], + frames=[Frame(class_name=f['className'], source_file=f.get('sourceFile') or None, line=f['line']) + for f in v.get('frames') or []], + ) + else: + logger.warning('[WITNESS] No uncaught throwable recorded; the witness has no target waypoint') + + task = Task(input_files=list(input_files), property_file=property_file, + producer_name=PRODUCER_NAME, producer_version=producer_version, base_dir=base_dir) + + logger.info(f'[WITNESS] Writing witness ({len(values)} nondet values, ' + f'target: {violation.throwable if violation else "none"}) to {out_dir}') + paths = write_witnesses(task, values, violation, out_dir=out_dir, formats=WITNESS_FORMATS) + for fmt, path in paths.items(): + logger.info(f'[WITNESS] Written {fmt}: {path}') + missing = [fmt for fmt in WITNESS_FORMATS if fmt not in paths] + if missing: + logger.error(f'[WITNESS] Not written: {missing}') + return dict(paths) diff --git a/symbolic-explorer/tests/check_marking_on_graphs.py b/symbolic-explorer/tests/check_marking_on_graphs.py new file mode 100644 index 00000000..df034999 --- /dev/null +++ b/symbolic-explorer/tests/check_marking_on_graphs.py @@ -0,0 +1,134 @@ +""" +Cross-checks the context-sensitive marking against a brute-force oracle on real extractor output. + +For each graph, random walks from the entry (with the same call-stack semantics as the DFS walk) +sample (node, stack) states at branches. For both children of each sampled branch it compares + + * is_interesting(child, stack) -- the summaries, evaluated against the stack + * an explicit search over realizable paths from (child, stack), bounded in stack depth + * context-insensitive marking -- plain backward reachability over every edge + +and reports any child the oracle can reach an assert from but is_interesting() calls +uninteresting (a soundness bug), and how many children each marking prunes. + + python3 tests/check_marking_on_graphs.py GRAPH.json [GRAPH.json ...] +""" + +import os +import random +import sys + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) +sys.setrecursionlimit(100000) + +from data.StaticAnalysisGraph import SAGraph as sagraph +from data.StaticAnalysisGraph.SAGraph import SAGraph, push, pop, depth, unwind, is_pseudo, unwind_pseudo + +ORACLE_DEPTH = 6 +ORACLE_STATES = 200_000 + + +def oracle(node, stack): + """True / False if an assert is (not) reachable on a realizable path; None if inconclusive.""" + worklist = [(node, stack)] + seen = set() + truncated = False + while worklist: + n, st = worklist.pop() + key = (id(n), stack_key(st)) + if key in seen: + continue + seen.add(key) + if len(seen) > ORACLE_STATES: + return None + if n.isTarget or n.isIncomplete: + return True + succ = list(n.next_exceptional) + if n.next_branched is not None: + succ.append(n.next_branched) + if n.is_call_site(): + if depth(st) >= ORACLE_DEPTH + depth(stack): + truncated = True + else: + worklist.extend((callee, push(st, n)) for callee in n.callees) + elif n.next_fallthrough is not None: + succ.append(n.next_fallthrough) + worklist.extend((s, st) for s in succ) + if n.isExit and st is not None: + frame, below = pop(st) + worklist.append((frame.return_site, below)) + if n.exceptionChain is not None and st is not None: + if is_pseudo(n.exceptionChain): + worklist.extend(unwind_pseudo(n.exceptionChain[0], st)) + else: + resumed = unwind(n.exceptionChain, st) + if resumed is not None: + worklist.append(resumed) + return None if truncated else False + + +def stack_key(st): + ids = [] + while st is not None: + ids.append(id(st[0])) + st = st[1] + return tuple(ids) + + +def context_insensitive(graph): + marked = set() + work = [n for n in graph.nodes.values() if n.isTarget] + while work: + n = work.pop() + if id(n) in marked: + continue + marked.add(id(n)) + work.extend(n.prev) + return marked + + +def check(path, walks=30, steps=400, rng=random.Random(0)): + g = SAGraph() + g.load_json_graph(path) + ci = context_insensitive(g) + stats = dict(samples=0, cs_pruned=0, ci_pruned=0, unsound=0, oracle_none=0, cs_over=0) + seen = set() + for _ in range(walks): + state = g.entry_node.walk_till_branch(None) + for _ in range(steps): + if state is None: + break + branch, stack = state + for child in (branch.next_fallthrough, branch.next_branched): + key = (id(child), stack_key(stack)) + if key in seen: + continue + seen.add(key) + cs = sagraph.is_interesting(child, stack) + truth = oracle(child, stack) + stats['samples'] += 1 + stats['cs_pruned'] += not cs + stats['ci_pruned'] += id(child) not in ci + if truth is None: + stats['oracle_none'] += 1 + elif truth and not cs: + stats['unsound'] += 1 + print(f'UNSOUND {path}: {child.id} depth={depth(stack)}') + elif cs and not truth: + stats['cs_over'] += 1 + if not cs and id(child) in ci: + pass + elif cs and id(child) not in ci: + print(f'CS MORE PERMISSIVE THAN CI {path}: {child.id}') + nxt = rng.choice([branch.next_fallthrough, branch.next_branched]) + state = nxt.walk_till_branch(stack) + return stats + + +if __name__ == '__main__': + total = {} + for p in sys.argv[1:]: + s = check(p) + for k, v in s.items(): + total[k] = total.get(k, 0) + v + print(total) diff --git a/symbolic-explorer/tests/data/fp_uf_core.smt2 b/symbolic-explorer/tests/data/fp_uf_core.smt2 new file mode 100644 index 00000000..db508eaf --- /dev/null +++ b/symbolic-explorer/tests/data/fp_uf_core.smt2 @@ -0,0 +1,159 @@ +; benchmark generated from python API +(set-info :status unknown) +(declare-fun D_1 () (_ FloatingPoint 11 53)) +(declare-fun D_0 () (_ FloatingPoint 11 53)) +(declare-fun math_log_0 ((_ FloatingPoint 11 53)) (_ FloatingPoint 11 53)) +(declare-fun D_2 () (_ FloatingPoint 11 53)) +(declare-fun D_3 () (_ FloatingPoint 11 53)) +(declare-fun D_4 () (_ FloatingPoint 11 53)) +(declare-fun math_log_1 ((_ FloatingPoint 11 53)) (_ FloatingPoint 11 53)) +(assert + (let ((?x24 (fp (_ bv0 1) (_ bv1031 11) (_ bv3623533393471838 52)))) + (let ((?x218 (ite true (_ -oo 11 53) ?x24))) + (let ((?x8 (fp.mul roundNearestTiesToEven D_0 D_1))) + (let (($x12 (= ?x8 (_ -zero 11 53)))) + (let ((?x219 (ite $x12 (_ -oo 11 53) ?x218))) + (let ((?x16 (fp (_ bv1 1) (_ bv1022 11) (_ bv1739715140794863 52)))) + (let ((?x19 (fp (_ bv0 1) (_ bv1022 11) (_ bv0 52)))) + (let (($x20 (= ?x8 ?x19))) + (let ((?x220 (ite $x20 ?x16 ?x219))) + (let ((?x221 (ite true (_ NaN 11 53) ?x220))) + (let ((?x29 (fp (_ bv0 1) (_ bv1023 11) (_ bv0 52)))) + (let (($x30 (= ?x8 ?x29))) + (let ((?x364 (ite $x30 (_ +zero 11 53) ?x221))) + (let ((?x40 (fp (_ bv1 1) (_ bv1023 11) (_ bv0 52)))) + (let (($x41 (= ?x8 ?x40))) + (let ((?x35 (ite $x41 (_ NaN 11 53) ?x364))) + (let ((?x36 (fp (_ bv0 1) (_ bv1022 11) (_ bv1739715140794863 52)))) + (let ((?x384 (ite false ?x36 ?x35))) + (let ((?x306 (ite false (_ NaN 11 53) ?x384))) + (let ((?x45 (fp (_ bv0 1) (_ bv1023 11) (_ bv444110266499851 52)))) + (let ((?x47 (fp (_ bv0 1) (_ bv1024 11) (_ bv2251799813685248 52)))) + (let (($x48 (= ?x8 ?x47))) + (let ((?x39 (ite $x48 ?x45 ?x306))) + (let ((?x57 (fp (_ bv1 1) (_ bv1024 11) (_ bv2251799813685248 52)))) + (let (($x58 (= ?x8 ?x57))) + (let ((?x525 (ite $x58 (_ NaN 11 53) ?x39))) + (let ((?x53 (fp (_ bv0 1) (_ bv1023 11) (_ bv1739715140794863 52)))) + (let ((?x55 (fp (_ bv0 1) (_ bv1025 11) (_ bv0 52)))) + (let (($x56 (= ?x8 ?x55))) + (let ((?x195 (ite $x56 ?x53 ?x525))) + (let ((?x66 (fp (_ bv1 1) (_ bv1025 11) (_ bv0 52)))) + (let (($x67 (= ?x8 ?x66))) + (let ((?x43 (ite $x67 (_ NaN 11 53) ?x195))) + (let ((?x62 (fp (_ bv0 1) (_ bv1023 11) (_ bv2744664355343667 52)))) + (let ((?x184 (ite false ?x62 ?x43))) + (let ((?x75 (fp (_ bv1 1) (_ bv1025 11) (_ bv1125899906842624 52)))) + (let (($x76 (= ?x8 ?x75))) + (let ((?x323 (ite $x76 (_ NaN 11 53) ?x184))) + (let ((?x71 (fp (_ bv0 1) (_ bv1024 11) (_ bv681361056027926 52)))) + (let ((?x73 (fp (_ bv0 1) (_ bv1026 11) (_ bv1125899906842624 52)))) + (let (($x74 (= ?x8 ?x73))) + (let ((?x493 (ite $x74 ?x71 ?x323))) + (let ((?x84 (fp (_ bv1 1) (_ bv1026 11) (_ bv1125899906842624 52)))) + (let (($x85 (= ?x8 ?x84))) + (let ((?x159 (ite $x85 (_ NaN 11 53) ?x493))) + (let ((?x79 (fp (_ bv0 1) (_ bv1025 11) (_ bv681361056027926 52)))) + (let ((?x82 (fp (_ bv0 1) (_ bv1029 11) (_ bv2533274790395904 52)))) + (let (($x83 (= ?x8 ?x82))) + (let ((?x507 (ite $x83 ?x79 ?x159))) + (let ((?x428 (ite false (_ NaN 11 53) ?x507))) + (let ((?x89 (fp (_ bv0 1) (_ bv1025 11) (_ bv3273841397727136 52)))) + (let ((?x92 (fp (_ bv0 1) (_ bv1032 11) (_ bv4292493394837504 52)))) + (let (($x93 (= ?x8 ?x92))) + (let ((?x379 (ite $x93 ?x89 ?x428))) + (let ((?x99 (fp (_ bv1 1) (_ bv1024 11) (_ bv681361056027925 52)))) + (let ((?x470 (ite false ?x99 ?x379))) + (let ((?x102 (fp (_ bv1 1) (_ bv1019 11) (_ bv2702159776422298 52)))) + (let (($x103 (= ?x8 ?x102))) + (let ((?x77 (ite $x103 (_ NaN 11 53) ?x470))) + (let ((?x109 (fp (_ bv0 1) (_ bv1023 11) (_ bv651805459980733 52)))) + (let ((?x113 (fp (_ bv0 1) (_ bv1024 11) (_ bv2570638124657944 52)))) + (let (($x114 (= ?x8 ?x113))) + (let ((?x483 (ite $x114 ?x109 ?x77))) + (let ((?x111 (fp (_ bv1 1) (_ bv1024 11) (_ bv2570638124657944 52)))) + (let (($x112 (= ?x8 ?x111))) + (let ((?x406 (ite $x112 (_ NaN 11 53) ?x483))) + (let ((?x119 (fp (_ bv0 1) (_ bv1021 11) (_ bv3631391185703701 52)))) + (let ((?x122 (fp (_ bv0 1) (_ bv1023 11) (_ bv2570638124657944 52)))) + (let (($x123 (= ?x8 ?x122))) + (let ((?x137 (ite $x123 ?x119 ?x406))) + (let ((?x120 (fp (_ bv1 1) (_ bv1023 11) (_ bv2570638124657944 52)))) + (let (($x121 (= ?x8 ?x120))) + (let ((?x190 (ite $x121 (_ NaN 11 53) ?x137))) + (let ((?x128 (fp (_ bv0 1) (_ bv1018 11) (_ bv2142646564017727 52)))) + (let ((?x135 (fp (_ bv0 1) (_ bv1023 11) (_ bv212558873981797 52)))) + (let (($x136 (= ?x8 ?x135))) + (let ((?x233 (ite $x136 ?x128 ?x190))) + (let ((?x131 (fp (_ bv1 1) (_ bv1020 11) (_ bv4199677819142547 52)))) + (let ((?x132 (fp (_ bv0 1) (_ bv1022 11) (_ bv2570638124657944 52)))) + (let (($x133 (= ?x8 ?x132))) + (let ((?x186 (ite $x133 ?x131 ?x233))) + (let ((?x143 (fp (_ bv1 1) (_ bv1022 11) (_ bv2570638124657944 52)))) + (let (($x144 (= ?x8 ?x143))) + (let ((?x305 (ite $x144 (_ NaN 11 53) ?x186))) + (let ((?x140 (fp (_ bv1 1) (_ bv1022 11) (_ bv1324324753833099 52)))) + (let ((?x141 (fp (_ bv0 1) (_ bv1022 11) (_ bv212558873981797 52)))) + (let (($x142 (= ?x8 ?x141))) + (let ((?x97 (ite $x142 ?x140 ?x305))) + (let ((?x153 (fp (_ bv0 1) (_ bv1024 11) (_ bv1617426887497577 52)))) + (let (($x154 (= ?x8 ?x153))) + (let ((?x171 (ite $x154 ?x29 ?x97))) + (let ((?x148 (fp (_ bv0 1) (_ bv1023 11) (_ bv2477857969768400 52)))) + (let ((?x150 (fp (_ bv0 1) (_ bv1025 11) (_ bv802078686650834 52)))) + (let (($x151 (= ?x8 ?x150))) + (let ((?x368 (ite $x151 ?x148 ?x171))) + (let ((?x263 (ite false (_ NaN 11 53) ?x368))) + (let ((?x158 (fp (_ bv0 1) (_ bv1023 11) (_ bv3773462844063412 52)))) + (let ((?x376 (ite false ?x158 ?x263))) + (let ((?x168 (fp (_ bv1 1) (_ bv1025 11) (_ bv2570638124657944 52)))) + (let (($x169 (= ?x8 ?x168))) + (let ((?x189 (ite $x169 (_ NaN 11 53) ?x376))) + (let ((?x166 (fp (_ bv0 1) (_ bv1023 11) (_ bv2921580872992412 52)))) + (let (($x167 (= ?x8 ?x166))) + (let ((?x383 (ite $x167 ?x19 ?x189))) + (let ((?x31 (fp (_ bv1 1) (_ bv1022 11) (_ bv0 52)))) + (let ((?x176 (fp (_ bv0 1) (_ bv1022 11) (_ bv959542878770698 52)))) + (let (($x177 (= ?x8 ?x176))) + (let ((?x378 (ite $x177 ?x31 ?x383))) + (let ((?x367 (ite false ?x40 ?x378))) + (let ((?x38 (fp (_ bv0 1) (_ bv1024 11) (_ bv0 52)))) + (let ((?x432 (ite false ?x38 ?x367))) + (let ((?x49 (fp (_ bv1 1) (_ bv1024 11) (_ bv0 52)))) + (let ((?x181 (fp (_ bv0 1) (_ bv1020 11) (_ bv372367821865420 52)))) + (let (($x182 (= ?x8 ?x181))) + (let ((?x517 (ite $x182 ?x49 ?x432))) + (let ((?x193 (fp (_ bv0 1) (_ bv1027 11) (_ bv1149976410304774 52)))) + (let (($x194 (= ?x8 ?x193))) + (let ((?x205 (ite $x194 ?x47 ?x517))) + (let ((?x59 (ite false ?x57 ?x205))) + (let ((?x64 (fp (_ bv0 1) (_ bv1025 11) (_ bv1125899906842624 52)))) + (let ((?x203 (fp (_ bv0 1) (_ bv1030 11) (_ bv718224185373583 52)))) + (let (($x204 (= ?x8 ?x203))) + (let ((?x161 (ite $x204 ?x64 ?x59))) + (let ((?x199 (fp (_ bv0 1) (_ bv1015 11) (_ bv3264724364776310 52)))) + (let (($x200 (= ?x8 ?x199))) + (let ((?x242 (ite $x200 ?x75 ?x161))) + (let ((?x213 (fp (_ bv0 1) (_ bv1037 11) (_ bv1550989187679584 52)))) + (let (($x214 (= ?x8 ?x213))) + (let ((?x94 (ite $x214 ?x73 ?x242))) + (let ((?x209 (fp (_ bv0 1) (_ bv1008 11) (_ bv2196247454973083 52)))) + (let (($x210 (= ?x8 ?x209))) + (let ((?x243 (ite $x210 ?x84 ?x94))) + (let ((?x217 (math_log_0 ?x8))) + (= ?x217 ?x243))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) +(assert + (let ((?x75 (fp (_ bv1 1) (_ bv1025 11) (_ bv1125899906842624 52)))) +(let ((?x73 (fp (_ bv0 1) (_ bv1026 11) (_ bv1125899906842624 52)))) +(let ((?x213 (fp (_ bv0 1) (_ bv1037 11) (_ bv1550989187679584 52)))) +(let ((?x226 (fp.add roundNearestTiesToEven D_4 D_3))) +(let ((?x228 (fp.add roundNearestTiesToEven ?x226 D_2))) +(let (($x316 (= ?x228 ?x213))) +(let ((?x418 (ite $x316 ?x73 ?x75))) +(let ((?x84 (fp (_ bv1 1) (_ bv1026 11) (_ bv1125899906842624 52)))) +(let ((?x209 (fp (_ bv0 1) (_ bv1008 11) (_ bv2196247454973083 52)))) +(let (($x315 (= ?x228 ?x209))) +(let ((?x476 (ite $x315 ?x84 ?x418))) +(let ((?x319 (math_log_1 ?x228))) +(= ?x319 ?x476)))))))))))))) +(check-sat) diff --git a/symbolic-explorer/tests/test_database.py b/symbolic-explorer/tests/test_database.py new file mode 100644 index 00000000..863fb5bd --- /dev/null +++ b/symbolic-explorer/tests/test_database.py @@ -0,0 +1,53 @@ +""" +Unit tests for the Database lock and for traces that cannot be added: a failure must neither leave +the lock held (the driver would wait for it forever) nor go unnoticed. + +Run from symbolic-explorer/ with the harness venv: + ../targets/sv-comp/scripts/.venv/bin/python3 -m unittest tests.test_database -v +""" + +import os +import sys +import unittest +from unittest import mock + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) + +from data import Database as database +from data.BinaryExecutionTree.Tree import Tree +from data.trace.Branch import Branch + + +class DatabaseLockTest(unittest.TestCase): + + def test_failing_insert_releases_the_lock(self): + db = database.Database.instance() + with mock.patch.object(Tree, 'add', side_effect=RecursionError('boom')): + with self.assertRaises(RecursionError): + db.add_trace('lock-test', 't', [Branch(1, 't', True, '(c1)')], [], [], False, False, reference_semantic_change=True) + self.assertTrue(database.lock.acquire(timeout=1)) + database.lock.release() + # The flags were recorded before the insert failed. + self.assertTrue(db.get_tree('lock-test').reference_semantic_change) + + def test_ingest_failure_is_recorded(self): + from constraint.ConstraintService import ConstraintService + db = database.Database.instance() + with mock.patch.object(Tree, 'add', side_effect=RecursionError('boom')): + ConstraintService.add_constraints('ingest-test', 't', [], [], [], False, False) + kinds = [e['kind'] for e in db.get_tree('ingest-test').execution_errors] + self.assertEqual(kinds, ['trace_ingest_failed']) + + def test_ingest_failure_keeps_a_stack_overflow(self): + # The driver must still drop that execution's violation (SVCompDriver.round_has_stack_overflow). + from constraint.ConstraintService import ConstraintService + from parse.TraceParser import Parser + db = database.Database.instance() + with mock.patch.object(Parser, 'parse_trace', side_effect=RecursionError('boom')): + ConstraintService.add_constraints('so-ingest-test', 't-so', [], [], [], True, False, stack_overflow=True) + self.assertEqual(db.get_witness_data('so-ingest-test')['trace_id'], 't-so') + self.assertTrue(db.is_stack_overflow_trace('so-ingest-test', 't-so')) + + +if __name__ == '__main__': + unittest.main() diff --git a/symbolic-explorer/tests/test_divergence.py b/symbolic-explorer/tests/test_divergence.py new file mode 100644 index 00000000..b8b4db9c --- /dev/null +++ b/symbolic-explorer/tests/test_divergence.py @@ -0,0 +1,337 @@ +""" +Unit tests for the handling of divergences in SVCompDriver: a run that does not take the side a +solution was solved for gets the branch re-solved with the failed solutions excluded, up to +divergence_retries times. Only then is the branch given up on, which is an internal error (and rules +out SAFE) unless the solution relied on an approximate model. + +It imports the driver, which needs the explorer's dependencies (z3, fastapi, ...) that the system +Python lacks, so run it with the harness venv, from symbolic-explorer/: + ../targets/sv-comp/scripts/.venv/bin/python3 -m unittest tests.test_divergence -v +""" + +import copy +import os +import sys +import unittest +from types import SimpleNamespace +from unittest import mock + +import z3 + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) + +from driver import SVCompDriver as svcomp +from svcomp.ResourceBudget import ResourceBudget +from solver.SolverHandler import SATResult, Z3Handler +from strategy.StrategyService import StrategyService + + +def trace_branch(id, has_branched): + return SimpleNamespace(id=id, has_branched=has_branched) + + +def int_solution(ctx, **values): + """A solution as Z3Handler.extract_and_encode_values builds it, for 32-bit int inputs.""" + return {name: {'encoded_value': str(v), 'plain_value': z3.BitVecVal(v, 32, ctx=ctx), 'index': name.split('_')[1]} + for name, v in values.items()} + + +class DivergenceTestBase(unittest.TestCase): + + def setUp(self): + self.tree = SimpleNamespace(expected_divergences=0, execution_errors=[]) + self.dropped, self.retired = [], set() + self.approximate = False + def record_expected_divergence(endpoint_id): + self.tree.expected_divergences += 1 + def record_execution_error(endpoint_id, kind, message): + self.tree.execution_errors.append({'kind': kind, 'message': message}) + db = SimpleNamespace(record_expected_divergence=record_expected_divergence, + record_execution_error=record_execution_error, + drop_solution=self.dropped.append, + retire_branch=self.retired.add, + is_approximate_trace=lambda endpoint_id, trace_id: self.approximate) + patcher = mock.patch.object(svcomp.Database, 'instance', return_value=db) + patcher.start() + self.addCleanup(patcher.stop) + self.driver = svcomp.SVCompDriver.__new__(svcomp.SVCompDriver) + self.driver.args = SimpleNamespace(solver_rlimit=0) + self.driver.state = svcomp.State() + self.driver.divergence_retries = 2 + self.driver.failed_solutions = {} + self.driver.query_cost = {} + self.driver.given_up = [] + self.driver.divergence_retry_count = 0 + self.driver.nr_solver_calls = 0 + self.driver.solver_limit_hits = 0 + self.driver.rlimit_level = {} + self.driver.budget = ResourceBudget(cgroup_limit=0) + self.ctx = z3.Context() + # Solved to take the branched side, which the tree has not explored yet. + self.target = SimpleNamespace(id=7, gid=70, trace_id='t', branched=None, skipped=object(), parent=None, choice=False, + sliced_query=False, ident=None) + self.aim(self.target, {'I_0': 1}) + + def aim(self, target, values): + self.driver.state.branch_to_explore = target + self.driver.state.solution = int_solution(self.ctx, **values) + self.driver.state.solution_is_guess = self.approximate + + def check(self, trace): + with self.assertLogs(svcomp.logger, level='INFO') as logs: + self.driver.check_divergence(trace) + return '\n'.join(logs.output) + + +class CheckDivergenceTest(DivergenceTestBase): + + def test_taking_the_predicted_side_is_silent(self): + with self.assertNoLogs(svcomp.logger, level='INFO'): + self.driver.check_divergence([trace_branch(7, True)]) + self.assertEqual(self.tree.expected_divergences, 0) + self.assertEqual(self.driver.failed_solutions, {}) + + def test_repeated_id_is_matched_by_position(self): + # The target is at depth 2; the same id also occurs at depth 0 (e.g. a switch whose case + # numbers wrap), taken the other way. Only the element at the target's depth counts. + root = SimpleNamespace(parent=None) + middle = SimpleNamespace(parent=root) + self.target.parent = middle + with self.assertNoLogs(svcomp.logger, level='INFO'): + self.driver.check_divergence([trace_branch(7, False), trace_branch(3, True), trace_branch(7, True)]) + self.assertEqual(self.tree.execution_errors, []) + + def test_unmodelled_exception_marker_does_not_count_as_a_level(self): + # The tree has no level for the marker (Tree.structural), so neither does the trace. + self.target.parent = SimpleNamespace(parent=None) + marker = SimpleNamespace(id=5, has_branched=True, inst='UNMODELLED_EXCEPTION') + with self.assertNoLogs(svcomp.logger, level='INFO'): + self.driver.check_divergence([trace_branch(3, True), marker, trace_branch(7, True)]) + self.assertEqual(self.tree.execution_errors, []) + self.assertEqual(self.driver.failed_solutions, {}) + + def test_given_up_side_reached_later_is_seen_on_the_live_node(self): + # The driver keeps a shallow copy of the target (the side open when it was solved); a later + # run that reaches that side extends the tree's own node, not the copy. + live = self.target + self.target = copy.copy(live) + self.target.live = live + self.aim(self.target, {'I_0': 1}) + self.driver.divergence_retries = 0 + self.check([trace_branch(7, False)]) + self.assertEqual(self.driver.unreached_given_up_branches(), [live]) + live.branched = object() + self.assertEqual(self.driver.unreached_given_up_branches(), []) + + def test_divergence_is_retried_first(self): + out = self.check([trace_branch(7, False)]) + self.assertNotIn('SWAT Assertion failed', out) + self.assertIn('Re-solving', out) + # The branch becomes a candidate again, with the failed solution remembered. + self.assertEqual(self.dropped, [70]) + self.assertEqual(len(self.driver.failed_solutions[70]), 1) + self.assertEqual(self.tree.execution_errors, []) + self.assertEqual(self.retired, set()) + self.assertEqual(self.driver.divergence_retry_count, 1) + + def test_exact_solutions_that_keep_diverging_are_an_error(self): + for i in range(2): + self.aim(self.target, {'I_0': i}) + self.check([trace_branch(8, True)]) + self.assertEqual(self.tree.execution_errors, []) + self.aim(self.target, {'I_0': 2}) + out = self.check([trace_branch(7, False)]) + self.assertIn('ERROR', out) + self.assertIn('SWAT Assertion failed', out) + self.assertEqual(self.tree.expected_divergences, 0) + # Recorded, so that the run cannot end SAFE, and out of the search for good + self.assertEqual([e['kind'] for e in self.tree.execution_errors], ['unexpected_divergence']) + self.assertEqual(self.retired, {70}) + self.assertEqual(self.driver.unreached_given_up_branches(), [self.target]) + self.assertEqual(len(self.driver.failed_solutions[70]), 3) + + def test_no_retries(self): + self.driver.divergence_retries = 0 + out = self.check([trace_branch(7, False)]) + self.assertIn('SWAT Assertion failed', out) + self.assertEqual(self.dropped, []) + self.assertEqual(self.retired, {70}) + + def test_divergence_of_a_guess_is_expected(self): + self.approximate = True + self.driver.divergence_retries = 0 + self.aim(self.target, {'I_0': 1}) + out = self.check([trace_branch(7, False)]) + self.assertNotIn('SWAT Assertion failed', out) + self.assertNotIn('ERROR', out) + self.assertEqual(self.tree.expected_divergences, 1) + self.assertEqual(self.tree.execution_errors, []) + # Given up on, but no SAFE downgrade of its own: the approximate model already rules SAFE out. + self.assertEqual(self.retired, {70}) + self.assertEqual(self.driver.given_up, []) + + def test_guess_is_retried_too(self): + self.approximate = True + self.aim(self.target, {'I_0': 1}) + out = self.check([trace_branch(8, True)]) + self.assertNotIn('SWAT Assertion failed', out) + self.assertEqual(self.tree.expected_divergences, 1) + self.assertEqual(self.dropped, [70]) + + def test_side_reached_later_lifts_the_downgrade(self): + self.driver.divergence_retries = 0 + self.check([trace_branch(7, False)]) + self.assertEqual(self.driver.unreached_given_up_branches(), [self.target]) + # Another run reaches the branched side after all: what lies behind it is in the tree now. + self.target.branched = object() + self.assertEqual(self.driver.unreached_given_up_branches(), []) + + def test_skipped_side(self): + target = SimpleNamespace(id=9, gid=90, trace_id='t', branched=object(), skipped=None, parent=None, + sliced_query=False, ident=None) + self.driver.divergence_retries = 0 + self.aim(target, {'I_0': 1}) + self.check([trace_branch(9, True)]) + self.assertEqual(self.driver.unreached_given_up_branches(), [target]) + target.skipped = object() + self.assertEqual(self.driver.unreached_given_up_branches(), []) + + +class SolveRetryTest(DivergenceTestBase): + """solve_retry tries the bounds in RETRY_BOUNDS, then no bound; only UNSAT without a bound + gives the branch up.""" + + def setUp(self): + super().setUp() + self.driver.failed_solutions[70] = [int_solution(self.ctx, I_0=2**31 - 1)] + self.calls = [] + + def solver(self, results): + def solve(b, solver_timeout_ms=None, solver_rlimit=None, extra_exprs=None, approximate_rlimit=None): + self.calls.append((b.gid, solver_rlimit, len(extra_exprs or []))) + result = results[len(self.calls) - 1] + return result, ({'I_0': 3} if result == SATResult.SAT else {}) + return mock.patch.object(svcomp.StrategyService, 'solve_branch', side_effect=solve) + + def test_smallest_bound_first(self): + with self.solver([SATResult.SAT]): + sat, sol = self.driver.solve_retry(self.target, None) + self.assertEqual(sat, SATResult.SAT) + # The exclusion plus the bound on I_0; a bounded query gets at most BOUNDED_RLIMIT + self.assertEqual(self.calls, [(70, svcomp.BOUNDED_RLIMIT, 2)]) + self.assertEqual(self.retired, set()) + + def test_bounded_failures_move_on(self): + results = [SATResult.UNSAT, SATResult.LIMIT, SATResult.UNKNOWN, SATResult.SAT] + with self.solver(results): + sat, _ = self.driver.solve_retry(self.target, 1000) + self.assertEqual(sat, SATResult.SAT) + self.assertEqual(self.calls, [(70, 1000, 2)] * 3 + [(70, 1000, 1)]) + self.assertEqual(self.driver.nr_solver_calls, 4) + self.assertEqual(self.retired, set()) + + def test_no_other_solution_gives_up(self): + with self.solver([SATResult.UNSAT] * 4): + with self.assertLogs(svcomp.logger, level='INFO') as logs: + sat, _ = self.driver.solve_retry(self.target, None) + self.assertEqual(sat, SATResult.UNSAT) + self.assertIn('SWAT Assertion failed', '\n'.join(logs.output)) + self.assertEqual(self.retired, {70}) + self.assertEqual([e['kind'] for e in self.tree.execution_errors], ['unexpected_divergence']) + + def test_unbounded_limit_is_passed_on(self): + # The caller escalates the resource limit; the branch is not given up on. + with self.solver([SATResult.UNSAT] * 3 + [SATResult.LIMIT]): + sat, _ = self.driver.solve_retry(self.target, 1000) + self.assertEqual(sat, SATResult.LIMIT) + self.assertEqual(self.retired, set()) + + def test_solve_candidates_routes_retries(self): + other = SimpleNamespace(id=8, gid=80, trace_id='t', choice=False) + with self.solver([SATResult.UNSAT] * 4 + [SATResult.SAT]): + found, _, gave_up = self.driver.solve_candidates([self.target, other]) + self.assertIs(found, other) + self.assertFalse(gave_up) + self.assertEqual(self.retired, {70}) + + +class RetryConstraintsTest(unittest.TestCase): + """Z3Handler.retry_constraints on a real solver.""" + + def setUp(self): + self.ctx = z3.Context() + self.x = z3.BitVec('I_0', 32, ctx=self.ctx) + self.y = z3.BitVec('I_1', 32, ctx=self.ctx) + + def model(self, *exprs): + s = z3.Solver(ctx=self.ctx) + s.add(*exprs) + if s.check() != z3.sat: + return None + m = s.model() + return {str(d.name()): m[d].as_signed_long() for d in m.decls()} + + def test_failed_solutions_are_excluded(self): + failed = [int_solution(self.ctx, I_0=5), int_solution(self.ctx, I_0=6)] + extra = Z3Handler.retry_constraints(failed, ctx=self.ctx) + self.assertEqual(self.model(self.x >= 5, self.x <= 7, *extra), {'I_0': 7}) + self.assertIsNone(self.model(self.x >= 5, self.x <= 6, *extra)) + + def test_exclusion_is_by_the_whole_solution(self): + # Excluding (I_0=1, I_1=2) still allows I_0=1 with another I_1. + extra = Z3Handler.retry_constraints([int_solution(self.ctx, I_0=1, I_1=2)], ctx=self.ctx) + m = self.model(self.x == 1, self.y >= 2, self.y <= 3, *extra) + self.assertEqual(m, {'I_0': 1, 'I_1': 3}) + + def test_bound_gives_small_values(self): + failed = [int_solution(self.ctx, I_0=2**31 - 1)] + extra = Z3Handler.retry_constraints(failed, 16, ctx=self.ctx) + m = self.model(self.x > 1, *extra) + self.assertLessEqual(m['I_0'], 16) + self.assertGreater(m['I_0'], 1) + # Bounds are signed: negative values are bounded below as well. + m = self.model(self.x < 0, *extra) + self.assertGreaterEqual(m['I_0'], -16) + self.assertIsNone(self.model(self.x > 16, *extra)) + + def test_bound_beyond_the_type_is_left_out(self): + b = z3.BitVec('B_0', 8, ctx=self.ctx) + failed = [{'B_0': {'encoded_value': '5', 'plain_value': z3.BitVecVal(5, 8, ctx=self.ctx), 'index': '0'}}] + self.assertEqual(len(Z3Handler.retry_constraints(failed, 256, ctx=self.ctx)), 1) + self.assertEqual(len(Z3Handler.retry_constraints(failed, 16, ctx=self.ctx)), 2) + + def test_arrays_and_empty_solutions(self): + # Array entries carry no Z3 value; with nothing to tell models apart, all are excluded. + failed = [{'[I_0': {'encoded_value': '[1]', 'plain_value': 'K(Int, 1)', 'index': '0'}}] + extra = Z3Handler.retry_constraints(failed, ctx=self.ctx) + self.assertIsNone(self.model(*extra)) + + def test_strings_and_doubles_are_excluded_exactly(self): + s = z3.String('java/lang/String_0', ctx=self.ctx) + d = z3.FP('D_1', z3.Float64(self.ctx)) + failed = [{'java/lang/String_0': {'encoded_value': 'a', 'plain_value': z3.StringVal('a', ctx=self.ctx), 'index': '0'}, + 'D_1': {'encoded_value': '0', 'plain_value': z3.FPVal(-0.0, z3.Float64(self.ctx)), 'index': '1'}}] + extra = Z3Handler.retry_constraints(failed, 16, ctx=self.ctx) + sol = z3.Solver(ctx=self.ctx) + sol.add(s == z3.StringVal('a', ctx=self.ctx), z3.Or(d == z3.FPVal(0.0, z3.Float64(self.ctx)), d == z3.FPVal(-0.0, z3.Float64(self.ctx))), *extra) + self.assertEqual(sol.check(), z3.sat) + # Only the other zero is left: the exclusion is by bit pattern, not by FP equality. + self.assertEqual(sol.model().eval(d).as_string(), z3.FPVal(0.0, z3.Float64(self.ctx)).as_string()) + + +class SolveBranchTest(unittest.TestCase): + """UNSAT under retry constraints does not make the branch infeasible.""" + + def test_unsat_with_extra_constraints_is_not_recorded(self): + db = mock.Mock() + branch = SimpleNamespace(gid=1, id=1, trace_id='t', parent=None, inputs=[], ufs=[], constraint={}) + with mock.patch('strategy.StrategyService.Database.instance', return_value=db), \ + mock.patch('strategy.StrategyService.Z3Handler.solve', return_value=(SATResult.UNSAT, {})): + StrategyService.solve_branch(branch, extra_exprs=['x']) + db.add_unsat_branch.assert_not_called() + StrategyService.solve_branch(branch) + db.add_unsat_branch.assert_called_once_with(1) + + +if __name__ == '__main__': + unittest.main() diff --git a/symbolic-explorer/tests/test_driver_robustness.py b/symbolic-explorer/tests/test_driver_robustness.py new file mode 100644 index 00000000..0ca281d4 --- /dev/null +++ b/symbolic-explorer/tests/test_driver_robustness.py @@ -0,0 +1,170 @@ +""" +Unit tests for how SVCompDriver ends a run whose search is incomplete or that failed: a crash ends +the run at once with UNKNOWN, never SAFE; a tree that does not cover every run, a trace that could +not be added and a run without a trace rule out SAFE. + +Needs the explorer's dependencies, so run it with the harness venv, from symbolic-explorer/: + ../targets/sv-comp/scripts/.venv/bin/python3 -m unittest tests.test_driver_robustness -v +""" + +import os +import sys +import tempfile +import unittest +from types import SimpleNamespace +from unittest import mock + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) + +from data.BinaryExecutionTree.Tree import Tree +from driver import SVCompDriver as svcomp +from svcomp.ResourceBudget import ResourceBudget + + +def not_proven(): + return SimpleNamespace(proven=False, reason=None, run=lambda: None, stats=lambda: {}) + + +class FakeDatabase: + def __init__(self): + self.tree = Tree(0) + self.traces = [] + + def get_tree(self, endpoint_id): + return self.tree + + def add_endpoint(self, endpoint_id): + pass + + def record_execution_error(self, endpoint_id, kind, message): + self.tree.record_execution_error(kind, message) + + def trace_count(self): + return len(self.traces) + + def get_trace(self, idx): + return self.traces[idx] + + def clear_witness_data(self, endpoint_id): + pass + + def get_execution_errors(self, endpoint_id, kind): + return [e for e in self.tree.execution_errors if e.get('kind') == kind] + + def count_symbolic_vars(self, endpoint_id): + return len(self.tree.symbolic_vars) + + def get_witness_data(self, endpoint_id): + return None + + def is_stack_overflow_trace(self, endpoint_id, trace_id): + return False + + +class DriverTest(unittest.TestCase): + + def setUp(self): + self.db = FakeDatabase() + patcher = mock.patch.object(svcomp.Database, 'instance', return_value=self.db) + patcher.start() + self.addCleanup(patcher.stop) + self.logdir = tempfile.mkdtemp() + self.driver = svcomp.SVCompDriver.__new__(svcomp.SVCompDriver) + self.driver.args = SimpleNamespace(java_path='java', agent='a', config='c', z3dir='z', port=0, + classpath=[], parallel_sa=False, logdir=self.logdir) + self.driver.state = svcomp.State() + self.driver.verification_category = svcomp.VerificationCategory.VALID_ASSERT_PRP + self.driver.sa = SimpleNamespace(cancel=lambda: None, duration=None, stats=lambda r: {}, slicing=False) + self.driver.sa_adopted_round = None + self.driver.round_idx = 0 + self.driver.nr_solver_calls = 0 + self.driver.solver_limit_hits = 0 + self.driver.verdict_reported = False + self.driver.kill_current_process = lambda: None + self.driver.precheck = not_proven() + self.driver.budget = ResourceBudget(cgroup_limit=0) + self.driver.verdict_lock = svcomp.threading.Lock() + self.driver.given_up = [] + self.driver.divergence_retry_count = 0 + + def run_with(self, run_testcase): + self.driver.run_testcase = run_testcase + with self.assertLogs(svcomp.verdict_logger, level='INFO') as logs: + self.driver.run() + verdicts = [l for l in logs.output if '[VERDICT' in l] + self.assertEqual(len(verdicts), 1) + self.assertTrue(self.driver.verdict_reported) + return verdicts[0] + + def test_crash_reports_unknown(self): + def crash(**kwargs): + raise RecursionError('maximum recursion depth exceeded') + verdict = self.run_with(crash) + self.assertIn('== DONT-KNOW', verdict) + self.assertEqual([e['kind'] for e in self.db.tree.execution_errors], ['explorer_crash']) + + def test_safe_stays_safe_on_a_complete_search(self): + self.assertIn('== OK', self.run_with(lambda **kwargs: svcomp.Verdict.SAFE)) + + def test_incomplete_search_rules_out_safe(self): + for kind in svcomp.INCOMPLETE_SEARCH_ERRORS: + with self.subTest(kind=kind): + self.db.tree = Tree(0) + self.driver.verdict_reported = False # one verdict per run + self.db.tree.record_execution_error(kind, 'x') + self.assertIn('== DONT-KNOW', self.run_with(lambda **kwargs: svcomp.Verdict.SAFE)) + + def test_incomplete_search_rules_out_a_proven_safe(self): + self.driver.precheck = SimpleNamespace(proven=True, reason='no throw', run=lambda: None, stats=lambda: {}) + self.db.tree.record_execution_error('trace_ingest_failed', 'x') + self.assertIn('== DONT-KNOW', self.run_with(lambda **kwargs: svcomp.Verdict.SAFE)) + + def test_incomplete_search_keeps_a_violation(self): + self.db.tree.record_execution_error('tree_mismatch', 'x') + self.driver.write_witness = lambda: None + self.assertIn('== ERROR', self.run_with(lambda **kwargs: svcomp.Verdict.VIOLATION)) + + def test_ingest_failure_ends_the_search(self): + self.db.tree.symbolic_vars.add('x') + self.db.tree.record_execution_error('trace_ingest_failed', 'trace t: RecursionError') + with self.assertLogs(svcomp.logger, level='INFO'): + action = self.driver.determine_next_step(svcomp.ExecutionStatus.SUCCESS, []) + self.assertEqual(action, svcomp.Action.REPORTVERDICT) + self.assertEqual(self.driver.state.verdict, svcomp.Verdict.UNKNOWN) + + def test_violation_in_the_output_still_counts_after_an_ingest_failure(self): + self.db.tree.record_execution_error('trace_ingest_failed', 'trace t: RecursionError') + with self.assertLogs(svcomp.logger, level='INFO'): + action = self.driver.determine_next_step(svcomp.ExecutionStatus.SUCCESS, + ['Exception in thread "main" java.lang.AssertionError']) + self.assertEqual(action, svcomp.Action.REPORTVERDICT) + self.assertEqual(self.driver.state.verdict, svcomp.Verdict.VIOLATION) + + +class NoTraceTest(unittest.TestCase): + """A run that sends no trace is not checked against the previous run's trace.""" + + def test_run_without_trace_is_recorded_not_checked(self): + db = FakeDatabase() + db.traces.append([SimpleNamespace(id=7, has_branched=False)]) + driver = svcomp.SVCompDriver.__new__(svcomp.SVCompDriver) + driver.args = SimpleNamespace(parallel_sa=True) + driver.state = svcomp.State() + driver.state.branch_to_explore = SimpleNamespace(id=7, branched=None, parent=None) + driver.sa = SimpleNamespace(start=lambda: None) + driver.precheck = not_proven() + driver.symbolicStorage = SimpleNamespace(input_type=None) + driver.build_command = lambda **kwargs: [] + driver.run_command_with_timeout = lambda cmd: (svcomp.ExecutionStatus.ERROR, []) + driver.check_divergence = mock.Mock() + with mock.patch.object(svcomp.Database, 'instance', return_value=db), \ + mock.patch.object(svcomp.TimingManager, 'instance'), \ + self.assertLogs(svcomp.logger, level='INFO'): + verdict = driver.run_testcase(java_path='java', agentpath='a', configpath='c', z3path='z', port=0, cp=[]) + self.assertEqual(verdict, svcomp.Verdict.UNKNOWN) + driver.check_divergence.assert_not_called() + # A failed run (ERROR) ends UNKNOWN by itself; no_trace is for a run that succeeded without a trace + + +if __name__ == '__main__': + unittest.main() diff --git a/symbolic-explorer/tests/test_failed_start_e2e.py b/symbolic-explorer/tests/test_failed_start_e2e.py new file mode 100644 index 00000000..9675e92f --- /dev/null +++ b/symbolic-explorer/tests/test_failed_start_e2e.py @@ -0,0 +1,145 @@ +""" +End-to-end reproducer: an SV-COMP run whose target JVM fails before main (a VerifyError from +inconsistently compiled classes, or a missing main class) is UNKNOWN, while a non-symbolic program +that runs to completion stays SAFE. Both through the explorer and through the competition package +wrapper (scripts/svcomp-package/run_swat.py), which compiles the task first. + +It runs SymbolicExplorer.py with the executor jar (build it with `../gradlew copyJar` in +symbolic-executor/) and the Z3 libraries in libs/java-library-path, and is skipped if either or +javac is missing. Run it with the harness venv, from symbolic-explorer/: + ../targets/sv-comp/scripts/.venv/bin/python3 -m unittest tests.test_failed_start_e2e -v +""" + +import json +import os +import re +import shutil +import socket +import subprocess +import sys +import tempfile +import unittest +from pathlib import Path + +EXPLORER = Path(__file__).resolve().parent.parent +ROOT = EXPLORER.parent +AGENT = ROOT / 'symbolic-executor/lib/symbolic-executor.jar' +Z3_DIR = ROOT / 'libs/java-library-path' +Z3_JAR = Z3_DIR / 'com.microsoft.z3.jar' +CONFIG = ROOT / 'targets/sv-comp/sv-comp.cfg' +PACKAGE = ROOT / 'scripts/svcomp-package' + + +def free_port(): + with socket.socket() as s: + s.bind(('localhost', 0)) + return s.getsockname()[1] + + +def write(path: Path, text: str): + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(text) + + +@unittest.skipUnless(AGENT.exists() and Z3_JAR.exists() and shutil.which('javac') and shutil.which('java'), + 'needs the executor jar, the Z3 libraries, java and javac') +class FailedStartTest(unittest.TestCase): + + def setUp(self): + self.tmp = Path(tempfile.mkdtemp()) + self.addCleanup(shutil.rmtree, self.tmp, ignore_errors=True) + + def javac(self, out: Path, *sources: Path): + subprocess.run(['javac', '-d', str(out)] + [str(s) for s in sources], check=True) + + def verdict(self, target_dir: Path) -> dict: + logdir = self.tmp / 'run' + logdir.mkdir() + cmd = [sys.executable, '-u', str(EXPLORER / 'SymbolicExplorer.py'), '-prp', 'valid-assert.prp', + '--agent', str(AGENT), '--config', str(CONFIG), '-z3', str(Z3_DIR), '--logdir', str(logdir), + '--mode', 'sv-comp', '--port', str(free_port()), '--target', 'Main', + '--classpath', str(target_dir), str(Z3_JAR)] + # The explorer ends itself with SIGTERM once the verdict is written + subprocess.run(cmd, cwd=logdir, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=120) + return json.loads((logdir / 'stats.json').read_text()) + + def test_verify_error_is_unknown(self): + # B extends A when Main is compiled, but not at run time: verifying Main fails before main + src = self.tmp / 'src' + write(src / 'A.java', 'public class A { public int f() { return 1; } }') + write(src / 'B.java', 'public class B extends A { }') + write(src / 'Main.java', 'public class Main { public static void main(String[] args) { A a = new B(); assert a.f() == 1; } }') + write(src / 'v2/B.java', 'public class B { }') + out = self.tmp / 'classes' + self.javac(out, src / 'A.java', src / 'B.java', src / 'Main.java') + self.javac(out, src / 'v2/B.java') + stats = self.verdict(out) + self.assertEqual(stats['verdict'], 'UNKNOWN') + self.assertIn('no_trace', [e['kind'] for e in stats['execution_errors']]) + + def test_missing_main_class_is_unknown(self): + empty = self.tmp / 'empty' + empty.mkdir() + stats = self.verdict(empty) + self.assertEqual(stats['verdict'], 'UNKNOWN') + + def test_non_symbolic_program_is_safe(self): + src = self.tmp / 'src' + write(src / 'Main.java', 'public class Main { public static void main(String[] args) { int x = 3; assert x == 3; } }') + out = self.tmp / 'classes' + self.javac(out, src / 'Main.java') + stats = self.verdict(out) + self.assertEqual(stats['verdict'], 'SAFE') + self.assertEqual(stats['execution_errors'], []) + + +@unittest.skipUnless(AGENT.exists() and Z3_JAR.exists() and shutil.which('javac') and shutil.which('java'), + 'needs the executor jar, the Z3 libraries, java and javac') +class PackageFailedStartTest(unittest.TestCase): + """The same through run_swat.py, in the layout of scripts/package-svcomp.sh.""" + + def setUp(self): + self.tmp = Path(tempfile.mkdtemp()) + self.addCleanup(shutil.rmtree, self.tmp, ignore_errors=True) + pkg = self.tmp / 'pkg' + (pkg / 'symbolic-executor/lib').mkdir(parents=True) + (pkg / 'libs').mkdir() + shutil.copy(PACKAGE / 'compile-target.sh', pkg) + shutil.copy(CONFIG, pkg) + # The wrapper's fixed explorer port, replaced so that parallel runs do not collide + wrapper = (PACKAGE / 'run_swat.py').read_text() + self.assertIn("'--port', \"8000\"", wrapper) + (pkg / 'run_swat.py').write_text(wrapper.replace("'--port', \"8000\"", f"'--port', \"{free_port()}\"")) + (pkg / 'symbolic-executor/lib/symbolic-executor.jar').symlink_to(AGENT) + (pkg / 'symbolic-explorer').symlink_to(EXPLORER) + (pkg / 'libs/java-library-path').symlink_to(Z3_DIR) + self.pkg = pkg + + def verdict(self, main_java: str | None) -> str: + task = self.tmp / 'task' + task.mkdir() + if main_java is not None: + write(task / 'Main.java', main_java) + else: + write(task / 'Other.java', 'public class Other { public static void main(String[] args) { assert false; } }') + prp = self.tmp / 'valid-assert.prp' + prp.write_text('CHECK( init(main()), LTL(G assert) )\n') + out = subprocess.run([sys.executable, '-u', str(self.pkg / 'run_swat.py'), str(prp), str(task)], + cwd=self.pkg, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, timeout=180).stdout + verdicts = [l for l in out.splitlines() if '[VERDICT valid-assert.prp]' in l] + self.assertTrue(verdicts, out[-2000:]) + return re.search(r'== [A-Z-]+', verdicts[-1]).group(0) + + def test_missing_main_class_is_unknown(self): + # Compiles, but there is no class Main: ClassNotFoundException before main + self.assertEqual(self.verdict(None), '== DONT-KNOW') + + def test_task_that_does_not_compile_is_unknown(self): + self.assertEqual(self.verdict('public class Main { public static void main(String[] args) { int x = ; } }'), '== DONT-KNOW') + + def test_non_symbolic_program_is_safe(self): + self.assertEqual(self.verdict('public class Main { public static void main(String[] args) { int x = 3; assert x == 3; } }'), '== OK') + + +if __name__ == '__main__': + unittest.main() diff --git a/symbolic-explorer/tests/test_fp_tactic.py b/symbolic-explorer/tests/test_fp_tactic.py new file mode 100644 index 00000000..2ebf844d --- /dev/null +++ b/symbolic-explorer/tests/test_fp_tactic.py @@ -0,0 +1,366 @@ +""" +Unit tests for solving queries with floating-point terms with Z3's qffp tactic first +(Z3Handler.check_query): which queries are selected, that its models look like the default +solver's, and when the default solver decides instead. + +Needs the explorer's dependencies, so run it with the harness venv, from symbolic-explorer/: + ../targets/sv-comp/scripts/.venv/bin/python3 -m unittest tests.test_fp_tactic -v +""" + +import itertools +import os +import sys +import threading +import time +import unittest +from types import SimpleNamespace +from unittest import mock + +import z3 + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) + +from data.Database import Database +from solver import SolverHandler as sh +from solver.SolverHandler import SATResult, Z3Handler, _has_fp, _entry_has_fp + +DATA = os.path.join(os.path.dirname(__file__), 'data') + +DECLS = ''' +(declare-fun D_0 () (_ FloatingPoint 11 53)) +(declare-fun D_8 () (_ FloatingPoint 11 53)) +(declare-fun D_9 () (_ FloatingPoint 11 53)) +(declare-fun F_5 () (_ FloatingPoint 8 24)) +(declare-fun |java/lang/String_1| () String) +(declare-fun math_sin_3 ((_ FloatingPoint 11 53)) (_ FloatingPoint 11 53)) +(declare-fun |[I_2| () (Array (_ BitVec 32) (_ BitVec 32))) +(declare-fun |[D_7| () (Array (_ BitVec 32) (_ FloatingPoint 11 53))) +(declare-fun |[I_2_length| () (_ BitVec 32)) +(declare-fun I_4 () (_ BitVec 32)) +(declare-fun I_6 () Int) +''' + +# FP with all the other theories SWAT queries mix it with: a SampledMath UF, parseFloat (str.to_int +# + int_to_bv + to_fp), an array, BV and Int arithmetic over a string length. +MIXED = ''' +(assert (fp.gt (math_sin_3 D_0) ((_ to_fp 11 53) RNE 0.5))) +(assert (fp.lt D_0 ((_ to_fp 11 53) RNE 10.0))) +(assert (fp.eq ((_ to_fp 11 53) RNE ((_ int2bv 64) (str.to_int |java/lang/String_1|))) (fp.add RNE D_0 ((_ to_fp 11 53) RNE 1.0)))) +(assert (< (str.len |java/lang/String_1|) 3)) +(assert (= (select |[I_2| I_4) #x00000007)) +(assert (bvult I_4 |[I_2_length|)) +(assert (bvult |[I_2_length| #x00000004)) +(assert (fp.isNaN F_5)) +(assert (> (+ (str.len |java/lang/String_1|) (bv2int I_4)) 1)) +''' + +# A pure QF_FP query (from float-nonlinear-calculation) neither solver decides within seconds +SLOW = ''' +(assert (let ((s (fp.sqrt RNE D_0)) (q (fp.div RNE D_9 D_8))) + (not (bvsle (ite (or (fp.isNaN s) false) #xffffffff (ite (fp.eq s q) #x00000000 (ite (fp.lt q s) #x00000001 #xffffffff))) + #x00000000)))) +''' + + +def parse(asserts, ctx=None): + return list(z3.parse_smt2_string(DECLS + asserts, ctx=ctx or z3.Context())) + + +def default_solver(query): + s = z3.Solver(ctx=query[0].ctx) + s.add(*query) + return s + + +class Stats: + """Statistics with an rlimit count `used` above what the context had used before.""" + + def __init__(self, used): + self.count = sh._last_rlimit_count + used + + def keys(self): + return ['rlimit count'] + + def get_key_value(self, key): + return self.count + + +class FakeTacticSolver: + """Stands in for Tactic('qffp').solver(): returns `result` (with `reason` or `model`).""" + + def __init__(self, result, reason='', model=None, used=1000, raises=None): + self.result, self.reason, self._model, self.used, self.raises = result, reason, model, used, raises + self.params = {} + + def add(self, *a): + pass + + def set(self, k, v): + self.params[k] = v + + def check(self): + if self.raises: + raise self.raises + return self.result + + def reason_unknown(self): + return self.reason + + def model(self): + return self._model + + def statistics(self): + return Stats(self.used) + + +def with_tactic(fake): + return mock.patch.object(sh.z3, 'Tactic', return_value=SimpleNamespace(solver=lambda: fake)) + + +class SelectionTest(unittest.TestCase): + + def has_fp(self, asserts): + return _has_fp(parse(asserts)) + + def test_fp(self): + self.assertTrue(self.has_fp('(assert (fp.isNaN D_0))')) + self.assertTrue(self.has_fp('(assert (fp.lt F_5 ((_ to_fp 8 24) RNE 1.0)))')) + + def test_fp_from_bits_only(self): + # No FP input, but an FP term (Float.intBitsToFloat) + self.assertTrue(self.has_fp('(assert (fp.isNaN ((_ to_fp 8 24) I_4)))')) + + def test_fp_with_uf(self): + self.assertTrue(self.has_fp('(assert (fp.gt (math_sin_3 D_0) D_0))')) + + def test_fp_with_string(self): + self.assertTrue(self.has_fp('(assert (fp.isZero ((_ to_fp 11 53) RNE ((_ int2bv 64) (str.to_int |java/lang/String_1|)))))')) + + def test_fp_with_array(self): + self.assertTrue(self.has_fp('(assert (fp.isZero (select |[D_7| I_4)))')) + self.assertTrue(self.has_fp('(assert (and (= (select |[I_2| I_4) #x00000001) (fp.isZero D_0)))')) + self.assertTrue(self.has_fp(MIXED)) + + def test_no_fp(self): + self.assertFalse(self.has_fp('(assert (bvslt I_4 #x00000003))')) + self.assertFalse(self.has_fp('(assert (and (> I_6 2) (= (str.len |java/lang/String_1|) I_6)))')) + self.assertFalse(self.has_fp('(assert (= (str.to_int |java/lang/String_1|) (bv2int (select |[I_2| I_4))))')) + self.assertFalse(_has_fp([])) + + def test_shared_subterms_and_deep_terms(self): + # A deep DAG is walked without recursion, each shared subterm once. + x = z3.BitVec('I_4', 32) + for _ in range(5000): + x = x + x + self.assertFalse(_has_fp([x == 0])) + self.assertTrue(_has_fp([z3.And(x == 0, z3.fpIsNaN(z3.FP('D_0', z3.Float64())))])) + + def test_entry_flag_is_kept(self): + entry = {'exprs': parse('(assert (fp.isNaN D_0))')} + self.assertTrue(_entry_has_fp(entry)) + entry['exprs'] = parse('(assert (bvslt I_4 #x00000003))') # not looked at again + self.assertTrue(_entry_has_fp(entry)) + + +class TacticResultTest(unittest.TestCase): + + def check(self, query, **kw): + self.default = default_solver(query) + return Z3Handler.check_query(self.default, query, query, **kw) + + def test_model_has_the_default_solvers_shape(self): + query = parse(MIXED) + res_t, model_t, solver_t, _, _ = self.check(query, fp_tactic=True) + self.assertIsNot(solver_t, self.default) + res_d, model_d, solver_d, _, _ = self.check(query, fp_tactic=False) + self.assertIs(solver_d, self.default) + self.assertEqual((res_t, res_d), (SATResult.SAT, SATResult.SAT)) + + def shape(m): + return {str(d.name()): (d.arity(), type(m[d]).__name__) for d in m.decls()} + self.assertEqual(shape(model_t), shape(model_d)) + self.assertEqual(shape(model_t), { + 'D_0': (0, 'FPNumRef'), 'F_5': (0, 'FPNumRef'), 'I_4': (0, 'BitVecNumRef'), + '[I_2': (0, 'ArrayRef'), '[I_2_length': (0, 'BitVecNumRef'), + 'java/lang/String_1': (0, 'SeqRef'), 'math_sin_3': (1, 'FuncInterp')}) + self.assertEqual(set(Z3Handler.extract_and_encode_values(model_t)), set(Z3Handler.extract_and_encode_values(model_d))) + + def test_model_check_does_not_complete_the_model(self): + # An input the query does not constrain stays out of the model, as with the default solver + query = parse('(assert (fp.isNaN D_0)) (assert (= I_4 I_4))') + _, model, _, _, _ = self.check(query, fp_tactic=True) + self.assertEqual([str(d.name()) for d in model.decls()], ['D_0']) + + def test_unsat(self): + query = parse('(assert (fp.lt (math_sin_3 D_0) (math_sin_3 D_0)))' + '(assert (= (str.len |java/lang/String_1|) 2))') + res, model, solver, _, _ = self.check(query, fp_tactic=True) + self.assertEqual(res, SATResult.UNSAT) + self.assertIsNot(solver, self.default) + + def test_slow_fp_uf_core_is_solved_quickly(self): + # A query from float-nonlinear-calculation (SampledMath math_log UFs): the default solver + # needs over 10 s (and minutes at seed 0), the qffp tactic well under a second. + with open(os.path.join(DATA, 'fp_uf_core.smt2')) as f: + query = list(z3.parse_smt2_string(f.read(), ctx=z3.Context())) + t = time.perf_counter() + res, model, solver, _, _ = self.check(query, fp_tactic=True, timeout_ms=30_000) + self.assertEqual(res, SATResult.SAT) + self.assertIsNot(solver, self.default) + self.assertLess(time.perf_counter() - t, 10) + + def test_rlimit_runs_out_in_the_tactic(self): + with open(os.path.join(DATA, 'fp_uf_core.smt2')) as f: + query = list(z3.parse_smt2_string(f.read(), ctx=z3.Context())) + res, _, solver, _, used = self.check(query, fp_tactic=True, rlimit=100_000) + self.assertEqual(res, SATResult.LIMIT) + self.assertIsNot(solver, self.default) # no fallback + self.assertLessEqual(used, 100_000 + 1000) + res, _, _, _, _ = self.check(query, fp_tactic=True, rlimit=100_000_000) + self.assertEqual(res, SATResult.SAT) + + def test_interrupt_stops_the_tactic(self): + query = parse(SLOW) + ctx = query[0].ctx + timer = threading.Timer(0.3, ctx.interrupt) + timer.start() + t = time.perf_counter() + try: + res, _, solver, _, _ = self.check(query, fp_tactic=True, rlimit=None) + finally: + timer.cancel() + self.assertEqual(res, SATResult.UNKNOWN) + self.assertIsNot(solver, self.default) # no fallback + self.assertLess(time.perf_counter() - t, 20) + + def test_timeout_in_the_tactic(self): + res, _, solver, _, _ = self.check(parse(SLOW), fp_tactic=True, timeout_ms=300) + self.assertEqual(res, SATResult.UNKNOWN) + self.assertIsNot(solver, self.default) + + +class FallbackTest(unittest.TestCase): + """The tactic is replaced by a fake; the default solver is real, or a mock to see it is not run.""" + + QUERY = '(assert (fp.gt D_0 ((_ to_fp 11 53) RNE 1.0)))' + + def run_with(self, fake, query=None, **kw): + query = query or parse(self.QUERY) + with with_tactic(fake): + return Z3Handler.check_query(default_solver(query), query, query, fp_tactic=True, **kw) + + def no_fallback(self, fake, **kw): + query = parse(self.QUERY) + solver = mock.Mock() + with with_tactic(fake): + res = Z3Handler.check_query(solver, query, query, fp_tactic=True, **kw) + solver.check.assert_not_called() + return res + + def assert_default_solved(self, out): + res, model, solver, _, _ = out + self.assertEqual(res, SATResult.SAT) + self.assertIsInstance(solver, z3.Solver) + self.assertTrue(sh._satisfies(model, solver.assertions())) + + def test_unknown_falls_back(self): + self.assert_default_solved(self.run_with(FakeTacticSolver(z3.unknown, 'smt tactic failed to show goal to be sat/unsat (incomplete (theory fpa))'))) + + def test_exception_falls_back(self): + self.assert_default_solved(self.run_with(FakeTacticSolver(None, raises=z3.Z3Exception('tactic failed')))) + + def test_tactic_that_cannot_be_made_falls_back(self): + query = parse(self.QUERY) + with mock.patch.object(sh.z3, 'Tactic', side_effect=z3.Z3Exception('unknown tactic')): + self.assert_default_solved(Z3Handler.check_query(default_solver(query), query, query, fp_tactic=True)) + + def test_wrong_model_falls_back(self): + query = parse(self.QUERY) + other = default_solver(parse('(assert (fp.isZero D_0))', ctx=query[0].ctx)) + self.assertEqual(other.check(), z3.sat) + self.assert_default_solved(self.run_with(FakeTacticSolver(z3.sat, model=other.model()), query=query)) + + def test_model_must_satisfy_the_assertions_before_lifting(self): + query = parse(self.QUERY) + ctx = query[0].ctx + good = default_solver(query) + self.assertEqual(good.check(), z3.sat) + original = query + [z3.FP('D_0', z3.Float64(ctx)) != good.model().eval(z3.FP('D_0', z3.Float64(ctx)))] + with with_tactic(FakeTacticSolver(z3.sat, model=good.model())): + res, model, solver, _, _ = Z3Handler.check_query(default_solver(original), query, original, fp_tactic=True) + self.assertEqual(res, SATResult.SAT) + self.assertIsInstance(solver, z3.Solver) + + def test_fallback_gets_what_is_left_of_the_budget(self): + query = parse(self.QUERY) + solver = mock.Mock() + solver.check.return_value = z3.unsat + solver.statistics.return_value = Stats(0) + with with_tactic(FakeTacticSolver(z3.unknown, 'incomplete', used=1000)): + res = Z3Handler.check_query(solver, query, query, fp_tactic=True, rlimit=5000, timeout_ms=60_000) + self.assertEqual(res[0], SATResult.UNSAT) + solver.set.assert_any_call('rlimit', 4000) + timeout = [c.args[1] for c in solver.set.call_args_list if c.args[0] == 'timeout'][0] + self.assertTrue(59_000 < timeout <= 60_000) + + def test_rlimit_exhausted_is_limit(self): + for reason in ('canceled', 'max. resource limit exceeded'): + fake = FakeTacticSolver(z3.unknown, reason) + self.assertEqual(self.no_fallback(fake, rlimit=5000)[0], SATResult.LIMIT) + self.assertEqual(fake.params, {'rlimit': 5000}) + + def test_interrupt_and_timeout_are_unknown(self): + self.assertEqual(self.no_fallback(FakeTacticSolver(z3.unknown, 'interrupted'), rlimit=5000)[0], SATResult.UNKNOWN) + self.assertEqual(self.no_fallback(FakeTacticSolver(z3.unknown, 'canceled'))[0], SATResult.UNKNOWN) + fake = FakeTacticSolver(z3.unknown, 'timeout') + self.assertEqual(self.no_fallback(fake, timeout_ms=1000)[0], SATResult.UNKNOWN) + self.assertEqual(fake.params, {'timeout': 1000}) + + +class SolveTest(unittest.TestCase): + """Z3Handler.solve picks the tactic for a query with FP, unless it is switched off.""" + + gids = itertools.count(1_000_000) + + def solve(self, constraint, path=()): + # The constraint cache keeps a node's constraint by gid: one per test + gid = next(self.gids) + node = SimpleNamespace(id=gid, gid=gid, trace_id='t', constraint={'t': constraint}) + return Z3Handler.solve(node, list(path), timeout_ms=None) + + def tactic_used(self, constraint, path=(), enabled=True): + db = Database.instance() + before = getattr(db, 'fp_tactic', True) + db.fp_tactic = enabled + try: + with mock.patch.object(sh.z3, 'Tactic', wraps=z3.Tactic) as tactic: + res, sol = self.solve(constraint, path) + finally: + db.fp_tactic = before + return tactic.called, res, sol + + def test_fp_query_uses_the_tactic(self): + used, res, sol = self.tactic_used(DECLS + '(assert (fp.isNaN D_0))') + self.assertTrue(used) + self.assertEqual(res, SATResult.SAT) + self.assertEqual(set(sol), {'D_0'}) + + def test_fp_in_the_path_only(self): + used, res, _ = self.tactic_used(DECLS + '(assert (bvslt I_4 #x00000000))', [DECLS + '(assert (fp.isNaN D_0))']) + self.assertTrue(used) + self.assertEqual(res, SATResult.SAT) + + def test_query_without_fp_does_not(self): + used, res, _ = self.tactic_used(DECLS + '(assert (bvslt I_4 #x00000000))') + self.assertFalse(used) + self.assertEqual(res, SATResult.SAT) + + def test_switched_off(self): + used, res, sol = self.tactic_used(DECLS + '(assert (fp.isNaN D_0))', enabled=False) + self.assertFalse(used) + self.assertEqual(res, SATResult.SAT) + self.assertEqual(set(sol), {'D_0'}) + + +if __name__ == '__main__': + unittest.main() diff --git a/symbolic-explorer/tests/test_int2bv_lifting.py b/symbolic-explorer/tests/test_int2bv_lifting.py new file mode 100644 index 00000000..ae2ee9ef --- /dev/null +++ b/symbolic-explorer/tests/test_int2bv_lifting.py @@ -0,0 +1,385 @@ +""" +Unit tests for solver/Int2BvLifting: comparisons over int_to_bv of bounded Integer terms are made over +the Integers, with the same truth value under every assignment. + +Needs z3, so run it with the harness venv, from symbolic-explorer/: + ../targets/sv-comp/scripts/.venv/bin/python3 -m unittest tests.test_int2bv_lifting -v +""" + +import os +import random +import sys +import unittest + +import z3 + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) + +from solver.Int2BvLifting import Int2BvLifting, lift_query, lift_inputs, JAVA_MAX_STRING_LENGTH, MAX_JAVA_CHAR + + +def mixes_sorts(e): + s = e.sexpr() + return 'int_to_bv' in s or 'ubv_to_int' in s or 'int2bv' in s or 'bv2int' in s + + +class Terms: + """Random terms over a string s, an 8-bit x and a 32-bit y, as the executor builds them.""" + + def __init__(self, rnd): + self.rnd = rnd + self.s = z3.String('s') + self.x = z3.BitVec('x', 8) + self.y = z3.BitVec('y', 32) + + def int_leaf(self): + r = self.rnd + return r.choice([ + lambda: z3.Length(self.s), + lambda: z3.IndexOf(self.s, z3.StringVal('a'), z3.IntVal(r.randint(-1, 2))), + lambda: z3.StrToCode(z3.SubString(self.s, z3.IntVal(r.randint(-1, 2)), z3.IntVal(1))), + lambda: z3.BV2Int(self.x), + lambda: z3.BV2Int(self.y), + lambda: z3.IntVal(r.choice([0, 1, -1, 3, 65535, 65536, 2 ** 31 - 1, 2 ** 31, -2 ** 31, 2 ** 32 - 1])), + ])() + + def int_term(self, depth): + r = self.rnd + if depth == 0 or r.random() < 0.3: + return self.int_leaf() + a, b = self.int_term(depth - 1), self.int_term(depth - 1) + return r.choice([ + lambda: a + b, + lambda: a - b, + lambda: a * z3.IntVal(r.choice([-1, 2, 3])), + lambda: -a, + lambda: z3.If(self.bool_term(depth - 1), a, b), + ])() + + def bv_term(self, width, depth): + r = self.rnd + if depth == 0 or r.random() < 0.25: + return r.choice([ + lambda: z3.Int2BV(self.int_term(2), width), + lambda: z3.BitVecVal(r.randint(0, 2 ** width - 1), width), + lambda: z3.BitVecVal(2 ** width - 1, width), + ])() + a, b = self.bv_term(width, depth - 1), self.bv_term(width, depth - 1) + options = [ + lambda: a + b, + lambda: a - b, + lambda: -a, + lambda: a * z3.BitVecVal(r.choice([2, 3, 2 ** width - 1]), width), + lambda: z3.If(self.bool_term(depth - 1), a, b), + ] + if width > 8: + narrow = 8 if width == 16 else 16 + options.append(lambda: z3.ZeroExt(width - narrow, self.bv_term(narrow, depth - 1))) + options.append(lambda: z3.SignExt(width - narrow, self.bv_term(narrow, depth - 1))) + return r.choice(options)() + + def bool_term(self, depth): + r = self.rnd + width = r.choice([8, 16, 32]) + a, b = self.bv_term(width, max(depth - 1, 0)), self.bv_term(width, max(depth - 1, 0)) + return r.choice([ + lambda: a == b, lambda: a != b, + lambda: a <= b, lambda: a < b, lambda: a >= b, lambda: a > b, + lambda: z3.ULE(a, b), lambda: z3.ULT(a, b), lambda: z3.UGE(a, b), lambda: z3.UGT(a, b), + lambda: z3.BV2Int(a) == z3.IntVal(r.randint(0, 300)), + ])() + + def assignment(self, java_chars=False): + r = self.rnd + chars = ['a', 'b', 'ÿ', '￿'] + ([] if java_chars else ['\U0001F600']) + s = ''.join(r.choice(chars) for _ in range(r.randint(0, 3))) + x = r.choice([0, 1, 127, 128, 255, r.randint(0, 255)]) + y = r.choice([0, 1, 2 ** 31 - 1, 2 ** 31, 2 ** 32 - 1, r.randint(0, 2 ** 32 - 1)]) + return [(self.s, z3.StringVal(s)), (self.x, z3.BitVecVal(x, 8)), (self.y, z3.BitVecVal(y, 32))] + + +def evaluate(e, assignment): + v = z3.simplify(z3.substitute(e, *assignment)) + if not (z3.is_true(v) or z3.is_false(v)): + raise AssertionError(f'{e} does not evaluate to a constant: {v}') + return z3.is_true(v) + + +class LiftingTest(unittest.TestCase): + + def test_random_comparisons_keep_their_truth_value(self): + rnd = random.Random(7) + terms = Terms(rnd) + lifted = 0 + for _ in range(150): + e = terms.bool_term(3) + lifting = Int2BvLifting() + r = lifting.rewrite(e) + lifted += not r.eq(e) + for _ in range(12): + a = terms.assignment() + self.assertEqual(evaluate(e, a), evaluate(r, a), f'\n{e}\nrewritten to\n{r}\nunder {a}') + self.assertGreater(lifted, 50) + + def test_string_length_comparisons_become_integer_comparisons(self): + s = z3.String('s') + length = z3.Int2BV(z3.Length(s), 32) + e = z3.And(z3.Not(length <= z3.BitVecVal(2, 32)), + z3.Int2BV(z3.StrToCode(z3.SubString(s, z3.IntVal(1), z3.IntVal(1))), 16) != z3.BitVecVal(38, 16)) + out = lift_query(Int2BvLifting(), [e]) + self.assertFalse(any(mixes_sorts(x) for x in out)) + # The length bound the rewrite relies on is part of the query. + self.assertTrue(any(x.eq(z3.Length(s) <= JAVA_MAX_STRING_LENGTH) for x in out)) + solver = z3.Solver() + solver.add(*out) + self.assertEqual(solver.check(), z3.sat) + self.assertTrue(z3.is_true(solver.model().eval(e, model_completion=True))) + + def test_overflowing_length_arithmetic_wraps(self): + # s.length() + 3 overflows for long strings: the lift must keep the wrap. + s = z3.String('s') + e = z3.BitVecVal(0, 32) > z3.Int2BV(z3.Length(s) + 3, 32) + r = Int2BvLifting().rewrite(e) + self.assertFalse(mixes_sorts(r)) + # (A model would need a string of 2^31 chars, so evaluate at given lengths instead.) + for n, holds in [(0, False), (2 ** 31 - 4, False), (2 ** 31 - 3, True), (2 ** 31 - 1, True)]: + v = z3.simplify(z3.substitute(r, (z3.Length(s), z3.IntVal(n)))) + self.assertEqual(z3.is_true(v), holds, f'length {n}: {v}') + + def test_signed_bv2int_of_a_length_is_the_length(self): + # JavaSMT's signed bv2int: ite(0 <= x, ubv_to_int(x), ubv_to_int(x) - 2^32) + s = z3.String('s') + x = z3.Int2BV(z3.Length(s), 32) + z3.BitVecVal(2 ** 32 - 1, 32) + signed = z3.If(z3.BitVecVal(0, 32) <= x, z3.BV2Int(x), z3.IntVal(-2 ** 32) + z3.BV2Int(x)) + e = z3.SubString(s, signed, z3.IntVal(1)) == z3.StringVal('a') + out = lift_query(Int2BvLifting(), [e]) + self.assertFalse(any(mixes_sorts(x) for x in out)) + + def test_char_widened_to_int_is_the_char_code(self): + # (int) s.charAt(0) as the executor builds it: the signed bv2int of the zero-extended char, + # with ubv_to_int(concat ...) expanded as Z3 does it. + s = z3.String('s') + code = z3.StrToCode(z3.SubString(s, z3.IntVal(0), z3.IntVal(1))) + char = z3.Int2BV(code, 16) + x = z3.Concat(z3.BitVecVal(0, 16), char) + unsigned = 65536 * z3.BV2Int(z3.BitVecVal(0, 16)) + z3.BV2Int(char) + widened = z3.If(z3.BitVecVal(0, 32) <= x, unsigned, z3.IntVal(-2 ** 32) + unsigned) + e = z3.StrFromCode(widened) == z3.StringVal('a') + out = lift_query(Int2BvLifting(java_chars=True), [e]) + self.assertFalse(any(mixes_sorts(x) for x in out)) + solver = z3.Solver() + solver.add(*out) + self.assertEqual(solver.check(), z3.sat) + self.assertEqual(solver.model().eval(z3.SubString(s, 0, 1)).as_string(), 'a') + + def test_lifted_inputs_keep_the_truth_value(self): + # x and y replaced by Integer stand-ins where they are only used as numbers: under every + # assignment, with the stand-ins at the inputs' unsigned values, the query is unchanged. + rnd = random.Random(11) + terms = Terms(rnd) + lifted = compared = 0 + for _ in range(150): + e = terms.bool_term(3) + out, inputs = lift_inputs(Int2BvLifting(), [e]) + lifted += bool(inputs) + for _ in range(12): + a = terms.assignment() + values = dict((str(v), val) for v, val in a) + stand_ins = [(x, z3.IntVal(values[str(v)].as_long())) for x, v in inputs.values()] + # Bounds the rewrite relied on (chars up to 0xFFFF) are part of out. + if not all(evaluate(o, a + stand_ins) for o in out[1:]): + continue + compared += 1 + self.assertEqual(evaluate(e, a), evaluate(out[0], a + stand_ins), f'\n{e}\nlifted to\n{out[0]}\nunder {a}') + self.assertGreater(lifted, 30) + self.assertGreater(compared, 1000) + + def test_char_at_a_symbolic_int_index_is_solved_over_the_integers(self): + # s.charAt(i) == 'b' with i a Java int: (str.at s (signed bv2int i)) + s = z3.String('java/lang/String_0') + i = z3.BitVec('I_1', 32) + signed = z3.If(z3.BitVecVal(0, 32) <= i, z3.BV2Int(i), z3.IntVal(-2 ** 32) + z3.BV2Int(i)) + e = z3.And(z3.SubString(s, signed, z3.IntVal(1)) == z3.StringVal('b'), z3.Length(s) == 3, + z3.Not(z3.PrefixOf(z3.StringVal('b'), s)), i != z3.BitVecVal(2, 32)) + out, inputs = lift_inputs(Int2BvLifting(), [e]) + self.assertEqual([str(v) for x, v in inputs.values()], ['I_1']) + self.assertFalse(any(mixes_sorts(x) for x in out)) + solver = z3.Solver() + solver.add(*out) + self.assertEqual(solver.check(), z3.sat) + from solver.SolverHandler import Z3Handler + encoded = {} + Z3Handler.map_lifted_inputs(solver.model(), inputs, encoded) + self.assertEqual(encoded['I_1']['encoded_value'], '1') + self.assertEqual(encoded['I_1']['index'], '1') + + def test_negative_inputs_map_back_to_negative_ints(self): + i = z3.BitVec('I_4', 32) + e = z3.BV2Int(i) == z3.IntVal(2 ** 32 - 5) + out, inputs = lift_inputs(Int2BvLifting(), [e]) + solver = z3.Solver() + solver.add(*out) + self.assertEqual(solver.check(), z3.sat) + from solver.SolverHandler import Z3Handler + encoded = {} + Z3Handler.map_lifted_inputs(solver.model(), inputs, encoded) + self.assertEqual(encoded['I_4']['encoded_value'], '-5') + + def test_inputs_used_in_bit_operations_stay_bit_vectors(self): + i = z3.BitVec('I_1', 32) + e = z3.And(z3.BV2Int(i) < 10, z3.Extract(3, 0, i) == z3.BitVecVal(5, 4)) + out, inputs = lift_inputs(Int2BvLifting(), [e]) + self.assertEqual(inputs, {}) + self.assertTrue(out[0].eq(e)) + + def test_terms_without_int2bv_are_left_alone(self): + y = z3.BitVec('y', 32) + e = z3.And(y + 1 > z3.BitVecVal(3, 32), z3.BV2Int(y) < 7) + self.assertTrue(Int2BvLifting().rewrite(e).eq(e)) + + +def char_code(s, i): + return z3.StrToCode(z3.SubString(s, z3.IntVal(i), z3.IntVal(1))) + + +def char_at(s, i): + """s.charAt(i) as the executor builds it (CharValue.java)""" + return z3.Int2BV(char_code(s, i), 16) + + +class JavaCharsLiftingTest(unittest.TestCase): + """Int2BvLifting(java_chars=True): char codes in [-1, 0xFFFF], each relied-on one bounded.""" + + def test_random_comparisons_keep_their_truth_value_under_the_bounds(self): + rnd = random.Random(11) + terms = Terms(rnd) + lifted = 0 + for _ in range(80): + e = terms.bool_term(3) + out = lift_query(Int2BvLifting(java_chars=True), [e]) + r, bounds = out[0], out[1:] + lifted += not r.eq(e) + for _ in range(8): + # Also with a char above 0xFFFF: then the bounds may exclude the assignment, but + # wherever they hold, the rewrite has the truth value of e. + a = terms.assignment(java_chars=rnd.random() < 0.7) + self.assertEqual(evaluate(z3.And(*out), a), evaluate(z3.And(e, *bounds), a), + f'\n{e}\nrewritten to\n{out}\nunder {a}') + for _ in range(4): + a = terms.assignment(java_chars=True) + self.assertTrue(all(evaluate(b, a) for b in bounds), + f'a bound excludes a Java string: {bounds} under {a}') + self.assertGreater(lifted, 25) + + def test_char_comparisons_differ_only_where_the_bound_excludes_the_string(self): + # A char above 0xFFFF is truncated by int_to_bv but not by the lift: the two disagree only + # where the char code bound is false, which no Java string makes it. + s = z3.String('s') + ops = [lambda a, b: a == b, lambda a, b: a != b, lambda a, b: a < b, lambda a, b: a <= b, + lambda a, b: a > b, lambda a, b: a >= b, z3.ULT, z3.ULE, z3.UGT, z3.UGE] + differ = 0 + for op in ops: + for c in [0, 0x41, 0x7FFF, 0x8000, 0xF600, 0xF601, 0xFFFF]: + for i, d in [(0, 0), (1, 0), (0, 1)]: + e = op(char_at(s, i) + z3.BitVecVal(d, 16), z3.BitVecVal(c, 16)) + out = lift_query(Int2BvLifting(java_chars=True), [e]) + self.assertFalse(any(mixes_sorts(x) for x in out)) + for v in ['', 'A', '\uf600', '\uffff', 'A\uffff', '\U0001F600', 'A\U0001F600']: + a = [(s, z3.StringVal(v))] + java = all(ord(ch) <= MAX_JAVA_CHAR for ch in v) + self.assertEqual(evaluate(z3.And(*out), a), evaluate(z3.And(e, *out[1:]), a), f'{e} under {v!r}') + if java: + self.assertEqual(evaluate(z3.And(*out), a), evaluate(e, a), f'{e} under {v!r}') + elif evaluate(out[0], a) != evaluate(e, a): + differ += 1 + self.assertFalse(evaluate(z3.And(*out[1:]), a)) + self.assertGreater(differ, 0) + + def test_char_code_lift_has_no_mod_and_its_bound_is_in_the_query(self): + # java-ranger LoopCharAt: s.length() > 5 && s.charAt(2) == 'B' + s = z3.String('s') + e = z3.And(z3.Int2BV(z3.Length(s), 32) > z3.BitVecVal(5, 32), char_at(s, 2) == z3.BitVecVal(66, 16)) + out = lift_query(Int2BvLifting(java_chars=True), [e]) + self.assertFalse(any(mixes_sorts(x) for x in out)) + self.assertNotIn('mod', ' '.join(x.sexpr() for x in out)) + self.assertTrue(any(x.eq(char_code(s, 2) <= MAX_JAVA_CHAR) for x in out)) + unicode = lift_query(Int2BvLifting(), [e]) + self.assertIn('mod', ' '.join(x.sexpr() for x in unicode)) + solver = z3.Solver() + solver.add(*out) + self.assertEqual(solver.check(), z3.sat) + self.assertEqual(solver.model().eval(z3.SubString(s, 2, 1)).as_string(), 'B') + + def test_a_char_above_0xffff_cannot_satisfy_a_char_comparison(self): + # Truncated to 16 bits, U+1F600 reads as the Java char 0xF600. The full range keeps that + # (exact for SMT strings); with Java chars, only the char 0xF600 itself is a solution. + s = z3.String('s') + e = z3.And(char_at(s, 0) == z3.BitVecVal(0xF600, 16), char_code(s, 0) > 0xFFFF) + self.assertEqual(self.check(lift_query(Int2BvLifting(), [e])), z3.sat) + self.assertEqual(self.check(lift_query(Int2BvLifting(java_chars=True), [e])), z3.unsat) + e = char_at(s, 0) == z3.BitVecVal(0xF600, 16) + solver = z3.Solver() + solver.add(*lift_query(Int2BvLifting(java_chars=True), [e])) + self.assertEqual(solver.check(), z3.sat) + self.assertEqual(solver.model().eval(char_code(s, 0)).as_long(), 0xF600) + + @staticmethod + def check(exprs): + solver = z3.Solver() + solver.add(*exprs) + return solver.check() + + def test_java_chars_only(self): + lifting = Int2BvLifting(java_chars=True) + s = z3.String('s') + c16, c32 = z3.BitVec('c16', 16), z3.BitVec('c32', 32) + ok = lambda x: lifting.java_chars_only(x == s) + self.assertTrue(ok(z3.Concat(s, z3.StringVal('a\u00ff\ud83d\uffff')))) + # Character.toString(char), new String(char[]): str.from_code of a 16-bit char + self.assertTrue(ok(z3.StrFromCode(z3.BV2Int(c16)))) + # StringBuilder.reverse: str.from_code of a char code of a string of Java chars + self.assertTrue(ok(z3.StrFromCode(char_code(s, 0)))) + # Integer.toHexString: digits + d = z3.BV2Int(z3.Extract(3, 0, c32)) + self.assertTrue(ok(z3.StrFromCode(d + z3.If(d > 9, z3.IntVal(87), z3.IntVal(48))))) + self.assertTrue(ok(z3.IntToStr(z3.BV2Int(c32)))) + # a literal with a surrogate pair is one SMT char above 0xFFFF (JavaSMT's makeString) + self.assertFalse(ok(z3.Concat(s, z3.StringVal('\U0001F600')))) + self.assertFalse(lifting.java_chars_only(z3.InRe(s, z3.Range('a', '\U0001F600')))) + # an int code point (appendCodePoint, indexOf(int)), or a char code above 0xFFFF + self.assertFalse(ok(z3.StrFromCode(z3.BV2Int(c32)))) + self.assertFalse(ok(z3.StrFromCode(char_code(s, 0) + 1))) + self.assertFalse(ok(z3.StrFromCode(z3.Int('i')))) + self.assertFalse(ok(z3.StrFromCode(z3.IntVal(0x10000)))) + self.assertTrue(ok(z3.StrFromCode(z3.IntVal(0xFFFF)))) + # Char-sorted terms: the executor builds none + self.assertFalse(lifting.java_chars_only(z3.SubSeq(s, 0, 1) == z3.Unit(z3.CharVal(65)))) + # deep terms do not hit the recursion limit + deep = s + for i in range(5000): + deep = z3.Concat(deep, z3.StrFromCode(z3.BV2Int(c16) + i % 2)) + self.assertFalse(ok(deep)) + + +class LiftInt2BvTest(unittest.TestCase): + """Z3Handler.lift_int2bv picks the lifting by the query.""" + + def lift(self, assertions): + from types import SimpleNamespace + from solver.SolverHandler import Z3Handler + return Z3Handler.lift_int2bv(SimpleNamespace(), assertions)[0] + + def test_java_chars_unless_the_query_has_a_char_above_0xffff(self): + s = z3.String('s') + e = char_at(s, 0) == z3.BitVecVal(66, 16) + out = self.lift([e, z3.PrefixOf(z3.StringVal('B\u00e9'), s)]) + self.assertNotIn('mod', ' '.join(x.sexpr() for x in out)) + self.assertTrue(any(x.eq(char_code(s, 0) <= MAX_JAVA_CHAR) for x in out)) + out = self.lift([e, z3.PrefixOf(z3.StringVal('B\U0001F600'), s)]) + self.assertIn('mod', ' '.join(x.sexpr() for x in out)) + self.assertFalse(any(x.eq(char_code(s, 0) <= MAX_JAVA_CHAR) for x in out)) + self.assertFalse(any(mixes_sorts(x) for x in out)) + + +if __name__ == '__main__': + unittest.main() diff --git a/symbolic-explorer/tests/test_loop_state_matching.py b/symbolic-explorer/tests/test_loop_state_matching.py new file mode 100644 index 00000000..0680d772 --- /dev/null +++ b/symbolic-explorer/tests/test_loop_state_matching.py @@ -0,0 +1,103 @@ +""" +Unit tests for loop-head state matching: Tree positions the loop-head states the executor reports +with a trace, and dfs() leaves out the branches below a state that was reached at a smaller +position already. + +Run from symbolic-explorer/: + python3 -m unittest tests.test_loop_state_matching -v +""" + +import os +import sys +import unittest + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) + +from data.BinaryExecutionTree.Tree import Tree +from data.trace.Branch import Branch +from strategy.DFS import dfs + +SYMBOLIC = '(declare-fun Z_0 () Bool)(assert Z_0)' + + +def trace(trace_id, *steps): + """Steps are (iid, taken) or (iid, taken, states).""" + branches = [] + for step in steps: + iid, taken = step[0], step[1] + states = step[2] if len(step) > 2 else () + branches.append(Branch(id=iid, trace_id=trace_id, has_branched=taken, constraint=SYMBOLIC, + loop_states=tuple(states))) + return branches + + +def open_branches(tree): + return sorted(n.id for n in dfs(tree, tree.root, set(), set())) + + +class LoopStateMatchingTest(unittest.TestCase): + + def setUp(self): + Tree.LOOP_STATE_MATCHING = True + + def tearDown(self): + Tree.LOOP_STATE_MATCHING = True + + def test_positions(self): + tree = Tree(0) + t = trace('t1', (1, True, ['A']), (2, False), (3, True, ['B', 'A'])) + tree.add(t, [], []) + # (branches before, their outcomes as a binary number, states before) + self.assertEqual(tree.loop_state_first, {'A': (0, 0, 0), 'B': (2, 0b10, 1)}) + self.assertEqual(tree.root.branched.skipped.loop_states, (('B', (2, 0b10, 1)), ('A', (2, 0b10, 2)))) + + def test_repeated_state_on_one_path(self): + tree = Tree(0) + # A loop whose state repeats: the second visit of A comes before branch 3 + tree.add(trace('t1', (1, True, ['A']), (2, True), (3, True, ['A']), (4, True)), [], []) + self.assertEqual(open_branches(tree), [1, 2]) + self.assertEqual(tree.walk['loop_state_pruned'], 1) + + def test_state_reached_earlier_on_another_path(self): + tree = Tree(0) + tree.add(trace('t1', (1, True), (2, True), (3, True, ['S']), (4, True)), [], []) + self.assertEqual(open_branches(tree), [1, 2, 3, 4]) + # Another path reaches S after one branch: below the first occurrence nothing is new + tree.add(trace('t2', (1, False, []), (5, True, ['S']), (6, True)), [], []) + self.assertEqual(open_branches(tree), [2, 5, 6]) + + def test_same_depth_ordered_by_outcomes(self): + tree = Tree(0) + tree.add(trace('t1', (1, True), (2, True, ['S']), (3, True)), [], []) + tree.add(trace('t2', (1, False), (4, True, ['S']), (5, True)), [], []) + # Both after one branch; the path with outcome 0 (not taken) comes first + self.assertEqual(open_branches(tree), [4, 5]) + + def test_equal_positions_do_not_prune(self): + tree = Tree(0) + tree.add(trace('t1', (1, True, ['S']), (2, True, ['S'])), [], []) + # the second visit is after a branch, the first not: only the second is dominated + self.assertEqual(open_branches(tree), [1]) + tree2 = Tree(0) + tree2.add(trace('t1', (1, True, ['S']), (2, True)), [], []) + tree2.add(trace('t2', (1, True, ['S']), (2, False)), [], []) + self.assertEqual(open_branches(tree2), [1]) + + def test_pruning_one_subtree_keeps_walking_the_others(self): + # The walk is iterative: a pruned subtree on one side of branch 1 must not end it, the other + # side's open branches are still offered. + tree = Tree(0) + tree.add(trace('t1', (1, False, ['A']), (2, True), (3, True, ['A']), (4, True)), [], []) + tree.add(trace('t2', (1, True, ['A']), (5, True)), [], []) + self.assertEqual(open_branches(tree), [2, 5]) + self.assertEqual(tree.walk['loop_state_pruned'], 1) + + def test_disabled(self): + Tree.LOOP_STATE_MATCHING = False + tree = Tree(0) + tree.add(trace('t1', (1, True, ['A']), (2, True), (3, True, ['A']), (4, True)), [], []) + self.assertEqual(open_branches(tree), [1, 2, 3, 4]) + + +if __name__ == '__main__': + unittest.main() diff --git a/symbolic-explorer/tests/test_math_models.py b/symbolic-explorer/tests/test_math_models.py new file mode 100644 index 00000000..a39fea79 --- /dev/null +++ b/symbolic-explorer/tests/test_math_models.py @@ -0,0 +1,111 @@ +""" +Unit tests for the explorer side of the approximate Math models: definitions of model functions +are sliced to the ones a query needs, and a query over a model gets a bounded resource limit. + +Needs the explorer's dependencies, so run it with the harness venv, from symbolic-explorer/: + ../targets/sv-comp/scripts/.venv/bin/python3 -m unittest tests.test_math_models -v +""" + +import os +import sys +import unittest +from types import SimpleNamespace +from unittest import mock + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) + +from solver.SolverHandler import SATResult +from strategy import StrategyService as ss +from strategy.StrategyService import StrategyService, declared_ufs + +FP = '(_ FloatingPoint 11 53)' +DECL_X = f'(declare-fun D_0 () {FP})' + + +def uf(name, arity=1): + return f'(declare-fun {name} ({" ".join([FP] * arity)}) {FP})' + + +def definition(name, arg='D_0', *nested): + return '\n'.join([DECL_X, *[uf(n) for n in nested], uf(name), f'(assert (= ({name} {arg}) (fp #b0 #b01111111111 #x0000000000000)))']) + + +def branch_on(name): + return '\n'.join([DECL_X, uf(name), f'(assert (fp.gt ({name} D_0) (fp #b0 #b00000000000 #x0000000000000)))']) + + +class DeclaredUfsTest(unittest.TestCase): + + def test_variables_are_not_functions(self): + self.assertEqual(declared_ufs(DECL_X), frozenset()) + self.assertEqual(declared_ufs(DECL_X + uf('math_tan_3') + uf('|math_pow_4|', 2)), {'math_tan_3', 'math_pow_4'}) + self.assertEqual(declared_ufs('(declare-fun toLowerCase (Int) Int)'), {'toLowerCase'}) + + +class SliceTest(unittest.TestCase): + + def test_unrelated_definitions_are_left_out(self): + tan, log = definition('math_tan_0'), definition('math_log_1') + sliced, ufs = StrategyService.slice_uf_definitions([tan, log], [branch_on('math_log_1')]) + self.assertEqual(sliced, [log]) + self.assertEqual(ufs, {'math_log_1'}) + + def test_nested_definitions_are_followed_in_order(self): + # math_pow_2's argument is math_log10_0(D_0); a query on math_pow_2 needs both. + inner = definition('math_log10_0') + outer = definition('math_pow_2', '(math_log10_0 D_0)', 'math_log10_0') + other = definition('math_tan_1') + sliced, ufs = StrategyService.slice_uf_definitions([inner, other, outer], [branch_on('math_pow_2')]) + self.assertEqual(sliced, [inner, outer]) + self.assertEqual(ufs, {'math_pow_2', 'math_log10_0'}) + + def test_constraints_without_functions_are_kept(self): + bound = f'{DECL_X}(assert (fp.lt D_0 (fp #b0 #b10000000000 #x0000000000000)))' + sliced, ufs = StrategyService.slice_uf_definitions([bound, definition('math_tan_0')], [DECL_X]) + self.assertEqual(sliced, [bound]) + self.assertEqual(ufs, set()) + + +class ApproximateLimitTest(unittest.TestCase): + + def solve(self, constraint, ufs, result, **kwargs): + node = SimpleNamespace(id=7, gid=7, trace_id='t', constraint={'t': constraint}, inputs=[], + ufs=[SimpleNamespace(definition=d) for d in ufs], parent=None) + db = mock.Mock() + with mock.patch.object(ss.Database, 'instance', return_value=db), \ + mock.patch.object(ss.Z3Handler, 'solve', return_value=(result, {})) as solve: + sat, _ = StrategyService.solve_branch(node, solver_timeout_ms=None, **kwargs) + return sat, solve.call_args.kwargs['rlimit'] + + def test_query_over_a_model_is_capped_and_given_up(self): + sat, rlimit = self.solve(branch_on('math_tan_0'), [definition('math_tan_0')], SATResult.LIMIT, + approximate_rlimit=100) + self.assertEqual((sat, rlimit), (SATResult.UNKNOWN, 100)) + + def test_smaller_base_limit_still_escalates(self): + sat, rlimit = self.solve(branch_on('math_tan_0'), [definition('math_tan_0')], SATResult.LIMIT, + solver_rlimit=10, approximate_rlimit=100) + self.assertEqual((sat, rlimit), (SATResult.LIMIT, 10)) + + def test_exact_query_is_not_capped(self): + # The trace has a model call, but this branch does not depend on it. + sat, rlimit = self.solve(f'{DECL_X}(assert (fp.isZero D_0))', [definition('math_tan_0')], SATResult.UNSAT, + approximate_rlimit=100) + self.assertEqual((sat, rlimit), (SATResult.UNSAT, None)) + + +class ModelValuesTest(unittest.TestCase): + + def test_model_functions_are_not_inputs(self): + from z3 import Solver, FP, Float64, Function, fpLT, FPVal, sat + from solver.SolverHandler import Z3Handler + x = FP('D_0', Float64()) + f = Function('math_tan_0', Float64(), Float64()) + s = Solver() + s.add(fpLT(FPVal(1.0, Float64()), f(x)), fpLT(x, FPVal(2.0, Float64()))) + self.assertEqual(s.check(), sat) + self.assertEqual(set(Z3Handler.extract_and_encode_values(s.model())), {'D_0'}) + + +if __name__ == '__main__': + unittest.main() diff --git a/symbolic-explorer/tests/test_no_trace.py b/symbolic-explorer/tests/test_no_trace.py new file mode 100644 index 00000000..3f83c994 --- /dev/null +++ b/symbolic-explorer/tests/test_no_trace.py @@ -0,0 +1,133 @@ +""" +Unit tests for SVCompDriver.determine_next_step on runs that sent no trace: the target never reached +its entry method (e.g. a VerifyError or a missing main class), so the run must not count as a +non-symbolic program that ran to completion. + +It imports the driver, which needs the explorer's dependencies (z3, fastapi, ...) that the system +Python lacks, so run it with the harness venv, from symbolic-explorer/: + ../targets/sv-comp/scripts/.venv/bin/python3 -m unittest tests.test_no_trace -v +""" + +import os +import sys +import unittest +from types import SimpleNamespace +from unittest import mock + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) + +from driver import SVCompDriver as svcomp +from svcomp.ResourceBudget import ResourceBudget + +VERIFY_ERROR_OUTPUT = [ + 'Error: Unable to initialize main class Main', + 'Caused by: java.lang.VerifyError: Bad type on operand stack', +] + + +class NoTraceTest(unittest.TestCase): + + def setUp(self): + self.tree = SimpleNamespace(symbolic_vars=set(), execution_errors=[], uncaught_exceptions=0) + def record_execution_error(endpoint_id, kind, message): + self.tree.execution_errors.append({'kind': kind, 'message': message}) + def record_uncaught_exception(endpoint_id): + self.tree.uncaught_exceptions += 1 + db = SimpleNamespace(record_execution_error=record_execution_error, + record_uncaught_exception=record_uncaught_exception, + get_tree=lambda endpoint_id: self.tree, + get_witness_data=lambda endpoint_id: None, + is_stack_overflow_trace=lambda endpoint_id, trace_id: False, + get_execution_errors=lambda endpoint_id, kind: [e for e in self.tree.execution_errors if e.get('kind') == kind], + count_symbolic_vars=lambda endpoint_id: len(self.tree.symbolic_vars)) + patcher = mock.patch.object(svcomp.Database, 'instance', return_value=db) + patcher.start() + self.addCleanup(patcher.stop) + self.driver = svcomp.SVCompDriver.__new__(svcomp.SVCompDriver) + self.driver.state = svcomp.State() + self.driver.verification_category = svcomp.VerificationCategory.VALID_ASSERT_PRP + + def step(self, output, received_trace): + return self.driver.determine_next_step(svcomp.ExecutionStatus.SUCCESS, output, received_trace) + + def test_failed_start_is_unknown(self): + self.assertEqual(self.step(VERIFY_ERROR_OUTPUT, received_trace=False), svcomp.Action.REPORTVERDICT) + self.assertEqual(self.driver.state.verdict, svcomp.Verdict.UNKNOWN) + self.assertEqual([e['kind'] for e in self.tree.execution_errors], ['no_trace']) + + def test_missing_trace_with_symbolic_vars_from_earlier_rounds_is_unknown(self): + self.tree.symbolic_vars = {'int_0'} + self.assertEqual(self.step([], received_trace=False), svcomp.Action.REPORTVERDICT) + self.assertEqual(self.driver.state.verdict, svcomp.Verdict.UNKNOWN) + + def test_non_symbolic_program_that_ran_is_non_symbolic(self): + self.assertEqual(self.step([], received_trace=True), svcomp.Action.REPORTVERDICT) + self.assertEqual(self.driver.state.verdict, svcomp.Verdict.NO_SYMBOLIC_VARS) + self.assertEqual(self.tree.execution_errors, []) + + def test_symbolic_program_that_ran_continues(self): + self.tree.symbolic_vars = {'int_0'} + self.assertEqual(self.step([], received_trace=True), svcomp.Action.SYMBOLICNEXT) + + def test_assertion_error_in_output_is_still_a_violation(self): + # E.g. an assert in the static initializer of the main class: program code that failed + # before main, a genuine violation. + output = ['Exception in thread "main" java.lang.ExceptionInInitializerError', + 'Caused by: java.lang.AssertionError'] + self.assertEqual(self.step(output, received_trace=False), svcomp.Action.REPORTVERDICT) + self.assertEqual(self.driver.state.verdict, svcomp.Verdict.VIOLATION) + + +class ReceivedTraceTest(unittest.TestCase): + """run_testcase decides received_trace from the trace count around the target run.""" + + def run_round(self, traces_sent, branch_to_explore=None, proven=False): + traces = {'n': 0} + tree = SimpleNamespace(symbolic_vars=set(), execution_errors=[]) + def record_execution_error(endpoint_id, kind, message): + tree.execution_errors.append({'kind': kind, 'message': message}) + db = SimpleNamespace(trace_count=lambda: traces['n'], clear_witness_data=lambda endpoint_id: None, + record_execution_error=record_execution_error, get_tree=lambda endpoint_id: tree, + get_trace=lambda idx: [], get_witness_data=lambda endpoint_id: None, + is_stack_overflow_trace=lambda endpoint_id, trace_id: False, + get_execution_errors=lambda endpoint_id, kind: [e for e in tree.execution_errors if e.get('kind') == kind], + count_symbolic_vars=lambda endpoint_id: len(tree.symbolic_vars)) + driver = svcomp.SVCompDriver.__new__(svcomp.SVCompDriver) + driver.state = svcomp.State() + driver.state.branch_to_explore = branch_to_explore + driver.verification_category = svcomp.VerificationCategory.VALID_ASSERT_PRP + driver.args = SimpleNamespace(parallel_sa=True) + driver.sa = SimpleNamespace(start=lambda: None) + driver.precheck = SimpleNamespace(proven=proven, reason=None, run=lambda: None, stats=lambda: {}) + driver.budget = ResourceBudget(cgroup_limit=0) + driver.build_command = lambda **kwargs: [] + def run_command(cmd): + traces['n'] += traces_sent + return svcomp.ExecutionStatus.SUCCESS, [] + driver.run_command_with_timeout = run_command + driver.check_divergence = mock.Mock() + with mock.patch.object(svcomp.Database, 'instance', return_value=db), \ + mock.patch.object(svcomp.TimingManager, 'instance'): + verdict = driver.run_testcase(None, None, None, None, None, None) + return verdict, tree, driver + + def test_round_with_trace(self): + verdict, tree, _ = self.run_round(traces_sent=1) + self.assertEqual(verdict, svcomp.Verdict.NO_SYMBOLIC_VARS) + + def test_round_without_trace(self): + verdict, tree, driver = self.run_round(traces_sent=0, branch_to_explore=SimpleNamespace(id=1)) + self.assertEqual(verdict, svcomp.Verdict.UNKNOWN) + self.assertEqual([e['kind'] for e in tree.execution_errors], ['no_trace']) + # The latest trace is one of an earlier round: no divergence check against it + driver.check_divergence.assert_not_called() + + def test_failed_start_is_unknown_despite_a_proof(self): + # The property pre-check (#73) proves nothing about a run that never reached main + verdict, tree, _ = self.run_round(traces_sent=0, proven=True) + self.assertEqual(verdict, svcomp.Verdict.UNKNOWN) + self.assertEqual([e['kind'] for e in tree.execution_errors], ['no_trace']) + + +if __name__ == '__main__': + unittest.main() diff --git a/symbolic-explorer/tests/test_prefer_small.py b/symbolic-explorer/tests/test_prefer_small.py new file mode 100644 index 00000000..62e8ce71 --- /dev/null +++ b/symbolic-explorer/tests/test_prefer_small.py @@ -0,0 +1,71 @@ +""" +Unit tests for preferring small models (SVCompDriver.prefer_small and its helpers). + +Run from symbolic-explorer/ with the harness venv (needs z3): + ../targets/sv-comp/scripts/.venv/bin/python3 -m unittest tests.test_prefer_small -v +""" +import os +import sys +import unittest + +import z3 + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) + +from driver.SVCompDriver import SVCompDriver, LARGE_INPUT +from solver.SolverHandler import Z3Handler + + +def solution(**values): + return {name: {'plain_value': v, 'encoded_value': str(v)} for name, v in values.items()} + + +class PreferSmallTest(unittest.TestCase): + + def test_large_bit_vector_and_integer_inputs_are_large(self): + self.assertTrue(SVCompDriver.is_large(solution(I_0=z3.BitVecVal(2**30, 32)))) + self.assertTrue(SVCompDriver.is_large(solution(I_0=z3.BitVecVal(-2**30, 32)))) + self.assertTrue(SVCompDriver.is_large(solution(J_0=z3.IntVal(LARGE_INPUT + 1)))) + + def test_large_and_infinite_floating_point_inputs_are_large(self): + d = z3.Float64() + self.assertTrue(SVCompDriver.is_large(solution(D_0=z3.FPVal(2.0 ** 67, d)))) + self.assertTrue(SVCompDriver.is_large(solution(D_0=z3.fpMinusInfinity(d)))) + self.assertTrue(SVCompDriver.is_large(solution(F_0=z3.FPVal(-1e6, z3.Float32())))) + + def test_small_and_non_numeric_inputs_are_not(self): + self.assertFalse(SVCompDriver.is_large(solution(I_0=z3.BitVecVal(-LARGE_INPUT, 32)))) + self.assertFalse(SVCompDriver.is_large(solution(D_0=z3.FPVal(-2.07e-317, z3.Float64())))) + self.assertFalse(SVCompDriver.is_large(solution(D_0=z3.fpNaN(z3.Float64())))) + self.assertFalse(SVCompDriver.is_large(solution(S_0=z3.StringVal("x" * 100000)))) + self.assertFalse(SVCompDriver.is_large({})) + + def test_bound_constraints_bound_without_excluding_the_solution(self): + sol = solution(I_0=z3.BitVecVal(2**30, 32)) + s = z3.Solver() + x = z3.BitVec('I_0', 32) + s.add(Z3Handler.bound_constraints(sol, 16)) + s.add(x > 3) + self.assertEqual(s.check(), z3.sat) + self.assertLessEqual(s.model()[x].as_signed_long(), 16) + s.add(x == 2**30) + self.assertEqual(s.check(), z3.unsat) + + def test_bound_constraints_bound_floating_point_inputs(self): + d = z3.Float64() + sol = solution(D_0=z3.FPVal(2.0 ** 67, d)) + x = z3.FP('D_0', d) + s = z3.Solver() + s.add(Z3Handler.bound_constraints(sol, 16)) + s.add(z3.fpGT(x, z3.FPVal(3.0, d))) + self.assertEqual(s.check(), z3.sat) + self.assertTrue(z3.is_true(z3.simplify(z3.fpLEQ(s.model()[x], z3.FPVal(16.0, d))))) + for excluded in (z3.fpPlusInfinity(d), z3.fpNaN(d)): + s2 = z3.Solver() + s2.add(Z3Handler.bound_constraints(sol, 16)) + s2.add(z3.fpIsInf(x) if excluded.isInf() else z3.fpIsNaN(x)) + self.assertEqual(s2.check(), z3.unsat) + + +if __name__ == '__main__': + unittest.main() diff --git a/symbolic-explorer/tests/test_property_precheck.py b/symbolic-explorer/tests/test_property_precheck.py new file mode 100644 index 00000000..d06a296e --- /dev/null +++ b/symbolic-explorer/tests/test_property_precheck.py @@ -0,0 +1,131 @@ +""" +Unit tests for the static property pre-check: how its result is read per property, and the verdict +the driver gives after round 0 when the property is proven. + +It imports the driver, which needs the explorer's dependencies (z3, fastapi, ...) that the system +Python lacks, so run it with the harness venv, from symbolic-explorer/: + ../targets/sv-comp/scripts/.venv/bin/python3 -m unittest tests.test_property_precheck -v +""" + +import json +import os +import subprocess +import sys +import tempfile +import unittest +from types import SimpleNamespace +from unittest import mock + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) + +from driver import SVCompDriver as svcomp +from svcomp import PropertyPreCheck as precheck + +RESULT = {"tool": "cfg-extraction", "version": "v1-0-gabc", "exception_free": True, "reason": "no throw", + "assertion_free": False, "assertion_reason": "Main.main: assert"} + + +def args(**kw): + a = dict(classpath=['/task/common', '/task/Main', '/libs/com.microsoft.z3.jar'], agent='/swat/executor.jar', + java_path='java', no_property_precheck=False, precheck_jar='/sa/cfg-extractor-precheck.jar') + a.update(kw) + return SimpleNamespace(**a) + + +def completed(stdout): + return subprocess.CompletedProcess(args=[], returncode=0, stdout=stdout, stderr='') + + +class PropertyPreCheckTest(unittest.TestCase): + + def run_check(self, prop, stdout=json.dumps(RESULT), **kw): + check = precheck.PropertyPreCheck(args(**kw), prop) + with mock.patch.object(precheck.subprocess, 'run', return_value=completed(stdout)) as run: + proven = check.run() + return check, proven, run + + def test_no_runtime_exception_uses_exception_freedom(self): + check, proven, _ = self.run_check('no-runtime-exception.prp') + self.assertTrue(proven) + self.assertEqual(check.status, 'proven') + self.assertEqual(check.reason, 'no throw') + + def test_valid_assert_uses_assertion_freedom(self): + check, proven, _ = self.run_check('valid-assert.prp') + self.assertFalse(proven) + self.assertEqual(check.status, 'not_proven') + self.assertEqual(check.reason, 'Main.main: assert') + + def test_command_leaves_out_z3(self): + _, _, run = self.run_check('no-runtime-exception.prp') + cmd = run.call_args[0][0] + self.assertEqual(cmd[:5], ['java', '-Xmx2g', '-cp', '/sa/cfg-extractor-precheck.jar', precheck.CHECK_CLASS]) + self.assertEqual(cmd[-2:], ['/task/common', '/task/Main']) + + def test_reported_version_is_recorded(self): + check, _, _ = self.run_check('no-runtime-exception.prp') + self.assertEqual(check.stats()['version'], 'v1-0-gabc') + + def test_without_the_option_the_jar_is_found_next_to_the_explorer(self): + with tempfile.TemporaryDirectory() as d: + jar = os.path.join(d, precheck.JAR_NAME) + open(jar, 'w').close() + with mock.patch.object(precheck, 'DEFAULT_JARS', (os.path.join(d, 'missing.jar'), jar)): + _, proven, run = self.run_check('no-runtime-exception.prp', precheck_jar=None) + self.assertTrue(proven) + self.assertEqual(run.call_args[0][0][3], jar) + + def test_a_missing_jar_proves_nothing(self): + with mock.patch.object(precheck, 'DEFAULT_JARS', ('/nonexistent/' + precheck.JAR_NAME,)): + check, proven, run = self.run_check('no-runtime-exception.prp', precheck_jar=None) + self.assertFalse(proven) + self.assertEqual(check.status, 'missing_jar') + run.assert_not_called() + + def test_other_property_and_flag_disable_it(self): + for check, proven, run in (self.run_check('no-deadlock.prp'), + self.run_check('no-runtime-exception.prp', no_property_precheck=True)): + self.assertFalse(proven) + self.assertEqual(check.status, 'disabled') + run.assert_not_called() + + def test_garbage_output_proves_nothing(self): + check, proven, _ = self.run_check('no-runtime-exception.prp', stdout='Exception in thread "main"') + self.assertFalse(proven) + self.assertEqual(check.status, 'failed') + + def test_timeout_proves_nothing(self): + check = precheck.PropertyPreCheck(args(), 'no-runtime-exception.prp') + with mock.patch.object(precheck.subprocess, 'run', side_effect=subprocess.TimeoutExpired('java', 60)): + self.assertFalse(check.run()) + self.assertEqual(check.status, 'timeout') + + +class VerdictWithProofTest(unittest.TestCase): + + def setUp(self): + self.errors = [] + db = SimpleNamespace(record_execution_error=lambda endpoint, kind, message: self.errors.append(kind)) + patcher = mock.patch.object(svcomp.Database, 'instance', return_value=db) + patcher.start() + self.addCleanup(patcher.stop) + self.driver = svcomp.SVCompDriver.__new__(svcomp.SVCompDriver) + self.driver.state = svcomp.State() + self.driver.precheck = SimpleNamespace(proven=True, reason='no throw') + + def test_safe_when_round_0_would_explore(self): + self.assertEqual(self.driver.verdict_with_proof(svcomp.Action.SYMBOLICNEXT), svcomp.Verdict.SAFE) + self.assertEqual(self.errors, []) + + def test_safe_when_round_0_gave_up(self): + self.driver.state.verdict = svcomp.Verdict.UNKNOWN + self.assertEqual(self.driver.verdict_with_proof(svcomp.Action.REPORTVERDICT), svcomp.Verdict.SAFE) + + def test_violation_contradicts_the_proof(self): + self.driver.state.verdict = svcomp.Verdict.VIOLATION + self.assertEqual(self.driver.verdict_with_proof(svcomp.Action.REPORTVERDICT), svcomp.Verdict.UNKNOWN) + self.assertEqual(self.errors, ['precheck_contradiction']) + + +if __name__ == '__main__': + unittest.main() diff --git a/symbolic-explorer/tests/test_relevance.py b/symbolic-explorer/tests/test_relevance.py new file mode 100644 index 00000000..c9746bf2 --- /dev/null +++ b/symbolic-explorer/tests/test_relevance.py @@ -0,0 +1,318 @@ +""" +Unit tests for the relevance analysis (Relevance.py) and its use in dfs (slicing). + +Graphs are built as the extractor's JSON, with data-flow facts, and loaded through +SAGraph.load_json_graph(); trees are built by hand as in test_sa_walk. + +Run from symbolic-explorer/: + python3 -m unittest tests.test_relevance -v +""" + +import os +import sys +import unittest + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) + +from data.BinaryExecutionTree.Leaf import Leaf +from data.BinaryExecutionTree.Tree import Tree +from strategy.DFS import dfs +from strategy.StrategyService import StrategyService +from tests.test_sa_walk import GraphBuilder, branch_node, keyed, leaf + +ASSERTION_CHAIN = ['java.lang.AssertionError', 'java.lang.Error', 'java.lang.Throwable'] +ISE_CHAIN = ['java.lang.IllegalStateException', 'java.lang.RuntimeException', 'java.lang.Exception', + 'java.lang.Throwable'] + + +class FactGraph(GraphBuilder): + """A GraphBuilder whose nodes carry data-flow facts and a method.""" + + def __init__(self): + super().__init__() + self.metadata['dataflowFacts'] = 1 + + def at(self, id, method='main', dataflow=(), control=(), terminates=False, call=None, **kw): + self.node(id, **kw) + n = self.nodes[-1] + n['metadata'] = f'method: {method}' + if dataflow: + n['dataflow'] = [[list(w), list(r)] for w, r in dataflow] + if control: + n['control'] = list(control) + if terminates: + n['terminates'] = True + if call is not None: + n['call'] = call + return self + + +def example(body=None, extra=None): + """ + The design example: + + val = nondet(); + for (i = 0; i < 100; i++) { if (i < val) print(i); } // h, b, p + assert val != C; // a, ae + + `body` replaces the facts of the print node p, `extra(builder)` adds more nodes and edges. + """ + g = FactGraph() + g.at('v', dataflow=[(['l:val', 'g:input'], ['g:input'])]) + g.at('i0', dataflow=[(['l:i'], [])]) + g.at('h', control=['l:i']) + g.at('b', control=['l:i', 'l:val']) + g.at('p', **(body or {'dataflow': [(['g:out'], ['l:i'])]})) + g.at('inc', dataflow=[(['l:i'], ['l:i'])]) + g.at('a', control=['l:val']) + g.at('ae', assertion=True, dataflow=[(['l:e'], [])]) + g.at('thr', dataflow=[(['g:exc'], ['l:e'])]) + g.at('unc', chain=ASSERTION_CHAIN) + g.at('ret', exit=True) + g.edge('v', 'i0').edge('i0', 'h') + g.branch('h', 'b', 'a') # i >= 100: leave the loop + g.branch('b', 'p', 'inc') # i >= val: skip the print + g.edge('p', 'inc').edge('inc', 'h') + g.branch('a', 'ae', 'ret') # val != C: no assertion error + g.edge('ae', 'thr').edge('thr', 'unc', 'THROW') + if extra is not None: + extra(g) + return g + + +def irrelevant(g): + return {n.id for n in g.nodes.values() if n.is_branch() and n.irrelevant} + + +class RelevanceTest(unittest.TestCase): + + def test_the_loop_of_the_example_is_irrelevant(self): + g = example().load() + self.assertEqual(irrelevant(g), {'h', 'b'}) + self.assertEqual({n.id for n in g.nodes['b'].region}, {'p'}) + self.assertIn('main|l:val', _rel(g)) + self.assertNotIn('main|l:i', _rel(g)) + + def test_writing_what_the_assert_reads_is_relevant(self): + g = example({'dataflow': [(['l:val'], ['l:i'])]}).load() + self.assertEqual(irrelevant(g), set()) + + def test_reading_input_is_relevant(self): + # val is an input, so the input cursor is relevant: reading another input in the region + # shifts which input val gets. + g = example({'dataflow': [(['l:x', 'g:input'], ['g:input'])]}).load() + self.assertEqual(irrelevant(g), set()) + + def test_ending_the_program_is_relevant(self): + g = example({'terminates': True}).load() + self.assertEqual(irrelevant(g), set()) + + def test_an_irrelevant_write_through_a_relevant_branch_is_relevant(self): + # The region writes x, which decides whether the assert is reached. + def extra(g): + g.at('gx', control=['l:x']) + g.edges = [e for e in g.edges if not (e['source'] == 'h' and e['target'] == 'a')] + g.edge('h', 'gx', 'TRUE_BRANCH') + g.branch('gx', 'a', 'ret') + g = example({'dataflow': [(['l:x'], ['l:i'])]}, extra).load() + self.assertEqual(irrelevant(g), set()) + + def test_a_callee_writing_a_relevant_field_is_relevant(self): + # p calls m(), which writes f; the assert's guard reads f. + def extra(g): + g.at('me', method='m', dataflow=[(['f:f:int'], [])]) + g.at('mx', method='m', exit=True) + g.edge('me', 'mx') + g.at('rs', dataflow=[]) + g.edges = [e for e in g.edges if not (e['source'] == 'p')] + g.call('p', 'me', 'rs', callee_exits=['mx']) + g.edge('rs', 'inc') + g.at('rf', dataflow=[(['l:val'], ['f:f:int'])]) + g.edges = [e for e in g.edges if not (e['source'] == 'v')] + g.edge('v', 'rf').edge('rf', 'i0') + g = example({'call': {'args': [], 'base': []}}, extra).load() + self.assertEqual(irrelevant(g), set()) + + def test_a_callee_writing_an_unread_field_is_irrelevant(self): + def extra(g): + g.at('me', method='m', dataflow=[(['f:log:int'], ['f:log:int'])]) + g.at('mx', method='m', exit=True) + g.edge('me', 'mx') + g.at('rs') + g.edges = [e for e in g.edges if not (e['source'] == 'p')] + g.call('p', 'me', 'rs', callee_exits=['mx']) + g.edge('rs', 'inc') + g = example({'call': {'args': [['l:i']], 'base': []}}, extra).load() + self.assertEqual(irrelevant(g), {'h', 'b'}) + + def test_an_exception_a_caller_catches_and_survives_is_relevant(self): + # p calls m(), which may throw an exception that main catches and then goes on. + def extra(g): + g.at('me', method='m', control=['p:0']) + g.at('mt', method='m') + g.at('mu', method='m', chain=ISE_CHAIN) + g.at('mx', method='m', exit=True) + g.branch('me', 'mx', 'mt') + g.edge('mt', 'mu', 'THROW') + g.at('rs') + g.at('hd', dataflow=[(['l:c'], ['g:exc'])]) + g.edges = [e for e in g.edges if not (e['source'] == 'p')] + g.call('p', 'me', 'rs', callee_exits=['mx'], handlers={'java.lang.IllegalStateException': 'hd'}) + g.edge('rs', 'inc').edge('hd', 'a') + g.edge('mu', 'hd', 'EXCEPTIONAL_RETURN') + g = example({'call': {'args': [['l:i']], 'base': []}}, extra).load() + self.assertEqual(irrelevant(g) & {'h', 'b'}, set()) + + def test_an_exception_nobody_catches_is_irrelevant(self): + # As above, but nothing catches it: the program ends with an uncaught exception. + def extra(g): + g.at('me', method='m', control=['p:0']) + g.at('mt', method='m') + g.at('mu', method='m', chain=ISE_CHAIN) + g.at('mx', method='m', exit=True) + g.branch('me', 'mx', 'mt') + g.edge('mt', 'mu', 'THROW') + g.at('rs') + g.edges = [e for e in g.edges if not (e['source'] == 'p')] + g.call('p', 'me', 'rs', callee_exits=['mx']) + g.edge('rs', 'inc') + g = example({'call': {'args': [['l:i']], 'base': []}}, extra).load() + self.assertEqual(irrelevant(g), {'h', 'b', 'me'}) + + def test_what_decides_a_caught_exception_is_relevant(self): + # sa-soundness/npe-catch-local: box = b == 7 ? null : new Box(); try { r = box.v; } + # catch (NullPointerException e) { assert ... }. The dereference is an implicit branch. + g = FactGraph() + g.at('in', dataflow=[(['l:b', 'g:input'], ['g:input'])]) + g.at('t', control=['l:b']) + g.at('nul', dataflow=[(['l:box'], [])]) + g.at('new', dataflow=[(['l:box'], [])]) + g.at('deref', dataflow=[(['l:r'], ['l:box', 'f:v:int'])]) + g.at('hd', dataflow=[(['l:e'], ['g:exc'])]) + g.at('ae', assertion=True) + g.at('ret', exit=True) + g.edge('in', 't') + g.branch('t', 'new', 'nul') + g.edge('nul', 'deref').edge('new', 'deref') + g.edge('deref', 'ret').edge('deref', 'hd', 'EXCEPTION') + g.edge('hd', 'ae').edge('ae', 'ret') + self.assertEqual(irrelevant(g.load()), set()) + + def test_without_facts_nothing_is_irrelevant(self): + g = example() + g.metadata.pop('dataflowFacts') + self.assertEqual(irrelevant(g.load()), set()) + + +def _rel(g): + """The relevant locations of `g`.""" + from data.StaticAnalysisGraph.Relevance import Relevance + r = Relevance(g.nodes.values()) + r.compute() + return r.relevant_locations + + +def link(*nodes): + """Sets the parents of a hand-built path or tree.""" + for n in nodes: + for c in (n.skipped, n.branched): + if c is not None: + c.parent = n + + +class SlicingTest(unittest.TestCase): + + def setUp(self): + self.g = keyed(example().load()) + + def iteration(self, took_print, rest, name): + """h (stay in the loop), then b, printing or not, then `rest` (the next tree node).""" + if took_print: + b = branch_node(f'b{name}', skipped=rest, key='b') + else: + b = branch_node(f'b{name}', branched=rest, key='b') + h = branch_node(f'h{name}', skipped=b, key='h') + b.constraint = {b.trace_id: f'(declare-fun val () Int) (assert (< i{name} val))'} + link(h, b) + return h + + def assert_tail(self, name, end): + a = branch_node(f'a{name}', branched=end, key='a') # val != C: the assert not reached + a.constraint = {a.trace_id: '(declare-fun val () Int) (assert (distinct val 3))'} + h = branch_node(f'hx{name}', branched=a, key='h') + link(h, a) + return h, a + + def test_the_loop_is_not_flipped_and_its_constraints_are_left_out(self): + tail, a = self.assert_tail('', leaf()) + root = self.iteration(False, self.iteration(True, tail, '1'), '0') + tree = Tree() + found = dfs(tree, root, set(), set(), self.g.entry_node) + self.assertEqual([n.id for n in found], ['a']) + # One trace took the whole path: only the b nodes have symbolic constraints. + n = a + while n is not None: + n.constraint = {'T': c for c in n.constraint.values()} + n.trace_id = 'T' + n = n.parent + dropped = [] + self.assertEqual(StrategyService.collect_path_constrains(a, dropped), []) + self.assertEqual({d.id for d in dropped}, {'b0', 'b1'}) + + def test_paths_that_differ_only_in_irrelevant_decisions_are_one(self): + # Two paths: print in iteration 0 or not, then leave; each reaches the assert unflipped. + tail1, a1 = self.assert_tail('1', leaf()) + tail2, a2 = self.assert_tail('2', leaf()) + b = branch_node('b0', skipped=tail1, branched=tail2, key='b') + root = branch_node('h0', skipped=b, key='h') + link(root, b) + found = dfs(Tree(), root, set(), set(), self.g.entry_node) + self.assertEqual([n.id for n in found], ['a1']) + # Once one of them was flipped, the other is covered. + a1.skipped = leaf() + found = dfs(Tree(), root, set(), set(), self.g.entry_node) + self.assertEqual(found, []) + + def test_a_branch_the_graph_has_no_node_for_poisons_the_region(self): + # A callback runs in the print and branches: the region of b is not what the graph says. + tail, a = self.assert_tail('', leaf()) + cb = branch_node('cb', skipped=tail, key=None, callback=True) + b = branch_node('b0', skipped=cb, key='b') + root = branch_node('h0', skipped=b, key='h') + link(root, b, cb) + tree = Tree() + found = dfs(tree, root, set(), set(), self.g.entry_node) + self.assertTrue(b.poisoned) + self.assertTrue(tree.walk['poisoned']) + found = dfs(tree, root, set(), set(), self.g.entry_node) # as select_branch does + self.assertEqual({n.id for n in found}, {'h0', 'b0', 'a', 'cb'}) + self.assertFalse(b.sliced) + + def test_an_abnormal_end_in_the_region_poisons_it(self): + end = leaf() + end.abnormal = True + b = branch_node('b0', skipped=end, key='b') + root = branch_node('h0', skipped=b, key='h') + link(root, b) + b.abnormal_below = root.abnormal_below = True + tree = Tree() + dfs(tree, root, set(), set(), self.g.entry_node) + self.assertTrue(b.poisoned) + self.assertTrue(root.poisoned) + + def test_a_closed_region_is_not_poisoned(self): + # The callback comes after the loop, outside every region. + cb = branch_node('cb', skipped=leaf(), key=None, callback=True) + a = branch_node('a', branched=cb, key='a') + hx = branch_node('hx', branched=a, key='h') + b = branch_node('b0', skipped=hx, key='b') + root = branch_node('h0', skipped=b, key='h') + link(root, b, hx, a, cb) + tree = Tree() + dfs(tree, root, set(), set(), self.g.entry_node) + self.assertFalse(b.poisoned) + self.assertFalse(root.poisoned) + + +if __name__ == '__main__': + unittest.main() diff --git a/symbolic-explorer/tests/test_resource_budget.py b/symbolic-explorer/tests/test_resource_budget.py new file mode 100644 index 00000000..579afbac --- /dev/null +++ b/symbolic-explorer/tests/test_resource_budget.py @@ -0,0 +1,245 @@ +""" +Unit tests for the SV-COMP resource budget (svcomp/ResourceBudget.py) and how SVCompDriver acts on +it: the derived limits, the monitor stopping the executor or a Z3 query, and the verdicts after a run +the budget stopped. + +It imports the driver, which needs the explorer's dependencies (z3, fastapi, ...) that the system +Python lacks, so run it with the harness venv, from symbolic-explorer/: + ../targets/sv-comp/scripts/.venv/bin/python3 -m unittest tests.test_resource_budget -v +""" + +import os +import subprocess +import sys +import tempfile +import time +import unittest +from types import SimpleNamespace +from unittest import mock + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) + +from driver import SVCompDriver as svcomp +from svcomp import ResourceBudget as rb +from svcomp.ResourceBudget import ResourceBudget, MB, MIB + + +class CgroupLimitTest(unittest.TestCase): + + def fs(self, cgroup_line, limits): + root = tempfile.mkdtemp() + os.makedirs(f'{root}/proc/self') + with open(f'{root}/proc/self/cgroup', 'w') as f: + f.write(cgroup_line + '\n') + for path, value in limits.items(): + os.makedirs(f'{root}/cg{path}', exist_ok=True) + with open(f'{root}/cg{path}/{"memory.limit_in_bytes" if path.startswith("/memory") else "memory.max"}', 'w') as f: + f.write(value + '\n') + return dict(proc_root=f'{root}/proc', cgroup_root=f'{root}/cg') + + def test_v2_takes_the_tightest_ancestor(self): + roots = self.fs('0::/bench/run/tool', {'/bench': '20000000000', '/bench/run': '15000000000', '/bench/run/tool': 'max'}) + self.assertEqual(rb.cgroup_memory_limit(**roots), 15_000_000_000) + + def test_v2_without_limit(self): + roots = self.fs('0::/user.slice/session.scope', {'/user.slice': 'max', '/user.slice/session.scope': 'max'}) + self.assertIsNone(rb.cgroup_memory_limit(**roots)) + + def test_v1_ignores_the_no_limit_value(self): + roots = self.fs('5:memory:/run', {'/memory/run': str(2**63 - 4096), '/memory': '8000000000'}) + self.assertEqual(rb.cgroup_memory_limit(**roots), 8_000_000_000) + + + def test_cpu_time_of_the_limiting_cgroup(self): + roots = self.fs('0::/bench/run', {'/bench': 'max', '/bench/run': '15000000000'}) + limit, directory = rb.limiting_cgroup(**roots) + self.assertEqual(limit, 15_000_000_000) + with open(f'{directory}/cpu.stat', 'w') as f: + f.write('usage_usec 12500000\nuser_usec 10000000\n') + self.assertEqual(rb.cgroup_cpu_time(directory), 12.5) + self.assertIsNone(rb.cgroup_cpu_time(None)) + + +class LimitsTest(unittest.TestCase): + + def test_competition_defaults(self): + b = ResourceBudget(cgroup_limit=0) + self.assertEqual(b.memory_limit, 15_000 * MB) + self.assertEqual(b.memory_threshold, 13_000 * MB) + # half of what is left after the explorer's share + self.assertEqual(b.heap_mib, (13_000 - 1_500) * MB // 2 // MIB) + self.assertEqual(b.cpu_deadline, 880) + + def test_lower_cgroup_limit_wins(self): + b = ResourceBudget(cgroup_limit=8_000 * MB) + self.assertEqual(b.memory_limit, 8_000 * MB) + self.assertLess(b.heap_mib * MIB + 1_500 * MB, b.memory_threshold) + + def test_cpu_used_before_counts(self): + b = ResourceBudget(cgroup_limit=0, cpu_used_s=12.5) + self.assertGreaterEqual(b.usage()[1], 12.5) + + def test_usage_includes_children(self): + b = ResourceBudget(cgroup_limit=0) + own = b.usage()[0] + with subprocess.Popen(['sleep', '5']) as p: + time.sleep(0.2) # until it is running + b._last_scan = 0.0 # due for a full scan + self.assertGreater(b.usage()[0], own) + p.kill() + + def test_usage_includes_the_executor_between_scans(self): + b = ResourceBudget(cgroup_limit=0) + own = b.usage()[0] + with subprocess.Popen(['sleep', '5']) as p, b.executor(p): + time.sleep(0.2) + self.assertGreater(b.usage()[0], own) + p.kill() + + +class MonitorTest(unittest.TestCase): + + def budget(self, rss, cpu): + b = ResourceBudget(cgroup_limit=0) + b.usage = lambda: (rss, cpu) + b.on_emergency = mock.Mock() + return b + + def test_cpu_deadline_kills_the_executor(self): + b = self.budget(100 * MB, 885) + with subprocess.Popen(['sleep', '30']) as p, b.executor(p) as stopped_by: + b.check() + self.assertEqual(p.wait(timeout=5), -9) # SIGKILL, so no shutdown hook sends a trace + self.assertEqual(stopped_by(), 'cpu') + self.assertTrue(b.cpu_exceeded.is_set()) + b.on_emergency.assert_not_called() + + def test_below_the_limits_nothing_happens(self): + b = self.budget(12_000 * MB, 800) + with subprocess.Popen(['sleep', '30']) as p, b.executor(p) as stopped_by: + b.check() + self.assertIsNone(p.poll()) + p.kill() + self.assertIsNone(stopped_by()) + self.assertFalse(b.cpu_exceeded.is_set()) + + def test_memory_interrupts_a_query(self): + b = self.budget(13_100 * MB, 10) + ctx = mock.Mock() + with b.solver(ctx) as interrupted_by: + b.check() + b.check() # still over: interrupted again, but counted once + self.assertEqual(ctx.interrupt.call_count, 2) + self.assertEqual(b.memory_hits, 1) + self.assertEqual(interrupted_by(), 'memory') + self.assertFalse(b.cpu_exceeded.is_set()) + + def test_query_that_does_not_stop_ends_the_run(self): + b = self.budget(13_100 * MB, 10) + with b.solver(mock.Mock()): + b.check() + b.check() + b.on_emergency.assert_not_called() + b._solver_interrupted_at -= rb.INTERRUPT_GRACE_S + 0.1 + b.check() + b.on_emergency.assert_called_once() + + def test_explorer_alone_over_the_limit_ends_the_run(self): + b = self.budget(14_100 * MB, 10) + b.check() + b.on_emergency.assert_called_once() + + def test_explorer_slightly_over_the_threshold_is_tolerated(self): + b = self.budget(13_100 * MB, 10) + b.check() + b.on_emergency.assert_not_called() + + def test_main_thread_stuck_after_the_deadline_ends_the_run(self): + b = self.budget(100 * MB, 890) + b.check() + b.on_emergency.assert_not_called() + b.cpu_exceeded_at -= rb.GRACE_S + 1 + b.check() + b.on_emergency.assert_called_once() + + +class DriverTest(unittest.TestCase): + + def setUp(self): + self.tree = SimpleNamespace(execution_errors=[], symbolic_vars={'I_0'}) + def record_execution_error(endpoint_id, kind, message): + self.tree.execution_errors.append({'kind': kind, 'message': message}) + db = SimpleNamespace(record_execution_error=record_execution_error, get_tree=lambda e: self.tree, + record_uncaught_exception=lambda e: None, + get_witness_data=lambda e: None, is_stack_overflow_trace=lambda e, t: False, + get_execution_errors=lambda e, kind: [x for x in self.tree.execution_errors if x.get('kind') == kind], + count_symbolic_vars=lambda e: len(self.tree.symbolic_vars)) + patcher = mock.patch.object(svcomp.Database, 'instance', return_value=db) + patcher.start() + self.addCleanup(patcher.stop) + d = svcomp.SVCompDriver.__new__(svcomp.SVCompDriver) + d.state = svcomp.State() + d.verification_category = svcomp.VerificationCategory.VALID_ASSERT_PRP + d.round_idx = 3 + d.stopped_by = None + d.budget = ResourceBudget(cgroup_limit=0) + self.driver = d + + def kinds(self): + return [e['kind'] for e in self.tree.execution_errors] + + def test_memory_stop_goes_on_exploring(self): + self.driver.stopped_by = 'memory' + step = self.driver.determine_next_step(svcomp.ExecutionStatus.RESOURCE_LIMIT, ['some output']) + self.assertEqual(step, svcomp.Action.SYMBOLICNEXT) + self.assertEqual(self.kinds(), ['resource_limit']) # rules out SAFE, see CUT_SHORT_ERRORS + + def test_memory_stop_of_the_first_run_is_unknown(self): + self.tree.symbolic_vars = set() # no trace yet: not "no symbolic inputs" + self.driver.stopped_by = 'memory' + step = self.driver.determine_next_step(svcomp.ExecutionStatus.RESOURCE_LIMIT, []) + self.assertEqual(step, svcomp.Action.REPORTVERDICT) + self.assertEqual(self.driver.state.verdict, svcomp.Verdict.UNKNOWN) + + def test_cpu_stop_is_unknown(self): + self.driver.stopped_by = 'cpu' + step = self.driver.determine_next_step(svcomp.ExecutionStatus.RESOURCE_LIMIT, []) + self.assertEqual(step, svcomp.Action.REPORTVERDICT) + self.assertEqual(self.driver.state.verdict, svcomp.Verdict.UNKNOWN) + + def test_violation_printed_before_the_stop_is_reported(self): + self.driver.stopped_by = 'cpu' + step = self.driver.determine_next_step(svcomp.ExecutionStatus.RESOURCE_LIMIT, + ['Exception in thread "main" java.lang.AssertionError']) + self.assertEqual(step, svcomp.Action.REPORTVERDICT) + self.assertEqual(self.driver.state.verdict, svcomp.Verdict.VIOLATION) + + def test_out_of_memory_error_is_recorded(self): + self.driver.verification_category = svcomp.VerificationCategory.NO_RUNTIME_EXCEPTION_PRP + out = ['java.lang.OutOfMemoryError: Requested array size exceeds VM limit', + 'Exception in thread "main" java.lang.OutOfMemoryError: Requested array size exceeds VM limit'] + step = self.driver.determine_next_step(svcomp.ExecutionStatus.SUCCESS, out) + self.assertEqual(step, svcomp.Action.SYMBOLICNEXT) + self.assertEqual(self.kinds(), ['out_of_memory']) + + def test_heap_and_jvm_options_in_the_command(self): + self.driver.symbolicStorage = SimpleNamespace(input_type=None) + self.driver.args = SimpleNamespace() + cmd = self.driver.build_command('agent.jar', 'sv-comp.cfg', 'z3', 8000, ['cp'], java_path='java') + self.assertIn(f'-Xmx{self.driver.budget.heap_mib}m', cmd) + self.assertIn('-XX:TieredStopAtLevel=1', cmd) + + def test_no_query_after_the_cpu_deadline(self): + self.driver.args = SimpleNamespace(solver_rlimit=0) + self.driver.nr_solver_calls = self.driver.solver_limit_hits = 0 + self.driver.rlimit_level = {} + self.driver.budget.cpu_exceeded.set() + with mock.patch.object(svcomp.StrategyService, 'solve_branch') as solve: + found, _, gave_up = self.driver.solve_candidates([SimpleNamespace(gid=1, id=1, choice=False)]) + solve.assert_not_called() + self.assertIsNone(found) + self.assertTrue(gave_up) # so the verdict is not SAFE + + +if __name__ == '__main__': + unittest.main() diff --git a/symbolic-explorer/tests/test_round_overhead.py b/symbolic-explorer/tests/test_round_overhead.py new file mode 100644 index 00000000..29f30002 --- /dev/null +++ b/symbolic-explorer/tests/test_round_overhead.py @@ -0,0 +1,122 @@ +""" +Unit tests for the per-round bookkeeping of the explorer: branch selection walks the Database's +tree without copying it, and the answers it reuses across rounds are the ones a fresh walk gives. + +Run with the harness venv, from symbolic-explorer/: + ../targets/sv-comp/scripts/.venv/bin/python3 -m unittest tests.test_round_overhead -v +""" + +import copy +import os +import sys +import unittest +from types import SimpleNamespace +from unittest import mock + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) + +from data.Database import Database +from data.trace.Branch import Branch +from data.trace.Input import Input +from driver import SVCompDriver as svcomp +from strategy.StrategyService import StrategyService + +DECL = '(declare-fun I_0 () (_ BitVec 32))\n' + + +def branch(id, trace_id, taken, constraint='(assert (bvsgt I_0 #x00000000))'): + return Branch(id, trace_id, taken, DECL + constraint) + + +class SelectBranchTest(unittest.TestCase): + + def setUp(self): + Database.instance().reset() + self.inputs = [Input('I_0', '0', 'int', DECL + '(assert true)', DECL + '(assert true)')] + + def add(self, trace_id, trace): + Database.instance().add_trace(0, trace_id, trace, self.inputs, [], False, False) + + def test_candidates_are_the_open_sides_in_dfs_order(self): + self.add('t1', [branch(1, 't1', True), branch(2, 't1', False), branch(3, 't1', True)]) + self.add('t2', [branch(1, 't2', True), branch(2, 't2', True)]) + nodes = StrategyService.select_branch(endpoint_id=0) + self.assertEqual([n.id for n in nodes], [1, 3]) # 2 has both sides now + + def test_solved_and_unsat_branches_are_left_out(self): + self.add('t1', [branch(1, 't1', True), branch(2, 't1', False)]) + first, second = StrategyService.select_branch(endpoint_id=0) + Database.instance().add_unsat_branch(first.gid) + Database.instance().add_solution(second.gid, {}, self.inputs, 0) + self.assertEqual(StrategyService.select_branch(endpoint_id=0), []) + + def test_the_tree_is_not_copied(self): + self.add('t1', [branch(1, 't1', True)]) + with mock.patch.object(copy, 'deepcopy', side_effect=AssertionError('copied')): + nodes = StrategyService.select_branch(endpoint_id=0) + self.assertEqual([n.id for n in nodes], [1]) + + def test_symbolic_check_is_remembered_per_node(self): + self.add('t1', [branch(1, 't1', True), branch(2, 't1', False, '(assert (= #x00000001 #x00000001))')]) + nodes = StrategyService.select_branch(endpoint_id=0) + first = [StrategyService.is_symbolic_branch(n) for n in nodes] + self.assertEqual(first, [True, False]) + with mock.patch('solver.ConstraintCache.get_constraint_cache', side_effect=AssertionError('asked again')): + self.assertEqual([StrategyService.is_symbolic_branch(n) for n in nodes], first) + + +class BranchToExploreTest(unittest.TestCase): + """The driver keeps the target as it was when it was solved: the tree's node gets its other + side as soon as the next trace arrives, and check_divergence must not take that as taken.""" + + def test_target_keeps_its_open_side(self): + Database.instance().reset() + inputs = [Input('I_0', '0', 'int', DECL + '(assert true)', DECL + '(assert true)')] + Database.instance().add_trace(0, 't1', [branch(1, 't1', False)], inputs, [], False, False) + node, = StrategyService.select_branch(endpoint_id=0) + driver = svcomp.SVCompDriver.__new__(svcomp.SVCompDriver) + driver.state = svcomp.State() + driver.symbolicStorage = svcomp.SymbolicStorage() + driver.pending_projected = None + with mock.patch.object(StrategyService, 'select_branch', return_value=[node]), \ + mock.patch.object(svcomp.SVCompDriver, 'solve_candidates', return_value=(node, {}, False)): + driver.sa_graph = SimpleNamespace(entry_node=None) + driver.sa = SimpleNamespace(take_graph=lambda: None) + driver.args = SimpleNamespace(sa_rank_distance=False) + driver.retrieve_solution() + # The next run takes the branched side, as solved for. + Database.instance().add_trace(0, 't2', [branch(1, 't2', True)], inputs, [], False, False) + self.assertIsNotNone(node.branched) + with self.assertNoLogs(svcomp.logger, level='ERROR'): + driver.check_divergence(Database.instance().get_trace(-1)) + + +class ExtractVariablesTest(unittest.TestCase): + """A formula is a DAG: with shared subterms it is exponentially larger as a tree, and it can + be deeper than the recursion limit.""" + + def test_shared_subterms_are_visited_once(self): + from z3 import BitVec, Context + from solver.ConstraintManager import ConstraintManager + ctx = Context() + x, y = BitVec('x', 64, ctx), BitVec('y', 64, ctx) + e = x + for _ in range(200): # 2^200 paths through the DAG + e = e + e + mgr = ConstraintManager(shared_context=ctx) + self.assertEqual(mgr._extract_variables(e == y), {'x', 'y'}) + + def test_deep_terms(self): + from z3 import BitVec, Context + from solver.ConstraintManager import ConstraintManager + ctx = Context() + x = BitVec('x', 32, ctx) + e = x + for i in range(5000): + e = e - (i % 7) + mgr = ConstraintManager(shared_context=ctx) + self.assertEqual(mgr._extract_variables(e == 0), {'x'}) + + +if __name__ == '__main__': + unittest.main() diff --git a/symbolic-explorer/tests/test_sa_guards.py b/symbolic-explorer/tests/test_sa_guards.py new file mode 100644 index 00000000..43ef2abd --- /dev/null +++ b/symbolic-explorer/tests/test_sa_guards.py @@ -0,0 +1,143 @@ +""" +Unit tests for when the static pre-analysis may prune at all: only under valid-assert, and only for +programs that run no application code outside main's call tree (other threads, shutdown hooks, +uncaught-exception handlers). + +Run from symbolic-explorer/: + python3 -m unittest tests.test_sa_guards -v +""" + +import os +import sys +import tempfile +import unittest +from types import SimpleNamespace + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) + +from data.StaticAnalysisGraph.SAGraph import is_interesting +from svcomp.StaticPreAnalysis import StaticPreAnalysis, SAStatus +from tests.test_sa_walk import GraphBuilder + +HOOK = "virtualinvoke $r1.($r2)" + + +def hook_graph(hook_statement=HOOK, expanded=False, callbacks=True): + """ + main: hk (a library call); if (x > 5) flag = 1; return -- the shutdown hook asserts flag != 1. + The extractor marks the library call an assertion point, as a callback reaches an assert. + + e0 -> hk -> b: FALSE -> s (flag = 1) -> x (exit), TRUE -> x + With `expanded`, hk is an application method the graph calls into instead (callee k0 -> kx). + """ + g = (GraphBuilder().node('e0').node('hk', assertion=callbacks).nodes_('r', 'b', 's').node('x', exit=True) + .edge('e0', 'hk').edge('r', 'b').branch('b', 's', 'x').edge('s', 'x')) + g.nodes[1]['statement'] = hook_statement + if expanded: + g.node('k0').node('kx', exit=True).edge('k0', 'kx').call('hk', 'k0', 'r', ['kx']) + else: + g.edge('hk', 'r') + if callbacks: + g.metadata['callbacksReachingAssert'] = [''] + return g + + +class AfterMainTest(unittest.TestCase): + + def test_leaving_main_is_interesting_when_a_callback_reaches_an_assert(self): + # Both sides of b only lead to main's exit; the hook runs after it. + n = hook_graph().load().nodes + self.assertTrue(is_interesting(n['b'].next_fallthrough, None)) + self.assertTrue(is_interesting(n['b'].next_branched, None)) + + def test_leaving_main_is_not_interesting_without_such_a_callback(self): + n = hook_graph(callbacks=False).load().nodes + self.assertFalse(is_interesting(n['b'].next_fallthrough, None)) + self.assertFalse(is_interesting(n['b'].next_branched, None)) + + +class AsyncEntryTest(unittest.TestCase): + + def sites(self, statement, expanded=False): + return hook_graph(statement, expanded).load().async_entry_sites() + + def test_library_calls_that_run_code_elsewhere(self): + for statement in [ + HOOK, + "virtualinvoke $r1.()", + "virtualinvoke $r1.()", # a Thread subclass + "interfaceinvoke $r1.($r2)", + "staticinvoke ($r1)", + "interfaceinvoke $r1.($r2)", + "staticinvoke ($r1)", + "virtualinvoke $r1.($r2, 1L)", + ]: + self.assertEqual(len(self.sites(statement)), 1, statement) + + def test_other_calls(self): + for statement in [ + "virtualinvoke $r1.()", + "virtualinvoke $r1.()", + "virtualinvoke $r1.($r2)", + "$r1 = new java.lang.Thread", + ]: + self.assertEqual(self.sites(statement), [], statement) + + def test_application_methods_of_the_same_name_do_not_count(self): + self.assertEqual(self.sites("virtualinvoke $r1.()", expanded=True), []) + + +class PropertyGuardTest(unittest.TestCase): + + def run_sa(self, prop, graph=None): + with tempfile.TemporaryDirectory() as d: + path = os.path.join(d, 'graph.json') + (graph or hook_graph(statement_ok())).save(path) + args = SimpleNamespace(sa_file=path, sa_path=None, property=prop, parallel_sa=False, + logdir=d, target='Main') + sa = StaticPreAnalysis(args) + sa.start() + sa.wait() + return sa, sa.take_graph() + + def test_valid_assert_loads_the_graph(self): + sa, graph = self.run_sa('valid-assert.prp') + self.assertEqual(sa.status, SAStatus.LOADED) + self.assertIsNotNone(graph) + + def test_no_runtime_exception_uses_its_own_target_points(self): + g = hook_graph(statement_ok()) + assertion = g.asserts[0] + site = next(n['id'] for n in g.nodes if n['id'] != assertion) + g.metadata['runtimeExceptionPointIds'] = [site] + sa, graph = self.run_sa('no-runtime-exception.prp', g) + self.assertEqual(sa.status, SAStatus.LOADED) + self.assertTrue(graph.nodes[site].isTarget) + self.assertFalse(graph.nodes[assertion].isTarget) + + def test_no_runtime_exception_needs_a_graph_with_its_target_points(self): + sa, graph = self.run_sa('no-runtime-exception.prp') # an older extractor's graph + self.assertEqual(sa.status, SAStatus.FAILED) + self.assertIsNone(graph) + + def test_other_properties_load_nothing(self): + for prop in ['no-deadlock.prp', None]: + sa, graph = self.run_sa(prop) + self.assertEqual(sa.status, SAStatus.DISABLED, prop) + self.assertIsNone(graph) + self.assertFalse(sa.stats(None)['enabled']) + self.assertTrue(sa.stats(None)['requested']) + + def test_graph_with_code_outside_mains_call_tree_is_not_used(self): + sa, graph = self.run_sa('valid-assert.prp', hook_graph()) + self.assertEqual(sa.status, SAStatus.REJECTED) + self.assertIsNone(graph) + self.assertIn('addShutdownHook', sa.stats(None)['rejected_reason']) + + +def statement_ok(): + return "virtualinvoke $r1.($r2)" + + +if __name__ == '__main__': + unittest.main() diff --git a/symbolic-explorer/tests/test_sa_walk.py b/symbolic-explorer/tests/test_sa_walk.py new file mode 100644 index 00000000..02dd2173 --- /dev/null +++ b/symbolic-explorer/tests/test_sa_walk.py @@ -0,0 +1,926 @@ +""" +Unit tests for the static-analysis graph walk that DFS runs in lockstep with the execution tree. + +Graphs are built as the extractor's JSON and loaded through SAGraph.load_json_graph(), so the +loader's invariant checks run too. Execution trees are built by hand: `dfs` only descends into real +`Node` instances, so they are created with Node.__new__ and their fields set directly. + +Run from symbolic-explorer/: + python3 -m unittest tests.test_sa_walk -v +""" + +import json +import os +import sys +import tempfile +import unittest + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) + +from data.BinaryExecutionTree.Node import Node +from data.BinaryExecutionTree.Leaf import Leaf +from data.BinaryExecutionTree.Tree import Tree +from data.StaticAnalysisGraph import SAGraph as sagraph +from data.StaticAnalysisGraph.SAGraph import SAGraph, SANode +from strategy.DFS import dfs, describe_walk + +AIOOBE_CHAIN = ['java.lang.ArrayIndexOutOfBoundsException', 'java.lang.IndexOutOfBoundsException', + 'java.lang.RuntimeException', 'java.lang.Exception', 'java.lang.Throwable'] +ARITH_CHAIN = ['java.lang.ArithmeticException', 'java.lang.RuntimeException', 'java.lang.Exception', + 'java.lang.Throwable'] + + +class GraphBuilder: + """Builds a graph in the extractor's JSON format.""" + + def __init__(self): + self.nodes = [] + self.edges = [] + self.exits = [] + self.asserts = [] + self.entry = None + self.metadata = {} # further graph metadata, e.g. callbacksReachingAssert + + def node(self, id, exit=False, incomplete=False, chain=None, assertion=False): + n = {'id': id, 'statement': id, 'stmtType': 'X', 'lineNumber': -1} + if incomplete: + n['isIncomplete'] = True + self.asserts.append(id) + if chain is not None: + n['exceptionType'] = chain[0] + n['exceptionTypeChain'] = chain + if exit: + self.exits.append(id) + if assertion: + self.asserts.append(id) + if self.entry is None: + self.entry = id + self.nodes.append(n) + return self + + def nodes_(self, *ids): + for id in ids: + self.node(id) + return self + + def edge(self, source, target, type='NORMAL', **extra): + e = {'source': source, 'target': target, 'type': type} + e.update(extra) + self.edges.append(e) + return self + + def branch(self, source, false_target, true_target, phantom=False): + prefix = 'PHANTOM_' if phantom else '' + self.edge(source, false_target, prefix + 'FALSE_BRANCH') + self.edge(source, true_target, prefix + 'TRUE_BRANCH') + return self + + def call(self, site, callee_entry, return_site, callee_exits=(), handlers=None): + self.edge(site, callee_entry, 'CALL', returnSite=return_site, handlers=handlers or {}) + for exit in callee_exits: + self.edge(exit, return_site, 'RETURN') + return self + + def load(self) -> SAGraph: + graph = {'nodes': self.nodes, 'edges': self.edges, 'entryNodeId': self.entry, + 'exitNodeIds': self.exits, + 'metadata': {'assertionPointIds': self.asserts, + 'isIncomplete': any(n.get('isIncomplete') for n in self.nodes), + **self.metadata}} + with tempfile.NamedTemporaryFile('w', suffix='.json', delete=False) as f: + json.dump(graph, f) + path = f.name + try: + g = SAGraph() + g.load_json_graph(path) + return g + finally: + os.unlink(path) + + def save(self, path: str): + """Writes the graph's JSON to `path`, as the extractor would.""" + graph = {'nodes': self.nodes, 'edges': self.edges, 'entryNodeId': self.entry, + 'exitNodeIds': self.exits, + 'metadata': {'assertionPointIds': self.asserts, **self.metadata}} + with open(path, 'w') as f: + json.dump(graph, f) + + +def branch_node(name, skipped=None, branched=None, key=None, pops=0, pushes=(), callback=False, choice=False): + """A tree node for one branch event; `None` children are unexplored sides.""" + n = Node.__new__(Node) + n.parent = None + n.id = name + n.trace_id = name + n.gid = name + n.kind = 'Branch' + n.concrete_only = False + n.inst = None + n.key = key + n.pops = pops + n.pushes = pushes + n.callback = callback + n.choice = choice + n.unmodelled_exception = False + n.constraint = {} + n.skipped = skipped + n.branched = branched + return n + + +def leaf(): + lf = Leaf.__new__(Leaf) + lf.parent = None + lf.inputs = [] + return lf + + +def keyed(g: SAGraph) -> SAGraph: + """Gives every branch and call site of `g` its node id as key, so a tree can name them.""" + index = g.entry_node.graph_index + for n in g.nodes.values(): + if n.is_branch() or n.is_call_site(): + n.key = n.id + index.keys[n.id] = n + return g + + +def candidates(tree_root, sa_graph): + """The unexplored branches dfs considers worth solving, by tree node name.""" + return {n.id for n in dfs(None, tree_root, set(), set(), sa_graph.entry_node)} + + +def shared_callee_graph(): + """ + main: c1 = check(); c2 = check(); if (..) assert -- check() has one branch and two exits. + + e0 -> c1 -CALL-> k0 ... RETURN -> r1 -> c2 -CALL-> k0 ... RETURN -> r2 -> bm + bm: FALSE -> a (assert), TRUE -> x (exit) + check: k0 -> kb: FALSE -> kx1 (exit), TRUE -> kx2 (exit) + """ + return (GraphBuilder() + .nodes_('e0', 'c1', 'r1', 'c2', 'r2', 'bm').node('a', assertion=True).node('x', exit=True) + .node('k0').node('kb').node('kx1', exit=True).node('kx2', exit=True) + .edge('e0', 'c1') + .call('c1', 'k0', 'r1', ['kx1', 'kx2']) + .edge('r1', 'c2') + .call('c2', 'k0', 'r2', ['kx1', 'kx2']) + .edge('r2', 'bm') + .branch('bm', 'a', 'x') + .edge('a', 'x') + .edge('k0', 'kb') + .branch('kb', 'kx1', 'kx2')) + + +class LoaderTest(unittest.TestCase): + + def test_return_fan_out_loads(self): + # One exit returning to two call sites: RETURN used to share the one fall-through slot. + g = shared_callee_graph().load() + self.assertIn(g.nodes['kx1'], g.nodes['r1'].prev) # RETURN edges are kept for the distances + self.assertIn(g.nodes['kx1'], g.nodes['r2'].prev) + self.assertIsNone(g.nodes['kx1'].next_fallthrough) + self.assertIs(g.nodes['c1'].return_site, g.nodes['r1']) + self.assertTrue(g.nodes['kx1'].isExit) + + def test_unknown_edge_type_is_rejected(self): + with self.assertRaises(ValueError): + GraphBuilder().nodes_('a', 'b').edge('a', 'b', 'TELEPORT').load() + + +class WalkTest(unittest.TestCase): + + def test_branch_free_call_is_crossed_in_one_walk(self): + g = (GraphBuilder().nodes_('e0', 'c1', 'r1', 'bm', 'f0').node('x', exit=True).node('fx', exit=True) + .edge('e0', 'c1').call('c1', 'f0', 'r1', ['fx']).edge('f0', 'fx') + .edge('r1', 'bm').branch('bm', 'x', 'x').load()) + node, stack = g.entry_node.walk_till_branch(None) + self.assertEqual(node.id, 'bm') + self.assertIsNone(stack) + + def test_return_goes_to_the_calling_site(self): + g = shared_callee_graph().load() + kb, stack = g.nodes['c2'].walk_till_branch(None) + self.assertEqual(kb.id, 'kb') + node, stack = kb.next_branched.walk_till_branch(stack) + self.assertEqual(node.id, 'bm') + self.assertIsNone(stack) + + def test_exit_with_empty_stack_gives_up(self): + g = shared_callee_graph().load() + self.assertIsNone(g.nodes['kx1'].walk_till_branch(None)) + + def test_incomplete_node_gives_up(self): + g = (GraphBuilder().nodes_('e0').node('i', incomplete=True).node('b').node('x', exit=True) + .edge('e0', 'i').edge('i', 'b').branch('b', 'x', 'x').load()) + self.assertIsNone(g.entry_node.walk_till_branch(None)) + + def test_branch_free_unbounded_recursion_terminates(self): + g = (GraphBuilder().nodes_('e0', 'c', 'r').node('x', exit=True) + .edge('e0', 'c').call('c', 'e0', 'r', ['x']).edge('r', 'x').load()) + self.assertIsNone(g.entry_node.walk_till_branch(None)) + + def test_branch_free_infinite_loop_terminates(self): + g = GraphBuilder().nodes_('a', 'b').edge('a', 'b').edge('b', 'a').load() + self.assertIsNone(g.entry_node.walk_till_branch(None)) + + +def inter_method_catch_graph(): + """ + InterMethodCatch: main calls test() inside try/catch(Exception); test()'s array store guard + fails into uncaught[AIOOBE], which main's handler catches. + + main: e0 -> c -CALL-> t0 ; r (return site) -> mx (exit) + handler h -> hb: FALSE -> a (assert), TRUE -> mx + test: t0 -> g: PHANTOM_TRUE -> tx (exit), PHANTOM_FALSE -> u (uncaught[AIOOBE]) + """ + return (GraphBuilder() + .nodes_('e0', 'c', 'r', 'h', 'hb').node('a', assertion=True).node('mx', exit=True) + .node('t0').node('g').node('tx', exit=True).node('u', chain=AIOOBE_CHAIN) + .edge('e0', 'c') + .call('c', 't0', 'r', ['tx'], handlers={'java.lang.Exception': 'h'}) + .edge('r', 'mx') + .edge('h', 'hb').branch('hb', 'a', 'mx').edge('a', 'mx') + .edge('t0', 'g').branch('g', 'u', 'tx', phantom=True) + .edge('u', 'h', 'EXCEPTIONAL_RETURN')) + + +class CannotRaiseTest(unittest.TestCase): + """Guards the property pre-check proves cannot raise (cannotRaise) lead nowhere on their exception side.""" + + def graph(self, cannot_raise: bool) -> SAGraph: + b = inter_method_catch_graph() + for n in b.nodes: + if n['id'] == 'g' and cannot_raise: + n['cannotRaise'] = True + return b.load() + + def test_exception_side_of_a_guard_that_cannot_raise_is_a_dead_end(self): + n = self.graph(True).nodes + dead = n['g'].next_fallthrough + self.assertEqual(dead.id, 'dead[g]') + self.assertIs(n['g'].next_branched, n['tx']) # the guard is still a branch + self.assertFalse(sagraph.is_interesting(dead, sagraph.push(None, n['c']))) + # The assert in main's handler was only reachable through the guard's failure. + self.assertFalse(n['c'].reachesTargetSameLevel) + self.assertIsNone(dead.walk_till_branch(sagraph.push(None, n['c']))) + + def test_unexplored_exception_side_is_no_candidate(self): + for cannot_raise, expected in ((False, {'n1'}), (True, set())): + g = keyed(self.graph(cannot_raise)) + n1 = branch_node('n1', skipped=None, branched=leaf(), key='g', pushes=('c',)) + self.assertEqual(candidates(n1, g), expected, f'cannotRaise={cannot_raise}') + + def test_statement_marks_change_nothing(self): + b = inter_method_catch_graph() + for n in b.nodes: + if n['id'] in ('c', 'tx'): + n['cannotRaise'] = True + n = b.load().nodes + self.assertIs(n['g'].next_fallthrough, n['u']) + self.assertTrue(n['c'].reachesTargetSameLevel) + + +class UnwindTest(unittest.TestCase): + + def test_escaping_exception_resumes_at_callers_handler(self): + g = inter_method_catch_graph().load() + g_node, stack = g.entry_node.walk_till_branch(None) + self.assertEqual(g_node.id, 'g') + node, stack = g_node.next_fallthrough.walk_till_branch(stack) + self.assertEqual(node.id, 'hb') + self.assertIsNone(stack) + + def test_unwinding_skips_frames_that_do_not_catch(self): + # main -> middle (catches only UnsupportedOperationException) -> inner: ArithmeticException + g = (GraphBuilder() + .nodes_('e0', 'c1', 'r1', 'h', 'hb').node('a', assertion=True).node('mx', exit=True) + .nodes_('m0', 'c2', 'hm').node('mret', exit=True) + .nodes_('i0', 'ig').node('ix', exit=True).node('u', chain=ARITH_CHAIN) + .edge('e0', 'c1') + .call('c1', 'm0', 'r1', ['mret'], handlers={'java.lang.ArithmeticException': 'h'}) + .edge('r1', 'mx').edge('h', 'hb').branch('hb', 'a', 'mx').edge('a', 'mx') + .edge('m0', 'c2') + .call('c2', 'i0', 'mret', ['ix'], handlers={'java.lang.UnsupportedOperationException': 'hm'}) + .edge('hm', 'mret') + .edge('i0', 'ig').branch('ig', 'u', 'ix', phantom=True) + .edge('u', 'h', 'EXCEPTIONAL_RETURN') + .load()) + ig, stack = g.entry_node.walk_till_branch(None) + self.assertEqual((ig.id, sagraph.depth(stack)), ('ig', 2)) + node, stack = ig.next_fallthrough.walk_till_branch(stack) + self.assertEqual(node.id, 'hb') + self.assertIsNone(stack) + + def test_most_specific_handler_of_a_frame_wins(self): + g = (GraphBuilder().nodes_('c', 'hr', 'he').node('u', chain=ARITH_CHAIN) + .edge('c', 'u', 'CALL', returnSite='hr', + handlers={'java.lang.Exception': 'he', 'java.lang.RuntimeException': 'hr'}) + .load()) + node, stack = sagraph.unwind(g.nodes['u'].exceptionChain, sagraph.push(None, g.nodes["c"])) + self.assertEqual(node.id, 'hr') + + def test_exception_leaving_main_gives_up(self): + g = (GraphBuilder().nodes_('e0', 'g').node('x', exit=True).node('u', chain=AIOOBE_CHAIN) + .edge('e0', 'g').branch('g', 'u', 'x', phantom=True).load()) + self.assertIsNone(g.nodes['u'].walk_till_branch(None)) + + +class DfsTest(unittest.TestCase): + + def test_second_return_pairs_with_callers_next_branch(self): + # Events: kb (1st call), kb (2nd call), bm. bm's TRUE side leads straight to main's exit, + # so its unexplored branched side must be pruned. That only happens if the second return + # resumed at r2: resuming at r1 would pair bm's event with kb again, whose sides are + # interesting, and losing track altogether would make everything interesting. + g = keyed(shared_callee_graph().load()) + n3 = branch_node('n3', skipped=leaf(), branched=None, key='bm', pops=1) + n2 = branch_node('n2', skipped=n3, branched=leaf(), key='kb', pops=1, pushes=('c2',)) + n1 = branch_node('n1', skipped=n2, branched=leaf(), key='kb', pushes=('c1',)) + self.assertEqual(candidates(n1, g), set()) + + def test_unchecked_pairing_is_not_pruned(self): + # As above, but the trace carries no keys: the walk still pairs bm's event with bm, but + # cannot check that pairing, so it does not prune by it. + g = shared_callee_graph().load() + n3 = branch_node('n3', skipped=leaf(), branched=None) + n2 = branch_node('n2', skipped=n3, branched=leaf()) + n1 = branch_node('n1', skipped=n2, branched=leaf()) + self.assertEqual(candidates(n1, g), {'n3'}) + + def test_unchecked_stack_is_not_pruned(self): + # The keys match, but a frame the trace reports has no call site in the graph (a library + # call, or a call site without a key), so the walk's stack cannot be checked. + # Under the first call to classify(), kb's sides cannot reach an assert (precision_graph). + # (b0 has no key, so its pruned side is still walked into.) + g = keyed(precision_graph().load()) + k_first = branch_node('k_first', skipped=leaf(), branched=None, key='kb', pushes=('c1',)) + n0 = branch_node('n0', skipped=k_first, branched=leaf()) + self.assertEqual(candidates(n0, g), set()) + k_first.pushes = (None,) + self.assertEqual(candidates(n0, g), {'k_first'}) + + def test_unexplored_side_leading_to_assert_is_kept(self): + g = keyed(shared_callee_graph().load()) + n3 = branch_node('n3', skipped=None, branched=leaf(), key='bm', pops=1) + n2 = branch_node('n2', skipped=n3, branched=leaf(), key='kb', pops=1, pushes=('c2',)) + n1 = branch_node('n1', skipped=n2, branched=leaf(), key='kb', pushes=('c1',)) + self.assertEqual(candidates(n1, g), {'n3'}) + + def test_unmodelled_exception_makes_everything_after_it_interesting(self): + # main: if (b1) exit; if (b2) exit; assert. b2's TRUE side cannot reach the assert, so it is + # pruned -- unless the executor reported an exception the graph does not model before b2: + # from there on the walk has no information. + g = keyed(GraphBuilder() + .nodes_('e0', 'b1', 'b2').node('a', assertion=True).node('x', exit=True) + .edge('e0', 'b1').branch('b1', 'b2', 'x').branch('b2', 'a', 'x').edge('a', 'x') + .load()) + n2 = branch_node('n2', skipped=leaf(), branched=None, key='b2') + n1 = branch_node('n1', skipped=n2, branched=leaf(), key='b1') + self.assertEqual(candidates(n1, g), set()) + n2.unmodelled_exception = True # the marker, kept on the node after it (Tree.add) + n2.key = None # without a key the walk cannot find the graph again (see KeyCheckTest) + self.assertEqual(candidates(n1, g), {'n2'}) + + def test_unmodelled_exception_mark_on_a_node_makes_everything_after_it_interesting(self): + # The same, with the marker kept on the node after it, as Tree.add does. + g = (GraphBuilder() + .nodes_('e0', 'b1', 'b2').node('a', assertion=True).node('x', exit=True) + .edge('e0', 'b1').branch('b1', 'b2', 'x').branch('b2', 'a', 'x').edge('a', 'x') + .load()) + n2 = branch_node('n2', skipped=leaf(), branched=None) + n1 = branch_node('n1', skipped=n2, branched=leaf()) + n2.unmodelled_exception = True + self.assertEqual(candidates(n1, g), {'n2'}) + + def exception_graph(self): + """ + main: if (b1) exit; c = f(); [handler h: if (b2) exit; assert]; f: if (bf) ...; return + h is the handler of main's call to f; b2's TRUE side cannot reach the assert. + """ + g = (GraphBuilder() + .nodes_('e0', 'b1', 'c1', 'r1', 'h', 'b2').node('a', assertion=True).node('x', exit=True) + .nodes_('f0', 'bf', 'fd').node('fx', exit=True).node('fu', chain=['?Exception']) + .edge('e0', 'b1').branch('b1', 'c1', 'x') + .call('c1', 'f0', 'r1', ['fx'], handlers={'java.lang.Throwable': 'h'}) + .edge('r1', 'x').edge('h', 'b2').branch('b2', 'a', 'x').edge('a', 'x') + .edge('f0', 'bf').branch('bf', 'fd', 'fx').edge('fd', 'fx').edge('fd', 'fu', 'EXCEPTION')) + next(e for e in g.edges if e['type'] == 'CALL')['unknownHandlers'] = {'?Exception': ['h']} + for n in g.nodes: + n['metadata'] = 'method: ' + ('f' if n['id'].startswith('f') or n['id'] == 'bf' else 'main') + return g.load() + + def tree_through_exception(self, after_branch_in_f=True): + """b1 (skipped), [bf in f], then an exception the graph does not model, then b2 with its + TRUE side unexplored.""" + n2 = branch_node('n2', skipped=leaf(), branched=None) + n2.unmodelled_exception = True + nxt = n2 + if after_branch_in_f: + nxt = branch_node('nf', skipped=nxt, branched=leaf()) + return branch_node('n1', skipped=nxt, branched=leaf()) + + def test_after_an_unmodelled_exception_the_walk_gives_up(self): + g = self.exception_graph() + # Without keys the walk cannot find the graph again (see KeyCheckTest for resync). + tree = self.tree_through_exception() + self.assertEqual(candidates(tree, g), {'n2'}) + + def test_guard_failure_is_followed_into_callers_handler(self): + # Events: the guard in test() fails (skipped), then the branch in main's handler. Its TRUE + # side skips the assert, so the unexplored branched side is uninteresting -- which the walk + # can only tell if it unwound from uncaught[AIOOBE] into main's handler. Stopping at the + # uncaught node means "no information" and keeps it. + g = keyed(inter_method_catch_graph().load()) + n2 = branch_node('n2', skipped=leaf(), branched=None, key='hb', pops=1) + n1 = branch_node('n1', skipped=n2, branched=leaf(), key='g', pushes=('c',)) + self.assertEqual(candidates(n1, g), set()) + + def test_guard_failure_keeps_handler_assert_reachable(self): + g = keyed(inter_method_catch_graph().load()) + n2 = branch_node('n2', skipped=None, branched=leaf(), key='hb', pops=1) + n1 = branch_node('n1', skipped=n2, branched=leaf(), key='g', pushes=('c',)) + self.assertEqual(candidates(n1, g), {'n2'}) + + def test_unwinding_picks_the_handler_of_the_frame_on_the_stack(self): + # test() is called twice, each time under a different handler: h1 leads to an assert, h2 + # does not. Events: g (1st call, safe), g (2nd call, fails), then the branch in h2. Its + # unexplored side is uninteresting only if the walk unwound into h2 -- the handler of the + # frame on top -- rather than into any handler uncaught[AIOOBE] has an edge to. + g = (GraphBuilder() + .nodes_('e0', 'c1', 'r1', 'c2', 'r2', 'h1', 'h1b', 'h2', 'h2b') + .node('a', assertion=True).node('mx', exit=True) + .node('t0').node('g').node('tx', exit=True).node('u', chain=AIOOBE_CHAIN) + .edge('e0', 'c1') + .call('c1', 't0', 'r1', ['tx'], handlers={'java.lang.Exception': 'h1'}) + .edge('r1', 'c2') + .call('c2', 't0', 'r2', ['tx'], handlers={'java.lang.RuntimeException': 'h2'}) + .edge('r2', 'mx') + .edge('h1', 'h1b').branch('h1b', 'a', 'mx').edge('a', 'mx') + .edge('h2', 'h2b').branch('h2b', 'mx', 'mx') + .edge('t0', 'g').branch('g', 'u', 'tx', phantom=True) + .edge('u', 'h1', 'EXCEPTIONAL_RETURN').edge('u', 'h2', 'EXCEPTIONAL_RETURN') + .load()) + keyed(g) + n3 = branch_node('n3', skipped=None, branched=None, key='h2b', pops=1) + n2 = branch_node('n2', skipped=n3, branched=leaf(), key='g', pops=1, pushes=('c2',)) + n1 = branch_node('n1', skipped=leaf(), branched=n2, key='g', pushes=('c1',)) + self.assertEqual(candidates(n1, g), set()) + + def test_recursion_levels_return_in_order(self): + # f(): fb: FALSE -> call f() -> fr -> fx ; TRUE -> fx. main: call f() -> r -> bm. + # Events: fb, fb, fb(taken), bm. bm's TRUE side is the exit, so its unexplored branched + # side must be pruned; that needs all three frames popped back to main's return site. + g = (GraphBuilder().nodes_('e0', 'c', 'r', 'bm').node('a', assertion=True).node('x', exit=True) + .nodes_('f0', 'fb', 'fc', 'fr').node('fx', exit=True) + .edge('e0', 'c').call('c', 'f0', 'r', ['fx']) + .edge('r', 'bm').branch('bm', 'a', 'x').edge('a', 'x') + .edge('f0', 'fb').branch('fb', 'fc', 'fx') + .call('fc', 'f0', 'fr', ['fx']).edge('fr', 'fx') + .load()) + keyed(g) + n4 = branch_node('n4', skipped=leaf(), branched=None, key='bm', pops=3) + n3 = branch_node('n3', skipped=leaf(), branched=n4, key='fb', pushes=('fc',)) + n2 = branch_node('n2', skipped=n3, branched=leaf(), key='fb', pushes=('fc',)) + n1 = branch_node('n1', skipped=n2, branched=leaf(), key='fb', pushes=('c',)) + self.assertEqual(candidates(n1, g), set()) + + +def precision_graph(): + """ + SharedCalleePrecision: classify() is called from a context that returns without reaching an + assert, and from one that asserts on its result. + + main: e0 -> b0: FALSE -> c1 -CALL-> k0, returns to r1 -> x (exit, no assert) + TRUE -> c2 -CALL-> k0, returns to r2 -> bm: FALSE -> a (assert), TRUE -> x + classify: k0 -> kb: FALSE -> kx1 (exit), TRUE -> kx2 (exit) + """ + return (GraphBuilder() + .nodes_('e0', 'b0', 'c1', 'r1', 'c2', 'r2', 'bm').node('a', assertion=True).node('x', exit=True) + .node('k0').node('kb').node('kx1', exit=True).node('kx2', exit=True) + .edge('e0', 'b0').branch('b0', 'c1', 'c2') + .call('c1', 'k0', 'r1', ['kx1', 'kx2']).edge('r1', 'x') + .call('c2', 'k0', 'r2', ['kx1', 'kx2']).edge('r2', 'bm') + .branch('bm', 'a', 'x').edge('a', 'x') + .edge('k0', 'kb').branch('kb', 'kx1', 'kx2')) + + + +class FakeTree(Tree): + """Collects every error dfs records (Tree.record_execution_error drops repeats).""" + def record_execution_error(self, kind, message): + self.execution_errors.append({'kind': kind, 'message': message}) + + +class KeyCheckTest(unittest.TestCase): + """Branch keys and stack deltas: dfs checks its walk against the trace, and finds the graph again.""" + + B1, CALL, BF, B2 = 'M|1|if|0', 'M|2|call:f()I|0', 'F|10|if|0', 'M|3|if|0' + + def graph(self): + """As DfsTest.exception_graph, with keys on the branches and the call site.""" + g = DfsTest.exception_graph(self) + g.nodes['b1'].key, g.nodes['c1'].key, g.nodes['bf'].key, g.nodes['b2'].key = \ + self.B1, self.CALL, self.BF, self.B2 + index = g.entry_node.graph_index + index.keys.update({n.key: n for n in g.nodes.values() if n.key is not None}) + return g + + def tree(self, b2_key=None, b2_pops=1, bf_key=None, bf_pushes=None, throws=True): + """ + n1 (b1, skipped), nf (bf in f, skipped), n2 (b2, TRUE side unexplored). b2 is + only reached through main's handler: by default f throws an exception the graph does not + model, so the walk is lost after nf and can only find b2 by resync. + """ + n2 = branch_node('n2', skipped=leaf(), branched=None, key=b2_key or self.B2, pops=b2_pops) + n2.unmodelled_exception = throws + nf = branch_node('nf', skipped=n2, branched=leaf(), key=bf_key or self.BF, + pushes=bf_pushes if bf_pushes is not None else (self.CALL,)) + return branch_node('n1', skipped=nf, branched=leaf(), key=self.B1) + + def run_dfs(self, tree, g): + fake = FakeTree() + found = {n.id for n in dfs(fake, tree, set(), set(), g.entry_node)} + return found, [e for e in fake.execution_errors if e['kind'] == 'sa_desync'] + + def test_walk_in_step_is_not_flagged(self): + g = self.graph() + _, desyncs = self.run_dfs(self.tree(), g) + self.assertEqual(desyncs, []) + + def test_wrong_key_is_a_desync(self): + g = self.graph() + _, desyncs = self.run_dfs(self.tree(bf_key='F|10|if|1'), g) + self.assertEqual(len(desyncs), 1) + + def test_wrong_stack_is_a_desync(self): + g = self.graph() + # The trace says bf runs two frames up, the walk entered one. + found, desyncs = self.run_dfs(self.tree(bf_pushes=(self.CALL, self.CALL)), g) + self.assertEqual(len(desyncs), 1) + + def test_missing_keys_are_not_checked(self): + g = self.graph() + g.nodes['bf'].key = None + _, desyncs = self.run_dfs(self.tree(bf_key='F|10|if|1'), g) + self.assertEqual(desyncs, []) + + def test_resync_after_an_unmodelled_exception(self): + g = self.graph() + # The walk is lost after the exception and finds b2 again by its key, with the stack from + # the trace (f's frame popped): b2's TRUE side cannot reach the assert. + found, desyncs = self.run_dfs(self.tree(), g) + self.assertEqual(found, set()) + self.assertEqual(desyncs, []) + + def test_the_walk_counts_how_it_paired_each_branch(self): + g = self.graph() + fake = FakeTree() + dfs(fake, self.tree(), set(), set(), g.entry_node) + self.assertEqual((fake.walk['branches'], fake.walk['verified'], fake.walk['resynced'], + fake.walk['lost'], fake.walk['desyncs']), (3, 2, 1, 0, 0)) + self.assertIn('3 branches (2 verified, 1 resynced, 0 unverified, 0 lost, 0 masked)', describe_walk(fake.walk)) + del g.entry_node.graph_index.keys[self.B2] # without its key, b2 cannot be found again + dfs(fake, self.tree(), set(), set(), g.entry_node) + self.assertEqual((fake.walk['verified'], fake.walk['resynced'], fake.walk['lost']), (2, 0, 1)) + + def test_no_resync_without_a_key(self): + g = self.graph() + del g.entry_node.graph_index.keys[self.B2] + found, _ = self.run_dfs(self.tree(), g) + self.assertEqual(found, {'n2'}) + + def test_no_resync_when_the_stack_does_not_lead_to_the_branch(self): + g = self.graph() + # The trace still has f's frame (no pop), but b2 is in main: the stack and the key disagree. + found, _ = self.run_dfs(self.tree(b2_pops=0), g) + self.assertEqual(found, {'n2'}) + + def test_no_resync_through_an_unknown_call_site(self): + g = self.graph() + found, _ = self.run_dfs(self.tree(b2_pops=0, bf_pushes=(None,)), g) + self.assertEqual(found, {'n2'}) + + def test_callback_branches_are_masked(self): + g = self.graph() + plain, _ = self.run_dfs(self.tree(), g) + # Before f is called, library code called back into application code: two branches the + # graph does not have, in a frame pushed by a library call. Unmasked, they would be a desync. + tree = self.tree() + nf = tree.skipped + cb2 = branch_node('cb2', skipped=nf, branched=leaf(), key='App.cb()V|7|if|1', callback=True) + cb1 = branch_node('cb1', skipped=cb2, branched=leaf(), key='App.cb()V|7|if|0', + pushes=('M|1|call:read()I|0',), callback=True) + tree.skipped = cb1 + nf.pops = 1 + found, desyncs = self.run_dfs(tree, g) + self.assertEqual(desyncs, []) + self.assertEqual(found, plain) # b2 is still pruned after the callback + for n in (cb1, cb2): + n.callback = False + _, desyncs = self.run_dfs(tree, g) + self.assertEqual(len(desyncs), 1) + + def test_choice_branches_are_masked(self): + g = self.graph() + plain, _ = self.run_dfs(self.tree(), g) + # Before f is called, an access at a symbolic index adds two choice branches the graph does + # not have, one with an unexplored side. Masked, the walk holds its place over them, and + # they are still solver targets. + tree = self.tree() + nf = tree.skipped + ch2 = branch_node('ch2', skipped=None, branched=nf, key='M|1|aaload|0#idx1', choice=True) + ch1 = branch_node('ch1', skipped=ch2, branched=leaf(), key='M|1|aaload|0#idx0', choice=True) + tree.skipped = ch1 + found, desyncs = self.run_dfs(tree, g) + self.assertEqual(desyncs, []) + self.assertEqual(found, plain | {'ch2'}) + for n in (ch1, ch2): + n.choice = False + _, desyncs = self.run_dfs(tree, g) + self.assertEqual(len(desyncs), 1) + + def test_open_ordinal_matches_any_on_its_line(self): + g = self.graph() + g.nodes['bf'].key = 'F|10|if|*' + _, desyncs = self.run_dfs(self.tree(bf_key='F|10|if|3'), g) + self.assertEqual(desyncs, []) + _, desyncs = self.run_dfs(self.tree(bf_key='F|11|if|0'), g) + self.assertEqual(len(desyncs), 1) + + +class MultiCalleeTest(unittest.TestCase): + """A call site with several known callees: summaries take the union, the walk resyncs inside.""" + + CALL, BF, BG, B2 = 'M|2|call:run()V|0', 'F|10|if|0', 'G|20|if|0', 'M|3|if|0' + + def graph(self): + """ + main: c1 = x.run() -- F.run or G.run; then if (b2) ...; F.run: if (bf) assert; + G.run: if (bg) ... (no assert). Keys on the branches and the call site. + """ + g = (GraphBuilder() + .nodes_('e0', 'c1', 'r1', 'b2', 'x2').node('mx', exit=True) + .nodes_('f0', 'bf', 'fd').node('fa', assertion=True).node('fx', exit=True) + .nodes_('g0', 'bg', 'gd').node('gx', exit=True) + .edge('e0', 'c1') + .call('c1', 'f0', 'r1', ['fx']).call('c1', 'g0', 'r1', ['gx']) + .edge('r1', 'b2').branch('b2', 'mx', 'x2').edge('x2', 'mx') + .edge('f0', 'bf').branch('bf', 'fd', 'fa').edge('fa', 'fx').edge('fd', 'fx') + .edge('g0', 'bg').branch('bg', 'gd', 'gx').edge('gd', 'gx')) + for n in g.nodes: + n['metadata'] = 'method: ' + ('F' if n['id'] in ('f0', 'bf', 'fd', 'fa', 'fx') + else 'G' if n['id'] in ('g0', 'bg', 'gd', 'gx') else 'main') + n['key'] = {'c1': self.CALL, 'bf': self.BF, 'bg': self.BG, 'b2': self.B2}.get(n['id']) + if n['key'] is None: + del n['key'] + return g.load() + + def run_dfs(self, tree, g): + fake = FakeTree() + found = {n.id for n in dfs(fake, tree, set(), set(), g.entry_node)} + return found, [e for e in fake.execution_errors if e['kind'] == 'sa_desync'] + + def test_summaries_take_the_union(self): + g = self.graph() + self.assertTrue(g.nodes['c1'].reachesTargetSameLevel) # through F.run + self.assertTrue(g.nodes['e0'].reachesTargetSameLevel) + self.assertFalse(g.nodes['r1'].reachesTargetSameLevel) + + def test_walk_stops_at_the_call_site(self): + g = self.graph() + self.assertIsNone(g.nodes['c1'].walk_till_branch(None)) + + def test_resync_in_the_callee_that_ran(self): + g = self.graph() + # G.run ran: its branch bg is found by key, under the call site c1. Neither side of bg + # reaches an assert, so its unexplored side is pruned. + ng = branch_node('ng', skipped=leaf(), branched=None, key=self.BG, pushes=(self.CALL,)) + found, desyncs = self.run_dfs(ng, g) + self.assertEqual(found, set()) + self.assertEqual(desyncs, []) + # F.run ran: bf's TRUE side reaches the assert. + nf = branch_node('nf', skipped=leaf(), branched=None, key=self.BF, pushes=(self.CALL,)) + found, _ = self.run_dfs(nf, g) + self.assertEqual(found, {'nf'}) + + def test_no_resync_into_a_method_the_call_site_cannot_call(self): + g = self.graph() + g.nodes['c1'].callees = [g.nodes['f0'], g.nodes['fx']] # G.run is not a callee any more + ng = branch_node('ng', skipped=leaf(), branched=None, key=self.BG, pushes=(self.CALL,)) + found, _ = self.run_dfs(ng, g) + self.assertEqual(found, {'ng'}) # no information + +class ContextSensitiveMarkingTest(unittest.TestCase): + + def test_callee_branch_is_interesting_only_in_the_asserting_context(self): + # Both calls to classify() have their branch half explored. Context-insensitively both + # unexplored sides are interesting, because the second caller reaches an assert; with the + # call stack only the one under the second call is. + g = keyed(precision_graph().load()) + k_first = branch_node('k_first', skipped=leaf(), branched=None, key='kb', pushes=('c1',)) + k_second = branch_node('k_second', skipped=leaf(), branched=None, key='kb', pushes=('c2',)) + n0 = branch_node('n0', skipped=k_first, branched=k_second, key='b0') + self.assertEqual(candidates(n0, g), {'k_second'}) + + def test_summaries(self): + n = precision_graph().load().nodes + self.assertFalse(n['kb'].reachesTargetSameLevel) + self.assertTrue(n['kb'].canReturn) + self.assertTrue(n['c2'].reachesTargetSameLevel) + self.assertFalse(n['c1'].reachesTargetSameLevel) + self.assertTrue(n['e0'].reachesTargetSameLevel) + + def test_assert_behind_the_return_site_is_found(self): + # Soundness: the assert is not in classify() but after the second call returns. The walk + # queries kb's sides with the second call on its stack, and has to look through the return. + n = precision_graph().load().nodes + second = sagraph.push(None, n['c2']) + first = sagraph.push(None, n['c1']) + self.assertTrue(sagraph.is_interesting(n['kx1'], second)) + self.assertFalse(sagraph.is_interesting(n['kx1'], first)) + # With no stack the walk is in main's frame, where classify's exit returns nowhere. + self.assertFalse(sagraph.is_interesting(n['kx1'], None)) + + def test_assert_in_callers_handler_is_found_through_unwinding(self): + # InterMethodCatch: the guard's failure side is test()'s uncaught[AIOOBE] node, which only + # reaches the assert in main's handler by unwinding. + n = inter_method_catch_graph().load().nodes + stack = sagraph.push(None, n['c']) + self.assertTrue(sagraph.is_interesting(n['u'], stack)) + self.assertFalse(sagraph.is_interesting(n['tx'], stack)) + self.assertEqual(n['u'].escapes, frozenset({tuple(AIOOBE_CHAIN)})) + self.assertFalse(n['u'].canReturn) # an escaping exception is not a return + self.assertTrue(n['c'].reachesTargetSameLevel) + + def test_exception_caught_one_frame_up_is_a_same_level_path_there(self): + # main -> middle -> inner; inner's ArithmeticException escapes middle (whose handler is for + # another type) and main catches it with an assert. So middle's call site into inner does + # not reach the assert at its own level, but escapes; main's call site into middle does. + g = (GraphBuilder() + .nodes_('e0', 'c1', 'r1', 'h').node('a', assertion=True).node('mx', exit=True) + .nodes_('m0', 'c2', 'hm').node('mret', exit=True) + .nodes_('i0', 'ig').node('ix', exit=True).node('u', chain=ARITH_CHAIN) + .edge('e0', 'c1') + .call('c1', 'm0', 'r1', ['mret'], handlers={'java.lang.ArithmeticException': 'h'}) + .edge('r1', 'mx').edge('h', 'a').edge('a', 'mx') + .edge('m0', 'c2') + .call('c2', 'i0', 'mret', ['ix'], handlers={'java.lang.UnsupportedOperationException': 'hm'}) + .edge('hm', 'mret') + .edge('i0', 'ig').branch('ig', 'u', 'ix', phantom=True) + .edge('u', 'h', 'EXCEPTIONAL_RETURN') + .load()) + n = g.nodes + self.assertFalse(n['c2'].reachesTargetSameLevel) + self.assertEqual(n['c2'].escapes, frozenset({tuple(ARITH_CHAIN)})) + self.assertTrue(n['c1'].reachesTargetSameLevel) + stack = sagraph.push(sagraph.push(None, n['c1']), n['c2']) + self.assertTrue(sagraph.is_interesting(n['u'], stack)) + self.assertFalse(sagraph.is_interesting(n['ix'], stack)) + + def unknown_exception_graph(self, unknown_handlers, middle=False): + """ + main calls f (through middle() if `middle`) inside a try whose handler h asserts. + f: if (..) { deref (may throw an exception of unknown type) } -- like npe-catch-up1. + """ + b = (GraphBuilder() + .nodes_('e0', 'c1', 'r1', 'h').node('a', assertion=True).node('mx', exit=True) + .nodes_('k0', 'kb', 'kd').node('kx', exit=True).node('ku', chain=['?Exception'])) + callee = 'k0' + if middle: + b.nodes_('m0', 'c2').node('mret', exit=True).node('mu', chain=['?Exception']) + callee = 'm0' + b.edge('e0', 'c1') + b.call('c1', callee, 'r1', ['mret'] if middle else ['kx'], + handlers={'java.lang.NullPointerException': 'h'}) + next(e for e in b.edges if e['source'] == 'c1' and e['type'] == 'CALL')['unknownHandlers'] = unknown_handlers + b.edge('r1', 'mx').edge('h', 'a').edge('a', 'mx') + if middle: + b.edge('m0', 'c2').call('c2', 'k0', 'mret', ['kx']) + b.edge('k0', 'kb').branch('kb', 'kd', 'kx').edge('kd', 'kx').edge('kd', 'ku', 'EXCEPTION') + return b.load() + + def test_unknown_exception_reaches_a_listed_handler_in_the_caller(self): + n = self.unknown_exception_graph({'?Exception': ['h']}).nodes + stack = sagraph.push(None, n['c1']) + self.assertTrue(sagraph.is_interesting(n['kd'], stack)) # may throw into main's handler + self.assertFalse(sagraph.is_interesting(n['kx'], stack)) # cannot + self.assertEqual(n['kd'].escapes, frozenset({('?Exception',)})) + self.assertTrue(n['c1'].reachesTargetSameLevel) + + def test_unknown_exception_is_not_caught_by_an_unlisted_handler(self): + n = self.unknown_exception_graph({}).nodes + self.assertFalse(sagraph.is_interesting(n['kd'], sagraph.push(None, n['c1']))) + self.assertFalse(n['c1'].reachesTargetSameLevel) + + def test_unknown_exception_unwinds_through_frames_without_handlers(self): + n = self.unknown_exception_graph({'?Exception': ['h']}, middle=True).nodes + stack = sagraph.push(sagraph.push(None, n['c1']), n['c2']) + self.assertTrue(sagraph.is_interesting(n['kd'], stack)) + self.assertFalse(sagraph.is_interesting(n['kx'], stack)) + self.assertIn(('?Exception',), n['c2'].escapes) # middle has no handler: it escapes + self.assertTrue(n['c1'].reachesTargetSameLevel) + + def test_unknown_exception_also_escapes_past_a_listed_handler(self): + # A listed handler might not catch it, so the escape stays in the call site's summary. + n = self.unknown_exception_graph({'?Exception': ['h']}).nodes + self.assertIn(('?Exception',), n['c1'].escapes) + + def test_walk_gives_up_at_an_unknown_exception_escape(self): + n = self.unknown_exception_graph({'?Exception': ['h']}).nodes + self.assertIsNone(n['ku'].walk_till_branch(sagraph.push(None, n['c1']))) + + def test_recursive_summaries_converge(self): + # f(): fb: FALSE -> call f() (returns to fr) -> fr -> fx ; TRUE -> fx. + # main: call f() -> r -> bm: FALSE -> a (assert), TRUE -> x. + g = (GraphBuilder().nodes_('e0', 'c', 'r', 'bm').node('a', assertion=True).node('x', exit=True) + .nodes_('f0', 'fb', 'fc', 'fr').node('fx', exit=True) + .edge('e0', 'c').call('c', 'f0', 'r', ['fx']) + .edge('r', 'bm').branch('bm', 'a', 'x').edge('a', 'x') + .edge('f0', 'fb').branch('fb', 'fc', 'fx') + .call('fc', 'f0', 'fr', ['fx']).edge('fr', 'fx') + .load()) + n = g.nodes + self.assertTrue(n['fc'].canReturn) + self.assertFalse(n['fc'].reachesTargetSameLevel) + # Three levels deep, the assert is found by returning through every frame. + stack = None + for site in ['c', 'fc', 'fc']: + stack = sagraph.push(stack, n[site]) + self.assertTrue(sagraph.is_interesting(n['fc'], stack)) + + def test_incomplete_callee_makes_the_call_interesting(self): + g = (GraphBuilder().nodes_('e0', 'b', 'c', 'r').node('x', exit=True) + .nodes_('f0').node('fi', incomplete=True).node('fx', exit=True) + .edge('e0', 'b').branch('b', 'c', 'x') + .call('c', 'f0', 'r', ['fx']).edge('r', 'x') + .edge('f0', 'fi').edge('fi', 'fx') + .load()) + self.assertTrue(sagraph.is_interesting(g.nodes['c'], None)) + self.assertFalse(sagraph.is_interesting(g.nodes['x'], None)) + + +class TargetDistanceTest(unittest.TestCase): + + def graph(self): + """ + e0 -> b1: FALSE -> b2, TRUE -> c + b2: FALSE -> x (exit), TRUE -> a2 (assert) + c: FALSE -> d, TRUE -> x; d: FALSE -> a1 (assert), TRUE -> x + """ + return (GraphBuilder().nodes_('e0', 'b1', 'b2', 'c', 'd') + .node('a1', assertion=True).node('a2', assertion=True).node('x', exit=True) + .edge('e0', 'b1').branch('b1', 'b2', 'c').branch('b2', 'x', 'a2') + .branch('c', 'd', 'x').branch('d', 'a1', 'x').edge('a1', 'x').edge('a2', 'x').load()) + + def test_distance_counts_branches_still_to_decide(self): + g = self.graph() + dist = {id: g.nodes[id].targetDistance for id in ('a1', 'a2', 'd', 'c', 'b2', 'b1', 'e0')} + self.assertEqual(dist, {'a1': 0, 'a2': 0, 'd': 1, 'c': 2, 'b2': 1, 'b1': 2, 'e0': 2}) + self.assertEqual(g.nodes['x'].targetDistance, float('inf')) + + def test_distance_crosses_calls(self): + g = (GraphBuilder().nodes_('e0', 'c1', 'r1', 'f0', 'fb').node('fa', assertion=True) + .node('fx', exit=True).node('x', exit=True) + .edge('e0', 'c1').call('c1', 'f0', 'r1', ['fx']).edge('r1', 'x') + .edge('f0', 'fb').branch('fb', 'fx', 'fa').edge('fa', 'fx').load()) + self.assertEqual(g.nodes['e0'].targetDistance, 1) + + def test_dfs_reports_distance_of_the_unexplored_side(self): + g = self.graph() + # Executed: b1 FALSE, then b2 FALSE. Unexplored: b1's TRUE side (c, 2 away) and b2's TRUE + # side (a2, the assert itself). + b2 = branch_node('b2', skipped=leaf()) + b1 = branch_node('b1', skipped=b2) + distances = {} + found = dfs(None, b1, set(), set(), g.entry_node, distances=distances) + self.assertEqual([n.id for n in found], ['b1', 'b2']) + self.assertEqual({n.id: d for n, d in distances.items()}, {'b1': 2, 'b2': 0}) + found.sort(key=lambda n: distances.get(n, float('inf'))) + self.assertEqual([n.id for n in found], ['b2', 'b1']) + + def test_dfs_without_sa_reports_no_distances(self): + b1 = branch_node('b1', skipped=leaf()) + distances = {} + self.assertEqual([n.id for n in dfs(None, b1, set(), set(), None, distances=distances)], ['b1']) + self.assertEqual(distances, {}) + + +class ConcreteOnlyBranchTest(unittest.TestCase): + + def test_concrete_only_guard_keeps_the_walk_in_step_but_is_no_candidate(self): + # e0 -> g: a phantom bounds guard (in bounds: b, out of bounds: u, which leaves main) + # b: FALSE -> x (exit), TRUE -> a (assert) + g = (GraphBuilder().nodes_('e0', 'g', 'b').node('u', chain=AIOOBE_CHAIN) + .node('a', assertion=True).node('x', exit=True) + .edge('e0', 'g').branch('g', 'u', 'b', phantom=True).branch('b', 'x', 'a').edge('a', 'x').load()) + # The executor records the guard of an untracked array as a concrete-only branch: in bounds, + # so taken. Without it, b would be paired with g and its FALSE side (u) pruned. + b = branch_node('b', skipped=leaf()) + guard = branch_node('g', branched=b) + guard.concrete_only = True + self.assertEqual(candidates(guard, g), {'b'}) + + +if __name__ == '__main__': + unittest.main() diff --git a/symbolic-explorer/tests/test_search_order.py b/symbolic-explorer/tests/test_search_order.py new file mode 100644 index 00000000..cfc691b0 --- /dev/null +++ b/symbolic-explorer/tests/test_search_order.py @@ -0,0 +1,77 @@ +""" +Unit tests for the search orders of strategy/StrategyService (--search-order): node depth, the +instruction and side a flip takes, and the sides the tree's traces covered. + +Run from symbolic-explorer/: + ../targets/sv-comp/scripts/.venv/bin/python3 -m unittest tests.test_search_order -v +""" + +import os +import sys +import unittest + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) + +from data.BinaryExecutionTree.Node import Node +from strategy.StrategyService import depth, open_side, covered_sides, first_coverers, trace_scores + +LOOP = 1 << 58 # one loop iteration in an iid + + +def node(iid, parent=None, gid=0, trace='t1'): + n = Node.__new__(Node) + n.id = iid + n.gid = gid + n.trace_id = trace + n.kind = "Branch" + n.parent = parent + n.branched = None + n.skipped = None + return n + + +class SearchOrderTest(unittest.TestCase): + + def setUp(self): + # root (iid 1) took its branched side twice around a loop (iid 2, then 2 + LOOP); + # the second iteration also went to the skipped side of iid 3, where the trace ended. + # (t1 ran first; t2 diverged from it at b's skipped side and added c.) + self.root = node(1, gid=1) + self.a = node(2, self.root, gid=2) + self.root.branched = self.a + self.b = node(2 + LOOP, self.a, gid=3) + self.a.branched = self.b + self.c = node(3, self.b, gid=4, trace='t2') + self.b.skipped = self.c + self.c.skipped = object() # a leaf + + def test_depth_counts_the_branches_above(self): + self.assertEqual([depth(n) for n in (self.root, self.a, self.b, self.c)], [0, 1, 2, 3]) + + def test_open_side_ignores_the_loop_count(self): + # a and b are the same instruction in different iterations: a has its skipped side open, + # b (which took the skipped side) its branched side + self.assertEqual(open_side(self.a), (2, False)) + self.assertEqual(open_side(self.b), (2, True)) + # c took its skipped side, so flipping it takes the branched one + self.assertEqual(open_side(self.c), (3, True)) + + def test_covered_sides_are_those_some_trace_took(self): + covered = covered_sides(self.root) + self.assertEqual(covered, {(1, True), (2, True), (2, False), (3, False)}) + # The two iterations took both sides of instruction 2: flipping either reaches nothing new. + self.assertIn(open_side(self.a), covered) + self.assertIn(open_side(self.b), covered) + self.assertNotIn(open_side(self.c), covered) + self.assertNotIn(open_side(self.root), covered) + + + def test_traces_score_the_sides_they_took_first(self): + first = first_coverers(self.root) + self.assertIs(first[(2, True)], self.a) # a was there before b + # t1 took 1-branched, 2-branched and 2-skipped (via b) first, t2 3-skipped + self.assertEqual(trace_scores(first), {'t1': 3, 't2': 1}) + + +if __name__ == '__main__': + unittest.main() diff --git a/symbolic-explorer/tests/test_shorten_strings.py b/symbolic-explorer/tests/test_shorten_strings.py new file mode 100644 index 00000000..eb15fecb --- /dev/null +++ b/symbolic-explorer/tests/test_shorten_strings.py @@ -0,0 +1,49 @@ +""" +Unit tests for Z3Handler.shorten_strings (--prefer-short-strings): the string inputs of a model are +made as short as the assertions allow, one input after the other. + +Needs z3, so run it with the harness venv, from symbolic-explorer/: + ../targets/sv-comp/scripts/.venv/bin/python3 -m unittest tests.test_shorten_strings -v +""" + +import os +import sys +import unittest + +import z3 + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) + +from solver.SolverHandler import Z3Handler + + +class ShortenStringsTest(unittest.TestCase): + + def test_inputs_get_the_shortest_length_the_query_allows(self): + s = z3.String('java/lang/String_0') + t = z3.String('java/lang/String_1') + solver = z3.Solver() + solver.add(z3.PrefixOf(z3.StringVal('-x'), s), z3.Length(s) + z3.Length(t) >= 7, z3.Length(t) >= 3) + solver.push() + solver.add(z3.Length(s) == 6, z3.Length(t) == 9) # a model with longer strings than needed + self.assertEqual(solver.check(), z3.sat) + model = solver.model() + solver.pop() + self.assertEqual(solver.check(), z3.sat) + shorter = Z3Handler.shorten_strings(solver, model, 0.01) + lengths = sorted(shorter.eval(z3.Length(v)).as_long() for v in (s, t)) + self.assertEqual(sum(lengths), 7) + self.assertTrue(shorter.eval(z3.PrefixOf(z3.StringVal('-x'), s))) + + def test_other_variables_are_left_alone(self): + i = z3.BitVec('I_0', 32) + tmp = z3.String('tmp') # not an input name + solver = z3.Solver() + solver.add(z3.Length(tmp) > 4, i == 3) + self.assertEqual(solver.check(), z3.sat) + model = Z3Handler.shorten_strings(solver, solver.model(), 0.01) + self.assertEqual(model.eval(i).as_long(), 3) + + +if __name__ == '__main__': + unittest.main() diff --git a/symbolic-explorer/tests/test_solver_rlimit.py b/symbolic-explorer/tests/test_solver_rlimit.py new file mode 100644 index 00000000..b095335b --- /dev/null +++ b/symbolic-explorer/tests/test_solver_rlimit.py @@ -0,0 +1,133 @@ +""" +Unit tests for SVCompDriver.solve_candidates: branches that run out of their solver resource limit +are retried with a growing limit after all cheaper queries, and an undecided branch rules out SAFE. + +Needs the explorer's dependencies, so run it with the harness venv, from symbolic-explorer/: + ../targets/sv-comp/scripts/.venv/bin/python3 -m unittest tests.test_solver_rlimit -v +""" + +import os +import sys +import unittest +from types import SimpleNamespace +from unittest import mock + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) + +from driver import SVCompDriver as svcomp +from svcomp.ResourceBudget import ResourceBudget +from solver.SolverHandler import SATResult + +BASE = 1000 + + +def branch(gid, choice=False): + return SimpleNamespace(gid=gid, id=gid, choice=choice) + + +class FakeSolver: + """Decides branch gid when given at least needed[gid] resources, with verdict results[gid].""" + + def __init__(self, needed, results): + self.needed, self.results, self.calls = needed, results, [] + + def __call__(self, b, solver_timeout_ms=None, solver_rlimit=None, approximate_rlimit=None): + self.calls.append((b.gid, solver_rlimit)) + if solver_rlimit is not None and solver_rlimit < self.needed.get(b.gid, 0): + return SATResult.LIMIT, {} + result = self.results[b.gid] + return result, ({'x': 1} if result == SATResult.SAT else {}) + + +class SolveCandidatesTest(unittest.TestCase): + + def driver(self, rlimit=BASE): + d = svcomp.SVCompDriver.__new__(svcomp.SVCompDriver) + d.args = SimpleNamespace(solver_rlimit=rlimit) + d.nr_solver_calls = 0 + d.solver_limit_hits = 0 + d.rlimit_level = {} + d.failed_solutions = {} + d.budget = ResourceBudget(cgroup_limit=0) + return d + + def solve(self, d, candidates, solver): + with mock.patch.object(svcomp.StrategyService, 'solve_branch', side_effect=solver): + return d.solve_candidates(candidates) + + def test_choice_branches_come_after_program_branches(self): + # 1 is a choice branch (e.g. another radix), 2 a branch of the program: both SAT, 2 first. + d = self.driver(rlimit=None) + s = FakeSolver({}, {1: SATResult.SAT, 2: SATResult.SAT}) + found, sol, gave_up = self.solve(d, [branch(1, choice=True), branch(2)], s) + self.assertEqual(found.gid, 2) + self.assertEqual(s.calls, [(2, None)]) + + def test_cheap_queries_come_before_retries(self): + # 1 needs 20x the base limit, 2 is UNSAT, 3 is SAT: 3 is found before 1 is retried. + d = self.driver() + s = FakeSolver({1: 20 * BASE}, {1: SATResult.SAT, 2: SATResult.UNSAT, 3: SATResult.SAT}) + found, sol, gave_up = self.solve(d, [branch(1), branch(2), branch(3)], s) + self.assertEqual(found.gid, 3) + self.assertEqual(s.calls, [(1, BASE), (2, BASE), (3, BASE)]) + self.assertEqual(d.rlimit_level, {1: 1}) + + def test_limit_grows_while_several_branches_compete(self): + # 1 and 2 both need 20x: they alternate at growing limits until 1 is decided. + d = self.driver() + s = FakeSolver({1: 20 * BASE, 2: 20 * BASE}, {1: SATResult.SAT, 2: SATResult.SAT}) + found, _, gave_up = self.solve(d, [branch(1), branch(2)], s) + self.assertEqual(found.gid, 1) + self.assertEqual(s.calls, [(1, BASE), (2, BASE), (1, 5 * BASE), (2, 5 * BASE), (1, 25 * BASE)]) + self.assertEqual(d.solver_limit_hits, 4) + self.assertFalse(gave_up) + + def test_lone_retry_is_unlimited(self): + # Nothing else is left to go first, so the retry is not escalated step by step. + d = self.driver() + s = FakeSolver({1: 10**9}, {1: SATResult.SAT, 2: SATResult.UNSAT}) + found, _, _ = self.solve(d, [branch(1), branch(2)], s) + self.assertEqual(found.gid, 1) + self.assertEqual(s.calls, [(1, BASE), (2, BASE), (1, None)]) + + def test_first_pass_is_limited_even_for_a_single_candidate(self): + # A branch escalated in earlier rounds keeps its level; only retries go unlimited. + d = self.driver() + s = FakeSolver({}, {1: SATResult.SAT}) + self.solve(d, [branch(1)], s) + self.assertEqual(s.calls, [(1, BASE)]) + + def test_escalated_branches_go_last_in_later_rounds(self): + d = self.driver() + d.rlimit_level = {1: 2} + s = FakeSolver({}, {1: SATResult.SAT, 2: SATResult.SAT}) + found, _, _ = self.solve(d, [branch(1), branch(2)], s) + self.assertEqual(found.gid, 2) + + def test_all_unsat_allows_safe(self): + d = self.driver() + s = FakeSolver({1: 5 * BASE}, {1: SATResult.UNSAT, 2: SATResult.UNSAT}) + self.assertEqual(self.solve(d, [branch(1), branch(2)], s), (None, {}, False)) + + def test_unknown_anywhere_rules_out_safe(self): + # An UNKNOWN before the last branch used to be forgotten when the last one was UNSAT. + d = self.driver() + s = FakeSolver({}, {1: SATResult.UNKNOWN, 2: SATResult.UNSAT}) + self.assertEqual(self.solve(d, [branch(1), branch(2)], s), (None, {}, True)) + + def test_limit_at_the_maximum_gives_up(self): + d = self.driver() + d.rlimit_level = {1: 40, 2: 40} # far past MAX_RLIMIT + s = FakeSolver({1: 2**40, 2: 2**40}, {1: SATResult.SAT, 2: SATResult.SAT}) + self.assertEqual(self.solve(d, [branch(1), branch(2)], s), (None, {}, True)) + self.assertEqual(s.calls, [(1, svcomp.MAX_RLIMIT), (2, svcomp.MAX_RLIMIT)]) + + def test_without_flag_queries_are_unlimited(self): + d = self.driver(rlimit=0) + s = FakeSolver({1: 10**9}, {1: SATResult.SAT}) + found, _, _ = self.solve(d, [branch(1)], s) + self.assertEqual(s.calls, [(1, None)]) + + +if __name__ == '__main__': + unittest.main() diff --git a/symbolic-explorer/tests/test_stack_overflow.py b/symbolic-explorer/tests/test_stack_overflow.py new file mode 100644 index 00000000..3cdc3c17 --- /dev/null +++ b/symbolic-explorer/tests/test_stack_overflow.py @@ -0,0 +1,95 @@ +""" +Unit tests for SVCompDriver.determine_next_step on executions whose trace reports a stack overflow +in the target: their outcome depends on the JVM's stack size, so a violation they show is not a +verdict, and an execution without symbolic inputs is not SAFE. + +It imports the driver, which needs the explorer's dependencies (z3, fastapi, ...) that the system +Python lacks, so run it with the harness venv, from symbolic-explorer/: + ../targets/sv-comp/scripts/.venv/bin/python3 -m unittest tests.test_stack_overflow -v +""" + +import os +import sys +import unittest +from types import SimpleNamespace +from unittest import mock + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) + +from driver import SVCompDriver as svcomp + +ASSERTION = 'Exception in thread "main" java.lang.AssertionError' +RUNTIME_EXCEPTION = '[SWAT] Uncaught top-level RuntimeException in symbolic execution' + + +class StackOverflowRoundTest(unittest.TestCase): + + def setUp(self): + self.tree = SimpleNamespace(execution_errors=[], symbolic_vars={'I_0'}, uncaught_exceptions=0) + self.overflowed_traces = set() + def record_execution_error(endpoint_id, kind, message): + self.tree.execution_errors.append({'kind': kind, 'message': message}) + def record_uncaught_exception(endpoint_id): + self.tree.uncaught_exceptions += 1 + db = SimpleNamespace( + record_execution_error=record_execution_error, + record_uncaught_exception=record_uncaught_exception, + get_tree=lambda endpoint_id: self.tree, + get_execution_errors=lambda endpoint_id, kind: [e for e in self.tree.execution_errors if e.get('kind') == kind], + count_symbolic_vars=lambda endpoint_id: len(self.tree.symbolic_vars), + get_witness_data=lambda endpoint_id: {'trace_id': 't1'}, + is_stack_overflow_trace=lambda endpoint_id, trace_id: trace_id in self.overflowed_traces) + patcher = mock.patch.object(svcomp.Database, 'instance', return_value=db) + patcher.start() + self.addCleanup(patcher.stop) + self.driver = svcomp.SVCompDriver.__new__(svcomp.SVCompDriver) + self.driver.state = svcomp.State() + + def step(self, category, output): + self.driver.verification_category = category + return self.driver.determine_next_step(svcomp.ExecutionStatus.SUCCESS, output) + + def test_assertion_without_stack_overflow_is_a_violation(self): + action = self.step(svcomp.VerificationCategory.VALID_ASSERT_PRP, [ASSERTION]) + self.assertEqual(action, svcomp.Action.REPORTVERDICT) + self.assertEqual(self.driver.state.verdict, svcomp.Verdict.VIOLATION) + + def test_assertion_after_stack_overflow_is_not_reported(self): + self.overflowed_traces.add('t1') + action = self.step(svcomp.VerificationCategory.VALID_ASSERT_PRP, [ASSERTION]) + self.assertEqual(action, svcomp.Action.SYMBOLICNEXT) + self.assertNotEqual(self.driver.state.verdict, svcomp.Verdict.VIOLATION) + self.assertEqual([e['kind'] for e in self.tree.execution_errors], ['stack_overflow_violation']) + + def test_runtime_exception_after_stack_overflow_is_not_reported(self): + self.overflowed_traces.add('t1') + action = self.step(svcomp.VerificationCategory.NO_RUNTIME_EXCEPTION_PRP, [RUNTIME_EXCEPTION]) + self.assertEqual(action, svcomp.Action.SYMBOLICNEXT) + self.assertNotEqual(self.driver.state.verdict, svcomp.Verdict.VIOLATION) + + def test_swat_exception_still_ends_the_run(self): + self.overflowed_traces.add('t1') + action = self.step(svcomp.VerificationCategory.VALID_ASSERT_PRP, [ASSERTION, '[SWAT Exception]: boom']) + self.assertEqual(action, svcomp.Action.REPORTVERDICT) + self.assertEqual(self.driver.state.verdict, svcomp.Verdict.UNKNOWN) + + def test_overflow_of_another_round_does_not_count(self): + self.overflowed_traces.add('t0') + self.step(svcomp.VerificationCategory.VALID_ASSERT_PRP, [ASSERTION]) + self.assertEqual(self.driver.state.verdict, svcomp.Verdict.VIOLATION) + + def test_no_symbolic_vars_after_stack_overflow_is_unknown(self): + self.overflowed_traces.add('t1') + self.tree.symbolic_vars = set() + action = self.step(svcomp.VerificationCategory.NO_RUNTIME_EXCEPTION_PRP, []) + self.assertEqual(action, svcomp.Action.REPORTVERDICT) + self.assertEqual(self.driver.state.verdict, svcomp.Verdict.UNKNOWN) + + def test_no_symbolic_vars_without_stack_overflow_is_safe(self): + self.tree.symbolic_vars = set() + self.step(svcomp.VerificationCategory.NO_RUNTIME_EXCEPTION_PRP, []) + self.assertEqual(self.driver.state.verdict, svcomp.Verdict.NO_SYMBOLIC_VARS) + + +if __name__ == '__main__': + unittest.main() diff --git a/symbolic-explorer/tests/test_static_initializers.py b/symbolic-explorer/tests/test_static_initializers.py new file mode 100644 index 00000000..b5e950df --- /dev/null +++ b/symbolic-explorer/tests/test_static_initializers.py @@ -0,0 +1,44 @@ +import unittest + +from data.trace.Branch import Branch +from data.trace.Special import Special, CLINIT, CLINIT_END +from data.trace.StaticInitializers import compose_deltas, strip_static_initializers + + +def branch(id, pops=0, pushes=()): + return Branch(id, 't', False, '(assert true)', pops=pops, pushes=tuple(pushes)) + + +def special(id, inst): + return Special(id, 't', False, inst) + + +class StripStaticInitializersTest(unittest.TestCase): + def test_compose(self): + self.assertEqual(compose_deltas((0, ('a', 'b')), (1, ('c',))), (0, ('a', 'c'))) + self.assertEqual(compose_deltas((1, ('a',)), (3, ())), (3, ())) + self.assertEqual(compose_deltas((0, ()), (2, ('x',))), (2, ('x',))) + + def test_initializer_left_out_and_its_frames_carried_over(self): + # b1 in the endpoint; b2 inside (one frame pushed); b3 in a callee after the + # initializer returned (its frame popped, the callee's pushed) + trace = [branch(1), special(10, CLINIT), branch(2, 0, ('clinit',)), special(11, CLINIT_END), + branch(3, 1, ('callee',))] + out = strip_static_initializers(trace) + self.assertEqual([e.id for e in out], [1, 3]) + self.assertEqual((out[1].pops, out[1].pushes), (0, ('callee',))) + + def test_nested_initializers(self): + trace = [special(10, CLINIT), branch(2, 0, ('c1',)), special(12, CLINIT), branch(4, 0, ('c2',)), + special(13, CLINIT_END), special(11, CLINIT_END), branch(5, 2, ())] + out = strip_static_initializers(trace) + self.assertEqual([e.id for e in out], [5]) + self.assertEqual((out[0].pops, out[0].pushes), (0, ())) + + def test_initializer_left_by_an_exception(self): + trace = [branch(1), special(10, CLINIT), branch(2)] + self.assertEqual([e.id for e in strip_static_initializers(trace)], [1]) + + +if __name__ == '__main__': + unittest.main() diff --git a/symbolic-explorer/tests/test_tree.py b/symbolic-explorer/tests/test_tree.py new file mode 100644 index 00000000..9b59256d --- /dev/null +++ b/symbolic-explorer/tests/test_tree.py @@ -0,0 +1,229 @@ +""" +Unit tests for the execution tree: adding traces (also very long ones, and ones that do not fit), +the UNMODELLED_EXCEPTION marker, and copying. + +Run from symbolic-explorer/ with the harness venv (dfs imports the explorer's dependencies): + ../targets/sv-comp/scripts/.venv/bin/python3 -m unittest tests.test_tree -v +""" + +import copy +import os +import sys +import threading +import unittest + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) + +from data.BinaryExecutionTree.Tree import Tree +from data.BinaryExecutionTree.Node import Node +from data.BinaryExecutionTree.Leaf import Leaf +from data.trace.Branch import Branch +from data.trace.Special import Special, UNMODELLED_EXCEPTION +from strategy.DFS import dfs + +# Longer than any recursion limit the interpreter would allow on a thread's C stack. +DEEP = 50_000 + + +def b(id, taken, trace_id='t'): + return Branch(id=id, trace_id=trace_id, has_branched=taken, constraint=f'(c{id})') + + +def s(id, inst='INVOKEVIRTUAL', trace_id='t'): + return Special(id=id, trace_id=trace_id, has_branched=True, inst=inst) + + +def path(tree): + """The nodes from the root along the side each node has a child on (branched first).""" + out, node = [], tree.root + while isinstance(node, Node): + out.append(node) + node = node.branched if node.branched is not None else node.skipped + return out, node + + +def in_thread(fn): + """Runs fn on a fresh thread, as the explorer does, and returns its result or raises its error.""" + result = {} + def run(): + try: + result['value'] = fn() + except BaseException as e: + result['error'] = e + t = threading.Thread(target=run) + t.start() + t.join() + if 'error' in result: + raise result['error'] + return result.get('value') + + +class TreeAddTest(unittest.TestCase): + + def test_single_trace_is_a_path_ending_in_a_leaf(self): + tree = Tree(0) + self.assertTrue(tree.add([b(1, True), s(2), b(3, False)], [], [])) + nodes, end = path(tree) + self.assertEqual([n.id for n in nodes], [1, 2, 3]) + self.assertEqual([n.kind for n in nodes], ['Branch', 'Special', 'Branch']) + self.assertIsInstance(end, Leaf) + self.assertIs(nodes[0].branched, nodes[1]) + self.assertIs(nodes[2].skipped, end) + self.assertIs(nodes[1].parent, nodes[0]) + self.assertEqual(nodes[0].constraint, {'t': '(c1)'}) + + def test_empty_trace_is_a_leaf(self): + tree = Tree(0) + self.assertTrue(tree.add([], [], [])) + self.assertIsInstance(tree.root, Leaf) + + def test_second_trace_shares_the_prefix_and_adds_the_other_side(self): + tree = Tree(0) + tree.add([b(1, True), b(2, True)], [], []) + tree.add([b(1, True, 'u'), b(2, False, 'u'), b(3, True, 'u')], [], []) + n1 = tree.root + n2 = n1.branched + self.assertIsNone(n1.skipped) + self.assertIsInstance(n2.branched, Leaf) + self.assertEqual(n2.skipped.id, 3) + self.assertEqual(n2.constraint, {'t': '(c2)', 'u': '(c2)'}) + + def test_trace_replaces_a_leaf(self): + tree = Tree(0) + tree.add([b(1, True)], [], []) + tree.add([b(1, True), b(2, False)], [], []) + self.assertEqual(tree.root.branched.id, 2) + + def test_shorter_trace_keeps_the_longer_path(self): + tree = Tree(0) + tree.add([b(1, True), b(2, False)], [], []) + self.assertTrue(tree.add([b(1, True)], [], [])) + self.assertEqual(tree.root.branched.id, 2) + + def test_mismatch_keeps_the_tree_and_records_an_incomplete_search(self): + tree = Tree(0) + tree.add([b(1, True), b(2, True)], [], []) + self.assertFalse(tree.add([b(1, True), b(7, True), b(8, True)], [], [])) + nodes, _ = path(tree) + self.assertEqual([n.id for n in nodes], [1, 2]) + self.assertEqual([e['kind'] for e in tree.execution_errors], ['tree_mismatch']) + + def test_kind_mismatch_at_the_same_id_does_not_fit(self): + tree = Tree(0) + tree.add([b(1, True), b(2, True)], [], []) + self.assertFalse(tree.add([b(1, True), s(2)], [], [])) + self.assertEqual(tree.root.branched.kind, 'Branch') + + +class UnmodelledExceptionTest(unittest.TestCase): + """The marker is only in the trace of the run that throws (#65, StringBuilderChars03).""" + + def test_marker_is_not_a_level_of_the_tree(self): + tree = Tree(0) + # A run that does not throw at the call, then one that does and ends there. + self.assertTrue(tree.add([b(1, True), s(5), b(2, True)], [], [])) + self.assertTrue(tree.add([b(1, True), s(5), s(5, UNMODELLED_EXCEPTION)], [], [])) + self.assertTrue(tree.add([b(1, True), s(5), b(2, False)], [], [])) + nodes, _ = path(tree) + self.assertEqual([n.id for n in nodes], [1, 5, 2]) + self.assertEqual(tree.execution_errors, []) + + def test_throwing_run_first(self): + tree = Tree(0) + self.assertTrue(tree.add([b(1, True), s(5), s(5, UNMODELLED_EXCEPTION)], [], [])) + self.assertTrue(tree.add([b(1, True), s(5), b(2, True)], [], [])) + nodes, _ = path(tree) + self.assertEqual([n.id for n in nodes], [1, 5, 2]) + self.assertFalse(any(n.unmodelled_exception for n in nodes)) + + def test_marker_is_kept_on_the_next_node(self): + tree = Tree(0) + tree.add([b(1, True), s(5), s(5, UNMODELLED_EXCEPTION), b(9, True)], [], []) + nodes, _ = path(tree) + self.assertEqual([n.id for n in nodes], [1, 5, 9]) + self.assertEqual([n.unmodelled_exception for n in nodes], [False, False, True]) + # A run that reaches the node without the exception does not clear the mark. + tree.add([b(1, True), s(5), b(9, False)], [], []) + self.assertTrue(nodes[2].unmodelled_exception) + + def test_mark_set_by_a_later_trace_on_an_existing_node(self): + tree = Tree(0) + tree.add([b(1, True), s(5), b(9, True)], [], []) + tree.add([b(1, True), s(5), s(5, UNMODELLED_EXCEPTION), b(9, True)], [], []) + self.assertTrue(path(tree)[0][2].unmodelled_exception) + + def test_handler_path_after_the_exception_does_not_fit(self): + # The throwing run continues in a handler: same branches, different behaviour, with no + # branch to tell them apart. The tree keeps the first path and the search is incomplete. + tree = Tree(0) + tree.add([b(1, True), s(5), b(2, True)], [], []) + self.assertFalse(tree.add([b(1, True), s(5), s(5, UNMODELLED_EXCEPTION), b(30, True)], [], [])) + self.assertEqual([e['kind'] for e in tree.execution_errors], ['tree_mismatch']) + + def test_structural_drops_markers_for_the_divergence_check(self): + trace = [b(1, True), s(5), s(5, UNMODELLED_EXCEPTION), b(9, True)] + self.assertEqual([(e.id, m) for e, m in Tree.structural(trace)], [(1, False), (5, False), (9, True)]) + + +class DeepTreeTest(unittest.TestCase): + """Traces far longer than the recursion limit (#59): a StackOverflowError on a symbolic path.""" + + def deep_trace(self, trace_id='t'): + return [b(i % 7, i % 3 == 0, trace_id) for i in range(DEEP)] + + def test_add_copy_and_walk_a_deep_tree(self): + def work(): + tree = Tree(0) + tree.add(self.deep_trace(), [], []) + # A second trace that takes the other side halfway down. + k = DEEP // 2 + flipped = self.deep_trace('u')[:k + 1] + flipped[k] = b(flipped[k].id, not flipped[k].has_branched, 'u') + assert tree.add(flipped, [], []) + copied = copy.deepcopy(tree) + candidates = dfs(copied, copied.root, set(), set()) + return tree, copied, candidates + tree, copied, candidates = in_thread(work) + nodes, leaves, work = [], [], [copied.root] + while work: + n = work.pop() + if isinstance(n, Node): + nodes.append(n) + work += [c for c in (n.skipped, n.branched) if c is not None] + else: + leaves.append(n) + self.assertEqual((len(nodes), len(leaves)), (DEEP, 2)) + self.assertIsNot(copied.root, tree.root) + nodes, _ = path(copied) + self.assertIs(nodes[1].parent, nodes[0]) + # Every node has one side unexplored, but the one where the second trace left the first. + self.assertEqual(len(candidates), DEEP - 1) + self.assertEqual(tree.execution_errors, []) + self.assertEqual(candidates[0].gid, copied.root.gid) + + def test_copy_keeps_shared_inputs_shared(self): + inputs = [object()] + tree = Tree(0) + tree.add([b(1, True), b(2, True)], inputs, []) + copied = copy.deepcopy(tree) + self.assertIs(copied.root.inputs, copied.root.branched.inputs) + self.assertIsNot(copied.root.inputs, inputs) + + +class DfsOrderTest(unittest.TestCase): + + def test_pre_order_skipped_side_first(self): + tree = Tree(0) + tree.add([b(1, False), b(2, False), b(3, True)], [], []) + tree.add([b(1, True), b(4, True)], [], []) + tree.add([b(1, False), b(2, True), b(5, True)], [], []) + self.assertEqual([n.id for n in dfs(tree, tree.root, set(), set())], [3, 5, 4]) + + def test_specials_are_passed_through(self): + tree = Tree(0) + tree.add([s(10), b(1, True), s(11), b(2, False)], [], []) + self.assertEqual([n.id for n in dfs(tree, tree.root, set(), set())], [1, 2]) + + +if __name__ == '__main__': + unittest.main() diff --git a/symbolic-explorer/timing/TimingManager.py b/symbolic-explorer/timing/TimingManager.py index 3b82240b..7cbb8298 100644 --- a/symbolic-explorer/timing/TimingManager.py +++ b/symbolic-explorer/timing/TimingManager.py @@ -2,11 +2,17 @@ Centralized timing manager for tracking execution time across different stages. This module provides a singleton TimingManager that tracks timing for: +- Static Pre-Analysis: Time the exploration waited for the external CFG extractor (see below) - Symbolic Executor: Time spent running instrumented Java code - SMT Solver: Time spent in Z3 solving constraints - Symbolic Explorer: Time spent in Python coordination logic - Witness Generation: Time spent generating witness files - Witness Validation: Time spent validating witness files + +The stages add up to total_time. ``static_pre_analysis`` is the time the exploration was blocked +by the pre-analysis: all of it by default, and none of it with --parallel-sa, where it runs in the +background in parallel with the exploration. Its own wall time is recorded +separately as ``static_pre_analysis_wall``, which is not a stage and overlaps the others. """ import json @@ -25,6 +31,8 @@ class TimingManager: def __init__(self): """Initialize timing storage.""" + self.static_analysis_time: float = 0.0 # blocking part, a stage of total_time + self.static_analysis_wall_time: float = 0.0 # whole pre-analysis, blocking or in the background self.executor_time: float = 0.0 self.solver_time: float = 0.0 self.explorer_time: float = 0.0 @@ -58,6 +66,21 @@ def stop_total_timer(self): """Stop the total execution timer.""" self.total_end_time = time.perf_counter() + def record_static_analysis_time(self, duration: float, blocking: bool = True): + """ + Record time spent in the static pre-analysis (external CFG extractor). + + Args: + duration: Time in seconds + blocking: Whether the exploration waited for it. Only blocking time is a stage of + total_time; a pre-analysis in the background overlaps the other stages. + """ + self.static_analysis_wall_time += duration + if blocking: + self.static_analysis_time += duration + logger.debug(f"[TIMING] Static Pre-Analysis ({'blocking' if blocking else 'background'}): +{duration:.3f}s " + f"(blocking total: {self.static_analysis_time:.3f}s, wall total: {self.static_analysis_wall_time:.3f}s)") + def record_executor_time(self, duration: float): """ Record time spent in symbolic executor (Java). @@ -69,15 +92,20 @@ def record_executor_time(self, duration: float): self.executor_count += 1 logger.debug(f"[TIMING] Executor: +{duration:.3f}s (total: {self.executor_time:.3f}s)") - def record_solver_time(self, duration: float): + def record_solver_time(self, duration: float, count: bool = True): """ - Record time spent in SMT solver. + Record time spent in the SMT solver. Args: duration: Time in seconds + count: Whether this is a distinct solver query. Pass False for + surrounding Z3 work (SMT-LIB parsing, model extraction) that + belongs to the solver stage but is not a check() call, so the + per-call average stays meaningful. """ self.solver_time += duration - self.solver_count += 1 + if count: + self.solver_count += 1 logger.debug(f"[TIMING] Solver: +{duration:.3f}s (total: {self.solver_time:.3f}s)") def record_explorer_time(self, duration: float): @@ -123,21 +151,26 @@ def get_aggregates(self) -> Dict[str, float]: total_time = self.total_end_time - self.total_start_time else: # Fallback: sum of all components - total_time = (self.executor_time + self.solver_time + self.explorer_time + - self.witness_generation_time + self.witness_validation_time) + total_time = (self.static_analysis_time + self.executor_time + self.solver_time + + self.explorer_time + self.witness_generation_time + + self.witness_validation_time) # Compute explorer time as residual: overhead time not accounted for by other stages - # explorer_time = total_time - executor_time - solver_time - witness_times - computed_explorer_time = max(0.0, total_time - self.executor_time - self.solver_time - - self.witness_generation_time - self.witness_validation_time) + # explorer_time = total_time - static_analysis_time - executor_time - solver_time - witness_times + computed_explorer_time = max(0.0, total_time - self.static_analysis_time - self.executor_time - + self.solver_time - self.witness_generation_time - + self.witness_validation_time) return { 'total_time': total_time, + 'static_pre_analysis': self.static_analysis_time, 'symbolic_executor': self.executor_time, 'smt_solver': self.solver_time, 'symbolic_explorer': computed_explorer_time, # Use computed residual, not measured value 'witness_generation': self.witness_generation_time, - 'witness_validation': self.witness_validation_time + 'witness_validation': self.witness_validation_time, + # Not a stage: overlaps the stages above when the pre-analysis ran in the background. + 'static_pre_analysis_wall': self.static_analysis_wall_time, } def get_statistics(self) -> Dict[str, Any]: @@ -202,18 +235,24 @@ def print_summary(self): logger.info(f"Total Execution Time: {total:>8.2f}s") if total > 0: + logger.info(f" - Static Pre-Analysis: {aggregates['static_pre_analysis']:>8.2f}s ({aggregates['static_pre_analysis']/total*100:>5.1f}%)") logger.info(f" - Symbolic Executor: {aggregates['symbolic_executor']:>8.2f}s ({aggregates['symbolic_executor']/total*100:>5.1f}%)") logger.info(f" - SMT Solver: {aggregates['smt_solver']:>8.2f}s ({aggregates['smt_solver']/total*100:>5.1f}%)") logger.info(f" - Symbolic Explorer: {aggregates['symbolic_explorer']:>8.2f}s ({aggregates['symbolic_explorer']/total*100:>5.1f}%)") logger.info(f" - Witness Generation: {aggregates['witness_generation']:>8.2f}s ({aggregates['witness_generation']/total*100:>5.1f}%)") logger.info(f" - Witness Validation: {aggregates['witness_validation']:>8.2f}s ({aggregates['witness_validation']/total*100:>5.1f}%)") else: + logger.info(f" - Static Pre-Analysis: {aggregates['static_pre_analysis']:>8.2f}s") logger.info(f" - Symbolic Executor: {aggregates['symbolic_executor']:>8.2f}s") logger.info(f" - SMT Solver: {aggregates['smt_solver']:>8.2f}s") logger.info(f" - Symbolic Explorer: {aggregates['symbolic_explorer']:>8.2f}s") logger.info(f" - Witness Generation: {aggregates['witness_generation']:>8.2f}s") logger.info(f" - Witness Validation: {aggregates['witness_validation']:>8.2f}s") + if aggregates['static_pre_analysis_wall'] > aggregates['static_pre_analysis']: + logger.info(f" (Static Pre-Analysis wall: {aggregates['static_pre_analysis_wall']:>8.2f}s, " + f"{aggregates['static_pre_analysis_wall'] - aggregates['static_pre_analysis']:.2f}s of it in the background)") + logger.info("") logger.info("Detailed Breakdown:") logger.info(f" - Executor runs: {statistics['executor_count']} rounds, avg {statistics['avg_executor_time']:.2f}s per round") diff --git a/targets/applications/.gitignore b/targets/applications/.gitignore new file mode 100644 index 00000000..3cfd4c56 --- /dev/null +++ b/targets/applications/.gitignore @@ -0,0 +1 @@ +http-sa-runs/ diff --git a/targets/applications/HTTP_SA.md b/targets/applications/HTTP_SA.md new file mode 100644 index 00000000..1bd05cfe --- /dev/null +++ b/targets/applications/HTTP_SA.md @@ -0,0 +1,129 @@ +# SWAT with static pre-analysis on the applications (HTTP mode) + +Status of the applications under `targets/applications/`, what it took to run them with SWAT's +HTTP driver with and without the static pre-analysis (SA), and where SA helps. Work of 2026-10-24/25 +on branch `niklas/applications-sa` (both repositories). + +## How to run + +```bash +cd targets/applications +./http_sa_experiment.py --jar --cfg --template 'GET|/path?x=' \ + --sa-entry pkg.Controller#method --label name # one endpoint, with and without SA +./http_sa_campaign.py --search-order coverage --time-limit 180 # the cases below +``` + +- Each run starts the instrumented application afresh (ports 18080/18078), runs + `SymbolicExplorer.py -m http`, and keeps `app.log`, `explorer.log` and `http_stats.json` in + `http-sa-runs/

{img}

{CAPTIONS.get(img, '')}

") + parts.append("") + (outdir / "report.html").write_text("\n".join(parts)) + + +def md_to_html(md): + """Just enough Markdown for summary.md: headings, bullet lines, pipe tables, `code`.""" + out, in_table = [], False + esc = lambda s: re.sub(r"`([^`]*)`", r"\1", s.replace("&", "&").replace("<", "<")) # noqa: E731 + for line in md.splitlines(): + if line.startswith("|"): + cells = [c.strip() for c in line.strip("|").split("|")] + if all(set(c) <= {"-"} for c in cells): + continue + if not in_table: + out.append("") + in_table = True + out.append("" + "".join(f"" for c in cells) + "") + else: + out.append("" + "".join(f"" for c in cells) + "") + continue + if in_table: + out.append("
{esc(c)}
{esc(c)}
") + in_table = False + if line.startswith("## "): + out.append(f"

{esc(line[3:])}

") + elif line.startswith("# "): + out.append(f"

{esc(line[2:])}

") + elif line.startswith("- "): + out.append(f"

• {esc(line[2:])}

") + elif line: + out.append(f"

{esc(line)}

") + if in_table: + out.append("") + return "\n".join(out) + + +def resolve_results(arg: str, prp: str) -> Path: + """Return the results file for a results file or a run directory (or its results/ folder).""" + path = Path(arg) + if path.is_file(): + return path + if not path.is_dir(): + raise SystemExit(f"error: {arg} is neither a results file nor a directory") + results_dir = path / "results" if (path / "results").is_dir() else path + matches = sorted(results_dir.glob(f"results_{prp}.prp_*.json")) + if len(matches) != 1: + found = ", ".join(p.name for p in sorted(results_dir.glob("results_*.json"))) or "none" + raise SystemExit(f"error: expected one results_{prp}.prp_*.json in {results_dir}, " + f"found {len(matches)} (results files there: {found})") + return matches[0] + + +def default_outdir(paths, labels) -> Path: + """runs/comparison_
_vs_..., from the labels or else the run directory names.""" + if labels: + names = labels + else: + names = [p.resolve().parent.parent.name.removeprefix("run_") for p in paths] + names = [re.sub(r"[^A-Za-z0-9._-]+", "-", n).strip("-") or "run" for n in names] + return RUNS_DIR / ("comparison_" + "_vs_".join(names)) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("results", nargs="+", + help="results__.json files or run directories; the first is the baseline") + parser.add_argument("--prp", default="valid-assert", + help="property whose results file is used for run directories (default: valid-assert)") + parser.add_argument("-o", "--outdir", + help="output directory (default: runs/comparison__vs_..., from the labels " + "or the run directory names)") + parser.add_argument("--label", action="append", help="label per run, in order") + parser.add_argument("--linear", action="store_true", + help="linear instead of log time axes in the cactus and score plots " + "(01, 01b, 02, 03, right half of 13); the default output directory " + "gets a _linear suffix") + args = parser.parse_args() + + if len(args.results) < 2: + parser.error("need at least two runs") + if len(args.results) > len(RUN_COLORS): + parser.error(f"at most {len(RUN_COLORS)} runs are supported") + if args.label and len(args.label) != len(args.results): + parser.error("--label must be given once per run") + paths = [resolve_results(p, args.prp) for p in args.results] + labels = args.label or [default_label(p) for p in paths] + runs = [load_run(p, lbl) for p, lbl in zip(paths, labels)] + for r in runs: + if not any(t.finished for t in r.tasks.values()): + parser.error(f"{r.path} has no finished tasks with timing data ({len(r.tasks)} tasks in total)") + for r, c in zip(runs, RUN_COLORS): + r.color = c + for r in runs: + if r.sa_mode == "unknown": + print(f"Warning: SA mode of {r.label} is unknown (no run_info.json or gitlog.txt); " + "static pre-analysis plots may treat it wrongly.") + timeouts = {r.timeout for r in runs} + if len(timeouts) > 1: + print(f"Warning: runs used different timeouts {sorted(t or 0 for t in timeouts)}; " + "only compare runs made with the same timeout.") + + global LINEAR + LINEAR = args.linear + outdir = Path(args.outdir) if args.outdir else default_outdir(paths, args.label) + if args.linear and not args.outdir: + outdir = outdir.with_name(outdir.name + "_linear") + outdir.mkdir(parents=True, exist_ok=True) + stages = active_stages(runs) + + images = [] + + def emit(name, fn, *a): + path = outdir / name + if fn(*a, path) is not False: + images.append(name) + print(f"wrote {path}") + + emit("01_cactus.svg", plot_cactus, runs) + emit("01b_cactus_free_sa.svg", plot_cactus_free_sa, runs) + emit("02_score.svg", plot_score, runs) + emit("03_stage_cactus.svg", plot_stage_cactus, runs, stages) + emit("04_scatter.svg", plot_scatter, runs) + emit("05_speedup.svg", plot_speedup, runs) + emit("06_stage_totals.svg", plot_stage_totals, runs, stages) + emit("07_suite_heatmap.svg", plot_suite_heatmap, runs, stages) + emit("08_suite_dots.svg", plot_suite_dots, runs) + emit("09_stage_share.svg", plot_stage_share, runs, stages) + emit("10_iterations.svg", plot_iterations, runs) + emit("11_top_deltas.svg", plot_top_deltas, runs, stages) + emit("12_sa_payoff.svg", plot_sa_payoff, runs) + emit("13_overhead.svg", plot_overhead, runs) + emit("14_sa_background.svg", plot_sa_background, runs) + + summary = summary_md(runs, stages) + (outdir / "summary.md").write_text(summary) + write_html(outdir, images, summary) + print(f"wrote {outdir / 'summary.md'} and {outdir / 'report.html'}") + + +if __name__ == "__main__": + main() diff --git a/targets/sv-comp/scripts/dtypes.py b/targets/sv-comp/scripts/dtypes.py deleted file mode 100644 index 9d2f1c6a..00000000 --- a/targets/sv-comp/scripts/dtypes.py +++ /dev/null @@ -1,38 +0,0 @@ -from pathlib import Path -from typing import Optional, TypedDict -import enum - -class ExpectedVerdict(enum.Enum): - VIOLATION = False - SAFE = True - -class Verdict(enum.Enum): - VIOLATION = "== ERROR" - SAFE = "== OK" - UNKNOWN = "== DONT-KNOW" - NO_SYMBOLIC_VARS = "== NON-SYMBOLIC" - -class VerificationCategory(enum.Enum): - VALID_ASSERT_PRP = "valid-assert.prp" - NO_RUNTIME_EXCEPTION_PRP = "no-runtime-exception.prp" - NO_DEADLOCK_PRP = "no-deadlock.prp" - -class Command(TypedDict): - target_dir: Path - target: Path - log_dir: Path - command: list[str] - -class VerificationResult(TypedDict): - points: int - case: str - -class VerificationTask(TypedDict): - category: VerificationCategory - verdict: ExpectedVerdict - file_path: Path - input_files: list[Path] - command: Optional[Command] - result: Optional[VerificationResult] - - \ No newline at end of file diff --git a/targets/sv-comp/scripts/lib/__init__.py b/targets/sv-comp/scripts/lib/__init__.py index 818811d0..b9a25c3c 100644 --- a/targets/sv-comp/scripts/lib/__init__.py +++ b/targets/sv-comp/scripts/lib/__init__.py @@ -17,7 +17,7 @@ check_port_availability, ExecutionStatus, ) -from .witness import generate_and_validate_witness +from .witness import validate_witness from .analysis.context_loss import main as analyze_context_loss from .comparison import compare_results from .utils import ci_print @@ -50,7 +50,7 @@ "check_port_availability", "ExecutionStatus", # Witness - "generate_and_validate_witness", + "validate_witness", # Analysis "analyze_context_loss", # Comparison diff --git a/targets/sv-comp/scripts/lib/analysis/context_loss.py b/targets/sv-comp/scripts/lib/analysis/context_loss.py index f8019d60..7bd3f5fa 100644 --- a/targets/sv-comp/scripts/lib/analysis/context_loss.py +++ b/targets/sv-comp/scripts/lib/analysis/context_loss.py @@ -1,398 +1,292 @@ #!/usr/bin/env python3 """ -Analyze SV-Comp results to identify context loss methods that need implementation. -Run this script in the directory containing the results JSON file. +Analyze a SV-COMP run from its consolidated per-testcase ``stats.json`` files. -Usage: python3 analyse_ctx_loss.py results_valid-assert.prp_XXXXXX.json +A run lives under ``runs/run_/`` with: + - ``results/results__.json`` : per-target verdict / points / timing + - ``logs//_/stats.json`` : per-testcase missing invocations, + context-loss subset, execution errors + +This reads that structured data directly — no log scraping — and prints a scoring +summary (Correct / Failed / Unk / Error / Timeout), the missing-invocation +superset and its authoritative context-loss subset (issue #25), and the +performance totals (symbolic execution iterations, solver calls) over the run. """ import json -import sys import re +import sys from collections import defaultdict from pathlib import Path +from typing import Optional +# lib/analysis/context_loss.py -> scripts/ +SCRIPT_DIR = Path(__file__).resolve().parents[2] -def extract_context_loss_methods(log_content: str) -> list[str]: - """Extract all method names causing context loss from log content.""" - methods = set() # Use set to dedupe - if not log_content: - return list(methods) - - # Look for "Context loss:" pattern - for match in re.finditer(r'Context loss:\s*(.+?)(?:\n|$)', log_content): - methods.add(match.group(1).strip()) - - # Also look for "Uninstrumented invocation" pattern - for match in re.finditer(r'Uninstrumented invocation.*?:\s*(.+?)(?:\n|$)', log_content): - methods.add(match.group(1).strip()) - - # Look for "Context loss recorded" with method info on previous lines - for match in re.finditer(r'(\S+\.\S+)\s*\n.*Context loss recorded', log_content): - methods.add(match.group(1).strip()) - - return list(methods) - - -def extract_missing_invocations(log_content: str) -> list[str]: - """Extract missing/unimplemented method invocations from log content.""" - methods = set() # Use set to dedupe - if not log_content: - return list(methods) - - # Pattern: "Error visiting Instruction INVOKE* owner/class/method:desc" - # Example: "Error visiting Instruction INVOKEVIRTUAL java/lang/Double/byteValue:()B" - for match in re.finditer( - r'Error visiting Instruction (INVOKE\w+)\s+(\S+?)(?:\s*\(|$)', - log_content - ): - invoke_type = match.group(1) - method_sig = match.group(2) - # Clean up the method signature (remove id info if present) - method_sig = re.sub(r'\s*\(id:.*', '', method_sig) - methods.add(f"{invoke_type} {method_sig}") - - # Pattern: "Not implemented: in " - for match in re.finditer(r'Not implemented:\s*(.+?)\s+in\s+(\S+)', log_content): - method = match.group(1).strip() - cls = match.group(2).strip() - methods.add(f"NOT_IMPLEMENTED {cls}/{method}") - - return list(methods) - - -def extract_symbolic_invocations_from_stats(stats_path: Path) -> list[str]: - """ - Extract symbolic method invocations from stats JSON files. - - Stats files contain entries like: - {"owner":"java/lang/Character","isSymbolic":true,"name":"isUnicodeIdentifierStart","desc":"(I)Z"} - - Returns list of method signatures that have isSymbolic=true (missing implementations). - """ - methods = set() - - # Check both stats_1.json and stats_-1.json - for stats_file in ['stats_1.json', 'stats_-1.json']: - file_path = stats_path / stats_file - if not file_path.exists(): - continue - - try: - with open(file_path) as f: - data = json.load(f) - - if isinstance(data, list): - for entry in data: - if isinstance(entry, dict) and entry.get('isSymbolic', False): - owner = entry.get('owner', '') - name = entry.get('name', '') - desc = entry.get('desc', '') - is_instance = entry.get('isInstance', False) - invoke_type = 'INVOKEVIRTUAL' if is_instance else 'INVOKESTATIC' - if owner and name: - methods.add(f"{invoke_type} {owner}/{name}:{desc}") - except (json.JSONDecodeError, IOError): - pass - - return list(methods) - +SCORE_BUCKETS = ['Correct', 'Failed', 'Unk', 'Error', 'Timeout'] -def extract_errors(log_content: str) -> list[str]: - """Extract error messages from log content.""" - errors = [] - if not log_content: - return errors +# "2026-09-08 12:49:27,348 ERROR [main] [ErrorHandler.java:91] ..." -> the leading +# timestamp is per-testcase noise; every number in the rest of the line (instruction +# offsets, instrumentation ids, source line numbers) varies for the same failure. +LOG_TIMESTAMP_RE = re.compile(r'^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}:\d{2}(?:[.,]\d+)?\s*') +# Guard against a preceding word char so identifiers keep their digits ('Refl2' +# stays distinct from 'Refl4'); only free-standing numbers are templated. +NUMBER_RE = re.compile(r'(? Optional[Path]: + """Return the most recently modified runs/run_* directory, or None.""" + if not runs_root.exists(): + return None + run_dirs = [p for p in runs_root.glob('run_*') if p.is_dir()] + if not run_dirs: + return None + return max(run_dirs, key=lambda p: p.stat().st_mtime) - return errors +def classify(case: str, points, exec_status, error) -> str: + """Bucket a single result into Correct / Failed / Unk / Error / Timeout. -def get_log_dir(logs_dir: Path, task_name: str, property_name: str) -> Path: + Precedence: a timeout or a crash/error is reported as such regardless of points; + otherwise positive points are Correct, negative points are Failed (wrong verdict), + and zero points are Unknown (unknown / non-symbolic). """ - Construct the path to the log directory for a given task. + s = str(exec_status).lower() + if 'timeout' in s: + return 'Timeout' + if 'error' in s or error or 'crash' in str(case).lower(): + return 'Error' + if points and points > 0: + return 'Correct' + if points and points < 0: + return 'Failed' + return 'Unk' + + +def load_json(path: Path): + """Load a JSON file, returning None if it is missing or unreadable.""" + try: + with open(path) as f: + return json.load(f) + except (OSError, json.JSONDecodeError): + return None - Task name format: folder/testname (e.g., "objects/objects03") - Log dir format: logs//_/ - """ - parts = task_name.split('/') - if len(parts) >= 2: - folder = parts[0] - testname = parts[1] - else: - folder = "" - testname = parts[0] - # Property name like "valid-assert.prp" -> "valid-assert" - prop_short = property_name.replace('.prp', '') +def stats_path_for(logs_dir: Path, target: str, category: str) -> Path: + """Path to a testcase's stats.json, derived from its target and property.""" + cat_short = category.replace('.prp', '') + return logs_dir / f"{target}_{cat_short}" / 'stats.json' - return logs_dir / folder / f"{testname}_{prop_short}" +def signature(inv: dict) -> str: + """Render a missing invocation as a stable owner/name:desc signature.""" + return f"{inv['owner']}/{inv['name']}:{inv['desc']}" -def get_log_path(logs_dir: Path, task_name: str, property_name: str) -> Path: - """ - Construct the path to the explorer.log for a given task. - """ - return get_log_dir(logs_dir, task_name, property_name) / "explorer.log" +def error_signature(message: str) -> str: + """Reduce an error log line to a template that groups identical failures. -def get_stats_dir(logs_dir: Path, task_name: str, property_name: str) -> Path: - """ - Construct the path to the stats directory (logs/ subdirectory) for a given task. + Drops the leading timestamp and replaces every number with ``N``, so the same + failure reported by many testcases — which otherwise differs only in its + timestamp, instruction offset, instrumentation id or source line — collapses + into a single group. """ - return get_log_dir(logs_dir, task_name, property_name) / "logs" - - -def read_log_file(log_path: Path) -> str: - """Read log file content, return empty string if not found.""" - try: - if log_path.exists(): - return log_path.read_text(errors='replace') - except Exception: - pass - return "" + return NUMBER_RE.sub('N', LOG_TIMESTAMP_RE.sub('', message.strip())) + + +def analyze_run(run_dir: Path): + """Aggregate and print the analysis for a single run directory.""" + run_dir = Path(run_dir) + results_dir = run_dir / 'results' + logs_dir = run_dir / 'logs' + + results_files = sorted(results_dir.glob('results_*.json')) + if not results_files: + print(f"No results files found in {results_dir}") + return + + print(f"Analyzing run: {run_dir.name}") + print(f" results: {results_dir}") + print(f" logs: {logs_dir}\n") + + # Scoring buckets per category, and total points per category. + score: dict = {} + points_by_cat: dict = {} + + # Missing-invocation aggregation across all testcases of the run. + # signature -> {tasks: set, count: int, context_loss: bool, isSymbolic: bool} + missing: dict = {} + # Errors grouped by normalized message template (see error_signature): + # template -> {tasks: [], variants: set of raw messages} + exec_errors: dict = {} + + # Performance counters summed over all testcases that reported a + # "performance" section: metric -> total / peak, plus how many did. + perf_total: dict = defaultdict(int) + perf_max: dict = defaultdict(int) + perf_tasks = 0 + + total_tasks = 0 + stats_missing = 0 # testcases with no stats.json on disk + + for rf in results_files: + data = load_json(rf) + if not data: + continue + category = data.get('category', rf.stem) + cat_buckets: dict = defaultdict(int) + + for target, tup in data.get('results', {}).items(): + total_tasks += 1 + case = tup[0] if len(tup) > 0 else '' + points = tup[1] if len(tup) > 1 else 0 + exec_status = tup[2] if len(tup) > 2 else '' + error = tup[3] if len(tup) > 3 else False + cat_buckets[classify(case, points, exec_status, error)] += 1 + + stats = load_json(stats_path_for(logs_dir, target, category)) + if stats is None: + stats_missing += 1 + continue + for inv in stats.get('missing_invocations', []): + sig = signature(inv) + m = missing.setdefault( + sig, {'tasks': set(), 'count': 0, 'context_loss': False, 'isSymbolic': False}) + m['tasks'].add(target) + m['count'] += inv.get('count', 1) + m['context_loss'] = m['context_loss'] or inv.get('context_loss', False) + m['isSymbolic'] = m['isSymbolic'] or inv.get('isSymbolic', False) + for e in stats.get('execution_errors', []): + msg = e.get('message', '') + grp = exec_errors.setdefault( + error_signature(msg), {'tasks': [], 'variants': set()}) + grp['tasks'].append(target) + grp['variants'].add(msg) + + perf = stats.get('performance') or {} + if perf: + perf_tasks += 1 + for metric, value in perf.items(): + if isinstance(value, bool) or not isinstance(value, (int, float)): + continue # only numeric counters are summable + perf_total[metric] += value + perf_max[metric] = max(perf_max[metric], value) + + score[category] = dict(cat_buckets) + points_by_cat[category] = data.get('points') + + _print_scoring_summary(score, points_by_cat, total_tasks) + _print_missing_invocations(missing) + _print_performance(perf_total, perf_max, perf_tasks) + _print_execution_errors(exec_errors) + + if stats_missing: + print(f"\nNote: {stats_missing} testcase(s) had no stats.json " + f"(crash before the explorer wrote one, or an older run).") + + +def _print_scoring_summary(score: dict, points_by_cat: dict, total_tasks: int): + print("=" * 78) + print("SCORING SUMMARY") + print("=" * 78) + header = f"{'Category':<26}" + "".join(f"{b:>9}" for b in SCORE_BUCKETS) + f"{'Total':>8}{'Points':>9}" + print(header) + print("-" * len(header)) + + totals = {b: 0 for b in SCORE_BUCKETS} + total_points = 0 + for category in sorted(score): + buckets = score[category] + row_total = sum(buckets.get(b, 0) for b in SCORE_BUCKETS) + pts = points_by_cat.get(category) or 0 + total_points += pts + for b in SCORE_BUCKETS: + totals[b] += buckets.get(b, 0) + if row_total == 0: + continue # don't clutter the table with categories that had no testcases + cells = "".join(f"{buckets.get(b, 0):>9}" for b in SCORE_BUCKETS) + print(f"{category:<26}{cells}{row_total:>8}{pts:>9}") + + print("-" * len(header)) + grand_total = sum(totals.values()) + cells = "".join(f"{totals[b]:>9}" for b in SCORE_BUCKETS) + print(f"{'TOTAL':<26}{cells}{grand_total:>8}{total_points:>9}") + print() + + +def _print_missing_invocations(missing: dict): + print("=" * 78) + cl = {sig: m for sig, m in missing.items() if m['context_loss']} + print(f"MISSING INVOCATIONS (superset: {len(missing)} signatures; " + f"context-loss subset: {len(cl)})") + print("=" * 78) + if not missing: + print("None.") + print() + return + + # Most-impactful first: by number of testcases, then total count. + for sig, m in sorted(missing.items(), key=lambda kv: (-len(kv[1]['tasks']), -kv[1]['count'])): + marker = "CL " if m['context_loss'] else " " + print(f" {marker}{len(m['tasks']):>4} tasks (count={m['count']:<5}) {sig}") + print() + + if cl: + print("-" * 78) + print(f"CONTEXT-LOSS METHODS (subset that caused symbolic context loss):") + for sig in sorted(cl): + print(f" {sig}") + print() + + +def _print_performance(perf_total: dict, perf_max: dict, perf_tasks: int): + print("=" * 78) + print(f"PERFORMANCE ({perf_tasks} testcase(s) reported a performance section)") + print("=" * 78) + if not perf_total: + print("None.") + print() + return + + header = f"{'Metric':<32}{'Total':>14}{'Mean/case':>12}{'Max':>10}" + print(header) + print("-" * len(header)) + for metric in sorted(perf_total): + total = perf_total[metric] + mean = total / perf_tasks if perf_tasks else 0.0 + print(f"{metric:<32}{total:>14}{mean:>12.1f}{perf_max[metric]:>10}") + print() + + +def _print_execution_errors(exec_errors: dict): + if not exec_errors: + return + print("=" * 78) + total_raw = sum(len(g['variants']) for g in exec_errors.values()) + print(f"EXECUTION ERRORS ({len(exec_errors)} distinct; " + f"{total_raw} raw message(s))") + print("=" * 78) + for template, grp in sorted(exec_errors.items(), key=lambda kv: -len(set(kv[1]['tasks']))): + unique = list(dict.fromkeys(grp['tasks'])) + variants = len(grp['variants']) + vtxt = f"({variants} variants)" if variants > 1 else "" + print(f" {len(unique):>4} tasks {vtxt:>14} {template[:300]}") + print(f" e.g. {', '.join(unique[:3])}") + print() def main(): - if len(sys.argv) < 2: - # Find the latest results file - results_dir = Path('.') - json_files = list(results_dir.glob('results_*.json')) - if not json_files: - json_files = list(Path('results').glob('results_*.json')) - if not json_files: - print("Usage: python3 analyse_ctx_loss.py ") - print("No results files found in current directory") - sys.exit(1) - results_file = max(json_files, key=lambda p: p.stat().st_mtime) - print(f"Using latest results file: {results_file}") + """Standalone entry point: analyze a run dir argument or the latest run.""" + if len(sys.argv) >= 2: + run_dir = Path(sys.argv[1]) else: - results_file = Path(sys.argv[1]) - - # Determine logs directory (sibling to results file or parent's logs/) - if results_file.parent.name == 'results': - logs_dir = results_file.parent.parent / 'logs' - else: - logs_dir = results_file.parent / 'logs' - - print(f"Looking for logs in: {logs_dir}") - - with open(results_file) as f: - data = json.load(f) - - results = data['results'] - category = data.get('category', 'valid-assert.prp') - - # Analyze all 0-point tasks - zero_points_by_folder = defaultdict(list) - context_loss_methods = defaultdict(list) - missing_invocations = defaultdict(list) - all_errors = defaultdict(list) - - for name, task in results.items(): - # task format: [status_change, points, execution_status, error_bool, validated, time] - points = task[1] - if points == 0: - folder = name.split('/')[0] - status = task[0] - exec_status = task[2] - - # Read the actual log file from disk - log_path = get_log_path(logs_dir, name, category) - log_content = read_log_file(log_path) - - # Get stats directory for symbolic invocations - stats_dir = get_stats_dir(logs_dir, name, category) - - zero_points_by_folder[folder].append({ - 'name': name, - 'status': status, - 'exec_status': exec_status, - 'log_path': str(log_path), - 'log_content': log_content, - 'log_exists': log_path.exists(), - 'stats_dir': str(stats_dir) - }) - - # Extract context loss methods - methods = extract_context_loss_methods(log_content) - for method in methods: - context_loss_methods[method].append(name) - - # Extract missing invocations from error logs - invocations = extract_missing_invocations(log_content) - for inv in invocations: - missing_invocations[inv].append(name) - - # Extract symbolic invocations from stats JSON files - symbolic_invocations = extract_symbolic_invocations_from_stats(stats_dir) - for inv in symbolic_invocations: - missing_invocations[inv].append(name) - - # Extract errors - errors = extract_errors(log_content) - for error in errors: - all_errors[error].append(name) - - # Print summary by folder - print("\n" + "=" * 80) - print("TASKS WITH 0 POINTS BY FOLDER") - print("=" * 80) - total_zero = 0 - logs_found = 0 - for folder, tasks in sorted(zero_points_by_folder.items(), key=lambda x: -len(x[1])): - folder_logs_found = sum(1 for t in tasks if t['log_exists']) - print(f"{folder}: {len(tasks)} tasks ({folder_logs_found} logs found)") - total_zero += len(tasks) - logs_found += folder_logs_found - print(f"\nTotal: {total_zero} tasks with 0 points ({logs_found} logs found)") - - # Print context loss methods sorted by frequency - print("\n" + "=" * 80) - print("CONTEXT LOSS METHODS (sorted by frequency)") - print("=" * 80) - - if not context_loss_methods: - print("No context loss methods found in logs.") - else: - for method, tasks in sorted(context_loss_methods.items(), key=lambda x: -len(x[1])): - print(f"\n{len(tasks):3d} tasks - {method}") - print(f" Examples: {', '.join(tasks[:3])}") - - # Print missing invocations sorted by frequency - print("\n" + "=" * 80) - print("MISSING/UNIMPLEMENTED INVOCATIONS (sorted by frequency)") - print("=" * 80) - - if not missing_invocations: - print("No missing invocations found in logs.") - else: - for inv, tasks in sorted(missing_invocations.items(), key=lambda x: -len(x[1])): - # Dedupe task list (same task may appear multiple times) - unique_tasks = list(dict.fromkeys(tasks)) - print(f"\n{len(unique_tasks):3d} tasks - {inv}") - print(f" Examples: {', '.join(unique_tasks[:3])}") - - # Print errors sorted by frequency - print("\n" + "=" * 80) - print("ERRORS (sorted by frequency)") - print("=" * 80) - - if not all_errors: - print("No specific errors found in logs.") - else: - for error, tasks in sorted(all_errors.items(), key=lambda x: -len(x[1]))[:20]: - print(f"\n{len(tasks):3d} tasks - {error[:100]}") - print(f" Examples: {', '.join(tasks[:3])}") - - # Detailed breakdown for each folder - print("\n" + "=" * 80) - print("DETAILED BREAKDOWN BY FOLDER") - print("=" * 80) - - for folder in ['autostub', 'jbmc-regression', 'java-ranger-regression', 'algorithms', 'argv-tasks', 'float-nonlinear-calculation', 'securibench', 'objects']: - if folder not in zero_points_by_folder: - continue - - print(f"\n--- {folder} ({len(zero_points_by_folder[folder])} tasks) ---") - - folder_context_loss = defaultdict(list) - folder_missing_invocations = defaultdict(list) - folder_errors = defaultdict(list) - other_issues = defaultdict(list) - tasks_with_missing_invocations = set() - - for task in zero_points_by_folder[folder]: - log_content = task['log_content'] - stats_dir = Path(task['stats_dir']) - methods = extract_context_loss_methods(log_content) - invocations = extract_missing_invocations(log_content) - symbolic_invocations = extract_symbolic_invocations_from_stats(stats_dir) - errors = extract_errors(log_content) - - if methods: - for method in methods: - folder_context_loss[method].append(task['name']) - - all_invocations = invocations + symbolic_invocations - if all_invocations: - tasks_with_missing_invocations.add(task['name']) - for inv in all_invocations: - folder_missing_invocations[inv].append(task['name']) - - # Always categorize by verdict/status (not just when no missing invocations) - if errors: - for error in errors: - folder_errors[error[:80]].append(task['name']) - - if 'timeout' in task['exec_status'].lower(): - other_issues['timeout'].append(task['name']) - elif not task['log_exists']: - other_issues['log file not found'].append(task['name']) - elif not log_content.strip(): - other_issues['log file empty'].append(task['name']) - elif 'DONT-KNOW' in log_content: - other_issues['verdict: DONT-KNOW'].append(task['name']) - elif 'NoThreadContextException' in log_content: - other_issues['NoThreadContextException'].append(task['name']) - elif not methods and not all_invocations: - other_issues['unknown (check log)'].append(task['name']) - - if folder_context_loss: - print(" Context loss methods:") - for method, tasks in sorted(folder_context_loss.items(), key=lambda x: -len(x[1]))[:10]: - unique_tasks = list(dict.fromkeys(tasks)) - print(f" {len(unique_tasks):3d} - {method[:80]}") - - if folder_missing_invocations: - print(f" Missing invocations ({len(tasks_with_missing_invocations)} unique tasks):") - for inv, tasks in sorted(folder_missing_invocations.items(), key=lambda x: -len(x[1]))[:10]: - unique_tasks = list(dict.fromkeys(tasks)) - print(f" {len(unique_tasks):3d} - {inv[:80]}") - - if folder_errors: - print(" Errors:") - for error, tasks in sorted(folder_errors.items(), key=lambda x: -len(x[1]))[:10]: - unique_tasks = list(dict.fromkeys(tasks)) - print(f" {len(unique_tasks):3d} - {error[:80]}") - - if other_issues: - print(" Other issues:") - for issue, tasks in sorted(other_issues.items(), key=lambda x: -len(x[1])): - unique_tasks = list(dict.fromkeys(tasks)) - print(f" {len(unique_tasks):3d} - {issue[:80]}") - - # Print sample logs for debugging - print("\n" + "=" * 80) - print("SAMPLE LOGS (first 5 tasks with 0 points)") - print("=" * 80) - - count = 0 - for name, task in results.items(): - if task[1] == 0 and count < 5: - log_path = get_log_path(logs_dir, name, category) - log_content = read_log_file(log_path) - - print(f"\n{name}:") - print(f" Status: {task[0]}") - print(f" Exec status: {task[2]}") - print(f" Log path: {log_path}") - print(f" Log exists: {log_path.exists()}") - if log_content: - # Show last 500 chars which usually has the verdict - print(f" Log tail: ...{log_content[-500:]}") - else: - print(f" Log: (empty or not found)") - count += 1 + run_dir = find_latest_run(SCRIPT_DIR / '..' / 'runs') + if run_dir is None: + print("Usage: python3 -m lib.analysis.context_loss ") + print("No runs found under runs/") + sys.exit(1) + print(f"Using latest run: {run_dir}") + analyze_run(run_dir) if __name__ == '__main__': diff --git a/targets/sv-comp/scripts/lib/analysis/performance.py b/targets/sv-comp/scripts/lib/analysis/performance.py index 797cedf0..e3985086 100644 --- a/targets/sv-comp/scripts/lib/analysis/performance.py +++ b/targets/sv-comp/scripts/lib/analysis/performance.py @@ -68,6 +68,7 @@ def print_slowest_testcases(log_base_dir: Path = None, top_n: int = 10): logger.info(f"\n{i}. {name}") logger.info(f" Total time: {total_time:.2f}s") logger.info(f" Breakdown:") + logger.info(f" - Static Pre-Analysis: {breakdown.get('static_pre_analysis', 0):.2f}s") logger.info(f" - Symbolic Executor: {breakdown.get('symbolic_executor', 0):.2f}s") logger.info(f" - SMT Solver: {breakdown.get('smt_solver', 0):.2f}s") logger.info(f" - Symbolic Explorer: {breakdown.get('symbolic_explorer', 0):.2f}s") diff --git a/targets/sv-comp/scripts/lib/analysis/timing.py b/targets/sv-comp/scripts/lib/analysis/timing.py index 97ec4e4b..4c3e7a6b 100644 --- a/targets/sv-comp/scripts/lib/analysis/timing.py +++ b/targets/sv-comp/scripts/lib/analysis/timing.py @@ -16,6 +16,23 @@ SCRIPT_DIR = Path(__file__).resolve().parent.parent.parent +# Display names for the timing stages. Derived names are wrong for initialisms +# (`'smt_solver'.title()` gives "Smt Solver"), so spell them out here. +STAGE_LABELS = { + 'static_pre_analysis': 'Static Pre-Analysis', + 'symbolic_executor': 'Symbolic Executor', + 'smt_solver': 'SMT Solver', + 'symbolic_explorer': 'Symbolic Explorer', + 'witness_generation': 'Witness Generation', + 'witness_validation': 'Witness Validation', +} + + +def stage_label(stage: str) -> str: + """Human-readable name for a timing stage.""" + return STAGE_LABELS.get(stage, stage.replace('_', ' ').title()) + + class TimingAnalysis: """Aggregate and analyze timing data from all testcases.""" @@ -58,6 +75,7 @@ def aggregate_timing_from_files(log_base_dir: Path = None) -> Dict: # Aggregate timing data total_timing = { + 'static_pre_analysis': 0.0, 'symbolic_executor': 0.0, 'smt_solver': 0.0, 'symbolic_explorer': 0.0, @@ -133,14 +151,14 @@ def print_timing_statistics(log_base_dir: Path = None): total_avg = sum(avg_timing.values()) for stage, time_val in avg_timing.items(): percent = (time_val / total_avg * 100) if total_avg > 0 else 0 - stage_name = stage.replace('_', ' ').title() + stage_name = stage_label(stage) logger.info(f" {stage_name:<24} {time_val:>8.2f}s ({percent:>5.1f}%)") logger.info(f" {'Total per testcase:':<24}{total_avg:>8.2f}s") logger.info("") logger.info("Min/Max time per testcase (seconds):") for stage in avg_timing.keys(): - stage_name = stage.replace('_', ' ').title() + stage_name = stage_label(stage) logger.info(f" {stage_name:<24} min={min_timing[stage]:>7.2f}s, max={max_timing[stage]:>7.2f}s") logger.info("") @@ -148,7 +166,7 @@ def print_timing_statistics(log_base_dir: Path = None): grand_total = sum(total_timing.values()) for stage, time_val in total_timing.items(): percent = (time_val / grand_total * 100) if grand_total > 0 else 0 - stage_name = stage.replace('_', ' ').title() + stage_name = stage_label(stage) logger.info(f" {stage_name:<24} {time_val:>8.2f}s ({percent:>5.1f}%)") logger.info(f" {'Grand total:':<24}{grand_total:>8.2f}s") logger.info("="*70) diff --git a/targets/sv-comp/scripts/lib/cfg_metrics.py b/targets/sv-comp/scripts/lib/cfg_metrics.py index d3133784..07ec72bd 100644 --- a/targets/sv-comp/scripts/lib/cfg_metrics.py +++ b/targets/sv-comp/scripts/lib/cfg_metrics.py @@ -10,7 +10,7 @@ from typing import Optional, TypedDict, Any from .dtypes import VerificationTask -from .selection import extract_testcases +from .selection import extract_testcases, task_rel_dir logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) @@ -109,18 +109,7 @@ def generate_cfg_command( java_files.append(potential_file) # Create output directory based on task structure (similar to logs) - # Get relative path from sv-benchmarks/java/ - try: - sv_benchmarks_java = Path('sv-benchmarks') / 'java' - # Find the sv-benchmarks parent directory - parts = list(test_case_dir.parts) - if 'sv-benchmarks' in parts: - sb_index = parts.index('sv-benchmarks') - rel_target_path = Path(*parts[sb_index+2:]) # Skip sv-benchmarks/java - else: - rel_target_path = Path(ver_task['file_path'].stem) - except (ValueError, IndexError): - rel_target_path = Path(ver_task['file_path'].stem) + rel_target_path = task_rel_dir(ver_task) target_name = ver_task['file_path'].stem category_suffix = ver_task['category'].value.replace('.prp', '') diff --git a/targets/sv-comp/scripts/lib/command_gen.py b/targets/sv-comp/scripts/lib/command_gen.py index ccfa7f68..4d2d68c1 100644 --- a/targets/sv-comp/scripts/lib/command_gen.py +++ b/targets/sv-comp/scripts/lib/command_gen.py @@ -1,9 +1,12 @@ -from .selection import extract_testcases +from .selection import extract_testcases, task_rel_dir +import datetime +import json import logging import socket from pathlib import Path from pprint import pformat -from .dtypes import Command, VerificationTask +from typing import Optional +from .dtypes import Command, VerificationCategory, VerificationTask logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) @@ -32,13 +35,20 @@ def is_port_available(port: int) -> bool: return False -def generate_command(ver_task: VerificationTask, logging_dir: Path, port: int=8087, config_file:str = 'swat.cfg') -> list[str]: +def generate_command(ver_task: VerificationTask, logging_dir: Path, port: int=8087, config_file:str = 'swat.cfg', no_sa: bool = False, parallel_sa: bool = False, sa_rank_distance: bool = False, sa_no_callback_targets: bool = False, no_sa_slicing: bool = False, no_loop_summaries: bool = False, no_prefer_small_models: bool = False, solver_rlimit: int = 0, cpu_limit_s: Optional[int] = None, search_order: str = 'coverage', sa_no_runtime_exception: bool = False) -> list[str]: test_case_dir = ver_task['file_path'].parent agent_path = BASE_PATH / 'symbolic-executor' / 'lib' / 'symbolic-executor.jar' config_path = SCRIPT_DIR / '..' / config_file library_path = BASE_PATH / 'libs' / 'java-library-path' + sa_path = BASE_PATH.parent / 'cfg-extraction' + # The graph has target points for valid-assert and no-runtime-exception only (the explorer + # enforces this too, see StaticPreAnalysis.SA_TARGETS). Under no-runtime-exception it is opt-in: + # sound, but it costs more than it saves on sv-benchmarks so far (see CLAUDE.md, Targets per property). + sa_properties = (VerificationCategory.VALID_ASSERT_PRP,) + \ + ((VerificationCategory.NO_RUNTIME_EXCEPTION_PRP,) if sa_no_runtime_exception else ()) + no_sa = no_sa or ver_task['category'] not in sa_properties base_command: list[str] = [str(PYENV_PATH), "-u", str(BASE_PATH / 'symbolic-explorer' / 'SymbolicExplorer.py'), "-prp", ver_task['category'].value, @@ -47,25 +57,71 @@ def generate_command(ver_task: VerificationTask, logging_dir: Path, port: int=80 "-z3", str(library_path), "--logdir", str(logging_dir), "--mode", "sv-comp", - '--port', str(port), - "--classpath"] + "--port", str(port), + "--target", "Main"] + \ + ([] if no_sa else ["--sa-path", str(sa_path)]) + \ + (["--parallel-sa"] if parallel_sa and not no_sa else []) + \ + (["--sa-rank-distance"] if sa_rank_distance and not no_sa else []) + \ + (["--sa-no-callback-targets"] if sa_no_callback_targets and not no_sa else []) + \ + (["--no-sa-slicing"] if no_sa_slicing and not no_sa else []) + \ + (["--no-loop-summaries"] if no_loop_summaries else []) + \ + (["--no-prefer-small-models"] if no_prefer_small_models else []) + \ + (["--solver-rlimit", str(solver_rlimit)] if solver_rlimit else []) + \ + ["--search-order", search_order] + \ + (["--cpu-limit", str(cpu_limit_s)] if cpu_limit_s else []) cp: list[str] = [] + inputs: list[str] = [] for input_file in ver_task['input_files']: - cp.append(str((test_case_dir / input_file).resolve())) + inputs.append(str((test_case_dir / input_file).resolve())) + cp.append(inputs[-1]) cp.append(str(BASE_PATH / 'libs' / 'java-library-path' / 'com.microsoft.z3.jar')) - return base_command + cp + # The explorer writes the violation witness (witness.yml/witness.graphml) to /witness + # from the trace of the violating execution; it needs the task's input files and property file. + property_path = test_case_dir.parent / 'properties' / ver_task['category'].value + witness_args: list[str] = ["--witness-dir", "witness", + "--witness-property", str(property_path.resolve()), + "--producer-version", "local", + "--witness-input"] + inputs + return base_command + witness_args + ["--classpath"] + cp -def generate_commands(ver_tasks: list[VerificationTask], config_file: str = 'swat.cfg') -> list[VerificationTask]: + +def new_run_timestamp() -> str: + """Returns a timestamp identifying a single run, shared across all its outputs.""" + return datetime.datetime.now().strftime("%Y%m%d_%H%M%S") + + +def run_dir(run_timestamp: str) -> Path: + """The directory holding a non-debug run's logs/ and results/.""" + return SCRIPT_DIR / '..' / 'runs' / f"run_{run_timestamp}" + + +def write_run_info(run_dir: Path, **settings) -> Path: + """Writes run_info.json with the settings of a run into its run dir. + + sa_mode is 'none', 'sequential' (the default: exploration waits for the pre-analysis) or + 'parallel' (--parallel-sa: the pre-analysis runs in the background). In 'parallel' runs the + static_pre_analysis timing stage is ~0 and its wall time is static_pre_analysis_wall. + """ + path = run_dir / 'run_info.json' + path.write_text(json.dumps({'format_version': 1, **settings}, indent=2) + '\n') + return path + + +def generate_commands(ver_tasks: list[VerificationTask], config_file: str = 'swat.cfg', run_timestamp: Optional[str] = None, no_sa: bool = False, parallel_sa: bool = False, sa_rank_distance: bool = False, sa_no_callback_targets: bool = False, no_sa_slicing: bool = False, no_loop_summaries: bool = False, no_prefer_small_models: bool = False, solver_rlimit: int = 0, cpu_limit_s: Optional[int] = None, search_order: str = 'coverage', sa_no_runtime_exception: bool = False) -> list[VerificationTask]: port = 9000 skipped_ports = [] - # Determine log directory base based on config file - log_dir_base = 'logs-debug' if 'debug' in config_file.lower() else 'logs' + # A single timestamp ties a run's per-testcase logs to its results. + if run_timestamp is None: + run_timestamp = new_run_timestamp() + + # Debug runs keep a timestamped history per target; normal runs share one run dir. + is_debug = 'debug' in config_file.lower() for ver_task in ver_tasks: # Find next available port @@ -80,21 +136,27 @@ def generate_commands(ver_tasks: list[VerificationTask], config_file: str = 'swa # Extract unique identifier for the target (based on its relative path and the name of the .yml file) target_dir = ver_task['file_path'].parent - # Get relative path from sv-benchmarks/java/ - sv_benchmarks_java = Path('sv-benchmarks') / 'java' - rel_target_path = target_dir.relative_to(target_dir.parents[len(target_dir.parts) - list(target_dir.parts).index('sv-benchmarks') - 1] / sv_benchmarks_java) + # Relative path below the benchmark dir's java/ folder + rel_target_path = task_rel_dir(ver_task) target_name = ver_task['file_path'].stem target = rel_target_path / target_name # Include category in log dir to avoid collisions when same file has multiple properties category_suffix = ver_task['category'].value.replace('.prp', '') - logging_dir = SCRIPT_DIR / '..' / log_dir_base / rel_target_path / f"{target_name}_{category_suffix}" + testcase = f"{target_name}_{category_suffix}" + if is_debug: + # runs-debug///run_/logs — grouped per target so a debug + # session keeps the history of its runs together. + logging_dir = SCRIPT_DIR / '..' / 'runs-debug' / rel_target_path / testcase / f"run_{run_timestamp}" / 'logs' + else: + # runs/run_/logs// — all testcases of a run share one run dir. + logging_dir = run_dir(run_timestamp) / 'logs' / rel_target_path / testcase command: Command = { 'target_dir': target_dir, 'target': target, 'log_dir': logging_dir, - 'command': generate_command(ver_task, logging_dir, port=port, config_file=config_file) + 'command': generate_command(ver_task, logging_dir, port=port, config_file=config_file, no_sa=no_sa, parallel_sa=parallel_sa, sa_rank_distance=sa_rank_distance, sa_no_callback_targets=sa_no_callback_targets, no_sa_slicing=no_sa_slicing, no_loop_summaries=no_loop_summaries, no_prefer_small_models=no_prefer_small_models, solver_rlimit=solver_rlimit, cpu_limit_s=cpu_limit_s, search_order=search_order, sa_no_runtime_exception=sa_no_runtime_exception) } ver_task['command'] = command port += 1 diff --git a/targets/sv-comp/scripts/lib/dtypes.py b/targets/sv-comp/scripts/lib/dtypes.py index 9d2f1c6a..d74a6ed5 100644 --- a/targets/sv-comp/scripts/lib/dtypes.py +++ b/targets/sv-comp/scripts/lib/dtypes.py @@ -34,5 +34,7 @@ class VerificationTask(TypedDict): input_files: list[Path] command: Optional[Command] result: Optional[VerificationResult] + # The java/ folder of the benchmark dir the task was found in; logs mirror paths below it. + benchmark_root: Path \ No newline at end of file diff --git a/targets/sv-comp/scripts/lib/execution.py b/targets/sv-comp/scripts/lib/execution.py index fbf1f1d0..d96d9676 100644 --- a/targets/sv-comp/scripts/lib/execution.py +++ b/targets/sv-comp/scripts/lib/execution.py @@ -1,5 +1,5 @@ from contextlib import contextmanager -import logging, os, enum, subprocess, signal +import logging, os, enum, subprocess, signal, threading import socket import time @@ -8,8 +8,9 @@ from pprint import pformat from .dtypes import Verdict, ExpectedVerdict, VerificationCategory, VerificationTask, Command from .utils import ci_print -from .command_gen import extract_testcases, generate_commands -from .witness import generate_and_validate_witness +from .command_gen import extract_testcases, generate_commands, new_run_timestamp, run_dir as make_run_dir +from .witness import validate_witness +from .analysis.timing import stage_label from collections import Counter from pathlib import Path @@ -23,6 +24,13 @@ logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) +# Subprocess output, relayed without a second timestamp and level +output_logger = logging.getLogger(f'{__name__}.output') +if not output_logger.handlers: + _output_handler = logging.StreamHandler() + _output_handler.setFormatter(logging.Formatter('%(message)s')) + output_logger.addHandler(_output_handler) + output_logger.propagate = False SCRIPT_DIR = Path(__file__).resolve().parent.parent # Now in lib/, so go up one more level class ExecutionStatus(enum.Enum): @@ -160,7 +168,7 @@ def determine_result(output: List[str], category: VerificationCategory, expected # Shouldn't reach here, but handle gracefully return 0, 'unknown' -def target_execution(ver_task: VerificationTask, create_witness: bool = True) -> tuple[Path, str, int, ExecutionStatus, bool, Optional[bool], float, dict]: +def target_execution(ver_task: VerificationTask, create_witness: bool = True, testcase_timeout_s: int = 900, print_output: bool = True) -> tuple[Path, str, int, ExecutionStatus, bool, Optional[bool], float, dict]: """ Execute a verification task and return results including execution time. @@ -183,8 +191,7 @@ def target_execution(ver_task: VerificationTask, create_witness: bool = True) -> start_time = time.perf_counter() with pushd(log_dir): - execution_status, output = run_command_with_timeout(cmd) - log_output(output) + execution_status, output = run_command_with_timeout(cmd, testcase_timeout_s, print_output=print_output) error: bool = check_for_dse_error(output) points, case = determine_result(output, ver_task['category'], ver_task['verdict']) @@ -194,9 +201,10 @@ def target_execution(ver_task: VerificationTask, create_witness: bool = True) -> 'case': case } - # Generate and validate witness (with timing) + # Validate the witness the explorer wrote for the violation verdict (with timing); the + # generation time is taken from the explorer's timing data below. if 'violation' in case and create_witness: - validated, witness_generation_time, witness_validation_time = generate_and_validate_witness(ver_task, output) + validated, witness_validation_time = validate_witness(ver_task) elapsed_time = time.perf_counter() - start_time @@ -208,7 +216,8 @@ def target_execution(ver_task: VerificationTask, create_witness: bool = True) -> with open(timing_file, 'r') as f: timing_data = json.load(f) timing_breakdown = timing_data.get('aggregates', {}) - # Update witness timing from our measurements + # Add the witness validation time measured here (generation is timed by the explorer) + witness_generation_time = timing_breakdown.get('witness_generation', witness_generation_time) timing_breakdown['witness_generation'] = witness_generation_time timing_breakdown['witness_validation'] = witness_validation_time @@ -242,15 +251,13 @@ def check_for_dse_error(output: List[str]) -> bool: -def log_output(output: List[str]): - for line in output: - logger.info(line.strip()) - -def run_command_with_timeout(cmd: list[str], timeout: int = 900) -> tuple[ExecutionStatus, list[str]]: - """Executes the given command and returns output from both STDOUT and STDERR.""" +def run_command_with_timeout(cmd: list[str], timeout: int = 900, print_output: bool = False) -> tuple[ExecutionStatus, list[str]]: + """Executes the given command and returns output from both STDOUT and STDERR. With + print_output, each line is relayed as it is (it carries its own timestamp and level) as + soon as the command writes it.""" logger.info(f'[TARGET EXECUTION]: Running symbolic-explorer: {cmd}') - output = [] + output: list[str] = [] with subprocess.Popen( cmd, stdout=subprocess.PIPE, @@ -259,31 +266,44 @@ def run_command_with_timeout(cmd: list[str], timeout: int = 900) -> tuple[Execut bufsize=1, start_new_session=True ) as proc: + def read(): + for line in proc.stdout: + line = line.rstrip('\n') + output.append(line) + if print_output: + output_logger.info(line.rstrip()) + + # Read in a thread, so the timeout also holds while the command writes nothing + reader = threading.Thread(target=read, daemon=True) + reader.start() try: - # Read output and wait for process to finish, with a timeout - stdout, _ = proc.communicate(timeout=timeout) - output = stdout.splitlines() - + proc.wait(timeout=timeout) + reader.join() return ExecutionStatus.SUCCESS, output - - + except subprocess.TimeoutExpired: os.killpg(proc.pid, signal.SIGKILL) # Kill the whole process group to prevent java subprocesses from sticking around proc.kill() - stdout, _ = proc.communicate() - output = stdout.splitlines() - return ExecutionStatus.TIMEOUT, output - + proc.wait() + reader.join(timeout=10) + return ExecutionStatus.TIMEOUT, list(output) + except Exception as e: logger.critical(f'[SVCOMP] Exception: {e}') os.killpg(proc.pid, signal.SIGKILL) proc.kill() - output = [str(e)] - return ExecutionStatus.ERROR, output + [str(e)] + return ExecutionStatus.ERROR, [str(e)] + - +def run_parallel(ver_tasks: list[VerificationTask], max_workers: int=50, create_witness: bool = True, run_dir: Optional[Path] = None, run_timestamp: Optional[str] = None, testcase_timeout_s: int = 900): + # Resolve the run context so all outputs (per-testcase logs + aggregated results) share one dir. + if run_timestamp is None: + run_timestamp = run_dir.name.replace('run_', '') if run_dir is not None else new_run_timestamp() + if run_dir is None: + run_dir = make_run_dir(run_timestamp) + run_dir = Path(run_dir) + results_dir = run_dir / 'results' -def run_parallel(ver_tasks: list[VerificationTask], max_workers: int=50, create_witness: bool = True): max_workers = min(max_workers, len(ver_tasks)) logger.info(f"Running parallel target execution with {max_workers} workers...") @@ -298,7 +318,7 @@ def run_parallel(ver_tasks: list[VerificationTask], max_workers: int=50, create_ # Create a mapping from futures to their corresponding tasks future_to_task = {} for ver_task in ver_tasks: - future = executor.submit(target_execution, ver_task, create_witness) + future = executor.submit(target_execution, ver_task, create_witness, testcase_timeout_s, print_output=False) future_to_task[future] = ver_task for future in concurrent.futures.as_completed(future_to_task): # type: ignore @@ -315,13 +335,13 @@ def run_parallel(ver_tasks: list[VerificationTask], max_workers: int=50, create_ total_elapsed_time = time.perf_counter() - total_start_time logger.info(f"Total parallel execution time: {total_elapsed_time:.2f}s") - evaluate_results(results) + evaluate_results(results, results_dir, run_timestamp) # Run aggregate analysis from timing files from .analysis.timing import TimingAnalysis - TimingAnalysis.print_timing_statistics() + TimingAnalysis.print_timing_statistics(run_dir / 'logs') -def evaluate_results(results): +def evaluate_results(results, results_dir: Path, run_timestamp: str): """Evaluate results with per-category statistics including timing.""" total_points = 0 category_stats = {} @@ -344,6 +364,7 @@ def evaluate_results(results): 'total': 0 }, 'stage_timing': { + 'static_pre_analysis': 0.0, 'symbolic_executor': 0.0, 'smt_solver': 0.0, 'symbolic_explorer': 0.0, @@ -432,6 +453,7 @@ def evaluate_results(results): stage_total = sum(stage_timing.values()) if stage_total > 0: logger.info(f"[{category_name}] Stage Timing Breakdown:") + logger.info(f" - Static Pre-Analysis: {stage_timing['static_pre_analysis']:>8.2f}s ({stage_timing['static_pre_analysis']/stage_total*100:>5.1f}%)") logger.info(f" - Symbolic Executor: {stage_timing['symbolic_executor']:>8.2f}s ({stage_timing['symbolic_executor']/stage_total*100:>5.1f}%)") logger.info(f" - SMT Solver: {stage_timing['smt_solver']:>8.2f}s ({stage_timing['smt_solver']/stage_total*100:>5.1f}%)") logger.info(f" - Symbolic Explorer: {stage_timing['symbolic_explorer']:>8.2f}s ({stage_timing['symbolic_explorer']/stage_total*100:>5.1f}%)") @@ -445,6 +467,7 @@ def evaluate_results(results): # Print aggregate stage timing across all categories aggregate_stage_timing = { + 'static_pre_analysis': 0.0, 'symbolic_executor': 0.0, 'smt_solver': 0.0, 'symbolic_explorer': 0.0, @@ -461,6 +484,7 @@ def evaluate_results(results): logger.info(f"AGGREGATE STAGE TIMING (ALL CATEGORIES)") logger.info(f"{'='*50}") logger.info(f"Total Stage Time: {aggregate_total:>8.2f}s") + logger.info(f" - Static Pre-Analysis: {aggregate_stage_timing['static_pre_analysis']:>8.2f}s ({aggregate_stage_timing['static_pre_analysis']/aggregate_total*100:>5.1f}%)") logger.info(f" - Symbolic Executor: {aggregate_stage_timing['symbolic_executor']:>8.2f}s ({aggregate_stage_timing['symbolic_executor']/aggregate_total*100:>5.1f}%)") logger.info(f" - SMT Solver: {aggregate_stage_timing['smt_solver']:>8.2f}s ({aggregate_stage_timing['smt_solver']/aggregate_total*100:>5.1f}%)") logger.info(f" - Symbolic Explorer: {aggregate_stage_timing['symbolic_explorer']:>8.2f}s ({aggregate_stage_timing['symbolic_explorer']/aggregate_total*100:>5.1f}%)") @@ -473,13 +497,13 @@ def evaluate_results(results): all_elapsed = [t[2] for t in all_times] logger.info(f"Overall timing: min={min(all_elapsed):.2f}s, max={max(all_elapsed):.2f}s, mean={sum(all_elapsed)/len(all_elapsed):.2f}s") - save_results(category_stats, results) + save_results(category_stats, results, results_dir, run_timestamp) # Save aggregate stage timing to CSV - save_aggregate_stage_timing(category_stats) + save_aggregate_stage_timing(category_stats, results_dir, run_timestamp) # Generate timing histogram - generate_timing_histogram(category_stats, results) + generate_timing_histogram(category_stats, results, results_dir, run_timestamp) summary_path = os.getenv("GITHUB_STEP_SUMMARY") if summary_path is not None: @@ -488,17 +512,17 @@ def evaluate_results(results): return total_points -def generate_timing_histogram(category_stats, results, folder='results'): +def generate_timing_histogram(category_stats, results, results_dir: Path, run_timestamp: str): """Generate histogram(s) of execution times using matplotlib.""" if not MATPLOTLIB_AVAILABLE: logger.warning("matplotlib not available, skipping histogram generation. Install with: pip install matplotlib numpy") return - folder = os.path.join(SCRIPT_DIR, '..', folder) + folder = str(results_dir) if not os.path.exists(folder): os.makedirs(folder) - timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") + timestamp = run_timestamp # Collect all times across categories all_times = [] @@ -592,7 +616,7 @@ def generate_timing_histogram(category_stats, results, folder='results'): box_data = [times for times in category_times_dict.values()] box_labels = list(category_times_dict.keys()) - bp = ax2.boxplot(box_data, labels=box_labels, patch_artist=True) + bp = ax2.boxplot(box_data, tick_labels=box_labels, patch_artist=True) for patch, color in zip(bp['boxes'], colors): patch.set_facecolor(color) @@ -610,14 +634,14 @@ def generate_timing_histogram(category_stats, results, folder='results'): logger.info(f"Timing boxplot saved to: {boxplot_path}") -def save_results(category_stats, results, folder='results'): +def save_results(category_stats, results, results_dir: Path, run_timestamp: str): """Save results with one JSON file per category, including timing data.""" - folder = os.path.join(SCRIPT_DIR, '..', folder) + folder = str(results_dir) if not os.path.exists(folder): os.makedirs(folder) - timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") + timestamp = run_timestamp # Save one file per category for category_name, stats in category_stats.items(): @@ -638,7 +662,7 @@ def save_results(category_stats, results, folder='results'): data = { 'category': category_name, 'points': stats['points'], - 'case_occurrences': stats['case_occurrences'], + 'case_occurrences': dict(sorted(stats['case_occurrences'].items())), 'execution_statuses': stats['execution_statuses'], 'dse_errors': stats['dse_errors'], 'witness_stats': stats['witness_stats'], @@ -656,19 +680,20 @@ def save_results(category_stats, results, folder='results'): logger.info(f"[{category_name}] Results saved to {filepath}") -def save_aggregate_stage_timing(category_stats, folder='results'): +def save_aggregate_stage_timing(category_stats, results_dir: Path, run_timestamp: str): """Save aggregate stage timing data to CSV file for easy analysis.""" - folder = os.path.join(SCRIPT_DIR, '..', folder) + folder = str(results_dir) if not os.path.exists(folder): os.makedirs(folder) - timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") + timestamp = run_timestamp filename = f"stage_timing_{timestamp}.csv" filepath = os.path.join(folder, filename) # Calculate aggregate across all categories aggregate_stage_timing = { + 'static_pre_analysis': 0.0, 'symbolic_executor': 0.0, 'smt_solver': 0.0, 'symbolic_explorer': 0.0, @@ -690,8 +715,7 @@ def save_aggregate_stage_timing(category_stats, folder='results'): # Data rows for stage, time_val in aggregate_stage_timing.items(): percentage = (time_val / aggregate_total * 100) if aggregate_total > 0 else 0.0 - stage_name = stage.replace('_', ' ').title() - f.write(f"{stage_name},{time_val:.2f},{percentage:.2f}\n") + f.write(f"{stage_label(stage)},{time_val:.2f},{percentage:.2f}\n") # Total row f.write(f"Total,{aggregate_total:.2f},100.00\n") @@ -798,11 +822,13 @@ def run_single_target(ver_tasks: list[VerificationTask], create_witness: bool = logger.info("\n" + "="*60) logger.info("TIMING BREAKDOWN FOR SINGLE TARGET") logger.info("="*60) - total = sum(timing_breakdown.values()) - for stage, time_val in timing_breakdown.items(): + # 'total_time' is the measured wall time of the run, not a stage; summing it + # along with the stages would double-count and halve every percentage. + stages = {k: v for k, v in timing_breakdown.items() if k != 'total_time'} + total = sum(stages.values()) + for stage, time_val in stages.items(): percent = (time_val / total * 100) if total > 0 else 0 - stage_name = stage.replace('_', ' ').title() - logger.info(f" {stage_name:<25} {time_val:>8.2f}s ({percent:>5.1f}%)") + logger.info(f" {stage_label(stage):<25} {time_val:>8.2f}s ({percent:>5.1f}%)") logger.info(f" {'Total':<25} {total:>8.2f}s") logger.info("="*60) @@ -834,7 +860,8 @@ def run_single_target(ver_tasks: list[VerificationTask], create_witness: bool = config_file = "sv-comp.cfg" else: raise ValueError(f"Invalid mode: {mode}") - ver_tasks_with_commands = generate_commands(ver_tasks, config_file)#[:10] + run_timestamp = new_run_timestamp() + ver_tasks_with_commands = generate_commands(ver_tasks, config_file, run_timestamp=run_timestamp)#[:10] logger.info(f"Generated {len(ver_tasks_with_commands)} commands.") # Check port availability before starting tests (sanity check) @@ -848,7 +875,7 @@ def run_single_target(ver_tasks: list[VerificationTask], create_witness: bool = if mode == Mode.SINGLE_TARGET: # type: ignore run_single_target(ver_tasks_with_commands) elif mode == Mode.PARALLEL: - run_parallel(ver_tasks_with_commands) + run_parallel(ver_tasks_with_commands, run_timestamp=run_timestamp) else: raise ValueError(f"Invalid mode: {mode}") diff --git a/targets/sv-comp/scripts/lib/selection.py b/targets/sv-comp/scripts/lib/selection.py index 48c33798..a8b8bcdc 100644 --- a/targets/sv-comp/scripts/lib/selection.py +++ b/targets/sv-comp/scripts/lib/selection.py @@ -22,8 +22,20 @@ def validate_property_file_format(data: dict[str, Any]) -> bool: "options" in data and "language" in data["options"] and data["options"]["language"] == "Java" ) +def benchmark_root(path: Path) -> Path: + """The folder task paths are taken relative to: the benchmark dir's java/ folder if it has one.""" + path = path.resolve() + return path / 'java' if (path / 'java').is_dir() else path + + +def task_rel_dir(ver_task: VerificationTask) -> Path: + """The task's directory relative to its benchmark root, e.g. argv-tasks/Foo.""" + return ver_task['file_path'].parent.resolve().relative_to(ver_task['benchmark_root']) + + def extract_testcases(path: Path) -> list[VerificationTask]: ver_tasks: list[VerificationTask] = [] + root = benchmark_root(path) # Use Path.rglob to recursively find all YAML files yaml_files = [*path.rglob("*.yml"), *path.rglob("*.yaml")] @@ -45,7 +57,8 @@ def extract_testcases(path: Path) -> list[VerificationTask]: 'file_path': file_path, 'input_files': data["input_files"], 'command': None, - 'result': None + 'result': None, + 'benchmark_root': root, } ver_tasks.append(ver_task) diff --git a/targets/sv-comp/scripts/lib/witness.py b/targets/sv-comp/scripts/lib/witness.py index 16fd26c4..e331abae 100644 --- a/targets/sv-comp/scripts/lib/witness.py +++ b/targets/sv-comp/scripts/lib/witness.py @@ -1,19 +1,40 @@ -import base64 +"""Validation of the witness files the Symbolic Explorer writes for violation verdicts. + +The explorer writes `witness.yml` (format 2.3) and `witness.graphml` to `/witness/` via the +`witnesscreator` package. This module only checks them: + +- YAML: WitnessLint, if `SWAT_WITNESSLINT` names a linter command (e.g. + `"/path/venv/bin/python /path/witnesslint/witnesslinter.py"`); WitnessLint does not check for a + target segment, so the number of `target` waypoints is checked here (exactly one is expected). +- GraphML: wit4java (the result is what `target_execution` reports as `validated`). + +A proper validator setup is tracked in #47. +""" from contextlib import contextmanager import os import enum import logging -import shutil +import re +import shlex import subprocess import time from typing import List, Optional, Tuple from pathlib import Path -from .dtypes import Verdict, ExpectedVerdict, VerificationCategory, VerificationTask, Command +from .dtypes import VerificationTask logger = logging.getLogger(__name__) +# Subprocess output, relayed without a second timestamp and level +output_logger = logging.getLogger(f'{__name__}.output') +if not output_logger.handlers: + _output_handler = logging.StreamHandler() + _output_handler.setFormatter(logging.Formatter('%(message)s')) + output_logger.addHandler(_output_handler) + output_logger.propagate = False SCRIPT_DIR = Path(__file__).resolve().parent.parent # Now in lib/, so go up one more level -WITNESS_CREATION_PATH = (SCRIPT_DIR.parent / 'WitnessCreator' / 'build' / 'libs' / 'WitnessCreator.jar') WITNESS_VALIDATION_PATH = (SCRIPT_DIR.parent / 'wit4java' / 'bin' / 'wit4java') +WITNESSLINT_ENV = 'SWAT_WITNESSLINT' +WITNESS_DIR_NAME = 'witness' +TARGET_WAYPOINT = re.compile(r'^\s*type:\s*["\']?target["\']?\s*$') class ExecutionStatus(enum.Enum): SUCCESS = "success" @@ -21,49 +42,64 @@ class ExecutionStatus(enum.Enum): TIMEOUT = "timeout" +def witness_dir(ver_task: VerificationTask) -> Path: + """The directory the explorer writes the witness files of the task to.""" + command_dict = ver_task.get('command') or {} + return Path(str(command_dict.get('log_dir', SCRIPT_DIR))) / WITNESS_DIR_NAME -def generate_and_validate_witness(ver_task: VerificationTask, output: List[str]) -> Tuple[Optional[bool], float, float]: - """Extract witnesses from `output`, create witness files and validate them. - Returns tuple of (validation_result, generation_time, validation_time): - - validation_result: True/False on validation result or None if no witness was found - - generation_time: Time spent generating witness (seconds) - - validation_time: Time spent validating witness (seconds) +def validate_witness(ver_task: VerificationTask) -> Tuple[Optional[bool], float]: + """Check the witness files the explorer wrote for a violation verdict. + + Returns (validation_result, validation_time): + - validation_result: wit4java's verdict on witness.graphml (True/False), or None if no + witness was written + - validation_time: time spent validating (seconds) """ - # Build an `info` dict compatible with the legacy implementation command_dict = ver_task.get('command', {}) info = { 'command': command_dict.get('command', []), 'log_dir': str(command_dict.get('log_dir', SCRIPT_DIR)) } - - out = extract_last_round(output) - witnesses = extract_markers(out, "[WITNESS]") - if not witnesses: - logger.debug("No witnesses found in output") - return None, 0.0, 0.0 - - witness_dir = assemble_files(info) - - logger.info("Generating witness files") - generation_start = time.perf_counter() - generate_witness(witness_dir, "\n".join(witnesses)) - generation_time = time.perf_counter() - generation_start + directory = witness_dir(ver_task) + yaml_file = directory / 'witness.yml' + graphml_file = directory / 'witness.graphml' + if not yaml_file.exists() and not graphml_file.exists(): + logger.warning(f"No witness files found in {directory}") + return None, 0.0 logger.info("Validating witness") validation_start = time.perf_counter() - validation_result = validate_witness(info) + check_yaml_witness(yaml_file, input_dirs(info)) + validation_result = validate_graphml_witness(info, graphml_file) if graphml_file.exists() else None validation_time = time.perf_counter() - validation_start - return validation_result, generation_time, validation_time + return validation_result, validation_time + + +def input_dirs(info: dict) -> List[str]: + """The task's source directories (classpath entries that are directories).""" + try: + classpath_index = info['command'].index('--classpath') + classpath_entries = info['command'][classpath_index + 1:] + except ValueError: + classpath_entries = [] + return [folder for folder in classpath_entries if Path(folder).is_dir()] + + +def property_file(ver_task: VerificationTask) -> Path: + """The property file of the task, e.g. sv-benchmarks/java/properties/valid-assert.prp.""" + return ver_task['file_path'].parent.parent / 'properties' / ver_task['category'].value def log_output(output: List[str]): + """Relays a subprocess' output as it is: its lines carry their own timestamp and level.""" for line in output: - logger.info(line.strip()) - -def validate_witness(info: dict) -> bool: - """Run the external witness validator and return True if witness is correct.""" + output_logger.info(line.rstrip()) + + +def validate_graphml_witness(info: dict, witness_file: Path) -> bool: + """Run wit4java on the GraphML witness and return True if it reports the witness as correct.""" cmd: List[str] = [str(WITNESS_VALIDATION_PATH), '--packages'] main_folder = '' @@ -84,7 +120,7 @@ def validate_witness(info: dict) -> bool: # ignore non-folders or missing paths continue - cmd.extend(['--witness', str(Path(info['log_dir']) / 'witness' / 'witness.graphml')]) + cmd.extend(['--witness', str(witness_file)]) if main_folder: cmd.append(main_folder) @@ -95,8 +131,8 @@ def validate_witness(info: dict) -> bool: if "wit4java: Witness Correct" in line: return True return False - - + + @contextmanager def pushd(dirname): """Context manager to temporarily change the working directory.""" @@ -109,35 +145,41 @@ def pushd(dirname): os.chdir(original_dir) - -def extract_last_round(out: List[str], marker: str = "============================== ROUND") -> str: - return "\n".join(out).split(marker)[-1] - +def count_target_waypoints(witness_file: Path) -> int: + """The number of `target` waypoints in a YAML witness (a violation witness needs exactly one).""" + with open(witness_file, encoding='utf-8') as f: + return sum(1 for line in f if TARGET_WAYPOINT.match(line)) -def extract_markers(out: str, marker: str) -> List[str]: - res: List[str] = [] - for line in out.split("\n"): - logger.debug(line) - if marker in line: - res.append(line) - return res +def check_yaml_witness(witness_file: Path, inputs: List[str]) -> Optional[bool]: + """Check the YAML witness: exactly one target waypoint, and WitnessLint if configured. -def generate_witness(witness_dir: str, witness: str) -> None: - with pushd(str(SCRIPT_DIR.parent / 'WitnessCreator')): - # base64 encode the string - enc = base64.b64encode(witness.encode()).decode() - cmd = [ - 'java', - '-jar', - str(WITNESS_CREATION_PATH), - enc, - str(witness_dir) - ] - logger.info(f"Running witness creator: {cmd}") - _, output = run_command_with_timeout(cmd) - logger.info("Witness created") - log_output(output) + Returns True iff all performed checks passed, False if one failed, None if there is no file. + """ + if not witness_file.exists(): + logger.warning(f"No YAML witness at {witness_file}") + return None + targets = count_target_waypoints(witness_file) + ok = targets == 1 + logger.info(f"YAML witness target waypoints: {targets} ({'ok' if ok else 'expected exactly one'}) ({witness_file})") + lint = lint_witness(witness_file, inputs) + if lint is False: + ok = False + return ok + + +def lint_witness(witness_file: Path, inputs: List[str]) -> Optional[bool]: + """Run WitnessLint on the YAML witness if SWAT_WITNESSLINT is set; True iff the witness is well-formed.""" + linter = os.environ.get(WITNESSLINT_ENV) + if not linter or not witness_file.exists(): + return None + cmd = shlex.split(linter) + ['--expectViolationWitness', '--expectedWitnessVersion', '2', + '--witness', str(witness_file)] + inputs + status, output = run_command_with_timeout(cmd) + log_output(output) + ok = status == ExecutionStatus.SUCCESS and any('finished with exit code 0' in line for line in output) + logger.info(f"WitnessLint: {'well-formed' if ok else 'FAULTY'} ({witness_file})") + return ok def run_command_with_timeout(cmd: list[str], timeout: int = 180) -> tuple[ExecutionStatus, list[str]]: """Executes the given command and returns output from both STDOUT and STDERR.""" @@ -155,16 +197,16 @@ def run_command_with_timeout(cmd: list[str], timeout: int = 180) -> tuple[Execut # Read output and wait for process to finish, with a timeout stdout, _ = proc.communicate(timeout=timeout) output = stdout.splitlines() - + return ExecutionStatus.SUCCESS, output - - + + except subprocess.TimeoutExpired: proc.kill() stdout, _ = proc.communicate() output = stdout.splitlines() return ExecutionStatus.TIMEOUT, output - + except Exception as e: logger.critical(f'[SVCOMP] Exception: {e}') proc.kill() @@ -172,28 +214,3 @@ def run_command_with_timeout(cmd: list[str], timeout: int = 180) -> tuple[Execut return ExecutionStatus.ERROR, output + [str(e)] -def assemble_files(info: dict) -> str: - witness_dir = Path(info['log_dir']) / 'witness' - witness_dir.mkdir(parents=True, exist_ok=True) - - try: - classpath_index = info['command'].index('--classpath') - classpath_entries = info['command'][classpath_index + 1:] - except ValueError: - classpath_entries = [] - - for folder in classpath_entries: - folder_path = Path(folder) - if not folder_path.exists(): - continue - for src in folder_path.rglob('*.java'): - # Preserve relative path structure (package directories) for WitnessCreator - rel_path = src.relative_to(folder_path) - dst = witness_dir / rel_path - dst.parent.mkdir(parents=True, exist_ok=True) - logger.info(f"Copying {src} to {dst}") - shutil.copy(str(src), str(dst)) - - return str(witness_dir) - - \ No newline at end of file diff --git a/targets/sv-comp/scripts/plot_timing_comparison.py b/targets/sv-comp/scripts/plot_timing_comparison.py new file mode 100644 index 00000000..da244fcb --- /dev/null +++ b/targets/sv-comp/scripts/plot_timing_comparison.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 +""" +Compare the timing behavior of two or more SV-COMP runs. + +For each result file, plots the cumulative number of test cases whose +`total_time` is <= t, for a simulated timeout t on the x-axis. Test cases +without a `total_time` (e.g. real timeouts) never count as finished. + +Usage: + plot_timing_comparison.py RESULTS.json [RESULTS.json ...] [-o out.png] + [--label NAME ...] [--linear] +""" + +import argparse +import json +import re +from pathlib import Path + +import matplotlib.pyplot as plt + +TIMING_INDEX = 6 + +# Fixed categorical order; runs keep their color by position on the command line. +COLORS = ["#2a78d6", "#eb6834", "#1baf7a", "#eda100", + "#e87ba4", "#008300", "#4a3aa7", "#e34948"] + + +def load_total_times(path): + """Return (sorted finished total_times, total number of test cases).""" + with open(path) as f: + results = json.load(f)["results"] + times = sorted( + entry[TIMING_INDEX]["total_time"] + for entry in results.values() + if isinstance(entry[TIMING_INDEX], dict) and "total_time" in entry[TIMING_INDEX] + ) + return times, len(results) + + +def default_label(path): + """Use the run directory name (run_) if it can be found.""" + for part in reversed(Path(path).resolve().parts): + if re.fullmatch(r"run_\d{8}_\d{6}", part): + return part + return Path(path).stem + + +def main(): + parser = argparse.ArgumentParser(description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("results", nargs="+", help="results__.json files") + parser.add_argument("-o", "--output", default="timing_comparison.png", + help="output image (default: timing_comparison.png)") + parser.add_argument("--label", action="append", + help="label per result file, in order (default: run directory name)") + parser.add_argument("--linear", action="store_true", help="use a linear x-axis instead of log") + parser.add_argument("--show", action="store_true", help="open an interactive window") + args = parser.parse_args() + + if len(args.results) > len(COLORS): + parser.error(f"at most {len(COLORS)} result files are supported") + if args.label and len(args.label) != len(args.results): + parser.error("--label must be given once per result file") + labels = args.label or [default_label(p) for p in args.results] + + fig, ax = plt.subplots(figsize=(10, 6)) + total_cases = set() + min_time, max_time = float("inf"), 0.0 + for path, label, color in zip(args.results, labels, COLORS): + times, total = load_total_times(path) + total_cases.add(total) + if times: + min_time, max_time = min(min_time, times[0]), max(max_time, times[-1]) + counts = range(1, len(times) + 1) + ax.step([0.0] + times, [0] + list(counts), where="post", + color=color, linewidth=2, label=f"{label} ({len(times)}/{total} finished)") + + if not args.linear: + ax.set_xscale("log") + ax.set_xlim(left=min_time / 1.5 if max_time else 0.1) + else: + ax.set_xlim(left=0) + ax.set_xlim(right=max_time * 1.1) + ax.set_ylim(0, max(total_cases) * 1.05) + if len(total_cases) == 1: + ax.axhline(total_cases.pop(), color="#999999", linewidth=1, linestyle="--", + label="total test cases") + else: + print(f"Warning: result files contain different numbers of test cases: {sorted(total_cases)}") + + ax.set_xlabel("Simulated timeout [s]" + ("" if args.linear else " (log scale)")) + ax.set_ylabel("Test cases finished (cumulative)") + ax.set_title("SWAT runtime comparison (total_time)") + ax.grid(True, which="major", color="#e0e0e0", linewidth=0.8) + ax.grid(True, which="minor", axis="x", color="#f0f0f0", linewidth=0.5) + ax.set_axisbelow(True) + for side in ("top", "right"): + ax.spines[side].set_visible(False) + ax.legend(loc="lower right", frameon=False) + fig.tight_layout() + + fig.savefig(args.output, dpi=150) + print(f"Saved plot to {args.output}") + if args.show: + plt.show() + + +if __name__ == "__main__": + main() diff --git a/targets/sv-comp/scripts/requirements.txt b/targets/sv-comp/scripts/requirements.txt index c8515db1..8daedad2 100644 --- a/targets/sv-comp/scripts/requirements.txt +++ b/targets/sv-comp/scripts/requirements.txt @@ -12,5 +12,5 @@ starlette==0.46.2 typing-inspection==0.4.0 typing_extensions==4.13.2 uvicorn==0.34.1 -z3-solver==4.14.1.0 +z3-solver==5.3.1.0 requests diff --git a/targets/sv-comp/scripts/run_locally.sh b/targets/sv-comp/scripts/run_locally.sh index 209bdc2a..0915c5ea 100755 --- a/targets/sv-comp/scripts/run_locally.sh +++ b/targets/sv-comp/scripts/run_locally.sh @@ -24,7 +24,7 @@ echo "Clone target Repository" "$SCRIPT_DIR/checkout.sh" echo "Running Tests..." -python3 "$SCRIPT_DIR/target_execution.py" +python3 "$SCRIPT_DIR/svcomp.py" test run --mode parallel --categories valid-assert.prp # Deactivate the virtual environment (optional) deactivate diff --git a/targets/sv-comp/scripts/target_execution.py b/targets/sv-comp/scripts/target_execution.py deleted file mode 100644 index 0184aba0..00000000 --- a/targets/sv-comp/scripts/target_execution.py +++ /dev/null @@ -1,703 +0,0 @@ -from contextlib import contextmanager -import logging, os, enum, subprocess -import socket -import time - -import concurrent.futures, json, datetime -from typing import List, Optional -from pprint import pformat -from dtypes import Verdict, ExpectedVerdict, VerificationCategory, VerificationTask, Command -from util import ci_print -from command_generation import extract_testcases, generate_commands -from witness_validation import generate_and_validate_witness -from collections import Counter -from pathlib import Path - -try: - import matplotlib.pyplot as plt - import numpy as np - MATPLOTLIB_AVAILABLE = True -except ImportError: - MATPLOTLIB_AVAILABLE = False - - -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger(__name__) -SCRIPT_DIR = Path(__file__).resolve().parent - -class ExecutionStatus(enum.Enum): - SUCCESS = "success" - ERROR = "error" - TIMEOUT = "timeout" - - - -class Mode(enum.Enum): - SINGLE_TARGET = "single" - PARALLEL = "parallel" - - -def check_port_availability(ver_tasks: list[VerificationTask]) -> tuple[bool, list[int]]: - """ - Check if all ports required by verification tasks are available. - - Args: - ver_tasks: List of verification tasks with command configuration - - Returns: - Tuple of (all_available: bool, occupied_ports: list[int]) - """ - occupied_ports = [] - required_ports = set() - - # Extract all required ports from commands - for ver_task in ver_tasks: - command = ver_task.get('command') - if command: - cmd_list = command.get('command', []) - # Find --port argument - try: - port_idx = cmd_list.index('--port') - if port_idx + 1 < len(cmd_list): - port = int(cmd_list[port_idx + 1]) - required_ports.add(port) - except (ValueError, IndexError): - continue - - if not required_ports: - logger.info("No ports found in commands") - return True, [] - - logger.info(f"Checking availability of {len(required_ports)} ports (range: {min(required_ports)}-{max(required_ports)})") - - # Check each port - for port in sorted(required_ports): - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - try: - # Try to bind to the port - sock.bind(('127.0.0.1', port)) - sock.close() - except OSError: - # Port is already in use - occupied_ports.append(port) - - if occupied_ports: - logger.error(f"The following {len(occupied_ports)} ports are already in use: {occupied_ports}") - logger.error("Please kill the processes using these ports before running tests.") - logger.error(f"You can find processes with: lsof -i :{occupied_ports[0]} (for example)") - return False, occupied_ports - else: - logger.info(f"All {len(required_ports)} ports are available") - return True, [] - - -@contextmanager -def pushd(dirname): - """Context manager to temporarily change the working directory.""" - original_dir = os.getcwd() - logger.info(f"Changing directory to: {dirname}") - os.chdir(dirname) - try: - yield - finally: - os.chdir(original_dir) - -def reset_log_dir(log_dir) -> None: - logger.info("Resetting log directory...") - if os.path.exists(log_dir): - os.system(f"rm -rf {log_dir}") - os.makedirs(log_dir) - - -def parse_verdict_from_output(output: List[str], category: VerificationCategory) -> Optional[Verdict]: - """Parse the verdict for a specific category from the output.""" - - for line in output: - if f"[VERDICT {category.value}]" in line: - if "== OK" in line: - return Verdict.SAFE - elif "== ERROR" in line: - return Verdict.VIOLATION - elif "== DONT-KNOW" in line: - return Verdict.UNKNOWN - elif "== NON-SYMBOLIC" in line: - return Verdict.NO_SYMBOLIC_VARS - - return None # No verdict found for this category - - -def determine_result(output: List[str], category: VerificationCategory, expected_verdict: ExpectedVerdict) -> tuple[int, str]: - """Determine points and case based on the actual vs expected verdict for a single category.""" - actual_verdict = parse_verdict_from_output(output, category) - - # If no verdict found in output, treat as crash - if actual_verdict is None: - if expected_verdict == ExpectedVerdict.SAFE: - return 0, 'safe -> crash' - else: - return 0, 'violation -> crash' - - # Compare actual vs expected - if expected_verdict == ExpectedVerdict.SAFE: - if actual_verdict == Verdict.SAFE: - return 2, 'safe -> safe' - elif actual_verdict == Verdict.VIOLATION: - return -16, 'safe -> violation' - elif actual_verdict == Verdict.UNKNOWN: - return 0, 'safe -> unknown' - elif actual_verdict == Verdict.NO_SYMBOLIC_VARS: - return 0, 'safe -> non-symbolic' - else: # expected_verdict == ExpectedVerdict.VIOLATION - if actual_verdict == Verdict.SAFE: - return -32, 'violation -> safe' - elif actual_verdict == Verdict.VIOLATION: - return 1, 'violation -> violation' - elif actual_verdict == Verdict.UNKNOWN: - return 0, 'violation -> unknown' - elif actual_verdict == Verdict.NO_SYMBOLIC_VARS: - return 0, 'violation -> non-symbolic' - - # Shouldn't reach here, but handle gracefully - return 0, 'unknown' - -def target_execution(ver_task: VerificationTask) -> tuple[Path, str, int, ExecutionStatus, bool, Optional[bool], float]: - """ - Execute a verification task and return results including execution time. - - Returns: - Tuple of (target, case, points, execution_status, error, validated, elapsed_time_seconds) - """ - command = ver_task['command'] - if command is None: - raise ValueError(f"No command found for verification task: {ver_task['file_path']}") - - target = command["target"] - ci_print(f'::group:: Executing target: {target} ({ver_task["category"].value}) ------') - log_dir = command["log_dir"] - reset_log_dir(log_dir) - cmd = command["command"] - validated = None - - start_time = time.perf_counter() - - with pushd(log_dir): - execution_status, output = run_command_with_timeout(cmd) - log_output(output) - error: bool = check_for_dse_error(output) - points, case = determine_result(output, ver_task['category'], ver_task['verdict']) - - # Store result in the verification task - ver_task['result'] = { - 'points': points, - 'case': case - } - - # ToDo: add witness validation - if 'violation' in case: - validated = generate_and_validate_witness(ver_task, output) - - elapsed_time = time.perf_counter() - start_time - - logger.info(f"{ver_task['category'].value} | Points: {points}, Case: {case}, Execution Status: {execution_status}, Error: {error}, Validated: {validated}, Time: {elapsed_time:.2f}s") - - ci_print(f"::endgroup::") - return target, case, points, execution_status, error, validated, elapsed_time - -def check_for_dse_error(output: List[str]) -> bool: - for line in output: - if "SWAT Assertion failed" in line: - return True - return False - - - -def log_output(output: List[str]): - for line in output: - logger.info(line.strip()) - -def run_command_with_timeout(cmd: list[str], timeout: int = 90) -> tuple[ExecutionStatus, list[str]]: - """Executes the given command and returns output from both STDOUT and STDERR.""" - - logger.info(f'[TARGET EXECUTION]: Running symbolic-explorer: {cmd}') - output = [] - with subprocess.Popen( - cmd, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - universal_newlines=True, - bufsize=1 - ) as proc: - try: - # Read output and wait for process to finish, with a timeout - stdout, _ = proc.communicate(timeout=timeout) - output = stdout.splitlines() - - return ExecutionStatus.SUCCESS, output - - - except subprocess.TimeoutExpired: - proc.kill() - stdout, _ = proc.communicate() - output = stdout.splitlines() - return ExecutionStatus.TIMEOUT, output - - except Exception as e: - logger.critical(f'[SVCOMP] Exception: {e}') - proc.kill() - output = [str(e)] - return ExecutionStatus.ERROR, output + [str(e)] - - - -def run_parallel(ver_tasks: list[VerificationTask], max_workers: int=50): - max_workers = min(max_workers, len(ver_tasks)) - - logger.info(f"Running parallel target execution with {max_workers} workers...") - - results = {} - for category in VerificationCategory: - results[category.value] = {} - - total_start_time = time.perf_counter() - - with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor: - # Create a mapping from futures to their corresponding tasks - future_to_task = {} - for ver_task in ver_tasks: - future = executor.submit(target_execution, ver_task) - future_to_task[future] = ver_task - - for future in concurrent.futures.as_completed(future_to_task): # type: ignore - try: - # Get the task associated with this future - ver_task = future_to_task[future] - target, case, points, execution_status, error, validated, elapsed_time = future.result() - logger.info(f"{ver_task['category'].value} | Points: {points}, Case: {case}, Execution Status: {execution_status}, Error: {error}, Validated: {validated}, Time: {elapsed_time:.2f}s") - - results[ver_task['category'].value][target] = (case, points, execution_status.value, error, validated, elapsed_time) - except Exception as e: - logger.error(f'Exception occurred: {e}') - - total_elapsed_time = time.perf_counter() - total_start_time - logger.info(f"Total parallel execution time: {total_elapsed_time:.2f}s") - - evaluate_results(results) - -def evaluate_results(results): - """Evaluate results with per-category statistics including timing.""" - total_points = 0 - category_stats = {} - all_times = [] # Collect all execution times for histogram - - # Initialize per-category stats - for category_name in results.keys(): - category_stats[category_name] = { - 'points': 0, - 'case_occurrences': {}, - 'execution_statuses': {}, - 'dse_errors': 0, - 'witness_stats': {}, - 'timing': { - 'times': [], - 'min': None, - 'max': None, - 'mean': None, - 'median': None, - 'total': 0 - } - } - - # Process each category's results - for category_name, category_results in results.items(): - category_points = 0 - category_times = [] - - for target, result_tuple in category_results.items(): - # Handle both old format (5 elements) and new format (6 elements with timing) - if len(result_tuple) == 6: - case, points, execution_status, error, validated, elapsed_time = result_tuple - else: - case, points, execution_status, error, validated = result_tuple - elapsed_time = 0.0 - - # Update category stats - category_points += points - category_times.append(elapsed_time) - all_times.append((category_name, target, elapsed_time)) - - stats = category_stats[category_name] - - if case in stats['case_occurrences']: - stats['case_occurrences'][case] += 1 - else: - stats['case_occurrences'][case] = 1 - - if execution_status in stats['execution_statuses']: - stats['execution_statuses'][execution_status] += 1 - else: - stats['execution_statuses'][execution_status] = 1 - - if error: - stats['dse_errors'] += 1 - - if validated in stats['witness_stats']: - stats['witness_stats'][validated] += 1 - else: - stats['witness_stats'][validated] = 1 - - logger.info(f"[{category_name}] Target: {target}, Execution Status: {execution_status}, Case: {case}, Points: {points}, Error: {error}, Validated: {validated}, Time: {elapsed_time:.2f}s") - - # Calculate timing statistics for this category - if category_times: - stats = category_stats[category_name] - stats['timing']['times'] = category_times - stats['timing']['min'] = min(category_times) - stats['timing']['max'] = max(category_times) - stats['timing']['mean'] = sum(category_times) / len(category_times) - stats['timing']['total'] = sum(category_times) - sorted_times = sorted(category_times) - mid = len(sorted_times) // 2 - stats['timing']['median'] = sorted_times[mid] if len(sorted_times) % 2 else (sorted_times[mid-1] + sorted_times[mid]) / 2 - - category_stats[category_name]['points'] = category_points - total_points += category_points - - logger.info(f"\n[{category_name}] Category Points: {category_points}") - logger.info(f"[{category_name}] Case occurrences: {category_stats[category_name]['case_occurrences']}") - logger.info(f"[{category_name}] Execution statuses: {category_stats[category_name]['execution_statuses']}") - logger.info(f"[{category_name}] DSE errors: {category_stats[category_name]['dse_errors']}") - logger.info(f"[{category_name}] Witness stats: {category_stats[category_name]['witness_stats']}") - - # Log timing stats - timing = category_stats[category_name]['timing'] - if timing['min'] is not None: - logger.info(f"[{category_name}] Timing: min={timing['min']:.2f}s, max={timing['max']:.2f}s, mean={timing['mean']:.2f}s, median={timing['median']:.2f}s, total={timing['total']:.2f}s") - - logger.info(f"\n{'='*50}") - logger.info(f"TOTAL POINTS (ALL CATEGORIES): {total_points}") - logger.info(f"{'='*50}") - - # Print overall timing summary - if all_times: - all_elapsed = [t[2] for t in all_times] - logger.info(f"Overall timing: min={min(all_elapsed):.2f}s, max={max(all_elapsed):.2f}s, mean={sum(all_elapsed)/len(all_elapsed):.2f}s") - - save_results(category_stats, results) - - # Generate timing histogram - generate_timing_histogram(category_stats, results) - - summary_path = os.getenv("GITHUB_STEP_SUMMARY") - if summary_path is not None: - print_github_ci(total_points, category_stats, results) - - return total_points - - -def generate_timing_histogram(category_stats, results, folder='results'): - """Generate histogram(s) of execution times using matplotlib.""" - if not MATPLOTLIB_AVAILABLE: - logger.warning("matplotlib not available, skipping histogram generation. Install with: pip install matplotlib numpy") - return - - folder = os.path.join(SCRIPT_DIR, '..', folder) - if not os.path.exists(folder): - os.makedirs(folder) - - timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") - - # Collect all times across categories - all_times = [] - category_times_dict = {} - - for category_name, stats in category_stats.items(): - times = stats['timing'].get('times', []) - if times: - category_times_dict[category_name] = times - all_times.extend(times) - - if not all_times: - logger.info("No timing data available for histogram generation") - return - - # Create figure with subplots - num_categories = len(category_times_dict) - fig_height = 4 + (num_categories * 3) # Dynamic height based on categories - fig, axes = plt.subplots(num_categories + 1, 1, figsize=(12, fig_height)) - - if num_categories == 0: - axes = [axes] - elif num_categories == 1: - axes = [axes[0], axes[1]] - - # Overall histogram - ax_overall = axes[0] - all_times_array = np.array(all_times) - - # Use log scale bins if there's wide variation - if max(all_times) > 10 * min(all_times) and min(all_times) > 0: - bins = np.logspace(np.log10(max(0.1, min(all_times))), np.log10(max(all_times) + 0.1), 30) - ax_overall.set_xscale('log') - else: - bins = 30 - - ax_overall.hist(all_times_array, bins=bins, edgecolor='black', alpha=0.7, color='steelblue') - ax_overall.axvline(np.mean(all_times_array), color='red', linestyle='--', linewidth=2, label=f'Mean: {np.mean(all_times_array):.2f}s') - ax_overall.axvline(np.median(all_times_array), color='green', linestyle='--', linewidth=2, label=f'Median: {np.median(all_times_array):.2f}s') - ax_overall.set_xlabel('Execution Time (seconds)') - ax_overall.set_ylabel('Frequency') - ax_overall.set_title(f'Overall Execution Time Distribution (n={len(all_times)})') - ax_overall.legend() - ax_overall.grid(True, alpha=0.3) - - # Add text box with statistics - stats_text = f'Min: {min(all_times):.2f}s\nMax: {max(all_times):.2f}s\nMean: {np.mean(all_times_array):.2f}s\nMedian: {np.median(all_times_array):.2f}s\nTotal: {sum(all_times):.2f}s' - ax_overall.text(0.98, 0.95, stats_text, transform=ax_overall.transAxes, fontsize=9, - verticalalignment='top', horizontalalignment='right', - bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.5)) - - # Per-category histograms - colors = plt.cm.Set2(np.linspace(0, 1, num_categories)) # type: ignore - for idx, (category_name, times) in enumerate(category_times_dict.items()): - ax = axes[idx + 1] - times_array = np.array(times) - - # Use same binning logic - if max(times) > 10 * min(times) and min(times) > 0: - cat_bins = np.logspace(np.log10(max(0.1, min(times))), np.log10(max(times) + 0.1), 25) - ax.set_xscale('log') - else: - cat_bins = 25 - - ax.hist(times_array, bins=cat_bins, edgecolor='black', alpha=0.7, color=colors[idx]) - ax.axvline(np.mean(times_array), color='red', linestyle='--', linewidth=2, label=f'Mean: {np.mean(times_array):.2f}s') - ax.axvline(np.median(times_array), color='green', linestyle='--', linewidth=2, label=f'Median: {np.median(times_array):.2f}s') - ax.set_xlabel('Execution Time (seconds)') - ax.set_ylabel('Frequency') - ax.set_title(f'{category_name} Execution Time Distribution (n={len(times)})') - ax.legend() - ax.grid(True, alpha=0.3) - - # Add stats box - cat_stats_text = f'Min: {min(times):.2f}s\nMax: {max(times):.2f}s\nMean: {np.mean(times_array):.2f}s\nMedian: {np.median(times_array):.2f}s' - ax.text(0.98, 0.95, cat_stats_text, transform=ax.transAxes, fontsize=9, - verticalalignment='top', horizontalalignment='right', - bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.5)) - - plt.tight_layout() - - # Save histogram - histogram_path = os.path.join(folder, f'timing_histogram_{timestamp}.png') - plt.savefig(histogram_path, dpi=150, bbox_inches='tight') - plt.close() - logger.info(f"Timing histogram saved to: {histogram_path}") - - # Also generate a box plot for comparison - if num_categories > 1: - fig2, ax2 = plt.subplots(figsize=(10, 6)) - box_data = [times for times in category_times_dict.values()] - box_labels = list(category_times_dict.keys()) - - bp = ax2.boxplot(box_data, labels=box_labels, patch_artist=True) - for patch, color in zip(bp['boxes'], colors): - patch.set_facecolor(color) - - ax2.set_ylabel('Execution Time (seconds)') - ax2.set_title('Execution Time Comparison by Category') - ax2.grid(True, alpha=0.3, axis='y') - - # Rotate labels if they're long - plt.xticks(rotation=45, ha='right') - plt.tight_layout() - - boxplot_path = os.path.join(folder, f'timing_boxplot_{timestamp}.png') - plt.savefig(boxplot_path, dpi=150, bbox_inches='tight') - plt.close() - logger.info(f"Timing boxplot saved to: {boxplot_path}") - - -def save_results(category_stats, results, folder='results'): - """Save results with one JSON file per category, including timing data.""" - folder = os.path.join(SCRIPT_DIR, '..', folder) - - if not os.path.exists(folder): - os.makedirs(folder) - - timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") - - # Save one file per category - for category_name, stats in category_stats.items(): - # Convert Path keys to strings for JSON serialization - category_results = results[category_name] - results_serializable = {str(k): v for k, v in category_results.items()} - - # Prepare timing stats (exclude raw times array for cleaner JSON) - timing_stats = { - 'min': stats['timing'].get('min'), - 'max': stats['timing'].get('max'), - 'mean': stats['timing'].get('mean'), - 'median': stats['timing'].get('median'), - 'total': stats['timing'].get('total'), - 'count': len(stats['timing'].get('times', [])) - } - - data = { - 'category': category_name, - 'points': stats['points'], - 'case_occurrences': stats['case_occurrences'], - 'execution_statuses': stats['execution_statuses'], - 'dse_errors': stats['dse_errors'], - 'witness_stats': stats['witness_stats'], - 'timing': timing_stats, - 'results': results_serializable - } - - filename = f"results_{category_name}_{timestamp}.json" - filepath = os.path.join(folder, filename) - - with open(filepath, 'w') as f: - json.dump(data, f, indent=4) - - logger.info(f"[{category_name}] Results saved to {filepath}") - - -def print_github_ci(total_points, category_stats, results): - """Print GitHub CI summary with per-category breakdown.""" - def bool_to_emoji(value): - if value is None: - return "❌" - return "✅" if value else "❌" - - summary_path = os.getenv("GITHUB_STEP_SUMMARY") - if summary_path is None: - raise EnvironmentError("GITHUB_STEP_SUMMARY is not set. Are you running inside GitHub Actions?") - - with open(summary_path, "a") as f: - # Header - f.write("## 🧾 Symbolic Execution Summary\n\n") - f.write(f"**Total Points (All Categories):** `{total_points}`\n\n") - - # Per-Category Summary - f.write("### 📊 Points by Category\n") - f.write("| Category | Points |\n") - f.write("|----------|--------|\n") - for category_name, stats in category_stats.items(): - f.write(f"| {category_name} | {stats['points']} |\n") - f.write("\n") - - # Detailed breakdown per category - for category_name, stats in category_stats.items(): - f.write(f"## {category_name}\n\n") - - # Case Occurrences - if stats['case_occurrences']: - f.write("### 📊 Case Occurrences\n") - f.write("| Case | Count |\n") - f.write("|------|-------|\n") - for k, v in stats['case_occurrences'].items(): - f.write(f"| {k} | {v} |\n") - f.write("\n") - - # Execution Statuses - if stats['execution_statuses']: - f.write("### ⚙️ Execution Statuses\n") - f.write("| Status | Count |\n") - f.write("|--------|-------|\n") - for k, v in stats['execution_statuses'].items(): - f.write(f"| {k} | {v} |\n") - f.write("\n") - - # DSE Errors - f.write(f"**DSE Errors:** `{stats['dse_errors']}`\n\n") - - # Timing Stats - timing = stats.get('timing', {}) - if timing.get('min') is not None: - f.write("### ⏱️ Timing Statistics\n") - f.write("| Metric | Value |\n") - f.write("|--------|-------|\n") - f.write(f"| Min | {timing['min']:.2f}s |\n") - f.write(f"| Max | {timing['max']:.2f}s |\n") - f.write(f"| Mean | {timing['mean']:.2f}s |\n") - f.write(f"| Median | {timing['median']:.2f}s |\n") - f.write(f"| Total | {timing['total']:.2f}s |\n") - f.write("\n") - - # Witness Stats - if stats['witness_stats'] and any(stats['witness_stats'].values()): - f.write("### 🧷 Witness Stats\n") - f.write("| Validated | Count |\n") - f.write("|-----------|-------|\n") - for validated, count in stats['witness_stats'].items(): - f.write(f"| {bool_to_emoji(validated)} | {count} |\n") - f.write("\n") - - -def select_target(ver_tasks: list[VerificationTask], target_name: str) -> Optional[VerificationTask]: - """Select a verification task by target name.""" - for ver_task in ver_tasks: - command = ver_task.get('command') - if command and str(command['target']) == target_name: - return ver_task - return None - - -def run_single_target(ver_tasks: list[VerificationTask]): - target = "autostub/Boolean_public_static_int_java_lang_Boolean_compare_boolean_boolean" - - ver_task = select_target(ver_tasks, target) - if ver_task is None: - logger.error(f"Target {target} not found.") - return - logger.info(f"Running single target: {target}") - ver_task["command"]["log_dir"] = Path(str(ver_task["command"]["log_dir"]).replace('/logs/', '/logs-debug/')) # type: ignore - target_execution(ver_task) - - - -if __name__ == "__main__": - mode = Mode.PARALLEL # Mode.SINGLE_TARGET - - # Configure which properties/categories to run (None = all) - selected_categories = [ - VerificationCategory.VALID_ASSERT_PRP, - #VerificationCategory.NO_RUNTIME_EXCEPTION_PRP - ] - - logger.info(f"Running target execution script in {mode.value} mode...") - test_dir = SCRIPT_DIR.parent / "sv-benchmarks" - logger.info(f"Base directory: {test_dir}") - ver_tasks: list[VerificationTask] = extract_testcases(test_dir) - logger.info(f"Extracted {len(ver_tasks)} test cases.") - - # Filter by selected categories if specified - if selected_categories is not None: # type: ignore - ver_tasks = [task for task in ver_tasks if task['category'] in selected_categories] - logger.info(f"Filtered to {len(ver_tasks)} test cases for categories: {[c.value for c in selected_categories]}") - - if mode == Mode.SINGLE_TARGET: # type: ignore - config_file = "swat-debug.cfg" - elif mode == Mode.PARALLEL: - config_file = "sv-comp.cfg" - else: - raise ValueError(f"Invalid mode: {mode}") - ver_tasks_with_commands = generate_commands(ver_tasks, config_file)#[:10] - logger.info(f"Generated {len(ver_tasks_with_commands)} commands.") - - # Check port availability before starting tests (sanity check) - # Note: Command generation already skips occupied ports, but this serves as a final validation - ports_available, occupied_ports = check_port_availability(ver_tasks_with_commands) - if not ports_available: - logger.warning(f"WARNING: {len(occupied_ports)} ports became occupied between command generation and execution.") - logger.warning(f"Occupied ports: {occupied_ports}") - logger.warning("This may cause some tests to fail. Consider restarting the script.") - - if mode == Mode.SINGLE_TARGET: # type: ignore - run_single_target(ver_tasks_with_commands) - elif mode == Mode.PARALLEL: - run_parallel(ver_tasks_with_commands) - else: - raise ValueError(f"Invalid mode: {mode}") - - logger.info("Done.") - diff --git a/targets/sv-comp/scripts/target_selection.py b/targets/sv-comp/scripts/target_selection.py deleted file mode 100644 index a72fa955..00000000 --- a/targets/sv-comp/scripts/target_selection.py +++ /dev/null @@ -1,90 +0,0 @@ -from typing import Any -from collections import defaultdict -import yaml -import logging -from pathlib import Path -from pprint import pformat -from dtypes import ExpectedVerdict, VerificationCategory, VerificationTask - -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger(__name__) - -SCRIPT_DIR = Path(__file__).resolve().parent - -def validate_property_file_format(data: dict[str, Any]) -> bool: - # Validate the structure - return ( - "format_version" in data and data["format_version"] == "2.0" and - "input_files" in data and isinstance(data["input_files"], list) and - "properties" in data and isinstance(data["properties"], list) and - len(data["properties"]) >= 1 and # type: ignore - "property_file" in data["properties"][0] and "expected_verdict" in data["properties"][0] and - "options" in data and "language" in data["options"] and data["options"]["language"] == "Java" - ) - -def extract_testcases(path: Path) -> list[VerificationTask]: - ver_tasks: list[VerificationTask] = [] - - # Use Path.rglob to recursively find all YAML files - yaml_files = [*path.rglob("*.yml"), *path.rglob("*.yaml")] - for file_path in yaml_files: - logger.debug(f"Loading YAML file: {file_path}") - with open(file_path, 'r') as f: - try: - data: dict[str, Any] = yaml.safe_load(f) - except yaml.YAMLError as e: - raise ValueError(f"Error parsing YAML file {file_path}: {e}") - - if not validate_property_file_format(data): - raise ValueError(f"Invalid format in YAML file: {file_path}") - - for property in data["properties"]: - ver_task: VerificationTask = { - 'category': VerificationCategory(property['property_file'].split('/')[-1]), - 'verdict': ExpectedVerdict(property['expected_verdict']), - 'file_path': file_path, - 'input_files': data["input_files"], - 'command': None, - 'result': None - } - ver_tasks.append(ver_task) - - logger.debug(f"Loaded test case: {file_path}") - - return ver_tasks - - -def print_testcase_statistics(ver_tasks: list[VerificationTask]) -> None: - """Print concise statistics about test cases by category.""" - # Count verdicts for each category using nested defaultdict - stats: defaultdict[VerificationCategory, defaultdict[ExpectedVerdict, int]] = defaultdict(lambda: defaultdict(int)) - - for task in ver_tasks: - stats[task['category']][task['verdict']] += 1 - - logger.info(f"{'='*60}") - logger.info(f"Test Case Statistics (Total: {len(ver_tasks)})") - logger.info(f"{'='*60}") - logger.info(f"{'Property':<30} {'Safe':<10} {'Violation':<10}") - logger.info(f"{'-'*60}") - - for category in VerificationCategory: - safe_count = stats[category][ExpectedVerdict.SAFE] - violation_count = stats[category][ExpectedVerdict.VIOLATION] - logger.info(f"{category.value:<30} {safe_count:<10} {violation_count:<10}") - - logger.info(f"{'='*60}") - - -if __name__ == "__main__": - logger.info("Testing testcase extraction script...") - test_dir = (SCRIPT_DIR / ".." / "sv-benchmarks").resolve() - logger.info(f"Base directory: {test_dir}") - test_cases = extract_testcases(test_dir) - logger.info(f"Loaded {len(test_cases)} test cases from directory: {test_dir}") - - print_testcase_statistics(test_cases) - - logger.info("\nFirst 3 test cases:") - for i, case in enumerate(test_cases[:3], 1): - logger.info(f"\nTest Case {i}:\n{pformat(dict(case), width=100)}") \ No newline at end of file diff --git a/targets/sv-comp/scripts/util.py b/targets/sv-comp/scripts/util.py deleted file mode 100644 index f88041ee..00000000 --- a/targets/sv-comp/scripts/util.py +++ /dev/null @@ -1,10 +0,0 @@ -import os -from typing import Any - - -IS_RUNNING_IN_CI: bool = os.getenv("CI", "false") == "true" - -def ci_print(message: Any) -> None: - """Print a message formatted for CI systems.""" - if IS_RUNNING_IN_CI: - print(f"{message}") \ No newline at end of file diff --git a/targets/sv-comp/scripts/verify_timing.py b/targets/sv-comp/scripts/verify_timing.py deleted file mode 100644 index 17c27fff..00000000 --- a/targets/sv-comp/scripts/verify_timing.py +++ /dev/null @@ -1,198 +0,0 @@ -#!/usr/bin/env python3 -""" -Quick verification script to manually check timing aggregation. -""" -import json -from pathlib import Path -import statistics - -# Find all timing files -script_dir = Path(__file__).resolve().parent -log_dir = script_dir.parent / 'logs' -timing_files = list(log_dir.rglob('timing_data.json')) - -print(f"Found {len(timing_files)} timing files\n") - -# Manually aggregate -totals = { - 'symbolic_executor': 0.0, - 'smt_solver': 0.0, - 'symbolic_explorer': 0.0, - 'witness_generation': 0.0, - 'witness_validation': 0.0 -} - -count = 0 -anomalies = [] -corrected_totals = { - 'symbolic_executor': 0.0, - 'smt_solver': 0.0, - 'symbolic_explorer': 0.0, - 'witness_generation': 0.0, - 'witness_validation': 0.0 -} - -# Collect per-testcase values for median computation -per_testcase = { - 'symbolic_executor': [], - 'smt_solver': [], - 'symbolic_explorer': [], - 'witness_generation': [], - 'witness_validation': [] -} - -# Store examples for each category -zero_solver_examples = [] -zero_explorer_examples = [] -both_zero_examples = [] - -for tf in timing_files: # Process ALL files - try: - with open(tf) as f: - data = json.load(f) - agg = data.get('aggregates', {}) - - # Check for anomalies (where solver/explorer > executor) - executor = agg.get('symbolic_executor', 0) - solver = agg.get('smt_solver', 0) - explorer = agg.get('symbolic_explorer', 0) - - if solver > executor or explorer > executor: - anomalies.append((tf.parent.name, executor, solver, explorer)) - - # Add to totals using OLD values (what's in file) - for key in totals: - totals[key] += agg.get(key, 0) - - # Also compute CORRECTED explorer time (as residual) - total_time = agg.get('total_time', 0) - witness_gen = agg.get('witness_generation', 0) - witness_val = agg.get('witness_validation', 0) - corrected_explorer = max(0.0, total_time - executor - solver - witness_gen - witness_val) - - corrected_totals['symbolic_executor'] += executor - corrected_totals['smt_solver'] += solver - corrected_totals['symbolic_explorer'] += corrected_explorer - corrected_totals['witness_generation'] += witness_gen - corrected_totals['witness_validation'] += witness_val - - # Store per-testcase values - per_testcase['symbolic_executor'].append(executor) - per_testcase['smt_solver'].append(solver) - per_testcase['symbolic_explorer'].append(corrected_explorer) - per_testcase['witness_generation'].append(witness_gen) - per_testcase['witness_validation'].append(witness_val) - - # Collect examples of zero-time cases - testcase_name = tf.parent.name - if solver == 0.0 and len(zero_solver_examples) < 10: - zero_solver_examples.append((testcase_name, executor, solver, corrected_explorer, total_time)) - if corrected_explorer == 0.0 and len(zero_explorer_examples) < 10: - zero_explorer_examples.append((testcase_name, executor, solver, corrected_explorer, total_time)) - if solver == 0.0 and corrected_explorer == 0.0 and len(both_zero_examples) < 10: - both_zero_examples.append((testcase_name, executor, solver, corrected_explorer, total_time)) - - count += 1 - except Exception as e: - print(f"ERROR reading {tf}: {e}") - -print(f"\nProcessed {count} files") -print(f"Found {len(anomalies)} anomalies where solver/explorer > executor") - -# Show examples of zero-time cases -print(f"\n{'='*60}") -print(f"EXAMPLES OF ZERO-TIME CASES:") -print(f"{'='*60}") - -print(f"\nTestcases with ZERO solver time (first 10):") -for name, executor, solver, explorer, total in zero_solver_examples: - print(f" {name}:") - print(f" Total: {total:.2f}s, Executor: {executor:.2f}s, Solver: {solver:.2f}s, Explorer: {explorer:.2f}s") - -print(f"\nTestcases with ZERO explorer overhead (first 10):") -for name, executor, solver, explorer, total in zero_explorer_examples: - print(f" {name}:") - print(f" Total: {total:.2f}s, Executor: {executor:.2f}s, Solver: {solver:.2f}s, Explorer: {explorer:.2f}s") - -print(f"\nTestcases with BOTH solver and explorer ZERO (first 10):") -for name, executor, solver, explorer, total in both_zero_examples: - print(f" {name}:") - print(f" Total: {total:.2f}s, Executor: {executor:.2f}s, Solver: {solver:.2f}s, Explorer: {explorer:.2f}s") - -if anomalies: - print(f"\n{'='*60}") - print(f"ANOMALIES (solver/explorer > executor):") - print(f"{'='*60}") - print(f"\nFirst 20 anomalies:") - for name, executor, solver, explorer in anomalies[:20]: - print(f" {name}: executor={executor:.2f}s, solver={solver:.2f}s, explorer={explorer:.2f}s") - -print(f"\n{'='*60}") -print(f"OLD (BUGGY) AGGREGATION (all {count} testcases):") -print(f"{'='*60}") - -total_time = sum(totals.values()) -for key, val in totals.items(): - avg = val / count if count > 0 else 0 - pct = (avg / (total_time/count) * 100) if total_time > 0 else 0 - print(f"{key:<24} avg={avg:>8.2f}s ({pct:>5.1f}%)") - -print(f"\n{'='*60}") -print(f"CORRECTED AGGREGATION - MEAN (explorer as residual):") -print(f"{'='*60}") - -corrected_total_time = sum(corrected_totals.values()) -for key, val in corrected_totals.items(): - avg = val / count if count > 0 else 0 - pct = (avg / (corrected_total_time/count) * 100) if corrected_total_time > 0 else 0 - print(f"{key:<24} avg={avg:>8.2f}s ({pct:>5.1f}%)") - -print(f"\n{'='*60}") -print(f"CORRECTED AGGREGATION - MEDIAN (explorer as residual):") -print(f"{'='*60}") - -# Compute median for each stage -medians = {key: statistics.median(vals) for key, vals in per_testcase.items()} -median_total = sum(medians.values()) - -for key, median_val in medians.items(): - pct = (median_val / median_total * 100) if median_total > 0 else 0 - print(f"{key:<24} median={median_val:>8.2f}s ({pct:>5.1f}%)") - -# Show distribution to validate the 0.00 medians -print(f"\n{'='*60}") -print(f"DISTRIBUTION ANALYSIS (to validate median values):") -print(f"{'='*60}") - -for key in ['symbolic_executor', 'smt_solver', 'symbolic_explorer']: - vals = per_testcase[key] - zero_count = sum(1 for v in vals if v == 0.0) - nonzero_count = len(vals) - zero_count - min_val = min(vals) if vals else 0 - max_val = max(vals) if vals else 0 - - print(f"\n{key}:") - print(f" Zero values: {zero_count:>4} ({zero_count/len(vals)*100:>5.1f}%)") - print(f" Non-zero values: {nonzero_count:>4} ({nonzero_count/len(vals)*100:>5.1f}%)") - print(f" Min: {min_val:.4f}s, Max: {max_val:.2f}s") - if nonzero_count > 0: - nonzero_vals = [v for v in vals if v > 0] - median_nonzero = statistics.median(nonzero_vals) - print(f" Median (non-zero only): {median_nonzero:.4f}s") - -print(f"\n{'='*60}") -print("Now comparing with TimingAnalysis results...") -print(f"{'='*60}") - -try: - from lib.analysis.timing import TimingAnalysis - stats = TimingAnalysis.aggregate_timing_from_files() - - if stats: - avg_timing = stats['avg_timing'] - total_avg = sum(avg_timing.values()) - for stage, time_val in avg_timing.items(): - percent = (time_val / total_avg * 100) if total_avg > 0 else 0 - print(f"{stage:<24} avg={time_val:>8.2f}s ({percent:>5.1f}%)") -except Exception as e: - print(f"Could not run TimingAnalysis: {e}") diff --git a/targets/sv-comp/scripts/witness_validation.py b/targets/sv-comp/scripts/witness_validation.py deleted file mode 100644 index 158821b9..00000000 --- a/targets/sv-comp/scripts/witness_validation.py +++ /dev/null @@ -1,187 +0,0 @@ -import base64 -from contextlib import contextmanager -import os -import enum -import logging -import shutil -import subprocess -from typing import List, Optional -from pathlib import Path -from dtypes import Verdict, ExpectedVerdict, VerificationCategory, VerificationTask, Command - -logger = logging.getLogger(__name__) -SCRIPT_DIR = Path(__file__).resolve().parent -WITNESS_CREATION_PATH = (SCRIPT_DIR.parent / 'WitnessCreator' / 'build' / 'libs' / 'WitnessCreator.jar') -WITNESS_VALIDATION_PATH = (SCRIPT_DIR.parent / 'wit4java' / 'bin' / 'wit4java') - -class ExecutionStatus(enum.Enum): - SUCCESS = "success" - ERROR = "error" - TIMEOUT = "timeout" - - - -def generate_and_validate_witness(ver_task: VerificationTask, output: List[str]) -> Optional[bool]: - """Extract witnesses from `output`, create witness files and validate them. - - Returns True/False on validation result or None if no witness was found. - """ - # Build an `info` dict compatible with the legacy implementation - command_dict = ver_task.get('command', {}) - info = { - 'command': command_dict.get('command', []), - 'log_dir': str(command_dict.get('log_dir', SCRIPT_DIR)) - } - - out = extract_last_round(output) - witnesses = extract_markers(out, "[WITNESS]") - if not witnesses: - logger.debug("No witnesses found in output") - return None - - witness_dir = assemble_files(info) - logger.info("Generating witness files") - generate_witness(witness_dir, "\n".join(witnesses)) - logger.info("Validating witness") - return validate_witness(info) - - -def log_output(output: List[str]): - for line in output: - logger.info(line.strip()) - -def validate_witness(info: dict) -> bool: - """Run the external witness validator and return True if witness is correct.""" - cmd: List[str] = [str(WITNESS_VALIDATION_PATH), '--packages'] - main_folder = '' - - # find classpath entries (items after --classpath) - try: - classpath_index = info['command'].index('--classpath') - classpath_entries = info['command'][classpath_index + 1:] - except ValueError: - classpath_entries = [] - - for folder in classpath_entries: - try: - if 'Main.java' in os.listdir(folder): - main_folder = folder - else: - cmd.append(folder) - except Exception: - # ignore non-folders or missing paths - continue - - cmd.extend(['--witness', str(Path(info['log_dir']) / 'witness' / 'witness.graphml')]) - if main_folder: - cmd.append(main_folder) - - _, output = run_command_with_timeout(cmd) - logger.info("Witness Validation Output") - log_output(output) - for line in output: - if "wit4java: Witness Correct" in line: - return True - return False - - -@contextmanager -def pushd(dirname): - """Context manager to temporarily change the working directory.""" - original_dir = os.getcwd() - logger.info(f"Changing directory to: {dirname}") - os.chdir(dirname) - try: - yield - finally: - os.chdir(original_dir) - - - -def extract_last_round(out: List[str], marker: str = "============================== ROUND") -> str: - return "\n".join(out).split(marker)[-1] - - - -def extract_markers(out: str, marker: str) -> List[str]: - res: List[str] = [] - for line in out.split("\n"): - logger.debug(line) - if marker in line: - res.append(line) - return res - -def generate_witness(witness_dir: str, witness: str) -> None: - with pushd(str(SCRIPT_DIR.parent / 'WitnessCreator')): - # base64 encode the string - enc = base64.b64encode(witness.encode()).decode() - cmd = [ - 'java', - '-jar', - str(WITNESS_CREATION_PATH), - enc, - str(witness_dir) - ] - logger.info(f"Running witness creator: {cmd}") - _, output = run_command_with_timeout(cmd) - logger.info("Witness created") - log_output(output) - -def run_command_with_timeout(cmd: list[str], timeout: int = 180) -> tuple[ExecutionStatus, list[str]]: - """Executes the given command and returns output from both STDOUT and STDERR.""" - - logger.info(f'[WITNESS] Running: {cmd}') - output = [] - with subprocess.Popen( - cmd, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - universal_newlines=True, - bufsize=1 - ) as proc: - try: - # Read output and wait for process to finish, with a timeout - stdout, _ = proc.communicate(timeout=timeout) - output = stdout.splitlines() - - return ExecutionStatus.SUCCESS, output - - - except subprocess.TimeoutExpired: - proc.kill() - stdout, _ = proc.communicate() - output = stdout.splitlines() - return ExecutionStatus.TIMEOUT, output - - except Exception as e: - logger.critical(f'[SVCOMP] Exception: {e}') - proc.kill() - output = [str(e)] - return ExecutionStatus.ERROR, output + [str(e)] - - -def assemble_files(info: dict) -> str: - witness_dir = Path(info['log_dir']) / 'witness' - witness_dir.mkdir(parents=True, exist_ok=True) - - try: - classpath_index = info['command'].index('--classpath') - classpath_entries = info['command'][classpath_index + 1:] - except ValueError: - classpath_entries = [] - - for folder in classpath_entries: - folder_path = Path(folder) - if not folder_path.exists(): - continue - for src in folder_path.rglob('*.java'): - # Preserve relative path structure (package directories) for WitnessCreator - rel_path = src.relative_to(folder_path) - dst = witness_dir / rel_path - dst.parent.mkdir(parents=True, exist_ok=True) - logger.info(f"Copying {src} to {dst}") - shutil.copy(str(src), str(dst)) - - return str(witness_dir) - - \ No newline at end of file diff --git a/targets/sv-comp/sv-comp.cfg b/targets/sv-comp/sv-comp.cfg index d66a6727..a25c083a 100644 --- a/targets/sv-comp/sv-comp.cfg +++ b/targets/sv-comp/sv-comp.cfg @@ -29,3 +29,12 @@ constraintsOnly=false solver.mode=HTTP svcomp.randomInputs=false + +# Report loop-head states with the trace; the explorer prunes below repeated ones +loopStateMatching=true + +# Merge diamonds and counting loops into formulas (symbolic/summary); --no-loop-summaries turns it off +loopSummaries=true + +# End a run whose application threads are deadlocked (thread/DeadlockWatchdog) +deadlockWatchdog=true diff --git a/targets/sv-comp/swat-benchmarks/.gitignore b/targets/sv-comp/swat-benchmarks/.gitignore new file mode 100644 index 00000000..80ff33f8 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/.gitignore @@ -0,0 +1,2 @@ +__pycache__/ +java/real-tools/lib-*/ diff --git a/targets/sv-comp/swat-benchmarks/README.md b/targets/sv-comp/swat-benchmarks/README.md new file mode 100644 index 00000000..9a70f80e --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/README.md @@ -0,0 +1,94 @@ +# swat-benchmarks + +Local SV-COMP-style tasks that measure where SWAT's static pre-analysis (SA) pruning helps, and +whether it stays sound. Results and findings: [RESULTS.md](RESULTS.md). + +Layout is the same as `sv-benchmarks/java/`: `java///Main.java` with +`java//.yml` next to it, `common/` (Verifier, ObjectFactory) and `properties/` copied +from the main suite. Only `valid-assert.prp` is used. `_true` tasks are SAFE, `_false` tasks are +VIOLATION. + +## Suites + +All tasks are generated by `scripts/gen.py`; the sources are committed so they can be read and +diffed, the `.class` files are not (they are gitignored repo-wide) and are built by +`scripts/build.sh`. + +Benefit families (`scripts/families.py`), each variant for n in {2, 4, 8, 16, 32} (dead-callees: +1..6, irrelevant-prefix: 2, 4, 6, 8), SAFE and VIOLATION. Every task has the same single assertion, +`target(a, b)`: three nested input-dependent branches, satisfiable in the VIOLATION variant +(a = 1001, b = 2996) and not in the SAFE one (it differs in one comparison). + +| suite | shape | variants | +|---|---|---| +| `dispatcher` | `cmd` picks one of n commands; n-1 handlers with 4 independent branches (16 paths), the last command calls `target` | `switch`, `ifchain`, `instance` (instance methods), `loop` (two dispatch rounds: the event-loop shape) | +| `validation-gauntlet` | n checks `if (v_i < i) reject`, then `target` behind two calls | `return`, `report` (3-branch error report, then return), `throw` (caught in main), `exit` (`System.exit`) | +| `error-handling` | n steps, each in try/catch; the catch logs (3 branches) and returns; `target` on the success path | `explicit` (throw one frame down), `deep` (two frames), `arith` (implicit ArithmeticException), `bounds` (implicit AIOOBE) | +| `dead-callees` | n helpers with 2 branches each, on paths that cannot reach `target` | `suffix` (after `target`), `guarded` (`if (mode == 1) {...; return;}`), `shared` (the same helper is also called before `target`: needs a context-sensitive walk) | +| `virtual-dispatch` | `cmd` selects one of n subclasses; only the last `run()` calls `target` | `local` (`new Ci().run()`: exact receiver), `factory` (receiver returned by a switch: ambiguous call), `array` (receiver read from an array) | +| `irrelevant-prefix` | control: n independent `if (x_i > 0) count++;` before `target` | `count` | +| `exception-dispatch` | the command is parsed from input `t`; for `t < 0` parsing throws, the handler derives the command from `t` too, then a `dispatcher/switch` (measures what SA loses after an exception it does not model) | `npe` (a NullPointerException: SA resumes at the handler from a trace event), `explicit` (control: an explicit throw, modelled precisely) | + +`sa-soundness` (`scripts/soundness.py`): hand-written SAFE/VIOLATION pairs around constructs where +the graph and the executor can disagree (arrays from JDK calls, implicit and unguarded exceptions +across frames, static initializers, String/enum switch, try/finally, object `==`, lambdas, +streams, reflection, recursion, default methods, anonymous/inner classes, `synchronized`), plus +narrowing tasks added after the first runs (`*-noarg`, `library-throw-catch`, `cce-catch-up1`, +`explicit-throw-generic-catch`, `clinit-unconditional`) and executor reproducers (`exec-*`). In +every pair the assert is `if (x > 1000 && x < HI) assert false;` with HI = 100000 (VIOLATION) or +1001 (SAFE), and it is only reached after flipping an input-dependent branch that the risky +construct sits on. Each task's comment says what it tests. + +`relevance` and `relevance-soundness` (`scripts/relevance.py`): tasks for the relevance analysis +(slicing). `relevance` has the design example verbatim (`example`, `example-long`) and families over +the loop length n in {10, 100, 1000} whose input-dependent branches cannot affect the assert; +`relevance-soundness` has SAFE/VIOLATION pairs where a wrong slice would hide a violation (writes +through fields, arrays, aliases, callees, library objects, callbacks, static initializers; input +reads; `assume`/`exit` and crashes inside a region; exceptions caught elsewhere). Some of them end +the all-zero run early on purpose (`check_labels.py` accepts that). + +`loop-state`: hand-written VIOLATION tasks against loop-head state matching, each a case where a +match would hide a violation: an `Object[]` whose length is symbolic (also under +`no-runtime-exception`, and with an unbounded loop), state held by the JDK (a System property, the +thread interrupt flag), and a class in a package SWAT does not instrument. None may come out SAFE; +the unbounded variant may time out. + +`real-tools` (`scripts/tools.py`, harnesses in `scripts/tools/`): harnesses around unmodified +GNU java-getopt 1.0.13 (`getopt-minitar`, a tar-like CLI) and opencsv 2.3's `CSVParser` +(`opencsv-import`). The tools are not in the repository: `scripts/fetch_tools.sh` downloads their +sources from Maven Central into `java/real-tools/lib-*/` (gitignored), which the tasks list on +their `input_files`. + +## Build, check, run + +```bash +cd targets/sv-comp/swat-benchmarks/scripts +./gen.py # regenerate sources and .yml files (all suites, or name some) +./fetch_tools.sh # only for real-tools +./build.sh # javac --release 8 -g:source,lines, classes next to the sources +./check_labels.py # check every label by running it (see below) + +./run_configs.sh # SA, --no-sa and --sa-rank-distance runs of everything +CONFIGS="sa nosa" ./run_configs.sh --suite dispatcher # TIMEOUT (300) and WORKERS (48) via env +./compare.py ../../runs/run_A ../../runs/run_B [--only-diff] [--csv out.csv] +../../scripts/.venv/bin/python3 report.py ../../runs/run_SA ../../runs/run_NOSA --out ../report +``` + +`run_configs.sh` refuses to start while another SV-COMP run is live, builds first, and runs +`svcomp test run --mode parallel --no-witness --categories valid-assert.prp --benchmark-dir +swat-benchmarks` (paths relative to `targets/sv-comp`). `compare.py` prints, per task and run, the +result, wall time, rounds, solver calls and the DFS visits from `explorer.log` (how many had a +pruned side, and how many had no SA node because the walker gave up). + +## How the labels were checked + +`check_labels.py` runs every task with `-ea` against a stub `Verifier` (`scripts/stub/`) that +replays chosen inputs. The inputs are in each task's header comment. + +- VIOLATION: the violating inputs must fail an assert. All do. +- SAFE: the paired VIOLATION task's inputs must not fail (the minimal difference is what makes it + safe). That the task is safe for all inputs is argued in its header comment: for the families, + `target` needs b = 3a - 7 < a with 1000 < a < 100000, i.e. a < 3.5 (no overflow in range); for + the soundness pairs, `x > 1000 && x < 1001` has no int solution; the tool harnesses say why. +- Every task: all inputs 0 (SWAT's first round) reach no assert, so each violation needs at least + one flipped branch. The gauntlet's `exit` variant exits there instead, which is allowed. diff --git a/targets/sv-comp/swat-benchmarks/RESULTS.md b/targets/sv-comp/swat-benchmarks/RESULTS.md new file mode 100644 index 00000000..8966596d --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/RESULTS.md @@ -0,0 +1,810 @@ +# Where SA pruning helps: results + +Runs of 2026-10-14 on `niklas/dev`, cfg-extractor jar of 2026-09-26 (current with +cfg-extraction `cdc73cb`, whose later change was comments only). Tasks and scripts: see +[README.md](README.md). + +## Configurations + +`svcomp test run --mode parallel --no-witness --categories valid-assert.prp --benchmark-dir +swat-benchmarks --testcase-timeout-s 300`, 48 workers (32 for the soundness reruns) on 128 cores; +one run per configuration, one after the other: + +| config | flags | run | +|---|---|---| +| SA | (default: sequential SA, graph used from round 0) | `run_20261014_162721` | +| no SA | `--no-sa` | `run_20261014_163338` | +| SA + rank | `--sa-rank-distance` | `run_20261014_164316` | +| final `sa-soundness` (incl. narrowing tasks) | SA / `--no-sa` | `run_20261014_172545` / `run_20261014_172627` | +| `real-tools` | SA / `--no-sa` | `run_20261014_171457` / `run_20261014_171958` | + +The machine was loaded (load ~100), so one round (a fresh JVM) took 1.3-1.9 s instead of ~1.2 s; +both configurations ran under the same load. Rounds and verdicts are deterministic (the soundness +suite gave identical results twice); wall times are not. + +## Summary + +This summary and the sections up to "Bugs found" describe the runs of 2026-10-14, before the +fixes; for the state after them see "Fixes (2026-10-15/16)". + +The two suites answer different questions, so they are counted separately. + +Family tasks (194; they measure what pruning gains): + +| | correct | timeouts | false SAFE | total wall time | +|---|---:|---:|---:|---:| +| no SA | 147 | 37 | 10 | >= 18571 s | +| SA | 174 | 10 | 10 | 7327 s | +| SA + rank | 175 | 9 | 10 | 6938 s | + +The 10 false SAFEs are the same tasks in every configuration, `validation-gauntlet/exit` and +`virtual-dispatch/array`: executor bugs E1 and E2 below. Pruning adds none here. + +`sa-soundness` tasks (96; they target the constructs where pruning can go wrong): + +| | correct | timeouts | false SAFE | +|---|---:|---:|---:| +| no SA | 71 | 0 | 10 | +| SA | 65 | 0 | 19 | + +The 10 false SAFEs without SA are executor bugs (E1-E3) and stay false SAFE with SA. The 9 more +with SA are pruning bugs (S1-S3), listed in the soundness table below. `--sa-rank-distance` was +only run on the first 72 tasks of this suite (before the narrowing tasks were added); there it gave +the same result as SA on every task (11 false SAFE each). It only reorders branches, so it cannot +change which ones are pruned. + +- **Pruning helps exactly when the dead side of a branch has a subtree to skip.** Then the pruned + search is constant in n while the unpruned one grows linearly (dispatcher, error-handling, + virtual-dispatch/local: 16 paths per handler) or exponentially (dead-callees: 3^n). At n = 8 + the SAFE dispatcher needs 117 rounds / 197 s without SA and 5 rounds / 17 s with it; from + n = 16 on, no-SA times out. +- **It does not help when the dead side is a leaf** (gauntlet `return`/`throw`: the rejecting side + is the one the first run takes anyway, so pruning saves 2 rounds of 36), **in event loops** + (dispatcher/`loop`: a handler in round 0 can still reach the assert in round 1), **at ambiguous + calls** (virtual-dispatch/`factory`: the graph is incomplete at the call, the walker gives up), + and **when every path reaches the assert** (irrelevant-prefix control: 2^n in both + configurations, SA only skips the dead sides of `target`'s own guards). +- **Cost:** extraction takes 4-10 s per task (median 5.5 s under load). With ~1.5 s per round SA + pays off once it saves about four rounds; for tiny tasks it is a net loss of ~5 s. +- **Soundness: SA turns 9 `sa-soundness` tasks into false SAFE** (10 -> 19) that are correct (or + UNKNOWN) without it: static initializers on an input-dependent path, exceptions without an edge + in the graph (ClassCastException, NullPointerException across frames, exceptions thrown inside + the JDK), and reflection. Separately, the executor gives false SAFE without any pruning for `System.exit`, + object-array reads at a symbolic index, and lambda bodies. Details and reproducers below. +- **`--sa-rank-distance` never hurt here** and fixed the VIOLATION cases with long paths + (dispatcher/loop: 5 rounds for every n instead of 8-36 and a timeout at n = 32; + irrelevant-prefix: 3 instead of 5-11). It changed no SAFE task and no verdict, apart from the + n = 32 loop task now solved. This differs from SV-COMP (682 -> 635 points), where the shortest + path to *some* assert point was often the wrong one; here every task has one assert. +- **Real tools (getopt, opencsv) could not show anything:** both are bound by Z3 String solving + (single queries of 70-264 s) and by a String encoding bug (E4) that makes runs diverge; neither + configuration got past round 5 in 300 s. + +## Per family + +![rounds](report/rounds.png) + +![wall time](report/time.png) + +SAFE tasks: their search is exhaustive, so they show the whole (pruned) search space. Hollow +markers: timed out, the value is a lower bound. All numbers, also for VIOLATION: +[report/families.md](report/families.md). + +| family / variant | rounds no SA -> SA (SAFE) | effect | +|---|---|---| +| dispatcher/switch, ifchain, instance | n=2: 21 -> 5, n=8: 117 -> 5, n>=16: TO -> 5 | all handlers pruned; constant in n | +| dispatcher/loop | TO -> TO | none: event loop | +| validation-gauntlet/report | n=8: 36 -> 10, n=32: 132 -> 34 | the error reports are pruned; the checks still need one round each | +| validation-gauntlet/return, throw | n=32: 36 -> 34 | the dead sides are leaves; ~2 rounds | +| validation-gauntlet/exit | 1 -> 1, false SAFE in both | executor bug E1 | +| error-handling/* (explicit, deep, arith, bounds) | n=32: 36-TO -> 3 | catch paths pruned, also for implicit ArithmeticException/AIOOBE and two frames up | +| dead-callees/suffix, guarded | n=4: 85-TO -> 3; n>=5: TO -> 3 | 3^n dead paths pruned | +| dead-callees/shared | n=4: 87 -> 5; n>=5: TO -> 5 | the context-sensitive walk prunes the dead calls of a helper that is live elsewhere | +| virtual-dispatch/local | n=8: 117 -> 5, n>=16: TO -> 5 | like dispatcher: exact receivers resolve | +| virtual-dispatch/factory | n=8: 117 -> 117 | none: ambiguous call, incomplete graph (correct verdicts) | +| virtual-dispatch/array | 18 -> 16, false SAFE in both | executor bug E2 | +| irrelevant-prefix (control) | n=4: 64 -> 48, n=6: TO -> 192 | exponential in both, as expected | + +VIOLATION tasks gain less, and how much depends on where DFS happens to look first: the switch +and instance dispatchers find the target after `n + 4` rounds without SA (the case comparisons +come before the handler bodies in DFS order), the if-chain only after exploring every handler +(same as SAFE). With SA all find it in 3-5 rounds. + +## Soundness (`sa-soundness`) + +VIOLATION / SAFE result per configuration, with rounds. "unk": UNKNOWN. + +| task | VIOLATION no SA | VIOLATION SA | SAFE no SA | SAFE SA | +|---|---|---|---|---| +| anonymous-class | ok/3 | ok/3 | ok/5 | ok/3 | +| anonymous-static-field | ok/3 | ok/3 | ok/5 | ok/4 | +| arith-catch-generic, -local, -up1, -up2 | ok/3 | ok/3 | ok/4-7 | ok/3 | +| bounds-catch-up1, -up2 | ok/3 | ok/3 | unk (E6) | unk (E6) | +| **cce-catch** | ok/3 | **FALSE SAFE/1** | ok/3 | ok/1 | +| **cce-catch-up1** | ok/3 | **FALSE SAFE/1** | ok/3 | ok/1 | +| **clinit-static-call, -new, -field** | ok/3 | **FALSE SAFE/1** | ok/3 | ok/1 | +| **clinit-in-callee** | unk/3 (E7) | **FALSE SAFE/1** | ok/3 | ok/1 | +| clinit-unconditional (control) | ok/2 | ok/2 | ok/4 | ok/2 | +| default-method | ok/3 | ok/3 | ok/5 | ok/3 | +| enum-switch, string-switch | ok/4 | ok/4 | ok/4 | ok/4 | +| explicit-throw-generic-catch (control) | ok/3 | ok/3 | ok/4 | ok/3 | +| finally-assert, finally-exits | ok/3, ok/6 | ok/3, ok/6 | ok/4, ok/7 | ok/4, ok/7 | +| inner-accessor | ok/3 | ok/3 | ok/6 | ok/3 | +| jdk-getmethods, jdk-toarray | ok/3 | ok/3 | ok/3 | ok/3 | +| **library-throw-catch** | ok/3 | **FALSE SAFE/1** | ok/3 | ok/1 | +| **npe-catch-up1** | unk/2 (E5) | **FALSE SAFE/1** | unk/2 | ok/1 | +| npe-catch-local | unk/2 (E5) | unk/2 | unk/2 | unk/2 | +| recursion-depth, recursion-mutual | ok/7, ok/5 | ok/7, ok/5 | ok/11, ok/7 | ok/11, ok/7 | +| ref-eq, ref-ne-chain | ok/3, ok/5 | ok/3, ok/5 | ok/3, ok/5 | ok/3, ok/5 | +| **reflection-noarg** | ok/3 | **FALSE SAFE/1** | ok/5 | ok/1 | +| reflection-invoke, reflection-desync | FALSE SAFE (E3) | FALSE SAFE/1 | ok | ok/1 | +| lambda-predicate, -consumer, -local-noarg | FALSE SAFE (E3) | FALSE SAFE (E3) | ok | ok | +| lambda-static-field, -static-field-noarg | FALSE SAFE (E3) | FALSE SAFE/1 | ok | ok/1 | +| stream-foreach, string-switch-nondet | unk (context loss) | unk | unk | unk | +| synchronized-block, synchronized-throw | ok/4, ok/3 | ok/4, ok/3 | ok/7, ok/5 | ok/5, ok/4 | +| exec-system-exit, -late | FALSE SAFE/1 (E1) | FALSE SAFE/1 | ok/1 | ok/1 | +| exec-object-array-index | FALSE SAFE/3 (E2) | FALSE SAFE/1 | ok/3 | ok/1 | +| exec-int-array-index | unk (E6) | unk | unk | unk | +| exec-string-char | unk (E4) | unk | unk | unk | + +Constructs that are handled correctly: implicit ArithmeticException / AIOOBE caught one or two +frames up and as `RuntimeException`, explicit throws caught as a supertype, arrays from JDK calls +(the phantom bounds guard regression), String and enum switches, try/finally duplication, +`refEquals`, recursion at an input-dependent depth, default methods, anonymous and inner classes +(synthetic accessors), `synchronized` (including an exception through the monitor-exit handler), +and `` that runs on every path. + +## Bugs found + +Status as of 2026-10-17: all fixed (see "Fixes (2026-10-15/16)", "S2" and the sections after it). + +Each has a minimal reproducing task in `java/sa-soundness/` (VIOLATION variant `_false`). None is +fixed here: the SA ones need extractor changes beyond a local fix, the executor ones an executor +rebuild and SV-COMP re-validation. + +### SA (extractor / walk): false SAFE only with pruning + +- **S1. Static initializer reached on an input-dependent path.** `` is not in the graph, + so an assert inside it is invisible; the graph has no assertion point at all and DFS prunes the + branch that leads to the first use of the class (static call, `new`, static field read, or + inside a branch-free callee). Reproducers: `clinit-static-call`, `clinit-new`, `clinit-field`, + `clinit-in-callee`; control `clinit-unconditional` (first use on every path) is correct. + Fix direction: treat the first use of an application class whose `` can reach an assert + (or is not analysed) as an assertion point or an incomplete node, or add implicit call edges. +- **S2. Exceptions the graph has no edge for, caught by the application.** The handler is + unreachable in the graph, so the branch that makes the exception happen is pruned: + ClassCastException from a `checkcast` (even in the same method: `cce-catch`; one frame up: + `cce-catch-up1`), NullPointerException one frame up (`npe-catch-up1`; locally there is an + EXCEPTION edge), and any exception thrown inside the JDK (`library-throw-catch`: + `Integer.parseInt` -> NumberFormatException, the usual way CLI tools handle bad arguments). + This is limitation 2 of `INTER_PROCEDURAL.md`, but its consequence is a false SAFE, not only + lost precision. Fix direction: for each handler that can catch such types (including + `RuntimeException`, `Exception`, `Throwable`), mark the statements in its try region that can + throw them (casts, dereferences, library calls) incomplete, or add EXCEPTION/escape edges. +- **S3. Reflection.** `Method.invoke` is a library call, so it gets no CALL edge and is not marked + incomplete ("only library targets"); the invoked method's assert is unreachable in the graph + (`reflection-noarg`). Fix direction: mark `Method.invoke`, `Constructor.newInstance`, + `Class.newInstance` (and `MethodHandle.invoke*`) incomplete. +- **S4 (latent). Lambdas stored in fields.** An interface call whose receiver comes from a field + falls back to the application subtypes of the interface; lambda classes do not exist statically, + so a `Runnable`/`IntConsumer` field holding a lambda resolves to "only library targets": no + edge, not incomplete, no assertion point (`lambda-static-field`, `lambda-static-field-noarg`). + Currently hidden because the executor misses these violations anyway (E3); it becomes a false + SAFE once E3 is fixed. Fix direction: treat interface calls on functional interfaces as + incomplete when the program has lambda bodies. + +(Superseded: S1, S3 and S4 are now fixed in the extractor.) A cheap stop-gap in SWAT for S1, S3 +and S4: do not use a graph that has no assertion point +(`metadata.assertionPointIds` empty) while the program has an assert; all of those reproducers +have such a graph. It does not cover S2. + +### Executor: false SAFE or lost results, also without pruning + +- **E1. `System.exit` loses the trace.** A run that ends in `System.exit` reports no branches, so + exploration stops with SAFE after the first round (`exec-system-exit`, also when branches were + recorded before the exit: `exec-system-exit-late`; `validation-gauntlet/exit`). +- **E2. Object array read at a symbolic index** is not a choice point and is not flagged as + precision loss, so other elements are never tried: false SAFE (`exec-object-array-index`, + `virtual-dispatch/array`). +- **E3. Lambda bodies record no symbolic branches,** neither for arguments passed through the + JDK's lambda forms nor for static fields read inside a no-argument lambda, and this is not + flagged as context loss: false SAFE (`lambda-*`). The same for arguments passed through + `Method.invoke` (`reflection-invoke`, `reflection-desync`; `reflection-noarg`, which reads a + static field instead, works). +- **E4. String inputs have no character range bound.** `StringValue.getBounds` returns `true`, + and `charAt` is the code point truncated to 16 bits, so Z3 may answer `charAt(0) == '-'` with + U+1002D; Java then sees a different string and the run diverges ("Target did not explore branch + ... as expected"); the result is lost (`exec-string-char` gives UNKNOWN for VIOLATION). This + blocked both real tools. Fix direction: bound input strings to `\u0000-￿` (e.g. + `str.in_re` with a range) or encode `charAt` without truncation. +- **E5. NullPointerException on `getfield`** halts the executor ("Trying to get a field of NULL"): + UNKNOWN (`npe-catch-local`). With SA, `npe-catch-up1` turns this UNKNOWN into a false SAFE (S2). +- **E6. "Error retrieving value for SV-Comp (at instruction 4)"** after solving for an int that is + used as an array index: UNKNOWN (`exec-int-array-index`, `bounds-catch-*_true`). +- **E7. "Invocation stack is broken"** when a `` runs inside a callee: UNKNOWN + (`clinit-in-callee_false` without SA). + +### Harness (fixed) + +- `svcomp test run --benchmark-dir` crashed for folder names other than `sv-benchmarks` + (`939933e`). + +## Fixes (2026-10-15/16) + +The sections above describe the state of 2026-10-14. All bugs found then are fixed except S2, +which is deferred to the planned trace-event redesign (see "Planned redesign" in the workspace +`CLAUDE.md`). Fixing them turned up more bugs, which are fixed too. SWAT commits are on +`niklas/dev`, extractor commits on cfg-extraction's `find-assertions`. + +| bug | cause | fix | commit | +|---|---|---|---| +| S1 static initializers | `` is not in the graph, so the path to a class's first use was pruned | statements that may initialize a class whose initializer can reach an assert (`new`, static calls and fields, via superclasses, transitively, also through application overrides of library methods) are assertion points (`ClinitTargets`) | cfg `8facdc9`, `8e2d20f` | +| S3 reflection | `Method.invoke` counted as a library call with nothing behind it | reflective calls (`Method.invoke`, `newInstance`, `Class.forName`, `MethodHandle.invoke*`) are incomplete | cfg `39a20fc`, `8e2d20f` | +| S4 lambdas in fields | lambda classes are not in the view, so an untraceable receiver had "only library targets" | untraceable receivers of an interface a lambda implements are unresolved, so incomplete | cfg `5ae7bf6` | +| S2 unguarded exceptions | see above | fixed on 2026-10-16, see "S2" below | | +| E1 `System.exit` | the trace was never sent | `System.exit`, `Runtime.exit`/`halt` send the trace first | `90fd881` | +| E2 symbolic object-array index | only the concrete element is tracked | sound only: records precision loss (UNKNOWN instead of false SAFE) | `d612f78` | +| E3 lambdas | the body ran in "unknown space", skipped silently | non-capturing lambda bodies run as the interface method; untracked instrumented code records context loss | `b67695d` | +| E3 reflection | `Method.invoke` arguments arrived boxed, and static targets got a receiver | arguments are unboxed, static targets get no receiver, `Object[]` arguments are checked for symbolic content | `45c2cd6` | +| E4 String chars | String inputs had no 16-bit bound | inputs are bounded to `\u0000-￿` | `16303f1` | +| E5 NPE on `GETFIELD` | the executor halted | the exception is followed like any other | `3b2b979` | +| E6 input name collision | solutions were matched to inputs by index (`[I_0` overwrote `I_0`) | matched by name; non-inputs ignored | `b6573fb` | +| E7 initializer left by an exception | its invoke stayed open | closed when the invoke below it ends | `f1ee845` | +| new: solved Strings | Z3's `as_string()` output is escaped (and ambiguous), and was passed on verbatim | read by code point, escaped for the command line, unescaped by `Verifier` | `16303f1` | +| new: String precision check | constraints on `java/lang/String_N` counted as precision loss (name pattern), so no task constraining a String symbolically could be SAFE | trace inputs are accepted by name | `afbd1db` | +| new: equal String inputs | `unescape("")` returned the interned `""`, so two empty inputs were one object and shared one variable (two false SAFEs on SV-COMP before this fix) | every String input is a fresh object | `5c0fdec` | +| new: `String.indexOf(String, int)` | modelled without Java's clamping of `fromIndex` | clamped into `[0, length]` | `34cd826` | +| new: `StringBuilder.setLength` | not modelled: any reused builder halted the executor | modelled for concrete lengths | `1a05ac4` | +| new: divergence ended in SAFE | a solution that did not reach its branch still marked the branch solved; nothing was left to try | an unexpected divergence makes a SAFE verdict UNKNOWN | `18b0af7` | +| new: divergence check by id | the check took the first trace element with the target's id, but ids repeat (all keys of a `LOOKUPSWITCH` share one, `TABLESWITCH` case numbers wrap at 16), so it reported divergences that did not happen | the target is found at its depth in the tree | `fc8e88e` | + +Reproducers are in `sa-soundness` (`exec-*` and the pairs named in the bug lists above), except +for the two divergence fixes, which are covered by `symbolic-explorer/tests/test_divergence.py` +and by `dispatcher/switch_n32` and `instance_n32` and `virtual-dispatch/local_n32` (32-case +switches). The two reproducers added last (`exec-equal-string-inputs`, +`exec-indexof-negative-from`) were not checked against the code before their fixes. + +### Results after the fixes + +swat-benchmarks (300 tasks; the 2026-10-14 runs had 266, without the narrowing tasks and real +tools). Final runs `run_20261016_094623` (SA), `_095359` (no SA), `_100453` (rank), with the +three n = 32 SAFE tasks that the id-based divergence check had turned UNKNOWN rerun after +`fc8e88e` (all SAFE again, in both SA configurations): + +| | 2026-10-14: correct / false SAFE | 2026-10-16: correct / false SAFE | +|---|---|---| +| no SA | 203 / 15 (of 266) | 238 / 0 (of 300) | +| SA | 227 / 21 | 259 / 5 | +| SA + rank | 228 / 21 | 262 / 5 | + +- Without SA there is no false SAFE left. The five with SA are all S2: `cce-catch`, + `cce-catch-up1`, `library-throw-catch`, `npe-catch-local`, `npe-catch-up1`. +- The families behave as on 2026-10-14 (constant rounds with SA for dispatcher, error-handling, + dead-callees, virtual-dispatch/local), except: `validation-gauntlet/exit` is now correct (E1; + no gain from SA, as the graph treats `exit` as a call that returns), and + `virtual-dispatch/array` is UNKNOWN instead of a false SAFE (E2). A few tasks that took + 250-300 s on 2026-10-14 now time out at 300 s; the machine was busier (see below). + + +SV-COMP (current suite, valid-assert, SA, 300 s, 50 workers): baseline = the code from before +these fixes (`939933e` with the jars of 2026-09-26/29), in a separate worktree. + +| | points | correct | false SAFE | false VIOLATION | +|---|---:|---:|---:|---:| +| baseline (`run_20261016_083422`) | 879 | 710 | 0 | 0 | +| fixed (`run_20261016_092145`) | 889 | 718 | 0 | 0 | + +- 13 tasks now correct that were not: `NullPointerException2-4`, `StaticCharMethods02/06`, + `char1`, `StringBuilderConstructors01`, `startswith` (jbmc/jdart), `Pred9`, `Refl4` (S1 via + `Class.forName`), String `compareTo`, `BinaryTreeSearch-MemUnsat02`, `Trie-MemUnsat01`. +- 5 correct before, not now; none is a false SAFE: + - `Trie-FunSat01`, `Trie-MemSat01`: SAFE -> UNKNOWN. They index an object array with a symbolic + char (`x.next[c]`); the baseline's SAFE was right by luck (E2). + - `Piecewise_true`: SAFE -> UNKNOWN: a solution did not reach its branch, so the search was not + exhaustive; the baseline reported SAFE regardless. + - `Long_shortValue`, `TCAS_prop1`: timeouts. They took 282 s and 264 s in the baseline already; + the final run was on a busier machine (load ~150; executor rounds 1.5x slower, and the barely + changed extractor equally). Run one at a time, the old and the new executor take the same time + on these tasks. +- `Optimization4` and `UnsatAckermann01` are wrong in both (UNKNOWN before, timeout now); + `UnsatAckermann01` ended early in the baseline only because of an executor error. +- One more false SAFE pair appeared on the way (`autostub` String `length` and + `indexOf(String, int)`), caused by the String fixes themselves; both are fixed + (`5c0fdec`, `34cd826`) and covered by reproducers. + +### S2 (2026-10-16) + +Design "B" of the planned redesign (see the workspace `CLAUDE.md`): + +- **Marking** (extractor, `PseudoExceptions`): statements that may throw an exception SWAT has no + guard for (calls, instance field and array accesses, casts, array lengths, monitors, throws) + get EXCEPTION edges to every local handler that might catch it and to an + `uncaught[?Exception]` node; with Error handlers in the program, every statement does the same + for `?Error` (only handlers for `Throwable`, `Error` or its subclasses). CALL edges list their + candidate handlers (`unknownHandlers`). SAGraph treats a pseudo escape as caught by any listed + handler, or by none. Commits: cfg `3b1fdf2`, SWAT `9ce3d15`. +- **Walk** (executor + `dfs`): when an exception happens that the graph does not model precisely + (anything but a guard failure or an explicit `throw`), the executor records an + `UNMODELLED_EXCEPTION` special event, and `dfs` stops trusting the graph for the rest of that + path (`552bcc0`). Guarded and explicit exceptions keep their precise walk, so the + error-handling family keeps its pruning. +- Found on the way: a static initializer left by an exception that was triggered by a field + access stayed open, so the handler below ran as untracked code (`86c0da2`). + +Results: the five S2 pairs and three new reproducers (`npe-in-callee-branch`, +`error-in-initializer-catch`, `finally-npe`) are correct with and without SA. swat-benchmarks +(306 tasks): SA 270 correct / 0 false SAFE (before: 256 / 5), SA + rank 271 / 0, no SA 241 / 0. +SV-COMP: unchanged at 889 points, 718 correct, 0 false SAFE (`run_20261016_143432`). The marking +oracle (`check_marking_on_graphs.py`) finds no unsound marking on all 636 SV-COMP graphs; the share +of pruned branch sides drops from 8.6% to 8.3%, all of it in `algorithms` (16.1% -> 11.5%), whose +handlers lead to asserts. Graphs grew by 11% in nodes and 63% in edges. + +### S2 precision: resuming at the handler (design C, 2026-10-16; removed 2026-10-17) + +Superseded by resync (next section), which recovered the same in every measurement; the code was +removed. Kept here for the measurements. + +With B, every unmodelled exception ends pruning for the rest of the path. Measured on the +`exception-dispatch` family (input-dependent dispatch after the handler), the `npe` variant needed +26, 58, 122 rounds for n = 2, 4, 8 and timed out above that, while `explicit` (same program, an +explicit throw) stays at 10 rounds for every n. + +- **Executor:** at the start of every catch handler the instrumentation records a + `HANDLER_ENTRY d key` special event: `d` is the frame depth relative to the last branch, `key` is + `owner.name(desc)|caught types|protected lines` (`HandlerKeys`), built from content because + SootUp keeps neither bytecode offsets nor the exception table's order. +- **Extractor:** handler entry nodes carry the same key (`handlerKey`); keys that are not unique in + their method (javac's copies of a `finally` handler) are left out. +- **Walk:** after `UNMODELLED_EXCEPTION`, `dfs` remembers where it was; at the next + `HANDLER_ENTRY` it pops `-d` frames off its own call stack and resumes at the handler node, if + the key is known and the handler is in the method of that frame. Anything else (unknown or + ambiguous key, a handler in a callee entered after the last branch, `d > 0`) keeps giving up, + which is always safe. + +Results: `npe` now matches `explicit` exactly (6 rounds VIOLATION, 10 SAFE, for every n). +New reproducers `npe-recursive-catch` (the handler is in one activation of a recursive method) and +`npe-handler-in-callee` (must give up) are correct in all configurations. swat-benchmarks +(330 tasks): SA 296 correct / 0 false SAFE, SA + rank 297 / 0, no SA 258 / 0; apart from the new +tasks only `validation-gauntlet/exit_n32` changed (it sits at the 300 s limit). SV-COMP: 890 +points, 719 correct, 0 false SAFE (`run_20261016_205345`; the two changed tasks are solver-time +noise at the timeout). Graph structure is unchanged on all 636 SV-COMP graphs; 190 of them carry +handler keys. Commits: cfg `726a144`, SWAT `6fb7464`, `50ce3ff`, `7f6aa4c`. + +### Branch keys, desync detection and resync (2026-10-17) + +The rest of the planned redesign: the walk is still positional, but every branch is now checked +against the trace, and the walk finds the graph again after losing it. + +- **Keys.** Every branch and call site is named `owner.name(desc)|line|kind|ordinal` (kind: `if`, + `switch`, `arr`, `newarr`, `div`, `call:name(desc)`; ordinal: position among that kind on the + line, in bytecode order; `#value` / `#n` for a switch case / an array dimension). The executor + names instructions while instrumenting (`instrument/BranchKeys`), the extractor names graph nodes + (`BranchKeys`). SootUp keeps no bytecode offsets, so the extractor reads the class file with ASM + and keeps an exact ordinal only where it can prove it: SootUp's `getStmts()` order is wrong for a + switch on a String, `getBlocksSorted()` order is wrong for javac's `finally` copies. Same-kind + instructions on a line whose signatures (an `if`'s operator and constant, a call's constant + argument, load/store) do not tell them apart get an open ordinal (`|*`, matching any); lines + whose kinds differ from the bytecode get no key. On the 636 SV-COMP graphs 93% of branch nodes + have a key, 22% of those with an open ordinal. +- **Stack deltas.** Each trace branch also carries the frames popped and the call sites (keys) + pushed since the previous branch. Frames of SWAT's own helpers (`UtilInstrumented.refEquals`) + are left out, since the graph has their branches inline. +- **Desync detection.** At every branch `dfs` checks the graph node's key and its call stack + against the trace (`check_branch`). A mismatch stops the walk on that path and records + `sa_desync`, which turns SAFE into UNKNOWN: an unkeyed branch before it may already have been + mispaired. +- **Resync.** Whenever the walk has no graph node (after an unmodelled exception, a desync, an + incomplete node), `dfs` looks up the next branch by key and rebuilds the call stack from the + trace's call sites (`resync`), provided every frame maps to a call site whose callee is the next + frame's method. + +Results: swat-benchmarks unchanged in verdicts (SA 296, SA + rank 297, no SA 262 of 330 correct, +0 false SAFE, no desync); resync removes most unpaired visits in `lambda-*`, `reflection-*`, +`finally-*`, `npe-handler-in-callee`, `anonymous-static-field`, and several SAFE tasks need fewer +rounds (`lambda-consumer_true` 5 -> 3). SV-COMP: 891 points, 720 correct, 0 false SAFE +(`run_20261017_102120`). Detection found one real desync class: in `nanoxml` and `printtokens` +(java-ranger-regression) a JDK `PushbackReader` calls back into the application's `IntReader.read()`; +the graph walks over the library call, so the old walk silently paired the callback's branches with +`StdXMLReader.atEOF()`. The verdicts were already UNKNOWN (context loss). Key mismatches found on +the way (string switches, `finally` copies, the `refEquals` helper frame) are fixed. + +Design C compared with resync alone (handler resumption switched off): no difference in verdicts, +rounds or unpaired visits on `exception-dispatch` and `sa-soundness`, and on SV-COMP (891 points +both, `run_20261017_100011`); the two tasks that differ are solver-time noise at the timeout. +Commits: cfg `6b5ee5a`, SWAT `1568fec`, `fc817ee`. Design C was removed afterwards. + +### Library callbacks (2026-10-17) + +Library code calling back into application code (a JDK `PushbackReader` around the application's +`Reader` in `nanoxml`/`printtokens`) runs in the library call's frame (the executor's +"delegation"), and its branches are in the trace but not in the graph, which has the call as a +plain library call. + +- **Executor:** when delegation is detected, the library frame is marked as a callback frame, and + every branch recorded while one is on the stack is flagged `callback`. A call by the application + itself through a library type (`Runnable.run()` on an application class) is not a callback: the + running class is a subtype of the frame's class. An exception leaving a library frame now records + `UNMODELLED_EXCEPTION` too (before, only one thrown before any application code ran in it). +- **Walk:** `dfs` masks callback branches like ``: the walk holds its place, and checks and + resync are skipped; the stack deltas still apply. + +Results: no desync left on swat-benchmarks or SV-COMP. `nanoxml` and `printtokens` keep their +graph through the callbacks (unpaired visits 50 -> 0, 670 -> 0). New pairs `library-callback` and +`library-callback-throw` are correct with and without SA. swat-benchmarks (334 tasks): SA 300, +SA + rank 301, no SA 266 correct, 0 false SAFE; SV-COMP 891 points, 720 correct, 0 false SAFE +(`run_20261017_145000`). Six Juliet tasks lose the graph after a failing socket call (an exception +out of library code), which the walk used to step over. + +Not done at first (since fixed, see "Callback targets" below): marking library calls that may call back as possibly reaching an assert. A callback +that runs is handled correctly (a tracked one is explored symbolically with its branches never +pruned; an untracked one records context loss). But an assert in a callback on a pruned path is a +**false SAFE** with SA: the graph does not have the callback, so the path to the library call is +pruned and never runs (`library-callback-assert_false`: false SAFE with SA, correct without). + +### Callback targets, multi-target calls, a solver cache bug (2026-10-17) + +- **Callback targets** (fixes the `library-callback-assert` false SAFE). The extractor collects the + application methods library code may call back: overrides of library methods and lambda or + method-reference targets (`CallbackTargets`). If one may reach an assert, every library call site + becomes an assertion point. On SV-COMP this fires only on securibench (its harness overrides + `PrintWriter.println` with the check), where it costs nothing (same rounds and pruning with and + without it). `--sa-no-callback-targets` turns it off. +- **Z3 on the extractor's classpath.** The harness puts Z3's jar on the target's classpath, and the + extractor treated Z3 as application code: its `toString()` methods assert, so the callback rule + fired on every task. `StaticPreAnalysis` now leaves the jar out. +- **Multi-target calls.** A call site whose dispatch is exact but has several targets, all + application methods, gets a CALL edge to each instead of being marked incomplete. The walk stops + there and resyncs inside the callee that ran; summaries take the union. `virtual-dispatch/factory` + now solves every n (n = 8 SAFE: 117 -> 11 rounds; n = 16, 32: timeout -> correct in 19 / 35 + rounds). On SV-COMP only two call sites qualify. +- **Solver cache bug (false SAFE, also without SA).** The explorer's constraint cache was keyed by + `(iid, trace)`; two branches of one trace share an iid when a switch has cases 16 apart or a loop + runs 64 iterations apart, and one got the other's constraint (unsat), so the branch was never + flipped. Found through `local_n32_false` turning SAFE once pruning explored more cases; reproducer + `exec-loop-iid-wrap` (false SAFE without SA before the fix). Fixed by keying by tree node. Several + 32-case tasks without SA went from timeout to correct. + +Results: swat-benchmarks (338 tasks) SA 308 correct, no SA 277 correct, 0 false SAFE, no desync; +SV-COMP 891 points, 720 correct, 0 false SAFE (`run_20261017_163713`). The marking oracle finds no +unsound marking on the 636 SV-COMP graphs. Commits: cfg `eb658a9`, `85a07db`; SWAT `6ced24e`, +`f6197d3`, `9046cd0`, `69c6b83`, `98eb95d`. + +### Still open + +- Call sites whose targets are not all known (an untraceable receiver of a library type, a lambda) + stay incomplete, and resync stays off inside a frame entered through one. +- Fresh arrays have a free base variable (`[I_0`), which counts as precision loss: SAFE tasks that + read an array at a symbolic index stay UNKNOWN (`bounds-catch-*_true`, `exec-int-array-index_true`). + A constant-array base for `newarray` would fix it, but changes array modelling everywhere. +- Real tools still time out: getopt and opencsv get through ~13 instead of ~5 rounds; opencsv + loses context in `ArrayList.add` of the parsed fields. + +## Real tools (step 4) + +| task | no SA | SA | +|---|---|---| +| `getopt-minitar` (GNU java-getopt 1.0.13, 3 nondet argv strings) | timeout after 4 rounds | timeout after 5 rounds | +| `opencsv-import` (opencsv 2.3 `CSVParser`, 1 nondet line) | timeout after 4 rounds | timeout after 4 rounds | + +Both are plain Java and fully analysable (no incomplete nodes, the walk never gave up), and SA +did prune (81 of 324 and 26 of 290 DFS visits had a dead side). But exploration is not the +bottleneck: single Z3 String queries took 264 s (getopt) and 70 s (opencsv), and both runs then +diverged on supplementary code points (E4). Two structural notes for later attempts: + +- getopt is driven by a loop in the caller, and the opencsv parser loops over the characters, so + most branches can reach a post-parse assert in a later iteration: the event-loop shape, with + pruning limited to the early exits (help, version, usage errors, rejected records). +- Picking argv tokens from a fixed list with `nondetInt` instead avoids String solving, but then + the tool's own branches are concrete and the only symbolic branches are the token choices, + which all come before parsing and can all reach the assert: nothing to prune. + +Tools in `org/apache`, `com/google` or `com/fasterxml` (commons-cli, commons-csv, Gson, Jackson) +are excluded from instrumentation by `sv-comp.cfg`, and JSON parsers mostly read through a +`java.io.Reader`, which loses the symbolic chars; so they were not tried. + +## Recommendations + +1. S1, S3 and S4 are fixed in the extractor (2026-10-15/16), so the zero-assertion-point stop-gap + is no longer needed. SA SAFE verdicts are still not trustworthy for programs that catch + unguarded or JDK exceptions (S2, fixed by designs B and C). Add the `sa-soundness` + suite to the regression runs: it takes about a minute per configuration. +2. Pruning pays off for programs whose dead branches lead into substantial code: dispatchers and + command handlers, error handling and reporting, helpers called only on rejected paths. That is + the shape of security-sink reachability or crash reproduction at a single site in a + command-style program. Expect nothing for event loops, all-paths-reach-the-assert programs and + ambiguous dispatch. +3. Keep `--sa-rank-distance` in mind for single-target tasks: here it was never worse and it found + deep violations in constant rounds. It is still a poor default for programs with many asserts + (SV-COMP). +4. Before trying real parsers again, fix E4, and consider `--solver-rlimit` so that slow String + queries do not stall the whole search. + +## Relevance (slicing), 2026-10-26 + +Branch `niklas/relevance-slicing` in both repos. Reachability pruning (everything above) cannot help +when both sides of a branch reach the assert. **Relevance** adds a static slice: a branch is +irrelevant if its region (the code up to its immediate post-dominator) contains no target, cannot +end the program, writes nothing a target or a relevant branch depends on, and contains no relevant +branch. SWAT does not flip irrelevant branches, leaves their constraints out of flip queries, and +identifies candidates by the sequence of relevant decisions before them, so paths that differ only +in irrelevant decisions count as one. How: cfg-extraction `DataflowFacts` (per-node data-flow +facts), SWAT `Relevance.py` (the analysis), `DFS.py` (slicing, dedup, poisoning), +`StrategyService.solve_branch` (full query first), `SVCompDriver.check_projected_divergence`. + +All runs: `--testcase-timeout-s 300`, 64 workers on 128 cores; "no slicing" adds `--no-sa-slicing` +(everything else the same code). + +| suite | slicing | no slicing | runs (slicing / no slicing) | +|---|---|---|---| +| `relevance` (26) | 26 correct | 16 correct, 10 timeouts | `run_20261026_152209` / `run_20261026_141423` | +| `relevance-soundness` (42) | 38 correct, 4 UNKNOWN, 0 false SAFE | the same 38 / 4 | `run_20261026_152342` / `run_20261026_142025` | +| `sa-soundness` (120) | 115 correct, 5 UNKNOWN | identical | `run_20261026_152414` / `run_20261026_144630` | +| other swat-benchmarks families | +2 dispatcher/loop, +2 irrelevant-prefix, nothing lost | | `run_20261026_143147` / `run_20261026_143728` | +| sv-benchmarks valid-assert (1013) | 1013 points (834 correct) | 1011 (833) | `run_20261026_152608` / `run_20261026_150332` | +| sv-benchmarks no-runtime-exception (771, `--sa-no-runtime-exception`) | 1279 points | 1279 | `run_20261026_153910` / `run_20261026_154730` | + +The 4 UNKNOWN in `relevance-soundness` are the same without slicing: two SAFE tasks whose initial +run ends with an uncaught exception (which rules out SAFE), and two whose comparator/`toString()` +callbacks cause context loss. The one `safe -> violation` in sv-benchmarks (`objects/objects09`) is +the same in both configurations. + +### Where it works well + +Rounds for the SAFE tasks (the whole search), slicing vs no slicing: + +| shape | n = 10 | n = 100 | n = 1000 | +|---|---|---|---| +| `loop-print`: `for i < n: if (i < val) print(i)`, then an assert on `val` (the design example) | 2 vs 11 | 2 vs 101 | 2 vs TO | +| `loop-helper`: each iteration calls a helper with input-dependent branches that update a log counter | 2 vs 6 | 2 vs 51 | 2 vs TO | +| `scratch-array`: the loop fills a scratch array (bounds-checked) the assert never reads | 2 vs 11 | 2 vs 101 | 2 vs TO | +| `two-inputs`: the loop branches on input `a`, the assert on input `b` | 3 vs 36 | 3 vs TO | 3 vs TO | + +`relevance/example` (the example verbatim, 100 iterations): 2 rounds, 1 query instead of 102 rounds +and 101 queries (8 s instead of 138 s). `example-long` (10000 iterations): 2 rounds, 92 s, all of it +in the executor; without slicing it times out after 6 rounds. With 100000 iterations a single +executor run takes over 280 s of CPU, so the executor, not the search, is the limit there. + +On the older families: `irrelevant-prefix/count` (n independent `if (x_i > 0) count++`, `count` never +read) takes 3 rounds for every n instead of 12, 48, TO, TO (n = 2, 4, 6, 8; it was the control +family that showed reachability pruning cannot help). `dispatcher/loop` SAFE (an event loop) now +finishes at n = 2 and 4 (36 and 64 rounds; TO without slicing), since the handlers' own branches +only touch state the next round does not read; from n = 8 on it still times out. + +So slicing works well when **input-dependent branches only feed state the target never reads**: +logging and printing, diagnostics and statistics counters, scratch buffers, bookkeeping in helpers, +and loops over one input while the target depends on another. The number of paths through such +code is then irrelevant: the search is constant in the loop length and in the number of such +branches, where it is linear (one decision per iteration) or exponential (independent decisions) +without slicing. + +On sv-benchmarks it rarely matters: 19.5% of all branches are irrelevant under valid-assert (64 +of 64 MinePump products have some, 289 of 636 graphs), but the tasks are short or bound by the +solver or the executor. Slicing saves 1-2 rounds on a few tasks and turns `argv-tasks/TimeChart` +from UNKNOWN into SAFE (4 rounds; without slicing two of its runs hit the memory limit on +solutions with extreme values for branches slicing never flips). + +### Where it does not help + +- **The loop's decisions feed the target** (`relevant-counter`: `if (i < val) count++`, then + `if (count == 7)`): the branch is relevant, and the search still enumerates one decision per + iteration. That needs **loop summarization** (`count = clamp(val, 0, 10)`), the next step. +- **The loop reads inputs**: every `Verifier.nondet*` advances the input cursor, which is relevant + as soon as any input is (the inputs are numbered by order), so a region that reads an input is + always relevant. +- **Library state, context-insensitivity, callbacks**: all library objects share one location + (`list.add` in a region is relevant if any list size is read); a helper with a relevant branch + makes every region that calls it relevant; a callback inside a region or a branch without a key + poisons the region at run time. +- **Event loops with large n** (`dispatcher/loop`, n >= 8): the handlers' dispatch decisions are + relevant (they decide whether the target is reached), so only the handlers' inner branches go. + +### Bugs found and fixed along the way + +- **`Class.desiredAssertionStatus()` counted as reflection** (cfg-extraction `1bef88d`): every class + with an assert calls it in its static initializer, and reflection reads and writes everything, so + every branch of MinePump and securibench was relevant. Only `Class.forName`/`newInstance` and + class loading run unknown code. +- **Statements that may throw were no decisions** (`c0cd81c`): 4 false SAFE in `sa-soundness` + (`npe-catch-local`, `npe-in-callee-branch`, `cce-catch`, `library-throw-catch`): a dereference + whose NullPointerException a local handler catches decides whether the handler (with the assert) + runs, but what it reads never became relevant, so the branch that set the receiver to null was + sliced. Statements with a normal and an exceptional successor are now implicit branches. +- **Crash below a pruned side** (`crash-in-callee`): a path that ends with an uncaught exception + inside an irrelevant region must make the region relevant again, but the crashing side cannot + reach the assert and was never walked. Leaves of such runs are marked, their ancestors carry + `abnormal_below`, and dfs walks such sides for the regions only. +- **Bad models of sliced queries** (`24cfcf2`): Z3 picked an array size of 2^30 for a sliced query + in `algorithms/InsertionSort-FunUnsat01` (and MergeSortIterative), which the executor cannot + allocate: two violations lost. The full query is now solved first; the sliced one only decides + where the full one is UNSAT. + +### Cost + +Extraction is about 9% slower (median 8.9 -> 9.75 s per task at 48 parallel extractions); the +analysis in SWAT takes milliseconds (1.2 s for all 636 sv-benchmarks graphs together). Graph +structure (nodes, edges, keys) is unchanged. + +### Open + +- Loop summarization for relevant loops (planned). +- **Library state is one location (`g:lib`).** Changing any library object (a list, a + StringBuilder) is relevant as soon as any library object's state is read. Fixes, cheapest first: + - *Type families* (about a day): `g:lib:collections`, `g:lib:strings`, `g:lib:io`, ... by a table + in `DataflowFacts`. Families must be coarse because library objects alias across types through + views (`map.keySet().remove(k)` changes the map, `list.subList(..).clear()` the list, an + iterator its collection); unknown types stay in `g:lib`. Separates a logging StringBuilder from + a checked list, not two lists. + - *Allocation sites* (2-3 days, mostly soundness work): `g:lib@` for a library object + allocated in the method (`ReceiverTypeResolver` traces locals to their `new`) whose reference + never escapes (no field/array store, no argument, no return); others fall back to the family. + Every call result must count as an alias of its receiver (`sb.append` returns `sb`, + `list.iterator()` is a view) except for a whitelist of fresh results (`size()`, `toString()`); + a wrong entry is a false SAFE. Objects passed between methods need a real points-to analysis + (weeks: SootUp has none ready). +- **The slice is context-insensitive.** A helper with a relevant branch makes every region that + calls it relevant (`relevance-soundness/shared-helper`: `clamp` is relevant because one caller + compares its result). Fix (1-2 days): analyse each method in two variants, "return value + relevant" and "not"; a call site induces the variant by whether its result local is relevant in + the caller's variant, so the fixpoint runs over (method, variant). Heap effects are the same in + every context. Rule (g) then uses the variant a call site induces, and the walk looks up a + branch's flag by (branch, variant of its frame), which follows from the call site on its stack; + dedup and poisoning are unchanged. The general version (per-callee summaries mapping relevant + outputs to relevant inputs and branches) is about a week. +- The slice is also flow-insensitive, and javac's local slot reuse merges unrelated locals. +- Poisoning for masked branches (callbacks, choice branches, static initializers) is conservative: + it poisons every open region, also one that closed before the masked code ran but had no + verified branch after it. + +## Loop summarization, 2026-10-26 + +Same branch (`niklas/relevance-slicing`), on top of relevance. Slicing cannot help when the loop's +branches *are* relevant: `for (i < n) if (i < val) count++; if (count == K) ...` needs one round +per iteration, because `count` is concrete and only the per-iteration branches can be flipped. +**Loop summaries** make the executor merge such code into formulas (`symbolic/summary` in the +executor): + +- **Diamonds**: an int `if` whose two sides are straight-line int arithmetic on locals and meet + again (`if (c) x++`, `if (c) x = a; else x = b`, `x = c ? a : b`). At the `if` the executor + computes both sides symbolically; a `SUMMARY_JOIN` probe at the join gives the written locals + (and a conditional expression's value) `ite(c, jump side, fall-through side)`. +- **Counting loops**: head `i < b` (or `i > b`), body straight-line int arithmetic that steps `i` + by +1 (-1) and adds constants to or sets constants in other locals, `b` unchanged. At the exit the + locals get closed forms in `N = max(b - i0, 0)`: `i = ite(i0 < b, b, i0)`, `x = x0 + d * N`, + `y = ite(N > 0, c, y0)`. A strict bound and a step of one rule out overflow of `i`; bit-vector + arithmetic wraps like Java's ints, so the closed forms are exact. + +The branches involved are flagged `merged`. The explorer treats them like sliced branches: not +flipped, constraints left out, not part of a path's relevant decisions. Ignoring the flag is +always sound (the merged formulas plus the kept constraints describe the same path), which is why +`Tree.add` keeps a node merged only if every trace through it merged it. A summary whose concrete +result differs from the execution's is dropped. Config `loopSummaries` (on in `sv-comp.cfg`); +`--no-loop-summaries` in the explorer and the harness. + +### Results + +`loop-summary` (30 tasks: `count-below`, `ternary-sum`, `found-index` are diamonds in a loop of +length n; `count-up`, `count-down` are counting loops up to an input bound), SAFE tasks, rounds +with vs without summaries: + +| family | n = 10 | n = 100 | n = 1000 | +|---|---|---|---| +| `count-below` (`if (i < val) count++`) | 1 vs 11 | 1 vs 101 | 1 vs TO | +| `ternary-sum` (`s = i < val ? s + 2 : s`) | 1 vs 11 | 1 vs 101 | 1 vs TO | +| `found-index` (`if (i == val) found = i`) | 1 vs 11 | 1 vs 101 | 1 vs TO | +| `count-up` (`while (i < val) { i++; s += 3; }`) | 1 vs TO | 1 vs TO | 1 vs TO | +| `count-down` (`while (i > 0) { i--; c += 2; }`) | 1 vs TO | 1 vs TO | 1 vs TO | + +VIOLATION tasks: 2 rounds each with summaries. Without summaries the counting loops time out at +every n: after the first flip of the loop head the solver picks a bound like 2^31 - 1, and the +executor run never ends. 30/30 correct with summaries, 15/30 without. + +`loop-summary-soundness` (18): 12 correct with summaries, 9 without; no false SAFE either way. +Summaries add `step-overflow` (an accumulator that must wrap around), `negative-start` (input start +and bound) and `nested-loops` (a counting loop inside a loop). The 6 UNKNOWN in both +configurations are the loops that must not be summarized (a call, a branch or a list in the body): +there the solver's huge bound makes the executor time out, as above. + +All swat-benchmarks (459): 436 correct with summaries, 416 without, no task lost +(`run_20261026_184010` / `run_20261026_180911`). sv-benchmarks valid-assert (1013 points, +`run_20261026_184605`) and no-runtime-exception (1279 points, `run_20261026_185901`) are identical +to the runs without summaries, apart from a crash in `rtems-lock-model/lock-00-01-10` that also +happens without them. **The shapes do not occur there**: the pre-pass finds no counting loop in +any sv-benchmarks class and 134 diamonds in all (95 of them in `java-ranger-regression`); their loops +access arrays, call methods or read inputs in their bodies. + +So summaries work well for **counters, flags and accumulators that a loop updates under +input-dependent conditions and that the target then checks** (counting matches, remembering an +index, summing weights), and for **counting loops up to an input bound with arithmetic-only +bodies**. Then the search is constant in the loop length, and a SAFE proof needs one query. + +### Bug found and fixed: merged values that get concretized (13 false SAFE) + +The first version gave 13 false SAFE on sv-benchmarks (`algorithms/InsertionSort-*`, +`MergeSortIterative-*`, `RedBlackTree-*`, `Tsp-*`, `SortedListInsert-*`, `BellmanFord-*`, +`float-nonlinear-calculation/coral79`). `Verifier.assume(N > 0)` compiles to a conditional +expression (`N <= 0 ? 0 : 1`) followed by the call: it was merged, the branch's constraint `N > 0` +was dropped, and SWAT's `assume` only looks at the concrete boolean. So solutions with `N <= 0` were +allowed, and the program halted on them unnoticed. In general a merged branch's outcome lives on +only in the merged formulas, so it is lost wherever the executor concretizes a value. +`TaintCheck` now follows the merged values forward through the method and drops a summary if +anything computed from them reaches a call, a field or array store, an array index or size, or a +return. Diamonds sharing a join label share its probe, and a pending merge whose join is never +reached unflags its branch. + +### Next steps, done (2026-10-26 evening) + +Runs: swat-benchmarks `run_20261026_202302`, sv-benchmarks valid-assert `run_20261026_202906`, +no-runtime-exception (`--sa-no-runtime-exception`) `run_20261026_204318`; 300 s, 64 workers. + +- **Bodies with other code (relevance-gated summaries).** Calls cannot change the caller's locals, + so a counting loop's closed forms stay exact whatever else its body does. The executor now + summarizes counting loops with any straight-line body (`BodyAnalysis`: one pass through ASM + frames over `local + c` / `c` / unknown; every live local the body writes must step by a constant + or be set to one; temporaries undefined at the head are ignored) and flags their branches + `mergedConditional`. The explorer honors such a flag only if the loop's region changes nothing + relevant but its method's locals (`Relevance`: `summary_ok`, i.e. no target, no terminator, no + relevant global write, no relevant branch or thrower besides the head, no call into a method with + a relevant branch); the loop then opens a region like a sliced branch, which whatever the graph + cannot see inside poisons. A loop's branches are flagged only when it leaves through its head, + and a head reached other than by the loop's back edge (a `BackEdge` plan marks it) starts a new + activation, so a loop left by an exception or a return keeps its constraints. + New `loop-summary` families `count-up-print` (a `println` in the body) and `count-up-log` (a + helper writing a field and an array): 1-2 rounds at every n. `loop-summary-soundness/body-call` + is now solved; `body-field-relevant` and `body-call-relevant`, whose bodies change what the + assert reads, keep their constraints (UNKNOWN, as before; never false SAFE). +- **Nested conditions.** An `if` inside a side whose own sides are blocks again becomes a nested op + that the simulation merges by its condition; javac lets such an if-else meet at the outer if's + end, so pending merges per join are a stack, applied innermost first. `IDIV`/`IREM` by a nonzero + constant are allowed in sides. New family `count-even` (`if (i < val) { if (i % 2 == 0) even++; + else odd++; }`): 1-2 rounds at every n; `diamond-then-check` 4 rounds instead of 12. +- **Non-strict bounds.** Counting loops may run while `i <= b` (step +1) or `i >= b` (step -1): + the closed forms count from `b + step`. They are wrong only for `b` at the type's limit, where the + loop never ends and nothing after it runs, so they add no execution that exists (only models + that none matches). New family `count-upto`: 1-2 rounds; `non-strict-bound` 3 rounds instead of + 103. +- **Small models** (`SVCompDriver.prefer_small`, `--no-prefer-small-models`). A solution with an + integral input beyond 65536 is re-solved with every integral input bounded by 16, 256 and 65536 + in turn; the first model is used (any model of the bounded query is one of the query), else the + original. Before, the solver's 2^30 for a loop bound made the next run never end. On sv-benchmarks + this alone gave **+4 valid-assert** (`algorithms/BinaryTreeSearch-MemUnsat01`, + `RedBlackTree-MemUnsat01` found, `argv-tasks/Base`, `jbmc-regression/calc` SAFE: all of them + used bounded models) **and +1 no-runtime-exception** (`jbmc-regression/calc`). +- **Deadlocks** (see below): `rtems-lock-model/lock-00-01-10` no longer crashes. + +Totals: swat-benchmarks 462 of 487 correct (no false SAFE); `loop-summary` 42/42, +`loop-summary-soundness` 14/22. sv-benchmarks valid-assert **1019 points** (was 1013), +no-runtime-exception **1281** (was 1279), no new false SAFE. + +### Deadlocked targets (the rtems-lock-model crash) + +`rtems-lock-model/lock-00-01-10` (its no-deadlock label is false: it can deadlock) was reported as a +crash under no-runtime-exception in some runs: a run deadlocked, its threads waited forever without +using CPU, so neither the target nor the explorer's CPU budget ended it, and the harness killed +everything at its wall-clock timeout (= the CPU limit) before the explorer reported anything. + +- The executor's `DeadlockWatchdog` (config `deadlockWatchdog`, on in `sv-comp.cfg`) ends a run + whose application threads (the main thread group, without DestroyJavaVM, itself and SWAT's log + appender workers) all wait forever: BLOCKED or WAITING (not TIMED_WAITING) with unchanged CPU + times for 3 s. It prints `[SWAT] Deadlock: ...` and exits normally, so the agent's shutdown hook + sends the trace; a deadlocked execution can do nothing more, so the trace is complete. +- The explorer's `ResourceBudget` also has a wall-clock deadline (default: the CPU limit), which + ends any other hang like the CPU deadline: UNKNOWN instead of a kill. + +The task now ends after about 18 s with UNKNOWN (context loss from its threads). SWAT does not +handle the no-deadlock property at all yet; the watchdog would be the basis for finding +deadlocks there (a deadlock is the violation), but a wrong detection would then be a false +VIOLATION, so it needs its own soundness review. + +### Remaining limitations + +- **Values passed on**: the taint check rules out a merged value passed to an instrumented + application method, or returned, although the executor keeps those symbolic. Following them into + the callee would merge more (an interprocedural taint analysis). +- **Steps other than one**: `i += 2` can step over the bound and overflow; a closed form needs a case + split on overflow. +- **Diamonds with other code in a side** are not merged (only loop bodies may contain other code): + the untaken side cannot be simulated if it calls or touches the heap. +- **Loops that cannot be summarized** (a branch, a list or a relevant field in the body) still + need one round per iteration, and plain DFS starves the branches behind them: it keeps flipping + the loop's exit one iteration further (`body-branch`, `heap-in-loop`, `body-*-relevant`: 130+ + rounds, UNKNOWN). `--search-order coverage` addresses that independently. diff --git a/targets/sv-comp/swat-benchmarks/java/common/org/sosy_lab/sv_benchmarks/ObjectFactory.java b/targets/sv-comp/swat-benchmarks/java/common/org/sosy_lab/sv_benchmarks/ObjectFactory.java new file mode 100644 index 00000000..e3c9efc7 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/common/org/sosy_lab/sv_benchmarks/ObjectFactory.java @@ -0,0 +1,13 @@ +// This file is part of the SV-Benchmarks collection of verification tasks: +// https://github.com/sosy-lab/sv-benchmarks +// +// SPDX-FileCopyrightText: Contributed by Falk Howar +// SPDX-FileCopyrightText: 2025 The SV-Benchmarks Community +// +// SPDX-License-Identifier: Apache-2.0 + +package org.sosy_lab.sv_benchmarks; + +public interface ObjectFactory { + T createObject(); +} diff --git a/targets/sv-comp/swat-benchmarks/java/common/org/sosy_lab/sv_benchmarks/Verifier.java b/targets/sv-comp/swat-benchmarks/java/common/org/sosy_lab/sv_benchmarks/Verifier.java new file mode 100644 index 00000000..8769f847 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/common/org/sosy_lab/sv_benchmarks/Verifier.java @@ -0,0 +1,101 @@ +// This file is part of the SV-Benchmarks collection of verification tasks: +// https://github.com/sosy-lab/sv-benchmarks +// +// SPDX-FileCopyrightText: Contributed by Peter Schrammel +// SPDX-FileCopyrightText: 2011-2025 The SV-Benchmarks Community +// +// SPDX-License-Identifier: Apache-2.0 + +package org.sosy_lab.sv_benchmarks; + +import java.util.Random; + +/** + * This class provides an interface between the benchmarks and the verification tools for + * + *
    + *
  • defining assumptions + *
  • generating nondeterministic values + *
+ * + * The verification semantics of the methods is defined in the method documentations below. The + * bodies of the methods are only approximate to allow execution, but do NOT define the verification + * semantics. + * + *

Also see java/README.md + */ +public final class Verifier { + /** + * Assume that the given condition holds true. Any execution that does not satisfy the condition + * cannot proceed. + * + * @param condition The assumption + */ + public static void assume(boolean condition) { + if (!condition) { + Runtime.getRuntime().halt(1); + } + } + + /** @return A nondeterministically chosen Boolean value (true or false) */ + public static boolean nondetBoolean() { + return new Random().nextBoolean(); + } + + /** @return A nondeterministicly chosen 8-bit signed integer value */ + public static byte nondetByte() { + return (byte) (new Random().nextInt()); + } + + /** @return A nondeterministicly chosen 16-bit unsigned integer value */ + public static char nondetChar() { + return (char) (new Random().nextInt()); + } + + /** @return A nondeterministicly chosen 16-bit signed integer value */ + public static short nondetShort() { + return (short) (new Random().nextInt()); + } + + /** @return A nondeterministicly chosen 32-bit signed integer value */ + public static int nondetInt() { + return new Random().nextInt(); + } + + /** @return A nondeterministicly chosen 64-bit signed integer value */ + public static long nondetLong() { + return new Random().nextLong(); + } + + /** @return A nondeterministicly chosen 32-bit IEEE 754 single-precision floating point value */ + public static float nondetFloat() { + return new Random().nextFloat(); + } + + /** @return A nondeterministicly chosen 64-bit IEEE 754 double-precision floating point value */ + public static double nondetDouble() { + return new Random().nextDouble(); + } + + /** + * @return A non-null character string of nondeterministically chosen length (in range + * [0,2147483647]) and consisting of nondeterministically chosen characters. + */ + public static String nondetString() { + Random random = new Random(); + int size = random.nextInt(); + assume(size >= 0); + byte[] bytes = new byte[size]; + random.nextBytes(bytes); + return new String(bytes); + } + + /** + * @return An object of given type chosen by the given factory implementation. + * @implnote For nondeterministic choice, the factory implementation must use the nondet methods + * above. + */ + public static T nondetObject(Class type, ObjectFactory factory) { + return factory.createObject(); + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n01_false.yml b/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n01_false.yml new file mode 100644 index 00000000..f2a06e49 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n01_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - guarded_n01_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n01_false/Main.java b/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n01_false/Main.java new file mode 100644 index 00000000..48e6ca4d --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n01_false/Main.java @@ -0,0 +1,40 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:0 int:2 int:1001 int:2996 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dead-callees/guarded: 1 helper calls that cannot reach the assert. +public class Main { + static int statistics; + + public static void main(String[] args) { + int mode = Verifier.nondetInt(); + int p = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c0 = Verifier.nondetInt(); + if (mode == 1) { + int stat = 0; + stat += helper0(c0); + statistics = stat; + return; + } + target(a, b); + } + + static int helper0(int x) { + if (x < 0) return 0; + if (x < 100) return 1; + return 2; + } + + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n01_true.yml b/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n01_true.yml new file mode 100644 index 00000000..f5cd55b7 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n01_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - guarded_n01_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n01_true/Main.java b/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n01_true/Main.java new file mode 100644 index 00000000..6c88103d --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n01_true/Main.java @@ -0,0 +1,41 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:0 int:2 int:1001 int:2996 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dead-callees/guarded: 1 helper calls that cannot reach the assert. +public class Main { + static int statistics; + + public static void main(String[] args) { + int mode = Verifier.nondetInt(); + int p = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c0 = Verifier.nondetInt(); + if (mode == 1) { + int stat = 0; + stat += helper0(c0); + statistics = stat; + return; + } + target(a, b); + } + + static int helper0(int x) { + if (x < 0) return 0; + if (x < 100) return 1; + return 2; + } + + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n02_false.yml b/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n02_false.yml new file mode 100644 index 00000000..d5b71ad1 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n02_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - guarded_n02_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n02_false/Main.java b/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n02_false/Main.java new file mode 100644 index 00000000..18144ca8 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n02_false/Main.java @@ -0,0 +1,48 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:0 int:2 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dead-callees/guarded: 2 helper calls that cannot reach the assert. +public class Main { + static int statistics; + + public static void main(String[] args) { + int mode = Verifier.nondetInt(); + int p = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c0 = Verifier.nondetInt(); + int c1 = Verifier.nondetInt(); + if (mode == 1) { + int stat = 0; + stat += helper0(c0); + stat += helper1(c1); + statistics = stat; + return; + } + target(a, b); + } + + static int helper0(int x) { + if (x < 0) return 0; + if (x < 100) return 1; + return 2; + } + + static int helper1(int x) { + if (x < 1) return 0; + if (x < 101) return 1; + return 2; + } + + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n02_true.yml b/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n02_true.yml new file mode 100644 index 00000000..e3f8e678 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n02_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - guarded_n02_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n02_true/Main.java b/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n02_true/Main.java new file mode 100644 index 00000000..97a9ec67 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n02_true/Main.java @@ -0,0 +1,49 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:0 int:2 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dead-callees/guarded: 2 helper calls that cannot reach the assert. +public class Main { + static int statistics; + + public static void main(String[] args) { + int mode = Verifier.nondetInt(); + int p = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c0 = Verifier.nondetInt(); + int c1 = Verifier.nondetInt(); + if (mode == 1) { + int stat = 0; + stat += helper0(c0); + stat += helper1(c1); + statistics = stat; + return; + } + target(a, b); + } + + static int helper0(int x) { + if (x < 0) return 0; + if (x < 100) return 1; + return 2; + } + + static int helper1(int x) { + if (x < 1) return 0; + if (x < 101) return 1; + return 2; + } + + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n03_false.yml b/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n03_false.yml new file mode 100644 index 00000000..f8100067 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n03_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - guarded_n03_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n03_false/Main.java b/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n03_false/Main.java new file mode 100644 index 00000000..ba26223e --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n03_false/Main.java @@ -0,0 +1,56 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:0 int:2 int:1001 int:2996 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dead-callees/guarded: 3 helper calls that cannot reach the assert. +public class Main { + static int statistics; + + public static void main(String[] args) { + int mode = Verifier.nondetInt(); + int p = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c0 = Verifier.nondetInt(); + int c1 = Verifier.nondetInt(); + int c2 = Verifier.nondetInt(); + if (mode == 1) { + int stat = 0; + stat += helper0(c0); + stat += helper1(c1); + stat += helper2(c2); + statistics = stat; + return; + } + target(a, b); + } + + static int helper0(int x) { + if (x < 0) return 0; + if (x < 100) return 1; + return 2; + } + + static int helper1(int x) { + if (x < 1) return 0; + if (x < 101) return 1; + return 2; + } + + static int helper2(int x) { + if (x < 2) return 0; + if (x < 102) return 1; + return 2; + } + + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n03_true.yml b/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n03_true.yml new file mode 100644 index 00000000..e6c59cea --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n03_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - guarded_n03_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n03_true/Main.java b/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n03_true/Main.java new file mode 100644 index 00000000..22133aef --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n03_true/Main.java @@ -0,0 +1,57 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:0 int:2 int:1001 int:2996 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dead-callees/guarded: 3 helper calls that cannot reach the assert. +public class Main { + static int statistics; + + public static void main(String[] args) { + int mode = Verifier.nondetInt(); + int p = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c0 = Verifier.nondetInt(); + int c1 = Verifier.nondetInt(); + int c2 = Verifier.nondetInt(); + if (mode == 1) { + int stat = 0; + stat += helper0(c0); + stat += helper1(c1); + stat += helper2(c2); + statistics = stat; + return; + } + target(a, b); + } + + static int helper0(int x) { + if (x < 0) return 0; + if (x < 100) return 1; + return 2; + } + + static int helper1(int x) { + if (x < 1) return 0; + if (x < 101) return 1; + return 2; + } + + static int helper2(int x) { + if (x < 2) return 0; + if (x < 102) return 1; + return 2; + } + + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n04_false.yml b/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n04_false.yml new file mode 100644 index 00000000..16f3911b --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n04_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - guarded_n04_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n04_false/Main.java b/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n04_false/Main.java new file mode 100644 index 00000000..8ef30fe8 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n04_false/Main.java @@ -0,0 +1,64 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:0 int:2 int:1001 int:2996 int:0 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dead-callees/guarded: 4 helper calls that cannot reach the assert. +public class Main { + static int statistics; + + public static void main(String[] args) { + int mode = Verifier.nondetInt(); + int p = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c0 = Verifier.nondetInt(); + int c1 = Verifier.nondetInt(); + int c2 = Verifier.nondetInt(); + int c3 = Verifier.nondetInt(); + if (mode == 1) { + int stat = 0; + stat += helper0(c0); + stat += helper1(c1); + stat += helper2(c2); + stat += helper3(c3); + statistics = stat; + return; + } + target(a, b); + } + + static int helper0(int x) { + if (x < 0) return 0; + if (x < 100) return 1; + return 2; + } + + static int helper1(int x) { + if (x < 1) return 0; + if (x < 101) return 1; + return 2; + } + + static int helper2(int x) { + if (x < 2) return 0; + if (x < 102) return 1; + return 2; + } + + static int helper3(int x) { + if (x < 3) return 0; + if (x < 103) return 1; + return 2; + } + + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n04_true.yml b/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n04_true.yml new file mode 100644 index 00000000..db20c37e --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n04_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - guarded_n04_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n04_true/Main.java b/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n04_true/Main.java new file mode 100644 index 00000000..f966ef5b --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n04_true/Main.java @@ -0,0 +1,65 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:0 int:2 int:1001 int:2996 int:0 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dead-callees/guarded: 4 helper calls that cannot reach the assert. +public class Main { + static int statistics; + + public static void main(String[] args) { + int mode = Verifier.nondetInt(); + int p = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c0 = Verifier.nondetInt(); + int c1 = Verifier.nondetInt(); + int c2 = Verifier.nondetInt(); + int c3 = Verifier.nondetInt(); + if (mode == 1) { + int stat = 0; + stat += helper0(c0); + stat += helper1(c1); + stat += helper2(c2); + stat += helper3(c3); + statistics = stat; + return; + } + target(a, b); + } + + static int helper0(int x) { + if (x < 0) return 0; + if (x < 100) return 1; + return 2; + } + + static int helper1(int x) { + if (x < 1) return 0; + if (x < 101) return 1; + return 2; + } + + static int helper2(int x) { + if (x < 2) return 0; + if (x < 102) return 1; + return 2; + } + + static int helper3(int x) { + if (x < 3) return 0; + if (x < 103) return 1; + return 2; + } + + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n05_false.yml b/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n05_false.yml new file mode 100644 index 00000000..17b49e21 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n05_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - guarded_n05_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n05_false/Main.java b/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n05_false/Main.java new file mode 100644 index 00000000..d1d734ae --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n05_false/Main.java @@ -0,0 +1,72 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:0 int:2 int:1001 int:2996 int:0 int:0 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dead-callees/guarded: 5 helper calls that cannot reach the assert. +public class Main { + static int statistics; + + public static void main(String[] args) { + int mode = Verifier.nondetInt(); + int p = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c0 = Verifier.nondetInt(); + int c1 = Verifier.nondetInt(); + int c2 = Verifier.nondetInt(); + int c3 = Verifier.nondetInt(); + int c4 = Verifier.nondetInt(); + if (mode == 1) { + int stat = 0; + stat += helper0(c0); + stat += helper1(c1); + stat += helper2(c2); + stat += helper3(c3); + stat += helper4(c4); + statistics = stat; + return; + } + target(a, b); + } + + static int helper0(int x) { + if (x < 0) return 0; + if (x < 100) return 1; + return 2; + } + + static int helper1(int x) { + if (x < 1) return 0; + if (x < 101) return 1; + return 2; + } + + static int helper2(int x) { + if (x < 2) return 0; + if (x < 102) return 1; + return 2; + } + + static int helper3(int x) { + if (x < 3) return 0; + if (x < 103) return 1; + return 2; + } + + static int helper4(int x) { + if (x < 4) return 0; + if (x < 104) return 1; + return 2; + } + + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n05_true.yml b/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n05_true.yml new file mode 100644 index 00000000..d45b0a84 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n05_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - guarded_n05_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n05_true/Main.java b/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n05_true/Main.java new file mode 100644 index 00000000..9eac347a --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n05_true/Main.java @@ -0,0 +1,73 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:0 int:2 int:1001 int:2996 int:0 int:0 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dead-callees/guarded: 5 helper calls that cannot reach the assert. +public class Main { + static int statistics; + + public static void main(String[] args) { + int mode = Verifier.nondetInt(); + int p = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c0 = Verifier.nondetInt(); + int c1 = Verifier.nondetInt(); + int c2 = Verifier.nondetInt(); + int c3 = Verifier.nondetInt(); + int c4 = Verifier.nondetInt(); + if (mode == 1) { + int stat = 0; + stat += helper0(c0); + stat += helper1(c1); + stat += helper2(c2); + stat += helper3(c3); + stat += helper4(c4); + statistics = stat; + return; + } + target(a, b); + } + + static int helper0(int x) { + if (x < 0) return 0; + if (x < 100) return 1; + return 2; + } + + static int helper1(int x) { + if (x < 1) return 0; + if (x < 101) return 1; + return 2; + } + + static int helper2(int x) { + if (x < 2) return 0; + if (x < 102) return 1; + return 2; + } + + static int helper3(int x) { + if (x < 3) return 0; + if (x < 103) return 1; + return 2; + } + + static int helper4(int x) { + if (x < 4) return 0; + if (x < 104) return 1; + return 2; + } + + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n06_false.yml b/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n06_false.yml new file mode 100644 index 00000000..86e894bf --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n06_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - guarded_n06_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n06_false/Main.java b/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n06_false/Main.java new file mode 100644 index 00000000..cbeb3b30 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n06_false/Main.java @@ -0,0 +1,80 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:0 int:2 int:1001 int:2996 int:0 int:0 int:0 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dead-callees/guarded: 6 helper calls that cannot reach the assert. +public class Main { + static int statistics; + + public static void main(String[] args) { + int mode = Verifier.nondetInt(); + int p = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c0 = Verifier.nondetInt(); + int c1 = Verifier.nondetInt(); + int c2 = Verifier.nondetInt(); + int c3 = Verifier.nondetInt(); + int c4 = Verifier.nondetInt(); + int c5 = Verifier.nondetInt(); + if (mode == 1) { + int stat = 0; + stat += helper0(c0); + stat += helper1(c1); + stat += helper2(c2); + stat += helper3(c3); + stat += helper4(c4); + stat += helper5(c5); + statistics = stat; + return; + } + target(a, b); + } + + static int helper0(int x) { + if (x < 0) return 0; + if (x < 100) return 1; + return 2; + } + + static int helper1(int x) { + if (x < 1) return 0; + if (x < 101) return 1; + return 2; + } + + static int helper2(int x) { + if (x < 2) return 0; + if (x < 102) return 1; + return 2; + } + + static int helper3(int x) { + if (x < 3) return 0; + if (x < 103) return 1; + return 2; + } + + static int helper4(int x) { + if (x < 4) return 0; + if (x < 104) return 1; + return 2; + } + + static int helper5(int x) { + if (x < 5) return 0; + if (x < 105) return 1; + return 2; + } + + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n06_true.yml b/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n06_true.yml new file mode 100644 index 00000000..9bdeb3da --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n06_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - guarded_n06_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n06_true/Main.java b/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n06_true/Main.java new file mode 100644 index 00000000..c5c0107a --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/guarded_n06_true/Main.java @@ -0,0 +1,81 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:0 int:2 int:1001 int:2996 int:0 int:0 int:0 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dead-callees/guarded: 6 helper calls that cannot reach the assert. +public class Main { + static int statistics; + + public static void main(String[] args) { + int mode = Verifier.nondetInt(); + int p = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c0 = Verifier.nondetInt(); + int c1 = Verifier.nondetInt(); + int c2 = Verifier.nondetInt(); + int c3 = Verifier.nondetInt(); + int c4 = Verifier.nondetInt(); + int c5 = Verifier.nondetInt(); + if (mode == 1) { + int stat = 0; + stat += helper0(c0); + stat += helper1(c1); + stat += helper2(c2); + stat += helper3(c3); + stat += helper4(c4); + stat += helper5(c5); + statistics = stat; + return; + } + target(a, b); + } + + static int helper0(int x) { + if (x < 0) return 0; + if (x < 100) return 1; + return 2; + } + + static int helper1(int x) { + if (x < 1) return 0; + if (x < 101) return 1; + return 2; + } + + static int helper2(int x) { + if (x < 2) return 0; + if (x < 102) return 1; + return 2; + } + + static int helper3(int x) { + if (x < 3) return 0; + if (x < 103) return 1; + return 2; + } + + static int helper4(int x) { + if (x < 4) return 0; + if (x < 104) return 1; + return 2; + } + + static int helper5(int x) { + if (x < 5) return 0; + if (x < 105) return 1; + return 2; + } + + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n01_false.yml b/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n01_false.yml new file mode 100644 index 00000000..e644698a --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n01_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - shared_n01_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n01_false/Main.java b/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n01_false/Main.java new file mode 100644 index 00000000..82ea8ee5 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n01_false/Main.java @@ -0,0 +1,42 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:0 int:100 int:1001 int:2996 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dead-callees/shared: 1 helper calls that cannot reach the assert. +public class Main { + static int statistics; + + public static void main(String[] args) { + int mode = Verifier.nondetInt(); + int p = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c0 = Verifier.nondetInt(); + if (mode == 1) { + int stat = 0; + stat += classify(c0); + statistics = stat; + return; + } + if (classify(p) == 2) { + target(a, b); + } + } + + // Two branches, three paths. + static int classify(int x) { + if (x < 0) return 0; + if (x < 100) return 1; + return 2; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n01_true.yml b/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n01_true.yml new file mode 100644 index 00000000..9a0651b2 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n01_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - shared_n01_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n01_true/Main.java b/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n01_true/Main.java new file mode 100644 index 00000000..53722ee5 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n01_true/Main.java @@ -0,0 +1,43 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:0 int:100 int:1001 int:2996 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dead-callees/shared: 1 helper calls that cannot reach the assert. +public class Main { + static int statistics; + + public static void main(String[] args) { + int mode = Verifier.nondetInt(); + int p = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c0 = Verifier.nondetInt(); + if (mode == 1) { + int stat = 0; + stat += classify(c0); + statistics = stat; + return; + } + if (classify(p) == 2) { + target(a, b); + } + } + + // Two branches, three paths. + static int classify(int x) { + if (x < 0) return 0; + if (x < 100) return 1; + return 2; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n02_false.yml b/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n02_false.yml new file mode 100644 index 00000000..7c6071eb --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n02_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - shared_n02_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n02_false/Main.java b/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n02_false/Main.java new file mode 100644 index 00000000..8bbee84d --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n02_false/Main.java @@ -0,0 +1,44 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:0 int:100 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dead-callees/shared: 2 helper calls that cannot reach the assert. +public class Main { + static int statistics; + + public static void main(String[] args) { + int mode = Verifier.nondetInt(); + int p = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c0 = Verifier.nondetInt(); + int c1 = Verifier.nondetInt(); + if (mode == 1) { + int stat = 0; + stat += classify(c0); + stat += classify(c1); + statistics = stat; + return; + } + if (classify(p) == 2) { + target(a, b); + } + } + + // Two branches, three paths. + static int classify(int x) { + if (x < 0) return 0; + if (x < 100) return 1; + return 2; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n02_true.yml b/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n02_true.yml new file mode 100644 index 00000000..41b08502 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n02_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - shared_n02_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n02_true/Main.java b/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n02_true/Main.java new file mode 100644 index 00000000..3b4d8c74 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n02_true/Main.java @@ -0,0 +1,45 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:0 int:100 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dead-callees/shared: 2 helper calls that cannot reach the assert. +public class Main { + static int statistics; + + public static void main(String[] args) { + int mode = Verifier.nondetInt(); + int p = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c0 = Verifier.nondetInt(); + int c1 = Verifier.nondetInt(); + if (mode == 1) { + int stat = 0; + stat += classify(c0); + stat += classify(c1); + statistics = stat; + return; + } + if (classify(p) == 2) { + target(a, b); + } + } + + // Two branches, three paths. + static int classify(int x) { + if (x < 0) return 0; + if (x < 100) return 1; + return 2; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n03_false.yml b/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n03_false.yml new file mode 100644 index 00000000..15481b54 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n03_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - shared_n03_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n03_false/Main.java b/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n03_false/Main.java new file mode 100644 index 00000000..c00781e5 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n03_false/Main.java @@ -0,0 +1,46 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:0 int:100 int:1001 int:2996 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dead-callees/shared: 3 helper calls that cannot reach the assert. +public class Main { + static int statistics; + + public static void main(String[] args) { + int mode = Verifier.nondetInt(); + int p = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c0 = Verifier.nondetInt(); + int c1 = Verifier.nondetInt(); + int c2 = Verifier.nondetInt(); + if (mode == 1) { + int stat = 0; + stat += classify(c0); + stat += classify(c1); + stat += classify(c2); + statistics = stat; + return; + } + if (classify(p) == 2) { + target(a, b); + } + } + + // Two branches, three paths. + static int classify(int x) { + if (x < 0) return 0; + if (x < 100) return 1; + return 2; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n03_true.yml b/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n03_true.yml new file mode 100644 index 00000000..2d594d6b --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n03_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - shared_n03_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n03_true/Main.java b/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n03_true/Main.java new file mode 100644 index 00000000..fdcfa659 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n03_true/Main.java @@ -0,0 +1,47 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:0 int:100 int:1001 int:2996 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dead-callees/shared: 3 helper calls that cannot reach the assert. +public class Main { + static int statistics; + + public static void main(String[] args) { + int mode = Verifier.nondetInt(); + int p = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c0 = Verifier.nondetInt(); + int c1 = Verifier.nondetInt(); + int c2 = Verifier.nondetInt(); + if (mode == 1) { + int stat = 0; + stat += classify(c0); + stat += classify(c1); + stat += classify(c2); + statistics = stat; + return; + } + if (classify(p) == 2) { + target(a, b); + } + } + + // Two branches, three paths. + static int classify(int x) { + if (x < 0) return 0; + if (x < 100) return 1; + return 2; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n04_false.yml b/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n04_false.yml new file mode 100644 index 00000000..ad171898 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n04_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - shared_n04_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n04_false/Main.java b/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n04_false/Main.java new file mode 100644 index 00000000..7bcf61e6 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n04_false/Main.java @@ -0,0 +1,48 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:0 int:100 int:1001 int:2996 int:0 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dead-callees/shared: 4 helper calls that cannot reach the assert. +public class Main { + static int statistics; + + public static void main(String[] args) { + int mode = Verifier.nondetInt(); + int p = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c0 = Verifier.nondetInt(); + int c1 = Verifier.nondetInt(); + int c2 = Verifier.nondetInt(); + int c3 = Verifier.nondetInt(); + if (mode == 1) { + int stat = 0; + stat += classify(c0); + stat += classify(c1); + stat += classify(c2); + stat += classify(c3); + statistics = stat; + return; + } + if (classify(p) == 2) { + target(a, b); + } + } + + // Two branches, three paths. + static int classify(int x) { + if (x < 0) return 0; + if (x < 100) return 1; + return 2; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n04_true.yml b/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n04_true.yml new file mode 100644 index 00000000..ac298dcf --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n04_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - shared_n04_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n04_true/Main.java b/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n04_true/Main.java new file mode 100644 index 00000000..7daa69d3 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n04_true/Main.java @@ -0,0 +1,49 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:0 int:100 int:1001 int:2996 int:0 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dead-callees/shared: 4 helper calls that cannot reach the assert. +public class Main { + static int statistics; + + public static void main(String[] args) { + int mode = Verifier.nondetInt(); + int p = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c0 = Verifier.nondetInt(); + int c1 = Verifier.nondetInt(); + int c2 = Verifier.nondetInt(); + int c3 = Verifier.nondetInt(); + if (mode == 1) { + int stat = 0; + stat += classify(c0); + stat += classify(c1); + stat += classify(c2); + stat += classify(c3); + statistics = stat; + return; + } + if (classify(p) == 2) { + target(a, b); + } + } + + // Two branches, three paths. + static int classify(int x) { + if (x < 0) return 0; + if (x < 100) return 1; + return 2; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n05_false.yml b/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n05_false.yml new file mode 100644 index 00000000..4a7248b3 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n05_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - shared_n05_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n05_false/Main.java b/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n05_false/Main.java new file mode 100644 index 00000000..7005797f --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n05_false/Main.java @@ -0,0 +1,50 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:0 int:100 int:1001 int:2996 int:0 int:0 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dead-callees/shared: 5 helper calls that cannot reach the assert. +public class Main { + static int statistics; + + public static void main(String[] args) { + int mode = Verifier.nondetInt(); + int p = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c0 = Verifier.nondetInt(); + int c1 = Verifier.nondetInt(); + int c2 = Verifier.nondetInt(); + int c3 = Verifier.nondetInt(); + int c4 = Verifier.nondetInt(); + if (mode == 1) { + int stat = 0; + stat += classify(c0); + stat += classify(c1); + stat += classify(c2); + stat += classify(c3); + stat += classify(c4); + statistics = stat; + return; + } + if (classify(p) == 2) { + target(a, b); + } + } + + // Two branches, three paths. + static int classify(int x) { + if (x < 0) return 0; + if (x < 100) return 1; + return 2; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n05_true.yml b/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n05_true.yml new file mode 100644 index 00000000..a66c41ec --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n05_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - shared_n05_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n05_true/Main.java b/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n05_true/Main.java new file mode 100644 index 00000000..fe5a06db --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n05_true/Main.java @@ -0,0 +1,51 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:0 int:100 int:1001 int:2996 int:0 int:0 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dead-callees/shared: 5 helper calls that cannot reach the assert. +public class Main { + static int statistics; + + public static void main(String[] args) { + int mode = Verifier.nondetInt(); + int p = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c0 = Verifier.nondetInt(); + int c1 = Verifier.nondetInt(); + int c2 = Verifier.nondetInt(); + int c3 = Verifier.nondetInt(); + int c4 = Verifier.nondetInt(); + if (mode == 1) { + int stat = 0; + stat += classify(c0); + stat += classify(c1); + stat += classify(c2); + stat += classify(c3); + stat += classify(c4); + statistics = stat; + return; + } + if (classify(p) == 2) { + target(a, b); + } + } + + // Two branches, three paths. + static int classify(int x) { + if (x < 0) return 0; + if (x < 100) return 1; + return 2; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n06_false.yml b/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n06_false.yml new file mode 100644 index 00000000..9bb7b491 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n06_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - shared_n06_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n06_false/Main.java b/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n06_false/Main.java new file mode 100644 index 00000000..74916ba3 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n06_false/Main.java @@ -0,0 +1,52 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:0 int:100 int:1001 int:2996 int:0 int:0 int:0 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dead-callees/shared: 6 helper calls that cannot reach the assert. +public class Main { + static int statistics; + + public static void main(String[] args) { + int mode = Verifier.nondetInt(); + int p = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c0 = Verifier.nondetInt(); + int c1 = Verifier.nondetInt(); + int c2 = Verifier.nondetInt(); + int c3 = Verifier.nondetInt(); + int c4 = Verifier.nondetInt(); + int c5 = Verifier.nondetInt(); + if (mode == 1) { + int stat = 0; + stat += classify(c0); + stat += classify(c1); + stat += classify(c2); + stat += classify(c3); + stat += classify(c4); + stat += classify(c5); + statistics = stat; + return; + } + if (classify(p) == 2) { + target(a, b); + } + } + + // Two branches, three paths. + static int classify(int x) { + if (x < 0) return 0; + if (x < 100) return 1; + return 2; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n06_true.yml b/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n06_true.yml new file mode 100644 index 00000000..ac8d31e4 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n06_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - shared_n06_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n06_true/Main.java b/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n06_true/Main.java new file mode 100644 index 00000000..189d1310 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/shared_n06_true/Main.java @@ -0,0 +1,53 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:0 int:100 int:1001 int:2996 int:0 int:0 int:0 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dead-callees/shared: 6 helper calls that cannot reach the assert. +public class Main { + static int statistics; + + public static void main(String[] args) { + int mode = Verifier.nondetInt(); + int p = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c0 = Verifier.nondetInt(); + int c1 = Verifier.nondetInt(); + int c2 = Verifier.nondetInt(); + int c3 = Verifier.nondetInt(); + int c4 = Verifier.nondetInt(); + int c5 = Verifier.nondetInt(); + if (mode == 1) { + int stat = 0; + stat += classify(c0); + stat += classify(c1); + stat += classify(c2); + stat += classify(c3); + stat += classify(c4); + stat += classify(c5); + statistics = stat; + return; + } + if (classify(p) == 2) { + target(a, b); + } + } + + // Two branches, three paths. + static int classify(int x) { + if (x < 0) return 0; + if (x < 100) return 1; + return 2; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n01_false.yml b/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n01_false.yml new file mode 100644 index 00000000..49d96ac9 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n01_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - suffix_n01_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n01_false/Main.java b/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n01_false/Main.java new file mode 100644 index 00000000..d8262e3e --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n01_false/Main.java @@ -0,0 +1,37 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:0 int:2 int:1001 int:2996 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dead-callees/suffix: 1 helper calls that cannot reach the assert. +public class Main { + static int statistics; + + public static void main(String[] args) { + int mode = Verifier.nondetInt(); + int p = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c0 = Verifier.nondetInt(); + target(a, b); + int stat = 0; + stat += helper0(c0); + statistics = stat; + } + + static int helper0(int x) { + if (x < 0) return 0; + if (x < 100) return 1; + return 2; + } + + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n01_true.yml b/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n01_true.yml new file mode 100644 index 00000000..13aa6a7e --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n01_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - suffix_n01_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n01_true/Main.java b/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n01_true/Main.java new file mode 100644 index 00000000..e007b645 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n01_true/Main.java @@ -0,0 +1,38 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:0 int:2 int:1001 int:2996 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dead-callees/suffix: 1 helper calls that cannot reach the assert. +public class Main { + static int statistics; + + public static void main(String[] args) { + int mode = Verifier.nondetInt(); + int p = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c0 = Verifier.nondetInt(); + target(a, b); + int stat = 0; + stat += helper0(c0); + statistics = stat; + } + + static int helper0(int x) { + if (x < 0) return 0; + if (x < 100) return 1; + return 2; + } + + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n02_false.yml b/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n02_false.yml new file mode 100644 index 00000000..1c12c94d --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n02_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - suffix_n02_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n02_false/Main.java b/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n02_false/Main.java new file mode 100644 index 00000000..d7258ece --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n02_false/Main.java @@ -0,0 +1,45 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:0 int:2 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dead-callees/suffix: 2 helper calls that cannot reach the assert. +public class Main { + static int statistics; + + public static void main(String[] args) { + int mode = Verifier.nondetInt(); + int p = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c0 = Verifier.nondetInt(); + int c1 = Verifier.nondetInt(); + target(a, b); + int stat = 0; + stat += helper0(c0); + stat += helper1(c1); + statistics = stat; + } + + static int helper0(int x) { + if (x < 0) return 0; + if (x < 100) return 1; + return 2; + } + + static int helper1(int x) { + if (x < 1) return 0; + if (x < 101) return 1; + return 2; + } + + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n02_true.yml b/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n02_true.yml new file mode 100644 index 00000000..90c4ffc5 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n02_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - suffix_n02_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n02_true/Main.java b/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n02_true/Main.java new file mode 100644 index 00000000..6ee40f80 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n02_true/Main.java @@ -0,0 +1,46 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:0 int:2 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dead-callees/suffix: 2 helper calls that cannot reach the assert. +public class Main { + static int statistics; + + public static void main(String[] args) { + int mode = Verifier.nondetInt(); + int p = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c0 = Verifier.nondetInt(); + int c1 = Verifier.nondetInt(); + target(a, b); + int stat = 0; + stat += helper0(c0); + stat += helper1(c1); + statistics = stat; + } + + static int helper0(int x) { + if (x < 0) return 0; + if (x < 100) return 1; + return 2; + } + + static int helper1(int x) { + if (x < 1) return 0; + if (x < 101) return 1; + return 2; + } + + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n03_false.yml b/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n03_false.yml new file mode 100644 index 00000000..ef7b44f7 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n03_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - suffix_n03_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n03_false/Main.java b/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n03_false/Main.java new file mode 100644 index 00000000..2f194cd2 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n03_false/Main.java @@ -0,0 +1,53 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:0 int:2 int:1001 int:2996 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dead-callees/suffix: 3 helper calls that cannot reach the assert. +public class Main { + static int statistics; + + public static void main(String[] args) { + int mode = Verifier.nondetInt(); + int p = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c0 = Verifier.nondetInt(); + int c1 = Verifier.nondetInt(); + int c2 = Verifier.nondetInt(); + target(a, b); + int stat = 0; + stat += helper0(c0); + stat += helper1(c1); + stat += helper2(c2); + statistics = stat; + } + + static int helper0(int x) { + if (x < 0) return 0; + if (x < 100) return 1; + return 2; + } + + static int helper1(int x) { + if (x < 1) return 0; + if (x < 101) return 1; + return 2; + } + + static int helper2(int x) { + if (x < 2) return 0; + if (x < 102) return 1; + return 2; + } + + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n03_true.yml b/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n03_true.yml new file mode 100644 index 00000000..416b9878 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n03_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - suffix_n03_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n03_true/Main.java b/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n03_true/Main.java new file mode 100644 index 00000000..00db9148 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n03_true/Main.java @@ -0,0 +1,54 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:0 int:2 int:1001 int:2996 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dead-callees/suffix: 3 helper calls that cannot reach the assert. +public class Main { + static int statistics; + + public static void main(String[] args) { + int mode = Verifier.nondetInt(); + int p = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c0 = Verifier.nondetInt(); + int c1 = Verifier.nondetInt(); + int c2 = Verifier.nondetInt(); + target(a, b); + int stat = 0; + stat += helper0(c0); + stat += helper1(c1); + stat += helper2(c2); + statistics = stat; + } + + static int helper0(int x) { + if (x < 0) return 0; + if (x < 100) return 1; + return 2; + } + + static int helper1(int x) { + if (x < 1) return 0; + if (x < 101) return 1; + return 2; + } + + static int helper2(int x) { + if (x < 2) return 0; + if (x < 102) return 1; + return 2; + } + + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n04_false.yml b/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n04_false.yml new file mode 100644 index 00000000..235a5574 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n04_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - suffix_n04_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n04_false/Main.java b/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n04_false/Main.java new file mode 100644 index 00000000..d78fa314 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n04_false/Main.java @@ -0,0 +1,61 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:0 int:2 int:1001 int:2996 int:0 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dead-callees/suffix: 4 helper calls that cannot reach the assert. +public class Main { + static int statistics; + + public static void main(String[] args) { + int mode = Verifier.nondetInt(); + int p = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c0 = Verifier.nondetInt(); + int c1 = Verifier.nondetInt(); + int c2 = Verifier.nondetInt(); + int c3 = Verifier.nondetInt(); + target(a, b); + int stat = 0; + stat += helper0(c0); + stat += helper1(c1); + stat += helper2(c2); + stat += helper3(c3); + statistics = stat; + } + + static int helper0(int x) { + if (x < 0) return 0; + if (x < 100) return 1; + return 2; + } + + static int helper1(int x) { + if (x < 1) return 0; + if (x < 101) return 1; + return 2; + } + + static int helper2(int x) { + if (x < 2) return 0; + if (x < 102) return 1; + return 2; + } + + static int helper3(int x) { + if (x < 3) return 0; + if (x < 103) return 1; + return 2; + } + + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n04_true.yml b/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n04_true.yml new file mode 100644 index 00000000..1f30cec4 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n04_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - suffix_n04_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n04_true/Main.java b/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n04_true/Main.java new file mode 100644 index 00000000..1bea8909 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n04_true/Main.java @@ -0,0 +1,62 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:0 int:2 int:1001 int:2996 int:0 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dead-callees/suffix: 4 helper calls that cannot reach the assert. +public class Main { + static int statistics; + + public static void main(String[] args) { + int mode = Verifier.nondetInt(); + int p = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c0 = Verifier.nondetInt(); + int c1 = Verifier.nondetInt(); + int c2 = Verifier.nondetInt(); + int c3 = Verifier.nondetInt(); + target(a, b); + int stat = 0; + stat += helper0(c0); + stat += helper1(c1); + stat += helper2(c2); + stat += helper3(c3); + statistics = stat; + } + + static int helper0(int x) { + if (x < 0) return 0; + if (x < 100) return 1; + return 2; + } + + static int helper1(int x) { + if (x < 1) return 0; + if (x < 101) return 1; + return 2; + } + + static int helper2(int x) { + if (x < 2) return 0; + if (x < 102) return 1; + return 2; + } + + static int helper3(int x) { + if (x < 3) return 0; + if (x < 103) return 1; + return 2; + } + + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n05_false.yml b/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n05_false.yml new file mode 100644 index 00000000..1344bacc --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n05_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - suffix_n05_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n05_false/Main.java b/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n05_false/Main.java new file mode 100644 index 00000000..cf902a9f --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n05_false/Main.java @@ -0,0 +1,69 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:0 int:2 int:1001 int:2996 int:0 int:0 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dead-callees/suffix: 5 helper calls that cannot reach the assert. +public class Main { + static int statistics; + + public static void main(String[] args) { + int mode = Verifier.nondetInt(); + int p = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c0 = Verifier.nondetInt(); + int c1 = Verifier.nondetInt(); + int c2 = Verifier.nondetInt(); + int c3 = Verifier.nondetInt(); + int c4 = Verifier.nondetInt(); + target(a, b); + int stat = 0; + stat += helper0(c0); + stat += helper1(c1); + stat += helper2(c2); + stat += helper3(c3); + stat += helper4(c4); + statistics = stat; + } + + static int helper0(int x) { + if (x < 0) return 0; + if (x < 100) return 1; + return 2; + } + + static int helper1(int x) { + if (x < 1) return 0; + if (x < 101) return 1; + return 2; + } + + static int helper2(int x) { + if (x < 2) return 0; + if (x < 102) return 1; + return 2; + } + + static int helper3(int x) { + if (x < 3) return 0; + if (x < 103) return 1; + return 2; + } + + static int helper4(int x) { + if (x < 4) return 0; + if (x < 104) return 1; + return 2; + } + + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n05_true.yml b/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n05_true.yml new file mode 100644 index 00000000..d12dd071 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n05_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - suffix_n05_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n05_true/Main.java b/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n05_true/Main.java new file mode 100644 index 00000000..cd0ea3bb --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n05_true/Main.java @@ -0,0 +1,70 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:0 int:2 int:1001 int:2996 int:0 int:0 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dead-callees/suffix: 5 helper calls that cannot reach the assert. +public class Main { + static int statistics; + + public static void main(String[] args) { + int mode = Verifier.nondetInt(); + int p = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c0 = Verifier.nondetInt(); + int c1 = Verifier.nondetInt(); + int c2 = Verifier.nondetInt(); + int c3 = Verifier.nondetInt(); + int c4 = Verifier.nondetInt(); + target(a, b); + int stat = 0; + stat += helper0(c0); + stat += helper1(c1); + stat += helper2(c2); + stat += helper3(c3); + stat += helper4(c4); + statistics = stat; + } + + static int helper0(int x) { + if (x < 0) return 0; + if (x < 100) return 1; + return 2; + } + + static int helper1(int x) { + if (x < 1) return 0; + if (x < 101) return 1; + return 2; + } + + static int helper2(int x) { + if (x < 2) return 0; + if (x < 102) return 1; + return 2; + } + + static int helper3(int x) { + if (x < 3) return 0; + if (x < 103) return 1; + return 2; + } + + static int helper4(int x) { + if (x < 4) return 0; + if (x < 104) return 1; + return 2; + } + + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n06_false.yml b/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n06_false.yml new file mode 100644 index 00000000..243eb6ca --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n06_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - suffix_n06_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n06_false/Main.java b/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n06_false/Main.java new file mode 100644 index 00000000..f0fdf889 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n06_false/Main.java @@ -0,0 +1,77 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:0 int:2 int:1001 int:2996 int:0 int:0 int:0 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dead-callees/suffix: 6 helper calls that cannot reach the assert. +public class Main { + static int statistics; + + public static void main(String[] args) { + int mode = Verifier.nondetInt(); + int p = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c0 = Verifier.nondetInt(); + int c1 = Verifier.nondetInt(); + int c2 = Verifier.nondetInt(); + int c3 = Verifier.nondetInt(); + int c4 = Verifier.nondetInt(); + int c5 = Verifier.nondetInt(); + target(a, b); + int stat = 0; + stat += helper0(c0); + stat += helper1(c1); + stat += helper2(c2); + stat += helper3(c3); + stat += helper4(c4); + stat += helper5(c5); + statistics = stat; + } + + static int helper0(int x) { + if (x < 0) return 0; + if (x < 100) return 1; + return 2; + } + + static int helper1(int x) { + if (x < 1) return 0; + if (x < 101) return 1; + return 2; + } + + static int helper2(int x) { + if (x < 2) return 0; + if (x < 102) return 1; + return 2; + } + + static int helper3(int x) { + if (x < 3) return 0; + if (x < 103) return 1; + return 2; + } + + static int helper4(int x) { + if (x < 4) return 0; + if (x < 104) return 1; + return 2; + } + + static int helper5(int x) { + if (x < 5) return 0; + if (x < 105) return 1; + return 2; + } + + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n06_true.yml b/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n06_true.yml new file mode 100644 index 00000000..22de661b --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n06_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - suffix_n06_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n06_true/Main.java b/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n06_true/Main.java new file mode 100644 index 00000000..2c97c3cf --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dead-callees/suffix_n06_true/Main.java @@ -0,0 +1,78 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:0 int:2 int:1001 int:2996 int:0 int:0 int:0 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dead-callees/suffix: 6 helper calls that cannot reach the assert. +public class Main { + static int statistics; + + public static void main(String[] args) { + int mode = Verifier.nondetInt(); + int p = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c0 = Verifier.nondetInt(); + int c1 = Verifier.nondetInt(); + int c2 = Verifier.nondetInt(); + int c3 = Verifier.nondetInt(); + int c4 = Verifier.nondetInt(); + int c5 = Verifier.nondetInt(); + target(a, b); + int stat = 0; + stat += helper0(c0); + stat += helper1(c1); + stat += helper2(c2); + stat += helper3(c3); + stat += helper4(c4); + stat += helper5(c5); + statistics = stat; + } + + static int helper0(int x) { + if (x < 0) return 0; + if (x < 100) return 1; + return 2; + } + + static int helper1(int x) { + if (x < 1) return 0; + if (x < 101) return 1; + return 2; + } + + static int helper2(int x) { + if (x < 2) return 0; + if (x < 102) return 1; + return 2; + } + + static int helper3(int x) { + if (x < 3) return 0; + if (x < 103) return 1; + return 2; + } + + static int helper4(int x) { + if (x < 4) return 0; + if (x < 104) return 1; + return 2; + } + + static int helper5(int x) { + if (x < 5) return 0; + if (x < 105) return 1; + return 2; + } + + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n02_false.yml b/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n02_false.yml new file mode 100644 index 00000000..5b55839d --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n02_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - ifchain_n02_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n02_false/Main.java b/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n02_false/Main.java new file mode 100644 index 00000000..739b81d9 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n02_false/Main.java @@ -0,0 +1,41 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dispatcher/ifchain: cmd picks one of 2 commands, only the last one (cmd == 1) can reach the assert. +public class Main { + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + int r = -1; + if (cmd == 0) r = handler0(a, b, c, d); + else if (cmd == 1) { target(a, b); r = 0; } + return r; + } + + static int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n02_true.yml b/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n02_true.yml new file mode 100644 index 00000000..737be69f --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n02_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - ifchain_n02_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n02_true/Main.java b/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n02_true/Main.java new file mode 100644 index 00000000..745b53dc --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n02_true/Main.java @@ -0,0 +1,42 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:1 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dispatcher/ifchain: cmd picks one of 2 commands, only the last one (cmd == 1) can reach the assert. +public class Main { + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + int r = -1; + if (cmd == 0) r = handler0(a, b, c, d); + else if (cmd == 1) { target(a, b); r = 0; } + return r; + } + + static int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n04_false.yml b/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n04_false.yml new file mode 100644 index 00000000..40df6c35 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n04_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - ifchain_n04_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n04_false/Main.java b/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n04_false/Main.java new file mode 100644 index 00000000..2758898f --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n04_false/Main.java @@ -0,0 +1,59 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:3 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dispatcher/ifchain: cmd picks one of 4 commands, only the last one (cmd == 3) can reach the assert. +public class Main { + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + int r = -1; + if (cmd == 0) r = handler0(a, b, c, d); + else if (cmd == 1) r = handler1(a, b, c, d); + else if (cmd == 2) r = handler2(a, b, c, d); + else if (cmd == 3) { target(a, b); r = 0; } + return r; + } + + static int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + static int handler1(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + static int handler2(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n04_true.yml b/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n04_true.yml new file mode 100644 index 00000000..a3d6cf69 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n04_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - ifchain_n04_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n04_true/Main.java b/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n04_true/Main.java new file mode 100644 index 00000000..5fc2a50f --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n04_true/Main.java @@ -0,0 +1,60 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:3 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dispatcher/ifchain: cmd picks one of 4 commands, only the last one (cmd == 3) can reach the assert. +public class Main { + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + int r = -1; + if (cmd == 0) r = handler0(a, b, c, d); + else if (cmd == 1) r = handler1(a, b, c, d); + else if (cmd == 2) r = handler2(a, b, c, d); + else if (cmd == 3) { target(a, b); r = 0; } + return r; + } + + static int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + static int handler1(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + static int handler2(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n08_false.yml b/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n08_false.yml new file mode 100644 index 00000000..f4fd4f2c --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n08_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - ifchain_n08_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n08_false/Main.java b/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n08_false/Main.java new file mode 100644 index 00000000..e0bc6bd9 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n08_false/Main.java @@ -0,0 +1,95 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:7 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dispatcher/ifchain: cmd picks one of 8 commands, only the last one (cmd == 7) can reach the assert. +public class Main { + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + int r = -1; + if (cmd == 0) r = handler0(a, b, c, d); + else if (cmd == 1) r = handler1(a, b, c, d); + else if (cmd == 2) r = handler2(a, b, c, d); + else if (cmd == 3) r = handler3(a, b, c, d); + else if (cmd == 4) r = handler4(a, b, c, d); + else if (cmd == 5) r = handler5(a, b, c, d); + else if (cmd == 6) r = handler6(a, b, c, d); + else if (cmd == 7) { target(a, b); r = 0; } + return r; + } + + static int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + static int handler1(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + static int handler2(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + static int handler3(int a, int b, int c, int d) { + int r = 3; + if (a > 3) r += 1; + if (b > 6) r += 2; + if (c < -3) r += 4; + if (d == 8) r += 8; + return r; + } + static int handler4(int a, int b, int c, int d) { + int r = 4; + if (a > 4) r += 1; + if (b > 8) r += 2; + if (c < -4) r += 4; + if (d == 9) r += 8; + return r; + } + static int handler5(int a, int b, int c, int d) { + int r = 5; + if (a > 5) r += 1; + if (b > 10) r += 2; + if (c < -5) r += 4; + if (d == 10) r += 8; + return r; + } + static int handler6(int a, int b, int c, int d) { + int r = 6; + if (a > 6) r += 1; + if (b > 12) r += 2; + if (c < -6) r += 4; + if (d == 11) r += 8; + return r; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n08_true.yml b/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n08_true.yml new file mode 100644 index 00000000..b21b231d --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n08_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - ifchain_n08_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n08_true/Main.java b/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n08_true/Main.java new file mode 100644 index 00000000..6529aa23 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n08_true/Main.java @@ -0,0 +1,96 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:7 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dispatcher/ifchain: cmd picks one of 8 commands, only the last one (cmd == 7) can reach the assert. +public class Main { + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + int r = -1; + if (cmd == 0) r = handler0(a, b, c, d); + else if (cmd == 1) r = handler1(a, b, c, d); + else if (cmd == 2) r = handler2(a, b, c, d); + else if (cmd == 3) r = handler3(a, b, c, d); + else if (cmd == 4) r = handler4(a, b, c, d); + else if (cmd == 5) r = handler5(a, b, c, d); + else if (cmd == 6) r = handler6(a, b, c, d); + else if (cmd == 7) { target(a, b); r = 0; } + return r; + } + + static int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + static int handler1(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + static int handler2(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + static int handler3(int a, int b, int c, int d) { + int r = 3; + if (a > 3) r += 1; + if (b > 6) r += 2; + if (c < -3) r += 4; + if (d == 8) r += 8; + return r; + } + static int handler4(int a, int b, int c, int d) { + int r = 4; + if (a > 4) r += 1; + if (b > 8) r += 2; + if (c < -4) r += 4; + if (d == 9) r += 8; + return r; + } + static int handler5(int a, int b, int c, int d) { + int r = 5; + if (a > 5) r += 1; + if (b > 10) r += 2; + if (c < -5) r += 4; + if (d == 10) r += 8; + return r; + } + static int handler6(int a, int b, int c, int d) { + int r = 6; + if (a > 6) r += 1; + if (b > 12) r += 2; + if (c < -6) r += 4; + if (d == 11) r += 8; + return r; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n16_false.yml b/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n16_false.yml new file mode 100644 index 00000000..b2eab20b --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n16_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - ifchain_n16_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n16_false/Main.java b/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n16_false/Main.java new file mode 100644 index 00000000..123e7f89 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n16_false/Main.java @@ -0,0 +1,167 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:15 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dispatcher/ifchain: cmd picks one of 16 commands, only the last one (cmd == 15) can reach the assert. +public class Main { + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + int r = -1; + if (cmd == 0) r = handler0(a, b, c, d); + else if (cmd == 1) r = handler1(a, b, c, d); + else if (cmd == 2) r = handler2(a, b, c, d); + else if (cmd == 3) r = handler3(a, b, c, d); + else if (cmd == 4) r = handler4(a, b, c, d); + else if (cmd == 5) r = handler5(a, b, c, d); + else if (cmd == 6) r = handler6(a, b, c, d); + else if (cmd == 7) r = handler7(a, b, c, d); + else if (cmd == 8) r = handler8(a, b, c, d); + else if (cmd == 9) r = handler9(a, b, c, d); + else if (cmd == 10) r = handler10(a, b, c, d); + else if (cmd == 11) r = handler11(a, b, c, d); + else if (cmd == 12) r = handler12(a, b, c, d); + else if (cmd == 13) r = handler13(a, b, c, d); + else if (cmd == 14) r = handler14(a, b, c, d); + else if (cmd == 15) { target(a, b); r = 0; } + return r; + } + + static int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + static int handler1(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + static int handler2(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + static int handler3(int a, int b, int c, int d) { + int r = 3; + if (a > 3) r += 1; + if (b > 6) r += 2; + if (c < -3) r += 4; + if (d == 8) r += 8; + return r; + } + static int handler4(int a, int b, int c, int d) { + int r = 4; + if (a > 4) r += 1; + if (b > 8) r += 2; + if (c < -4) r += 4; + if (d == 9) r += 8; + return r; + } + static int handler5(int a, int b, int c, int d) { + int r = 5; + if (a > 5) r += 1; + if (b > 10) r += 2; + if (c < -5) r += 4; + if (d == 10) r += 8; + return r; + } + static int handler6(int a, int b, int c, int d) { + int r = 6; + if (a > 6) r += 1; + if (b > 12) r += 2; + if (c < -6) r += 4; + if (d == 11) r += 8; + return r; + } + static int handler7(int a, int b, int c, int d) { + int r = 7; + if (a > 7) r += 1; + if (b > 14) r += 2; + if (c < -7) r += 4; + if (d == 12) r += 8; + return r; + } + static int handler8(int a, int b, int c, int d) { + int r = 8; + if (a > 8) r += 1; + if (b > 16) r += 2; + if (c < -8) r += 4; + if (d == 13) r += 8; + return r; + } + static int handler9(int a, int b, int c, int d) { + int r = 9; + if (a > 9) r += 1; + if (b > 18) r += 2; + if (c < -9) r += 4; + if (d == 14) r += 8; + return r; + } + static int handler10(int a, int b, int c, int d) { + int r = 10; + if (a > 10) r += 1; + if (b > 20) r += 2; + if (c < -10) r += 4; + if (d == 15) r += 8; + return r; + } + static int handler11(int a, int b, int c, int d) { + int r = 11; + if (a > 11) r += 1; + if (b > 22) r += 2; + if (c < -11) r += 4; + if (d == 16) r += 8; + return r; + } + static int handler12(int a, int b, int c, int d) { + int r = 12; + if (a > 12) r += 1; + if (b > 24) r += 2; + if (c < -12) r += 4; + if (d == 17) r += 8; + return r; + } + static int handler13(int a, int b, int c, int d) { + int r = 13; + if (a > 13) r += 1; + if (b > 26) r += 2; + if (c < -13) r += 4; + if (d == 18) r += 8; + return r; + } + static int handler14(int a, int b, int c, int d) { + int r = 14; + if (a > 14) r += 1; + if (b > 28) r += 2; + if (c < -14) r += 4; + if (d == 19) r += 8; + return r; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n16_true.yml b/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n16_true.yml new file mode 100644 index 00000000..919f94f7 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n16_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - ifchain_n16_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n16_true/Main.java b/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n16_true/Main.java new file mode 100644 index 00000000..20cd5fa7 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n16_true/Main.java @@ -0,0 +1,168 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:15 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dispatcher/ifchain: cmd picks one of 16 commands, only the last one (cmd == 15) can reach the assert. +public class Main { + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + int r = -1; + if (cmd == 0) r = handler0(a, b, c, d); + else if (cmd == 1) r = handler1(a, b, c, d); + else if (cmd == 2) r = handler2(a, b, c, d); + else if (cmd == 3) r = handler3(a, b, c, d); + else if (cmd == 4) r = handler4(a, b, c, d); + else if (cmd == 5) r = handler5(a, b, c, d); + else if (cmd == 6) r = handler6(a, b, c, d); + else if (cmd == 7) r = handler7(a, b, c, d); + else if (cmd == 8) r = handler8(a, b, c, d); + else if (cmd == 9) r = handler9(a, b, c, d); + else if (cmd == 10) r = handler10(a, b, c, d); + else if (cmd == 11) r = handler11(a, b, c, d); + else if (cmd == 12) r = handler12(a, b, c, d); + else if (cmd == 13) r = handler13(a, b, c, d); + else if (cmd == 14) r = handler14(a, b, c, d); + else if (cmd == 15) { target(a, b); r = 0; } + return r; + } + + static int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + static int handler1(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + static int handler2(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + static int handler3(int a, int b, int c, int d) { + int r = 3; + if (a > 3) r += 1; + if (b > 6) r += 2; + if (c < -3) r += 4; + if (d == 8) r += 8; + return r; + } + static int handler4(int a, int b, int c, int d) { + int r = 4; + if (a > 4) r += 1; + if (b > 8) r += 2; + if (c < -4) r += 4; + if (d == 9) r += 8; + return r; + } + static int handler5(int a, int b, int c, int d) { + int r = 5; + if (a > 5) r += 1; + if (b > 10) r += 2; + if (c < -5) r += 4; + if (d == 10) r += 8; + return r; + } + static int handler6(int a, int b, int c, int d) { + int r = 6; + if (a > 6) r += 1; + if (b > 12) r += 2; + if (c < -6) r += 4; + if (d == 11) r += 8; + return r; + } + static int handler7(int a, int b, int c, int d) { + int r = 7; + if (a > 7) r += 1; + if (b > 14) r += 2; + if (c < -7) r += 4; + if (d == 12) r += 8; + return r; + } + static int handler8(int a, int b, int c, int d) { + int r = 8; + if (a > 8) r += 1; + if (b > 16) r += 2; + if (c < -8) r += 4; + if (d == 13) r += 8; + return r; + } + static int handler9(int a, int b, int c, int d) { + int r = 9; + if (a > 9) r += 1; + if (b > 18) r += 2; + if (c < -9) r += 4; + if (d == 14) r += 8; + return r; + } + static int handler10(int a, int b, int c, int d) { + int r = 10; + if (a > 10) r += 1; + if (b > 20) r += 2; + if (c < -10) r += 4; + if (d == 15) r += 8; + return r; + } + static int handler11(int a, int b, int c, int d) { + int r = 11; + if (a > 11) r += 1; + if (b > 22) r += 2; + if (c < -11) r += 4; + if (d == 16) r += 8; + return r; + } + static int handler12(int a, int b, int c, int d) { + int r = 12; + if (a > 12) r += 1; + if (b > 24) r += 2; + if (c < -12) r += 4; + if (d == 17) r += 8; + return r; + } + static int handler13(int a, int b, int c, int d) { + int r = 13; + if (a > 13) r += 1; + if (b > 26) r += 2; + if (c < -13) r += 4; + if (d == 18) r += 8; + return r; + } + static int handler14(int a, int b, int c, int d) { + int r = 14; + if (a > 14) r += 1; + if (b > 28) r += 2; + if (c < -14) r += 4; + if (d == 19) r += 8; + return r; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n32_false.yml b/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n32_false.yml new file mode 100644 index 00000000..e6698fc7 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n32_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - ifchain_n32_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n32_false/Main.java b/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n32_false/Main.java new file mode 100644 index 00000000..e85d4a09 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n32_false/Main.java @@ -0,0 +1,311 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:31 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dispatcher/ifchain: cmd picks one of 32 commands, only the last one (cmd == 31) can reach the assert. +public class Main { + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + int r = -1; + if (cmd == 0) r = handler0(a, b, c, d); + else if (cmd == 1) r = handler1(a, b, c, d); + else if (cmd == 2) r = handler2(a, b, c, d); + else if (cmd == 3) r = handler3(a, b, c, d); + else if (cmd == 4) r = handler4(a, b, c, d); + else if (cmd == 5) r = handler5(a, b, c, d); + else if (cmd == 6) r = handler6(a, b, c, d); + else if (cmd == 7) r = handler7(a, b, c, d); + else if (cmd == 8) r = handler8(a, b, c, d); + else if (cmd == 9) r = handler9(a, b, c, d); + else if (cmd == 10) r = handler10(a, b, c, d); + else if (cmd == 11) r = handler11(a, b, c, d); + else if (cmd == 12) r = handler12(a, b, c, d); + else if (cmd == 13) r = handler13(a, b, c, d); + else if (cmd == 14) r = handler14(a, b, c, d); + else if (cmd == 15) r = handler15(a, b, c, d); + else if (cmd == 16) r = handler16(a, b, c, d); + else if (cmd == 17) r = handler17(a, b, c, d); + else if (cmd == 18) r = handler18(a, b, c, d); + else if (cmd == 19) r = handler19(a, b, c, d); + else if (cmd == 20) r = handler20(a, b, c, d); + else if (cmd == 21) r = handler21(a, b, c, d); + else if (cmd == 22) r = handler22(a, b, c, d); + else if (cmd == 23) r = handler23(a, b, c, d); + else if (cmd == 24) r = handler24(a, b, c, d); + else if (cmd == 25) r = handler25(a, b, c, d); + else if (cmd == 26) r = handler26(a, b, c, d); + else if (cmd == 27) r = handler27(a, b, c, d); + else if (cmd == 28) r = handler28(a, b, c, d); + else if (cmd == 29) r = handler29(a, b, c, d); + else if (cmd == 30) r = handler30(a, b, c, d); + else if (cmd == 31) { target(a, b); r = 0; } + return r; + } + + static int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + static int handler1(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + static int handler2(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + static int handler3(int a, int b, int c, int d) { + int r = 3; + if (a > 3) r += 1; + if (b > 6) r += 2; + if (c < -3) r += 4; + if (d == 8) r += 8; + return r; + } + static int handler4(int a, int b, int c, int d) { + int r = 4; + if (a > 4) r += 1; + if (b > 8) r += 2; + if (c < -4) r += 4; + if (d == 9) r += 8; + return r; + } + static int handler5(int a, int b, int c, int d) { + int r = 5; + if (a > 5) r += 1; + if (b > 10) r += 2; + if (c < -5) r += 4; + if (d == 10) r += 8; + return r; + } + static int handler6(int a, int b, int c, int d) { + int r = 6; + if (a > 6) r += 1; + if (b > 12) r += 2; + if (c < -6) r += 4; + if (d == 11) r += 8; + return r; + } + static int handler7(int a, int b, int c, int d) { + int r = 7; + if (a > 7) r += 1; + if (b > 14) r += 2; + if (c < -7) r += 4; + if (d == 12) r += 8; + return r; + } + static int handler8(int a, int b, int c, int d) { + int r = 8; + if (a > 8) r += 1; + if (b > 16) r += 2; + if (c < -8) r += 4; + if (d == 13) r += 8; + return r; + } + static int handler9(int a, int b, int c, int d) { + int r = 9; + if (a > 9) r += 1; + if (b > 18) r += 2; + if (c < -9) r += 4; + if (d == 14) r += 8; + return r; + } + static int handler10(int a, int b, int c, int d) { + int r = 10; + if (a > 10) r += 1; + if (b > 20) r += 2; + if (c < -10) r += 4; + if (d == 15) r += 8; + return r; + } + static int handler11(int a, int b, int c, int d) { + int r = 11; + if (a > 11) r += 1; + if (b > 22) r += 2; + if (c < -11) r += 4; + if (d == 16) r += 8; + return r; + } + static int handler12(int a, int b, int c, int d) { + int r = 12; + if (a > 12) r += 1; + if (b > 24) r += 2; + if (c < -12) r += 4; + if (d == 17) r += 8; + return r; + } + static int handler13(int a, int b, int c, int d) { + int r = 13; + if (a > 13) r += 1; + if (b > 26) r += 2; + if (c < -13) r += 4; + if (d == 18) r += 8; + return r; + } + static int handler14(int a, int b, int c, int d) { + int r = 14; + if (a > 14) r += 1; + if (b > 28) r += 2; + if (c < -14) r += 4; + if (d == 19) r += 8; + return r; + } + static int handler15(int a, int b, int c, int d) { + int r = 15; + if (a > 15) r += 1; + if (b > 30) r += 2; + if (c < -15) r += 4; + if (d == 20) r += 8; + return r; + } + static int handler16(int a, int b, int c, int d) { + int r = 16; + if (a > 16) r += 1; + if (b > 32) r += 2; + if (c < -16) r += 4; + if (d == 21) r += 8; + return r; + } + static int handler17(int a, int b, int c, int d) { + int r = 17; + if (a > 17) r += 1; + if (b > 34) r += 2; + if (c < -17) r += 4; + if (d == 22) r += 8; + return r; + } + static int handler18(int a, int b, int c, int d) { + int r = 18; + if (a > 18) r += 1; + if (b > 36) r += 2; + if (c < -18) r += 4; + if (d == 23) r += 8; + return r; + } + static int handler19(int a, int b, int c, int d) { + int r = 19; + if (a > 19) r += 1; + if (b > 38) r += 2; + if (c < -19) r += 4; + if (d == 24) r += 8; + return r; + } + static int handler20(int a, int b, int c, int d) { + int r = 20; + if (a > 20) r += 1; + if (b > 40) r += 2; + if (c < -20) r += 4; + if (d == 25) r += 8; + return r; + } + static int handler21(int a, int b, int c, int d) { + int r = 21; + if (a > 21) r += 1; + if (b > 42) r += 2; + if (c < -21) r += 4; + if (d == 26) r += 8; + return r; + } + static int handler22(int a, int b, int c, int d) { + int r = 22; + if (a > 22) r += 1; + if (b > 44) r += 2; + if (c < -22) r += 4; + if (d == 27) r += 8; + return r; + } + static int handler23(int a, int b, int c, int d) { + int r = 23; + if (a > 23) r += 1; + if (b > 46) r += 2; + if (c < -23) r += 4; + if (d == 28) r += 8; + return r; + } + static int handler24(int a, int b, int c, int d) { + int r = 24; + if (a > 24) r += 1; + if (b > 48) r += 2; + if (c < -24) r += 4; + if (d == 29) r += 8; + return r; + } + static int handler25(int a, int b, int c, int d) { + int r = 25; + if (a > 25) r += 1; + if (b > 50) r += 2; + if (c < -25) r += 4; + if (d == 30) r += 8; + return r; + } + static int handler26(int a, int b, int c, int d) { + int r = 26; + if (a > 26) r += 1; + if (b > 52) r += 2; + if (c < -26) r += 4; + if (d == 31) r += 8; + return r; + } + static int handler27(int a, int b, int c, int d) { + int r = 27; + if (a > 27) r += 1; + if (b > 54) r += 2; + if (c < -27) r += 4; + if (d == 32) r += 8; + return r; + } + static int handler28(int a, int b, int c, int d) { + int r = 28; + if (a > 28) r += 1; + if (b > 56) r += 2; + if (c < -28) r += 4; + if (d == 33) r += 8; + return r; + } + static int handler29(int a, int b, int c, int d) { + int r = 29; + if (a > 29) r += 1; + if (b > 58) r += 2; + if (c < -29) r += 4; + if (d == 34) r += 8; + return r; + } + static int handler30(int a, int b, int c, int d) { + int r = 30; + if (a > 30) r += 1; + if (b > 60) r += 2; + if (c < -30) r += 4; + if (d == 35) r += 8; + return r; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n32_true.yml b/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n32_true.yml new file mode 100644 index 00000000..e24c51aa --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n32_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - ifchain_n32_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n32_true/Main.java b/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n32_true/Main.java new file mode 100644 index 00000000..2cdbc563 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/ifchain_n32_true/Main.java @@ -0,0 +1,312 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:31 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dispatcher/ifchain: cmd picks one of 32 commands, only the last one (cmd == 31) can reach the assert. +public class Main { + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + int r = -1; + if (cmd == 0) r = handler0(a, b, c, d); + else if (cmd == 1) r = handler1(a, b, c, d); + else if (cmd == 2) r = handler2(a, b, c, d); + else if (cmd == 3) r = handler3(a, b, c, d); + else if (cmd == 4) r = handler4(a, b, c, d); + else if (cmd == 5) r = handler5(a, b, c, d); + else if (cmd == 6) r = handler6(a, b, c, d); + else if (cmd == 7) r = handler7(a, b, c, d); + else if (cmd == 8) r = handler8(a, b, c, d); + else if (cmd == 9) r = handler9(a, b, c, d); + else if (cmd == 10) r = handler10(a, b, c, d); + else if (cmd == 11) r = handler11(a, b, c, d); + else if (cmd == 12) r = handler12(a, b, c, d); + else if (cmd == 13) r = handler13(a, b, c, d); + else if (cmd == 14) r = handler14(a, b, c, d); + else if (cmd == 15) r = handler15(a, b, c, d); + else if (cmd == 16) r = handler16(a, b, c, d); + else if (cmd == 17) r = handler17(a, b, c, d); + else if (cmd == 18) r = handler18(a, b, c, d); + else if (cmd == 19) r = handler19(a, b, c, d); + else if (cmd == 20) r = handler20(a, b, c, d); + else if (cmd == 21) r = handler21(a, b, c, d); + else if (cmd == 22) r = handler22(a, b, c, d); + else if (cmd == 23) r = handler23(a, b, c, d); + else if (cmd == 24) r = handler24(a, b, c, d); + else if (cmd == 25) r = handler25(a, b, c, d); + else if (cmd == 26) r = handler26(a, b, c, d); + else if (cmd == 27) r = handler27(a, b, c, d); + else if (cmd == 28) r = handler28(a, b, c, d); + else if (cmd == 29) r = handler29(a, b, c, d); + else if (cmd == 30) r = handler30(a, b, c, d); + else if (cmd == 31) { target(a, b); r = 0; } + return r; + } + + static int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + static int handler1(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + static int handler2(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + static int handler3(int a, int b, int c, int d) { + int r = 3; + if (a > 3) r += 1; + if (b > 6) r += 2; + if (c < -3) r += 4; + if (d == 8) r += 8; + return r; + } + static int handler4(int a, int b, int c, int d) { + int r = 4; + if (a > 4) r += 1; + if (b > 8) r += 2; + if (c < -4) r += 4; + if (d == 9) r += 8; + return r; + } + static int handler5(int a, int b, int c, int d) { + int r = 5; + if (a > 5) r += 1; + if (b > 10) r += 2; + if (c < -5) r += 4; + if (d == 10) r += 8; + return r; + } + static int handler6(int a, int b, int c, int d) { + int r = 6; + if (a > 6) r += 1; + if (b > 12) r += 2; + if (c < -6) r += 4; + if (d == 11) r += 8; + return r; + } + static int handler7(int a, int b, int c, int d) { + int r = 7; + if (a > 7) r += 1; + if (b > 14) r += 2; + if (c < -7) r += 4; + if (d == 12) r += 8; + return r; + } + static int handler8(int a, int b, int c, int d) { + int r = 8; + if (a > 8) r += 1; + if (b > 16) r += 2; + if (c < -8) r += 4; + if (d == 13) r += 8; + return r; + } + static int handler9(int a, int b, int c, int d) { + int r = 9; + if (a > 9) r += 1; + if (b > 18) r += 2; + if (c < -9) r += 4; + if (d == 14) r += 8; + return r; + } + static int handler10(int a, int b, int c, int d) { + int r = 10; + if (a > 10) r += 1; + if (b > 20) r += 2; + if (c < -10) r += 4; + if (d == 15) r += 8; + return r; + } + static int handler11(int a, int b, int c, int d) { + int r = 11; + if (a > 11) r += 1; + if (b > 22) r += 2; + if (c < -11) r += 4; + if (d == 16) r += 8; + return r; + } + static int handler12(int a, int b, int c, int d) { + int r = 12; + if (a > 12) r += 1; + if (b > 24) r += 2; + if (c < -12) r += 4; + if (d == 17) r += 8; + return r; + } + static int handler13(int a, int b, int c, int d) { + int r = 13; + if (a > 13) r += 1; + if (b > 26) r += 2; + if (c < -13) r += 4; + if (d == 18) r += 8; + return r; + } + static int handler14(int a, int b, int c, int d) { + int r = 14; + if (a > 14) r += 1; + if (b > 28) r += 2; + if (c < -14) r += 4; + if (d == 19) r += 8; + return r; + } + static int handler15(int a, int b, int c, int d) { + int r = 15; + if (a > 15) r += 1; + if (b > 30) r += 2; + if (c < -15) r += 4; + if (d == 20) r += 8; + return r; + } + static int handler16(int a, int b, int c, int d) { + int r = 16; + if (a > 16) r += 1; + if (b > 32) r += 2; + if (c < -16) r += 4; + if (d == 21) r += 8; + return r; + } + static int handler17(int a, int b, int c, int d) { + int r = 17; + if (a > 17) r += 1; + if (b > 34) r += 2; + if (c < -17) r += 4; + if (d == 22) r += 8; + return r; + } + static int handler18(int a, int b, int c, int d) { + int r = 18; + if (a > 18) r += 1; + if (b > 36) r += 2; + if (c < -18) r += 4; + if (d == 23) r += 8; + return r; + } + static int handler19(int a, int b, int c, int d) { + int r = 19; + if (a > 19) r += 1; + if (b > 38) r += 2; + if (c < -19) r += 4; + if (d == 24) r += 8; + return r; + } + static int handler20(int a, int b, int c, int d) { + int r = 20; + if (a > 20) r += 1; + if (b > 40) r += 2; + if (c < -20) r += 4; + if (d == 25) r += 8; + return r; + } + static int handler21(int a, int b, int c, int d) { + int r = 21; + if (a > 21) r += 1; + if (b > 42) r += 2; + if (c < -21) r += 4; + if (d == 26) r += 8; + return r; + } + static int handler22(int a, int b, int c, int d) { + int r = 22; + if (a > 22) r += 1; + if (b > 44) r += 2; + if (c < -22) r += 4; + if (d == 27) r += 8; + return r; + } + static int handler23(int a, int b, int c, int d) { + int r = 23; + if (a > 23) r += 1; + if (b > 46) r += 2; + if (c < -23) r += 4; + if (d == 28) r += 8; + return r; + } + static int handler24(int a, int b, int c, int d) { + int r = 24; + if (a > 24) r += 1; + if (b > 48) r += 2; + if (c < -24) r += 4; + if (d == 29) r += 8; + return r; + } + static int handler25(int a, int b, int c, int d) { + int r = 25; + if (a > 25) r += 1; + if (b > 50) r += 2; + if (c < -25) r += 4; + if (d == 30) r += 8; + return r; + } + static int handler26(int a, int b, int c, int d) { + int r = 26; + if (a > 26) r += 1; + if (b > 52) r += 2; + if (c < -26) r += 4; + if (d == 31) r += 8; + return r; + } + static int handler27(int a, int b, int c, int d) { + int r = 27; + if (a > 27) r += 1; + if (b > 54) r += 2; + if (c < -27) r += 4; + if (d == 32) r += 8; + return r; + } + static int handler28(int a, int b, int c, int d) { + int r = 28; + if (a > 28) r += 1; + if (b > 56) r += 2; + if (c < -28) r += 4; + if (d == 33) r += 8; + return r; + } + static int handler29(int a, int b, int c, int d) { + int r = 29; + if (a > 29) r += 1; + if (b > 58) r += 2; + if (c < -29) r += 4; + if (d == 34) r += 8; + return r; + } + static int handler30(int a, int b, int c, int d) { + int r = 30; + if (a > 30) r += 1; + if (b > 60) r += 2; + if (c < -30) r += 4; + if (d == 35) r += 8; + return r; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n02_false.yml b/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n02_false.yml new file mode 100644 index 00000000..e536dd5f --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n02_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - instance_n02_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n02_false/Main.java b/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n02_false/Main.java new file mode 100644 index 00000000..87f7a750 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n02_false/Main.java @@ -0,0 +1,47 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dispatcher/instance: cmd picks one of 2 commands, only the last one (cmd == 1) can reach the assert. +public class Main { + int calls; + + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + Main m = new Main(); + m.calls = dispatch(m, cmd, a, b, c, d); + } + + static int dispatch(Main m, int cmd, int a, int b, int c, int d) { + int r; + switch (cmd) { + case 0: r = m.handler0(a, b, c, d); break; + case 1: m.target(a, b); r = 0; break; + default: r = -1; + } + return r; + } + + int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + + // The only assertion in the program. + void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n02_true.yml b/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n02_true.yml new file mode 100644 index 00000000..1d280350 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n02_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - instance_n02_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n02_true/Main.java b/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n02_true/Main.java new file mode 100644 index 00000000..02d6531b --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n02_true/Main.java @@ -0,0 +1,48 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:1 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dispatcher/instance: cmd picks one of 2 commands, only the last one (cmd == 1) can reach the assert. +public class Main { + int calls; + + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + Main m = new Main(); + m.calls = dispatch(m, cmd, a, b, c, d); + } + + static int dispatch(Main m, int cmd, int a, int b, int c, int d) { + int r; + switch (cmd) { + case 0: r = m.handler0(a, b, c, d); break; + case 1: m.target(a, b); r = 0; break; + default: r = -1; + } + return r; + } + + int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + + // The only assertion in the program. + void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n04_false.yml b/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n04_false.yml new file mode 100644 index 00000000..d0fdaaf8 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n04_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - instance_n04_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n04_false/Main.java b/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n04_false/Main.java new file mode 100644 index 00000000..5ec6320d --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n04_false/Main.java @@ -0,0 +1,65 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:3 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dispatcher/instance: cmd picks one of 4 commands, only the last one (cmd == 3) can reach the assert. +public class Main { + int calls; + + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + Main m = new Main(); + m.calls = dispatch(m, cmd, a, b, c, d); + } + + static int dispatch(Main m, int cmd, int a, int b, int c, int d) { + int r; + switch (cmd) { + case 0: r = m.handler0(a, b, c, d); break; + case 1: r = m.handler1(a, b, c, d); break; + case 2: r = m.handler2(a, b, c, d); break; + case 3: m.target(a, b); r = 0; break; + default: r = -1; + } + return r; + } + + int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + int handler1(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + int handler2(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + + // The only assertion in the program. + void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n04_true.yml b/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n04_true.yml new file mode 100644 index 00000000..8efc8db3 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n04_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - instance_n04_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n04_true/Main.java b/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n04_true/Main.java new file mode 100644 index 00000000..a2d9ce72 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n04_true/Main.java @@ -0,0 +1,66 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:3 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dispatcher/instance: cmd picks one of 4 commands, only the last one (cmd == 3) can reach the assert. +public class Main { + int calls; + + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + Main m = new Main(); + m.calls = dispatch(m, cmd, a, b, c, d); + } + + static int dispatch(Main m, int cmd, int a, int b, int c, int d) { + int r; + switch (cmd) { + case 0: r = m.handler0(a, b, c, d); break; + case 1: r = m.handler1(a, b, c, d); break; + case 2: r = m.handler2(a, b, c, d); break; + case 3: m.target(a, b); r = 0; break; + default: r = -1; + } + return r; + } + + int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + int handler1(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + int handler2(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + + // The only assertion in the program. + void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n08_false.yml b/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n08_false.yml new file mode 100644 index 00000000..a95faffa --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n08_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - instance_n08_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n08_false/Main.java b/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n08_false/Main.java new file mode 100644 index 00000000..d6c43941 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n08_false/Main.java @@ -0,0 +1,101 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:7 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dispatcher/instance: cmd picks one of 8 commands, only the last one (cmd == 7) can reach the assert. +public class Main { + int calls; + + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + Main m = new Main(); + m.calls = dispatch(m, cmd, a, b, c, d); + } + + static int dispatch(Main m, int cmd, int a, int b, int c, int d) { + int r; + switch (cmd) { + case 0: r = m.handler0(a, b, c, d); break; + case 1: r = m.handler1(a, b, c, d); break; + case 2: r = m.handler2(a, b, c, d); break; + case 3: r = m.handler3(a, b, c, d); break; + case 4: r = m.handler4(a, b, c, d); break; + case 5: r = m.handler5(a, b, c, d); break; + case 6: r = m.handler6(a, b, c, d); break; + case 7: m.target(a, b); r = 0; break; + default: r = -1; + } + return r; + } + + int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + int handler1(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + int handler2(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + int handler3(int a, int b, int c, int d) { + int r = 3; + if (a > 3) r += 1; + if (b > 6) r += 2; + if (c < -3) r += 4; + if (d == 8) r += 8; + return r; + } + int handler4(int a, int b, int c, int d) { + int r = 4; + if (a > 4) r += 1; + if (b > 8) r += 2; + if (c < -4) r += 4; + if (d == 9) r += 8; + return r; + } + int handler5(int a, int b, int c, int d) { + int r = 5; + if (a > 5) r += 1; + if (b > 10) r += 2; + if (c < -5) r += 4; + if (d == 10) r += 8; + return r; + } + int handler6(int a, int b, int c, int d) { + int r = 6; + if (a > 6) r += 1; + if (b > 12) r += 2; + if (c < -6) r += 4; + if (d == 11) r += 8; + return r; + } + + // The only assertion in the program. + void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n08_true.yml b/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n08_true.yml new file mode 100644 index 00000000..9ca79d0f --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n08_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - instance_n08_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n08_true/Main.java b/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n08_true/Main.java new file mode 100644 index 00000000..ff013d7d --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n08_true/Main.java @@ -0,0 +1,102 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:7 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dispatcher/instance: cmd picks one of 8 commands, only the last one (cmd == 7) can reach the assert. +public class Main { + int calls; + + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + Main m = new Main(); + m.calls = dispatch(m, cmd, a, b, c, d); + } + + static int dispatch(Main m, int cmd, int a, int b, int c, int d) { + int r; + switch (cmd) { + case 0: r = m.handler0(a, b, c, d); break; + case 1: r = m.handler1(a, b, c, d); break; + case 2: r = m.handler2(a, b, c, d); break; + case 3: r = m.handler3(a, b, c, d); break; + case 4: r = m.handler4(a, b, c, d); break; + case 5: r = m.handler5(a, b, c, d); break; + case 6: r = m.handler6(a, b, c, d); break; + case 7: m.target(a, b); r = 0; break; + default: r = -1; + } + return r; + } + + int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + int handler1(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + int handler2(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + int handler3(int a, int b, int c, int d) { + int r = 3; + if (a > 3) r += 1; + if (b > 6) r += 2; + if (c < -3) r += 4; + if (d == 8) r += 8; + return r; + } + int handler4(int a, int b, int c, int d) { + int r = 4; + if (a > 4) r += 1; + if (b > 8) r += 2; + if (c < -4) r += 4; + if (d == 9) r += 8; + return r; + } + int handler5(int a, int b, int c, int d) { + int r = 5; + if (a > 5) r += 1; + if (b > 10) r += 2; + if (c < -5) r += 4; + if (d == 10) r += 8; + return r; + } + int handler6(int a, int b, int c, int d) { + int r = 6; + if (a > 6) r += 1; + if (b > 12) r += 2; + if (c < -6) r += 4; + if (d == 11) r += 8; + return r; + } + + // The only assertion in the program. + void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n16_false.yml b/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n16_false.yml new file mode 100644 index 00000000..5aa2a056 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n16_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - instance_n16_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n16_false/Main.java b/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n16_false/Main.java new file mode 100644 index 00000000..b39db803 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n16_false/Main.java @@ -0,0 +1,173 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:15 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dispatcher/instance: cmd picks one of 16 commands, only the last one (cmd == 15) can reach the assert. +public class Main { + int calls; + + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + Main m = new Main(); + m.calls = dispatch(m, cmd, a, b, c, d); + } + + static int dispatch(Main m, int cmd, int a, int b, int c, int d) { + int r; + switch (cmd) { + case 0: r = m.handler0(a, b, c, d); break; + case 1: r = m.handler1(a, b, c, d); break; + case 2: r = m.handler2(a, b, c, d); break; + case 3: r = m.handler3(a, b, c, d); break; + case 4: r = m.handler4(a, b, c, d); break; + case 5: r = m.handler5(a, b, c, d); break; + case 6: r = m.handler6(a, b, c, d); break; + case 7: r = m.handler7(a, b, c, d); break; + case 8: r = m.handler8(a, b, c, d); break; + case 9: r = m.handler9(a, b, c, d); break; + case 10: r = m.handler10(a, b, c, d); break; + case 11: r = m.handler11(a, b, c, d); break; + case 12: r = m.handler12(a, b, c, d); break; + case 13: r = m.handler13(a, b, c, d); break; + case 14: r = m.handler14(a, b, c, d); break; + case 15: m.target(a, b); r = 0; break; + default: r = -1; + } + return r; + } + + int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + int handler1(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + int handler2(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + int handler3(int a, int b, int c, int d) { + int r = 3; + if (a > 3) r += 1; + if (b > 6) r += 2; + if (c < -3) r += 4; + if (d == 8) r += 8; + return r; + } + int handler4(int a, int b, int c, int d) { + int r = 4; + if (a > 4) r += 1; + if (b > 8) r += 2; + if (c < -4) r += 4; + if (d == 9) r += 8; + return r; + } + int handler5(int a, int b, int c, int d) { + int r = 5; + if (a > 5) r += 1; + if (b > 10) r += 2; + if (c < -5) r += 4; + if (d == 10) r += 8; + return r; + } + int handler6(int a, int b, int c, int d) { + int r = 6; + if (a > 6) r += 1; + if (b > 12) r += 2; + if (c < -6) r += 4; + if (d == 11) r += 8; + return r; + } + int handler7(int a, int b, int c, int d) { + int r = 7; + if (a > 7) r += 1; + if (b > 14) r += 2; + if (c < -7) r += 4; + if (d == 12) r += 8; + return r; + } + int handler8(int a, int b, int c, int d) { + int r = 8; + if (a > 8) r += 1; + if (b > 16) r += 2; + if (c < -8) r += 4; + if (d == 13) r += 8; + return r; + } + int handler9(int a, int b, int c, int d) { + int r = 9; + if (a > 9) r += 1; + if (b > 18) r += 2; + if (c < -9) r += 4; + if (d == 14) r += 8; + return r; + } + int handler10(int a, int b, int c, int d) { + int r = 10; + if (a > 10) r += 1; + if (b > 20) r += 2; + if (c < -10) r += 4; + if (d == 15) r += 8; + return r; + } + int handler11(int a, int b, int c, int d) { + int r = 11; + if (a > 11) r += 1; + if (b > 22) r += 2; + if (c < -11) r += 4; + if (d == 16) r += 8; + return r; + } + int handler12(int a, int b, int c, int d) { + int r = 12; + if (a > 12) r += 1; + if (b > 24) r += 2; + if (c < -12) r += 4; + if (d == 17) r += 8; + return r; + } + int handler13(int a, int b, int c, int d) { + int r = 13; + if (a > 13) r += 1; + if (b > 26) r += 2; + if (c < -13) r += 4; + if (d == 18) r += 8; + return r; + } + int handler14(int a, int b, int c, int d) { + int r = 14; + if (a > 14) r += 1; + if (b > 28) r += 2; + if (c < -14) r += 4; + if (d == 19) r += 8; + return r; + } + + // The only assertion in the program. + void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n16_true.yml b/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n16_true.yml new file mode 100644 index 00000000..ec40c11d --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n16_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - instance_n16_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n16_true/Main.java b/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n16_true/Main.java new file mode 100644 index 00000000..47bf2907 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n16_true/Main.java @@ -0,0 +1,174 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:15 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dispatcher/instance: cmd picks one of 16 commands, only the last one (cmd == 15) can reach the assert. +public class Main { + int calls; + + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + Main m = new Main(); + m.calls = dispatch(m, cmd, a, b, c, d); + } + + static int dispatch(Main m, int cmd, int a, int b, int c, int d) { + int r; + switch (cmd) { + case 0: r = m.handler0(a, b, c, d); break; + case 1: r = m.handler1(a, b, c, d); break; + case 2: r = m.handler2(a, b, c, d); break; + case 3: r = m.handler3(a, b, c, d); break; + case 4: r = m.handler4(a, b, c, d); break; + case 5: r = m.handler5(a, b, c, d); break; + case 6: r = m.handler6(a, b, c, d); break; + case 7: r = m.handler7(a, b, c, d); break; + case 8: r = m.handler8(a, b, c, d); break; + case 9: r = m.handler9(a, b, c, d); break; + case 10: r = m.handler10(a, b, c, d); break; + case 11: r = m.handler11(a, b, c, d); break; + case 12: r = m.handler12(a, b, c, d); break; + case 13: r = m.handler13(a, b, c, d); break; + case 14: r = m.handler14(a, b, c, d); break; + case 15: m.target(a, b); r = 0; break; + default: r = -1; + } + return r; + } + + int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + int handler1(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + int handler2(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + int handler3(int a, int b, int c, int d) { + int r = 3; + if (a > 3) r += 1; + if (b > 6) r += 2; + if (c < -3) r += 4; + if (d == 8) r += 8; + return r; + } + int handler4(int a, int b, int c, int d) { + int r = 4; + if (a > 4) r += 1; + if (b > 8) r += 2; + if (c < -4) r += 4; + if (d == 9) r += 8; + return r; + } + int handler5(int a, int b, int c, int d) { + int r = 5; + if (a > 5) r += 1; + if (b > 10) r += 2; + if (c < -5) r += 4; + if (d == 10) r += 8; + return r; + } + int handler6(int a, int b, int c, int d) { + int r = 6; + if (a > 6) r += 1; + if (b > 12) r += 2; + if (c < -6) r += 4; + if (d == 11) r += 8; + return r; + } + int handler7(int a, int b, int c, int d) { + int r = 7; + if (a > 7) r += 1; + if (b > 14) r += 2; + if (c < -7) r += 4; + if (d == 12) r += 8; + return r; + } + int handler8(int a, int b, int c, int d) { + int r = 8; + if (a > 8) r += 1; + if (b > 16) r += 2; + if (c < -8) r += 4; + if (d == 13) r += 8; + return r; + } + int handler9(int a, int b, int c, int d) { + int r = 9; + if (a > 9) r += 1; + if (b > 18) r += 2; + if (c < -9) r += 4; + if (d == 14) r += 8; + return r; + } + int handler10(int a, int b, int c, int d) { + int r = 10; + if (a > 10) r += 1; + if (b > 20) r += 2; + if (c < -10) r += 4; + if (d == 15) r += 8; + return r; + } + int handler11(int a, int b, int c, int d) { + int r = 11; + if (a > 11) r += 1; + if (b > 22) r += 2; + if (c < -11) r += 4; + if (d == 16) r += 8; + return r; + } + int handler12(int a, int b, int c, int d) { + int r = 12; + if (a > 12) r += 1; + if (b > 24) r += 2; + if (c < -12) r += 4; + if (d == 17) r += 8; + return r; + } + int handler13(int a, int b, int c, int d) { + int r = 13; + if (a > 13) r += 1; + if (b > 26) r += 2; + if (c < -13) r += 4; + if (d == 18) r += 8; + return r; + } + int handler14(int a, int b, int c, int d) { + int r = 14; + if (a > 14) r += 1; + if (b > 28) r += 2; + if (c < -14) r += 4; + if (d == 19) r += 8; + return r; + } + + // The only assertion in the program. + void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n32_false.yml b/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n32_false.yml new file mode 100644 index 00000000..f0b28706 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n32_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - instance_n32_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n32_false/Main.java b/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n32_false/Main.java new file mode 100644 index 00000000..ba0a508f --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n32_false/Main.java @@ -0,0 +1,317 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:31 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dispatcher/instance: cmd picks one of 32 commands, only the last one (cmd == 31) can reach the assert. +public class Main { + int calls; + + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + Main m = new Main(); + m.calls = dispatch(m, cmd, a, b, c, d); + } + + static int dispatch(Main m, int cmd, int a, int b, int c, int d) { + int r; + switch (cmd) { + case 0: r = m.handler0(a, b, c, d); break; + case 1: r = m.handler1(a, b, c, d); break; + case 2: r = m.handler2(a, b, c, d); break; + case 3: r = m.handler3(a, b, c, d); break; + case 4: r = m.handler4(a, b, c, d); break; + case 5: r = m.handler5(a, b, c, d); break; + case 6: r = m.handler6(a, b, c, d); break; + case 7: r = m.handler7(a, b, c, d); break; + case 8: r = m.handler8(a, b, c, d); break; + case 9: r = m.handler9(a, b, c, d); break; + case 10: r = m.handler10(a, b, c, d); break; + case 11: r = m.handler11(a, b, c, d); break; + case 12: r = m.handler12(a, b, c, d); break; + case 13: r = m.handler13(a, b, c, d); break; + case 14: r = m.handler14(a, b, c, d); break; + case 15: r = m.handler15(a, b, c, d); break; + case 16: r = m.handler16(a, b, c, d); break; + case 17: r = m.handler17(a, b, c, d); break; + case 18: r = m.handler18(a, b, c, d); break; + case 19: r = m.handler19(a, b, c, d); break; + case 20: r = m.handler20(a, b, c, d); break; + case 21: r = m.handler21(a, b, c, d); break; + case 22: r = m.handler22(a, b, c, d); break; + case 23: r = m.handler23(a, b, c, d); break; + case 24: r = m.handler24(a, b, c, d); break; + case 25: r = m.handler25(a, b, c, d); break; + case 26: r = m.handler26(a, b, c, d); break; + case 27: r = m.handler27(a, b, c, d); break; + case 28: r = m.handler28(a, b, c, d); break; + case 29: r = m.handler29(a, b, c, d); break; + case 30: r = m.handler30(a, b, c, d); break; + case 31: m.target(a, b); r = 0; break; + default: r = -1; + } + return r; + } + + int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + int handler1(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + int handler2(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + int handler3(int a, int b, int c, int d) { + int r = 3; + if (a > 3) r += 1; + if (b > 6) r += 2; + if (c < -3) r += 4; + if (d == 8) r += 8; + return r; + } + int handler4(int a, int b, int c, int d) { + int r = 4; + if (a > 4) r += 1; + if (b > 8) r += 2; + if (c < -4) r += 4; + if (d == 9) r += 8; + return r; + } + int handler5(int a, int b, int c, int d) { + int r = 5; + if (a > 5) r += 1; + if (b > 10) r += 2; + if (c < -5) r += 4; + if (d == 10) r += 8; + return r; + } + int handler6(int a, int b, int c, int d) { + int r = 6; + if (a > 6) r += 1; + if (b > 12) r += 2; + if (c < -6) r += 4; + if (d == 11) r += 8; + return r; + } + int handler7(int a, int b, int c, int d) { + int r = 7; + if (a > 7) r += 1; + if (b > 14) r += 2; + if (c < -7) r += 4; + if (d == 12) r += 8; + return r; + } + int handler8(int a, int b, int c, int d) { + int r = 8; + if (a > 8) r += 1; + if (b > 16) r += 2; + if (c < -8) r += 4; + if (d == 13) r += 8; + return r; + } + int handler9(int a, int b, int c, int d) { + int r = 9; + if (a > 9) r += 1; + if (b > 18) r += 2; + if (c < -9) r += 4; + if (d == 14) r += 8; + return r; + } + int handler10(int a, int b, int c, int d) { + int r = 10; + if (a > 10) r += 1; + if (b > 20) r += 2; + if (c < -10) r += 4; + if (d == 15) r += 8; + return r; + } + int handler11(int a, int b, int c, int d) { + int r = 11; + if (a > 11) r += 1; + if (b > 22) r += 2; + if (c < -11) r += 4; + if (d == 16) r += 8; + return r; + } + int handler12(int a, int b, int c, int d) { + int r = 12; + if (a > 12) r += 1; + if (b > 24) r += 2; + if (c < -12) r += 4; + if (d == 17) r += 8; + return r; + } + int handler13(int a, int b, int c, int d) { + int r = 13; + if (a > 13) r += 1; + if (b > 26) r += 2; + if (c < -13) r += 4; + if (d == 18) r += 8; + return r; + } + int handler14(int a, int b, int c, int d) { + int r = 14; + if (a > 14) r += 1; + if (b > 28) r += 2; + if (c < -14) r += 4; + if (d == 19) r += 8; + return r; + } + int handler15(int a, int b, int c, int d) { + int r = 15; + if (a > 15) r += 1; + if (b > 30) r += 2; + if (c < -15) r += 4; + if (d == 20) r += 8; + return r; + } + int handler16(int a, int b, int c, int d) { + int r = 16; + if (a > 16) r += 1; + if (b > 32) r += 2; + if (c < -16) r += 4; + if (d == 21) r += 8; + return r; + } + int handler17(int a, int b, int c, int d) { + int r = 17; + if (a > 17) r += 1; + if (b > 34) r += 2; + if (c < -17) r += 4; + if (d == 22) r += 8; + return r; + } + int handler18(int a, int b, int c, int d) { + int r = 18; + if (a > 18) r += 1; + if (b > 36) r += 2; + if (c < -18) r += 4; + if (d == 23) r += 8; + return r; + } + int handler19(int a, int b, int c, int d) { + int r = 19; + if (a > 19) r += 1; + if (b > 38) r += 2; + if (c < -19) r += 4; + if (d == 24) r += 8; + return r; + } + int handler20(int a, int b, int c, int d) { + int r = 20; + if (a > 20) r += 1; + if (b > 40) r += 2; + if (c < -20) r += 4; + if (d == 25) r += 8; + return r; + } + int handler21(int a, int b, int c, int d) { + int r = 21; + if (a > 21) r += 1; + if (b > 42) r += 2; + if (c < -21) r += 4; + if (d == 26) r += 8; + return r; + } + int handler22(int a, int b, int c, int d) { + int r = 22; + if (a > 22) r += 1; + if (b > 44) r += 2; + if (c < -22) r += 4; + if (d == 27) r += 8; + return r; + } + int handler23(int a, int b, int c, int d) { + int r = 23; + if (a > 23) r += 1; + if (b > 46) r += 2; + if (c < -23) r += 4; + if (d == 28) r += 8; + return r; + } + int handler24(int a, int b, int c, int d) { + int r = 24; + if (a > 24) r += 1; + if (b > 48) r += 2; + if (c < -24) r += 4; + if (d == 29) r += 8; + return r; + } + int handler25(int a, int b, int c, int d) { + int r = 25; + if (a > 25) r += 1; + if (b > 50) r += 2; + if (c < -25) r += 4; + if (d == 30) r += 8; + return r; + } + int handler26(int a, int b, int c, int d) { + int r = 26; + if (a > 26) r += 1; + if (b > 52) r += 2; + if (c < -26) r += 4; + if (d == 31) r += 8; + return r; + } + int handler27(int a, int b, int c, int d) { + int r = 27; + if (a > 27) r += 1; + if (b > 54) r += 2; + if (c < -27) r += 4; + if (d == 32) r += 8; + return r; + } + int handler28(int a, int b, int c, int d) { + int r = 28; + if (a > 28) r += 1; + if (b > 56) r += 2; + if (c < -28) r += 4; + if (d == 33) r += 8; + return r; + } + int handler29(int a, int b, int c, int d) { + int r = 29; + if (a > 29) r += 1; + if (b > 58) r += 2; + if (c < -29) r += 4; + if (d == 34) r += 8; + return r; + } + int handler30(int a, int b, int c, int d) { + int r = 30; + if (a > 30) r += 1; + if (b > 60) r += 2; + if (c < -30) r += 4; + if (d == 35) r += 8; + return r; + } + + // The only assertion in the program. + void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n32_true.yml b/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n32_true.yml new file mode 100644 index 00000000..038eda4c --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n32_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - instance_n32_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n32_true/Main.java b/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n32_true/Main.java new file mode 100644 index 00000000..163dd278 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/instance_n32_true/Main.java @@ -0,0 +1,318 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:31 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dispatcher/instance: cmd picks one of 32 commands, only the last one (cmd == 31) can reach the assert. +public class Main { + int calls; + + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + Main m = new Main(); + m.calls = dispatch(m, cmd, a, b, c, d); + } + + static int dispatch(Main m, int cmd, int a, int b, int c, int d) { + int r; + switch (cmd) { + case 0: r = m.handler0(a, b, c, d); break; + case 1: r = m.handler1(a, b, c, d); break; + case 2: r = m.handler2(a, b, c, d); break; + case 3: r = m.handler3(a, b, c, d); break; + case 4: r = m.handler4(a, b, c, d); break; + case 5: r = m.handler5(a, b, c, d); break; + case 6: r = m.handler6(a, b, c, d); break; + case 7: r = m.handler7(a, b, c, d); break; + case 8: r = m.handler8(a, b, c, d); break; + case 9: r = m.handler9(a, b, c, d); break; + case 10: r = m.handler10(a, b, c, d); break; + case 11: r = m.handler11(a, b, c, d); break; + case 12: r = m.handler12(a, b, c, d); break; + case 13: r = m.handler13(a, b, c, d); break; + case 14: r = m.handler14(a, b, c, d); break; + case 15: r = m.handler15(a, b, c, d); break; + case 16: r = m.handler16(a, b, c, d); break; + case 17: r = m.handler17(a, b, c, d); break; + case 18: r = m.handler18(a, b, c, d); break; + case 19: r = m.handler19(a, b, c, d); break; + case 20: r = m.handler20(a, b, c, d); break; + case 21: r = m.handler21(a, b, c, d); break; + case 22: r = m.handler22(a, b, c, d); break; + case 23: r = m.handler23(a, b, c, d); break; + case 24: r = m.handler24(a, b, c, d); break; + case 25: r = m.handler25(a, b, c, d); break; + case 26: r = m.handler26(a, b, c, d); break; + case 27: r = m.handler27(a, b, c, d); break; + case 28: r = m.handler28(a, b, c, d); break; + case 29: r = m.handler29(a, b, c, d); break; + case 30: r = m.handler30(a, b, c, d); break; + case 31: m.target(a, b); r = 0; break; + default: r = -1; + } + return r; + } + + int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + int handler1(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + int handler2(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + int handler3(int a, int b, int c, int d) { + int r = 3; + if (a > 3) r += 1; + if (b > 6) r += 2; + if (c < -3) r += 4; + if (d == 8) r += 8; + return r; + } + int handler4(int a, int b, int c, int d) { + int r = 4; + if (a > 4) r += 1; + if (b > 8) r += 2; + if (c < -4) r += 4; + if (d == 9) r += 8; + return r; + } + int handler5(int a, int b, int c, int d) { + int r = 5; + if (a > 5) r += 1; + if (b > 10) r += 2; + if (c < -5) r += 4; + if (d == 10) r += 8; + return r; + } + int handler6(int a, int b, int c, int d) { + int r = 6; + if (a > 6) r += 1; + if (b > 12) r += 2; + if (c < -6) r += 4; + if (d == 11) r += 8; + return r; + } + int handler7(int a, int b, int c, int d) { + int r = 7; + if (a > 7) r += 1; + if (b > 14) r += 2; + if (c < -7) r += 4; + if (d == 12) r += 8; + return r; + } + int handler8(int a, int b, int c, int d) { + int r = 8; + if (a > 8) r += 1; + if (b > 16) r += 2; + if (c < -8) r += 4; + if (d == 13) r += 8; + return r; + } + int handler9(int a, int b, int c, int d) { + int r = 9; + if (a > 9) r += 1; + if (b > 18) r += 2; + if (c < -9) r += 4; + if (d == 14) r += 8; + return r; + } + int handler10(int a, int b, int c, int d) { + int r = 10; + if (a > 10) r += 1; + if (b > 20) r += 2; + if (c < -10) r += 4; + if (d == 15) r += 8; + return r; + } + int handler11(int a, int b, int c, int d) { + int r = 11; + if (a > 11) r += 1; + if (b > 22) r += 2; + if (c < -11) r += 4; + if (d == 16) r += 8; + return r; + } + int handler12(int a, int b, int c, int d) { + int r = 12; + if (a > 12) r += 1; + if (b > 24) r += 2; + if (c < -12) r += 4; + if (d == 17) r += 8; + return r; + } + int handler13(int a, int b, int c, int d) { + int r = 13; + if (a > 13) r += 1; + if (b > 26) r += 2; + if (c < -13) r += 4; + if (d == 18) r += 8; + return r; + } + int handler14(int a, int b, int c, int d) { + int r = 14; + if (a > 14) r += 1; + if (b > 28) r += 2; + if (c < -14) r += 4; + if (d == 19) r += 8; + return r; + } + int handler15(int a, int b, int c, int d) { + int r = 15; + if (a > 15) r += 1; + if (b > 30) r += 2; + if (c < -15) r += 4; + if (d == 20) r += 8; + return r; + } + int handler16(int a, int b, int c, int d) { + int r = 16; + if (a > 16) r += 1; + if (b > 32) r += 2; + if (c < -16) r += 4; + if (d == 21) r += 8; + return r; + } + int handler17(int a, int b, int c, int d) { + int r = 17; + if (a > 17) r += 1; + if (b > 34) r += 2; + if (c < -17) r += 4; + if (d == 22) r += 8; + return r; + } + int handler18(int a, int b, int c, int d) { + int r = 18; + if (a > 18) r += 1; + if (b > 36) r += 2; + if (c < -18) r += 4; + if (d == 23) r += 8; + return r; + } + int handler19(int a, int b, int c, int d) { + int r = 19; + if (a > 19) r += 1; + if (b > 38) r += 2; + if (c < -19) r += 4; + if (d == 24) r += 8; + return r; + } + int handler20(int a, int b, int c, int d) { + int r = 20; + if (a > 20) r += 1; + if (b > 40) r += 2; + if (c < -20) r += 4; + if (d == 25) r += 8; + return r; + } + int handler21(int a, int b, int c, int d) { + int r = 21; + if (a > 21) r += 1; + if (b > 42) r += 2; + if (c < -21) r += 4; + if (d == 26) r += 8; + return r; + } + int handler22(int a, int b, int c, int d) { + int r = 22; + if (a > 22) r += 1; + if (b > 44) r += 2; + if (c < -22) r += 4; + if (d == 27) r += 8; + return r; + } + int handler23(int a, int b, int c, int d) { + int r = 23; + if (a > 23) r += 1; + if (b > 46) r += 2; + if (c < -23) r += 4; + if (d == 28) r += 8; + return r; + } + int handler24(int a, int b, int c, int d) { + int r = 24; + if (a > 24) r += 1; + if (b > 48) r += 2; + if (c < -24) r += 4; + if (d == 29) r += 8; + return r; + } + int handler25(int a, int b, int c, int d) { + int r = 25; + if (a > 25) r += 1; + if (b > 50) r += 2; + if (c < -25) r += 4; + if (d == 30) r += 8; + return r; + } + int handler26(int a, int b, int c, int d) { + int r = 26; + if (a > 26) r += 1; + if (b > 52) r += 2; + if (c < -26) r += 4; + if (d == 31) r += 8; + return r; + } + int handler27(int a, int b, int c, int d) { + int r = 27; + if (a > 27) r += 1; + if (b > 54) r += 2; + if (c < -27) r += 4; + if (d == 32) r += 8; + return r; + } + int handler28(int a, int b, int c, int d) { + int r = 28; + if (a > 28) r += 1; + if (b > 56) r += 2; + if (c < -28) r += 4; + if (d == 33) r += 8; + return r; + } + int handler29(int a, int b, int c, int d) { + int r = 29; + if (a > 29) r += 1; + if (b > 58) r += 2; + if (c < -29) r += 4; + if (d == 34) r += 8; + return r; + } + int handler30(int a, int b, int c, int d) { + int r = 30; + if (a > 30) r += 1; + if (b > 60) r += 2; + if (c < -30) r += 4; + if (d == 35) r += 8; + return r; + } + + // The only assertion in the program. + void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n02_false.yml b/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n02_false.yml new file mode 100644 index 00000000..cd58cb5c --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n02_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - loop_n02_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n02_false/Main.java b/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n02_false/Main.java new file mode 100644 index 00000000..010f9379 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n02_false/Main.java @@ -0,0 +1,46 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dispatcher/loop: cmd picks one of 2 commands, only the last one (cmd == 1) can reach the assert. +public class Main { + public static void main(String[] args) { + for (int round = 0; round < 2; round++) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + int r; + switch (cmd) { + case 0: r = handler0(a, b, c, d); break; + case 1: target(a, b); r = 0; break; + default: r = -1; + } + return r; + } + + static int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n02_true.yml b/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n02_true.yml new file mode 100644 index 00000000..3864b72d --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n02_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - loop_n02_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n02_true/Main.java b/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n02_true/Main.java new file mode 100644 index 00000000..a93fa116 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n02_true/Main.java @@ -0,0 +1,47 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:1 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dispatcher/loop: cmd picks one of 2 commands, only the last one (cmd == 1) can reach the assert. +public class Main { + public static void main(String[] args) { + for (int round = 0; round < 2; round++) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + int r; + switch (cmd) { + case 0: r = handler0(a, b, c, d); break; + case 1: target(a, b); r = 0; break; + default: r = -1; + } + return r; + } + + static int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n04_false.yml b/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n04_false.yml new file mode 100644 index 00000000..a0edff20 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n04_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - loop_n04_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n04_false/Main.java b/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n04_false/Main.java new file mode 100644 index 00000000..971dc7fe --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n04_false/Main.java @@ -0,0 +1,64 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:3 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dispatcher/loop: cmd picks one of 4 commands, only the last one (cmd == 3) can reach the assert. +public class Main { + public static void main(String[] args) { + for (int round = 0; round < 2; round++) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + int r; + switch (cmd) { + case 0: r = handler0(a, b, c, d); break; + case 1: r = handler1(a, b, c, d); break; + case 2: r = handler2(a, b, c, d); break; + case 3: target(a, b); r = 0; break; + default: r = -1; + } + return r; + } + + static int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + static int handler1(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + static int handler2(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n04_true.yml b/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n04_true.yml new file mode 100644 index 00000000..1318394c --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n04_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - loop_n04_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n04_true/Main.java b/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n04_true/Main.java new file mode 100644 index 00000000..aa647005 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n04_true/Main.java @@ -0,0 +1,65 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:3 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dispatcher/loop: cmd picks one of 4 commands, only the last one (cmd == 3) can reach the assert. +public class Main { + public static void main(String[] args) { + for (int round = 0; round < 2; round++) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + int r; + switch (cmd) { + case 0: r = handler0(a, b, c, d); break; + case 1: r = handler1(a, b, c, d); break; + case 2: r = handler2(a, b, c, d); break; + case 3: target(a, b); r = 0; break; + default: r = -1; + } + return r; + } + + static int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + static int handler1(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + static int handler2(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n08_false.yml b/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n08_false.yml new file mode 100644 index 00000000..4004574c --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n08_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - loop_n08_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n08_false/Main.java b/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n08_false/Main.java new file mode 100644 index 00000000..9cd82d73 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n08_false/Main.java @@ -0,0 +1,100 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:7 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dispatcher/loop: cmd picks one of 8 commands, only the last one (cmd == 7) can reach the assert. +public class Main { + public static void main(String[] args) { + for (int round = 0; round < 2; round++) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + int r; + switch (cmd) { + case 0: r = handler0(a, b, c, d); break; + case 1: r = handler1(a, b, c, d); break; + case 2: r = handler2(a, b, c, d); break; + case 3: r = handler3(a, b, c, d); break; + case 4: r = handler4(a, b, c, d); break; + case 5: r = handler5(a, b, c, d); break; + case 6: r = handler6(a, b, c, d); break; + case 7: target(a, b); r = 0; break; + default: r = -1; + } + return r; + } + + static int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + static int handler1(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + static int handler2(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + static int handler3(int a, int b, int c, int d) { + int r = 3; + if (a > 3) r += 1; + if (b > 6) r += 2; + if (c < -3) r += 4; + if (d == 8) r += 8; + return r; + } + static int handler4(int a, int b, int c, int d) { + int r = 4; + if (a > 4) r += 1; + if (b > 8) r += 2; + if (c < -4) r += 4; + if (d == 9) r += 8; + return r; + } + static int handler5(int a, int b, int c, int d) { + int r = 5; + if (a > 5) r += 1; + if (b > 10) r += 2; + if (c < -5) r += 4; + if (d == 10) r += 8; + return r; + } + static int handler6(int a, int b, int c, int d) { + int r = 6; + if (a > 6) r += 1; + if (b > 12) r += 2; + if (c < -6) r += 4; + if (d == 11) r += 8; + return r; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n08_true.yml b/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n08_true.yml new file mode 100644 index 00000000..2fa0f84f --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n08_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - loop_n08_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n08_true/Main.java b/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n08_true/Main.java new file mode 100644 index 00000000..e68b8fb8 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n08_true/Main.java @@ -0,0 +1,101 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:7 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dispatcher/loop: cmd picks one of 8 commands, only the last one (cmd == 7) can reach the assert. +public class Main { + public static void main(String[] args) { + for (int round = 0; round < 2; round++) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + int r; + switch (cmd) { + case 0: r = handler0(a, b, c, d); break; + case 1: r = handler1(a, b, c, d); break; + case 2: r = handler2(a, b, c, d); break; + case 3: r = handler3(a, b, c, d); break; + case 4: r = handler4(a, b, c, d); break; + case 5: r = handler5(a, b, c, d); break; + case 6: r = handler6(a, b, c, d); break; + case 7: target(a, b); r = 0; break; + default: r = -1; + } + return r; + } + + static int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + static int handler1(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + static int handler2(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + static int handler3(int a, int b, int c, int d) { + int r = 3; + if (a > 3) r += 1; + if (b > 6) r += 2; + if (c < -3) r += 4; + if (d == 8) r += 8; + return r; + } + static int handler4(int a, int b, int c, int d) { + int r = 4; + if (a > 4) r += 1; + if (b > 8) r += 2; + if (c < -4) r += 4; + if (d == 9) r += 8; + return r; + } + static int handler5(int a, int b, int c, int d) { + int r = 5; + if (a > 5) r += 1; + if (b > 10) r += 2; + if (c < -5) r += 4; + if (d == 10) r += 8; + return r; + } + static int handler6(int a, int b, int c, int d) { + int r = 6; + if (a > 6) r += 1; + if (b > 12) r += 2; + if (c < -6) r += 4; + if (d == 11) r += 8; + return r; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n16_false.yml b/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n16_false.yml new file mode 100644 index 00000000..af430e33 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n16_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - loop_n16_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n16_false/Main.java b/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n16_false/Main.java new file mode 100644 index 00000000..657ee037 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n16_false/Main.java @@ -0,0 +1,172 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:15 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dispatcher/loop: cmd picks one of 16 commands, only the last one (cmd == 15) can reach the assert. +public class Main { + public static void main(String[] args) { + for (int round = 0; round < 2; round++) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + int r; + switch (cmd) { + case 0: r = handler0(a, b, c, d); break; + case 1: r = handler1(a, b, c, d); break; + case 2: r = handler2(a, b, c, d); break; + case 3: r = handler3(a, b, c, d); break; + case 4: r = handler4(a, b, c, d); break; + case 5: r = handler5(a, b, c, d); break; + case 6: r = handler6(a, b, c, d); break; + case 7: r = handler7(a, b, c, d); break; + case 8: r = handler8(a, b, c, d); break; + case 9: r = handler9(a, b, c, d); break; + case 10: r = handler10(a, b, c, d); break; + case 11: r = handler11(a, b, c, d); break; + case 12: r = handler12(a, b, c, d); break; + case 13: r = handler13(a, b, c, d); break; + case 14: r = handler14(a, b, c, d); break; + case 15: target(a, b); r = 0; break; + default: r = -1; + } + return r; + } + + static int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + static int handler1(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + static int handler2(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + static int handler3(int a, int b, int c, int d) { + int r = 3; + if (a > 3) r += 1; + if (b > 6) r += 2; + if (c < -3) r += 4; + if (d == 8) r += 8; + return r; + } + static int handler4(int a, int b, int c, int d) { + int r = 4; + if (a > 4) r += 1; + if (b > 8) r += 2; + if (c < -4) r += 4; + if (d == 9) r += 8; + return r; + } + static int handler5(int a, int b, int c, int d) { + int r = 5; + if (a > 5) r += 1; + if (b > 10) r += 2; + if (c < -5) r += 4; + if (d == 10) r += 8; + return r; + } + static int handler6(int a, int b, int c, int d) { + int r = 6; + if (a > 6) r += 1; + if (b > 12) r += 2; + if (c < -6) r += 4; + if (d == 11) r += 8; + return r; + } + static int handler7(int a, int b, int c, int d) { + int r = 7; + if (a > 7) r += 1; + if (b > 14) r += 2; + if (c < -7) r += 4; + if (d == 12) r += 8; + return r; + } + static int handler8(int a, int b, int c, int d) { + int r = 8; + if (a > 8) r += 1; + if (b > 16) r += 2; + if (c < -8) r += 4; + if (d == 13) r += 8; + return r; + } + static int handler9(int a, int b, int c, int d) { + int r = 9; + if (a > 9) r += 1; + if (b > 18) r += 2; + if (c < -9) r += 4; + if (d == 14) r += 8; + return r; + } + static int handler10(int a, int b, int c, int d) { + int r = 10; + if (a > 10) r += 1; + if (b > 20) r += 2; + if (c < -10) r += 4; + if (d == 15) r += 8; + return r; + } + static int handler11(int a, int b, int c, int d) { + int r = 11; + if (a > 11) r += 1; + if (b > 22) r += 2; + if (c < -11) r += 4; + if (d == 16) r += 8; + return r; + } + static int handler12(int a, int b, int c, int d) { + int r = 12; + if (a > 12) r += 1; + if (b > 24) r += 2; + if (c < -12) r += 4; + if (d == 17) r += 8; + return r; + } + static int handler13(int a, int b, int c, int d) { + int r = 13; + if (a > 13) r += 1; + if (b > 26) r += 2; + if (c < -13) r += 4; + if (d == 18) r += 8; + return r; + } + static int handler14(int a, int b, int c, int d) { + int r = 14; + if (a > 14) r += 1; + if (b > 28) r += 2; + if (c < -14) r += 4; + if (d == 19) r += 8; + return r; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n16_true.yml b/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n16_true.yml new file mode 100644 index 00000000..c407b9ec --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n16_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - loop_n16_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n16_true/Main.java b/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n16_true/Main.java new file mode 100644 index 00000000..9daf8938 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n16_true/Main.java @@ -0,0 +1,173 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:15 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dispatcher/loop: cmd picks one of 16 commands, only the last one (cmd == 15) can reach the assert. +public class Main { + public static void main(String[] args) { + for (int round = 0; round < 2; round++) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + int r; + switch (cmd) { + case 0: r = handler0(a, b, c, d); break; + case 1: r = handler1(a, b, c, d); break; + case 2: r = handler2(a, b, c, d); break; + case 3: r = handler3(a, b, c, d); break; + case 4: r = handler4(a, b, c, d); break; + case 5: r = handler5(a, b, c, d); break; + case 6: r = handler6(a, b, c, d); break; + case 7: r = handler7(a, b, c, d); break; + case 8: r = handler8(a, b, c, d); break; + case 9: r = handler9(a, b, c, d); break; + case 10: r = handler10(a, b, c, d); break; + case 11: r = handler11(a, b, c, d); break; + case 12: r = handler12(a, b, c, d); break; + case 13: r = handler13(a, b, c, d); break; + case 14: r = handler14(a, b, c, d); break; + case 15: target(a, b); r = 0; break; + default: r = -1; + } + return r; + } + + static int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + static int handler1(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + static int handler2(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + static int handler3(int a, int b, int c, int d) { + int r = 3; + if (a > 3) r += 1; + if (b > 6) r += 2; + if (c < -3) r += 4; + if (d == 8) r += 8; + return r; + } + static int handler4(int a, int b, int c, int d) { + int r = 4; + if (a > 4) r += 1; + if (b > 8) r += 2; + if (c < -4) r += 4; + if (d == 9) r += 8; + return r; + } + static int handler5(int a, int b, int c, int d) { + int r = 5; + if (a > 5) r += 1; + if (b > 10) r += 2; + if (c < -5) r += 4; + if (d == 10) r += 8; + return r; + } + static int handler6(int a, int b, int c, int d) { + int r = 6; + if (a > 6) r += 1; + if (b > 12) r += 2; + if (c < -6) r += 4; + if (d == 11) r += 8; + return r; + } + static int handler7(int a, int b, int c, int d) { + int r = 7; + if (a > 7) r += 1; + if (b > 14) r += 2; + if (c < -7) r += 4; + if (d == 12) r += 8; + return r; + } + static int handler8(int a, int b, int c, int d) { + int r = 8; + if (a > 8) r += 1; + if (b > 16) r += 2; + if (c < -8) r += 4; + if (d == 13) r += 8; + return r; + } + static int handler9(int a, int b, int c, int d) { + int r = 9; + if (a > 9) r += 1; + if (b > 18) r += 2; + if (c < -9) r += 4; + if (d == 14) r += 8; + return r; + } + static int handler10(int a, int b, int c, int d) { + int r = 10; + if (a > 10) r += 1; + if (b > 20) r += 2; + if (c < -10) r += 4; + if (d == 15) r += 8; + return r; + } + static int handler11(int a, int b, int c, int d) { + int r = 11; + if (a > 11) r += 1; + if (b > 22) r += 2; + if (c < -11) r += 4; + if (d == 16) r += 8; + return r; + } + static int handler12(int a, int b, int c, int d) { + int r = 12; + if (a > 12) r += 1; + if (b > 24) r += 2; + if (c < -12) r += 4; + if (d == 17) r += 8; + return r; + } + static int handler13(int a, int b, int c, int d) { + int r = 13; + if (a > 13) r += 1; + if (b > 26) r += 2; + if (c < -13) r += 4; + if (d == 18) r += 8; + return r; + } + static int handler14(int a, int b, int c, int d) { + int r = 14; + if (a > 14) r += 1; + if (b > 28) r += 2; + if (c < -14) r += 4; + if (d == 19) r += 8; + return r; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n32_false.yml b/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n32_false.yml new file mode 100644 index 00000000..29aff887 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n32_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - loop_n32_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n32_false/Main.java b/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n32_false/Main.java new file mode 100644 index 00000000..0b932bc1 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n32_false/Main.java @@ -0,0 +1,316 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:31 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dispatcher/loop: cmd picks one of 32 commands, only the last one (cmd == 31) can reach the assert. +public class Main { + public static void main(String[] args) { + for (int round = 0; round < 2; round++) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + int r; + switch (cmd) { + case 0: r = handler0(a, b, c, d); break; + case 1: r = handler1(a, b, c, d); break; + case 2: r = handler2(a, b, c, d); break; + case 3: r = handler3(a, b, c, d); break; + case 4: r = handler4(a, b, c, d); break; + case 5: r = handler5(a, b, c, d); break; + case 6: r = handler6(a, b, c, d); break; + case 7: r = handler7(a, b, c, d); break; + case 8: r = handler8(a, b, c, d); break; + case 9: r = handler9(a, b, c, d); break; + case 10: r = handler10(a, b, c, d); break; + case 11: r = handler11(a, b, c, d); break; + case 12: r = handler12(a, b, c, d); break; + case 13: r = handler13(a, b, c, d); break; + case 14: r = handler14(a, b, c, d); break; + case 15: r = handler15(a, b, c, d); break; + case 16: r = handler16(a, b, c, d); break; + case 17: r = handler17(a, b, c, d); break; + case 18: r = handler18(a, b, c, d); break; + case 19: r = handler19(a, b, c, d); break; + case 20: r = handler20(a, b, c, d); break; + case 21: r = handler21(a, b, c, d); break; + case 22: r = handler22(a, b, c, d); break; + case 23: r = handler23(a, b, c, d); break; + case 24: r = handler24(a, b, c, d); break; + case 25: r = handler25(a, b, c, d); break; + case 26: r = handler26(a, b, c, d); break; + case 27: r = handler27(a, b, c, d); break; + case 28: r = handler28(a, b, c, d); break; + case 29: r = handler29(a, b, c, d); break; + case 30: r = handler30(a, b, c, d); break; + case 31: target(a, b); r = 0; break; + default: r = -1; + } + return r; + } + + static int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + static int handler1(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + static int handler2(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + static int handler3(int a, int b, int c, int d) { + int r = 3; + if (a > 3) r += 1; + if (b > 6) r += 2; + if (c < -3) r += 4; + if (d == 8) r += 8; + return r; + } + static int handler4(int a, int b, int c, int d) { + int r = 4; + if (a > 4) r += 1; + if (b > 8) r += 2; + if (c < -4) r += 4; + if (d == 9) r += 8; + return r; + } + static int handler5(int a, int b, int c, int d) { + int r = 5; + if (a > 5) r += 1; + if (b > 10) r += 2; + if (c < -5) r += 4; + if (d == 10) r += 8; + return r; + } + static int handler6(int a, int b, int c, int d) { + int r = 6; + if (a > 6) r += 1; + if (b > 12) r += 2; + if (c < -6) r += 4; + if (d == 11) r += 8; + return r; + } + static int handler7(int a, int b, int c, int d) { + int r = 7; + if (a > 7) r += 1; + if (b > 14) r += 2; + if (c < -7) r += 4; + if (d == 12) r += 8; + return r; + } + static int handler8(int a, int b, int c, int d) { + int r = 8; + if (a > 8) r += 1; + if (b > 16) r += 2; + if (c < -8) r += 4; + if (d == 13) r += 8; + return r; + } + static int handler9(int a, int b, int c, int d) { + int r = 9; + if (a > 9) r += 1; + if (b > 18) r += 2; + if (c < -9) r += 4; + if (d == 14) r += 8; + return r; + } + static int handler10(int a, int b, int c, int d) { + int r = 10; + if (a > 10) r += 1; + if (b > 20) r += 2; + if (c < -10) r += 4; + if (d == 15) r += 8; + return r; + } + static int handler11(int a, int b, int c, int d) { + int r = 11; + if (a > 11) r += 1; + if (b > 22) r += 2; + if (c < -11) r += 4; + if (d == 16) r += 8; + return r; + } + static int handler12(int a, int b, int c, int d) { + int r = 12; + if (a > 12) r += 1; + if (b > 24) r += 2; + if (c < -12) r += 4; + if (d == 17) r += 8; + return r; + } + static int handler13(int a, int b, int c, int d) { + int r = 13; + if (a > 13) r += 1; + if (b > 26) r += 2; + if (c < -13) r += 4; + if (d == 18) r += 8; + return r; + } + static int handler14(int a, int b, int c, int d) { + int r = 14; + if (a > 14) r += 1; + if (b > 28) r += 2; + if (c < -14) r += 4; + if (d == 19) r += 8; + return r; + } + static int handler15(int a, int b, int c, int d) { + int r = 15; + if (a > 15) r += 1; + if (b > 30) r += 2; + if (c < -15) r += 4; + if (d == 20) r += 8; + return r; + } + static int handler16(int a, int b, int c, int d) { + int r = 16; + if (a > 16) r += 1; + if (b > 32) r += 2; + if (c < -16) r += 4; + if (d == 21) r += 8; + return r; + } + static int handler17(int a, int b, int c, int d) { + int r = 17; + if (a > 17) r += 1; + if (b > 34) r += 2; + if (c < -17) r += 4; + if (d == 22) r += 8; + return r; + } + static int handler18(int a, int b, int c, int d) { + int r = 18; + if (a > 18) r += 1; + if (b > 36) r += 2; + if (c < -18) r += 4; + if (d == 23) r += 8; + return r; + } + static int handler19(int a, int b, int c, int d) { + int r = 19; + if (a > 19) r += 1; + if (b > 38) r += 2; + if (c < -19) r += 4; + if (d == 24) r += 8; + return r; + } + static int handler20(int a, int b, int c, int d) { + int r = 20; + if (a > 20) r += 1; + if (b > 40) r += 2; + if (c < -20) r += 4; + if (d == 25) r += 8; + return r; + } + static int handler21(int a, int b, int c, int d) { + int r = 21; + if (a > 21) r += 1; + if (b > 42) r += 2; + if (c < -21) r += 4; + if (d == 26) r += 8; + return r; + } + static int handler22(int a, int b, int c, int d) { + int r = 22; + if (a > 22) r += 1; + if (b > 44) r += 2; + if (c < -22) r += 4; + if (d == 27) r += 8; + return r; + } + static int handler23(int a, int b, int c, int d) { + int r = 23; + if (a > 23) r += 1; + if (b > 46) r += 2; + if (c < -23) r += 4; + if (d == 28) r += 8; + return r; + } + static int handler24(int a, int b, int c, int d) { + int r = 24; + if (a > 24) r += 1; + if (b > 48) r += 2; + if (c < -24) r += 4; + if (d == 29) r += 8; + return r; + } + static int handler25(int a, int b, int c, int d) { + int r = 25; + if (a > 25) r += 1; + if (b > 50) r += 2; + if (c < -25) r += 4; + if (d == 30) r += 8; + return r; + } + static int handler26(int a, int b, int c, int d) { + int r = 26; + if (a > 26) r += 1; + if (b > 52) r += 2; + if (c < -26) r += 4; + if (d == 31) r += 8; + return r; + } + static int handler27(int a, int b, int c, int d) { + int r = 27; + if (a > 27) r += 1; + if (b > 54) r += 2; + if (c < -27) r += 4; + if (d == 32) r += 8; + return r; + } + static int handler28(int a, int b, int c, int d) { + int r = 28; + if (a > 28) r += 1; + if (b > 56) r += 2; + if (c < -28) r += 4; + if (d == 33) r += 8; + return r; + } + static int handler29(int a, int b, int c, int d) { + int r = 29; + if (a > 29) r += 1; + if (b > 58) r += 2; + if (c < -29) r += 4; + if (d == 34) r += 8; + return r; + } + static int handler30(int a, int b, int c, int d) { + int r = 30; + if (a > 30) r += 1; + if (b > 60) r += 2; + if (c < -30) r += 4; + if (d == 35) r += 8; + return r; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n32_true.yml b/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n32_true.yml new file mode 100644 index 00000000..d66e1da2 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n32_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - loop_n32_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n32_true/Main.java b/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n32_true/Main.java new file mode 100644 index 00000000..1241fcbe --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/loop_n32_true/Main.java @@ -0,0 +1,317 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:31 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dispatcher/loop: cmd picks one of 32 commands, only the last one (cmd == 31) can reach the assert. +public class Main { + public static void main(String[] args) { + for (int round = 0; round < 2; round++) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + int r; + switch (cmd) { + case 0: r = handler0(a, b, c, d); break; + case 1: r = handler1(a, b, c, d); break; + case 2: r = handler2(a, b, c, d); break; + case 3: r = handler3(a, b, c, d); break; + case 4: r = handler4(a, b, c, d); break; + case 5: r = handler5(a, b, c, d); break; + case 6: r = handler6(a, b, c, d); break; + case 7: r = handler7(a, b, c, d); break; + case 8: r = handler8(a, b, c, d); break; + case 9: r = handler9(a, b, c, d); break; + case 10: r = handler10(a, b, c, d); break; + case 11: r = handler11(a, b, c, d); break; + case 12: r = handler12(a, b, c, d); break; + case 13: r = handler13(a, b, c, d); break; + case 14: r = handler14(a, b, c, d); break; + case 15: r = handler15(a, b, c, d); break; + case 16: r = handler16(a, b, c, d); break; + case 17: r = handler17(a, b, c, d); break; + case 18: r = handler18(a, b, c, d); break; + case 19: r = handler19(a, b, c, d); break; + case 20: r = handler20(a, b, c, d); break; + case 21: r = handler21(a, b, c, d); break; + case 22: r = handler22(a, b, c, d); break; + case 23: r = handler23(a, b, c, d); break; + case 24: r = handler24(a, b, c, d); break; + case 25: r = handler25(a, b, c, d); break; + case 26: r = handler26(a, b, c, d); break; + case 27: r = handler27(a, b, c, d); break; + case 28: r = handler28(a, b, c, d); break; + case 29: r = handler29(a, b, c, d); break; + case 30: r = handler30(a, b, c, d); break; + case 31: target(a, b); r = 0; break; + default: r = -1; + } + return r; + } + + static int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + static int handler1(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + static int handler2(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + static int handler3(int a, int b, int c, int d) { + int r = 3; + if (a > 3) r += 1; + if (b > 6) r += 2; + if (c < -3) r += 4; + if (d == 8) r += 8; + return r; + } + static int handler4(int a, int b, int c, int d) { + int r = 4; + if (a > 4) r += 1; + if (b > 8) r += 2; + if (c < -4) r += 4; + if (d == 9) r += 8; + return r; + } + static int handler5(int a, int b, int c, int d) { + int r = 5; + if (a > 5) r += 1; + if (b > 10) r += 2; + if (c < -5) r += 4; + if (d == 10) r += 8; + return r; + } + static int handler6(int a, int b, int c, int d) { + int r = 6; + if (a > 6) r += 1; + if (b > 12) r += 2; + if (c < -6) r += 4; + if (d == 11) r += 8; + return r; + } + static int handler7(int a, int b, int c, int d) { + int r = 7; + if (a > 7) r += 1; + if (b > 14) r += 2; + if (c < -7) r += 4; + if (d == 12) r += 8; + return r; + } + static int handler8(int a, int b, int c, int d) { + int r = 8; + if (a > 8) r += 1; + if (b > 16) r += 2; + if (c < -8) r += 4; + if (d == 13) r += 8; + return r; + } + static int handler9(int a, int b, int c, int d) { + int r = 9; + if (a > 9) r += 1; + if (b > 18) r += 2; + if (c < -9) r += 4; + if (d == 14) r += 8; + return r; + } + static int handler10(int a, int b, int c, int d) { + int r = 10; + if (a > 10) r += 1; + if (b > 20) r += 2; + if (c < -10) r += 4; + if (d == 15) r += 8; + return r; + } + static int handler11(int a, int b, int c, int d) { + int r = 11; + if (a > 11) r += 1; + if (b > 22) r += 2; + if (c < -11) r += 4; + if (d == 16) r += 8; + return r; + } + static int handler12(int a, int b, int c, int d) { + int r = 12; + if (a > 12) r += 1; + if (b > 24) r += 2; + if (c < -12) r += 4; + if (d == 17) r += 8; + return r; + } + static int handler13(int a, int b, int c, int d) { + int r = 13; + if (a > 13) r += 1; + if (b > 26) r += 2; + if (c < -13) r += 4; + if (d == 18) r += 8; + return r; + } + static int handler14(int a, int b, int c, int d) { + int r = 14; + if (a > 14) r += 1; + if (b > 28) r += 2; + if (c < -14) r += 4; + if (d == 19) r += 8; + return r; + } + static int handler15(int a, int b, int c, int d) { + int r = 15; + if (a > 15) r += 1; + if (b > 30) r += 2; + if (c < -15) r += 4; + if (d == 20) r += 8; + return r; + } + static int handler16(int a, int b, int c, int d) { + int r = 16; + if (a > 16) r += 1; + if (b > 32) r += 2; + if (c < -16) r += 4; + if (d == 21) r += 8; + return r; + } + static int handler17(int a, int b, int c, int d) { + int r = 17; + if (a > 17) r += 1; + if (b > 34) r += 2; + if (c < -17) r += 4; + if (d == 22) r += 8; + return r; + } + static int handler18(int a, int b, int c, int d) { + int r = 18; + if (a > 18) r += 1; + if (b > 36) r += 2; + if (c < -18) r += 4; + if (d == 23) r += 8; + return r; + } + static int handler19(int a, int b, int c, int d) { + int r = 19; + if (a > 19) r += 1; + if (b > 38) r += 2; + if (c < -19) r += 4; + if (d == 24) r += 8; + return r; + } + static int handler20(int a, int b, int c, int d) { + int r = 20; + if (a > 20) r += 1; + if (b > 40) r += 2; + if (c < -20) r += 4; + if (d == 25) r += 8; + return r; + } + static int handler21(int a, int b, int c, int d) { + int r = 21; + if (a > 21) r += 1; + if (b > 42) r += 2; + if (c < -21) r += 4; + if (d == 26) r += 8; + return r; + } + static int handler22(int a, int b, int c, int d) { + int r = 22; + if (a > 22) r += 1; + if (b > 44) r += 2; + if (c < -22) r += 4; + if (d == 27) r += 8; + return r; + } + static int handler23(int a, int b, int c, int d) { + int r = 23; + if (a > 23) r += 1; + if (b > 46) r += 2; + if (c < -23) r += 4; + if (d == 28) r += 8; + return r; + } + static int handler24(int a, int b, int c, int d) { + int r = 24; + if (a > 24) r += 1; + if (b > 48) r += 2; + if (c < -24) r += 4; + if (d == 29) r += 8; + return r; + } + static int handler25(int a, int b, int c, int d) { + int r = 25; + if (a > 25) r += 1; + if (b > 50) r += 2; + if (c < -25) r += 4; + if (d == 30) r += 8; + return r; + } + static int handler26(int a, int b, int c, int d) { + int r = 26; + if (a > 26) r += 1; + if (b > 52) r += 2; + if (c < -26) r += 4; + if (d == 31) r += 8; + return r; + } + static int handler27(int a, int b, int c, int d) { + int r = 27; + if (a > 27) r += 1; + if (b > 54) r += 2; + if (c < -27) r += 4; + if (d == 32) r += 8; + return r; + } + static int handler28(int a, int b, int c, int d) { + int r = 28; + if (a > 28) r += 1; + if (b > 56) r += 2; + if (c < -28) r += 4; + if (d == 33) r += 8; + return r; + } + static int handler29(int a, int b, int c, int d) { + int r = 29; + if (a > 29) r += 1; + if (b > 58) r += 2; + if (c < -29) r += 4; + if (d == 34) r += 8; + return r; + } + static int handler30(int a, int b, int c, int d) { + int r = 30; + if (a > 30) r += 1; + if (b > 60) r += 2; + if (c < -30) r += 4; + if (d == 35) r += 8; + return r; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n02_false.yml b/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n02_false.yml new file mode 100644 index 00000000..8d2ad6fd --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n02_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - switch_n02_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n02_false/Main.java b/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n02_false/Main.java new file mode 100644 index 00000000..4c860df3 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n02_false/Main.java @@ -0,0 +1,44 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dispatcher/switch: cmd picks one of 2 commands, only the last one (cmd == 1) can reach the assert. +public class Main { + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + int r; + switch (cmd) { + case 0: r = handler0(a, b, c, d); break; + case 1: target(a, b); r = 0; break; + default: r = -1; + } + return r; + } + + static int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n02_true.yml b/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n02_true.yml new file mode 100644 index 00000000..cdeef23e --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n02_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - switch_n02_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n02_true/Main.java b/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n02_true/Main.java new file mode 100644 index 00000000..c8f543c3 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n02_true/Main.java @@ -0,0 +1,45 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:1 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dispatcher/switch: cmd picks one of 2 commands, only the last one (cmd == 1) can reach the assert. +public class Main { + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + int r; + switch (cmd) { + case 0: r = handler0(a, b, c, d); break; + case 1: target(a, b); r = 0; break; + default: r = -1; + } + return r; + } + + static int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n04_false.yml b/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n04_false.yml new file mode 100644 index 00000000..034b073c --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n04_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - switch_n04_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n04_false/Main.java b/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n04_false/Main.java new file mode 100644 index 00000000..4de3b13a --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n04_false/Main.java @@ -0,0 +1,62 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:3 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dispatcher/switch: cmd picks one of 4 commands, only the last one (cmd == 3) can reach the assert. +public class Main { + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + int r; + switch (cmd) { + case 0: r = handler0(a, b, c, d); break; + case 1: r = handler1(a, b, c, d); break; + case 2: r = handler2(a, b, c, d); break; + case 3: target(a, b); r = 0; break; + default: r = -1; + } + return r; + } + + static int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + static int handler1(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + static int handler2(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n04_true.yml b/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n04_true.yml new file mode 100644 index 00000000..fbcc1371 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n04_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - switch_n04_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n04_true/Main.java b/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n04_true/Main.java new file mode 100644 index 00000000..820af5a0 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n04_true/Main.java @@ -0,0 +1,63 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:3 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dispatcher/switch: cmd picks one of 4 commands, only the last one (cmd == 3) can reach the assert. +public class Main { + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + int r; + switch (cmd) { + case 0: r = handler0(a, b, c, d); break; + case 1: r = handler1(a, b, c, d); break; + case 2: r = handler2(a, b, c, d); break; + case 3: target(a, b); r = 0; break; + default: r = -1; + } + return r; + } + + static int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + static int handler1(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + static int handler2(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n08_false.yml b/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n08_false.yml new file mode 100644 index 00000000..9f5b078c --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n08_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - switch_n08_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n08_false/Main.java b/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n08_false/Main.java new file mode 100644 index 00000000..e1564e10 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n08_false/Main.java @@ -0,0 +1,98 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:7 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dispatcher/switch: cmd picks one of 8 commands, only the last one (cmd == 7) can reach the assert. +public class Main { + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + int r; + switch (cmd) { + case 0: r = handler0(a, b, c, d); break; + case 1: r = handler1(a, b, c, d); break; + case 2: r = handler2(a, b, c, d); break; + case 3: r = handler3(a, b, c, d); break; + case 4: r = handler4(a, b, c, d); break; + case 5: r = handler5(a, b, c, d); break; + case 6: r = handler6(a, b, c, d); break; + case 7: target(a, b); r = 0; break; + default: r = -1; + } + return r; + } + + static int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + static int handler1(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + static int handler2(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + static int handler3(int a, int b, int c, int d) { + int r = 3; + if (a > 3) r += 1; + if (b > 6) r += 2; + if (c < -3) r += 4; + if (d == 8) r += 8; + return r; + } + static int handler4(int a, int b, int c, int d) { + int r = 4; + if (a > 4) r += 1; + if (b > 8) r += 2; + if (c < -4) r += 4; + if (d == 9) r += 8; + return r; + } + static int handler5(int a, int b, int c, int d) { + int r = 5; + if (a > 5) r += 1; + if (b > 10) r += 2; + if (c < -5) r += 4; + if (d == 10) r += 8; + return r; + } + static int handler6(int a, int b, int c, int d) { + int r = 6; + if (a > 6) r += 1; + if (b > 12) r += 2; + if (c < -6) r += 4; + if (d == 11) r += 8; + return r; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n08_true.yml b/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n08_true.yml new file mode 100644 index 00000000..255589b9 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n08_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - switch_n08_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n08_true/Main.java b/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n08_true/Main.java new file mode 100644 index 00000000..72e3bc5f --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n08_true/Main.java @@ -0,0 +1,99 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:7 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dispatcher/switch: cmd picks one of 8 commands, only the last one (cmd == 7) can reach the assert. +public class Main { + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + int r; + switch (cmd) { + case 0: r = handler0(a, b, c, d); break; + case 1: r = handler1(a, b, c, d); break; + case 2: r = handler2(a, b, c, d); break; + case 3: r = handler3(a, b, c, d); break; + case 4: r = handler4(a, b, c, d); break; + case 5: r = handler5(a, b, c, d); break; + case 6: r = handler6(a, b, c, d); break; + case 7: target(a, b); r = 0; break; + default: r = -1; + } + return r; + } + + static int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + static int handler1(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + static int handler2(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + static int handler3(int a, int b, int c, int d) { + int r = 3; + if (a > 3) r += 1; + if (b > 6) r += 2; + if (c < -3) r += 4; + if (d == 8) r += 8; + return r; + } + static int handler4(int a, int b, int c, int d) { + int r = 4; + if (a > 4) r += 1; + if (b > 8) r += 2; + if (c < -4) r += 4; + if (d == 9) r += 8; + return r; + } + static int handler5(int a, int b, int c, int d) { + int r = 5; + if (a > 5) r += 1; + if (b > 10) r += 2; + if (c < -5) r += 4; + if (d == 10) r += 8; + return r; + } + static int handler6(int a, int b, int c, int d) { + int r = 6; + if (a > 6) r += 1; + if (b > 12) r += 2; + if (c < -6) r += 4; + if (d == 11) r += 8; + return r; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n16_false.yml b/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n16_false.yml new file mode 100644 index 00000000..3b53496b --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n16_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - switch_n16_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n16_false/Main.java b/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n16_false/Main.java new file mode 100644 index 00000000..21606f9e --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n16_false/Main.java @@ -0,0 +1,170 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:15 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dispatcher/switch: cmd picks one of 16 commands, only the last one (cmd == 15) can reach the assert. +public class Main { + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + int r; + switch (cmd) { + case 0: r = handler0(a, b, c, d); break; + case 1: r = handler1(a, b, c, d); break; + case 2: r = handler2(a, b, c, d); break; + case 3: r = handler3(a, b, c, d); break; + case 4: r = handler4(a, b, c, d); break; + case 5: r = handler5(a, b, c, d); break; + case 6: r = handler6(a, b, c, d); break; + case 7: r = handler7(a, b, c, d); break; + case 8: r = handler8(a, b, c, d); break; + case 9: r = handler9(a, b, c, d); break; + case 10: r = handler10(a, b, c, d); break; + case 11: r = handler11(a, b, c, d); break; + case 12: r = handler12(a, b, c, d); break; + case 13: r = handler13(a, b, c, d); break; + case 14: r = handler14(a, b, c, d); break; + case 15: target(a, b); r = 0; break; + default: r = -1; + } + return r; + } + + static int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + static int handler1(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + static int handler2(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + static int handler3(int a, int b, int c, int d) { + int r = 3; + if (a > 3) r += 1; + if (b > 6) r += 2; + if (c < -3) r += 4; + if (d == 8) r += 8; + return r; + } + static int handler4(int a, int b, int c, int d) { + int r = 4; + if (a > 4) r += 1; + if (b > 8) r += 2; + if (c < -4) r += 4; + if (d == 9) r += 8; + return r; + } + static int handler5(int a, int b, int c, int d) { + int r = 5; + if (a > 5) r += 1; + if (b > 10) r += 2; + if (c < -5) r += 4; + if (d == 10) r += 8; + return r; + } + static int handler6(int a, int b, int c, int d) { + int r = 6; + if (a > 6) r += 1; + if (b > 12) r += 2; + if (c < -6) r += 4; + if (d == 11) r += 8; + return r; + } + static int handler7(int a, int b, int c, int d) { + int r = 7; + if (a > 7) r += 1; + if (b > 14) r += 2; + if (c < -7) r += 4; + if (d == 12) r += 8; + return r; + } + static int handler8(int a, int b, int c, int d) { + int r = 8; + if (a > 8) r += 1; + if (b > 16) r += 2; + if (c < -8) r += 4; + if (d == 13) r += 8; + return r; + } + static int handler9(int a, int b, int c, int d) { + int r = 9; + if (a > 9) r += 1; + if (b > 18) r += 2; + if (c < -9) r += 4; + if (d == 14) r += 8; + return r; + } + static int handler10(int a, int b, int c, int d) { + int r = 10; + if (a > 10) r += 1; + if (b > 20) r += 2; + if (c < -10) r += 4; + if (d == 15) r += 8; + return r; + } + static int handler11(int a, int b, int c, int d) { + int r = 11; + if (a > 11) r += 1; + if (b > 22) r += 2; + if (c < -11) r += 4; + if (d == 16) r += 8; + return r; + } + static int handler12(int a, int b, int c, int d) { + int r = 12; + if (a > 12) r += 1; + if (b > 24) r += 2; + if (c < -12) r += 4; + if (d == 17) r += 8; + return r; + } + static int handler13(int a, int b, int c, int d) { + int r = 13; + if (a > 13) r += 1; + if (b > 26) r += 2; + if (c < -13) r += 4; + if (d == 18) r += 8; + return r; + } + static int handler14(int a, int b, int c, int d) { + int r = 14; + if (a > 14) r += 1; + if (b > 28) r += 2; + if (c < -14) r += 4; + if (d == 19) r += 8; + return r; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n16_true.yml b/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n16_true.yml new file mode 100644 index 00000000..7c028f55 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n16_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - switch_n16_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n16_true/Main.java b/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n16_true/Main.java new file mode 100644 index 00000000..5c5e7d95 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n16_true/Main.java @@ -0,0 +1,171 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:15 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dispatcher/switch: cmd picks one of 16 commands, only the last one (cmd == 15) can reach the assert. +public class Main { + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + int r; + switch (cmd) { + case 0: r = handler0(a, b, c, d); break; + case 1: r = handler1(a, b, c, d); break; + case 2: r = handler2(a, b, c, d); break; + case 3: r = handler3(a, b, c, d); break; + case 4: r = handler4(a, b, c, d); break; + case 5: r = handler5(a, b, c, d); break; + case 6: r = handler6(a, b, c, d); break; + case 7: r = handler7(a, b, c, d); break; + case 8: r = handler8(a, b, c, d); break; + case 9: r = handler9(a, b, c, d); break; + case 10: r = handler10(a, b, c, d); break; + case 11: r = handler11(a, b, c, d); break; + case 12: r = handler12(a, b, c, d); break; + case 13: r = handler13(a, b, c, d); break; + case 14: r = handler14(a, b, c, d); break; + case 15: target(a, b); r = 0; break; + default: r = -1; + } + return r; + } + + static int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + static int handler1(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + static int handler2(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + static int handler3(int a, int b, int c, int d) { + int r = 3; + if (a > 3) r += 1; + if (b > 6) r += 2; + if (c < -3) r += 4; + if (d == 8) r += 8; + return r; + } + static int handler4(int a, int b, int c, int d) { + int r = 4; + if (a > 4) r += 1; + if (b > 8) r += 2; + if (c < -4) r += 4; + if (d == 9) r += 8; + return r; + } + static int handler5(int a, int b, int c, int d) { + int r = 5; + if (a > 5) r += 1; + if (b > 10) r += 2; + if (c < -5) r += 4; + if (d == 10) r += 8; + return r; + } + static int handler6(int a, int b, int c, int d) { + int r = 6; + if (a > 6) r += 1; + if (b > 12) r += 2; + if (c < -6) r += 4; + if (d == 11) r += 8; + return r; + } + static int handler7(int a, int b, int c, int d) { + int r = 7; + if (a > 7) r += 1; + if (b > 14) r += 2; + if (c < -7) r += 4; + if (d == 12) r += 8; + return r; + } + static int handler8(int a, int b, int c, int d) { + int r = 8; + if (a > 8) r += 1; + if (b > 16) r += 2; + if (c < -8) r += 4; + if (d == 13) r += 8; + return r; + } + static int handler9(int a, int b, int c, int d) { + int r = 9; + if (a > 9) r += 1; + if (b > 18) r += 2; + if (c < -9) r += 4; + if (d == 14) r += 8; + return r; + } + static int handler10(int a, int b, int c, int d) { + int r = 10; + if (a > 10) r += 1; + if (b > 20) r += 2; + if (c < -10) r += 4; + if (d == 15) r += 8; + return r; + } + static int handler11(int a, int b, int c, int d) { + int r = 11; + if (a > 11) r += 1; + if (b > 22) r += 2; + if (c < -11) r += 4; + if (d == 16) r += 8; + return r; + } + static int handler12(int a, int b, int c, int d) { + int r = 12; + if (a > 12) r += 1; + if (b > 24) r += 2; + if (c < -12) r += 4; + if (d == 17) r += 8; + return r; + } + static int handler13(int a, int b, int c, int d) { + int r = 13; + if (a > 13) r += 1; + if (b > 26) r += 2; + if (c < -13) r += 4; + if (d == 18) r += 8; + return r; + } + static int handler14(int a, int b, int c, int d) { + int r = 14; + if (a > 14) r += 1; + if (b > 28) r += 2; + if (c < -14) r += 4; + if (d == 19) r += 8; + return r; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n32_false.yml b/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n32_false.yml new file mode 100644 index 00000000..756fd491 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n32_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - switch_n32_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n32_false/Main.java b/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n32_false/Main.java new file mode 100644 index 00000000..8d0fb680 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n32_false/Main.java @@ -0,0 +1,314 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:31 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dispatcher/switch: cmd picks one of 32 commands, only the last one (cmd == 31) can reach the assert. +public class Main { + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + int r; + switch (cmd) { + case 0: r = handler0(a, b, c, d); break; + case 1: r = handler1(a, b, c, d); break; + case 2: r = handler2(a, b, c, d); break; + case 3: r = handler3(a, b, c, d); break; + case 4: r = handler4(a, b, c, d); break; + case 5: r = handler5(a, b, c, d); break; + case 6: r = handler6(a, b, c, d); break; + case 7: r = handler7(a, b, c, d); break; + case 8: r = handler8(a, b, c, d); break; + case 9: r = handler9(a, b, c, d); break; + case 10: r = handler10(a, b, c, d); break; + case 11: r = handler11(a, b, c, d); break; + case 12: r = handler12(a, b, c, d); break; + case 13: r = handler13(a, b, c, d); break; + case 14: r = handler14(a, b, c, d); break; + case 15: r = handler15(a, b, c, d); break; + case 16: r = handler16(a, b, c, d); break; + case 17: r = handler17(a, b, c, d); break; + case 18: r = handler18(a, b, c, d); break; + case 19: r = handler19(a, b, c, d); break; + case 20: r = handler20(a, b, c, d); break; + case 21: r = handler21(a, b, c, d); break; + case 22: r = handler22(a, b, c, d); break; + case 23: r = handler23(a, b, c, d); break; + case 24: r = handler24(a, b, c, d); break; + case 25: r = handler25(a, b, c, d); break; + case 26: r = handler26(a, b, c, d); break; + case 27: r = handler27(a, b, c, d); break; + case 28: r = handler28(a, b, c, d); break; + case 29: r = handler29(a, b, c, d); break; + case 30: r = handler30(a, b, c, d); break; + case 31: target(a, b); r = 0; break; + default: r = -1; + } + return r; + } + + static int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + static int handler1(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + static int handler2(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + static int handler3(int a, int b, int c, int d) { + int r = 3; + if (a > 3) r += 1; + if (b > 6) r += 2; + if (c < -3) r += 4; + if (d == 8) r += 8; + return r; + } + static int handler4(int a, int b, int c, int d) { + int r = 4; + if (a > 4) r += 1; + if (b > 8) r += 2; + if (c < -4) r += 4; + if (d == 9) r += 8; + return r; + } + static int handler5(int a, int b, int c, int d) { + int r = 5; + if (a > 5) r += 1; + if (b > 10) r += 2; + if (c < -5) r += 4; + if (d == 10) r += 8; + return r; + } + static int handler6(int a, int b, int c, int d) { + int r = 6; + if (a > 6) r += 1; + if (b > 12) r += 2; + if (c < -6) r += 4; + if (d == 11) r += 8; + return r; + } + static int handler7(int a, int b, int c, int d) { + int r = 7; + if (a > 7) r += 1; + if (b > 14) r += 2; + if (c < -7) r += 4; + if (d == 12) r += 8; + return r; + } + static int handler8(int a, int b, int c, int d) { + int r = 8; + if (a > 8) r += 1; + if (b > 16) r += 2; + if (c < -8) r += 4; + if (d == 13) r += 8; + return r; + } + static int handler9(int a, int b, int c, int d) { + int r = 9; + if (a > 9) r += 1; + if (b > 18) r += 2; + if (c < -9) r += 4; + if (d == 14) r += 8; + return r; + } + static int handler10(int a, int b, int c, int d) { + int r = 10; + if (a > 10) r += 1; + if (b > 20) r += 2; + if (c < -10) r += 4; + if (d == 15) r += 8; + return r; + } + static int handler11(int a, int b, int c, int d) { + int r = 11; + if (a > 11) r += 1; + if (b > 22) r += 2; + if (c < -11) r += 4; + if (d == 16) r += 8; + return r; + } + static int handler12(int a, int b, int c, int d) { + int r = 12; + if (a > 12) r += 1; + if (b > 24) r += 2; + if (c < -12) r += 4; + if (d == 17) r += 8; + return r; + } + static int handler13(int a, int b, int c, int d) { + int r = 13; + if (a > 13) r += 1; + if (b > 26) r += 2; + if (c < -13) r += 4; + if (d == 18) r += 8; + return r; + } + static int handler14(int a, int b, int c, int d) { + int r = 14; + if (a > 14) r += 1; + if (b > 28) r += 2; + if (c < -14) r += 4; + if (d == 19) r += 8; + return r; + } + static int handler15(int a, int b, int c, int d) { + int r = 15; + if (a > 15) r += 1; + if (b > 30) r += 2; + if (c < -15) r += 4; + if (d == 20) r += 8; + return r; + } + static int handler16(int a, int b, int c, int d) { + int r = 16; + if (a > 16) r += 1; + if (b > 32) r += 2; + if (c < -16) r += 4; + if (d == 21) r += 8; + return r; + } + static int handler17(int a, int b, int c, int d) { + int r = 17; + if (a > 17) r += 1; + if (b > 34) r += 2; + if (c < -17) r += 4; + if (d == 22) r += 8; + return r; + } + static int handler18(int a, int b, int c, int d) { + int r = 18; + if (a > 18) r += 1; + if (b > 36) r += 2; + if (c < -18) r += 4; + if (d == 23) r += 8; + return r; + } + static int handler19(int a, int b, int c, int d) { + int r = 19; + if (a > 19) r += 1; + if (b > 38) r += 2; + if (c < -19) r += 4; + if (d == 24) r += 8; + return r; + } + static int handler20(int a, int b, int c, int d) { + int r = 20; + if (a > 20) r += 1; + if (b > 40) r += 2; + if (c < -20) r += 4; + if (d == 25) r += 8; + return r; + } + static int handler21(int a, int b, int c, int d) { + int r = 21; + if (a > 21) r += 1; + if (b > 42) r += 2; + if (c < -21) r += 4; + if (d == 26) r += 8; + return r; + } + static int handler22(int a, int b, int c, int d) { + int r = 22; + if (a > 22) r += 1; + if (b > 44) r += 2; + if (c < -22) r += 4; + if (d == 27) r += 8; + return r; + } + static int handler23(int a, int b, int c, int d) { + int r = 23; + if (a > 23) r += 1; + if (b > 46) r += 2; + if (c < -23) r += 4; + if (d == 28) r += 8; + return r; + } + static int handler24(int a, int b, int c, int d) { + int r = 24; + if (a > 24) r += 1; + if (b > 48) r += 2; + if (c < -24) r += 4; + if (d == 29) r += 8; + return r; + } + static int handler25(int a, int b, int c, int d) { + int r = 25; + if (a > 25) r += 1; + if (b > 50) r += 2; + if (c < -25) r += 4; + if (d == 30) r += 8; + return r; + } + static int handler26(int a, int b, int c, int d) { + int r = 26; + if (a > 26) r += 1; + if (b > 52) r += 2; + if (c < -26) r += 4; + if (d == 31) r += 8; + return r; + } + static int handler27(int a, int b, int c, int d) { + int r = 27; + if (a > 27) r += 1; + if (b > 54) r += 2; + if (c < -27) r += 4; + if (d == 32) r += 8; + return r; + } + static int handler28(int a, int b, int c, int d) { + int r = 28; + if (a > 28) r += 1; + if (b > 56) r += 2; + if (c < -28) r += 4; + if (d == 33) r += 8; + return r; + } + static int handler29(int a, int b, int c, int d) { + int r = 29; + if (a > 29) r += 1; + if (b > 58) r += 2; + if (c < -29) r += 4; + if (d == 34) r += 8; + return r; + } + static int handler30(int a, int b, int c, int d) { + int r = 30; + if (a > 30) r += 1; + if (b > 60) r += 2; + if (c < -30) r += 4; + if (d == 35) r += 8; + return r; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n32_true.yml b/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n32_true.yml new file mode 100644 index 00000000..e45c69c0 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n32_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - switch_n32_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n32_true/Main.java b/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n32_true/Main.java new file mode 100644 index 00000000..906edb56 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/dispatcher/switch_n32_true/Main.java @@ -0,0 +1,315 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:31 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// dispatcher/switch: cmd picks one of 32 commands, only the last one (cmd == 31) can reach the assert. +public class Main { + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + int r; + switch (cmd) { + case 0: r = handler0(a, b, c, d); break; + case 1: r = handler1(a, b, c, d); break; + case 2: r = handler2(a, b, c, d); break; + case 3: r = handler3(a, b, c, d); break; + case 4: r = handler4(a, b, c, d); break; + case 5: r = handler5(a, b, c, d); break; + case 6: r = handler6(a, b, c, d); break; + case 7: r = handler7(a, b, c, d); break; + case 8: r = handler8(a, b, c, d); break; + case 9: r = handler9(a, b, c, d); break; + case 10: r = handler10(a, b, c, d); break; + case 11: r = handler11(a, b, c, d); break; + case 12: r = handler12(a, b, c, d); break; + case 13: r = handler13(a, b, c, d); break; + case 14: r = handler14(a, b, c, d); break; + case 15: r = handler15(a, b, c, d); break; + case 16: r = handler16(a, b, c, d); break; + case 17: r = handler17(a, b, c, d); break; + case 18: r = handler18(a, b, c, d); break; + case 19: r = handler19(a, b, c, d); break; + case 20: r = handler20(a, b, c, d); break; + case 21: r = handler21(a, b, c, d); break; + case 22: r = handler22(a, b, c, d); break; + case 23: r = handler23(a, b, c, d); break; + case 24: r = handler24(a, b, c, d); break; + case 25: r = handler25(a, b, c, d); break; + case 26: r = handler26(a, b, c, d); break; + case 27: r = handler27(a, b, c, d); break; + case 28: r = handler28(a, b, c, d); break; + case 29: r = handler29(a, b, c, d); break; + case 30: r = handler30(a, b, c, d); break; + case 31: target(a, b); r = 0; break; + default: r = -1; + } + return r; + } + + static int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + static int handler1(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + static int handler2(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + static int handler3(int a, int b, int c, int d) { + int r = 3; + if (a > 3) r += 1; + if (b > 6) r += 2; + if (c < -3) r += 4; + if (d == 8) r += 8; + return r; + } + static int handler4(int a, int b, int c, int d) { + int r = 4; + if (a > 4) r += 1; + if (b > 8) r += 2; + if (c < -4) r += 4; + if (d == 9) r += 8; + return r; + } + static int handler5(int a, int b, int c, int d) { + int r = 5; + if (a > 5) r += 1; + if (b > 10) r += 2; + if (c < -5) r += 4; + if (d == 10) r += 8; + return r; + } + static int handler6(int a, int b, int c, int d) { + int r = 6; + if (a > 6) r += 1; + if (b > 12) r += 2; + if (c < -6) r += 4; + if (d == 11) r += 8; + return r; + } + static int handler7(int a, int b, int c, int d) { + int r = 7; + if (a > 7) r += 1; + if (b > 14) r += 2; + if (c < -7) r += 4; + if (d == 12) r += 8; + return r; + } + static int handler8(int a, int b, int c, int d) { + int r = 8; + if (a > 8) r += 1; + if (b > 16) r += 2; + if (c < -8) r += 4; + if (d == 13) r += 8; + return r; + } + static int handler9(int a, int b, int c, int d) { + int r = 9; + if (a > 9) r += 1; + if (b > 18) r += 2; + if (c < -9) r += 4; + if (d == 14) r += 8; + return r; + } + static int handler10(int a, int b, int c, int d) { + int r = 10; + if (a > 10) r += 1; + if (b > 20) r += 2; + if (c < -10) r += 4; + if (d == 15) r += 8; + return r; + } + static int handler11(int a, int b, int c, int d) { + int r = 11; + if (a > 11) r += 1; + if (b > 22) r += 2; + if (c < -11) r += 4; + if (d == 16) r += 8; + return r; + } + static int handler12(int a, int b, int c, int d) { + int r = 12; + if (a > 12) r += 1; + if (b > 24) r += 2; + if (c < -12) r += 4; + if (d == 17) r += 8; + return r; + } + static int handler13(int a, int b, int c, int d) { + int r = 13; + if (a > 13) r += 1; + if (b > 26) r += 2; + if (c < -13) r += 4; + if (d == 18) r += 8; + return r; + } + static int handler14(int a, int b, int c, int d) { + int r = 14; + if (a > 14) r += 1; + if (b > 28) r += 2; + if (c < -14) r += 4; + if (d == 19) r += 8; + return r; + } + static int handler15(int a, int b, int c, int d) { + int r = 15; + if (a > 15) r += 1; + if (b > 30) r += 2; + if (c < -15) r += 4; + if (d == 20) r += 8; + return r; + } + static int handler16(int a, int b, int c, int d) { + int r = 16; + if (a > 16) r += 1; + if (b > 32) r += 2; + if (c < -16) r += 4; + if (d == 21) r += 8; + return r; + } + static int handler17(int a, int b, int c, int d) { + int r = 17; + if (a > 17) r += 1; + if (b > 34) r += 2; + if (c < -17) r += 4; + if (d == 22) r += 8; + return r; + } + static int handler18(int a, int b, int c, int d) { + int r = 18; + if (a > 18) r += 1; + if (b > 36) r += 2; + if (c < -18) r += 4; + if (d == 23) r += 8; + return r; + } + static int handler19(int a, int b, int c, int d) { + int r = 19; + if (a > 19) r += 1; + if (b > 38) r += 2; + if (c < -19) r += 4; + if (d == 24) r += 8; + return r; + } + static int handler20(int a, int b, int c, int d) { + int r = 20; + if (a > 20) r += 1; + if (b > 40) r += 2; + if (c < -20) r += 4; + if (d == 25) r += 8; + return r; + } + static int handler21(int a, int b, int c, int d) { + int r = 21; + if (a > 21) r += 1; + if (b > 42) r += 2; + if (c < -21) r += 4; + if (d == 26) r += 8; + return r; + } + static int handler22(int a, int b, int c, int d) { + int r = 22; + if (a > 22) r += 1; + if (b > 44) r += 2; + if (c < -22) r += 4; + if (d == 27) r += 8; + return r; + } + static int handler23(int a, int b, int c, int d) { + int r = 23; + if (a > 23) r += 1; + if (b > 46) r += 2; + if (c < -23) r += 4; + if (d == 28) r += 8; + return r; + } + static int handler24(int a, int b, int c, int d) { + int r = 24; + if (a > 24) r += 1; + if (b > 48) r += 2; + if (c < -24) r += 4; + if (d == 29) r += 8; + return r; + } + static int handler25(int a, int b, int c, int d) { + int r = 25; + if (a > 25) r += 1; + if (b > 50) r += 2; + if (c < -25) r += 4; + if (d == 30) r += 8; + return r; + } + static int handler26(int a, int b, int c, int d) { + int r = 26; + if (a > 26) r += 1; + if (b > 52) r += 2; + if (c < -26) r += 4; + if (d == 31) r += 8; + return r; + } + static int handler27(int a, int b, int c, int d) { + int r = 27; + if (a > 27) r += 1; + if (b > 54) r += 2; + if (c < -27) r += 4; + if (d == 32) r += 8; + return r; + } + static int handler28(int a, int b, int c, int d) { + int r = 28; + if (a > 28) r += 1; + if (b > 56) r += 2; + if (c < -28) r += 4; + if (d == 33) r += 8; + return r; + } + static int handler29(int a, int b, int c, int d) { + int r = 29; + if (a > 29) r += 1; + if (b > 58) r += 2; + if (c < -29) r += 4; + if (d == 34) r += 8; + return r; + } + static int handler30(int a, int b, int c, int d) { + int r = 30; + if (a > 30) r += 1; + if (b > 60) r += 2; + if (c < -30) r += 4; + if (d == 35) r += 8; + return r; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n02_false.yml b/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n02_false.yml new file mode 100644 index 00000000..120694a9 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n02_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - arith_n02_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n02_false/Main.java b/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n02_false/Main.java new file mode 100644 index 00000000..9028b0f8 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n02_false/Main.java @@ -0,0 +1,53 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// error-handling/arith: 2 steps that can fail with ArithmeticException; each catch block logs and returns. +public class Main { + static int logged; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int x0 = Verifier.nondetInt(); + int x1 = Verifier.nondetInt(); + int acc = 0; + try { + acc += step0(x0); + } catch (ArithmeticException e) { + log(0, x0); + return; + } + try { + acc += step1(x1); + } catch (ArithmeticException e) { + log(1, x1); + return; + } + target(a, b); + } + + static int step0(int x) { + return 100 / (x - 0 - 1); + } + static int step1(int x) { + return 100 / (x - 1 - 1); + } + + // Logs a failed step; three branches, no assert. + static void log(int step, int x) { + if (x < -1000) logged += 100; + if (x > 1000) logged += 10; + if (x == step) logged += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n02_true.yml b/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n02_true.yml new file mode 100644 index 00000000..7c09ef44 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n02_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - arith_n02_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n02_true/Main.java b/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n02_true/Main.java new file mode 100644 index 00000000..9268ef49 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n02_true/Main.java @@ -0,0 +1,54 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// error-handling/arith: 2 steps that can fail with ArithmeticException; each catch block logs and returns. +public class Main { + static int logged; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int x0 = Verifier.nondetInt(); + int x1 = Verifier.nondetInt(); + int acc = 0; + try { + acc += step0(x0); + } catch (ArithmeticException e) { + log(0, x0); + return; + } + try { + acc += step1(x1); + } catch (ArithmeticException e) { + log(1, x1); + return; + } + target(a, b); + } + + static int step0(int x) { + return 100 / (x - 0 - 1); + } + static int step1(int x) { + return 100 / (x - 1 - 1); + } + + // Logs a failed step; three branches, no assert. + static void log(int step, int x) { + if (x < -1000) logged += 100; + if (x > 1000) logged += 10; + if (x == step) logged += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n04_false.yml b/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n04_false.yml new file mode 100644 index 00000000..2a957e61 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n04_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - arith_n04_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n04_false/Main.java b/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n04_false/Main.java new file mode 100644 index 00000000..10ce7a74 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n04_false/Main.java @@ -0,0 +1,73 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:2996 int:0 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// error-handling/arith: 4 steps that can fail with ArithmeticException; each catch block logs and returns. +public class Main { + static int logged; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int x0 = Verifier.nondetInt(); + int x1 = Verifier.nondetInt(); + int x2 = Verifier.nondetInt(); + int x3 = Verifier.nondetInt(); + int acc = 0; + try { + acc += step0(x0); + } catch (ArithmeticException e) { + log(0, x0); + return; + } + try { + acc += step1(x1); + } catch (ArithmeticException e) { + log(1, x1); + return; + } + try { + acc += step2(x2); + } catch (ArithmeticException e) { + log(2, x2); + return; + } + try { + acc += step3(x3); + } catch (ArithmeticException e) { + log(3, x3); + return; + } + target(a, b); + } + + static int step0(int x) { + return 100 / (x - 0 - 1); + } + static int step1(int x) { + return 100 / (x - 1 - 1); + } + static int step2(int x) { + return 100 / (x - 2 - 1); + } + static int step3(int x) { + return 100 / (x - 3 - 1); + } + + // Logs a failed step; three branches, no assert. + static void log(int step, int x) { + if (x < -1000) logged += 100; + if (x > 1000) logged += 10; + if (x == step) logged += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n04_true.yml b/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n04_true.yml new file mode 100644 index 00000000..ee2e7064 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n04_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - arith_n04_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n04_true/Main.java b/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n04_true/Main.java new file mode 100644 index 00000000..92a365e8 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n04_true/Main.java @@ -0,0 +1,74 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:1001 int:2996 int:0 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// error-handling/arith: 4 steps that can fail with ArithmeticException; each catch block logs and returns. +public class Main { + static int logged; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int x0 = Verifier.nondetInt(); + int x1 = Verifier.nondetInt(); + int x2 = Verifier.nondetInt(); + int x3 = Verifier.nondetInt(); + int acc = 0; + try { + acc += step0(x0); + } catch (ArithmeticException e) { + log(0, x0); + return; + } + try { + acc += step1(x1); + } catch (ArithmeticException e) { + log(1, x1); + return; + } + try { + acc += step2(x2); + } catch (ArithmeticException e) { + log(2, x2); + return; + } + try { + acc += step3(x3); + } catch (ArithmeticException e) { + log(3, x3); + return; + } + target(a, b); + } + + static int step0(int x) { + return 100 / (x - 0 - 1); + } + static int step1(int x) { + return 100 / (x - 1 - 1); + } + static int step2(int x) { + return 100 / (x - 2 - 1); + } + static int step3(int x) { + return 100 / (x - 3 - 1); + } + + // Logs a failed step; three branches, no assert. + static void log(int step, int x) { + if (x < -1000) logged += 100; + if (x > 1000) logged += 10; + if (x == step) logged += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n08_false.yml b/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n08_false.yml new file mode 100644 index 00000000..eadf3670 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n08_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - arith_n08_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n08_false/Main.java b/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n08_false/Main.java new file mode 100644 index 00000000..9e14a15e --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n08_false/Main.java @@ -0,0 +1,113 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:2996 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// error-handling/arith: 8 steps that can fail with ArithmeticException; each catch block logs and returns. +public class Main { + static int logged; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int x0 = Verifier.nondetInt(); + int x1 = Verifier.nondetInt(); + int x2 = Verifier.nondetInt(); + int x3 = Verifier.nondetInt(); + int x4 = Verifier.nondetInt(); + int x5 = Verifier.nondetInt(); + int x6 = Verifier.nondetInt(); + int x7 = Verifier.nondetInt(); + int acc = 0; + try { + acc += step0(x0); + } catch (ArithmeticException e) { + log(0, x0); + return; + } + try { + acc += step1(x1); + } catch (ArithmeticException e) { + log(1, x1); + return; + } + try { + acc += step2(x2); + } catch (ArithmeticException e) { + log(2, x2); + return; + } + try { + acc += step3(x3); + } catch (ArithmeticException e) { + log(3, x3); + return; + } + try { + acc += step4(x4); + } catch (ArithmeticException e) { + log(4, x4); + return; + } + try { + acc += step5(x5); + } catch (ArithmeticException e) { + log(5, x5); + return; + } + try { + acc += step6(x6); + } catch (ArithmeticException e) { + log(6, x6); + return; + } + try { + acc += step7(x7); + } catch (ArithmeticException e) { + log(7, x7); + return; + } + target(a, b); + } + + static int step0(int x) { + return 100 / (x - 0 - 1); + } + static int step1(int x) { + return 100 / (x - 1 - 1); + } + static int step2(int x) { + return 100 / (x - 2 - 1); + } + static int step3(int x) { + return 100 / (x - 3 - 1); + } + static int step4(int x) { + return 100 / (x - 4 - 1); + } + static int step5(int x) { + return 100 / (x - 5 - 1); + } + static int step6(int x) { + return 100 / (x - 6 - 1); + } + static int step7(int x) { + return 100 / (x - 7 - 1); + } + + // Logs a failed step; three branches, no assert. + static void log(int step, int x) { + if (x < -1000) logged += 100; + if (x > 1000) logged += 10; + if (x == step) logged += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n08_true.yml b/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n08_true.yml new file mode 100644 index 00000000..cd562621 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n08_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - arith_n08_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n08_true/Main.java b/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n08_true/Main.java new file mode 100644 index 00000000..23564234 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n08_true/Main.java @@ -0,0 +1,114 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:1001 int:2996 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// error-handling/arith: 8 steps that can fail with ArithmeticException; each catch block logs and returns. +public class Main { + static int logged; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int x0 = Verifier.nondetInt(); + int x1 = Verifier.nondetInt(); + int x2 = Verifier.nondetInt(); + int x3 = Verifier.nondetInt(); + int x4 = Verifier.nondetInt(); + int x5 = Verifier.nondetInt(); + int x6 = Verifier.nondetInt(); + int x7 = Verifier.nondetInt(); + int acc = 0; + try { + acc += step0(x0); + } catch (ArithmeticException e) { + log(0, x0); + return; + } + try { + acc += step1(x1); + } catch (ArithmeticException e) { + log(1, x1); + return; + } + try { + acc += step2(x2); + } catch (ArithmeticException e) { + log(2, x2); + return; + } + try { + acc += step3(x3); + } catch (ArithmeticException e) { + log(3, x3); + return; + } + try { + acc += step4(x4); + } catch (ArithmeticException e) { + log(4, x4); + return; + } + try { + acc += step5(x5); + } catch (ArithmeticException e) { + log(5, x5); + return; + } + try { + acc += step6(x6); + } catch (ArithmeticException e) { + log(6, x6); + return; + } + try { + acc += step7(x7); + } catch (ArithmeticException e) { + log(7, x7); + return; + } + target(a, b); + } + + static int step0(int x) { + return 100 / (x - 0 - 1); + } + static int step1(int x) { + return 100 / (x - 1 - 1); + } + static int step2(int x) { + return 100 / (x - 2 - 1); + } + static int step3(int x) { + return 100 / (x - 3 - 1); + } + static int step4(int x) { + return 100 / (x - 4 - 1); + } + static int step5(int x) { + return 100 / (x - 5 - 1); + } + static int step6(int x) { + return 100 / (x - 6 - 1); + } + static int step7(int x) { + return 100 / (x - 7 - 1); + } + + // Logs a failed step; three branches, no assert. + static void log(int step, int x) { + if (x < -1000) logged += 100; + if (x > 1000) logged += 10; + if (x == step) logged += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n16_false.yml b/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n16_false.yml new file mode 100644 index 00000000..99cb2aa3 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n16_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - arith_n16_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n16_false/Main.java b/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n16_false/Main.java new file mode 100644 index 00000000..05495cb6 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n16_false/Main.java @@ -0,0 +1,193 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:2996 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// error-handling/arith: 16 steps that can fail with ArithmeticException; each catch block logs and returns. +public class Main { + static int logged; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int x0 = Verifier.nondetInt(); + int x1 = Verifier.nondetInt(); + int x2 = Verifier.nondetInt(); + int x3 = Verifier.nondetInt(); + int x4 = Verifier.nondetInt(); + int x5 = Verifier.nondetInt(); + int x6 = Verifier.nondetInt(); + int x7 = Verifier.nondetInt(); + int x8 = Verifier.nondetInt(); + int x9 = Verifier.nondetInt(); + int x10 = Verifier.nondetInt(); + int x11 = Verifier.nondetInt(); + int x12 = Verifier.nondetInt(); + int x13 = Verifier.nondetInt(); + int x14 = Verifier.nondetInt(); + int x15 = Verifier.nondetInt(); + int acc = 0; + try { + acc += step0(x0); + } catch (ArithmeticException e) { + log(0, x0); + return; + } + try { + acc += step1(x1); + } catch (ArithmeticException e) { + log(1, x1); + return; + } + try { + acc += step2(x2); + } catch (ArithmeticException e) { + log(2, x2); + return; + } + try { + acc += step3(x3); + } catch (ArithmeticException e) { + log(3, x3); + return; + } + try { + acc += step4(x4); + } catch (ArithmeticException e) { + log(4, x4); + return; + } + try { + acc += step5(x5); + } catch (ArithmeticException e) { + log(5, x5); + return; + } + try { + acc += step6(x6); + } catch (ArithmeticException e) { + log(6, x6); + return; + } + try { + acc += step7(x7); + } catch (ArithmeticException e) { + log(7, x7); + return; + } + try { + acc += step8(x8); + } catch (ArithmeticException e) { + log(8, x8); + return; + } + try { + acc += step9(x9); + } catch (ArithmeticException e) { + log(9, x9); + return; + } + try { + acc += step10(x10); + } catch (ArithmeticException e) { + log(10, x10); + return; + } + try { + acc += step11(x11); + } catch (ArithmeticException e) { + log(11, x11); + return; + } + try { + acc += step12(x12); + } catch (ArithmeticException e) { + log(12, x12); + return; + } + try { + acc += step13(x13); + } catch (ArithmeticException e) { + log(13, x13); + return; + } + try { + acc += step14(x14); + } catch (ArithmeticException e) { + log(14, x14); + return; + } + try { + acc += step15(x15); + } catch (ArithmeticException e) { + log(15, x15); + return; + } + target(a, b); + } + + static int step0(int x) { + return 100 / (x - 0 - 1); + } + static int step1(int x) { + return 100 / (x - 1 - 1); + } + static int step2(int x) { + return 100 / (x - 2 - 1); + } + static int step3(int x) { + return 100 / (x - 3 - 1); + } + static int step4(int x) { + return 100 / (x - 4 - 1); + } + static int step5(int x) { + return 100 / (x - 5 - 1); + } + static int step6(int x) { + return 100 / (x - 6 - 1); + } + static int step7(int x) { + return 100 / (x - 7 - 1); + } + static int step8(int x) { + return 100 / (x - 8 - 1); + } + static int step9(int x) { + return 100 / (x - 9 - 1); + } + static int step10(int x) { + return 100 / (x - 10 - 1); + } + static int step11(int x) { + return 100 / (x - 11 - 1); + } + static int step12(int x) { + return 100 / (x - 12 - 1); + } + static int step13(int x) { + return 100 / (x - 13 - 1); + } + static int step14(int x) { + return 100 / (x - 14 - 1); + } + static int step15(int x) { + return 100 / (x - 15 - 1); + } + + // Logs a failed step; three branches, no assert. + static void log(int step, int x) { + if (x < -1000) logged += 100; + if (x > 1000) logged += 10; + if (x == step) logged += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n16_true.yml b/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n16_true.yml new file mode 100644 index 00000000..91ac3774 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n16_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - arith_n16_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n16_true/Main.java b/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n16_true/Main.java new file mode 100644 index 00000000..b5cd2cac --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n16_true/Main.java @@ -0,0 +1,194 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:1001 int:2996 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// error-handling/arith: 16 steps that can fail with ArithmeticException; each catch block logs and returns. +public class Main { + static int logged; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int x0 = Verifier.nondetInt(); + int x1 = Verifier.nondetInt(); + int x2 = Verifier.nondetInt(); + int x3 = Verifier.nondetInt(); + int x4 = Verifier.nondetInt(); + int x5 = Verifier.nondetInt(); + int x6 = Verifier.nondetInt(); + int x7 = Verifier.nondetInt(); + int x8 = Verifier.nondetInt(); + int x9 = Verifier.nondetInt(); + int x10 = Verifier.nondetInt(); + int x11 = Verifier.nondetInt(); + int x12 = Verifier.nondetInt(); + int x13 = Verifier.nondetInt(); + int x14 = Verifier.nondetInt(); + int x15 = Verifier.nondetInt(); + int acc = 0; + try { + acc += step0(x0); + } catch (ArithmeticException e) { + log(0, x0); + return; + } + try { + acc += step1(x1); + } catch (ArithmeticException e) { + log(1, x1); + return; + } + try { + acc += step2(x2); + } catch (ArithmeticException e) { + log(2, x2); + return; + } + try { + acc += step3(x3); + } catch (ArithmeticException e) { + log(3, x3); + return; + } + try { + acc += step4(x4); + } catch (ArithmeticException e) { + log(4, x4); + return; + } + try { + acc += step5(x5); + } catch (ArithmeticException e) { + log(5, x5); + return; + } + try { + acc += step6(x6); + } catch (ArithmeticException e) { + log(6, x6); + return; + } + try { + acc += step7(x7); + } catch (ArithmeticException e) { + log(7, x7); + return; + } + try { + acc += step8(x8); + } catch (ArithmeticException e) { + log(8, x8); + return; + } + try { + acc += step9(x9); + } catch (ArithmeticException e) { + log(9, x9); + return; + } + try { + acc += step10(x10); + } catch (ArithmeticException e) { + log(10, x10); + return; + } + try { + acc += step11(x11); + } catch (ArithmeticException e) { + log(11, x11); + return; + } + try { + acc += step12(x12); + } catch (ArithmeticException e) { + log(12, x12); + return; + } + try { + acc += step13(x13); + } catch (ArithmeticException e) { + log(13, x13); + return; + } + try { + acc += step14(x14); + } catch (ArithmeticException e) { + log(14, x14); + return; + } + try { + acc += step15(x15); + } catch (ArithmeticException e) { + log(15, x15); + return; + } + target(a, b); + } + + static int step0(int x) { + return 100 / (x - 0 - 1); + } + static int step1(int x) { + return 100 / (x - 1 - 1); + } + static int step2(int x) { + return 100 / (x - 2 - 1); + } + static int step3(int x) { + return 100 / (x - 3 - 1); + } + static int step4(int x) { + return 100 / (x - 4 - 1); + } + static int step5(int x) { + return 100 / (x - 5 - 1); + } + static int step6(int x) { + return 100 / (x - 6 - 1); + } + static int step7(int x) { + return 100 / (x - 7 - 1); + } + static int step8(int x) { + return 100 / (x - 8 - 1); + } + static int step9(int x) { + return 100 / (x - 9 - 1); + } + static int step10(int x) { + return 100 / (x - 10 - 1); + } + static int step11(int x) { + return 100 / (x - 11 - 1); + } + static int step12(int x) { + return 100 / (x - 12 - 1); + } + static int step13(int x) { + return 100 / (x - 13 - 1); + } + static int step14(int x) { + return 100 / (x - 14 - 1); + } + static int step15(int x) { + return 100 / (x - 15 - 1); + } + + // Logs a failed step; three branches, no assert. + static void log(int step, int x) { + if (x < -1000) logged += 100; + if (x > 1000) logged += 10; + if (x == step) logged += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n32_false.yml b/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n32_false.yml new file mode 100644 index 00000000..6eb96781 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n32_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - arith_n32_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n32_false/Main.java b/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n32_false/Main.java new file mode 100644 index 00000000..f23c358a --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n32_false/Main.java @@ -0,0 +1,353 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:2996 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// error-handling/arith: 32 steps that can fail with ArithmeticException; each catch block logs and returns. +public class Main { + static int logged; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int x0 = Verifier.nondetInt(); + int x1 = Verifier.nondetInt(); + int x2 = Verifier.nondetInt(); + int x3 = Verifier.nondetInt(); + int x4 = Verifier.nondetInt(); + int x5 = Verifier.nondetInt(); + int x6 = Verifier.nondetInt(); + int x7 = Verifier.nondetInt(); + int x8 = Verifier.nondetInt(); + int x9 = Verifier.nondetInt(); + int x10 = Verifier.nondetInt(); + int x11 = Verifier.nondetInt(); + int x12 = Verifier.nondetInt(); + int x13 = Verifier.nondetInt(); + int x14 = Verifier.nondetInt(); + int x15 = Verifier.nondetInt(); + int x16 = Verifier.nondetInt(); + int x17 = Verifier.nondetInt(); + int x18 = Verifier.nondetInt(); + int x19 = Verifier.nondetInt(); + int x20 = Verifier.nondetInt(); + int x21 = Verifier.nondetInt(); + int x22 = Verifier.nondetInt(); + int x23 = Verifier.nondetInt(); + int x24 = Verifier.nondetInt(); + int x25 = Verifier.nondetInt(); + int x26 = Verifier.nondetInt(); + int x27 = Verifier.nondetInt(); + int x28 = Verifier.nondetInt(); + int x29 = Verifier.nondetInt(); + int x30 = Verifier.nondetInt(); + int x31 = Verifier.nondetInt(); + int acc = 0; + try { + acc += step0(x0); + } catch (ArithmeticException e) { + log(0, x0); + return; + } + try { + acc += step1(x1); + } catch (ArithmeticException e) { + log(1, x1); + return; + } + try { + acc += step2(x2); + } catch (ArithmeticException e) { + log(2, x2); + return; + } + try { + acc += step3(x3); + } catch (ArithmeticException e) { + log(3, x3); + return; + } + try { + acc += step4(x4); + } catch (ArithmeticException e) { + log(4, x4); + return; + } + try { + acc += step5(x5); + } catch (ArithmeticException e) { + log(5, x5); + return; + } + try { + acc += step6(x6); + } catch (ArithmeticException e) { + log(6, x6); + return; + } + try { + acc += step7(x7); + } catch (ArithmeticException e) { + log(7, x7); + return; + } + try { + acc += step8(x8); + } catch (ArithmeticException e) { + log(8, x8); + return; + } + try { + acc += step9(x9); + } catch (ArithmeticException e) { + log(9, x9); + return; + } + try { + acc += step10(x10); + } catch (ArithmeticException e) { + log(10, x10); + return; + } + try { + acc += step11(x11); + } catch (ArithmeticException e) { + log(11, x11); + return; + } + try { + acc += step12(x12); + } catch (ArithmeticException e) { + log(12, x12); + return; + } + try { + acc += step13(x13); + } catch (ArithmeticException e) { + log(13, x13); + return; + } + try { + acc += step14(x14); + } catch (ArithmeticException e) { + log(14, x14); + return; + } + try { + acc += step15(x15); + } catch (ArithmeticException e) { + log(15, x15); + return; + } + try { + acc += step16(x16); + } catch (ArithmeticException e) { + log(16, x16); + return; + } + try { + acc += step17(x17); + } catch (ArithmeticException e) { + log(17, x17); + return; + } + try { + acc += step18(x18); + } catch (ArithmeticException e) { + log(18, x18); + return; + } + try { + acc += step19(x19); + } catch (ArithmeticException e) { + log(19, x19); + return; + } + try { + acc += step20(x20); + } catch (ArithmeticException e) { + log(20, x20); + return; + } + try { + acc += step21(x21); + } catch (ArithmeticException e) { + log(21, x21); + return; + } + try { + acc += step22(x22); + } catch (ArithmeticException e) { + log(22, x22); + return; + } + try { + acc += step23(x23); + } catch (ArithmeticException e) { + log(23, x23); + return; + } + try { + acc += step24(x24); + } catch (ArithmeticException e) { + log(24, x24); + return; + } + try { + acc += step25(x25); + } catch (ArithmeticException e) { + log(25, x25); + return; + } + try { + acc += step26(x26); + } catch (ArithmeticException e) { + log(26, x26); + return; + } + try { + acc += step27(x27); + } catch (ArithmeticException e) { + log(27, x27); + return; + } + try { + acc += step28(x28); + } catch (ArithmeticException e) { + log(28, x28); + return; + } + try { + acc += step29(x29); + } catch (ArithmeticException e) { + log(29, x29); + return; + } + try { + acc += step30(x30); + } catch (ArithmeticException e) { + log(30, x30); + return; + } + try { + acc += step31(x31); + } catch (ArithmeticException e) { + log(31, x31); + return; + } + target(a, b); + } + + static int step0(int x) { + return 100 / (x - 0 - 1); + } + static int step1(int x) { + return 100 / (x - 1 - 1); + } + static int step2(int x) { + return 100 / (x - 2 - 1); + } + static int step3(int x) { + return 100 / (x - 3 - 1); + } + static int step4(int x) { + return 100 / (x - 4 - 1); + } + static int step5(int x) { + return 100 / (x - 5 - 1); + } + static int step6(int x) { + return 100 / (x - 6 - 1); + } + static int step7(int x) { + return 100 / (x - 7 - 1); + } + static int step8(int x) { + return 100 / (x - 8 - 1); + } + static int step9(int x) { + return 100 / (x - 9 - 1); + } + static int step10(int x) { + return 100 / (x - 10 - 1); + } + static int step11(int x) { + return 100 / (x - 11 - 1); + } + static int step12(int x) { + return 100 / (x - 12 - 1); + } + static int step13(int x) { + return 100 / (x - 13 - 1); + } + static int step14(int x) { + return 100 / (x - 14 - 1); + } + static int step15(int x) { + return 100 / (x - 15 - 1); + } + static int step16(int x) { + return 100 / (x - 16 - 1); + } + static int step17(int x) { + return 100 / (x - 17 - 1); + } + static int step18(int x) { + return 100 / (x - 18 - 1); + } + static int step19(int x) { + return 100 / (x - 19 - 1); + } + static int step20(int x) { + return 100 / (x - 20 - 1); + } + static int step21(int x) { + return 100 / (x - 21 - 1); + } + static int step22(int x) { + return 100 / (x - 22 - 1); + } + static int step23(int x) { + return 100 / (x - 23 - 1); + } + static int step24(int x) { + return 100 / (x - 24 - 1); + } + static int step25(int x) { + return 100 / (x - 25 - 1); + } + static int step26(int x) { + return 100 / (x - 26 - 1); + } + static int step27(int x) { + return 100 / (x - 27 - 1); + } + static int step28(int x) { + return 100 / (x - 28 - 1); + } + static int step29(int x) { + return 100 / (x - 29 - 1); + } + static int step30(int x) { + return 100 / (x - 30 - 1); + } + static int step31(int x) { + return 100 / (x - 31 - 1); + } + + // Logs a failed step; three branches, no assert. + static void log(int step, int x) { + if (x < -1000) logged += 100; + if (x > 1000) logged += 10; + if (x == step) logged += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n32_true.yml b/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n32_true.yml new file mode 100644 index 00000000..4e6567b5 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n32_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - arith_n32_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n32_true/Main.java b/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n32_true/Main.java new file mode 100644 index 00000000..77ef7ce5 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/arith_n32_true/Main.java @@ -0,0 +1,354 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:1001 int:2996 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// error-handling/arith: 32 steps that can fail with ArithmeticException; each catch block logs and returns. +public class Main { + static int logged; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int x0 = Verifier.nondetInt(); + int x1 = Verifier.nondetInt(); + int x2 = Verifier.nondetInt(); + int x3 = Verifier.nondetInt(); + int x4 = Verifier.nondetInt(); + int x5 = Verifier.nondetInt(); + int x6 = Verifier.nondetInt(); + int x7 = Verifier.nondetInt(); + int x8 = Verifier.nondetInt(); + int x9 = Verifier.nondetInt(); + int x10 = Verifier.nondetInt(); + int x11 = Verifier.nondetInt(); + int x12 = Verifier.nondetInt(); + int x13 = Verifier.nondetInt(); + int x14 = Verifier.nondetInt(); + int x15 = Verifier.nondetInt(); + int x16 = Verifier.nondetInt(); + int x17 = Verifier.nondetInt(); + int x18 = Verifier.nondetInt(); + int x19 = Verifier.nondetInt(); + int x20 = Verifier.nondetInt(); + int x21 = Verifier.nondetInt(); + int x22 = Verifier.nondetInt(); + int x23 = Verifier.nondetInt(); + int x24 = Verifier.nondetInt(); + int x25 = Verifier.nondetInt(); + int x26 = Verifier.nondetInt(); + int x27 = Verifier.nondetInt(); + int x28 = Verifier.nondetInt(); + int x29 = Verifier.nondetInt(); + int x30 = Verifier.nondetInt(); + int x31 = Verifier.nondetInt(); + int acc = 0; + try { + acc += step0(x0); + } catch (ArithmeticException e) { + log(0, x0); + return; + } + try { + acc += step1(x1); + } catch (ArithmeticException e) { + log(1, x1); + return; + } + try { + acc += step2(x2); + } catch (ArithmeticException e) { + log(2, x2); + return; + } + try { + acc += step3(x3); + } catch (ArithmeticException e) { + log(3, x3); + return; + } + try { + acc += step4(x4); + } catch (ArithmeticException e) { + log(4, x4); + return; + } + try { + acc += step5(x5); + } catch (ArithmeticException e) { + log(5, x5); + return; + } + try { + acc += step6(x6); + } catch (ArithmeticException e) { + log(6, x6); + return; + } + try { + acc += step7(x7); + } catch (ArithmeticException e) { + log(7, x7); + return; + } + try { + acc += step8(x8); + } catch (ArithmeticException e) { + log(8, x8); + return; + } + try { + acc += step9(x9); + } catch (ArithmeticException e) { + log(9, x9); + return; + } + try { + acc += step10(x10); + } catch (ArithmeticException e) { + log(10, x10); + return; + } + try { + acc += step11(x11); + } catch (ArithmeticException e) { + log(11, x11); + return; + } + try { + acc += step12(x12); + } catch (ArithmeticException e) { + log(12, x12); + return; + } + try { + acc += step13(x13); + } catch (ArithmeticException e) { + log(13, x13); + return; + } + try { + acc += step14(x14); + } catch (ArithmeticException e) { + log(14, x14); + return; + } + try { + acc += step15(x15); + } catch (ArithmeticException e) { + log(15, x15); + return; + } + try { + acc += step16(x16); + } catch (ArithmeticException e) { + log(16, x16); + return; + } + try { + acc += step17(x17); + } catch (ArithmeticException e) { + log(17, x17); + return; + } + try { + acc += step18(x18); + } catch (ArithmeticException e) { + log(18, x18); + return; + } + try { + acc += step19(x19); + } catch (ArithmeticException e) { + log(19, x19); + return; + } + try { + acc += step20(x20); + } catch (ArithmeticException e) { + log(20, x20); + return; + } + try { + acc += step21(x21); + } catch (ArithmeticException e) { + log(21, x21); + return; + } + try { + acc += step22(x22); + } catch (ArithmeticException e) { + log(22, x22); + return; + } + try { + acc += step23(x23); + } catch (ArithmeticException e) { + log(23, x23); + return; + } + try { + acc += step24(x24); + } catch (ArithmeticException e) { + log(24, x24); + return; + } + try { + acc += step25(x25); + } catch (ArithmeticException e) { + log(25, x25); + return; + } + try { + acc += step26(x26); + } catch (ArithmeticException e) { + log(26, x26); + return; + } + try { + acc += step27(x27); + } catch (ArithmeticException e) { + log(27, x27); + return; + } + try { + acc += step28(x28); + } catch (ArithmeticException e) { + log(28, x28); + return; + } + try { + acc += step29(x29); + } catch (ArithmeticException e) { + log(29, x29); + return; + } + try { + acc += step30(x30); + } catch (ArithmeticException e) { + log(30, x30); + return; + } + try { + acc += step31(x31); + } catch (ArithmeticException e) { + log(31, x31); + return; + } + target(a, b); + } + + static int step0(int x) { + return 100 / (x - 0 - 1); + } + static int step1(int x) { + return 100 / (x - 1 - 1); + } + static int step2(int x) { + return 100 / (x - 2 - 1); + } + static int step3(int x) { + return 100 / (x - 3 - 1); + } + static int step4(int x) { + return 100 / (x - 4 - 1); + } + static int step5(int x) { + return 100 / (x - 5 - 1); + } + static int step6(int x) { + return 100 / (x - 6 - 1); + } + static int step7(int x) { + return 100 / (x - 7 - 1); + } + static int step8(int x) { + return 100 / (x - 8 - 1); + } + static int step9(int x) { + return 100 / (x - 9 - 1); + } + static int step10(int x) { + return 100 / (x - 10 - 1); + } + static int step11(int x) { + return 100 / (x - 11 - 1); + } + static int step12(int x) { + return 100 / (x - 12 - 1); + } + static int step13(int x) { + return 100 / (x - 13 - 1); + } + static int step14(int x) { + return 100 / (x - 14 - 1); + } + static int step15(int x) { + return 100 / (x - 15 - 1); + } + static int step16(int x) { + return 100 / (x - 16 - 1); + } + static int step17(int x) { + return 100 / (x - 17 - 1); + } + static int step18(int x) { + return 100 / (x - 18 - 1); + } + static int step19(int x) { + return 100 / (x - 19 - 1); + } + static int step20(int x) { + return 100 / (x - 20 - 1); + } + static int step21(int x) { + return 100 / (x - 21 - 1); + } + static int step22(int x) { + return 100 / (x - 22 - 1); + } + static int step23(int x) { + return 100 / (x - 23 - 1); + } + static int step24(int x) { + return 100 / (x - 24 - 1); + } + static int step25(int x) { + return 100 / (x - 25 - 1); + } + static int step26(int x) { + return 100 / (x - 26 - 1); + } + static int step27(int x) { + return 100 / (x - 27 - 1); + } + static int step28(int x) { + return 100 / (x - 28 - 1); + } + static int step29(int x) { + return 100 / (x - 29 - 1); + } + static int step30(int x) { + return 100 / (x - 30 - 1); + } + static int step31(int x) { + return 100 / (x - 31 - 1); + } + + // Logs a failed step; three branches, no assert. + static void log(int step, int x) { + if (x < -1000) logged += 100; + if (x > 1000) logged += 10; + if (x == step) logged += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n02_false.yml b/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n02_false.yml new file mode 100644 index 00000000..95811d6d --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n02_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - bounds_n02_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n02_false/Main.java b/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n02_false/Main.java new file mode 100644 index 00000000..7d54ac7e --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n02_false/Main.java @@ -0,0 +1,55 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// error-handling/bounds: 2 steps that can fail with ArrayIndexOutOfBoundsException; each catch block logs and returns. +public class Main { + static final int[] TABLE = {3, 1, 4, 1}; + + static int logged; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int x0 = Verifier.nondetInt(); + int x1 = Verifier.nondetInt(); + int acc = 0; + try { + acc += step0(x0); + } catch (ArrayIndexOutOfBoundsException e) { + log(0, x0); + return; + } + try { + acc += step1(x1); + } catch (ArrayIndexOutOfBoundsException e) { + log(1, x1); + return; + } + target(a, b); + } + + static int step0(int x) { + return TABLE[x] + 0; + } + static int step1(int x) { + return TABLE[x] + 1; + } + + // Logs a failed step; three branches, no assert. + static void log(int step, int x) { + if (x < -1000) logged += 100; + if (x > 1000) logged += 10; + if (x == step) logged += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n02_true.yml b/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n02_true.yml new file mode 100644 index 00000000..4575a833 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n02_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - bounds_n02_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n02_true/Main.java b/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n02_true/Main.java new file mode 100644 index 00000000..621daf2b --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n02_true/Main.java @@ -0,0 +1,56 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// error-handling/bounds: 2 steps that can fail with ArrayIndexOutOfBoundsException; each catch block logs and returns. +public class Main { + static final int[] TABLE = {3, 1, 4, 1}; + + static int logged; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int x0 = Verifier.nondetInt(); + int x1 = Verifier.nondetInt(); + int acc = 0; + try { + acc += step0(x0); + } catch (ArrayIndexOutOfBoundsException e) { + log(0, x0); + return; + } + try { + acc += step1(x1); + } catch (ArrayIndexOutOfBoundsException e) { + log(1, x1); + return; + } + target(a, b); + } + + static int step0(int x) { + return TABLE[x] + 0; + } + static int step1(int x) { + return TABLE[x] + 1; + } + + // Logs a failed step; three branches, no assert. + static void log(int step, int x) { + if (x < -1000) logged += 100; + if (x > 1000) logged += 10; + if (x == step) logged += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n04_false.yml b/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n04_false.yml new file mode 100644 index 00000000..15ee8390 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n04_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - bounds_n04_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n04_false/Main.java b/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n04_false/Main.java new file mode 100644 index 00000000..d187c304 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n04_false/Main.java @@ -0,0 +1,75 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:2996 int:0 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// error-handling/bounds: 4 steps that can fail with ArrayIndexOutOfBoundsException; each catch block logs and returns. +public class Main { + static final int[] TABLE = {3, 1, 4, 1}; + + static int logged; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int x0 = Verifier.nondetInt(); + int x1 = Verifier.nondetInt(); + int x2 = Verifier.nondetInt(); + int x3 = Verifier.nondetInt(); + int acc = 0; + try { + acc += step0(x0); + } catch (ArrayIndexOutOfBoundsException e) { + log(0, x0); + return; + } + try { + acc += step1(x1); + } catch (ArrayIndexOutOfBoundsException e) { + log(1, x1); + return; + } + try { + acc += step2(x2); + } catch (ArrayIndexOutOfBoundsException e) { + log(2, x2); + return; + } + try { + acc += step3(x3); + } catch (ArrayIndexOutOfBoundsException e) { + log(3, x3); + return; + } + target(a, b); + } + + static int step0(int x) { + return TABLE[x] + 0; + } + static int step1(int x) { + return TABLE[x] + 1; + } + static int step2(int x) { + return TABLE[x] + 2; + } + static int step3(int x) { + return TABLE[x] + 3; + } + + // Logs a failed step; three branches, no assert. + static void log(int step, int x) { + if (x < -1000) logged += 100; + if (x > 1000) logged += 10; + if (x == step) logged += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n04_true.yml b/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n04_true.yml new file mode 100644 index 00000000..86476d5f --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n04_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - bounds_n04_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n04_true/Main.java b/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n04_true/Main.java new file mode 100644 index 00000000..cf3e361c --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n04_true/Main.java @@ -0,0 +1,76 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:1001 int:2996 int:0 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// error-handling/bounds: 4 steps that can fail with ArrayIndexOutOfBoundsException; each catch block logs and returns. +public class Main { + static final int[] TABLE = {3, 1, 4, 1}; + + static int logged; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int x0 = Verifier.nondetInt(); + int x1 = Verifier.nondetInt(); + int x2 = Verifier.nondetInt(); + int x3 = Verifier.nondetInt(); + int acc = 0; + try { + acc += step0(x0); + } catch (ArrayIndexOutOfBoundsException e) { + log(0, x0); + return; + } + try { + acc += step1(x1); + } catch (ArrayIndexOutOfBoundsException e) { + log(1, x1); + return; + } + try { + acc += step2(x2); + } catch (ArrayIndexOutOfBoundsException e) { + log(2, x2); + return; + } + try { + acc += step3(x3); + } catch (ArrayIndexOutOfBoundsException e) { + log(3, x3); + return; + } + target(a, b); + } + + static int step0(int x) { + return TABLE[x] + 0; + } + static int step1(int x) { + return TABLE[x] + 1; + } + static int step2(int x) { + return TABLE[x] + 2; + } + static int step3(int x) { + return TABLE[x] + 3; + } + + // Logs a failed step; three branches, no assert. + static void log(int step, int x) { + if (x < -1000) logged += 100; + if (x > 1000) logged += 10; + if (x == step) logged += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n08_false.yml b/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n08_false.yml new file mode 100644 index 00000000..cc68dcff --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n08_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - bounds_n08_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n08_false/Main.java b/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n08_false/Main.java new file mode 100644 index 00000000..94781478 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n08_false/Main.java @@ -0,0 +1,115 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:2996 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// error-handling/bounds: 8 steps that can fail with ArrayIndexOutOfBoundsException; each catch block logs and returns. +public class Main { + static final int[] TABLE = {3, 1, 4, 1}; + + static int logged; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int x0 = Verifier.nondetInt(); + int x1 = Verifier.nondetInt(); + int x2 = Verifier.nondetInt(); + int x3 = Verifier.nondetInt(); + int x4 = Verifier.nondetInt(); + int x5 = Verifier.nondetInt(); + int x6 = Verifier.nondetInt(); + int x7 = Verifier.nondetInt(); + int acc = 0; + try { + acc += step0(x0); + } catch (ArrayIndexOutOfBoundsException e) { + log(0, x0); + return; + } + try { + acc += step1(x1); + } catch (ArrayIndexOutOfBoundsException e) { + log(1, x1); + return; + } + try { + acc += step2(x2); + } catch (ArrayIndexOutOfBoundsException e) { + log(2, x2); + return; + } + try { + acc += step3(x3); + } catch (ArrayIndexOutOfBoundsException e) { + log(3, x3); + return; + } + try { + acc += step4(x4); + } catch (ArrayIndexOutOfBoundsException e) { + log(4, x4); + return; + } + try { + acc += step5(x5); + } catch (ArrayIndexOutOfBoundsException e) { + log(5, x5); + return; + } + try { + acc += step6(x6); + } catch (ArrayIndexOutOfBoundsException e) { + log(6, x6); + return; + } + try { + acc += step7(x7); + } catch (ArrayIndexOutOfBoundsException e) { + log(7, x7); + return; + } + target(a, b); + } + + static int step0(int x) { + return TABLE[x] + 0; + } + static int step1(int x) { + return TABLE[x] + 1; + } + static int step2(int x) { + return TABLE[x] + 2; + } + static int step3(int x) { + return TABLE[x] + 3; + } + static int step4(int x) { + return TABLE[x] + 4; + } + static int step5(int x) { + return TABLE[x] + 5; + } + static int step6(int x) { + return TABLE[x] + 6; + } + static int step7(int x) { + return TABLE[x] + 7; + } + + // Logs a failed step; three branches, no assert. + static void log(int step, int x) { + if (x < -1000) logged += 100; + if (x > 1000) logged += 10; + if (x == step) logged += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n08_true.yml b/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n08_true.yml new file mode 100644 index 00000000..3a9f144f --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n08_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - bounds_n08_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n08_true/Main.java b/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n08_true/Main.java new file mode 100644 index 00000000..65f84c5c --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n08_true/Main.java @@ -0,0 +1,116 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:1001 int:2996 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// error-handling/bounds: 8 steps that can fail with ArrayIndexOutOfBoundsException; each catch block logs and returns. +public class Main { + static final int[] TABLE = {3, 1, 4, 1}; + + static int logged; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int x0 = Verifier.nondetInt(); + int x1 = Verifier.nondetInt(); + int x2 = Verifier.nondetInt(); + int x3 = Verifier.nondetInt(); + int x4 = Verifier.nondetInt(); + int x5 = Verifier.nondetInt(); + int x6 = Verifier.nondetInt(); + int x7 = Verifier.nondetInt(); + int acc = 0; + try { + acc += step0(x0); + } catch (ArrayIndexOutOfBoundsException e) { + log(0, x0); + return; + } + try { + acc += step1(x1); + } catch (ArrayIndexOutOfBoundsException e) { + log(1, x1); + return; + } + try { + acc += step2(x2); + } catch (ArrayIndexOutOfBoundsException e) { + log(2, x2); + return; + } + try { + acc += step3(x3); + } catch (ArrayIndexOutOfBoundsException e) { + log(3, x3); + return; + } + try { + acc += step4(x4); + } catch (ArrayIndexOutOfBoundsException e) { + log(4, x4); + return; + } + try { + acc += step5(x5); + } catch (ArrayIndexOutOfBoundsException e) { + log(5, x5); + return; + } + try { + acc += step6(x6); + } catch (ArrayIndexOutOfBoundsException e) { + log(6, x6); + return; + } + try { + acc += step7(x7); + } catch (ArrayIndexOutOfBoundsException e) { + log(7, x7); + return; + } + target(a, b); + } + + static int step0(int x) { + return TABLE[x] + 0; + } + static int step1(int x) { + return TABLE[x] + 1; + } + static int step2(int x) { + return TABLE[x] + 2; + } + static int step3(int x) { + return TABLE[x] + 3; + } + static int step4(int x) { + return TABLE[x] + 4; + } + static int step5(int x) { + return TABLE[x] + 5; + } + static int step6(int x) { + return TABLE[x] + 6; + } + static int step7(int x) { + return TABLE[x] + 7; + } + + // Logs a failed step; three branches, no assert. + static void log(int step, int x) { + if (x < -1000) logged += 100; + if (x > 1000) logged += 10; + if (x == step) logged += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n16_false.yml b/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n16_false.yml new file mode 100644 index 00000000..e2a55499 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n16_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - bounds_n16_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n16_false/Main.java b/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n16_false/Main.java new file mode 100644 index 00000000..cceb11ad --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n16_false/Main.java @@ -0,0 +1,195 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:2996 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// error-handling/bounds: 16 steps that can fail with ArrayIndexOutOfBoundsException; each catch block logs and returns. +public class Main { + static final int[] TABLE = {3, 1, 4, 1}; + + static int logged; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int x0 = Verifier.nondetInt(); + int x1 = Verifier.nondetInt(); + int x2 = Verifier.nondetInt(); + int x3 = Verifier.nondetInt(); + int x4 = Verifier.nondetInt(); + int x5 = Verifier.nondetInt(); + int x6 = Verifier.nondetInt(); + int x7 = Verifier.nondetInt(); + int x8 = Verifier.nondetInt(); + int x9 = Verifier.nondetInt(); + int x10 = Verifier.nondetInt(); + int x11 = Verifier.nondetInt(); + int x12 = Verifier.nondetInt(); + int x13 = Verifier.nondetInt(); + int x14 = Verifier.nondetInt(); + int x15 = Verifier.nondetInt(); + int acc = 0; + try { + acc += step0(x0); + } catch (ArrayIndexOutOfBoundsException e) { + log(0, x0); + return; + } + try { + acc += step1(x1); + } catch (ArrayIndexOutOfBoundsException e) { + log(1, x1); + return; + } + try { + acc += step2(x2); + } catch (ArrayIndexOutOfBoundsException e) { + log(2, x2); + return; + } + try { + acc += step3(x3); + } catch (ArrayIndexOutOfBoundsException e) { + log(3, x3); + return; + } + try { + acc += step4(x4); + } catch (ArrayIndexOutOfBoundsException e) { + log(4, x4); + return; + } + try { + acc += step5(x5); + } catch (ArrayIndexOutOfBoundsException e) { + log(5, x5); + return; + } + try { + acc += step6(x6); + } catch (ArrayIndexOutOfBoundsException e) { + log(6, x6); + return; + } + try { + acc += step7(x7); + } catch (ArrayIndexOutOfBoundsException e) { + log(7, x7); + return; + } + try { + acc += step8(x8); + } catch (ArrayIndexOutOfBoundsException e) { + log(8, x8); + return; + } + try { + acc += step9(x9); + } catch (ArrayIndexOutOfBoundsException e) { + log(9, x9); + return; + } + try { + acc += step10(x10); + } catch (ArrayIndexOutOfBoundsException e) { + log(10, x10); + return; + } + try { + acc += step11(x11); + } catch (ArrayIndexOutOfBoundsException e) { + log(11, x11); + return; + } + try { + acc += step12(x12); + } catch (ArrayIndexOutOfBoundsException e) { + log(12, x12); + return; + } + try { + acc += step13(x13); + } catch (ArrayIndexOutOfBoundsException e) { + log(13, x13); + return; + } + try { + acc += step14(x14); + } catch (ArrayIndexOutOfBoundsException e) { + log(14, x14); + return; + } + try { + acc += step15(x15); + } catch (ArrayIndexOutOfBoundsException e) { + log(15, x15); + return; + } + target(a, b); + } + + static int step0(int x) { + return TABLE[x] + 0; + } + static int step1(int x) { + return TABLE[x] + 1; + } + static int step2(int x) { + return TABLE[x] + 2; + } + static int step3(int x) { + return TABLE[x] + 3; + } + static int step4(int x) { + return TABLE[x] + 4; + } + static int step5(int x) { + return TABLE[x] + 5; + } + static int step6(int x) { + return TABLE[x] + 6; + } + static int step7(int x) { + return TABLE[x] + 7; + } + static int step8(int x) { + return TABLE[x] + 8; + } + static int step9(int x) { + return TABLE[x] + 9; + } + static int step10(int x) { + return TABLE[x] + 10; + } + static int step11(int x) { + return TABLE[x] + 11; + } + static int step12(int x) { + return TABLE[x] + 12; + } + static int step13(int x) { + return TABLE[x] + 13; + } + static int step14(int x) { + return TABLE[x] + 14; + } + static int step15(int x) { + return TABLE[x] + 15; + } + + // Logs a failed step; three branches, no assert. + static void log(int step, int x) { + if (x < -1000) logged += 100; + if (x > 1000) logged += 10; + if (x == step) logged += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n16_true.yml b/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n16_true.yml new file mode 100644 index 00000000..3b0e8486 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n16_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - bounds_n16_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n16_true/Main.java b/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n16_true/Main.java new file mode 100644 index 00000000..eb82206a --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n16_true/Main.java @@ -0,0 +1,196 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:1001 int:2996 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// error-handling/bounds: 16 steps that can fail with ArrayIndexOutOfBoundsException; each catch block logs and returns. +public class Main { + static final int[] TABLE = {3, 1, 4, 1}; + + static int logged; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int x0 = Verifier.nondetInt(); + int x1 = Verifier.nondetInt(); + int x2 = Verifier.nondetInt(); + int x3 = Verifier.nondetInt(); + int x4 = Verifier.nondetInt(); + int x5 = Verifier.nondetInt(); + int x6 = Verifier.nondetInt(); + int x7 = Verifier.nondetInt(); + int x8 = Verifier.nondetInt(); + int x9 = Verifier.nondetInt(); + int x10 = Verifier.nondetInt(); + int x11 = Verifier.nondetInt(); + int x12 = Verifier.nondetInt(); + int x13 = Verifier.nondetInt(); + int x14 = Verifier.nondetInt(); + int x15 = Verifier.nondetInt(); + int acc = 0; + try { + acc += step0(x0); + } catch (ArrayIndexOutOfBoundsException e) { + log(0, x0); + return; + } + try { + acc += step1(x1); + } catch (ArrayIndexOutOfBoundsException e) { + log(1, x1); + return; + } + try { + acc += step2(x2); + } catch (ArrayIndexOutOfBoundsException e) { + log(2, x2); + return; + } + try { + acc += step3(x3); + } catch (ArrayIndexOutOfBoundsException e) { + log(3, x3); + return; + } + try { + acc += step4(x4); + } catch (ArrayIndexOutOfBoundsException e) { + log(4, x4); + return; + } + try { + acc += step5(x5); + } catch (ArrayIndexOutOfBoundsException e) { + log(5, x5); + return; + } + try { + acc += step6(x6); + } catch (ArrayIndexOutOfBoundsException e) { + log(6, x6); + return; + } + try { + acc += step7(x7); + } catch (ArrayIndexOutOfBoundsException e) { + log(7, x7); + return; + } + try { + acc += step8(x8); + } catch (ArrayIndexOutOfBoundsException e) { + log(8, x8); + return; + } + try { + acc += step9(x9); + } catch (ArrayIndexOutOfBoundsException e) { + log(9, x9); + return; + } + try { + acc += step10(x10); + } catch (ArrayIndexOutOfBoundsException e) { + log(10, x10); + return; + } + try { + acc += step11(x11); + } catch (ArrayIndexOutOfBoundsException e) { + log(11, x11); + return; + } + try { + acc += step12(x12); + } catch (ArrayIndexOutOfBoundsException e) { + log(12, x12); + return; + } + try { + acc += step13(x13); + } catch (ArrayIndexOutOfBoundsException e) { + log(13, x13); + return; + } + try { + acc += step14(x14); + } catch (ArrayIndexOutOfBoundsException e) { + log(14, x14); + return; + } + try { + acc += step15(x15); + } catch (ArrayIndexOutOfBoundsException e) { + log(15, x15); + return; + } + target(a, b); + } + + static int step0(int x) { + return TABLE[x] + 0; + } + static int step1(int x) { + return TABLE[x] + 1; + } + static int step2(int x) { + return TABLE[x] + 2; + } + static int step3(int x) { + return TABLE[x] + 3; + } + static int step4(int x) { + return TABLE[x] + 4; + } + static int step5(int x) { + return TABLE[x] + 5; + } + static int step6(int x) { + return TABLE[x] + 6; + } + static int step7(int x) { + return TABLE[x] + 7; + } + static int step8(int x) { + return TABLE[x] + 8; + } + static int step9(int x) { + return TABLE[x] + 9; + } + static int step10(int x) { + return TABLE[x] + 10; + } + static int step11(int x) { + return TABLE[x] + 11; + } + static int step12(int x) { + return TABLE[x] + 12; + } + static int step13(int x) { + return TABLE[x] + 13; + } + static int step14(int x) { + return TABLE[x] + 14; + } + static int step15(int x) { + return TABLE[x] + 15; + } + + // Logs a failed step; three branches, no assert. + static void log(int step, int x) { + if (x < -1000) logged += 100; + if (x > 1000) logged += 10; + if (x == step) logged += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n32_false.yml b/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n32_false.yml new file mode 100644 index 00000000..a454c02d --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n32_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - bounds_n32_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n32_false/Main.java b/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n32_false/Main.java new file mode 100644 index 00000000..5e3ecdff --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n32_false/Main.java @@ -0,0 +1,355 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:2996 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// error-handling/bounds: 32 steps that can fail with ArrayIndexOutOfBoundsException; each catch block logs and returns. +public class Main { + static final int[] TABLE = {3, 1, 4, 1}; + + static int logged; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int x0 = Verifier.nondetInt(); + int x1 = Verifier.nondetInt(); + int x2 = Verifier.nondetInt(); + int x3 = Verifier.nondetInt(); + int x4 = Verifier.nondetInt(); + int x5 = Verifier.nondetInt(); + int x6 = Verifier.nondetInt(); + int x7 = Verifier.nondetInt(); + int x8 = Verifier.nondetInt(); + int x9 = Verifier.nondetInt(); + int x10 = Verifier.nondetInt(); + int x11 = Verifier.nondetInt(); + int x12 = Verifier.nondetInt(); + int x13 = Verifier.nondetInt(); + int x14 = Verifier.nondetInt(); + int x15 = Verifier.nondetInt(); + int x16 = Verifier.nondetInt(); + int x17 = Verifier.nondetInt(); + int x18 = Verifier.nondetInt(); + int x19 = Verifier.nondetInt(); + int x20 = Verifier.nondetInt(); + int x21 = Verifier.nondetInt(); + int x22 = Verifier.nondetInt(); + int x23 = Verifier.nondetInt(); + int x24 = Verifier.nondetInt(); + int x25 = Verifier.nondetInt(); + int x26 = Verifier.nondetInt(); + int x27 = Verifier.nondetInt(); + int x28 = Verifier.nondetInt(); + int x29 = Verifier.nondetInt(); + int x30 = Verifier.nondetInt(); + int x31 = Verifier.nondetInt(); + int acc = 0; + try { + acc += step0(x0); + } catch (ArrayIndexOutOfBoundsException e) { + log(0, x0); + return; + } + try { + acc += step1(x1); + } catch (ArrayIndexOutOfBoundsException e) { + log(1, x1); + return; + } + try { + acc += step2(x2); + } catch (ArrayIndexOutOfBoundsException e) { + log(2, x2); + return; + } + try { + acc += step3(x3); + } catch (ArrayIndexOutOfBoundsException e) { + log(3, x3); + return; + } + try { + acc += step4(x4); + } catch (ArrayIndexOutOfBoundsException e) { + log(4, x4); + return; + } + try { + acc += step5(x5); + } catch (ArrayIndexOutOfBoundsException e) { + log(5, x5); + return; + } + try { + acc += step6(x6); + } catch (ArrayIndexOutOfBoundsException e) { + log(6, x6); + return; + } + try { + acc += step7(x7); + } catch (ArrayIndexOutOfBoundsException e) { + log(7, x7); + return; + } + try { + acc += step8(x8); + } catch (ArrayIndexOutOfBoundsException e) { + log(8, x8); + return; + } + try { + acc += step9(x9); + } catch (ArrayIndexOutOfBoundsException e) { + log(9, x9); + return; + } + try { + acc += step10(x10); + } catch (ArrayIndexOutOfBoundsException e) { + log(10, x10); + return; + } + try { + acc += step11(x11); + } catch (ArrayIndexOutOfBoundsException e) { + log(11, x11); + return; + } + try { + acc += step12(x12); + } catch (ArrayIndexOutOfBoundsException e) { + log(12, x12); + return; + } + try { + acc += step13(x13); + } catch (ArrayIndexOutOfBoundsException e) { + log(13, x13); + return; + } + try { + acc += step14(x14); + } catch (ArrayIndexOutOfBoundsException e) { + log(14, x14); + return; + } + try { + acc += step15(x15); + } catch (ArrayIndexOutOfBoundsException e) { + log(15, x15); + return; + } + try { + acc += step16(x16); + } catch (ArrayIndexOutOfBoundsException e) { + log(16, x16); + return; + } + try { + acc += step17(x17); + } catch (ArrayIndexOutOfBoundsException e) { + log(17, x17); + return; + } + try { + acc += step18(x18); + } catch (ArrayIndexOutOfBoundsException e) { + log(18, x18); + return; + } + try { + acc += step19(x19); + } catch (ArrayIndexOutOfBoundsException e) { + log(19, x19); + return; + } + try { + acc += step20(x20); + } catch (ArrayIndexOutOfBoundsException e) { + log(20, x20); + return; + } + try { + acc += step21(x21); + } catch (ArrayIndexOutOfBoundsException e) { + log(21, x21); + return; + } + try { + acc += step22(x22); + } catch (ArrayIndexOutOfBoundsException e) { + log(22, x22); + return; + } + try { + acc += step23(x23); + } catch (ArrayIndexOutOfBoundsException e) { + log(23, x23); + return; + } + try { + acc += step24(x24); + } catch (ArrayIndexOutOfBoundsException e) { + log(24, x24); + return; + } + try { + acc += step25(x25); + } catch (ArrayIndexOutOfBoundsException e) { + log(25, x25); + return; + } + try { + acc += step26(x26); + } catch (ArrayIndexOutOfBoundsException e) { + log(26, x26); + return; + } + try { + acc += step27(x27); + } catch (ArrayIndexOutOfBoundsException e) { + log(27, x27); + return; + } + try { + acc += step28(x28); + } catch (ArrayIndexOutOfBoundsException e) { + log(28, x28); + return; + } + try { + acc += step29(x29); + } catch (ArrayIndexOutOfBoundsException e) { + log(29, x29); + return; + } + try { + acc += step30(x30); + } catch (ArrayIndexOutOfBoundsException e) { + log(30, x30); + return; + } + try { + acc += step31(x31); + } catch (ArrayIndexOutOfBoundsException e) { + log(31, x31); + return; + } + target(a, b); + } + + static int step0(int x) { + return TABLE[x] + 0; + } + static int step1(int x) { + return TABLE[x] + 1; + } + static int step2(int x) { + return TABLE[x] + 2; + } + static int step3(int x) { + return TABLE[x] + 3; + } + static int step4(int x) { + return TABLE[x] + 4; + } + static int step5(int x) { + return TABLE[x] + 5; + } + static int step6(int x) { + return TABLE[x] + 6; + } + static int step7(int x) { + return TABLE[x] + 7; + } + static int step8(int x) { + return TABLE[x] + 8; + } + static int step9(int x) { + return TABLE[x] + 9; + } + static int step10(int x) { + return TABLE[x] + 10; + } + static int step11(int x) { + return TABLE[x] + 11; + } + static int step12(int x) { + return TABLE[x] + 12; + } + static int step13(int x) { + return TABLE[x] + 13; + } + static int step14(int x) { + return TABLE[x] + 14; + } + static int step15(int x) { + return TABLE[x] + 15; + } + static int step16(int x) { + return TABLE[x] + 16; + } + static int step17(int x) { + return TABLE[x] + 17; + } + static int step18(int x) { + return TABLE[x] + 18; + } + static int step19(int x) { + return TABLE[x] + 19; + } + static int step20(int x) { + return TABLE[x] + 20; + } + static int step21(int x) { + return TABLE[x] + 21; + } + static int step22(int x) { + return TABLE[x] + 22; + } + static int step23(int x) { + return TABLE[x] + 23; + } + static int step24(int x) { + return TABLE[x] + 24; + } + static int step25(int x) { + return TABLE[x] + 25; + } + static int step26(int x) { + return TABLE[x] + 26; + } + static int step27(int x) { + return TABLE[x] + 27; + } + static int step28(int x) { + return TABLE[x] + 28; + } + static int step29(int x) { + return TABLE[x] + 29; + } + static int step30(int x) { + return TABLE[x] + 30; + } + static int step31(int x) { + return TABLE[x] + 31; + } + + // Logs a failed step; three branches, no assert. + static void log(int step, int x) { + if (x < -1000) logged += 100; + if (x > 1000) logged += 10; + if (x == step) logged += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n32_true.yml b/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n32_true.yml new file mode 100644 index 00000000..255ddcc1 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n32_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - bounds_n32_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n32_true/Main.java b/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n32_true/Main.java new file mode 100644 index 00000000..cef379c2 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/bounds_n32_true/Main.java @@ -0,0 +1,356 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:1001 int:2996 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// error-handling/bounds: 32 steps that can fail with ArrayIndexOutOfBoundsException; each catch block logs and returns. +public class Main { + static final int[] TABLE = {3, 1, 4, 1}; + + static int logged; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int x0 = Verifier.nondetInt(); + int x1 = Verifier.nondetInt(); + int x2 = Verifier.nondetInt(); + int x3 = Verifier.nondetInt(); + int x4 = Verifier.nondetInt(); + int x5 = Verifier.nondetInt(); + int x6 = Verifier.nondetInt(); + int x7 = Verifier.nondetInt(); + int x8 = Verifier.nondetInt(); + int x9 = Verifier.nondetInt(); + int x10 = Verifier.nondetInt(); + int x11 = Verifier.nondetInt(); + int x12 = Verifier.nondetInt(); + int x13 = Verifier.nondetInt(); + int x14 = Verifier.nondetInt(); + int x15 = Verifier.nondetInt(); + int x16 = Verifier.nondetInt(); + int x17 = Verifier.nondetInt(); + int x18 = Verifier.nondetInt(); + int x19 = Verifier.nondetInt(); + int x20 = Verifier.nondetInt(); + int x21 = Verifier.nondetInt(); + int x22 = Verifier.nondetInt(); + int x23 = Verifier.nondetInt(); + int x24 = Verifier.nondetInt(); + int x25 = Verifier.nondetInt(); + int x26 = Verifier.nondetInt(); + int x27 = Verifier.nondetInt(); + int x28 = Verifier.nondetInt(); + int x29 = Verifier.nondetInt(); + int x30 = Verifier.nondetInt(); + int x31 = Verifier.nondetInt(); + int acc = 0; + try { + acc += step0(x0); + } catch (ArrayIndexOutOfBoundsException e) { + log(0, x0); + return; + } + try { + acc += step1(x1); + } catch (ArrayIndexOutOfBoundsException e) { + log(1, x1); + return; + } + try { + acc += step2(x2); + } catch (ArrayIndexOutOfBoundsException e) { + log(2, x2); + return; + } + try { + acc += step3(x3); + } catch (ArrayIndexOutOfBoundsException e) { + log(3, x3); + return; + } + try { + acc += step4(x4); + } catch (ArrayIndexOutOfBoundsException e) { + log(4, x4); + return; + } + try { + acc += step5(x5); + } catch (ArrayIndexOutOfBoundsException e) { + log(5, x5); + return; + } + try { + acc += step6(x6); + } catch (ArrayIndexOutOfBoundsException e) { + log(6, x6); + return; + } + try { + acc += step7(x7); + } catch (ArrayIndexOutOfBoundsException e) { + log(7, x7); + return; + } + try { + acc += step8(x8); + } catch (ArrayIndexOutOfBoundsException e) { + log(8, x8); + return; + } + try { + acc += step9(x9); + } catch (ArrayIndexOutOfBoundsException e) { + log(9, x9); + return; + } + try { + acc += step10(x10); + } catch (ArrayIndexOutOfBoundsException e) { + log(10, x10); + return; + } + try { + acc += step11(x11); + } catch (ArrayIndexOutOfBoundsException e) { + log(11, x11); + return; + } + try { + acc += step12(x12); + } catch (ArrayIndexOutOfBoundsException e) { + log(12, x12); + return; + } + try { + acc += step13(x13); + } catch (ArrayIndexOutOfBoundsException e) { + log(13, x13); + return; + } + try { + acc += step14(x14); + } catch (ArrayIndexOutOfBoundsException e) { + log(14, x14); + return; + } + try { + acc += step15(x15); + } catch (ArrayIndexOutOfBoundsException e) { + log(15, x15); + return; + } + try { + acc += step16(x16); + } catch (ArrayIndexOutOfBoundsException e) { + log(16, x16); + return; + } + try { + acc += step17(x17); + } catch (ArrayIndexOutOfBoundsException e) { + log(17, x17); + return; + } + try { + acc += step18(x18); + } catch (ArrayIndexOutOfBoundsException e) { + log(18, x18); + return; + } + try { + acc += step19(x19); + } catch (ArrayIndexOutOfBoundsException e) { + log(19, x19); + return; + } + try { + acc += step20(x20); + } catch (ArrayIndexOutOfBoundsException e) { + log(20, x20); + return; + } + try { + acc += step21(x21); + } catch (ArrayIndexOutOfBoundsException e) { + log(21, x21); + return; + } + try { + acc += step22(x22); + } catch (ArrayIndexOutOfBoundsException e) { + log(22, x22); + return; + } + try { + acc += step23(x23); + } catch (ArrayIndexOutOfBoundsException e) { + log(23, x23); + return; + } + try { + acc += step24(x24); + } catch (ArrayIndexOutOfBoundsException e) { + log(24, x24); + return; + } + try { + acc += step25(x25); + } catch (ArrayIndexOutOfBoundsException e) { + log(25, x25); + return; + } + try { + acc += step26(x26); + } catch (ArrayIndexOutOfBoundsException e) { + log(26, x26); + return; + } + try { + acc += step27(x27); + } catch (ArrayIndexOutOfBoundsException e) { + log(27, x27); + return; + } + try { + acc += step28(x28); + } catch (ArrayIndexOutOfBoundsException e) { + log(28, x28); + return; + } + try { + acc += step29(x29); + } catch (ArrayIndexOutOfBoundsException e) { + log(29, x29); + return; + } + try { + acc += step30(x30); + } catch (ArrayIndexOutOfBoundsException e) { + log(30, x30); + return; + } + try { + acc += step31(x31); + } catch (ArrayIndexOutOfBoundsException e) { + log(31, x31); + return; + } + target(a, b); + } + + static int step0(int x) { + return TABLE[x] + 0; + } + static int step1(int x) { + return TABLE[x] + 1; + } + static int step2(int x) { + return TABLE[x] + 2; + } + static int step3(int x) { + return TABLE[x] + 3; + } + static int step4(int x) { + return TABLE[x] + 4; + } + static int step5(int x) { + return TABLE[x] + 5; + } + static int step6(int x) { + return TABLE[x] + 6; + } + static int step7(int x) { + return TABLE[x] + 7; + } + static int step8(int x) { + return TABLE[x] + 8; + } + static int step9(int x) { + return TABLE[x] + 9; + } + static int step10(int x) { + return TABLE[x] + 10; + } + static int step11(int x) { + return TABLE[x] + 11; + } + static int step12(int x) { + return TABLE[x] + 12; + } + static int step13(int x) { + return TABLE[x] + 13; + } + static int step14(int x) { + return TABLE[x] + 14; + } + static int step15(int x) { + return TABLE[x] + 15; + } + static int step16(int x) { + return TABLE[x] + 16; + } + static int step17(int x) { + return TABLE[x] + 17; + } + static int step18(int x) { + return TABLE[x] + 18; + } + static int step19(int x) { + return TABLE[x] + 19; + } + static int step20(int x) { + return TABLE[x] + 20; + } + static int step21(int x) { + return TABLE[x] + 21; + } + static int step22(int x) { + return TABLE[x] + 22; + } + static int step23(int x) { + return TABLE[x] + 23; + } + static int step24(int x) { + return TABLE[x] + 24; + } + static int step25(int x) { + return TABLE[x] + 25; + } + static int step26(int x) { + return TABLE[x] + 26; + } + static int step27(int x) { + return TABLE[x] + 27; + } + static int step28(int x) { + return TABLE[x] + 28; + } + static int step29(int x) { + return TABLE[x] + 29; + } + static int step30(int x) { + return TABLE[x] + 30; + } + static int step31(int x) { + return TABLE[x] + 31; + } + + // Logs a failed step; three branches, no assert. + static void log(int step, int x) { + if (x < -1000) logged += 100; + if (x > 1000) logged += 10; + if (x == step) logged += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n02_false.yml b/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n02_false.yml new file mode 100644 index 00000000..c0e534ed --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n02_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - deep_n02_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n02_false/Main.java b/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n02_false/Main.java new file mode 100644 index 00000000..ea4b5163 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n02_false/Main.java @@ -0,0 +1,63 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// error-handling/deep: 2 steps that can fail with IllegalArgumentException; each catch block logs and returns. +public class Main { + static int logged; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int x0 = Verifier.nondetInt(); + int x1 = Verifier.nondetInt(); + int acc = 0; + try { + acc += step0(x0); + } catch (IllegalArgumentException e) { + log(0, x0); + return; + } + try { + acc += step1(x1); + } catch (IllegalArgumentException e) { + log(1, x1); + return; + } + target(a, b); + } + + static int step0(int x) { + return check0(x) + 0; + } + + static int check0(int x) { + if (x < -1) throw new IllegalArgumentException(); + return x; + } + static int step1(int x) { + return check1(x) + 1; + } + + static int check1(int x) { + if (x < -2) throw new IllegalArgumentException(); + return x; + } + + // Logs a failed step; three branches, no assert. + static void log(int step, int x) { + if (x < -1000) logged += 100; + if (x > 1000) logged += 10; + if (x == step) logged += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n02_true.yml b/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n02_true.yml new file mode 100644 index 00000000..daa3453a --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n02_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - deep_n02_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n02_true/Main.java b/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n02_true/Main.java new file mode 100644 index 00000000..90b51ab7 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n02_true/Main.java @@ -0,0 +1,64 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// error-handling/deep: 2 steps that can fail with IllegalArgumentException; each catch block logs and returns. +public class Main { + static int logged; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int x0 = Verifier.nondetInt(); + int x1 = Verifier.nondetInt(); + int acc = 0; + try { + acc += step0(x0); + } catch (IllegalArgumentException e) { + log(0, x0); + return; + } + try { + acc += step1(x1); + } catch (IllegalArgumentException e) { + log(1, x1); + return; + } + target(a, b); + } + + static int step0(int x) { + return check0(x) + 0; + } + + static int check0(int x) { + if (x < -1) throw new IllegalArgumentException(); + return x; + } + static int step1(int x) { + return check1(x) + 1; + } + + static int check1(int x) { + if (x < -2) throw new IllegalArgumentException(); + return x; + } + + // Logs a failed step; three branches, no assert. + static void log(int step, int x) { + if (x < -1000) logged += 100; + if (x > 1000) logged += 10; + if (x == step) logged += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n04_false.yml b/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n04_false.yml new file mode 100644 index 00000000..d7744299 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n04_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - deep_n04_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n04_false/Main.java b/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n04_false/Main.java new file mode 100644 index 00000000..b66bdd98 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n04_false/Main.java @@ -0,0 +1,93 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:2996 int:0 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// error-handling/deep: 4 steps that can fail with IllegalArgumentException; each catch block logs and returns. +public class Main { + static int logged; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int x0 = Verifier.nondetInt(); + int x1 = Verifier.nondetInt(); + int x2 = Verifier.nondetInt(); + int x3 = Verifier.nondetInt(); + int acc = 0; + try { + acc += step0(x0); + } catch (IllegalArgumentException e) { + log(0, x0); + return; + } + try { + acc += step1(x1); + } catch (IllegalArgumentException e) { + log(1, x1); + return; + } + try { + acc += step2(x2); + } catch (IllegalArgumentException e) { + log(2, x2); + return; + } + try { + acc += step3(x3); + } catch (IllegalArgumentException e) { + log(3, x3); + return; + } + target(a, b); + } + + static int step0(int x) { + return check0(x) + 0; + } + + static int check0(int x) { + if (x < -1) throw new IllegalArgumentException(); + return x; + } + static int step1(int x) { + return check1(x) + 1; + } + + static int check1(int x) { + if (x < -2) throw new IllegalArgumentException(); + return x; + } + static int step2(int x) { + return check2(x) + 2; + } + + static int check2(int x) { + if (x < -3) throw new IllegalArgumentException(); + return x; + } + static int step3(int x) { + return check3(x) + 3; + } + + static int check3(int x) { + if (x < -4) throw new IllegalArgumentException(); + return x; + } + + // Logs a failed step; three branches, no assert. + static void log(int step, int x) { + if (x < -1000) logged += 100; + if (x > 1000) logged += 10; + if (x == step) logged += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n04_true.yml b/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n04_true.yml new file mode 100644 index 00000000..2cc65a19 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n04_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - deep_n04_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n04_true/Main.java b/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n04_true/Main.java new file mode 100644 index 00000000..b23e833a --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n04_true/Main.java @@ -0,0 +1,94 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:1001 int:2996 int:0 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// error-handling/deep: 4 steps that can fail with IllegalArgumentException; each catch block logs and returns. +public class Main { + static int logged; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int x0 = Verifier.nondetInt(); + int x1 = Verifier.nondetInt(); + int x2 = Verifier.nondetInt(); + int x3 = Verifier.nondetInt(); + int acc = 0; + try { + acc += step0(x0); + } catch (IllegalArgumentException e) { + log(0, x0); + return; + } + try { + acc += step1(x1); + } catch (IllegalArgumentException e) { + log(1, x1); + return; + } + try { + acc += step2(x2); + } catch (IllegalArgumentException e) { + log(2, x2); + return; + } + try { + acc += step3(x3); + } catch (IllegalArgumentException e) { + log(3, x3); + return; + } + target(a, b); + } + + static int step0(int x) { + return check0(x) + 0; + } + + static int check0(int x) { + if (x < -1) throw new IllegalArgumentException(); + return x; + } + static int step1(int x) { + return check1(x) + 1; + } + + static int check1(int x) { + if (x < -2) throw new IllegalArgumentException(); + return x; + } + static int step2(int x) { + return check2(x) + 2; + } + + static int check2(int x) { + if (x < -3) throw new IllegalArgumentException(); + return x; + } + static int step3(int x) { + return check3(x) + 3; + } + + static int check3(int x) { + if (x < -4) throw new IllegalArgumentException(); + return x; + } + + // Logs a failed step; three branches, no assert. + static void log(int step, int x) { + if (x < -1000) logged += 100; + if (x > 1000) logged += 10; + if (x == step) logged += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n08_false.yml b/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n08_false.yml new file mode 100644 index 00000000..93f16fca --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n08_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - deep_n08_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n08_false/Main.java b/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n08_false/Main.java new file mode 100644 index 00000000..b1f0b006 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n08_false/Main.java @@ -0,0 +1,153 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:2996 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// error-handling/deep: 8 steps that can fail with IllegalArgumentException; each catch block logs and returns. +public class Main { + static int logged; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int x0 = Verifier.nondetInt(); + int x1 = Verifier.nondetInt(); + int x2 = Verifier.nondetInt(); + int x3 = Verifier.nondetInt(); + int x4 = Verifier.nondetInt(); + int x5 = Verifier.nondetInt(); + int x6 = Verifier.nondetInt(); + int x7 = Verifier.nondetInt(); + int acc = 0; + try { + acc += step0(x0); + } catch (IllegalArgumentException e) { + log(0, x0); + return; + } + try { + acc += step1(x1); + } catch (IllegalArgumentException e) { + log(1, x1); + return; + } + try { + acc += step2(x2); + } catch (IllegalArgumentException e) { + log(2, x2); + return; + } + try { + acc += step3(x3); + } catch (IllegalArgumentException e) { + log(3, x3); + return; + } + try { + acc += step4(x4); + } catch (IllegalArgumentException e) { + log(4, x4); + return; + } + try { + acc += step5(x5); + } catch (IllegalArgumentException e) { + log(5, x5); + return; + } + try { + acc += step6(x6); + } catch (IllegalArgumentException e) { + log(6, x6); + return; + } + try { + acc += step7(x7); + } catch (IllegalArgumentException e) { + log(7, x7); + return; + } + target(a, b); + } + + static int step0(int x) { + return check0(x) + 0; + } + + static int check0(int x) { + if (x < -1) throw new IllegalArgumentException(); + return x; + } + static int step1(int x) { + return check1(x) + 1; + } + + static int check1(int x) { + if (x < -2) throw new IllegalArgumentException(); + return x; + } + static int step2(int x) { + return check2(x) + 2; + } + + static int check2(int x) { + if (x < -3) throw new IllegalArgumentException(); + return x; + } + static int step3(int x) { + return check3(x) + 3; + } + + static int check3(int x) { + if (x < -4) throw new IllegalArgumentException(); + return x; + } + static int step4(int x) { + return check4(x) + 4; + } + + static int check4(int x) { + if (x < -5) throw new IllegalArgumentException(); + return x; + } + static int step5(int x) { + return check5(x) + 5; + } + + static int check5(int x) { + if (x < -6) throw new IllegalArgumentException(); + return x; + } + static int step6(int x) { + return check6(x) + 6; + } + + static int check6(int x) { + if (x < -7) throw new IllegalArgumentException(); + return x; + } + static int step7(int x) { + return check7(x) + 7; + } + + static int check7(int x) { + if (x < -8) throw new IllegalArgumentException(); + return x; + } + + // Logs a failed step; three branches, no assert. + static void log(int step, int x) { + if (x < -1000) logged += 100; + if (x > 1000) logged += 10; + if (x == step) logged += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n08_true.yml b/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n08_true.yml new file mode 100644 index 00000000..8fa8e82a --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n08_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - deep_n08_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n08_true/Main.java b/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n08_true/Main.java new file mode 100644 index 00000000..cdeeaec8 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n08_true/Main.java @@ -0,0 +1,154 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:1001 int:2996 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// error-handling/deep: 8 steps that can fail with IllegalArgumentException; each catch block logs and returns. +public class Main { + static int logged; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int x0 = Verifier.nondetInt(); + int x1 = Verifier.nondetInt(); + int x2 = Verifier.nondetInt(); + int x3 = Verifier.nondetInt(); + int x4 = Verifier.nondetInt(); + int x5 = Verifier.nondetInt(); + int x6 = Verifier.nondetInt(); + int x7 = Verifier.nondetInt(); + int acc = 0; + try { + acc += step0(x0); + } catch (IllegalArgumentException e) { + log(0, x0); + return; + } + try { + acc += step1(x1); + } catch (IllegalArgumentException e) { + log(1, x1); + return; + } + try { + acc += step2(x2); + } catch (IllegalArgumentException e) { + log(2, x2); + return; + } + try { + acc += step3(x3); + } catch (IllegalArgumentException e) { + log(3, x3); + return; + } + try { + acc += step4(x4); + } catch (IllegalArgumentException e) { + log(4, x4); + return; + } + try { + acc += step5(x5); + } catch (IllegalArgumentException e) { + log(5, x5); + return; + } + try { + acc += step6(x6); + } catch (IllegalArgumentException e) { + log(6, x6); + return; + } + try { + acc += step7(x7); + } catch (IllegalArgumentException e) { + log(7, x7); + return; + } + target(a, b); + } + + static int step0(int x) { + return check0(x) + 0; + } + + static int check0(int x) { + if (x < -1) throw new IllegalArgumentException(); + return x; + } + static int step1(int x) { + return check1(x) + 1; + } + + static int check1(int x) { + if (x < -2) throw new IllegalArgumentException(); + return x; + } + static int step2(int x) { + return check2(x) + 2; + } + + static int check2(int x) { + if (x < -3) throw new IllegalArgumentException(); + return x; + } + static int step3(int x) { + return check3(x) + 3; + } + + static int check3(int x) { + if (x < -4) throw new IllegalArgumentException(); + return x; + } + static int step4(int x) { + return check4(x) + 4; + } + + static int check4(int x) { + if (x < -5) throw new IllegalArgumentException(); + return x; + } + static int step5(int x) { + return check5(x) + 5; + } + + static int check5(int x) { + if (x < -6) throw new IllegalArgumentException(); + return x; + } + static int step6(int x) { + return check6(x) + 6; + } + + static int check6(int x) { + if (x < -7) throw new IllegalArgumentException(); + return x; + } + static int step7(int x) { + return check7(x) + 7; + } + + static int check7(int x) { + if (x < -8) throw new IllegalArgumentException(); + return x; + } + + // Logs a failed step; three branches, no assert. + static void log(int step, int x) { + if (x < -1000) logged += 100; + if (x > 1000) logged += 10; + if (x == step) logged += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n16_false.yml b/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n16_false.yml new file mode 100644 index 00000000..8f4d49bd --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n16_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - deep_n16_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n16_false/Main.java b/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n16_false/Main.java new file mode 100644 index 00000000..4157a81f --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n16_false/Main.java @@ -0,0 +1,273 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:2996 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// error-handling/deep: 16 steps that can fail with IllegalArgumentException; each catch block logs and returns. +public class Main { + static int logged; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int x0 = Verifier.nondetInt(); + int x1 = Verifier.nondetInt(); + int x2 = Verifier.nondetInt(); + int x3 = Verifier.nondetInt(); + int x4 = Verifier.nondetInt(); + int x5 = Verifier.nondetInt(); + int x6 = Verifier.nondetInt(); + int x7 = Verifier.nondetInt(); + int x8 = Verifier.nondetInt(); + int x9 = Verifier.nondetInt(); + int x10 = Verifier.nondetInt(); + int x11 = Verifier.nondetInt(); + int x12 = Verifier.nondetInt(); + int x13 = Verifier.nondetInt(); + int x14 = Verifier.nondetInt(); + int x15 = Verifier.nondetInt(); + int acc = 0; + try { + acc += step0(x0); + } catch (IllegalArgumentException e) { + log(0, x0); + return; + } + try { + acc += step1(x1); + } catch (IllegalArgumentException e) { + log(1, x1); + return; + } + try { + acc += step2(x2); + } catch (IllegalArgumentException e) { + log(2, x2); + return; + } + try { + acc += step3(x3); + } catch (IllegalArgumentException e) { + log(3, x3); + return; + } + try { + acc += step4(x4); + } catch (IllegalArgumentException e) { + log(4, x4); + return; + } + try { + acc += step5(x5); + } catch (IllegalArgumentException e) { + log(5, x5); + return; + } + try { + acc += step6(x6); + } catch (IllegalArgumentException e) { + log(6, x6); + return; + } + try { + acc += step7(x7); + } catch (IllegalArgumentException e) { + log(7, x7); + return; + } + try { + acc += step8(x8); + } catch (IllegalArgumentException e) { + log(8, x8); + return; + } + try { + acc += step9(x9); + } catch (IllegalArgumentException e) { + log(9, x9); + return; + } + try { + acc += step10(x10); + } catch (IllegalArgumentException e) { + log(10, x10); + return; + } + try { + acc += step11(x11); + } catch (IllegalArgumentException e) { + log(11, x11); + return; + } + try { + acc += step12(x12); + } catch (IllegalArgumentException e) { + log(12, x12); + return; + } + try { + acc += step13(x13); + } catch (IllegalArgumentException e) { + log(13, x13); + return; + } + try { + acc += step14(x14); + } catch (IllegalArgumentException e) { + log(14, x14); + return; + } + try { + acc += step15(x15); + } catch (IllegalArgumentException e) { + log(15, x15); + return; + } + target(a, b); + } + + static int step0(int x) { + return check0(x) + 0; + } + + static int check0(int x) { + if (x < -1) throw new IllegalArgumentException(); + return x; + } + static int step1(int x) { + return check1(x) + 1; + } + + static int check1(int x) { + if (x < -2) throw new IllegalArgumentException(); + return x; + } + static int step2(int x) { + return check2(x) + 2; + } + + static int check2(int x) { + if (x < -3) throw new IllegalArgumentException(); + return x; + } + static int step3(int x) { + return check3(x) + 3; + } + + static int check3(int x) { + if (x < -4) throw new IllegalArgumentException(); + return x; + } + static int step4(int x) { + return check4(x) + 4; + } + + static int check4(int x) { + if (x < -5) throw new IllegalArgumentException(); + return x; + } + static int step5(int x) { + return check5(x) + 5; + } + + static int check5(int x) { + if (x < -6) throw new IllegalArgumentException(); + return x; + } + static int step6(int x) { + return check6(x) + 6; + } + + static int check6(int x) { + if (x < -7) throw new IllegalArgumentException(); + return x; + } + static int step7(int x) { + return check7(x) + 7; + } + + static int check7(int x) { + if (x < -8) throw new IllegalArgumentException(); + return x; + } + static int step8(int x) { + return check8(x) + 8; + } + + static int check8(int x) { + if (x < -9) throw new IllegalArgumentException(); + return x; + } + static int step9(int x) { + return check9(x) + 9; + } + + static int check9(int x) { + if (x < -10) throw new IllegalArgumentException(); + return x; + } + static int step10(int x) { + return check10(x) + 10; + } + + static int check10(int x) { + if (x < -11) throw new IllegalArgumentException(); + return x; + } + static int step11(int x) { + return check11(x) + 11; + } + + static int check11(int x) { + if (x < -12) throw new IllegalArgumentException(); + return x; + } + static int step12(int x) { + return check12(x) + 12; + } + + static int check12(int x) { + if (x < -13) throw new IllegalArgumentException(); + return x; + } + static int step13(int x) { + return check13(x) + 13; + } + + static int check13(int x) { + if (x < -14) throw new IllegalArgumentException(); + return x; + } + static int step14(int x) { + return check14(x) + 14; + } + + static int check14(int x) { + if (x < -15) throw new IllegalArgumentException(); + return x; + } + static int step15(int x) { + return check15(x) + 15; + } + + static int check15(int x) { + if (x < -16) throw new IllegalArgumentException(); + return x; + } + + // Logs a failed step; three branches, no assert. + static void log(int step, int x) { + if (x < -1000) logged += 100; + if (x > 1000) logged += 10; + if (x == step) logged += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n16_true.yml b/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n16_true.yml new file mode 100644 index 00000000..f8063453 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n16_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - deep_n16_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n16_true/Main.java b/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n16_true/Main.java new file mode 100644 index 00000000..1d72618d --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n16_true/Main.java @@ -0,0 +1,274 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:1001 int:2996 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// error-handling/deep: 16 steps that can fail with IllegalArgumentException; each catch block logs and returns. +public class Main { + static int logged; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int x0 = Verifier.nondetInt(); + int x1 = Verifier.nondetInt(); + int x2 = Verifier.nondetInt(); + int x3 = Verifier.nondetInt(); + int x4 = Verifier.nondetInt(); + int x5 = Verifier.nondetInt(); + int x6 = Verifier.nondetInt(); + int x7 = Verifier.nondetInt(); + int x8 = Verifier.nondetInt(); + int x9 = Verifier.nondetInt(); + int x10 = Verifier.nondetInt(); + int x11 = Verifier.nondetInt(); + int x12 = Verifier.nondetInt(); + int x13 = Verifier.nondetInt(); + int x14 = Verifier.nondetInt(); + int x15 = Verifier.nondetInt(); + int acc = 0; + try { + acc += step0(x0); + } catch (IllegalArgumentException e) { + log(0, x0); + return; + } + try { + acc += step1(x1); + } catch (IllegalArgumentException e) { + log(1, x1); + return; + } + try { + acc += step2(x2); + } catch (IllegalArgumentException e) { + log(2, x2); + return; + } + try { + acc += step3(x3); + } catch (IllegalArgumentException e) { + log(3, x3); + return; + } + try { + acc += step4(x4); + } catch (IllegalArgumentException e) { + log(4, x4); + return; + } + try { + acc += step5(x5); + } catch (IllegalArgumentException e) { + log(5, x5); + return; + } + try { + acc += step6(x6); + } catch (IllegalArgumentException e) { + log(6, x6); + return; + } + try { + acc += step7(x7); + } catch (IllegalArgumentException e) { + log(7, x7); + return; + } + try { + acc += step8(x8); + } catch (IllegalArgumentException e) { + log(8, x8); + return; + } + try { + acc += step9(x9); + } catch (IllegalArgumentException e) { + log(9, x9); + return; + } + try { + acc += step10(x10); + } catch (IllegalArgumentException e) { + log(10, x10); + return; + } + try { + acc += step11(x11); + } catch (IllegalArgumentException e) { + log(11, x11); + return; + } + try { + acc += step12(x12); + } catch (IllegalArgumentException e) { + log(12, x12); + return; + } + try { + acc += step13(x13); + } catch (IllegalArgumentException e) { + log(13, x13); + return; + } + try { + acc += step14(x14); + } catch (IllegalArgumentException e) { + log(14, x14); + return; + } + try { + acc += step15(x15); + } catch (IllegalArgumentException e) { + log(15, x15); + return; + } + target(a, b); + } + + static int step0(int x) { + return check0(x) + 0; + } + + static int check0(int x) { + if (x < -1) throw new IllegalArgumentException(); + return x; + } + static int step1(int x) { + return check1(x) + 1; + } + + static int check1(int x) { + if (x < -2) throw new IllegalArgumentException(); + return x; + } + static int step2(int x) { + return check2(x) + 2; + } + + static int check2(int x) { + if (x < -3) throw new IllegalArgumentException(); + return x; + } + static int step3(int x) { + return check3(x) + 3; + } + + static int check3(int x) { + if (x < -4) throw new IllegalArgumentException(); + return x; + } + static int step4(int x) { + return check4(x) + 4; + } + + static int check4(int x) { + if (x < -5) throw new IllegalArgumentException(); + return x; + } + static int step5(int x) { + return check5(x) + 5; + } + + static int check5(int x) { + if (x < -6) throw new IllegalArgumentException(); + return x; + } + static int step6(int x) { + return check6(x) + 6; + } + + static int check6(int x) { + if (x < -7) throw new IllegalArgumentException(); + return x; + } + static int step7(int x) { + return check7(x) + 7; + } + + static int check7(int x) { + if (x < -8) throw new IllegalArgumentException(); + return x; + } + static int step8(int x) { + return check8(x) + 8; + } + + static int check8(int x) { + if (x < -9) throw new IllegalArgumentException(); + return x; + } + static int step9(int x) { + return check9(x) + 9; + } + + static int check9(int x) { + if (x < -10) throw new IllegalArgumentException(); + return x; + } + static int step10(int x) { + return check10(x) + 10; + } + + static int check10(int x) { + if (x < -11) throw new IllegalArgumentException(); + return x; + } + static int step11(int x) { + return check11(x) + 11; + } + + static int check11(int x) { + if (x < -12) throw new IllegalArgumentException(); + return x; + } + static int step12(int x) { + return check12(x) + 12; + } + + static int check12(int x) { + if (x < -13) throw new IllegalArgumentException(); + return x; + } + static int step13(int x) { + return check13(x) + 13; + } + + static int check13(int x) { + if (x < -14) throw new IllegalArgumentException(); + return x; + } + static int step14(int x) { + return check14(x) + 14; + } + + static int check14(int x) { + if (x < -15) throw new IllegalArgumentException(); + return x; + } + static int step15(int x) { + return check15(x) + 15; + } + + static int check15(int x) { + if (x < -16) throw new IllegalArgumentException(); + return x; + } + + // Logs a failed step; three branches, no assert. + static void log(int step, int x) { + if (x < -1000) logged += 100; + if (x > 1000) logged += 10; + if (x == step) logged += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n32_false.yml b/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n32_false.yml new file mode 100644 index 00000000..a66bed2c --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n32_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - deep_n32_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n32_false/Main.java b/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n32_false/Main.java new file mode 100644 index 00000000..b401ab3f --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n32_false/Main.java @@ -0,0 +1,513 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:2996 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// error-handling/deep: 32 steps that can fail with IllegalArgumentException; each catch block logs and returns. +public class Main { + static int logged; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int x0 = Verifier.nondetInt(); + int x1 = Verifier.nondetInt(); + int x2 = Verifier.nondetInt(); + int x3 = Verifier.nondetInt(); + int x4 = Verifier.nondetInt(); + int x5 = Verifier.nondetInt(); + int x6 = Verifier.nondetInt(); + int x7 = Verifier.nondetInt(); + int x8 = Verifier.nondetInt(); + int x9 = Verifier.nondetInt(); + int x10 = Verifier.nondetInt(); + int x11 = Verifier.nondetInt(); + int x12 = Verifier.nondetInt(); + int x13 = Verifier.nondetInt(); + int x14 = Verifier.nondetInt(); + int x15 = Verifier.nondetInt(); + int x16 = Verifier.nondetInt(); + int x17 = Verifier.nondetInt(); + int x18 = Verifier.nondetInt(); + int x19 = Verifier.nondetInt(); + int x20 = Verifier.nondetInt(); + int x21 = Verifier.nondetInt(); + int x22 = Verifier.nondetInt(); + int x23 = Verifier.nondetInt(); + int x24 = Verifier.nondetInt(); + int x25 = Verifier.nondetInt(); + int x26 = Verifier.nondetInt(); + int x27 = Verifier.nondetInt(); + int x28 = Verifier.nondetInt(); + int x29 = Verifier.nondetInt(); + int x30 = Verifier.nondetInt(); + int x31 = Verifier.nondetInt(); + int acc = 0; + try { + acc += step0(x0); + } catch (IllegalArgumentException e) { + log(0, x0); + return; + } + try { + acc += step1(x1); + } catch (IllegalArgumentException e) { + log(1, x1); + return; + } + try { + acc += step2(x2); + } catch (IllegalArgumentException e) { + log(2, x2); + return; + } + try { + acc += step3(x3); + } catch (IllegalArgumentException e) { + log(3, x3); + return; + } + try { + acc += step4(x4); + } catch (IllegalArgumentException e) { + log(4, x4); + return; + } + try { + acc += step5(x5); + } catch (IllegalArgumentException e) { + log(5, x5); + return; + } + try { + acc += step6(x6); + } catch (IllegalArgumentException e) { + log(6, x6); + return; + } + try { + acc += step7(x7); + } catch (IllegalArgumentException e) { + log(7, x7); + return; + } + try { + acc += step8(x8); + } catch (IllegalArgumentException e) { + log(8, x8); + return; + } + try { + acc += step9(x9); + } catch (IllegalArgumentException e) { + log(9, x9); + return; + } + try { + acc += step10(x10); + } catch (IllegalArgumentException e) { + log(10, x10); + return; + } + try { + acc += step11(x11); + } catch (IllegalArgumentException e) { + log(11, x11); + return; + } + try { + acc += step12(x12); + } catch (IllegalArgumentException e) { + log(12, x12); + return; + } + try { + acc += step13(x13); + } catch (IllegalArgumentException e) { + log(13, x13); + return; + } + try { + acc += step14(x14); + } catch (IllegalArgumentException e) { + log(14, x14); + return; + } + try { + acc += step15(x15); + } catch (IllegalArgumentException e) { + log(15, x15); + return; + } + try { + acc += step16(x16); + } catch (IllegalArgumentException e) { + log(16, x16); + return; + } + try { + acc += step17(x17); + } catch (IllegalArgumentException e) { + log(17, x17); + return; + } + try { + acc += step18(x18); + } catch (IllegalArgumentException e) { + log(18, x18); + return; + } + try { + acc += step19(x19); + } catch (IllegalArgumentException e) { + log(19, x19); + return; + } + try { + acc += step20(x20); + } catch (IllegalArgumentException e) { + log(20, x20); + return; + } + try { + acc += step21(x21); + } catch (IllegalArgumentException e) { + log(21, x21); + return; + } + try { + acc += step22(x22); + } catch (IllegalArgumentException e) { + log(22, x22); + return; + } + try { + acc += step23(x23); + } catch (IllegalArgumentException e) { + log(23, x23); + return; + } + try { + acc += step24(x24); + } catch (IllegalArgumentException e) { + log(24, x24); + return; + } + try { + acc += step25(x25); + } catch (IllegalArgumentException e) { + log(25, x25); + return; + } + try { + acc += step26(x26); + } catch (IllegalArgumentException e) { + log(26, x26); + return; + } + try { + acc += step27(x27); + } catch (IllegalArgumentException e) { + log(27, x27); + return; + } + try { + acc += step28(x28); + } catch (IllegalArgumentException e) { + log(28, x28); + return; + } + try { + acc += step29(x29); + } catch (IllegalArgumentException e) { + log(29, x29); + return; + } + try { + acc += step30(x30); + } catch (IllegalArgumentException e) { + log(30, x30); + return; + } + try { + acc += step31(x31); + } catch (IllegalArgumentException e) { + log(31, x31); + return; + } + target(a, b); + } + + static int step0(int x) { + return check0(x) + 0; + } + + static int check0(int x) { + if (x < -1) throw new IllegalArgumentException(); + return x; + } + static int step1(int x) { + return check1(x) + 1; + } + + static int check1(int x) { + if (x < -2) throw new IllegalArgumentException(); + return x; + } + static int step2(int x) { + return check2(x) + 2; + } + + static int check2(int x) { + if (x < -3) throw new IllegalArgumentException(); + return x; + } + static int step3(int x) { + return check3(x) + 3; + } + + static int check3(int x) { + if (x < -4) throw new IllegalArgumentException(); + return x; + } + static int step4(int x) { + return check4(x) + 4; + } + + static int check4(int x) { + if (x < -5) throw new IllegalArgumentException(); + return x; + } + static int step5(int x) { + return check5(x) + 5; + } + + static int check5(int x) { + if (x < -6) throw new IllegalArgumentException(); + return x; + } + static int step6(int x) { + return check6(x) + 6; + } + + static int check6(int x) { + if (x < -7) throw new IllegalArgumentException(); + return x; + } + static int step7(int x) { + return check7(x) + 7; + } + + static int check7(int x) { + if (x < -8) throw new IllegalArgumentException(); + return x; + } + static int step8(int x) { + return check8(x) + 8; + } + + static int check8(int x) { + if (x < -9) throw new IllegalArgumentException(); + return x; + } + static int step9(int x) { + return check9(x) + 9; + } + + static int check9(int x) { + if (x < -10) throw new IllegalArgumentException(); + return x; + } + static int step10(int x) { + return check10(x) + 10; + } + + static int check10(int x) { + if (x < -11) throw new IllegalArgumentException(); + return x; + } + static int step11(int x) { + return check11(x) + 11; + } + + static int check11(int x) { + if (x < -12) throw new IllegalArgumentException(); + return x; + } + static int step12(int x) { + return check12(x) + 12; + } + + static int check12(int x) { + if (x < -13) throw new IllegalArgumentException(); + return x; + } + static int step13(int x) { + return check13(x) + 13; + } + + static int check13(int x) { + if (x < -14) throw new IllegalArgumentException(); + return x; + } + static int step14(int x) { + return check14(x) + 14; + } + + static int check14(int x) { + if (x < -15) throw new IllegalArgumentException(); + return x; + } + static int step15(int x) { + return check15(x) + 15; + } + + static int check15(int x) { + if (x < -16) throw new IllegalArgumentException(); + return x; + } + static int step16(int x) { + return check16(x) + 16; + } + + static int check16(int x) { + if (x < -17) throw new IllegalArgumentException(); + return x; + } + static int step17(int x) { + return check17(x) + 17; + } + + static int check17(int x) { + if (x < -18) throw new IllegalArgumentException(); + return x; + } + static int step18(int x) { + return check18(x) + 18; + } + + static int check18(int x) { + if (x < -19) throw new IllegalArgumentException(); + return x; + } + static int step19(int x) { + return check19(x) + 19; + } + + static int check19(int x) { + if (x < -20) throw new IllegalArgumentException(); + return x; + } + static int step20(int x) { + return check20(x) + 20; + } + + static int check20(int x) { + if (x < -21) throw new IllegalArgumentException(); + return x; + } + static int step21(int x) { + return check21(x) + 21; + } + + static int check21(int x) { + if (x < -22) throw new IllegalArgumentException(); + return x; + } + static int step22(int x) { + return check22(x) + 22; + } + + static int check22(int x) { + if (x < -23) throw new IllegalArgumentException(); + return x; + } + static int step23(int x) { + return check23(x) + 23; + } + + static int check23(int x) { + if (x < -24) throw new IllegalArgumentException(); + return x; + } + static int step24(int x) { + return check24(x) + 24; + } + + static int check24(int x) { + if (x < -25) throw new IllegalArgumentException(); + return x; + } + static int step25(int x) { + return check25(x) + 25; + } + + static int check25(int x) { + if (x < -26) throw new IllegalArgumentException(); + return x; + } + static int step26(int x) { + return check26(x) + 26; + } + + static int check26(int x) { + if (x < -27) throw new IllegalArgumentException(); + return x; + } + static int step27(int x) { + return check27(x) + 27; + } + + static int check27(int x) { + if (x < -28) throw new IllegalArgumentException(); + return x; + } + static int step28(int x) { + return check28(x) + 28; + } + + static int check28(int x) { + if (x < -29) throw new IllegalArgumentException(); + return x; + } + static int step29(int x) { + return check29(x) + 29; + } + + static int check29(int x) { + if (x < -30) throw new IllegalArgumentException(); + return x; + } + static int step30(int x) { + return check30(x) + 30; + } + + static int check30(int x) { + if (x < -31) throw new IllegalArgumentException(); + return x; + } + static int step31(int x) { + return check31(x) + 31; + } + + static int check31(int x) { + if (x < -32) throw new IllegalArgumentException(); + return x; + } + + // Logs a failed step; three branches, no assert. + static void log(int step, int x) { + if (x < -1000) logged += 100; + if (x > 1000) logged += 10; + if (x == step) logged += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n32_true.yml b/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n32_true.yml new file mode 100644 index 00000000..011f9204 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n32_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - deep_n32_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n32_true/Main.java b/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n32_true/Main.java new file mode 100644 index 00000000..debdf3bc --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/deep_n32_true/Main.java @@ -0,0 +1,514 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:1001 int:2996 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// error-handling/deep: 32 steps that can fail with IllegalArgumentException; each catch block logs and returns. +public class Main { + static int logged; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int x0 = Verifier.nondetInt(); + int x1 = Verifier.nondetInt(); + int x2 = Verifier.nondetInt(); + int x3 = Verifier.nondetInt(); + int x4 = Verifier.nondetInt(); + int x5 = Verifier.nondetInt(); + int x6 = Verifier.nondetInt(); + int x7 = Verifier.nondetInt(); + int x8 = Verifier.nondetInt(); + int x9 = Verifier.nondetInt(); + int x10 = Verifier.nondetInt(); + int x11 = Verifier.nondetInt(); + int x12 = Verifier.nondetInt(); + int x13 = Verifier.nondetInt(); + int x14 = Verifier.nondetInt(); + int x15 = Verifier.nondetInt(); + int x16 = Verifier.nondetInt(); + int x17 = Verifier.nondetInt(); + int x18 = Verifier.nondetInt(); + int x19 = Verifier.nondetInt(); + int x20 = Verifier.nondetInt(); + int x21 = Verifier.nondetInt(); + int x22 = Verifier.nondetInt(); + int x23 = Verifier.nondetInt(); + int x24 = Verifier.nondetInt(); + int x25 = Verifier.nondetInt(); + int x26 = Verifier.nondetInt(); + int x27 = Verifier.nondetInt(); + int x28 = Verifier.nondetInt(); + int x29 = Verifier.nondetInt(); + int x30 = Verifier.nondetInt(); + int x31 = Verifier.nondetInt(); + int acc = 0; + try { + acc += step0(x0); + } catch (IllegalArgumentException e) { + log(0, x0); + return; + } + try { + acc += step1(x1); + } catch (IllegalArgumentException e) { + log(1, x1); + return; + } + try { + acc += step2(x2); + } catch (IllegalArgumentException e) { + log(2, x2); + return; + } + try { + acc += step3(x3); + } catch (IllegalArgumentException e) { + log(3, x3); + return; + } + try { + acc += step4(x4); + } catch (IllegalArgumentException e) { + log(4, x4); + return; + } + try { + acc += step5(x5); + } catch (IllegalArgumentException e) { + log(5, x5); + return; + } + try { + acc += step6(x6); + } catch (IllegalArgumentException e) { + log(6, x6); + return; + } + try { + acc += step7(x7); + } catch (IllegalArgumentException e) { + log(7, x7); + return; + } + try { + acc += step8(x8); + } catch (IllegalArgumentException e) { + log(8, x8); + return; + } + try { + acc += step9(x9); + } catch (IllegalArgumentException e) { + log(9, x9); + return; + } + try { + acc += step10(x10); + } catch (IllegalArgumentException e) { + log(10, x10); + return; + } + try { + acc += step11(x11); + } catch (IllegalArgumentException e) { + log(11, x11); + return; + } + try { + acc += step12(x12); + } catch (IllegalArgumentException e) { + log(12, x12); + return; + } + try { + acc += step13(x13); + } catch (IllegalArgumentException e) { + log(13, x13); + return; + } + try { + acc += step14(x14); + } catch (IllegalArgumentException e) { + log(14, x14); + return; + } + try { + acc += step15(x15); + } catch (IllegalArgumentException e) { + log(15, x15); + return; + } + try { + acc += step16(x16); + } catch (IllegalArgumentException e) { + log(16, x16); + return; + } + try { + acc += step17(x17); + } catch (IllegalArgumentException e) { + log(17, x17); + return; + } + try { + acc += step18(x18); + } catch (IllegalArgumentException e) { + log(18, x18); + return; + } + try { + acc += step19(x19); + } catch (IllegalArgumentException e) { + log(19, x19); + return; + } + try { + acc += step20(x20); + } catch (IllegalArgumentException e) { + log(20, x20); + return; + } + try { + acc += step21(x21); + } catch (IllegalArgumentException e) { + log(21, x21); + return; + } + try { + acc += step22(x22); + } catch (IllegalArgumentException e) { + log(22, x22); + return; + } + try { + acc += step23(x23); + } catch (IllegalArgumentException e) { + log(23, x23); + return; + } + try { + acc += step24(x24); + } catch (IllegalArgumentException e) { + log(24, x24); + return; + } + try { + acc += step25(x25); + } catch (IllegalArgumentException e) { + log(25, x25); + return; + } + try { + acc += step26(x26); + } catch (IllegalArgumentException e) { + log(26, x26); + return; + } + try { + acc += step27(x27); + } catch (IllegalArgumentException e) { + log(27, x27); + return; + } + try { + acc += step28(x28); + } catch (IllegalArgumentException e) { + log(28, x28); + return; + } + try { + acc += step29(x29); + } catch (IllegalArgumentException e) { + log(29, x29); + return; + } + try { + acc += step30(x30); + } catch (IllegalArgumentException e) { + log(30, x30); + return; + } + try { + acc += step31(x31); + } catch (IllegalArgumentException e) { + log(31, x31); + return; + } + target(a, b); + } + + static int step0(int x) { + return check0(x) + 0; + } + + static int check0(int x) { + if (x < -1) throw new IllegalArgumentException(); + return x; + } + static int step1(int x) { + return check1(x) + 1; + } + + static int check1(int x) { + if (x < -2) throw new IllegalArgumentException(); + return x; + } + static int step2(int x) { + return check2(x) + 2; + } + + static int check2(int x) { + if (x < -3) throw new IllegalArgumentException(); + return x; + } + static int step3(int x) { + return check3(x) + 3; + } + + static int check3(int x) { + if (x < -4) throw new IllegalArgumentException(); + return x; + } + static int step4(int x) { + return check4(x) + 4; + } + + static int check4(int x) { + if (x < -5) throw new IllegalArgumentException(); + return x; + } + static int step5(int x) { + return check5(x) + 5; + } + + static int check5(int x) { + if (x < -6) throw new IllegalArgumentException(); + return x; + } + static int step6(int x) { + return check6(x) + 6; + } + + static int check6(int x) { + if (x < -7) throw new IllegalArgumentException(); + return x; + } + static int step7(int x) { + return check7(x) + 7; + } + + static int check7(int x) { + if (x < -8) throw new IllegalArgumentException(); + return x; + } + static int step8(int x) { + return check8(x) + 8; + } + + static int check8(int x) { + if (x < -9) throw new IllegalArgumentException(); + return x; + } + static int step9(int x) { + return check9(x) + 9; + } + + static int check9(int x) { + if (x < -10) throw new IllegalArgumentException(); + return x; + } + static int step10(int x) { + return check10(x) + 10; + } + + static int check10(int x) { + if (x < -11) throw new IllegalArgumentException(); + return x; + } + static int step11(int x) { + return check11(x) + 11; + } + + static int check11(int x) { + if (x < -12) throw new IllegalArgumentException(); + return x; + } + static int step12(int x) { + return check12(x) + 12; + } + + static int check12(int x) { + if (x < -13) throw new IllegalArgumentException(); + return x; + } + static int step13(int x) { + return check13(x) + 13; + } + + static int check13(int x) { + if (x < -14) throw new IllegalArgumentException(); + return x; + } + static int step14(int x) { + return check14(x) + 14; + } + + static int check14(int x) { + if (x < -15) throw new IllegalArgumentException(); + return x; + } + static int step15(int x) { + return check15(x) + 15; + } + + static int check15(int x) { + if (x < -16) throw new IllegalArgumentException(); + return x; + } + static int step16(int x) { + return check16(x) + 16; + } + + static int check16(int x) { + if (x < -17) throw new IllegalArgumentException(); + return x; + } + static int step17(int x) { + return check17(x) + 17; + } + + static int check17(int x) { + if (x < -18) throw new IllegalArgumentException(); + return x; + } + static int step18(int x) { + return check18(x) + 18; + } + + static int check18(int x) { + if (x < -19) throw new IllegalArgumentException(); + return x; + } + static int step19(int x) { + return check19(x) + 19; + } + + static int check19(int x) { + if (x < -20) throw new IllegalArgumentException(); + return x; + } + static int step20(int x) { + return check20(x) + 20; + } + + static int check20(int x) { + if (x < -21) throw new IllegalArgumentException(); + return x; + } + static int step21(int x) { + return check21(x) + 21; + } + + static int check21(int x) { + if (x < -22) throw new IllegalArgumentException(); + return x; + } + static int step22(int x) { + return check22(x) + 22; + } + + static int check22(int x) { + if (x < -23) throw new IllegalArgumentException(); + return x; + } + static int step23(int x) { + return check23(x) + 23; + } + + static int check23(int x) { + if (x < -24) throw new IllegalArgumentException(); + return x; + } + static int step24(int x) { + return check24(x) + 24; + } + + static int check24(int x) { + if (x < -25) throw new IllegalArgumentException(); + return x; + } + static int step25(int x) { + return check25(x) + 25; + } + + static int check25(int x) { + if (x < -26) throw new IllegalArgumentException(); + return x; + } + static int step26(int x) { + return check26(x) + 26; + } + + static int check26(int x) { + if (x < -27) throw new IllegalArgumentException(); + return x; + } + static int step27(int x) { + return check27(x) + 27; + } + + static int check27(int x) { + if (x < -28) throw new IllegalArgumentException(); + return x; + } + static int step28(int x) { + return check28(x) + 28; + } + + static int check28(int x) { + if (x < -29) throw new IllegalArgumentException(); + return x; + } + static int step29(int x) { + return check29(x) + 29; + } + + static int check29(int x) { + if (x < -30) throw new IllegalArgumentException(); + return x; + } + static int step30(int x) { + return check30(x) + 30; + } + + static int check30(int x) { + if (x < -31) throw new IllegalArgumentException(); + return x; + } + static int step31(int x) { + return check31(x) + 31; + } + + static int check31(int x) { + if (x < -32) throw new IllegalArgumentException(); + return x; + } + + // Logs a failed step; three branches, no assert. + static void log(int step, int x) { + if (x < -1000) logged += 100; + if (x > 1000) logged += 10; + if (x == step) logged += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n02_false.yml b/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n02_false.yml new file mode 100644 index 00000000..44ff6d22 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n02_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - explicit_n02_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n02_false/Main.java b/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n02_false/Main.java new file mode 100644 index 00000000..9b8eb986 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n02_false/Main.java @@ -0,0 +1,55 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// error-handling/explicit: 2 steps that can fail with IllegalArgumentException; each catch block logs and returns. +public class Main { + static int logged; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int x0 = Verifier.nondetInt(); + int x1 = Verifier.nondetInt(); + int acc = 0; + try { + acc += step0(x0); + } catch (IllegalArgumentException e) { + log(0, x0); + return; + } + try { + acc += step1(x1); + } catch (IllegalArgumentException e) { + log(1, x1); + return; + } + target(a, b); + } + + static int step0(int x) { + if (x < -1) throw new IllegalArgumentException(); + return x + 0; + } + static int step1(int x) { + if (x < -2) throw new IllegalArgumentException(); + return x + 1; + } + + // Logs a failed step; three branches, no assert. + static void log(int step, int x) { + if (x < -1000) logged += 100; + if (x > 1000) logged += 10; + if (x == step) logged += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n02_true.yml b/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n02_true.yml new file mode 100644 index 00000000..68be1aa6 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n02_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - explicit_n02_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n02_true/Main.java b/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n02_true/Main.java new file mode 100644 index 00000000..8365ae41 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n02_true/Main.java @@ -0,0 +1,56 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// error-handling/explicit: 2 steps that can fail with IllegalArgumentException; each catch block logs and returns. +public class Main { + static int logged; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int x0 = Verifier.nondetInt(); + int x1 = Verifier.nondetInt(); + int acc = 0; + try { + acc += step0(x0); + } catch (IllegalArgumentException e) { + log(0, x0); + return; + } + try { + acc += step1(x1); + } catch (IllegalArgumentException e) { + log(1, x1); + return; + } + target(a, b); + } + + static int step0(int x) { + if (x < -1) throw new IllegalArgumentException(); + return x + 0; + } + static int step1(int x) { + if (x < -2) throw new IllegalArgumentException(); + return x + 1; + } + + // Logs a failed step; three branches, no assert. + static void log(int step, int x) { + if (x < -1000) logged += 100; + if (x > 1000) logged += 10; + if (x == step) logged += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n04_false.yml b/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n04_false.yml new file mode 100644 index 00000000..7f1320b6 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n04_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - explicit_n04_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n04_false/Main.java b/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n04_false/Main.java new file mode 100644 index 00000000..cdcf5363 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n04_false/Main.java @@ -0,0 +1,77 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:2996 int:0 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// error-handling/explicit: 4 steps that can fail with IllegalArgumentException; each catch block logs and returns. +public class Main { + static int logged; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int x0 = Verifier.nondetInt(); + int x1 = Verifier.nondetInt(); + int x2 = Verifier.nondetInt(); + int x3 = Verifier.nondetInt(); + int acc = 0; + try { + acc += step0(x0); + } catch (IllegalArgumentException e) { + log(0, x0); + return; + } + try { + acc += step1(x1); + } catch (IllegalArgumentException e) { + log(1, x1); + return; + } + try { + acc += step2(x2); + } catch (IllegalArgumentException e) { + log(2, x2); + return; + } + try { + acc += step3(x3); + } catch (IllegalArgumentException e) { + log(3, x3); + return; + } + target(a, b); + } + + static int step0(int x) { + if (x < -1) throw new IllegalArgumentException(); + return x + 0; + } + static int step1(int x) { + if (x < -2) throw new IllegalArgumentException(); + return x + 1; + } + static int step2(int x) { + if (x < -3) throw new IllegalArgumentException(); + return x + 2; + } + static int step3(int x) { + if (x < -4) throw new IllegalArgumentException(); + return x + 3; + } + + // Logs a failed step; three branches, no assert. + static void log(int step, int x) { + if (x < -1000) logged += 100; + if (x > 1000) logged += 10; + if (x == step) logged += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n04_true.yml b/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n04_true.yml new file mode 100644 index 00000000..b195a91d --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n04_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - explicit_n04_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n04_true/Main.java b/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n04_true/Main.java new file mode 100644 index 00000000..e4fd365a --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n04_true/Main.java @@ -0,0 +1,78 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:1001 int:2996 int:0 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// error-handling/explicit: 4 steps that can fail with IllegalArgumentException; each catch block logs and returns. +public class Main { + static int logged; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int x0 = Verifier.nondetInt(); + int x1 = Verifier.nondetInt(); + int x2 = Verifier.nondetInt(); + int x3 = Verifier.nondetInt(); + int acc = 0; + try { + acc += step0(x0); + } catch (IllegalArgumentException e) { + log(0, x0); + return; + } + try { + acc += step1(x1); + } catch (IllegalArgumentException e) { + log(1, x1); + return; + } + try { + acc += step2(x2); + } catch (IllegalArgumentException e) { + log(2, x2); + return; + } + try { + acc += step3(x3); + } catch (IllegalArgumentException e) { + log(3, x3); + return; + } + target(a, b); + } + + static int step0(int x) { + if (x < -1) throw new IllegalArgumentException(); + return x + 0; + } + static int step1(int x) { + if (x < -2) throw new IllegalArgumentException(); + return x + 1; + } + static int step2(int x) { + if (x < -3) throw new IllegalArgumentException(); + return x + 2; + } + static int step3(int x) { + if (x < -4) throw new IllegalArgumentException(); + return x + 3; + } + + // Logs a failed step; three branches, no assert. + static void log(int step, int x) { + if (x < -1000) logged += 100; + if (x > 1000) logged += 10; + if (x == step) logged += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n08_false.yml b/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n08_false.yml new file mode 100644 index 00000000..552c8fb9 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n08_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - explicit_n08_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n08_false/Main.java b/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n08_false/Main.java new file mode 100644 index 00000000..c51d0fb3 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n08_false/Main.java @@ -0,0 +1,121 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:2996 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// error-handling/explicit: 8 steps that can fail with IllegalArgumentException; each catch block logs and returns. +public class Main { + static int logged; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int x0 = Verifier.nondetInt(); + int x1 = Verifier.nondetInt(); + int x2 = Verifier.nondetInt(); + int x3 = Verifier.nondetInt(); + int x4 = Verifier.nondetInt(); + int x5 = Verifier.nondetInt(); + int x6 = Verifier.nondetInt(); + int x7 = Verifier.nondetInt(); + int acc = 0; + try { + acc += step0(x0); + } catch (IllegalArgumentException e) { + log(0, x0); + return; + } + try { + acc += step1(x1); + } catch (IllegalArgumentException e) { + log(1, x1); + return; + } + try { + acc += step2(x2); + } catch (IllegalArgumentException e) { + log(2, x2); + return; + } + try { + acc += step3(x3); + } catch (IllegalArgumentException e) { + log(3, x3); + return; + } + try { + acc += step4(x4); + } catch (IllegalArgumentException e) { + log(4, x4); + return; + } + try { + acc += step5(x5); + } catch (IllegalArgumentException e) { + log(5, x5); + return; + } + try { + acc += step6(x6); + } catch (IllegalArgumentException e) { + log(6, x6); + return; + } + try { + acc += step7(x7); + } catch (IllegalArgumentException e) { + log(7, x7); + return; + } + target(a, b); + } + + static int step0(int x) { + if (x < -1) throw new IllegalArgumentException(); + return x + 0; + } + static int step1(int x) { + if (x < -2) throw new IllegalArgumentException(); + return x + 1; + } + static int step2(int x) { + if (x < -3) throw new IllegalArgumentException(); + return x + 2; + } + static int step3(int x) { + if (x < -4) throw new IllegalArgumentException(); + return x + 3; + } + static int step4(int x) { + if (x < -5) throw new IllegalArgumentException(); + return x + 4; + } + static int step5(int x) { + if (x < -6) throw new IllegalArgumentException(); + return x + 5; + } + static int step6(int x) { + if (x < -7) throw new IllegalArgumentException(); + return x + 6; + } + static int step7(int x) { + if (x < -8) throw new IllegalArgumentException(); + return x + 7; + } + + // Logs a failed step; three branches, no assert. + static void log(int step, int x) { + if (x < -1000) logged += 100; + if (x > 1000) logged += 10; + if (x == step) logged += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n08_true.yml b/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n08_true.yml new file mode 100644 index 00000000..2aa65aab --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n08_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - explicit_n08_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n08_true/Main.java b/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n08_true/Main.java new file mode 100644 index 00000000..91e3e34f --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n08_true/Main.java @@ -0,0 +1,122 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:1001 int:2996 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// error-handling/explicit: 8 steps that can fail with IllegalArgumentException; each catch block logs and returns. +public class Main { + static int logged; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int x0 = Verifier.nondetInt(); + int x1 = Verifier.nondetInt(); + int x2 = Verifier.nondetInt(); + int x3 = Verifier.nondetInt(); + int x4 = Verifier.nondetInt(); + int x5 = Verifier.nondetInt(); + int x6 = Verifier.nondetInt(); + int x7 = Verifier.nondetInt(); + int acc = 0; + try { + acc += step0(x0); + } catch (IllegalArgumentException e) { + log(0, x0); + return; + } + try { + acc += step1(x1); + } catch (IllegalArgumentException e) { + log(1, x1); + return; + } + try { + acc += step2(x2); + } catch (IllegalArgumentException e) { + log(2, x2); + return; + } + try { + acc += step3(x3); + } catch (IllegalArgumentException e) { + log(3, x3); + return; + } + try { + acc += step4(x4); + } catch (IllegalArgumentException e) { + log(4, x4); + return; + } + try { + acc += step5(x5); + } catch (IllegalArgumentException e) { + log(5, x5); + return; + } + try { + acc += step6(x6); + } catch (IllegalArgumentException e) { + log(6, x6); + return; + } + try { + acc += step7(x7); + } catch (IllegalArgumentException e) { + log(7, x7); + return; + } + target(a, b); + } + + static int step0(int x) { + if (x < -1) throw new IllegalArgumentException(); + return x + 0; + } + static int step1(int x) { + if (x < -2) throw new IllegalArgumentException(); + return x + 1; + } + static int step2(int x) { + if (x < -3) throw new IllegalArgumentException(); + return x + 2; + } + static int step3(int x) { + if (x < -4) throw new IllegalArgumentException(); + return x + 3; + } + static int step4(int x) { + if (x < -5) throw new IllegalArgumentException(); + return x + 4; + } + static int step5(int x) { + if (x < -6) throw new IllegalArgumentException(); + return x + 5; + } + static int step6(int x) { + if (x < -7) throw new IllegalArgumentException(); + return x + 6; + } + static int step7(int x) { + if (x < -8) throw new IllegalArgumentException(); + return x + 7; + } + + // Logs a failed step; three branches, no assert. + static void log(int step, int x) { + if (x < -1000) logged += 100; + if (x > 1000) logged += 10; + if (x == step) logged += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n16_false.yml b/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n16_false.yml new file mode 100644 index 00000000..56f9f93c --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n16_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - explicit_n16_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n16_false/Main.java b/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n16_false/Main.java new file mode 100644 index 00000000..7b86bb90 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n16_false/Main.java @@ -0,0 +1,209 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:2996 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// error-handling/explicit: 16 steps that can fail with IllegalArgumentException; each catch block logs and returns. +public class Main { + static int logged; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int x0 = Verifier.nondetInt(); + int x1 = Verifier.nondetInt(); + int x2 = Verifier.nondetInt(); + int x3 = Verifier.nondetInt(); + int x4 = Verifier.nondetInt(); + int x5 = Verifier.nondetInt(); + int x6 = Verifier.nondetInt(); + int x7 = Verifier.nondetInt(); + int x8 = Verifier.nondetInt(); + int x9 = Verifier.nondetInt(); + int x10 = Verifier.nondetInt(); + int x11 = Verifier.nondetInt(); + int x12 = Verifier.nondetInt(); + int x13 = Verifier.nondetInt(); + int x14 = Verifier.nondetInt(); + int x15 = Verifier.nondetInt(); + int acc = 0; + try { + acc += step0(x0); + } catch (IllegalArgumentException e) { + log(0, x0); + return; + } + try { + acc += step1(x1); + } catch (IllegalArgumentException e) { + log(1, x1); + return; + } + try { + acc += step2(x2); + } catch (IllegalArgumentException e) { + log(2, x2); + return; + } + try { + acc += step3(x3); + } catch (IllegalArgumentException e) { + log(3, x3); + return; + } + try { + acc += step4(x4); + } catch (IllegalArgumentException e) { + log(4, x4); + return; + } + try { + acc += step5(x5); + } catch (IllegalArgumentException e) { + log(5, x5); + return; + } + try { + acc += step6(x6); + } catch (IllegalArgumentException e) { + log(6, x6); + return; + } + try { + acc += step7(x7); + } catch (IllegalArgumentException e) { + log(7, x7); + return; + } + try { + acc += step8(x8); + } catch (IllegalArgumentException e) { + log(8, x8); + return; + } + try { + acc += step9(x9); + } catch (IllegalArgumentException e) { + log(9, x9); + return; + } + try { + acc += step10(x10); + } catch (IllegalArgumentException e) { + log(10, x10); + return; + } + try { + acc += step11(x11); + } catch (IllegalArgumentException e) { + log(11, x11); + return; + } + try { + acc += step12(x12); + } catch (IllegalArgumentException e) { + log(12, x12); + return; + } + try { + acc += step13(x13); + } catch (IllegalArgumentException e) { + log(13, x13); + return; + } + try { + acc += step14(x14); + } catch (IllegalArgumentException e) { + log(14, x14); + return; + } + try { + acc += step15(x15); + } catch (IllegalArgumentException e) { + log(15, x15); + return; + } + target(a, b); + } + + static int step0(int x) { + if (x < -1) throw new IllegalArgumentException(); + return x + 0; + } + static int step1(int x) { + if (x < -2) throw new IllegalArgumentException(); + return x + 1; + } + static int step2(int x) { + if (x < -3) throw new IllegalArgumentException(); + return x + 2; + } + static int step3(int x) { + if (x < -4) throw new IllegalArgumentException(); + return x + 3; + } + static int step4(int x) { + if (x < -5) throw new IllegalArgumentException(); + return x + 4; + } + static int step5(int x) { + if (x < -6) throw new IllegalArgumentException(); + return x + 5; + } + static int step6(int x) { + if (x < -7) throw new IllegalArgumentException(); + return x + 6; + } + static int step7(int x) { + if (x < -8) throw new IllegalArgumentException(); + return x + 7; + } + static int step8(int x) { + if (x < -9) throw new IllegalArgumentException(); + return x + 8; + } + static int step9(int x) { + if (x < -10) throw new IllegalArgumentException(); + return x + 9; + } + static int step10(int x) { + if (x < -11) throw new IllegalArgumentException(); + return x + 10; + } + static int step11(int x) { + if (x < -12) throw new IllegalArgumentException(); + return x + 11; + } + static int step12(int x) { + if (x < -13) throw new IllegalArgumentException(); + return x + 12; + } + static int step13(int x) { + if (x < -14) throw new IllegalArgumentException(); + return x + 13; + } + static int step14(int x) { + if (x < -15) throw new IllegalArgumentException(); + return x + 14; + } + static int step15(int x) { + if (x < -16) throw new IllegalArgumentException(); + return x + 15; + } + + // Logs a failed step; three branches, no assert. + static void log(int step, int x) { + if (x < -1000) logged += 100; + if (x > 1000) logged += 10; + if (x == step) logged += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n16_true.yml b/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n16_true.yml new file mode 100644 index 00000000..1b49a662 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n16_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - explicit_n16_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n16_true/Main.java b/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n16_true/Main.java new file mode 100644 index 00000000..2e433deb --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n16_true/Main.java @@ -0,0 +1,210 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:1001 int:2996 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// error-handling/explicit: 16 steps that can fail with IllegalArgumentException; each catch block logs and returns. +public class Main { + static int logged; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int x0 = Verifier.nondetInt(); + int x1 = Verifier.nondetInt(); + int x2 = Verifier.nondetInt(); + int x3 = Verifier.nondetInt(); + int x4 = Verifier.nondetInt(); + int x5 = Verifier.nondetInt(); + int x6 = Verifier.nondetInt(); + int x7 = Verifier.nondetInt(); + int x8 = Verifier.nondetInt(); + int x9 = Verifier.nondetInt(); + int x10 = Verifier.nondetInt(); + int x11 = Verifier.nondetInt(); + int x12 = Verifier.nondetInt(); + int x13 = Verifier.nondetInt(); + int x14 = Verifier.nondetInt(); + int x15 = Verifier.nondetInt(); + int acc = 0; + try { + acc += step0(x0); + } catch (IllegalArgumentException e) { + log(0, x0); + return; + } + try { + acc += step1(x1); + } catch (IllegalArgumentException e) { + log(1, x1); + return; + } + try { + acc += step2(x2); + } catch (IllegalArgumentException e) { + log(2, x2); + return; + } + try { + acc += step3(x3); + } catch (IllegalArgumentException e) { + log(3, x3); + return; + } + try { + acc += step4(x4); + } catch (IllegalArgumentException e) { + log(4, x4); + return; + } + try { + acc += step5(x5); + } catch (IllegalArgumentException e) { + log(5, x5); + return; + } + try { + acc += step6(x6); + } catch (IllegalArgumentException e) { + log(6, x6); + return; + } + try { + acc += step7(x7); + } catch (IllegalArgumentException e) { + log(7, x7); + return; + } + try { + acc += step8(x8); + } catch (IllegalArgumentException e) { + log(8, x8); + return; + } + try { + acc += step9(x9); + } catch (IllegalArgumentException e) { + log(9, x9); + return; + } + try { + acc += step10(x10); + } catch (IllegalArgumentException e) { + log(10, x10); + return; + } + try { + acc += step11(x11); + } catch (IllegalArgumentException e) { + log(11, x11); + return; + } + try { + acc += step12(x12); + } catch (IllegalArgumentException e) { + log(12, x12); + return; + } + try { + acc += step13(x13); + } catch (IllegalArgumentException e) { + log(13, x13); + return; + } + try { + acc += step14(x14); + } catch (IllegalArgumentException e) { + log(14, x14); + return; + } + try { + acc += step15(x15); + } catch (IllegalArgumentException e) { + log(15, x15); + return; + } + target(a, b); + } + + static int step0(int x) { + if (x < -1) throw new IllegalArgumentException(); + return x + 0; + } + static int step1(int x) { + if (x < -2) throw new IllegalArgumentException(); + return x + 1; + } + static int step2(int x) { + if (x < -3) throw new IllegalArgumentException(); + return x + 2; + } + static int step3(int x) { + if (x < -4) throw new IllegalArgumentException(); + return x + 3; + } + static int step4(int x) { + if (x < -5) throw new IllegalArgumentException(); + return x + 4; + } + static int step5(int x) { + if (x < -6) throw new IllegalArgumentException(); + return x + 5; + } + static int step6(int x) { + if (x < -7) throw new IllegalArgumentException(); + return x + 6; + } + static int step7(int x) { + if (x < -8) throw new IllegalArgumentException(); + return x + 7; + } + static int step8(int x) { + if (x < -9) throw new IllegalArgumentException(); + return x + 8; + } + static int step9(int x) { + if (x < -10) throw new IllegalArgumentException(); + return x + 9; + } + static int step10(int x) { + if (x < -11) throw new IllegalArgumentException(); + return x + 10; + } + static int step11(int x) { + if (x < -12) throw new IllegalArgumentException(); + return x + 11; + } + static int step12(int x) { + if (x < -13) throw new IllegalArgumentException(); + return x + 12; + } + static int step13(int x) { + if (x < -14) throw new IllegalArgumentException(); + return x + 13; + } + static int step14(int x) { + if (x < -15) throw new IllegalArgumentException(); + return x + 14; + } + static int step15(int x) { + if (x < -16) throw new IllegalArgumentException(); + return x + 15; + } + + // Logs a failed step; three branches, no assert. + static void log(int step, int x) { + if (x < -1000) logged += 100; + if (x > 1000) logged += 10; + if (x == step) logged += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n32_false.yml b/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n32_false.yml new file mode 100644 index 00000000..74c13342 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n32_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - explicit_n32_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n32_false/Main.java b/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n32_false/Main.java new file mode 100644 index 00000000..f983bc39 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n32_false/Main.java @@ -0,0 +1,385 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:2996 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// error-handling/explicit: 32 steps that can fail with IllegalArgumentException; each catch block logs and returns. +public class Main { + static int logged; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int x0 = Verifier.nondetInt(); + int x1 = Verifier.nondetInt(); + int x2 = Verifier.nondetInt(); + int x3 = Verifier.nondetInt(); + int x4 = Verifier.nondetInt(); + int x5 = Verifier.nondetInt(); + int x6 = Verifier.nondetInt(); + int x7 = Verifier.nondetInt(); + int x8 = Verifier.nondetInt(); + int x9 = Verifier.nondetInt(); + int x10 = Verifier.nondetInt(); + int x11 = Verifier.nondetInt(); + int x12 = Verifier.nondetInt(); + int x13 = Verifier.nondetInt(); + int x14 = Verifier.nondetInt(); + int x15 = Verifier.nondetInt(); + int x16 = Verifier.nondetInt(); + int x17 = Verifier.nondetInt(); + int x18 = Verifier.nondetInt(); + int x19 = Verifier.nondetInt(); + int x20 = Verifier.nondetInt(); + int x21 = Verifier.nondetInt(); + int x22 = Verifier.nondetInt(); + int x23 = Verifier.nondetInt(); + int x24 = Verifier.nondetInt(); + int x25 = Verifier.nondetInt(); + int x26 = Verifier.nondetInt(); + int x27 = Verifier.nondetInt(); + int x28 = Verifier.nondetInt(); + int x29 = Verifier.nondetInt(); + int x30 = Verifier.nondetInt(); + int x31 = Verifier.nondetInt(); + int acc = 0; + try { + acc += step0(x0); + } catch (IllegalArgumentException e) { + log(0, x0); + return; + } + try { + acc += step1(x1); + } catch (IllegalArgumentException e) { + log(1, x1); + return; + } + try { + acc += step2(x2); + } catch (IllegalArgumentException e) { + log(2, x2); + return; + } + try { + acc += step3(x3); + } catch (IllegalArgumentException e) { + log(3, x3); + return; + } + try { + acc += step4(x4); + } catch (IllegalArgumentException e) { + log(4, x4); + return; + } + try { + acc += step5(x5); + } catch (IllegalArgumentException e) { + log(5, x5); + return; + } + try { + acc += step6(x6); + } catch (IllegalArgumentException e) { + log(6, x6); + return; + } + try { + acc += step7(x7); + } catch (IllegalArgumentException e) { + log(7, x7); + return; + } + try { + acc += step8(x8); + } catch (IllegalArgumentException e) { + log(8, x8); + return; + } + try { + acc += step9(x9); + } catch (IllegalArgumentException e) { + log(9, x9); + return; + } + try { + acc += step10(x10); + } catch (IllegalArgumentException e) { + log(10, x10); + return; + } + try { + acc += step11(x11); + } catch (IllegalArgumentException e) { + log(11, x11); + return; + } + try { + acc += step12(x12); + } catch (IllegalArgumentException e) { + log(12, x12); + return; + } + try { + acc += step13(x13); + } catch (IllegalArgumentException e) { + log(13, x13); + return; + } + try { + acc += step14(x14); + } catch (IllegalArgumentException e) { + log(14, x14); + return; + } + try { + acc += step15(x15); + } catch (IllegalArgumentException e) { + log(15, x15); + return; + } + try { + acc += step16(x16); + } catch (IllegalArgumentException e) { + log(16, x16); + return; + } + try { + acc += step17(x17); + } catch (IllegalArgumentException e) { + log(17, x17); + return; + } + try { + acc += step18(x18); + } catch (IllegalArgumentException e) { + log(18, x18); + return; + } + try { + acc += step19(x19); + } catch (IllegalArgumentException e) { + log(19, x19); + return; + } + try { + acc += step20(x20); + } catch (IllegalArgumentException e) { + log(20, x20); + return; + } + try { + acc += step21(x21); + } catch (IllegalArgumentException e) { + log(21, x21); + return; + } + try { + acc += step22(x22); + } catch (IllegalArgumentException e) { + log(22, x22); + return; + } + try { + acc += step23(x23); + } catch (IllegalArgumentException e) { + log(23, x23); + return; + } + try { + acc += step24(x24); + } catch (IllegalArgumentException e) { + log(24, x24); + return; + } + try { + acc += step25(x25); + } catch (IllegalArgumentException e) { + log(25, x25); + return; + } + try { + acc += step26(x26); + } catch (IllegalArgumentException e) { + log(26, x26); + return; + } + try { + acc += step27(x27); + } catch (IllegalArgumentException e) { + log(27, x27); + return; + } + try { + acc += step28(x28); + } catch (IllegalArgumentException e) { + log(28, x28); + return; + } + try { + acc += step29(x29); + } catch (IllegalArgumentException e) { + log(29, x29); + return; + } + try { + acc += step30(x30); + } catch (IllegalArgumentException e) { + log(30, x30); + return; + } + try { + acc += step31(x31); + } catch (IllegalArgumentException e) { + log(31, x31); + return; + } + target(a, b); + } + + static int step0(int x) { + if (x < -1) throw new IllegalArgumentException(); + return x + 0; + } + static int step1(int x) { + if (x < -2) throw new IllegalArgumentException(); + return x + 1; + } + static int step2(int x) { + if (x < -3) throw new IllegalArgumentException(); + return x + 2; + } + static int step3(int x) { + if (x < -4) throw new IllegalArgumentException(); + return x + 3; + } + static int step4(int x) { + if (x < -5) throw new IllegalArgumentException(); + return x + 4; + } + static int step5(int x) { + if (x < -6) throw new IllegalArgumentException(); + return x + 5; + } + static int step6(int x) { + if (x < -7) throw new IllegalArgumentException(); + return x + 6; + } + static int step7(int x) { + if (x < -8) throw new IllegalArgumentException(); + return x + 7; + } + static int step8(int x) { + if (x < -9) throw new IllegalArgumentException(); + return x + 8; + } + static int step9(int x) { + if (x < -10) throw new IllegalArgumentException(); + return x + 9; + } + static int step10(int x) { + if (x < -11) throw new IllegalArgumentException(); + return x + 10; + } + static int step11(int x) { + if (x < -12) throw new IllegalArgumentException(); + return x + 11; + } + static int step12(int x) { + if (x < -13) throw new IllegalArgumentException(); + return x + 12; + } + static int step13(int x) { + if (x < -14) throw new IllegalArgumentException(); + return x + 13; + } + static int step14(int x) { + if (x < -15) throw new IllegalArgumentException(); + return x + 14; + } + static int step15(int x) { + if (x < -16) throw new IllegalArgumentException(); + return x + 15; + } + static int step16(int x) { + if (x < -17) throw new IllegalArgumentException(); + return x + 16; + } + static int step17(int x) { + if (x < -18) throw new IllegalArgumentException(); + return x + 17; + } + static int step18(int x) { + if (x < -19) throw new IllegalArgumentException(); + return x + 18; + } + static int step19(int x) { + if (x < -20) throw new IllegalArgumentException(); + return x + 19; + } + static int step20(int x) { + if (x < -21) throw new IllegalArgumentException(); + return x + 20; + } + static int step21(int x) { + if (x < -22) throw new IllegalArgumentException(); + return x + 21; + } + static int step22(int x) { + if (x < -23) throw new IllegalArgumentException(); + return x + 22; + } + static int step23(int x) { + if (x < -24) throw new IllegalArgumentException(); + return x + 23; + } + static int step24(int x) { + if (x < -25) throw new IllegalArgumentException(); + return x + 24; + } + static int step25(int x) { + if (x < -26) throw new IllegalArgumentException(); + return x + 25; + } + static int step26(int x) { + if (x < -27) throw new IllegalArgumentException(); + return x + 26; + } + static int step27(int x) { + if (x < -28) throw new IllegalArgumentException(); + return x + 27; + } + static int step28(int x) { + if (x < -29) throw new IllegalArgumentException(); + return x + 28; + } + static int step29(int x) { + if (x < -30) throw new IllegalArgumentException(); + return x + 29; + } + static int step30(int x) { + if (x < -31) throw new IllegalArgumentException(); + return x + 30; + } + static int step31(int x) { + if (x < -32) throw new IllegalArgumentException(); + return x + 31; + } + + // Logs a failed step; three branches, no assert. + static void log(int step, int x) { + if (x < -1000) logged += 100; + if (x > 1000) logged += 10; + if (x == step) logged += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n32_true.yml b/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n32_true.yml new file mode 100644 index 00000000..6bd03d60 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n32_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - explicit_n32_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n32_true/Main.java b/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n32_true/Main.java new file mode 100644 index 00000000..eb477a81 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/error-handling/explicit_n32_true/Main.java @@ -0,0 +1,386 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:1001 int:2996 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// error-handling/explicit: 32 steps that can fail with IllegalArgumentException; each catch block logs and returns. +public class Main { + static int logged; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int x0 = Verifier.nondetInt(); + int x1 = Verifier.nondetInt(); + int x2 = Verifier.nondetInt(); + int x3 = Verifier.nondetInt(); + int x4 = Verifier.nondetInt(); + int x5 = Verifier.nondetInt(); + int x6 = Verifier.nondetInt(); + int x7 = Verifier.nondetInt(); + int x8 = Verifier.nondetInt(); + int x9 = Verifier.nondetInt(); + int x10 = Verifier.nondetInt(); + int x11 = Verifier.nondetInt(); + int x12 = Verifier.nondetInt(); + int x13 = Verifier.nondetInt(); + int x14 = Verifier.nondetInt(); + int x15 = Verifier.nondetInt(); + int x16 = Verifier.nondetInt(); + int x17 = Verifier.nondetInt(); + int x18 = Verifier.nondetInt(); + int x19 = Verifier.nondetInt(); + int x20 = Verifier.nondetInt(); + int x21 = Verifier.nondetInt(); + int x22 = Verifier.nondetInt(); + int x23 = Verifier.nondetInt(); + int x24 = Verifier.nondetInt(); + int x25 = Verifier.nondetInt(); + int x26 = Verifier.nondetInt(); + int x27 = Verifier.nondetInt(); + int x28 = Verifier.nondetInt(); + int x29 = Verifier.nondetInt(); + int x30 = Verifier.nondetInt(); + int x31 = Verifier.nondetInt(); + int acc = 0; + try { + acc += step0(x0); + } catch (IllegalArgumentException e) { + log(0, x0); + return; + } + try { + acc += step1(x1); + } catch (IllegalArgumentException e) { + log(1, x1); + return; + } + try { + acc += step2(x2); + } catch (IllegalArgumentException e) { + log(2, x2); + return; + } + try { + acc += step3(x3); + } catch (IllegalArgumentException e) { + log(3, x3); + return; + } + try { + acc += step4(x4); + } catch (IllegalArgumentException e) { + log(4, x4); + return; + } + try { + acc += step5(x5); + } catch (IllegalArgumentException e) { + log(5, x5); + return; + } + try { + acc += step6(x6); + } catch (IllegalArgumentException e) { + log(6, x6); + return; + } + try { + acc += step7(x7); + } catch (IllegalArgumentException e) { + log(7, x7); + return; + } + try { + acc += step8(x8); + } catch (IllegalArgumentException e) { + log(8, x8); + return; + } + try { + acc += step9(x9); + } catch (IllegalArgumentException e) { + log(9, x9); + return; + } + try { + acc += step10(x10); + } catch (IllegalArgumentException e) { + log(10, x10); + return; + } + try { + acc += step11(x11); + } catch (IllegalArgumentException e) { + log(11, x11); + return; + } + try { + acc += step12(x12); + } catch (IllegalArgumentException e) { + log(12, x12); + return; + } + try { + acc += step13(x13); + } catch (IllegalArgumentException e) { + log(13, x13); + return; + } + try { + acc += step14(x14); + } catch (IllegalArgumentException e) { + log(14, x14); + return; + } + try { + acc += step15(x15); + } catch (IllegalArgumentException e) { + log(15, x15); + return; + } + try { + acc += step16(x16); + } catch (IllegalArgumentException e) { + log(16, x16); + return; + } + try { + acc += step17(x17); + } catch (IllegalArgumentException e) { + log(17, x17); + return; + } + try { + acc += step18(x18); + } catch (IllegalArgumentException e) { + log(18, x18); + return; + } + try { + acc += step19(x19); + } catch (IllegalArgumentException e) { + log(19, x19); + return; + } + try { + acc += step20(x20); + } catch (IllegalArgumentException e) { + log(20, x20); + return; + } + try { + acc += step21(x21); + } catch (IllegalArgumentException e) { + log(21, x21); + return; + } + try { + acc += step22(x22); + } catch (IllegalArgumentException e) { + log(22, x22); + return; + } + try { + acc += step23(x23); + } catch (IllegalArgumentException e) { + log(23, x23); + return; + } + try { + acc += step24(x24); + } catch (IllegalArgumentException e) { + log(24, x24); + return; + } + try { + acc += step25(x25); + } catch (IllegalArgumentException e) { + log(25, x25); + return; + } + try { + acc += step26(x26); + } catch (IllegalArgumentException e) { + log(26, x26); + return; + } + try { + acc += step27(x27); + } catch (IllegalArgumentException e) { + log(27, x27); + return; + } + try { + acc += step28(x28); + } catch (IllegalArgumentException e) { + log(28, x28); + return; + } + try { + acc += step29(x29); + } catch (IllegalArgumentException e) { + log(29, x29); + return; + } + try { + acc += step30(x30); + } catch (IllegalArgumentException e) { + log(30, x30); + return; + } + try { + acc += step31(x31); + } catch (IllegalArgumentException e) { + log(31, x31); + return; + } + target(a, b); + } + + static int step0(int x) { + if (x < -1) throw new IllegalArgumentException(); + return x + 0; + } + static int step1(int x) { + if (x < -2) throw new IllegalArgumentException(); + return x + 1; + } + static int step2(int x) { + if (x < -3) throw new IllegalArgumentException(); + return x + 2; + } + static int step3(int x) { + if (x < -4) throw new IllegalArgumentException(); + return x + 3; + } + static int step4(int x) { + if (x < -5) throw new IllegalArgumentException(); + return x + 4; + } + static int step5(int x) { + if (x < -6) throw new IllegalArgumentException(); + return x + 5; + } + static int step6(int x) { + if (x < -7) throw new IllegalArgumentException(); + return x + 6; + } + static int step7(int x) { + if (x < -8) throw new IllegalArgumentException(); + return x + 7; + } + static int step8(int x) { + if (x < -9) throw new IllegalArgumentException(); + return x + 8; + } + static int step9(int x) { + if (x < -10) throw new IllegalArgumentException(); + return x + 9; + } + static int step10(int x) { + if (x < -11) throw new IllegalArgumentException(); + return x + 10; + } + static int step11(int x) { + if (x < -12) throw new IllegalArgumentException(); + return x + 11; + } + static int step12(int x) { + if (x < -13) throw new IllegalArgumentException(); + return x + 12; + } + static int step13(int x) { + if (x < -14) throw new IllegalArgumentException(); + return x + 13; + } + static int step14(int x) { + if (x < -15) throw new IllegalArgumentException(); + return x + 14; + } + static int step15(int x) { + if (x < -16) throw new IllegalArgumentException(); + return x + 15; + } + static int step16(int x) { + if (x < -17) throw new IllegalArgumentException(); + return x + 16; + } + static int step17(int x) { + if (x < -18) throw new IllegalArgumentException(); + return x + 17; + } + static int step18(int x) { + if (x < -19) throw new IllegalArgumentException(); + return x + 18; + } + static int step19(int x) { + if (x < -20) throw new IllegalArgumentException(); + return x + 19; + } + static int step20(int x) { + if (x < -21) throw new IllegalArgumentException(); + return x + 20; + } + static int step21(int x) { + if (x < -22) throw new IllegalArgumentException(); + return x + 21; + } + static int step22(int x) { + if (x < -23) throw new IllegalArgumentException(); + return x + 22; + } + static int step23(int x) { + if (x < -24) throw new IllegalArgumentException(); + return x + 23; + } + static int step24(int x) { + if (x < -25) throw new IllegalArgumentException(); + return x + 24; + } + static int step25(int x) { + if (x < -26) throw new IllegalArgumentException(); + return x + 25; + } + static int step26(int x) { + if (x < -27) throw new IllegalArgumentException(); + return x + 26; + } + static int step27(int x) { + if (x < -28) throw new IllegalArgumentException(); + return x + 27; + } + static int step28(int x) { + if (x < -29) throw new IllegalArgumentException(); + return x + 28; + } + static int step29(int x) { + if (x < -30) throw new IllegalArgumentException(); + return x + 29; + } + static int step30(int x) { + if (x < -31) throw new IllegalArgumentException(); + return x + 30; + } + static int step31(int x) { + if (x < -32) throw new IllegalArgumentException(); + return x + 31; + } + + // Logs a failed step; three branches, no assert. + static void log(int step, int x) { + if (x < -1000) logged += 100; + if (x > 1000) logged += 10; + if (x == step) logged += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n02_false.yml b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n02_false.yml new file mode 100644 index 00000000..44ff6d22 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n02_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - explicit_n02_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n02_false/Main.java b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n02_false/Main.java new file mode 100644 index 00000000..3b286e18 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n02_false/Main.java @@ -0,0 +1,62 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:-2 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// exception-dispatch/explicit: the command is parsed from t; for t < 0 parsing throws +// (IllegalArgumentException) and the handler derives the command as -t - 1. Only command 1 reaches the assert. +public class Main { + static class Holder { + int base = 0; + } + + static final Holder HOLDER = new Holder(); + + static int parse(int t) { + if (t < 0) throw new IllegalArgumentException(); + return HOLDER.base + t; + } + + public static void main(String[] args) { + int t = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + int cmd; + try { + cmd = parse(t); + } catch (IllegalArgumentException e) { + cmd = -t - 1; + } + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + int r; + switch (cmd) { + case 0: r = handler0(a, b, c, d); break; + case 1: target(a, b); r = 0; break; + default: r = -1; + } + return r; + } + + static int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n02_true.yml b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n02_true.yml new file mode 100644 index 00000000..68be1aa6 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n02_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - explicit_n02_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n02_true/Main.java b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n02_true/Main.java new file mode 100644 index 00000000..2859bcb4 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n02_true/Main.java @@ -0,0 +1,63 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:-2 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// exception-dispatch/explicit: the command is parsed from t; for t < 0 parsing throws +// (IllegalArgumentException) and the handler derives the command as -t - 1. Only command 1 reaches the assert. +public class Main { + static class Holder { + int base = 0; + } + + static final Holder HOLDER = new Holder(); + + static int parse(int t) { + if (t < 0) throw new IllegalArgumentException(); + return HOLDER.base + t; + } + + public static void main(String[] args) { + int t = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + int cmd; + try { + cmd = parse(t); + } catch (IllegalArgumentException e) { + cmd = -t - 1; + } + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + int r; + switch (cmd) { + case 0: r = handler0(a, b, c, d); break; + case 1: target(a, b); r = 0; break; + default: r = -1; + } + return r; + } + + static int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n04_false.yml b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n04_false.yml new file mode 100644 index 00000000..7f1320b6 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n04_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - explicit_n04_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n04_false/Main.java b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n04_false/Main.java new file mode 100644 index 00000000..70bbff01 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n04_false/Main.java @@ -0,0 +1,80 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:-4 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// exception-dispatch/explicit: the command is parsed from t; for t < 0 parsing throws +// (IllegalArgumentException) and the handler derives the command as -t - 1. Only command 3 reaches the assert. +public class Main { + static class Holder { + int base = 0; + } + + static final Holder HOLDER = new Holder(); + + static int parse(int t) { + if (t < 0) throw new IllegalArgumentException(); + return HOLDER.base + t; + } + + public static void main(String[] args) { + int t = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + int cmd; + try { + cmd = parse(t); + } catch (IllegalArgumentException e) { + cmd = -t - 1; + } + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + int r; + switch (cmd) { + case 0: r = handler0(a, b, c, d); break; + case 1: r = handler1(a, b, c, d); break; + case 2: r = handler2(a, b, c, d); break; + case 3: target(a, b); r = 0; break; + default: r = -1; + } + return r; + } + + static int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + static int handler1(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + static int handler2(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n04_true.yml b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n04_true.yml new file mode 100644 index 00000000..b195a91d --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n04_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - explicit_n04_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n04_true/Main.java b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n04_true/Main.java new file mode 100644 index 00000000..84609383 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n04_true/Main.java @@ -0,0 +1,81 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:-4 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// exception-dispatch/explicit: the command is parsed from t; for t < 0 parsing throws +// (IllegalArgumentException) and the handler derives the command as -t - 1. Only command 3 reaches the assert. +public class Main { + static class Holder { + int base = 0; + } + + static final Holder HOLDER = new Holder(); + + static int parse(int t) { + if (t < 0) throw new IllegalArgumentException(); + return HOLDER.base + t; + } + + public static void main(String[] args) { + int t = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + int cmd; + try { + cmd = parse(t); + } catch (IllegalArgumentException e) { + cmd = -t - 1; + } + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + int r; + switch (cmd) { + case 0: r = handler0(a, b, c, d); break; + case 1: r = handler1(a, b, c, d); break; + case 2: r = handler2(a, b, c, d); break; + case 3: target(a, b); r = 0; break; + default: r = -1; + } + return r; + } + + static int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + static int handler1(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + static int handler2(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n08_false.yml b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n08_false.yml new file mode 100644 index 00000000..552c8fb9 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n08_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - explicit_n08_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n08_false/Main.java b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n08_false/Main.java new file mode 100644 index 00000000..b4acd395 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n08_false/Main.java @@ -0,0 +1,116 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:-8 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// exception-dispatch/explicit: the command is parsed from t; for t < 0 parsing throws +// (IllegalArgumentException) and the handler derives the command as -t - 1. Only command 7 reaches the assert. +public class Main { + static class Holder { + int base = 0; + } + + static final Holder HOLDER = new Holder(); + + static int parse(int t) { + if (t < 0) throw new IllegalArgumentException(); + return HOLDER.base + t; + } + + public static void main(String[] args) { + int t = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + int cmd; + try { + cmd = parse(t); + } catch (IllegalArgumentException e) { + cmd = -t - 1; + } + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + int r; + switch (cmd) { + case 0: r = handler0(a, b, c, d); break; + case 1: r = handler1(a, b, c, d); break; + case 2: r = handler2(a, b, c, d); break; + case 3: r = handler3(a, b, c, d); break; + case 4: r = handler4(a, b, c, d); break; + case 5: r = handler5(a, b, c, d); break; + case 6: r = handler6(a, b, c, d); break; + case 7: target(a, b); r = 0; break; + default: r = -1; + } + return r; + } + + static int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + static int handler1(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + static int handler2(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + static int handler3(int a, int b, int c, int d) { + int r = 3; + if (a > 3) r += 1; + if (b > 6) r += 2; + if (c < -3) r += 4; + if (d == 8) r += 8; + return r; + } + static int handler4(int a, int b, int c, int d) { + int r = 4; + if (a > 4) r += 1; + if (b > 8) r += 2; + if (c < -4) r += 4; + if (d == 9) r += 8; + return r; + } + static int handler5(int a, int b, int c, int d) { + int r = 5; + if (a > 5) r += 1; + if (b > 10) r += 2; + if (c < -5) r += 4; + if (d == 10) r += 8; + return r; + } + static int handler6(int a, int b, int c, int d) { + int r = 6; + if (a > 6) r += 1; + if (b > 12) r += 2; + if (c < -6) r += 4; + if (d == 11) r += 8; + return r; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n08_true.yml b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n08_true.yml new file mode 100644 index 00000000..2aa65aab --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n08_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - explicit_n08_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n08_true/Main.java b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n08_true/Main.java new file mode 100644 index 00000000..2d05e43f --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n08_true/Main.java @@ -0,0 +1,117 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:-8 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// exception-dispatch/explicit: the command is parsed from t; for t < 0 parsing throws +// (IllegalArgumentException) and the handler derives the command as -t - 1. Only command 7 reaches the assert. +public class Main { + static class Holder { + int base = 0; + } + + static final Holder HOLDER = new Holder(); + + static int parse(int t) { + if (t < 0) throw new IllegalArgumentException(); + return HOLDER.base + t; + } + + public static void main(String[] args) { + int t = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + int cmd; + try { + cmd = parse(t); + } catch (IllegalArgumentException e) { + cmd = -t - 1; + } + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + int r; + switch (cmd) { + case 0: r = handler0(a, b, c, d); break; + case 1: r = handler1(a, b, c, d); break; + case 2: r = handler2(a, b, c, d); break; + case 3: r = handler3(a, b, c, d); break; + case 4: r = handler4(a, b, c, d); break; + case 5: r = handler5(a, b, c, d); break; + case 6: r = handler6(a, b, c, d); break; + case 7: target(a, b); r = 0; break; + default: r = -1; + } + return r; + } + + static int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + static int handler1(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + static int handler2(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + static int handler3(int a, int b, int c, int d) { + int r = 3; + if (a > 3) r += 1; + if (b > 6) r += 2; + if (c < -3) r += 4; + if (d == 8) r += 8; + return r; + } + static int handler4(int a, int b, int c, int d) { + int r = 4; + if (a > 4) r += 1; + if (b > 8) r += 2; + if (c < -4) r += 4; + if (d == 9) r += 8; + return r; + } + static int handler5(int a, int b, int c, int d) { + int r = 5; + if (a > 5) r += 1; + if (b > 10) r += 2; + if (c < -5) r += 4; + if (d == 10) r += 8; + return r; + } + static int handler6(int a, int b, int c, int d) { + int r = 6; + if (a > 6) r += 1; + if (b > 12) r += 2; + if (c < -6) r += 4; + if (d == 11) r += 8; + return r; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n16_false.yml b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n16_false.yml new file mode 100644 index 00000000..56f9f93c --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n16_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - explicit_n16_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n16_false/Main.java b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n16_false/Main.java new file mode 100644 index 00000000..05bba40a --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n16_false/Main.java @@ -0,0 +1,188 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:-16 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// exception-dispatch/explicit: the command is parsed from t; for t < 0 parsing throws +// (IllegalArgumentException) and the handler derives the command as -t - 1. Only command 15 reaches the assert. +public class Main { + static class Holder { + int base = 0; + } + + static final Holder HOLDER = new Holder(); + + static int parse(int t) { + if (t < 0) throw new IllegalArgumentException(); + return HOLDER.base + t; + } + + public static void main(String[] args) { + int t = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + int cmd; + try { + cmd = parse(t); + } catch (IllegalArgumentException e) { + cmd = -t - 1; + } + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + int r; + switch (cmd) { + case 0: r = handler0(a, b, c, d); break; + case 1: r = handler1(a, b, c, d); break; + case 2: r = handler2(a, b, c, d); break; + case 3: r = handler3(a, b, c, d); break; + case 4: r = handler4(a, b, c, d); break; + case 5: r = handler5(a, b, c, d); break; + case 6: r = handler6(a, b, c, d); break; + case 7: r = handler7(a, b, c, d); break; + case 8: r = handler8(a, b, c, d); break; + case 9: r = handler9(a, b, c, d); break; + case 10: r = handler10(a, b, c, d); break; + case 11: r = handler11(a, b, c, d); break; + case 12: r = handler12(a, b, c, d); break; + case 13: r = handler13(a, b, c, d); break; + case 14: r = handler14(a, b, c, d); break; + case 15: target(a, b); r = 0; break; + default: r = -1; + } + return r; + } + + static int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + static int handler1(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + static int handler2(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + static int handler3(int a, int b, int c, int d) { + int r = 3; + if (a > 3) r += 1; + if (b > 6) r += 2; + if (c < -3) r += 4; + if (d == 8) r += 8; + return r; + } + static int handler4(int a, int b, int c, int d) { + int r = 4; + if (a > 4) r += 1; + if (b > 8) r += 2; + if (c < -4) r += 4; + if (d == 9) r += 8; + return r; + } + static int handler5(int a, int b, int c, int d) { + int r = 5; + if (a > 5) r += 1; + if (b > 10) r += 2; + if (c < -5) r += 4; + if (d == 10) r += 8; + return r; + } + static int handler6(int a, int b, int c, int d) { + int r = 6; + if (a > 6) r += 1; + if (b > 12) r += 2; + if (c < -6) r += 4; + if (d == 11) r += 8; + return r; + } + static int handler7(int a, int b, int c, int d) { + int r = 7; + if (a > 7) r += 1; + if (b > 14) r += 2; + if (c < -7) r += 4; + if (d == 12) r += 8; + return r; + } + static int handler8(int a, int b, int c, int d) { + int r = 8; + if (a > 8) r += 1; + if (b > 16) r += 2; + if (c < -8) r += 4; + if (d == 13) r += 8; + return r; + } + static int handler9(int a, int b, int c, int d) { + int r = 9; + if (a > 9) r += 1; + if (b > 18) r += 2; + if (c < -9) r += 4; + if (d == 14) r += 8; + return r; + } + static int handler10(int a, int b, int c, int d) { + int r = 10; + if (a > 10) r += 1; + if (b > 20) r += 2; + if (c < -10) r += 4; + if (d == 15) r += 8; + return r; + } + static int handler11(int a, int b, int c, int d) { + int r = 11; + if (a > 11) r += 1; + if (b > 22) r += 2; + if (c < -11) r += 4; + if (d == 16) r += 8; + return r; + } + static int handler12(int a, int b, int c, int d) { + int r = 12; + if (a > 12) r += 1; + if (b > 24) r += 2; + if (c < -12) r += 4; + if (d == 17) r += 8; + return r; + } + static int handler13(int a, int b, int c, int d) { + int r = 13; + if (a > 13) r += 1; + if (b > 26) r += 2; + if (c < -13) r += 4; + if (d == 18) r += 8; + return r; + } + static int handler14(int a, int b, int c, int d) { + int r = 14; + if (a > 14) r += 1; + if (b > 28) r += 2; + if (c < -14) r += 4; + if (d == 19) r += 8; + return r; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n16_true.yml b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n16_true.yml new file mode 100644 index 00000000..1b49a662 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n16_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - explicit_n16_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n16_true/Main.java b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n16_true/Main.java new file mode 100644 index 00000000..6d63877a --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n16_true/Main.java @@ -0,0 +1,189 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:-16 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// exception-dispatch/explicit: the command is parsed from t; for t < 0 parsing throws +// (IllegalArgumentException) and the handler derives the command as -t - 1. Only command 15 reaches the assert. +public class Main { + static class Holder { + int base = 0; + } + + static final Holder HOLDER = new Holder(); + + static int parse(int t) { + if (t < 0) throw new IllegalArgumentException(); + return HOLDER.base + t; + } + + public static void main(String[] args) { + int t = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + int cmd; + try { + cmd = parse(t); + } catch (IllegalArgumentException e) { + cmd = -t - 1; + } + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + int r; + switch (cmd) { + case 0: r = handler0(a, b, c, d); break; + case 1: r = handler1(a, b, c, d); break; + case 2: r = handler2(a, b, c, d); break; + case 3: r = handler3(a, b, c, d); break; + case 4: r = handler4(a, b, c, d); break; + case 5: r = handler5(a, b, c, d); break; + case 6: r = handler6(a, b, c, d); break; + case 7: r = handler7(a, b, c, d); break; + case 8: r = handler8(a, b, c, d); break; + case 9: r = handler9(a, b, c, d); break; + case 10: r = handler10(a, b, c, d); break; + case 11: r = handler11(a, b, c, d); break; + case 12: r = handler12(a, b, c, d); break; + case 13: r = handler13(a, b, c, d); break; + case 14: r = handler14(a, b, c, d); break; + case 15: target(a, b); r = 0; break; + default: r = -1; + } + return r; + } + + static int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + static int handler1(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + static int handler2(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + static int handler3(int a, int b, int c, int d) { + int r = 3; + if (a > 3) r += 1; + if (b > 6) r += 2; + if (c < -3) r += 4; + if (d == 8) r += 8; + return r; + } + static int handler4(int a, int b, int c, int d) { + int r = 4; + if (a > 4) r += 1; + if (b > 8) r += 2; + if (c < -4) r += 4; + if (d == 9) r += 8; + return r; + } + static int handler5(int a, int b, int c, int d) { + int r = 5; + if (a > 5) r += 1; + if (b > 10) r += 2; + if (c < -5) r += 4; + if (d == 10) r += 8; + return r; + } + static int handler6(int a, int b, int c, int d) { + int r = 6; + if (a > 6) r += 1; + if (b > 12) r += 2; + if (c < -6) r += 4; + if (d == 11) r += 8; + return r; + } + static int handler7(int a, int b, int c, int d) { + int r = 7; + if (a > 7) r += 1; + if (b > 14) r += 2; + if (c < -7) r += 4; + if (d == 12) r += 8; + return r; + } + static int handler8(int a, int b, int c, int d) { + int r = 8; + if (a > 8) r += 1; + if (b > 16) r += 2; + if (c < -8) r += 4; + if (d == 13) r += 8; + return r; + } + static int handler9(int a, int b, int c, int d) { + int r = 9; + if (a > 9) r += 1; + if (b > 18) r += 2; + if (c < -9) r += 4; + if (d == 14) r += 8; + return r; + } + static int handler10(int a, int b, int c, int d) { + int r = 10; + if (a > 10) r += 1; + if (b > 20) r += 2; + if (c < -10) r += 4; + if (d == 15) r += 8; + return r; + } + static int handler11(int a, int b, int c, int d) { + int r = 11; + if (a > 11) r += 1; + if (b > 22) r += 2; + if (c < -11) r += 4; + if (d == 16) r += 8; + return r; + } + static int handler12(int a, int b, int c, int d) { + int r = 12; + if (a > 12) r += 1; + if (b > 24) r += 2; + if (c < -12) r += 4; + if (d == 17) r += 8; + return r; + } + static int handler13(int a, int b, int c, int d) { + int r = 13; + if (a > 13) r += 1; + if (b > 26) r += 2; + if (c < -13) r += 4; + if (d == 18) r += 8; + return r; + } + static int handler14(int a, int b, int c, int d) { + int r = 14; + if (a > 14) r += 1; + if (b > 28) r += 2; + if (c < -14) r += 4; + if (d == 19) r += 8; + return r; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n32_false.yml b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n32_false.yml new file mode 100644 index 00000000..74c13342 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n32_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - explicit_n32_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n32_false/Main.java b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n32_false/Main.java new file mode 100644 index 00000000..c214ff61 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n32_false/Main.java @@ -0,0 +1,332 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:-32 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// exception-dispatch/explicit: the command is parsed from t; for t < 0 parsing throws +// (IllegalArgumentException) and the handler derives the command as -t - 1. Only command 31 reaches the assert. +public class Main { + static class Holder { + int base = 0; + } + + static final Holder HOLDER = new Holder(); + + static int parse(int t) { + if (t < 0) throw new IllegalArgumentException(); + return HOLDER.base + t; + } + + public static void main(String[] args) { + int t = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + int cmd; + try { + cmd = parse(t); + } catch (IllegalArgumentException e) { + cmd = -t - 1; + } + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + int r; + switch (cmd) { + case 0: r = handler0(a, b, c, d); break; + case 1: r = handler1(a, b, c, d); break; + case 2: r = handler2(a, b, c, d); break; + case 3: r = handler3(a, b, c, d); break; + case 4: r = handler4(a, b, c, d); break; + case 5: r = handler5(a, b, c, d); break; + case 6: r = handler6(a, b, c, d); break; + case 7: r = handler7(a, b, c, d); break; + case 8: r = handler8(a, b, c, d); break; + case 9: r = handler9(a, b, c, d); break; + case 10: r = handler10(a, b, c, d); break; + case 11: r = handler11(a, b, c, d); break; + case 12: r = handler12(a, b, c, d); break; + case 13: r = handler13(a, b, c, d); break; + case 14: r = handler14(a, b, c, d); break; + case 15: r = handler15(a, b, c, d); break; + case 16: r = handler16(a, b, c, d); break; + case 17: r = handler17(a, b, c, d); break; + case 18: r = handler18(a, b, c, d); break; + case 19: r = handler19(a, b, c, d); break; + case 20: r = handler20(a, b, c, d); break; + case 21: r = handler21(a, b, c, d); break; + case 22: r = handler22(a, b, c, d); break; + case 23: r = handler23(a, b, c, d); break; + case 24: r = handler24(a, b, c, d); break; + case 25: r = handler25(a, b, c, d); break; + case 26: r = handler26(a, b, c, d); break; + case 27: r = handler27(a, b, c, d); break; + case 28: r = handler28(a, b, c, d); break; + case 29: r = handler29(a, b, c, d); break; + case 30: r = handler30(a, b, c, d); break; + case 31: target(a, b); r = 0; break; + default: r = -1; + } + return r; + } + + static int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + static int handler1(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + static int handler2(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + static int handler3(int a, int b, int c, int d) { + int r = 3; + if (a > 3) r += 1; + if (b > 6) r += 2; + if (c < -3) r += 4; + if (d == 8) r += 8; + return r; + } + static int handler4(int a, int b, int c, int d) { + int r = 4; + if (a > 4) r += 1; + if (b > 8) r += 2; + if (c < -4) r += 4; + if (d == 9) r += 8; + return r; + } + static int handler5(int a, int b, int c, int d) { + int r = 5; + if (a > 5) r += 1; + if (b > 10) r += 2; + if (c < -5) r += 4; + if (d == 10) r += 8; + return r; + } + static int handler6(int a, int b, int c, int d) { + int r = 6; + if (a > 6) r += 1; + if (b > 12) r += 2; + if (c < -6) r += 4; + if (d == 11) r += 8; + return r; + } + static int handler7(int a, int b, int c, int d) { + int r = 7; + if (a > 7) r += 1; + if (b > 14) r += 2; + if (c < -7) r += 4; + if (d == 12) r += 8; + return r; + } + static int handler8(int a, int b, int c, int d) { + int r = 8; + if (a > 8) r += 1; + if (b > 16) r += 2; + if (c < -8) r += 4; + if (d == 13) r += 8; + return r; + } + static int handler9(int a, int b, int c, int d) { + int r = 9; + if (a > 9) r += 1; + if (b > 18) r += 2; + if (c < -9) r += 4; + if (d == 14) r += 8; + return r; + } + static int handler10(int a, int b, int c, int d) { + int r = 10; + if (a > 10) r += 1; + if (b > 20) r += 2; + if (c < -10) r += 4; + if (d == 15) r += 8; + return r; + } + static int handler11(int a, int b, int c, int d) { + int r = 11; + if (a > 11) r += 1; + if (b > 22) r += 2; + if (c < -11) r += 4; + if (d == 16) r += 8; + return r; + } + static int handler12(int a, int b, int c, int d) { + int r = 12; + if (a > 12) r += 1; + if (b > 24) r += 2; + if (c < -12) r += 4; + if (d == 17) r += 8; + return r; + } + static int handler13(int a, int b, int c, int d) { + int r = 13; + if (a > 13) r += 1; + if (b > 26) r += 2; + if (c < -13) r += 4; + if (d == 18) r += 8; + return r; + } + static int handler14(int a, int b, int c, int d) { + int r = 14; + if (a > 14) r += 1; + if (b > 28) r += 2; + if (c < -14) r += 4; + if (d == 19) r += 8; + return r; + } + static int handler15(int a, int b, int c, int d) { + int r = 15; + if (a > 15) r += 1; + if (b > 30) r += 2; + if (c < -15) r += 4; + if (d == 20) r += 8; + return r; + } + static int handler16(int a, int b, int c, int d) { + int r = 16; + if (a > 16) r += 1; + if (b > 32) r += 2; + if (c < -16) r += 4; + if (d == 21) r += 8; + return r; + } + static int handler17(int a, int b, int c, int d) { + int r = 17; + if (a > 17) r += 1; + if (b > 34) r += 2; + if (c < -17) r += 4; + if (d == 22) r += 8; + return r; + } + static int handler18(int a, int b, int c, int d) { + int r = 18; + if (a > 18) r += 1; + if (b > 36) r += 2; + if (c < -18) r += 4; + if (d == 23) r += 8; + return r; + } + static int handler19(int a, int b, int c, int d) { + int r = 19; + if (a > 19) r += 1; + if (b > 38) r += 2; + if (c < -19) r += 4; + if (d == 24) r += 8; + return r; + } + static int handler20(int a, int b, int c, int d) { + int r = 20; + if (a > 20) r += 1; + if (b > 40) r += 2; + if (c < -20) r += 4; + if (d == 25) r += 8; + return r; + } + static int handler21(int a, int b, int c, int d) { + int r = 21; + if (a > 21) r += 1; + if (b > 42) r += 2; + if (c < -21) r += 4; + if (d == 26) r += 8; + return r; + } + static int handler22(int a, int b, int c, int d) { + int r = 22; + if (a > 22) r += 1; + if (b > 44) r += 2; + if (c < -22) r += 4; + if (d == 27) r += 8; + return r; + } + static int handler23(int a, int b, int c, int d) { + int r = 23; + if (a > 23) r += 1; + if (b > 46) r += 2; + if (c < -23) r += 4; + if (d == 28) r += 8; + return r; + } + static int handler24(int a, int b, int c, int d) { + int r = 24; + if (a > 24) r += 1; + if (b > 48) r += 2; + if (c < -24) r += 4; + if (d == 29) r += 8; + return r; + } + static int handler25(int a, int b, int c, int d) { + int r = 25; + if (a > 25) r += 1; + if (b > 50) r += 2; + if (c < -25) r += 4; + if (d == 30) r += 8; + return r; + } + static int handler26(int a, int b, int c, int d) { + int r = 26; + if (a > 26) r += 1; + if (b > 52) r += 2; + if (c < -26) r += 4; + if (d == 31) r += 8; + return r; + } + static int handler27(int a, int b, int c, int d) { + int r = 27; + if (a > 27) r += 1; + if (b > 54) r += 2; + if (c < -27) r += 4; + if (d == 32) r += 8; + return r; + } + static int handler28(int a, int b, int c, int d) { + int r = 28; + if (a > 28) r += 1; + if (b > 56) r += 2; + if (c < -28) r += 4; + if (d == 33) r += 8; + return r; + } + static int handler29(int a, int b, int c, int d) { + int r = 29; + if (a > 29) r += 1; + if (b > 58) r += 2; + if (c < -29) r += 4; + if (d == 34) r += 8; + return r; + } + static int handler30(int a, int b, int c, int d) { + int r = 30; + if (a > 30) r += 1; + if (b > 60) r += 2; + if (c < -30) r += 4; + if (d == 35) r += 8; + return r; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n32_true.yml b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n32_true.yml new file mode 100644 index 00000000..6bd03d60 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n32_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - explicit_n32_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n32_true/Main.java b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n32_true/Main.java new file mode 100644 index 00000000..efb2d054 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/explicit_n32_true/Main.java @@ -0,0 +1,333 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:-32 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// exception-dispatch/explicit: the command is parsed from t; for t < 0 parsing throws +// (IllegalArgumentException) and the handler derives the command as -t - 1. Only command 31 reaches the assert. +public class Main { + static class Holder { + int base = 0; + } + + static final Holder HOLDER = new Holder(); + + static int parse(int t) { + if (t < 0) throw new IllegalArgumentException(); + return HOLDER.base + t; + } + + public static void main(String[] args) { + int t = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + int cmd; + try { + cmd = parse(t); + } catch (IllegalArgumentException e) { + cmd = -t - 1; + } + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + int r; + switch (cmd) { + case 0: r = handler0(a, b, c, d); break; + case 1: r = handler1(a, b, c, d); break; + case 2: r = handler2(a, b, c, d); break; + case 3: r = handler3(a, b, c, d); break; + case 4: r = handler4(a, b, c, d); break; + case 5: r = handler5(a, b, c, d); break; + case 6: r = handler6(a, b, c, d); break; + case 7: r = handler7(a, b, c, d); break; + case 8: r = handler8(a, b, c, d); break; + case 9: r = handler9(a, b, c, d); break; + case 10: r = handler10(a, b, c, d); break; + case 11: r = handler11(a, b, c, d); break; + case 12: r = handler12(a, b, c, d); break; + case 13: r = handler13(a, b, c, d); break; + case 14: r = handler14(a, b, c, d); break; + case 15: r = handler15(a, b, c, d); break; + case 16: r = handler16(a, b, c, d); break; + case 17: r = handler17(a, b, c, d); break; + case 18: r = handler18(a, b, c, d); break; + case 19: r = handler19(a, b, c, d); break; + case 20: r = handler20(a, b, c, d); break; + case 21: r = handler21(a, b, c, d); break; + case 22: r = handler22(a, b, c, d); break; + case 23: r = handler23(a, b, c, d); break; + case 24: r = handler24(a, b, c, d); break; + case 25: r = handler25(a, b, c, d); break; + case 26: r = handler26(a, b, c, d); break; + case 27: r = handler27(a, b, c, d); break; + case 28: r = handler28(a, b, c, d); break; + case 29: r = handler29(a, b, c, d); break; + case 30: r = handler30(a, b, c, d); break; + case 31: target(a, b); r = 0; break; + default: r = -1; + } + return r; + } + + static int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + static int handler1(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + static int handler2(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + static int handler3(int a, int b, int c, int d) { + int r = 3; + if (a > 3) r += 1; + if (b > 6) r += 2; + if (c < -3) r += 4; + if (d == 8) r += 8; + return r; + } + static int handler4(int a, int b, int c, int d) { + int r = 4; + if (a > 4) r += 1; + if (b > 8) r += 2; + if (c < -4) r += 4; + if (d == 9) r += 8; + return r; + } + static int handler5(int a, int b, int c, int d) { + int r = 5; + if (a > 5) r += 1; + if (b > 10) r += 2; + if (c < -5) r += 4; + if (d == 10) r += 8; + return r; + } + static int handler6(int a, int b, int c, int d) { + int r = 6; + if (a > 6) r += 1; + if (b > 12) r += 2; + if (c < -6) r += 4; + if (d == 11) r += 8; + return r; + } + static int handler7(int a, int b, int c, int d) { + int r = 7; + if (a > 7) r += 1; + if (b > 14) r += 2; + if (c < -7) r += 4; + if (d == 12) r += 8; + return r; + } + static int handler8(int a, int b, int c, int d) { + int r = 8; + if (a > 8) r += 1; + if (b > 16) r += 2; + if (c < -8) r += 4; + if (d == 13) r += 8; + return r; + } + static int handler9(int a, int b, int c, int d) { + int r = 9; + if (a > 9) r += 1; + if (b > 18) r += 2; + if (c < -9) r += 4; + if (d == 14) r += 8; + return r; + } + static int handler10(int a, int b, int c, int d) { + int r = 10; + if (a > 10) r += 1; + if (b > 20) r += 2; + if (c < -10) r += 4; + if (d == 15) r += 8; + return r; + } + static int handler11(int a, int b, int c, int d) { + int r = 11; + if (a > 11) r += 1; + if (b > 22) r += 2; + if (c < -11) r += 4; + if (d == 16) r += 8; + return r; + } + static int handler12(int a, int b, int c, int d) { + int r = 12; + if (a > 12) r += 1; + if (b > 24) r += 2; + if (c < -12) r += 4; + if (d == 17) r += 8; + return r; + } + static int handler13(int a, int b, int c, int d) { + int r = 13; + if (a > 13) r += 1; + if (b > 26) r += 2; + if (c < -13) r += 4; + if (d == 18) r += 8; + return r; + } + static int handler14(int a, int b, int c, int d) { + int r = 14; + if (a > 14) r += 1; + if (b > 28) r += 2; + if (c < -14) r += 4; + if (d == 19) r += 8; + return r; + } + static int handler15(int a, int b, int c, int d) { + int r = 15; + if (a > 15) r += 1; + if (b > 30) r += 2; + if (c < -15) r += 4; + if (d == 20) r += 8; + return r; + } + static int handler16(int a, int b, int c, int d) { + int r = 16; + if (a > 16) r += 1; + if (b > 32) r += 2; + if (c < -16) r += 4; + if (d == 21) r += 8; + return r; + } + static int handler17(int a, int b, int c, int d) { + int r = 17; + if (a > 17) r += 1; + if (b > 34) r += 2; + if (c < -17) r += 4; + if (d == 22) r += 8; + return r; + } + static int handler18(int a, int b, int c, int d) { + int r = 18; + if (a > 18) r += 1; + if (b > 36) r += 2; + if (c < -18) r += 4; + if (d == 23) r += 8; + return r; + } + static int handler19(int a, int b, int c, int d) { + int r = 19; + if (a > 19) r += 1; + if (b > 38) r += 2; + if (c < -19) r += 4; + if (d == 24) r += 8; + return r; + } + static int handler20(int a, int b, int c, int d) { + int r = 20; + if (a > 20) r += 1; + if (b > 40) r += 2; + if (c < -20) r += 4; + if (d == 25) r += 8; + return r; + } + static int handler21(int a, int b, int c, int d) { + int r = 21; + if (a > 21) r += 1; + if (b > 42) r += 2; + if (c < -21) r += 4; + if (d == 26) r += 8; + return r; + } + static int handler22(int a, int b, int c, int d) { + int r = 22; + if (a > 22) r += 1; + if (b > 44) r += 2; + if (c < -22) r += 4; + if (d == 27) r += 8; + return r; + } + static int handler23(int a, int b, int c, int d) { + int r = 23; + if (a > 23) r += 1; + if (b > 46) r += 2; + if (c < -23) r += 4; + if (d == 28) r += 8; + return r; + } + static int handler24(int a, int b, int c, int d) { + int r = 24; + if (a > 24) r += 1; + if (b > 48) r += 2; + if (c < -24) r += 4; + if (d == 29) r += 8; + return r; + } + static int handler25(int a, int b, int c, int d) { + int r = 25; + if (a > 25) r += 1; + if (b > 50) r += 2; + if (c < -25) r += 4; + if (d == 30) r += 8; + return r; + } + static int handler26(int a, int b, int c, int d) { + int r = 26; + if (a > 26) r += 1; + if (b > 52) r += 2; + if (c < -26) r += 4; + if (d == 31) r += 8; + return r; + } + static int handler27(int a, int b, int c, int d) { + int r = 27; + if (a > 27) r += 1; + if (b > 54) r += 2; + if (c < -27) r += 4; + if (d == 32) r += 8; + return r; + } + static int handler28(int a, int b, int c, int d) { + int r = 28; + if (a > 28) r += 1; + if (b > 56) r += 2; + if (c < -28) r += 4; + if (d == 33) r += 8; + return r; + } + static int handler29(int a, int b, int c, int d) { + int r = 29; + if (a > 29) r += 1; + if (b > 58) r += 2; + if (c < -29) r += 4; + if (d == 34) r += 8; + return r; + } + static int handler30(int a, int b, int c, int d) { + int r = 30; + if (a > 30) r += 1; + if (b > 60) r += 2; + if (c < -30) r += 4; + if (d == 35) r += 8; + return r; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n02_false.yml b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n02_false.yml new file mode 100644 index 00000000..09603d57 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n02_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - npe_n02_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n02_false/Main.java b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n02_false/Main.java new file mode 100644 index 00000000..6b0c09cf --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n02_false/Main.java @@ -0,0 +1,62 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:-2 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// exception-dispatch/npe: the command is parsed from t; for t < 0 parsing throws +// (NullPointerException) and the handler derives the command as -t - 1. Only command 1 reaches the assert. +public class Main { + static class Holder { + int base = 0; + } + + static final Holder HOLDER = new Holder(); + + static int parse(int t) { + Holder h = t < 0 ? null : HOLDER; + return h.base + t; + } + + public static void main(String[] args) { + int t = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + int cmd; + try { + cmd = parse(t); + } catch (NullPointerException e) { + cmd = -t - 1; + } + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + int r; + switch (cmd) { + case 0: r = handler0(a, b, c, d); break; + case 1: target(a, b); r = 0; break; + default: r = -1; + } + return r; + } + + static int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n02_true.yml b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n02_true.yml new file mode 100644 index 00000000..ab75dc48 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n02_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - npe_n02_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n02_true/Main.java b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n02_true/Main.java new file mode 100644 index 00000000..b6d34be1 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n02_true/Main.java @@ -0,0 +1,63 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:-2 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// exception-dispatch/npe: the command is parsed from t; for t < 0 parsing throws +// (NullPointerException) and the handler derives the command as -t - 1. Only command 1 reaches the assert. +public class Main { + static class Holder { + int base = 0; + } + + static final Holder HOLDER = new Holder(); + + static int parse(int t) { + Holder h = t < 0 ? null : HOLDER; + return h.base + t; + } + + public static void main(String[] args) { + int t = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + int cmd; + try { + cmd = parse(t); + } catch (NullPointerException e) { + cmd = -t - 1; + } + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + int r; + switch (cmd) { + case 0: r = handler0(a, b, c, d); break; + case 1: target(a, b); r = 0; break; + default: r = -1; + } + return r; + } + + static int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n04_false.yml b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n04_false.yml new file mode 100644 index 00000000..b32e4c77 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n04_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - npe_n04_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n04_false/Main.java b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n04_false/Main.java new file mode 100644 index 00000000..588bba8b --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n04_false/Main.java @@ -0,0 +1,80 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:-4 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// exception-dispatch/npe: the command is parsed from t; for t < 0 parsing throws +// (NullPointerException) and the handler derives the command as -t - 1. Only command 3 reaches the assert. +public class Main { + static class Holder { + int base = 0; + } + + static final Holder HOLDER = new Holder(); + + static int parse(int t) { + Holder h = t < 0 ? null : HOLDER; + return h.base + t; + } + + public static void main(String[] args) { + int t = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + int cmd; + try { + cmd = parse(t); + } catch (NullPointerException e) { + cmd = -t - 1; + } + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + int r; + switch (cmd) { + case 0: r = handler0(a, b, c, d); break; + case 1: r = handler1(a, b, c, d); break; + case 2: r = handler2(a, b, c, d); break; + case 3: target(a, b); r = 0; break; + default: r = -1; + } + return r; + } + + static int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + static int handler1(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + static int handler2(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n04_true.yml b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n04_true.yml new file mode 100644 index 00000000..48752e6f --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n04_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - npe_n04_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n04_true/Main.java b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n04_true/Main.java new file mode 100644 index 00000000..dee9954d --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n04_true/Main.java @@ -0,0 +1,81 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:-4 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// exception-dispatch/npe: the command is parsed from t; for t < 0 parsing throws +// (NullPointerException) and the handler derives the command as -t - 1. Only command 3 reaches the assert. +public class Main { + static class Holder { + int base = 0; + } + + static final Holder HOLDER = new Holder(); + + static int parse(int t) { + Holder h = t < 0 ? null : HOLDER; + return h.base + t; + } + + public static void main(String[] args) { + int t = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + int cmd; + try { + cmd = parse(t); + } catch (NullPointerException e) { + cmd = -t - 1; + } + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + int r; + switch (cmd) { + case 0: r = handler0(a, b, c, d); break; + case 1: r = handler1(a, b, c, d); break; + case 2: r = handler2(a, b, c, d); break; + case 3: target(a, b); r = 0; break; + default: r = -1; + } + return r; + } + + static int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + static int handler1(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + static int handler2(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n08_false.yml b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n08_false.yml new file mode 100644 index 00000000..d85c5fb0 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n08_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - npe_n08_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n08_false/Main.java b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n08_false/Main.java new file mode 100644 index 00000000..b3f036c5 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n08_false/Main.java @@ -0,0 +1,116 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:-8 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// exception-dispatch/npe: the command is parsed from t; for t < 0 parsing throws +// (NullPointerException) and the handler derives the command as -t - 1. Only command 7 reaches the assert. +public class Main { + static class Holder { + int base = 0; + } + + static final Holder HOLDER = new Holder(); + + static int parse(int t) { + Holder h = t < 0 ? null : HOLDER; + return h.base + t; + } + + public static void main(String[] args) { + int t = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + int cmd; + try { + cmd = parse(t); + } catch (NullPointerException e) { + cmd = -t - 1; + } + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + int r; + switch (cmd) { + case 0: r = handler0(a, b, c, d); break; + case 1: r = handler1(a, b, c, d); break; + case 2: r = handler2(a, b, c, d); break; + case 3: r = handler3(a, b, c, d); break; + case 4: r = handler4(a, b, c, d); break; + case 5: r = handler5(a, b, c, d); break; + case 6: r = handler6(a, b, c, d); break; + case 7: target(a, b); r = 0; break; + default: r = -1; + } + return r; + } + + static int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + static int handler1(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + static int handler2(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + static int handler3(int a, int b, int c, int d) { + int r = 3; + if (a > 3) r += 1; + if (b > 6) r += 2; + if (c < -3) r += 4; + if (d == 8) r += 8; + return r; + } + static int handler4(int a, int b, int c, int d) { + int r = 4; + if (a > 4) r += 1; + if (b > 8) r += 2; + if (c < -4) r += 4; + if (d == 9) r += 8; + return r; + } + static int handler5(int a, int b, int c, int d) { + int r = 5; + if (a > 5) r += 1; + if (b > 10) r += 2; + if (c < -5) r += 4; + if (d == 10) r += 8; + return r; + } + static int handler6(int a, int b, int c, int d) { + int r = 6; + if (a > 6) r += 1; + if (b > 12) r += 2; + if (c < -6) r += 4; + if (d == 11) r += 8; + return r; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n08_true.yml b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n08_true.yml new file mode 100644 index 00000000..ce62f90e --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n08_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - npe_n08_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n08_true/Main.java b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n08_true/Main.java new file mode 100644 index 00000000..1168fd8e --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n08_true/Main.java @@ -0,0 +1,117 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:-8 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// exception-dispatch/npe: the command is parsed from t; for t < 0 parsing throws +// (NullPointerException) and the handler derives the command as -t - 1. Only command 7 reaches the assert. +public class Main { + static class Holder { + int base = 0; + } + + static final Holder HOLDER = new Holder(); + + static int parse(int t) { + Holder h = t < 0 ? null : HOLDER; + return h.base + t; + } + + public static void main(String[] args) { + int t = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + int cmd; + try { + cmd = parse(t); + } catch (NullPointerException e) { + cmd = -t - 1; + } + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + int r; + switch (cmd) { + case 0: r = handler0(a, b, c, d); break; + case 1: r = handler1(a, b, c, d); break; + case 2: r = handler2(a, b, c, d); break; + case 3: r = handler3(a, b, c, d); break; + case 4: r = handler4(a, b, c, d); break; + case 5: r = handler5(a, b, c, d); break; + case 6: r = handler6(a, b, c, d); break; + case 7: target(a, b); r = 0; break; + default: r = -1; + } + return r; + } + + static int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + static int handler1(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + static int handler2(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + static int handler3(int a, int b, int c, int d) { + int r = 3; + if (a > 3) r += 1; + if (b > 6) r += 2; + if (c < -3) r += 4; + if (d == 8) r += 8; + return r; + } + static int handler4(int a, int b, int c, int d) { + int r = 4; + if (a > 4) r += 1; + if (b > 8) r += 2; + if (c < -4) r += 4; + if (d == 9) r += 8; + return r; + } + static int handler5(int a, int b, int c, int d) { + int r = 5; + if (a > 5) r += 1; + if (b > 10) r += 2; + if (c < -5) r += 4; + if (d == 10) r += 8; + return r; + } + static int handler6(int a, int b, int c, int d) { + int r = 6; + if (a > 6) r += 1; + if (b > 12) r += 2; + if (c < -6) r += 4; + if (d == 11) r += 8; + return r; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n16_false.yml b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n16_false.yml new file mode 100644 index 00000000..eade2c4b --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n16_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - npe_n16_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n16_false/Main.java b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n16_false/Main.java new file mode 100644 index 00000000..c8d63e64 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n16_false/Main.java @@ -0,0 +1,188 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:-16 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// exception-dispatch/npe: the command is parsed from t; for t < 0 parsing throws +// (NullPointerException) and the handler derives the command as -t - 1. Only command 15 reaches the assert. +public class Main { + static class Holder { + int base = 0; + } + + static final Holder HOLDER = new Holder(); + + static int parse(int t) { + Holder h = t < 0 ? null : HOLDER; + return h.base + t; + } + + public static void main(String[] args) { + int t = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + int cmd; + try { + cmd = parse(t); + } catch (NullPointerException e) { + cmd = -t - 1; + } + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + int r; + switch (cmd) { + case 0: r = handler0(a, b, c, d); break; + case 1: r = handler1(a, b, c, d); break; + case 2: r = handler2(a, b, c, d); break; + case 3: r = handler3(a, b, c, d); break; + case 4: r = handler4(a, b, c, d); break; + case 5: r = handler5(a, b, c, d); break; + case 6: r = handler6(a, b, c, d); break; + case 7: r = handler7(a, b, c, d); break; + case 8: r = handler8(a, b, c, d); break; + case 9: r = handler9(a, b, c, d); break; + case 10: r = handler10(a, b, c, d); break; + case 11: r = handler11(a, b, c, d); break; + case 12: r = handler12(a, b, c, d); break; + case 13: r = handler13(a, b, c, d); break; + case 14: r = handler14(a, b, c, d); break; + case 15: target(a, b); r = 0; break; + default: r = -1; + } + return r; + } + + static int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + static int handler1(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + static int handler2(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + static int handler3(int a, int b, int c, int d) { + int r = 3; + if (a > 3) r += 1; + if (b > 6) r += 2; + if (c < -3) r += 4; + if (d == 8) r += 8; + return r; + } + static int handler4(int a, int b, int c, int d) { + int r = 4; + if (a > 4) r += 1; + if (b > 8) r += 2; + if (c < -4) r += 4; + if (d == 9) r += 8; + return r; + } + static int handler5(int a, int b, int c, int d) { + int r = 5; + if (a > 5) r += 1; + if (b > 10) r += 2; + if (c < -5) r += 4; + if (d == 10) r += 8; + return r; + } + static int handler6(int a, int b, int c, int d) { + int r = 6; + if (a > 6) r += 1; + if (b > 12) r += 2; + if (c < -6) r += 4; + if (d == 11) r += 8; + return r; + } + static int handler7(int a, int b, int c, int d) { + int r = 7; + if (a > 7) r += 1; + if (b > 14) r += 2; + if (c < -7) r += 4; + if (d == 12) r += 8; + return r; + } + static int handler8(int a, int b, int c, int d) { + int r = 8; + if (a > 8) r += 1; + if (b > 16) r += 2; + if (c < -8) r += 4; + if (d == 13) r += 8; + return r; + } + static int handler9(int a, int b, int c, int d) { + int r = 9; + if (a > 9) r += 1; + if (b > 18) r += 2; + if (c < -9) r += 4; + if (d == 14) r += 8; + return r; + } + static int handler10(int a, int b, int c, int d) { + int r = 10; + if (a > 10) r += 1; + if (b > 20) r += 2; + if (c < -10) r += 4; + if (d == 15) r += 8; + return r; + } + static int handler11(int a, int b, int c, int d) { + int r = 11; + if (a > 11) r += 1; + if (b > 22) r += 2; + if (c < -11) r += 4; + if (d == 16) r += 8; + return r; + } + static int handler12(int a, int b, int c, int d) { + int r = 12; + if (a > 12) r += 1; + if (b > 24) r += 2; + if (c < -12) r += 4; + if (d == 17) r += 8; + return r; + } + static int handler13(int a, int b, int c, int d) { + int r = 13; + if (a > 13) r += 1; + if (b > 26) r += 2; + if (c < -13) r += 4; + if (d == 18) r += 8; + return r; + } + static int handler14(int a, int b, int c, int d) { + int r = 14; + if (a > 14) r += 1; + if (b > 28) r += 2; + if (c < -14) r += 4; + if (d == 19) r += 8; + return r; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n16_true.yml b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n16_true.yml new file mode 100644 index 00000000..63860ccc --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n16_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - npe_n16_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n16_true/Main.java b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n16_true/Main.java new file mode 100644 index 00000000..6981c711 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n16_true/Main.java @@ -0,0 +1,189 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:-16 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// exception-dispatch/npe: the command is parsed from t; for t < 0 parsing throws +// (NullPointerException) and the handler derives the command as -t - 1. Only command 15 reaches the assert. +public class Main { + static class Holder { + int base = 0; + } + + static final Holder HOLDER = new Holder(); + + static int parse(int t) { + Holder h = t < 0 ? null : HOLDER; + return h.base + t; + } + + public static void main(String[] args) { + int t = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + int cmd; + try { + cmd = parse(t); + } catch (NullPointerException e) { + cmd = -t - 1; + } + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + int r; + switch (cmd) { + case 0: r = handler0(a, b, c, d); break; + case 1: r = handler1(a, b, c, d); break; + case 2: r = handler2(a, b, c, d); break; + case 3: r = handler3(a, b, c, d); break; + case 4: r = handler4(a, b, c, d); break; + case 5: r = handler5(a, b, c, d); break; + case 6: r = handler6(a, b, c, d); break; + case 7: r = handler7(a, b, c, d); break; + case 8: r = handler8(a, b, c, d); break; + case 9: r = handler9(a, b, c, d); break; + case 10: r = handler10(a, b, c, d); break; + case 11: r = handler11(a, b, c, d); break; + case 12: r = handler12(a, b, c, d); break; + case 13: r = handler13(a, b, c, d); break; + case 14: r = handler14(a, b, c, d); break; + case 15: target(a, b); r = 0; break; + default: r = -1; + } + return r; + } + + static int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + static int handler1(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + static int handler2(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + static int handler3(int a, int b, int c, int d) { + int r = 3; + if (a > 3) r += 1; + if (b > 6) r += 2; + if (c < -3) r += 4; + if (d == 8) r += 8; + return r; + } + static int handler4(int a, int b, int c, int d) { + int r = 4; + if (a > 4) r += 1; + if (b > 8) r += 2; + if (c < -4) r += 4; + if (d == 9) r += 8; + return r; + } + static int handler5(int a, int b, int c, int d) { + int r = 5; + if (a > 5) r += 1; + if (b > 10) r += 2; + if (c < -5) r += 4; + if (d == 10) r += 8; + return r; + } + static int handler6(int a, int b, int c, int d) { + int r = 6; + if (a > 6) r += 1; + if (b > 12) r += 2; + if (c < -6) r += 4; + if (d == 11) r += 8; + return r; + } + static int handler7(int a, int b, int c, int d) { + int r = 7; + if (a > 7) r += 1; + if (b > 14) r += 2; + if (c < -7) r += 4; + if (d == 12) r += 8; + return r; + } + static int handler8(int a, int b, int c, int d) { + int r = 8; + if (a > 8) r += 1; + if (b > 16) r += 2; + if (c < -8) r += 4; + if (d == 13) r += 8; + return r; + } + static int handler9(int a, int b, int c, int d) { + int r = 9; + if (a > 9) r += 1; + if (b > 18) r += 2; + if (c < -9) r += 4; + if (d == 14) r += 8; + return r; + } + static int handler10(int a, int b, int c, int d) { + int r = 10; + if (a > 10) r += 1; + if (b > 20) r += 2; + if (c < -10) r += 4; + if (d == 15) r += 8; + return r; + } + static int handler11(int a, int b, int c, int d) { + int r = 11; + if (a > 11) r += 1; + if (b > 22) r += 2; + if (c < -11) r += 4; + if (d == 16) r += 8; + return r; + } + static int handler12(int a, int b, int c, int d) { + int r = 12; + if (a > 12) r += 1; + if (b > 24) r += 2; + if (c < -12) r += 4; + if (d == 17) r += 8; + return r; + } + static int handler13(int a, int b, int c, int d) { + int r = 13; + if (a > 13) r += 1; + if (b > 26) r += 2; + if (c < -13) r += 4; + if (d == 18) r += 8; + return r; + } + static int handler14(int a, int b, int c, int d) { + int r = 14; + if (a > 14) r += 1; + if (b > 28) r += 2; + if (c < -14) r += 4; + if (d == 19) r += 8; + return r; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n32_false.yml b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n32_false.yml new file mode 100644 index 00000000..7986f2a0 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n32_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - npe_n32_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n32_false/Main.java b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n32_false/Main.java new file mode 100644 index 00000000..3fb8dfc5 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n32_false/Main.java @@ -0,0 +1,332 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:-32 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// exception-dispatch/npe: the command is parsed from t; for t < 0 parsing throws +// (NullPointerException) and the handler derives the command as -t - 1. Only command 31 reaches the assert. +public class Main { + static class Holder { + int base = 0; + } + + static final Holder HOLDER = new Holder(); + + static int parse(int t) { + Holder h = t < 0 ? null : HOLDER; + return h.base + t; + } + + public static void main(String[] args) { + int t = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + int cmd; + try { + cmd = parse(t); + } catch (NullPointerException e) { + cmd = -t - 1; + } + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + int r; + switch (cmd) { + case 0: r = handler0(a, b, c, d); break; + case 1: r = handler1(a, b, c, d); break; + case 2: r = handler2(a, b, c, d); break; + case 3: r = handler3(a, b, c, d); break; + case 4: r = handler4(a, b, c, d); break; + case 5: r = handler5(a, b, c, d); break; + case 6: r = handler6(a, b, c, d); break; + case 7: r = handler7(a, b, c, d); break; + case 8: r = handler8(a, b, c, d); break; + case 9: r = handler9(a, b, c, d); break; + case 10: r = handler10(a, b, c, d); break; + case 11: r = handler11(a, b, c, d); break; + case 12: r = handler12(a, b, c, d); break; + case 13: r = handler13(a, b, c, d); break; + case 14: r = handler14(a, b, c, d); break; + case 15: r = handler15(a, b, c, d); break; + case 16: r = handler16(a, b, c, d); break; + case 17: r = handler17(a, b, c, d); break; + case 18: r = handler18(a, b, c, d); break; + case 19: r = handler19(a, b, c, d); break; + case 20: r = handler20(a, b, c, d); break; + case 21: r = handler21(a, b, c, d); break; + case 22: r = handler22(a, b, c, d); break; + case 23: r = handler23(a, b, c, d); break; + case 24: r = handler24(a, b, c, d); break; + case 25: r = handler25(a, b, c, d); break; + case 26: r = handler26(a, b, c, d); break; + case 27: r = handler27(a, b, c, d); break; + case 28: r = handler28(a, b, c, d); break; + case 29: r = handler29(a, b, c, d); break; + case 30: r = handler30(a, b, c, d); break; + case 31: target(a, b); r = 0; break; + default: r = -1; + } + return r; + } + + static int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + static int handler1(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + static int handler2(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + static int handler3(int a, int b, int c, int d) { + int r = 3; + if (a > 3) r += 1; + if (b > 6) r += 2; + if (c < -3) r += 4; + if (d == 8) r += 8; + return r; + } + static int handler4(int a, int b, int c, int d) { + int r = 4; + if (a > 4) r += 1; + if (b > 8) r += 2; + if (c < -4) r += 4; + if (d == 9) r += 8; + return r; + } + static int handler5(int a, int b, int c, int d) { + int r = 5; + if (a > 5) r += 1; + if (b > 10) r += 2; + if (c < -5) r += 4; + if (d == 10) r += 8; + return r; + } + static int handler6(int a, int b, int c, int d) { + int r = 6; + if (a > 6) r += 1; + if (b > 12) r += 2; + if (c < -6) r += 4; + if (d == 11) r += 8; + return r; + } + static int handler7(int a, int b, int c, int d) { + int r = 7; + if (a > 7) r += 1; + if (b > 14) r += 2; + if (c < -7) r += 4; + if (d == 12) r += 8; + return r; + } + static int handler8(int a, int b, int c, int d) { + int r = 8; + if (a > 8) r += 1; + if (b > 16) r += 2; + if (c < -8) r += 4; + if (d == 13) r += 8; + return r; + } + static int handler9(int a, int b, int c, int d) { + int r = 9; + if (a > 9) r += 1; + if (b > 18) r += 2; + if (c < -9) r += 4; + if (d == 14) r += 8; + return r; + } + static int handler10(int a, int b, int c, int d) { + int r = 10; + if (a > 10) r += 1; + if (b > 20) r += 2; + if (c < -10) r += 4; + if (d == 15) r += 8; + return r; + } + static int handler11(int a, int b, int c, int d) { + int r = 11; + if (a > 11) r += 1; + if (b > 22) r += 2; + if (c < -11) r += 4; + if (d == 16) r += 8; + return r; + } + static int handler12(int a, int b, int c, int d) { + int r = 12; + if (a > 12) r += 1; + if (b > 24) r += 2; + if (c < -12) r += 4; + if (d == 17) r += 8; + return r; + } + static int handler13(int a, int b, int c, int d) { + int r = 13; + if (a > 13) r += 1; + if (b > 26) r += 2; + if (c < -13) r += 4; + if (d == 18) r += 8; + return r; + } + static int handler14(int a, int b, int c, int d) { + int r = 14; + if (a > 14) r += 1; + if (b > 28) r += 2; + if (c < -14) r += 4; + if (d == 19) r += 8; + return r; + } + static int handler15(int a, int b, int c, int d) { + int r = 15; + if (a > 15) r += 1; + if (b > 30) r += 2; + if (c < -15) r += 4; + if (d == 20) r += 8; + return r; + } + static int handler16(int a, int b, int c, int d) { + int r = 16; + if (a > 16) r += 1; + if (b > 32) r += 2; + if (c < -16) r += 4; + if (d == 21) r += 8; + return r; + } + static int handler17(int a, int b, int c, int d) { + int r = 17; + if (a > 17) r += 1; + if (b > 34) r += 2; + if (c < -17) r += 4; + if (d == 22) r += 8; + return r; + } + static int handler18(int a, int b, int c, int d) { + int r = 18; + if (a > 18) r += 1; + if (b > 36) r += 2; + if (c < -18) r += 4; + if (d == 23) r += 8; + return r; + } + static int handler19(int a, int b, int c, int d) { + int r = 19; + if (a > 19) r += 1; + if (b > 38) r += 2; + if (c < -19) r += 4; + if (d == 24) r += 8; + return r; + } + static int handler20(int a, int b, int c, int d) { + int r = 20; + if (a > 20) r += 1; + if (b > 40) r += 2; + if (c < -20) r += 4; + if (d == 25) r += 8; + return r; + } + static int handler21(int a, int b, int c, int d) { + int r = 21; + if (a > 21) r += 1; + if (b > 42) r += 2; + if (c < -21) r += 4; + if (d == 26) r += 8; + return r; + } + static int handler22(int a, int b, int c, int d) { + int r = 22; + if (a > 22) r += 1; + if (b > 44) r += 2; + if (c < -22) r += 4; + if (d == 27) r += 8; + return r; + } + static int handler23(int a, int b, int c, int d) { + int r = 23; + if (a > 23) r += 1; + if (b > 46) r += 2; + if (c < -23) r += 4; + if (d == 28) r += 8; + return r; + } + static int handler24(int a, int b, int c, int d) { + int r = 24; + if (a > 24) r += 1; + if (b > 48) r += 2; + if (c < -24) r += 4; + if (d == 29) r += 8; + return r; + } + static int handler25(int a, int b, int c, int d) { + int r = 25; + if (a > 25) r += 1; + if (b > 50) r += 2; + if (c < -25) r += 4; + if (d == 30) r += 8; + return r; + } + static int handler26(int a, int b, int c, int d) { + int r = 26; + if (a > 26) r += 1; + if (b > 52) r += 2; + if (c < -26) r += 4; + if (d == 31) r += 8; + return r; + } + static int handler27(int a, int b, int c, int d) { + int r = 27; + if (a > 27) r += 1; + if (b > 54) r += 2; + if (c < -27) r += 4; + if (d == 32) r += 8; + return r; + } + static int handler28(int a, int b, int c, int d) { + int r = 28; + if (a > 28) r += 1; + if (b > 56) r += 2; + if (c < -28) r += 4; + if (d == 33) r += 8; + return r; + } + static int handler29(int a, int b, int c, int d) { + int r = 29; + if (a > 29) r += 1; + if (b > 58) r += 2; + if (c < -29) r += 4; + if (d == 34) r += 8; + return r; + } + static int handler30(int a, int b, int c, int d) { + int r = 30; + if (a > 30) r += 1; + if (b > 60) r += 2; + if (c < -30) r += 4; + if (d == 35) r += 8; + return r; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n32_true.yml b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n32_true.yml new file mode 100644 index 00000000..10cba577 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n32_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - npe_n32_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n32_true/Main.java b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n32_true/Main.java new file mode 100644 index 00000000..753b6542 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/exception-dispatch/npe_n32_true/Main.java @@ -0,0 +1,333 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:-32 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// exception-dispatch/npe: the command is parsed from t; for t < 0 parsing throws +// (NullPointerException) and the handler derives the command as -t - 1. Only command 31 reaches the assert. +public class Main { + static class Holder { + int base = 0; + } + + static final Holder HOLDER = new Holder(); + + static int parse(int t) { + Holder h = t < 0 ? null : HOLDER; + return h.base + t; + } + + public static void main(String[] args) { + int t = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + int cmd; + try { + cmd = parse(t); + } catch (NullPointerException e) { + cmd = -t - 1; + } + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + int r; + switch (cmd) { + case 0: r = handler0(a, b, c, d); break; + case 1: r = handler1(a, b, c, d); break; + case 2: r = handler2(a, b, c, d); break; + case 3: r = handler3(a, b, c, d); break; + case 4: r = handler4(a, b, c, d); break; + case 5: r = handler5(a, b, c, d); break; + case 6: r = handler6(a, b, c, d); break; + case 7: r = handler7(a, b, c, d); break; + case 8: r = handler8(a, b, c, d); break; + case 9: r = handler9(a, b, c, d); break; + case 10: r = handler10(a, b, c, d); break; + case 11: r = handler11(a, b, c, d); break; + case 12: r = handler12(a, b, c, d); break; + case 13: r = handler13(a, b, c, d); break; + case 14: r = handler14(a, b, c, d); break; + case 15: r = handler15(a, b, c, d); break; + case 16: r = handler16(a, b, c, d); break; + case 17: r = handler17(a, b, c, d); break; + case 18: r = handler18(a, b, c, d); break; + case 19: r = handler19(a, b, c, d); break; + case 20: r = handler20(a, b, c, d); break; + case 21: r = handler21(a, b, c, d); break; + case 22: r = handler22(a, b, c, d); break; + case 23: r = handler23(a, b, c, d); break; + case 24: r = handler24(a, b, c, d); break; + case 25: r = handler25(a, b, c, d); break; + case 26: r = handler26(a, b, c, d); break; + case 27: r = handler27(a, b, c, d); break; + case 28: r = handler28(a, b, c, d); break; + case 29: r = handler29(a, b, c, d); break; + case 30: r = handler30(a, b, c, d); break; + case 31: target(a, b); r = 0; break; + default: r = -1; + } + return r; + } + + static int handler0(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + static int handler1(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + static int handler2(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + static int handler3(int a, int b, int c, int d) { + int r = 3; + if (a > 3) r += 1; + if (b > 6) r += 2; + if (c < -3) r += 4; + if (d == 8) r += 8; + return r; + } + static int handler4(int a, int b, int c, int d) { + int r = 4; + if (a > 4) r += 1; + if (b > 8) r += 2; + if (c < -4) r += 4; + if (d == 9) r += 8; + return r; + } + static int handler5(int a, int b, int c, int d) { + int r = 5; + if (a > 5) r += 1; + if (b > 10) r += 2; + if (c < -5) r += 4; + if (d == 10) r += 8; + return r; + } + static int handler6(int a, int b, int c, int d) { + int r = 6; + if (a > 6) r += 1; + if (b > 12) r += 2; + if (c < -6) r += 4; + if (d == 11) r += 8; + return r; + } + static int handler7(int a, int b, int c, int d) { + int r = 7; + if (a > 7) r += 1; + if (b > 14) r += 2; + if (c < -7) r += 4; + if (d == 12) r += 8; + return r; + } + static int handler8(int a, int b, int c, int d) { + int r = 8; + if (a > 8) r += 1; + if (b > 16) r += 2; + if (c < -8) r += 4; + if (d == 13) r += 8; + return r; + } + static int handler9(int a, int b, int c, int d) { + int r = 9; + if (a > 9) r += 1; + if (b > 18) r += 2; + if (c < -9) r += 4; + if (d == 14) r += 8; + return r; + } + static int handler10(int a, int b, int c, int d) { + int r = 10; + if (a > 10) r += 1; + if (b > 20) r += 2; + if (c < -10) r += 4; + if (d == 15) r += 8; + return r; + } + static int handler11(int a, int b, int c, int d) { + int r = 11; + if (a > 11) r += 1; + if (b > 22) r += 2; + if (c < -11) r += 4; + if (d == 16) r += 8; + return r; + } + static int handler12(int a, int b, int c, int d) { + int r = 12; + if (a > 12) r += 1; + if (b > 24) r += 2; + if (c < -12) r += 4; + if (d == 17) r += 8; + return r; + } + static int handler13(int a, int b, int c, int d) { + int r = 13; + if (a > 13) r += 1; + if (b > 26) r += 2; + if (c < -13) r += 4; + if (d == 18) r += 8; + return r; + } + static int handler14(int a, int b, int c, int d) { + int r = 14; + if (a > 14) r += 1; + if (b > 28) r += 2; + if (c < -14) r += 4; + if (d == 19) r += 8; + return r; + } + static int handler15(int a, int b, int c, int d) { + int r = 15; + if (a > 15) r += 1; + if (b > 30) r += 2; + if (c < -15) r += 4; + if (d == 20) r += 8; + return r; + } + static int handler16(int a, int b, int c, int d) { + int r = 16; + if (a > 16) r += 1; + if (b > 32) r += 2; + if (c < -16) r += 4; + if (d == 21) r += 8; + return r; + } + static int handler17(int a, int b, int c, int d) { + int r = 17; + if (a > 17) r += 1; + if (b > 34) r += 2; + if (c < -17) r += 4; + if (d == 22) r += 8; + return r; + } + static int handler18(int a, int b, int c, int d) { + int r = 18; + if (a > 18) r += 1; + if (b > 36) r += 2; + if (c < -18) r += 4; + if (d == 23) r += 8; + return r; + } + static int handler19(int a, int b, int c, int d) { + int r = 19; + if (a > 19) r += 1; + if (b > 38) r += 2; + if (c < -19) r += 4; + if (d == 24) r += 8; + return r; + } + static int handler20(int a, int b, int c, int d) { + int r = 20; + if (a > 20) r += 1; + if (b > 40) r += 2; + if (c < -20) r += 4; + if (d == 25) r += 8; + return r; + } + static int handler21(int a, int b, int c, int d) { + int r = 21; + if (a > 21) r += 1; + if (b > 42) r += 2; + if (c < -21) r += 4; + if (d == 26) r += 8; + return r; + } + static int handler22(int a, int b, int c, int d) { + int r = 22; + if (a > 22) r += 1; + if (b > 44) r += 2; + if (c < -22) r += 4; + if (d == 27) r += 8; + return r; + } + static int handler23(int a, int b, int c, int d) { + int r = 23; + if (a > 23) r += 1; + if (b > 46) r += 2; + if (c < -23) r += 4; + if (d == 28) r += 8; + return r; + } + static int handler24(int a, int b, int c, int d) { + int r = 24; + if (a > 24) r += 1; + if (b > 48) r += 2; + if (c < -24) r += 4; + if (d == 29) r += 8; + return r; + } + static int handler25(int a, int b, int c, int d) { + int r = 25; + if (a > 25) r += 1; + if (b > 50) r += 2; + if (c < -25) r += 4; + if (d == 30) r += 8; + return r; + } + static int handler26(int a, int b, int c, int d) { + int r = 26; + if (a > 26) r += 1; + if (b > 52) r += 2; + if (c < -26) r += 4; + if (d == 31) r += 8; + return r; + } + static int handler27(int a, int b, int c, int d) { + int r = 27; + if (a > 27) r += 1; + if (b > 54) r += 2; + if (c < -27) r += 4; + if (d == 32) r += 8; + return r; + } + static int handler28(int a, int b, int c, int d) { + int r = 28; + if (a > 28) r += 1; + if (b > 56) r += 2; + if (c < -28) r += 4; + if (d == 33) r += 8; + return r; + } + static int handler29(int a, int b, int c, int d) { + int r = 29; + if (a > 29) r += 1; + if (b > 58) r += 2; + if (c < -29) r += 4; + if (d == 34) r += 8; + return r; + } + static int handler30(int a, int b, int c, int d) { + int r = 30; + if (a > 30) r += 1; + if (b > 60) r += 2; + if (c < -30) r += 4; + if (d == 35) r += 8; + return r; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/irrelevant-prefix/count_n02_false.yml b/targets/sv-comp/swat-benchmarks/java/irrelevant-prefix/count_n02_false.yml new file mode 100644 index 00000000..b48f9c44 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/irrelevant-prefix/count_n02_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - count_n02_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/irrelevant-prefix/count_n02_false/Main.java b/targets/sv-comp/swat-benchmarks/java/irrelevant-prefix/count_n02_false/Main.java new file mode 100644 index 00000000..4e074860 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/irrelevant-prefix/count_n02_false/Main.java @@ -0,0 +1,28 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// irrelevant-prefix/count: 2 independent branches before the only assert (control: nothing to prune). +public class Main { + static int count; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int x0 = Verifier.nondetInt(); + int x1 = Verifier.nondetInt(); + if (x0 > 0) count++; + if (x1 > 0) count++; + target(a, b); + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/irrelevant-prefix/count_n02_true.yml b/targets/sv-comp/swat-benchmarks/java/irrelevant-prefix/count_n02_true.yml new file mode 100644 index 00000000..afbeae88 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/irrelevant-prefix/count_n02_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - count_n02_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/irrelevant-prefix/count_n02_true/Main.java b/targets/sv-comp/swat-benchmarks/java/irrelevant-prefix/count_n02_true/Main.java new file mode 100644 index 00000000..4cecdbeb --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/irrelevant-prefix/count_n02_true/Main.java @@ -0,0 +1,29 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// irrelevant-prefix/count: 2 independent branches before the only assert (control: nothing to prune). +public class Main { + static int count; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int x0 = Verifier.nondetInt(); + int x1 = Verifier.nondetInt(); + if (x0 > 0) count++; + if (x1 > 0) count++; + target(a, b); + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/irrelevant-prefix/count_n04_false.yml b/targets/sv-comp/swat-benchmarks/java/irrelevant-prefix/count_n04_false.yml new file mode 100644 index 00000000..92e2aaca --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/irrelevant-prefix/count_n04_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - count_n04_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/irrelevant-prefix/count_n04_false/Main.java b/targets/sv-comp/swat-benchmarks/java/irrelevant-prefix/count_n04_false/Main.java new file mode 100644 index 00000000..aba065f5 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/irrelevant-prefix/count_n04_false/Main.java @@ -0,0 +1,32 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:2996 int:0 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// irrelevant-prefix/count: 4 independent branches before the only assert (control: nothing to prune). +public class Main { + static int count; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int x0 = Verifier.nondetInt(); + int x1 = Verifier.nondetInt(); + int x2 = Verifier.nondetInt(); + int x3 = Verifier.nondetInt(); + if (x0 > 0) count++; + if (x1 > 0) count++; + if (x2 > 0) count++; + if (x3 > 0) count++; + target(a, b); + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/irrelevant-prefix/count_n04_true.yml b/targets/sv-comp/swat-benchmarks/java/irrelevant-prefix/count_n04_true.yml new file mode 100644 index 00000000..15cde89e --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/irrelevant-prefix/count_n04_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - count_n04_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/irrelevant-prefix/count_n04_true/Main.java b/targets/sv-comp/swat-benchmarks/java/irrelevant-prefix/count_n04_true/Main.java new file mode 100644 index 00000000..60981768 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/irrelevant-prefix/count_n04_true/Main.java @@ -0,0 +1,33 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:1001 int:2996 int:0 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// irrelevant-prefix/count: 4 independent branches before the only assert (control: nothing to prune). +public class Main { + static int count; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int x0 = Verifier.nondetInt(); + int x1 = Verifier.nondetInt(); + int x2 = Verifier.nondetInt(); + int x3 = Verifier.nondetInt(); + if (x0 > 0) count++; + if (x1 > 0) count++; + if (x2 > 0) count++; + if (x3 > 0) count++; + target(a, b); + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/irrelevant-prefix/count_n06_false.yml b/targets/sv-comp/swat-benchmarks/java/irrelevant-prefix/count_n06_false.yml new file mode 100644 index 00000000..725ccb42 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/irrelevant-prefix/count_n06_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - count_n06_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/irrelevant-prefix/count_n06_false/Main.java b/targets/sv-comp/swat-benchmarks/java/irrelevant-prefix/count_n06_false/Main.java new file mode 100644 index 00000000..3bd06d24 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/irrelevant-prefix/count_n06_false/Main.java @@ -0,0 +1,36 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:2996 int:0 int:0 int:0 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// irrelevant-prefix/count: 6 independent branches before the only assert (control: nothing to prune). +public class Main { + static int count; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int x0 = Verifier.nondetInt(); + int x1 = Verifier.nondetInt(); + int x2 = Verifier.nondetInt(); + int x3 = Verifier.nondetInt(); + int x4 = Verifier.nondetInt(); + int x5 = Verifier.nondetInt(); + if (x0 > 0) count++; + if (x1 > 0) count++; + if (x2 > 0) count++; + if (x3 > 0) count++; + if (x4 > 0) count++; + if (x5 > 0) count++; + target(a, b); + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/irrelevant-prefix/count_n06_true.yml b/targets/sv-comp/swat-benchmarks/java/irrelevant-prefix/count_n06_true.yml new file mode 100644 index 00000000..2a2df05b --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/irrelevant-prefix/count_n06_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - count_n06_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/irrelevant-prefix/count_n06_true/Main.java b/targets/sv-comp/swat-benchmarks/java/irrelevant-prefix/count_n06_true/Main.java new file mode 100644 index 00000000..5476e0af --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/irrelevant-prefix/count_n06_true/Main.java @@ -0,0 +1,37 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:1001 int:2996 int:0 int:0 int:0 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// irrelevant-prefix/count: 6 independent branches before the only assert (control: nothing to prune). +public class Main { + static int count; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int x0 = Verifier.nondetInt(); + int x1 = Verifier.nondetInt(); + int x2 = Verifier.nondetInt(); + int x3 = Verifier.nondetInt(); + int x4 = Verifier.nondetInt(); + int x5 = Verifier.nondetInt(); + if (x0 > 0) count++; + if (x1 > 0) count++; + if (x2 > 0) count++; + if (x3 > 0) count++; + if (x4 > 0) count++; + if (x5 > 0) count++; + target(a, b); + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/irrelevant-prefix/count_n08_false.yml b/targets/sv-comp/swat-benchmarks/java/irrelevant-prefix/count_n08_false.yml new file mode 100644 index 00000000..2edbdcd4 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/irrelevant-prefix/count_n08_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - count_n08_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/irrelevant-prefix/count_n08_false/Main.java b/targets/sv-comp/swat-benchmarks/java/irrelevant-prefix/count_n08_false/Main.java new file mode 100644 index 00000000..f523f289 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/irrelevant-prefix/count_n08_false/Main.java @@ -0,0 +1,40 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:2996 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// irrelevant-prefix/count: 8 independent branches before the only assert (control: nothing to prune). +public class Main { + static int count; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int x0 = Verifier.nondetInt(); + int x1 = Verifier.nondetInt(); + int x2 = Verifier.nondetInt(); + int x3 = Verifier.nondetInt(); + int x4 = Verifier.nondetInt(); + int x5 = Verifier.nondetInt(); + int x6 = Verifier.nondetInt(); + int x7 = Verifier.nondetInt(); + if (x0 > 0) count++; + if (x1 > 0) count++; + if (x2 > 0) count++; + if (x3 > 0) count++; + if (x4 > 0) count++; + if (x5 > 0) count++; + if (x6 > 0) count++; + if (x7 > 0) count++; + target(a, b); + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/irrelevant-prefix/count_n08_true.yml b/targets/sv-comp/swat-benchmarks/java/irrelevant-prefix/count_n08_true.yml new file mode 100644 index 00000000..3426e109 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/irrelevant-prefix/count_n08_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - count_n08_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/irrelevant-prefix/count_n08_true/Main.java b/targets/sv-comp/swat-benchmarks/java/irrelevant-prefix/count_n08_true/Main.java new file mode 100644 index 00000000..87f712e8 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/irrelevant-prefix/count_n08_true/Main.java @@ -0,0 +1,41 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:1001 int:2996 int:0 int:0 int:0 int:0 int:0 int:0 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// irrelevant-prefix/count: 8 independent branches before the only assert (control: nothing to prune). +public class Main { + static int count; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int x0 = Verifier.nondetInt(); + int x1 = Verifier.nondetInt(); + int x2 = Verifier.nondetInt(); + int x3 = Verifier.nondetInt(); + int x4 = Verifier.nondetInt(); + int x5 = Verifier.nondetInt(); + int x6 = Verifier.nondetInt(); + int x7 = Verifier.nondetInt(); + if (x0 > 0) count++; + if (x1 > 0) count++; + if (x2 > 0) count++; + if (x3 > 0) count++; + if (x4 > 0) count++; + if (x5 > 0) count++; + if (x6 > 0) count++; + if (x7 > 0) count++; + target(a, b); + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-state/excluded-package_false.yml b/targets/sv-comp/swat-benchmarks/java/loop-state/excluded-package_false.yml new file mode 100644 index 00000000..fbe9560d --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-state/excluded-package_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - excluded-package_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-state/excluded-package_false/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-state/excluded-package_false/Main.java new file mode 100644 index 00000000..a4cba08f --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-state/excluded-package_false/Main.java @@ -0,0 +1,12 @@ +// Loop-state matching: Counter lives in a package SWAT does not instrument (com/google), so its +// static field is outside the hashed state. +import org.sosy_lab.sv_benchmarks.Verifier; +import com.google.loopstate.Counter; + +public class Main { + public static void main(String[] args) { + while (Verifier.nondetBoolean()) { + if (Counter.inc() == 3) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-state/excluded-package_false/com/google/loopstate/Counter.java b/targets/sv-comp/swat-benchmarks/java/loop-state/excluded-package_false/com/google/loopstate/Counter.java new file mode 100644 index 00000000..84b406e8 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-state/excluded-package_false/com/google/loopstate/Counter.java @@ -0,0 +1,7 @@ +package com.google.loopstate; + +public class Counter { + public static int c; + + public static int inc() { return ++c; } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-state/interrupt-flag_false.yml b/targets/sv-comp/swat-benchmarks/java/loop-state/interrupt-flag_false.yml new file mode 100644 index 00000000..1fb419c0 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-state/interrupt-flag_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - interrupt-flag_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-state/interrupt-flag_false/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-state/interrupt-flag_false/Main.java new file mode 100644 index 00000000..b082f7d9 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-state/interrupt-flag_false/Main.java @@ -0,0 +1,19 @@ +// Loop-state matching: the thread interrupt flag is state outside the hash. Two paths reach the +// same hashed state with the flag clear and set; Thread.interrupted() later tells them apart. +import org.sosy_lab.sv_benchmarks.Verifier; + +public class Main { + public static void main(String[] args) { + if (Verifier.nondetBoolean()) { + if (Verifier.nondetBoolean()) { args = args; } // smaller position: flag clear + } else { + Thread.currentThread().interrupt(); // larger position: flag set, same hashed state + if (Verifier.nondetBoolean()) { args = args; } + } + for (int i = 0; i < 2; i++) { + if (Verifier.nondetBoolean()) { + if (Thread.interrupted()) assert false; + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-state/objarray-length-nre_false.yml b/targets/sv-comp/swat-benchmarks/java/loop-state/objarray-length-nre_false.yml new file mode 100644 index 00000000..0f9a043a --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-state/objarray-length-nre_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - objarray-length-nre_false/ +properties: + - property_file: ../properties/no-runtime-exception.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-state/objarray-length-nre_false/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-state/objarray-length-nre_false/Main.java new file mode 100644 index 00000000..21393e2c --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-state/objarray-length-nre_false/Main.java @@ -0,0 +1,22 @@ +// As objarray-length_false, under no-runtime-exception: the violation is an IllegalStateException. +import org.sosy_lab.sv_benchmarks.Verifier; + +public class Main { + public static void main(String[] args) { + int n = Verifier.nondetInt(); + if (n < 0 || n > 3) return; + Object[] a = new Object[n]; // the shadow length stays symbolic + n = 0; // n dead: only a.length carries the input + // javac: a true condition falls through (outcome 0), so the then-side has the smaller position + if (Verifier.nondetBoolean()) { + if (a.length != 0) return; // the smaller path pins a.length == 0 + } else { + if (Verifier.nondetBoolean()) { n = 0; } // same number of branches, a.length unconstrained + } + for (int i = 0; i < 2; i++) { // bounded: the baseline explores everything + if (Verifier.nondetBoolean()) { + if (a.length == 2) throw new IllegalStateException(); + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-state/objarray-length-unbounded_false.yml b/targets/sv-comp/swat-benchmarks/java/loop-state/objarray-length-unbounded_false.yml new file mode 100644 index 00000000..817594a6 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-state/objarray-length-unbounded_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - objarray-length-unbounded_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-state/objarray-length-unbounded_false/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-state/objarray-length-unbounded_false/Main.java new file mode 100644 index 00000000..a8727c68 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-state/objarray-length-unbounded_false/Main.java @@ -0,0 +1,21 @@ +// As objarray-length_false, with an unbounded loop: a wrong match gives SAFE where the search can +// never finish otherwise. +import org.sosy_lab.sv_benchmarks.Verifier; + +public class Main { + public static void main(String[] args) { + int n = Verifier.nondetInt(); + if (n < 0 || n > 3) return; + Object[] a = new Object[n]; + n = 0; // n dead: only a.length carries the input + // javac: a true condition falls through (outcome 0), so the then-side has the smaller position + if (Verifier.nondetBoolean()) { + if (a.length != 0) return; // the smaller path pins a.length == 0 + } else { + if (Verifier.nondetBoolean()) { n = 0; } // same number of branches, a.length unconstrained + } + while (Verifier.nondetBoolean()) { + if (a.length == 2) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-state/objarray-length_false.yml b/targets/sv-comp/swat-benchmarks/java/loop-state/objarray-length_false.yml new file mode 100644 index 00000000..fe5eccb1 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-state/objarray-length_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - objarray-length_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-state/objarray-length_false/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-state/objarray-length_false/Main.java new file mode 100644 index 00000000..6098ac25 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-state/objarray-length_false/Main.java @@ -0,0 +1,24 @@ +// Loop-state matching: the length of new Object[n] stays symbolic after n is dead, so the state at +// the loop head is not concrete and must not be matched (a path that pins a.length == 0 would +// otherwise cut off one that can reach a.length == 2). +import org.sosy_lab.sv_benchmarks.Verifier; + +public class Main { + public static void main(String[] args) { + int n = Verifier.nondetInt(); + if (n < 0 || n > 3) return; + Object[] a = new Object[n]; // the shadow length stays symbolic + n = 0; // n dead: only a.length carries the input + // javac: a true condition falls through (outcome 0), so the then-side has the smaller position + if (Verifier.nondetBoolean()) { + if (a.length != 0) return; // the smaller path pins a.length == 0 + } else { + if (Verifier.nondetBoolean()) { n = 0; } // same number of branches, a.length unconstrained + } + for (int i = 0; i < 2; i++) { // bounded: the baseline explores everything + if (Verifier.nondetBoolean()) { + if (a.length == 2) assert false; + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-state/system-property_false.yml b/targets/sv-comp/swat-benchmarks/java/loop-state/system-property_false.yml new file mode 100644 index 00000000..6cafe71f --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-state/system-property_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - system-property_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-state/system-property_false/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-state/system-property_false/Main.java new file mode 100644 index 00000000..33649e7e --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-state/system-property_false/Main.java @@ -0,0 +1,13 @@ +// Loop-state matching: the loop keeps its counter in a System property, outside the hashed state. +// A run stops reporting states once it calls into the library. +import org.sosy_lab.sv_benchmarks.Verifier; + +public class Main { + public static void main(String[] args) { + while (Verifier.nondetBoolean()) { + int c = Integer.getInteger("loopstate.c", 0) + 1; + System.setProperty("loopstate.c", Integer.toString(c)); + if (c == 3) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/body-branch_false.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/body-branch_false.yml new file mode 100644 index 00000000..bcd3a0f7 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/body-branch_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - body-branch_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/body-branch_false/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/body-branch_false/Main.java new file mode 100644 index 00000000..00f71cbf --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/body-branch_false/Main.java @@ -0,0 +1,21 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:5 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// A counting loop with a branch in its body. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int i = 0; + int s = 0; + while (i < val) { + i++; + if (i == 3) { + s += 10; + } + s += 1; + } + if (s == 15) { + if (b > 1000 && b < 100000) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/body-branch_true.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/body-branch_true.yml new file mode 100644 index 00000000..0b6dabb0 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/body-branch_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - body-branch_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/body-branch_true/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/body-branch_true/Main.java new file mode 100644 index 00000000..c2105e02 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/body-branch_true/Main.java @@ -0,0 +1,22 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:5 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// A counting loop with a branch in its body. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int i = 0; + int s = 0; + while (i < val) { + i++; + if (i == 3) { + s += 10; + } + s += 1; + } + if (s == 15) { + if (b > 1000 && b < 1001) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/body-call-relevant_false.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/body-call-relevant_false.yml new file mode 100644 index 00000000..d75c2480 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/body-call-relevant_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - body-call-relevant_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/body-call-relevant_false/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/body-call-relevant_false/Main.java new file mode 100644 index 00000000..0d7da90f --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/body-call-relevant_false/Main.java @@ -0,0 +1,25 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:4 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// A counting loop whose body calls a helper that sets a flag the assert's guard reads. +public class Main { + static boolean reached; + + static void visit(int i) { + if (i == 4) { + reached = true; + } + } + + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int i = 0; + while (i < val) { + i++; + visit(i); + } + if (reached) { + if (b > 1000 && b < 100000) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/body-call-relevant_true.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/body-call-relevant_true.yml new file mode 100644 index 00000000..ae411119 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/body-call-relevant_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - body-call-relevant_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/body-call-relevant_true/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/body-call-relevant_true/Main.java new file mode 100644 index 00000000..47fe500b --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/body-call-relevant_true/Main.java @@ -0,0 +1,26 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:4 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// A counting loop whose body calls a helper that sets a flag the assert's guard reads. +public class Main { + static boolean reached; + + static void visit(int i) { + if (i == 4) { + reached = true; + } + } + + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int i = 0; + while (i < val) { + i++; + visit(i); + } + if (reached) { + if (b > 1000 && b < 1001) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/body-call_false.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/body-call_false.yml new file mode 100644 index 00000000..197e8bca --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/body-call_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - body-call_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/body-call_false/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/body-call_false/Main.java new file mode 100644 index 00000000..b95d8f02 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/body-call_false/Main.java @@ -0,0 +1,19 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:5 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// A counting loop with a call in its body: not a pure loop. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int i = 0; + int s = 0; + while (i < val) { + i++; + s += 2; + System.out.println(i); + } + if (s == 10) { + if (b > 1000 && b < 100000) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/body-call_true.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/body-call_true.yml new file mode 100644 index 00000000..9f10134a --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/body-call_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - body-call_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/body-call_true/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/body-call_true/Main.java new file mode 100644 index 00000000..02b5bed2 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/body-call_true/Main.java @@ -0,0 +1,20 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:5 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// A counting loop with a call in its body: not a pure loop. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int i = 0; + int s = 0; + while (i < val) { + i++; + s += 2; + System.out.println(i); + } + if (s == 10) { + if (b > 1000 && b < 1001) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/body-field-relevant_false.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/body-field-relevant_false.yml new file mode 100644 index 00000000..ef2805d7 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/body-field-relevant_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - body-field-relevant_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/body-field-relevant_false/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/body-field-relevant_false/Main.java new file mode 100644 index 00000000..526e537d --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/body-field-relevant_false/Main.java @@ -0,0 +1,22 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:5 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// A counting loop whose body also counts in a field the assert's guard reads: the field's count +// depends on the trip count but has no closed form, so the summary must not be used. +public class Main { + static int hits; + + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int i = 0; + int s = 0; + while (i < val) { + i++; + s++; + hits = hits + 1; + } + if (hits == 5) { + if (b > 1000 && b < 100000) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/body-field-relevant_true.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/body-field-relevant_true.yml new file mode 100644 index 00000000..4c8b5fab --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/body-field-relevant_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - body-field-relevant_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/body-field-relevant_true/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/body-field-relevant_true/Main.java new file mode 100644 index 00000000..a24f5388 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/body-field-relevant_true/Main.java @@ -0,0 +1,23 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:5 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// A counting loop whose body also counts in a field the assert's guard reads: the field's count +// depends on the trip count but has no closed form, so the summary must not be used. +public class Main { + static int hits; + + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int i = 0; + int s = 0; + while (i < val) { + i++; + s++; + hits = hits + 1; + } + if (hits == 5) { + if (b > 1000 && b < 1001) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/counter-overflow_false.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/counter-overflow_false.yml new file mode 100644 index 00000000..7404b0fa --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/counter-overflow_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - counter-overflow_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/counter-overflow_false/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/counter-overflow_false/Main.java new file mode 100644 index 00000000..a849b383 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/counter-overflow_false/Main.java @@ -0,0 +1,18 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:3 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// The merged counter must wrap around like Java's int: three steps of 10^9 are negative. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int count = 0; + for (int i = 0; i < 5; i++) { + if (i < val) { + count += 1000000000; + } + } + if (count < 0) { + if (b > 1000 && b < 100000) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/counter-overflow_true.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/counter-overflow_true.yml new file mode 100644 index 00000000..2ed8d800 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/counter-overflow_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - counter-overflow_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/counter-overflow_true/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/counter-overflow_true/Main.java new file mode 100644 index 00000000..0a86833f --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/counter-overflow_true/Main.java @@ -0,0 +1,19 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:3 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// The merged counter must wrap around like Java's int: three steps of 10^9 are negative. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int count = 0; + for (int i = 0; i < 5; i++) { + if (i < val) { + count += 1000000000; + } + } + if (count < 0) { + if (b > 1000 && b < 1001) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/diamond-then-check_false.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/diamond-then-check_false.yml new file mode 100644 index 00000000..85c885f6 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/diamond-then-check_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - diamond-then-check_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/diamond-then-check_false/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/diamond-then-check_false/Main.java new file mode 100644 index 00000000..b679ed3c --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/diamond-then-check_false/Main.java @@ -0,0 +1,23 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:5 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// Merged counters feed a later input-dependent check. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int even = 0; + int odd = 0; + for (int i = 0; i < 10; i++) { + if (i < val) { + if (i % 2 == 0) { + even++; + } else { + odd++; + } + } + } + if (even == 3 && odd == 2) { + if (b > 1000 && b < 100000) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/diamond-then-check_true.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/diamond-then-check_true.yml new file mode 100644 index 00000000..b2554616 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/diamond-then-check_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - diamond-then-check_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/diamond-then-check_true/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/diamond-then-check_true/Main.java new file mode 100644 index 00000000..2e1f878a --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/diamond-then-check_true/Main.java @@ -0,0 +1,24 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:5 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// Merged counters feed a later input-dependent check. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int even = 0; + int odd = 0; + for (int i = 0; i < 10; i++) { + if (i < val) { + if (i % 2 == 0) { + even++; + } else { + odd++; + } + } + } + if (even == 3 && odd == 2) { + if (b > 1000 && b < 1001) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/heap-in-loop_false.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/heap-in-loop_false.yml new file mode 100644 index 00000000..f04752e8 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/heap-in-loop_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - heap-in-loop_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/heap-in-loop_false/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/heap-in-loop_false/Main.java new file mode 100644 index 00000000..20463a5d --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/heap-in-loop_false/Main.java @@ -0,0 +1,20 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:4 int:1001 +import java.util.ArrayList; +import java.util.List; +import org.sosy_lab.sv_benchmarks.Verifier; +// A counting loop that adds to a list, whose size the assert's guard reads. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + List list = new ArrayList(); + int i = 0; + while (i < val) { + i++; + list.add(i); + } + if (list.size() == 4) { + if (b > 1000 && b < 100000) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/heap-in-loop_true.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/heap-in-loop_true.yml new file mode 100644 index 00000000..f868bbfe --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/heap-in-loop_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - heap-in-loop_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/heap-in-loop_true/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/heap-in-loop_true/Main.java new file mode 100644 index 00000000..e1efafb7 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/heap-in-loop_true/Main.java @@ -0,0 +1,21 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:4 int:1001 +import java.util.ArrayList; +import java.util.List; +import org.sosy_lab.sv_benchmarks.Verifier; +// A counting loop that adds to a list, whose size the assert's guard reads. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + List list = new ArrayList(); + int i = 0; + while (i < val) { + i++; + list.add(i); + } + if (list.size() == 4) { + if (b > 1000 && b < 1001) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/negative-start_false.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/negative-start_false.yml new file mode 100644 index 00000000..90cbe4fc --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/negative-start_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - negative-start_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/negative-start_false/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/negative-start_false/Main.java new file mode 100644 index 00000000..3ac319b3 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/negative-start_false/Main.java @@ -0,0 +1,20 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:-4 int:3 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// A counting loop from an input start to an input bound. +public class Main { + public static void main(String[] args) { + int lo = Verifier.nondetInt(); + int hi = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + if (hi - lo > 50 || lo < -50) return; + int i = lo; + int n = 0; + while (i < hi) { + i++; + n++; + } + if (n == 7 && i == 3) { + if (b > 1000 && b < 100000) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/negative-start_true.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/negative-start_true.yml new file mode 100644 index 00000000..261db7ec --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/negative-start_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - negative-start_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/negative-start_true/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/negative-start_true/Main.java new file mode 100644 index 00000000..da291cc4 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/negative-start_true/Main.java @@ -0,0 +1,21 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:-4 int:3 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// A counting loop from an input start to an input bound. +public class Main { + public static void main(String[] args) { + int lo = Verifier.nondetInt(); + int hi = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + if (hi - lo > 50 || lo < -50) return; + int i = lo; + int n = 0; + while (i < hi) { + i++; + n++; + } + if (n == 7 && i == 3) { + if (b > 1000 && b < 1001) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/nested-loops_false.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/nested-loops_false.yml new file mode 100644 index 00000000..77bdf121 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/nested-loops_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - nested-loops_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/nested-loops_false/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/nested-loops_false/Main.java new file mode 100644 index 00000000..9c278cb7 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/nested-loops_false/Main.java @@ -0,0 +1,20 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:3 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// A counting loop inside a loop: c = 4 * max(val, 0). +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = 0; + for (int k = 0; k < 4; k++) { + int j = 0; + while (j < val) { + j++; + c++; + } + } + if (c == 12) { + if (b > 1000 && b < 100000) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/nested-loops_true.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/nested-loops_true.yml new file mode 100644 index 00000000..b72b3c49 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/nested-loops_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - nested-loops_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/nested-loops_true/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/nested-loops_true/Main.java new file mode 100644 index 00000000..e722831a --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/nested-loops_true/Main.java @@ -0,0 +1,21 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:3 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// A counting loop inside a loop: c = 4 * max(val, 0). +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = 0; + for (int k = 0; k < 4; k++) { + int j = 0; + while (j < val) { + j++; + c++; + } + } + if (c == 12) { + if (b > 1000 && b < 1001) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/non-strict-bound_false.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/non-strict-bound_false.yml new file mode 100644 index 00000000..7d062ec2 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/non-strict-bound_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - non-strict-bound_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/non-strict-bound_false/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/non-strict-bound_false/Main.java new file mode 100644 index 00000000..9ab2eee3 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/non-strict-bound_false/Main.java @@ -0,0 +1,19 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:5 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// i <= val: the loop never ends for val = 2^31 - 1, so it has no closed form. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + if (val > 100) return; + int i = 0; + int s = 0; + while (i <= val) { + i++; + s++; + } + if (s == 6) { + if (b > 1000 && b < 100000) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/non-strict-bound_true.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/non-strict-bound_true.yml new file mode 100644 index 00000000..5dae5883 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/non-strict-bound_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - non-strict-bound_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/non-strict-bound_true/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/non-strict-bound_true/Main.java new file mode 100644 index 00000000..635532a2 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/non-strict-bound_true/Main.java @@ -0,0 +1,20 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:5 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// i <= val: the loop never ends for val = 2^31 - 1, so it has no closed form. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + if (val > 100) return; + int i = 0; + int s = 0; + while (i <= val) { + i++; + s++; + } + if (s == 6) { + if (b > 1000 && b < 1001) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/step-overflow_false.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/step-overflow_false.yml new file mode 100644 index 00000000..5776b7b1 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/step-overflow_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - step-overflow_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/step-overflow_false/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/step-overflow_false/Main.java new file mode 100644 index 00000000..132f83af --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/step-overflow_false/Main.java @@ -0,0 +1,19 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:3 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// The summarized loop's accumulator must wrap around like Java's int. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + if (val > 100) return; + int i = 0; + int s = 0; + while (i < val) { + i++; + s += 1000000000; + } + if (s < 0) { + if (b > 1000 && b < 100000) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/step-overflow_true.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/step-overflow_true.yml new file mode 100644 index 00000000..df24c5fe --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/step-overflow_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - step-overflow_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/step-overflow_true/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/step-overflow_true/Main.java new file mode 100644 index 00000000..bd91bc2d --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary-soundness/step-overflow_true/Main.java @@ -0,0 +1,20 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:3 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// The summarized loop's accumulator must wrap around like Java's int. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + if (val > 100) return; + int i = 0; + int s = 0; + while (i < val) { + i++; + s += 1000000000; + } + if (s < 0) { + if (b > 1000 && b < 1001) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-below_n0010_false.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-below_n0010_false.yml new file mode 100644 index 00000000..451981d9 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-below_n0010_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - count-below_n0010_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-below_n0010_false/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-below_n0010_false/Main.java new file mode 100644 index 00000000..8086b7a7 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-below_n0010_false/Main.java @@ -0,0 +1,15 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// loop-summary/count-below: count = number of i < n with i < val. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int count = 0; + for (int i = 0; i < 10; i++) { + if (i < val) { + count++; + } + } + if (count == 7) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-below_n0010_true.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-below_n0010_true.yml new file mode 100644 index 00000000..578a048b --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-below_n0010_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - count-below_n0010_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-below_n0010_true/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-below_n0010_true/Main.java new file mode 100644 index 00000000..14df4bb3 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-below_n0010_true/Main.java @@ -0,0 +1,16 @@ +// Expected verdict: SAFE. count is at most n. +// Paired VIOLATION inputs (must not fail here): int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// loop-summary/count-below: count = number of i < n with i < val. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int count = 0; + for (int i = 0; i < 10; i++) { + if (i < val) { + count++; + } + } + if (count == 11) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-below_n0100_false.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-below_n0100_false.yml new file mode 100644 index 00000000..1a6ec692 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-below_n0100_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - count-below_n0100_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-below_n0100_false/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-below_n0100_false/Main.java new file mode 100644 index 00000000..30061cb6 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-below_n0100_false/Main.java @@ -0,0 +1,15 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:97 +import org.sosy_lab.sv_benchmarks.Verifier; +// loop-summary/count-below: count = number of i < n with i < val. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int count = 0; + for (int i = 0; i < 100; i++) { + if (i < val) { + count++; + } + } + if (count == 97) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-below_n0100_true.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-below_n0100_true.yml new file mode 100644 index 00000000..10154927 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-below_n0100_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - count-below_n0100_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-below_n0100_true/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-below_n0100_true/Main.java new file mode 100644 index 00000000..499994a0 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-below_n0100_true/Main.java @@ -0,0 +1,16 @@ +// Expected verdict: SAFE. count is at most n. +// Paired VIOLATION inputs (must not fail here): int:97 +import org.sosy_lab.sv_benchmarks.Verifier; +// loop-summary/count-below: count = number of i < n with i < val. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int count = 0; + for (int i = 0; i < 100; i++) { + if (i < val) { + count++; + } + } + if (count == 101) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-below_n1000_false.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-below_n1000_false.yml new file mode 100644 index 00000000..110b09d9 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-below_n1000_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - count-below_n1000_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-below_n1000_false/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-below_n1000_false/Main.java new file mode 100644 index 00000000..68e29a4d --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-below_n1000_false/Main.java @@ -0,0 +1,15 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:997 +import org.sosy_lab.sv_benchmarks.Verifier; +// loop-summary/count-below: count = number of i < n with i < val. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int count = 0; + for (int i = 0; i < 1000; i++) { + if (i < val) { + count++; + } + } + if (count == 997) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-below_n1000_true.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-below_n1000_true.yml new file mode 100644 index 00000000..246b168a --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-below_n1000_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - count-below_n1000_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-below_n1000_true/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-below_n1000_true/Main.java new file mode 100644 index 00000000..9853e69c --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-below_n1000_true/Main.java @@ -0,0 +1,16 @@ +// Expected verdict: SAFE. count is at most n. +// Paired VIOLATION inputs (must not fail here): int:997 +import org.sosy_lab.sv_benchmarks.Verifier; +// loop-summary/count-below: count = number of i < n with i < val. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int count = 0; + for (int i = 0; i < 1000; i++) { + if (i < val) { + count++; + } + } + if (count == 1001) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-down_n0010_false.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-down_n0010_false.yml new file mode 100644 index 00000000..50b1fda9 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-down_n0010_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - count-down_n0010_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-down_n0010_false/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-down_n0010_false/Main.java new file mode 100644 index 00000000..d548d986 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-down_n0010_false/Main.java @@ -0,0 +1,15 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:10 +import org.sosy_lab.sv_benchmarks.Verifier; +// loop-summary/count-down: a loop counting an input down to zero. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int i = val; + int c = 0; + while (i > 0) { + i--; + c += 2; + } + if (c == 20) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-down_n0010_true.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-down_n0010_true.yml new file mode 100644 index 00000000..b6eab1d0 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-down_n0010_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - count-down_n0010_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-down_n0010_true/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-down_n0010_true/Main.java new file mode 100644 index 00000000..2b35cd89 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-down_n0010_true/Main.java @@ -0,0 +1,16 @@ +// Expected verdict: SAFE. c is even. +// Paired VIOLATION inputs (must not fail here): int:10 +import org.sosy_lab.sv_benchmarks.Verifier; +// loop-summary/count-down: a loop counting an input down to zero. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int i = val; + int c = 0; + while (i > 0) { + i--; + c += 2; + } + if (c == 21) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-down_n0100_false.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-down_n0100_false.yml new file mode 100644 index 00000000..2d79b71b --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-down_n0100_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - count-down_n0100_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-down_n0100_false/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-down_n0100_false/Main.java new file mode 100644 index 00000000..278cb457 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-down_n0100_false/Main.java @@ -0,0 +1,15 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:100 +import org.sosy_lab.sv_benchmarks.Verifier; +// loop-summary/count-down: a loop counting an input down to zero. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int i = val; + int c = 0; + while (i > 0) { + i--; + c += 2; + } + if (c == 200) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-down_n0100_true.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-down_n0100_true.yml new file mode 100644 index 00000000..3baff44a --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-down_n0100_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - count-down_n0100_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-down_n0100_true/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-down_n0100_true/Main.java new file mode 100644 index 00000000..ab5e58b2 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-down_n0100_true/Main.java @@ -0,0 +1,16 @@ +// Expected verdict: SAFE. c is even. +// Paired VIOLATION inputs (must not fail here): int:100 +import org.sosy_lab.sv_benchmarks.Verifier; +// loop-summary/count-down: a loop counting an input down to zero. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int i = val; + int c = 0; + while (i > 0) { + i--; + c += 2; + } + if (c == 201) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-down_n1000_false.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-down_n1000_false.yml new file mode 100644 index 00000000..4875eca5 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-down_n1000_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - count-down_n1000_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-down_n1000_false/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-down_n1000_false/Main.java new file mode 100644 index 00000000..5c83eb3d --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-down_n1000_false/Main.java @@ -0,0 +1,15 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1000 +import org.sosy_lab.sv_benchmarks.Verifier; +// loop-summary/count-down: a loop counting an input down to zero. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int i = val; + int c = 0; + while (i > 0) { + i--; + c += 2; + } + if (c == 2000) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-down_n1000_true.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-down_n1000_true.yml new file mode 100644 index 00000000..f2fe654c --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-down_n1000_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - count-down_n1000_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-down_n1000_true/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-down_n1000_true/Main.java new file mode 100644 index 00000000..6c375da5 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-down_n1000_true/Main.java @@ -0,0 +1,16 @@ +// Expected verdict: SAFE. c is even. +// Paired VIOLATION inputs (must not fail here): int:1000 +import org.sosy_lab.sv_benchmarks.Verifier; +// loop-summary/count-down: a loop counting an input down to zero. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int i = val; + int c = 0; + while (i > 0) { + i--; + c += 2; + } + if (c == 2001) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-even_n0010_false.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-even_n0010_false.yml new file mode 100644 index 00000000..250550a0 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-even_n0010_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - count-even_n0010_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-even_n0010_false/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-even_n0010_false/Main.java new file mode 100644 index 00000000..d51c1cf2 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-even_n0010_false/Main.java @@ -0,0 +1,20 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// loop-summary/count-even: a nested condition per iteration (i % 2 on a constant divisor). +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int even = 0; + int odd = 0; + for (int i = 0; i < 10; i++) { + if (i < val) { + if (i % 2 == 0) { + even++; + } else { + odd++; + } + } + } + if (even == 4) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-even_n0010_true.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-even_n0010_true.yml new file mode 100644 index 00000000..143661e1 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-even_n0010_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - count-even_n0010_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-even_n0010_true/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-even_n0010_true/Main.java new file mode 100644 index 00000000..46e57aaa --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-even_n0010_true/Main.java @@ -0,0 +1,21 @@ +// Expected verdict: SAFE. even counts the even i < min(val, n): at most n / 2. +// Paired VIOLATION inputs (must not fail here): int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// loop-summary/count-even: a nested condition per iteration (i % 2 on a constant divisor). +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int even = 0; + int odd = 0; + for (int i = 0; i < 10; i++) { + if (i < val) { + if (i % 2 == 0) { + even++; + } else { + odd++; + } + } + } + if (even == 6) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-even_n0100_false.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-even_n0100_false.yml new file mode 100644 index 00000000..1ae07f61 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-even_n0100_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - count-even_n0100_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-even_n0100_false/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-even_n0100_false/Main.java new file mode 100644 index 00000000..84e9bd44 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-even_n0100_false/Main.java @@ -0,0 +1,20 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:97 +import org.sosy_lab.sv_benchmarks.Verifier; +// loop-summary/count-even: a nested condition per iteration (i % 2 on a constant divisor). +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int even = 0; + int odd = 0; + for (int i = 0; i < 100; i++) { + if (i < val) { + if (i % 2 == 0) { + even++; + } else { + odd++; + } + } + } + if (even == 49) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-even_n0100_true.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-even_n0100_true.yml new file mode 100644 index 00000000..8934a72d --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-even_n0100_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - count-even_n0100_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-even_n0100_true/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-even_n0100_true/Main.java new file mode 100644 index 00000000..50b357ef --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-even_n0100_true/Main.java @@ -0,0 +1,21 @@ +// Expected verdict: SAFE. even counts the even i < min(val, n): at most n / 2. +// Paired VIOLATION inputs (must not fail here): int:97 +import org.sosy_lab.sv_benchmarks.Verifier; +// loop-summary/count-even: a nested condition per iteration (i % 2 on a constant divisor). +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int even = 0; + int odd = 0; + for (int i = 0; i < 100; i++) { + if (i < val) { + if (i % 2 == 0) { + even++; + } else { + odd++; + } + } + } + if (even == 51) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-even_n1000_false.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-even_n1000_false.yml new file mode 100644 index 00000000..6a9998d5 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-even_n1000_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - count-even_n1000_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-even_n1000_false/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-even_n1000_false/Main.java new file mode 100644 index 00000000..57d7da2d --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-even_n1000_false/Main.java @@ -0,0 +1,20 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:997 +import org.sosy_lab.sv_benchmarks.Verifier; +// loop-summary/count-even: a nested condition per iteration (i % 2 on a constant divisor). +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int even = 0; + int odd = 0; + for (int i = 0; i < 1000; i++) { + if (i < val) { + if (i % 2 == 0) { + even++; + } else { + odd++; + } + } + } + if (even == 499) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-even_n1000_true.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-even_n1000_true.yml new file mode 100644 index 00000000..17186f70 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-even_n1000_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - count-even_n1000_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-even_n1000_true/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-even_n1000_true/Main.java new file mode 100644 index 00000000..79175d14 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-even_n1000_true/Main.java @@ -0,0 +1,21 @@ +// Expected verdict: SAFE. even counts the even i < min(val, n): at most n / 2. +// Paired VIOLATION inputs (must not fail here): int:997 +import org.sosy_lab.sv_benchmarks.Verifier; +// loop-summary/count-even: a nested condition per iteration (i % 2 on a constant divisor). +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int even = 0; + int odd = 0; + for (int i = 0; i < 1000; i++) { + if (i < val) { + if (i % 2 == 0) { + even++; + } else { + odd++; + } + } + } + if (even == 501) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-log_n0010_false.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-log_n0010_false.yml new file mode 100644 index 00000000..49803760 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-log_n0010_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - count-up-log_n0010_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-log_n0010_false/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-log_n0010_false/Main.java new file mode 100644 index 00000000..8574bfdf --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-log_n0010_false/Main.java @@ -0,0 +1,24 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:10 +import org.sosy_lab.sv_benchmarks.Verifier; +// loop-summary/count-up-log: a counting loop that also logs into a field and a scratch array. +public class Main { + static int logged; + static int[] recent = new int[8]; + + static void log(int i) { + logged++; + recent[i & 7] = i; + } + + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int i = 0; + int s = 0; + while (i < val) { + i++; + s += 2; + log(i); + } + if (s == 20) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-log_n0010_true.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-log_n0010_true.yml new file mode 100644 index 00000000..077fee9d --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-log_n0010_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - count-up-log_n0010_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-log_n0010_true/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-log_n0010_true/Main.java new file mode 100644 index 00000000..db7d7a62 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-log_n0010_true/Main.java @@ -0,0 +1,25 @@ +// Expected verdict: SAFE. s is even. +// Paired VIOLATION inputs (must not fail here): int:10 +import org.sosy_lab.sv_benchmarks.Verifier; +// loop-summary/count-up-log: a counting loop that also logs into a field and a scratch array. +public class Main { + static int logged; + static int[] recent = new int[8]; + + static void log(int i) { + logged++; + recent[i & 7] = i; + } + + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int i = 0; + int s = 0; + while (i < val) { + i++; + s += 2; + log(i); + } + if (s == 21) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-log_n0100_false.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-log_n0100_false.yml new file mode 100644 index 00000000..073632cd --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-log_n0100_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - count-up-log_n0100_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-log_n0100_false/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-log_n0100_false/Main.java new file mode 100644 index 00000000..f8ecc577 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-log_n0100_false/Main.java @@ -0,0 +1,24 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:100 +import org.sosy_lab.sv_benchmarks.Verifier; +// loop-summary/count-up-log: a counting loop that also logs into a field and a scratch array. +public class Main { + static int logged; + static int[] recent = new int[8]; + + static void log(int i) { + logged++; + recent[i & 7] = i; + } + + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int i = 0; + int s = 0; + while (i < val) { + i++; + s += 2; + log(i); + } + if (s == 200) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-log_n0100_true.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-log_n0100_true.yml new file mode 100644 index 00000000..cc714afa --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-log_n0100_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - count-up-log_n0100_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-log_n0100_true/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-log_n0100_true/Main.java new file mode 100644 index 00000000..27135bf1 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-log_n0100_true/Main.java @@ -0,0 +1,25 @@ +// Expected verdict: SAFE. s is even. +// Paired VIOLATION inputs (must not fail here): int:100 +import org.sosy_lab.sv_benchmarks.Verifier; +// loop-summary/count-up-log: a counting loop that also logs into a field and a scratch array. +public class Main { + static int logged; + static int[] recent = new int[8]; + + static void log(int i) { + logged++; + recent[i & 7] = i; + } + + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int i = 0; + int s = 0; + while (i < val) { + i++; + s += 2; + log(i); + } + if (s == 201) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-log_n1000_false.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-log_n1000_false.yml new file mode 100644 index 00000000..6619f0e1 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-log_n1000_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - count-up-log_n1000_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-log_n1000_false/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-log_n1000_false/Main.java new file mode 100644 index 00000000..08465ab8 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-log_n1000_false/Main.java @@ -0,0 +1,24 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1000 +import org.sosy_lab.sv_benchmarks.Verifier; +// loop-summary/count-up-log: a counting loop that also logs into a field and a scratch array. +public class Main { + static int logged; + static int[] recent = new int[8]; + + static void log(int i) { + logged++; + recent[i & 7] = i; + } + + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int i = 0; + int s = 0; + while (i < val) { + i++; + s += 2; + log(i); + } + if (s == 2000) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-log_n1000_true.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-log_n1000_true.yml new file mode 100644 index 00000000..ad110217 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-log_n1000_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - count-up-log_n1000_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-log_n1000_true/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-log_n1000_true/Main.java new file mode 100644 index 00000000..5a0ffb36 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-log_n1000_true/Main.java @@ -0,0 +1,25 @@ +// Expected verdict: SAFE. s is even. +// Paired VIOLATION inputs (must not fail here): int:1000 +import org.sosy_lab.sv_benchmarks.Verifier; +// loop-summary/count-up-log: a counting loop that also logs into a field and a scratch array. +public class Main { + static int logged; + static int[] recent = new int[8]; + + static void log(int i) { + logged++; + recent[i & 7] = i; + } + + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int i = 0; + int s = 0; + while (i < val) { + i++; + s += 2; + log(i); + } + if (s == 2001) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-print_n0010_false.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-print_n0010_false.yml new file mode 100644 index 00000000..09708bff --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-print_n0010_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - count-up-print_n0010_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-print_n0010_false/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-print_n0010_false/Main.java new file mode 100644 index 00000000..7d995157 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-print_n0010_false/Main.java @@ -0,0 +1,16 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:10 +import org.sosy_lab.sv_benchmarks.Verifier; +// loop-summary/count-up-print: a counting loop up to an input bound that also prints. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int i = 0; + int s = 0; + while (i < val) { + i++; + s += 3; + System.out.println(i); + } + if (s == 30) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-print_n0010_true.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-print_n0010_true.yml new file mode 100644 index 00000000..bb15ceec --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-print_n0010_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - count-up-print_n0010_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-print_n0010_true/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-print_n0010_true/Main.java new file mode 100644 index 00000000..7f98c985 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-print_n0010_true/Main.java @@ -0,0 +1,17 @@ +// Expected verdict: SAFE. s = 3 * max(val, 0) with val <= 2^31 - 1, which is never 3n + 1 (mod 2^32). +// Paired VIOLATION inputs (must not fail here): int:10 +import org.sosy_lab.sv_benchmarks.Verifier; +// loop-summary/count-up-print: a counting loop up to an input bound that also prints. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int i = 0; + int s = 0; + while (i < val) { + i++; + s += 3; + System.out.println(i); + } + if (s == 31) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-print_n0100_false.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-print_n0100_false.yml new file mode 100644 index 00000000..36ffea11 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-print_n0100_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - count-up-print_n0100_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-print_n0100_false/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-print_n0100_false/Main.java new file mode 100644 index 00000000..1702a157 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-print_n0100_false/Main.java @@ -0,0 +1,16 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:100 +import org.sosy_lab.sv_benchmarks.Verifier; +// loop-summary/count-up-print: a counting loop up to an input bound that also prints. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int i = 0; + int s = 0; + while (i < val) { + i++; + s += 3; + System.out.println(i); + } + if (s == 300) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-print_n0100_true.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-print_n0100_true.yml new file mode 100644 index 00000000..8137b525 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-print_n0100_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - count-up-print_n0100_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-print_n0100_true/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-print_n0100_true/Main.java new file mode 100644 index 00000000..4f5e3cfd --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-print_n0100_true/Main.java @@ -0,0 +1,17 @@ +// Expected verdict: SAFE. s = 3 * max(val, 0) with val <= 2^31 - 1, which is never 3n + 1 (mod 2^32). +// Paired VIOLATION inputs (must not fail here): int:100 +import org.sosy_lab.sv_benchmarks.Verifier; +// loop-summary/count-up-print: a counting loop up to an input bound that also prints. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int i = 0; + int s = 0; + while (i < val) { + i++; + s += 3; + System.out.println(i); + } + if (s == 301) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-print_n1000_false.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-print_n1000_false.yml new file mode 100644 index 00000000..2555b482 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-print_n1000_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - count-up-print_n1000_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-print_n1000_false/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-print_n1000_false/Main.java new file mode 100644 index 00000000..a600177f --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-print_n1000_false/Main.java @@ -0,0 +1,16 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1000 +import org.sosy_lab.sv_benchmarks.Verifier; +// loop-summary/count-up-print: a counting loop up to an input bound that also prints. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int i = 0; + int s = 0; + while (i < val) { + i++; + s += 3; + System.out.println(i); + } + if (s == 3000) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-print_n1000_true.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-print_n1000_true.yml new file mode 100644 index 00000000..9f7bd847 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-print_n1000_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - count-up-print_n1000_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-print_n1000_true/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-print_n1000_true/Main.java new file mode 100644 index 00000000..46c6e618 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up-print_n1000_true/Main.java @@ -0,0 +1,17 @@ +// Expected verdict: SAFE. s = 3 * max(val, 0) with val <= 2^31 - 1, which is never 3n + 1 (mod 2^32). +// Paired VIOLATION inputs (must not fail here): int:1000 +import org.sosy_lab.sv_benchmarks.Verifier; +// loop-summary/count-up-print: a counting loop up to an input bound that also prints. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int i = 0; + int s = 0; + while (i < val) { + i++; + s += 3; + System.out.println(i); + } + if (s == 3001) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up_n0010_false.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up_n0010_false.yml new file mode 100644 index 00000000..001d700b --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up_n0010_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - count-up_n0010_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up_n0010_false/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up_n0010_false/Main.java new file mode 100644 index 00000000..514f7cb9 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up_n0010_false/Main.java @@ -0,0 +1,15 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:10 +import org.sosy_lab.sv_benchmarks.Verifier; +// loop-summary/count-up: a counting loop up to an input bound. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int i = 0; + int s = 0; + while (i < val) { + i++; + s += 3; + } + if (s == 30) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up_n0010_true.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up_n0010_true.yml new file mode 100644 index 00000000..bb48165e --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up_n0010_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - count-up_n0010_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up_n0010_true/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up_n0010_true/Main.java new file mode 100644 index 00000000..d75acb56 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up_n0010_true/Main.java @@ -0,0 +1,16 @@ +// Expected verdict: SAFE. s = 3 * max(val, 0) with val <= 2^31 - 1, which is never 3n + 1 (mod 2^32). +// Paired VIOLATION inputs (must not fail here): int:10 +import org.sosy_lab.sv_benchmarks.Verifier; +// loop-summary/count-up: a counting loop up to an input bound. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int i = 0; + int s = 0; + while (i < val) { + i++; + s += 3; + } + if (s == 31) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up_n0100_false.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up_n0100_false.yml new file mode 100644 index 00000000..7df85574 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up_n0100_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - count-up_n0100_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up_n0100_false/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up_n0100_false/Main.java new file mode 100644 index 00000000..6fc4b27f --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up_n0100_false/Main.java @@ -0,0 +1,15 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:100 +import org.sosy_lab.sv_benchmarks.Verifier; +// loop-summary/count-up: a counting loop up to an input bound. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int i = 0; + int s = 0; + while (i < val) { + i++; + s += 3; + } + if (s == 300) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up_n0100_true.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up_n0100_true.yml new file mode 100644 index 00000000..22fcb535 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up_n0100_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - count-up_n0100_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up_n0100_true/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up_n0100_true/Main.java new file mode 100644 index 00000000..975ad3da --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up_n0100_true/Main.java @@ -0,0 +1,16 @@ +// Expected verdict: SAFE. s = 3 * max(val, 0) with val <= 2^31 - 1, which is never 3n + 1 (mod 2^32). +// Paired VIOLATION inputs (must not fail here): int:100 +import org.sosy_lab.sv_benchmarks.Verifier; +// loop-summary/count-up: a counting loop up to an input bound. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int i = 0; + int s = 0; + while (i < val) { + i++; + s += 3; + } + if (s == 301) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up_n1000_false.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up_n1000_false.yml new file mode 100644 index 00000000..df4ade96 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up_n1000_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - count-up_n1000_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up_n1000_false/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up_n1000_false/Main.java new file mode 100644 index 00000000..1f38600d --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up_n1000_false/Main.java @@ -0,0 +1,15 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1000 +import org.sosy_lab.sv_benchmarks.Verifier; +// loop-summary/count-up: a counting loop up to an input bound. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int i = 0; + int s = 0; + while (i < val) { + i++; + s += 3; + } + if (s == 3000) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up_n1000_true.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up_n1000_true.yml new file mode 100644 index 00000000..c462e14a --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up_n1000_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - count-up_n1000_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up_n1000_true/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up_n1000_true/Main.java new file mode 100644 index 00000000..e2ad6dec --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-up_n1000_true/Main.java @@ -0,0 +1,16 @@ +// Expected verdict: SAFE. s = 3 * max(val, 0) with val <= 2^31 - 1, which is never 3n + 1 (mod 2^32). +// Paired VIOLATION inputs (must not fail here): int:1000 +import org.sosy_lab.sv_benchmarks.Verifier; +// loop-summary/count-up: a counting loop up to an input bound. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int i = 0; + int s = 0; + while (i < val) { + i++; + s += 3; + } + if (s == 3001) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-upto_n0010_false.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-upto_n0010_false.yml new file mode 100644 index 00000000..b5d79068 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-upto_n0010_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - count-upto_n0010_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-upto_n0010_false/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-upto_n0010_false/Main.java new file mode 100644 index 00000000..00bf2560 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-upto_n0010_false/Main.java @@ -0,0 +1,15 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:10 +import org.sosy_lab.sv_benchmarks.Verifier; +// loop-summary/count-upto: a counting loop up to and including an input bound. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int i = 1; + int s = 0; + while (i <= val) { + i++; + s += 2; + } + if (s == 20) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-upto_n0010_true.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-upto_n0010_true.yml new file mode 100644 index 00000000..d25d800c --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-upto_n0010_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - count-upto_n0010_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-upto_n0010_true/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-upto_n0010_true/Main.java new file mode 100644 index 00000000..b95e2b3f --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-upto_n0010_true/Main.java @@ -0,0 +1,16 @@ +// Expected verdict: SAFE. s is even. +// Paired VIOLATION inputs (must not fail here): int:10 +import org.sosy_lab.sv_benchmarks.Verifier; +// loop-summary/count-upto: a counting loop up to and including an input bound. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int i = 1; + int s = 0; + while (i <= val) { + i++; + s += 2; + } + if (s == 21) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-upto_n0100_false.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-upto_n0100_false.yml new file mode 100644 index 00000000..7207b3f4 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-upto_n0100_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - count-upto_n0100_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-upto_n0100_false/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-upto_n0100_false/Main.java new file mode 100644 index 00000000..7c826b66 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-upto_n0100_false/Main.java @@ -0,0 +1,15 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:100 +import org.sosy_lab.sv_benchmarks.Verifier; +// loop-summary/count-upto: a counting loop up to and including an input bound. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int i = 1; + int s = 0; + while (i <= val) { + i++; + s += 2; + } + if (s == 200) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-upto_n0100_true.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-upto_n0100_true.yml new file mode 100644 index 00000000..57c9c669 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-upto_n0100_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - count-upto_n0100_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-upto_n0100_true/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-upto_n0100_true/Main.java new file mode 100644 index 00000000..d2b83178 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-upto_n0100_true/Main.java @@ -0,0 +1,16 @@ +// Expected verdict: SAFE. s is even. +// Paired VIOLATION inputs (must not fail here): int:100 +import org.sosy_lab.sv_benchmarks.Verifier; +// loop-summary/count-upto: a counting loop up to and including an input bound. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int i = 1; + int s = 0; + while (i <= val) { + i++; + s += 2; + } + if (s == 201) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-upto_n1000_false.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-upto_n1000_false.yml new file mode 100644 index 00000000..6446d5b3 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-upto_n1000_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - count-upto_n1000_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-upto_n1000_false/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-upto_n1000_false/Main.java new file mode 100644 index 00000000..85e9ebaf --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-upto_n1000_false/Main.java @@ -0,0 +1,15 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1000 +import org.sosy_lab.sv_benchmarks.Verifier; +// loop-summary/count-upto: a counting loop up to and including an input bound. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int i = 1; + int s = 0; + while (i <= val) { + i++; + s += 2; + } + if (s == 2000) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-upto_n1000_true.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-upto_n1000_true.yml new file mode 100644 index 00000000..36b90a25 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-upto_n1000_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - count-upto_n1000_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/count-upto_n1000_true/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-upto_n1000_true/Main.java new file mode 100644 index 00000000..0831658d --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/count-upto_n1000_true/Main.java @@ -0,0 +1,16 @@ +// Expected verdict: SAFE. s is even. +// Paired VIOLATION inputs (must not fail here): int:1000 +import org.sosy_lab.sv_benchmarks.Verifier; +// loop-summary/count-upto: a counting loop up to and including an input bound. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int i = 1; + int s = 0; + while (i <= val) { + i++; + s += 2; + } + if (s == 2001) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/found-index_n0010_false.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary/found-index_n0010_false.yml new file mode 100644 index 00000000..990abd05 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/found-index_n0010_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - found-index_n0010_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/found-index_n0010_false/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary/found-index_n0010_false/Main.java new file mode 100644 index 00000000..5175f95e --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/found-index_n0010_false/Main.java @@ -0,0 +1,15 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:8 +import org.sosy_lab.sv_benchmarks.Verifier; +// loop-summary/found-index: found = the index equal to val, or -1. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int found = -1; + for (int i = 0; i < 10; i++) { + if (i == val) { + found = i; + } + } + if (found == 8) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/found-index_n0010_true.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary/found-index_n0010_true.yml new file mode 100644 index 00000000..11dc286b --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/found-index_n0010_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - found-index_n0010_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/found-index_n0010_true/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary/found-index_n0010_true/Main.java new file mode 100644 index 00000000..e7adf889 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/found-index_n0010_true/Main.java @@ -0,0 +1,16 @@ +// Expected verdict: SAFE. found is below n. +// Paired VIOLATION inputs (must not fail here): int:8 +import org.sosy_lab.sv_benchmarks.Verifier; +// loop-summary/found-index: found = the index equal to val, or -1. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int found = -1; + for (int i = 0; i < 10; i++) { + if (i == val) { + found = i; + } + } + if (found == 10) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/found-index_n0100_false.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary/found-index_n0100_false.yml new file mode 100644 index 00000000..0576ccc4 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/found-index_n0100_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - found-index_n0100_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/found-index_n0100_false/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary/found-index_n0100_false/Main.java new file mode 100644 index 00000000..2947ea2a --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/found-index_n0100_false/Main.java @@ -0,0 +1,15 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:98 +import org.sosy_lab.sv_benchmarks.Verifier; +// loop-summary/found-index: found = the index equal to val, or -1. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int found = -1; + for (int i = 0; i < 100; i++) { + if (i == val) { + found = i; + } + } + if (found == 98) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/found-index_n0100_true.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary/found-index_n0100_true.yml new file mode 100644 index 00000000..2a739dab --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/found-index_n0100_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - found-index_n0100_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/found-index_n0100_true/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary/found-index_n0100_true/Main.java new file mode 100644 index 00000000..f0529aee --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/found-index_n0100_true/Main.java @@ -0,0 +1,16 @@ +// Expected verdict: SAFE. found is below n. +// Paired VIOLATION inputs (must not fail here): int:98 +import org.sosy_lab.sv_benchmarks.Verifier; +// loop-summary/found-index: found = the index equal to val, or -1. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int found = -1; + for (int i = 0; i < 100; i++) { + if (i == val) { + found = i; + } + } + if (found == 100) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/found-index_n1000_false.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary/found-index_n1000_false.yml new file mode 100644 index 00000000..4dd8b19b --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/found-index_n1000_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - found-index_n1000_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/found-index_n1000_false/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary/found-index_n1000_false/Main.java new file mode 100644 index 00000000..d872da83 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/found-index_n1000_false/Main.java @@ -0,0 +1,15 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:998 +import org.sosy_lab.sv_benchmarks.Verifier; +// loop-summary/found-index: found = the index equal to val, or -1. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int found = -1; + for (int i = 0; i < 1000; i++) { + if (i == val) { + found = i; + } + } + if (found == 998) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/found-index_n1000_true.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary/found-index_n1000_true.yml new file mode 100644 index 00000000..25bbb631 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/found-index_n1000_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - found-index_n1000_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/found-index_n1000_true/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary/found-index_n1000_true/Main.java new file mode 100644 index 00000000..3fbd3a4f --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/found-index_n1000_true/Main.java @@ -0,0 +1,16 @@ +// Expected verdict: SAFE. found is below n. +// Paired VIOLATION inputs (must not fail here): int:998 +import org.sosy_lab.sv_benchmarks.Verifier; +// loop-summary/found-index: found = the index equal to val, or -1. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int found = -1; + for (int i = 0; i < 1000; i++) { + if (i == val) { + found = i; + } + } + if (found == 1000) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/ternary-sum_n0010_false.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary/ternary-sum_n0010_false.yml new file mode 100644 index 00000000..98f37d36 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/ternary-sum_n0010_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - ternary-sum_n0010_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/ternary-sum_n0010_false/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary/ternary-sum_n0010_false/Main.java new file mode 100644 index 00000000..7452ac68 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/ternary-sum_n0010_false/Main.java @@ -0,0 +1,13 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// loop-summary/ternary-sum: s grows by 2 in every iteration with i < val (a conditional expression). +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int s = 0; + for (int i = 0; i < 10; i++) { + s = i < val ? s + 2 : s; + } + if (s == 14) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/ternary-sum_n0010_true.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary/ternary-sum_n0010_true.yml new file mode 100644 index 00000000..39c24a23 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/ternary-sum_n0010_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - ternary-sum_n0010_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/ternary-sum_n0010_true/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary/ternary-sum_n0010_true/Main.java new file mode 100644 index 00000000..93631c90 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/ternary-sum_n0010_true/Main.java @@ -0,0 +1,14 @@ +// Expected verdict: SAFE. s is at most 2n. +// Paired VIOLATION inputs (must not fail here): int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// loop-summary/ternary-sum: s grows by 2 in every iteration with i < val (a conditional expression). +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int s = 0; + for (int i = 0; i < 10; i++) { + s = i < val ? s + 2 : s; + } + if (s == 22) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/ternary-sum_n0100_false.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary/ternary-sum_n0100_false.yml new file mode 100644 index 00000000..68992b3d --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/ternary-sum_n0100_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - ternary-sum_n0100_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/ternary-sum_n0100_false/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary/ternary-sum_n0100_false/Main.java new file mode 100644 index 00000000..61acbef6 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/ternary-sum_n0100_false/Main.java @@ -0,0 +1,13 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:97 +import org.sosy_lab.sv_benchmarks.Verifier; +// loop-summary/ternary-sum: s grows by 2 in every iteration with i < val (a conditional expression). +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int s = 0; + for (int i = 0; i < 100; i++) { + s = i < val ? s + 2 : s; + } + if (s == 194) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/ternary-sum_n0100_true.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary/ternary-sum_n0100_true.yml new file mode 100644 index 00000000..0f71f7b1 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/ternary-sum_n0100_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - ternary-sum_n0100_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/ternary-sum_n0100_true/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary/ternary-sum_n0100_true/Main.java new file mode 100644 index 00000000..505f618d --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/ternary-sum_n0100_true/Main.java @@ -0,0 +1,14 @@ +// Expected verdict: SAFE. s is at most 2n. +// Paired VIOLATION inputs (must not fail here): int:97 +import org.sosy_lab.sv_benchmarks.Verifier; +// loop-summary/ternary-sum: s grows by 2 in every iteration with i < val (a conditional expression). +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int s = 0; + for (int i = 0; i < 100; i++) { + s = i < val ? s + 2 : s; + } + if (s == 202) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/ternary-sum_n1000_false.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary/ternary-sum_n1000_false.yml new file mode 100644 index 00000000..1532d4bf --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/ternary-sum_n1000_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - ternary-sum_n1000_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/ternary-sum_n1000_false/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary/ternary-sum_n1000_false/Main.java new file mode 100644 index 00000000..be5de1c2 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/ternary-sum_n1000_false/Main.java @@ -0,0 +1,13 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:997 +import org.sosy_lab.sv_benchmarks.Verifier; +// loop-summary/ternary-sum: s grows by 2 in every iteration with i < val (a conditional expression). +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int s = 0; + for (int i = 0; i < 1000; i++) { + s = i < val ? s + 2 : s; + } + if (s == 1994) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/ternary-sum_n1000_true.yml b/targets/sv-comp/swat-benchmarks/java/loop-summary/ternary-sum_n1000_true.yml new file mode 100644 index 00000000..d9b78f61 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/ternary-sum_n1000_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - ternary-sum_n1000_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/loop-summary/ternary-sum_n1000_true/Main.java b/targets/sv-comp/swat-benchmarks/java/loop-summary/ternary-sum_n1000_true/Main.java new file mode 100644 index 00000000..d363a7de --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/loop-summary/ternary-sum_n1000_true/Main.java @@ -0,0 +1,14 @@ +// Expected verdict: SAFE. s is at most 2n. +// Paired VIOLATION inputs (must not fail here): int:997 +import org.sosy_lab.sv_benchmarks.Verifier; +// loop-summary/ternary-sum: s grows by 2 in every iteration with i < val (a conditional expression). +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int s = 0; + for (int i = 0; i < 1000; i++) { + s = i < val ? s + 2 : s; + } + if (s == 2002) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/properties/no-deadlock.prp b/targets/sv-comp/swat-benchmarks/java/properties/no-deadlock.prp new file mode 100644 index 00000000..baa56420 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/properties/no-deadlock.prp @@ -0,0 +1 @@ +CHECK( init(Main.main()), LTL(G !deadlock) ) diff --git a/targets/sv-comp/swat-benchmarks/java/properties/no-deadlock.prp.license b/targets/sv-comp/swat-benchmarks/java/properties/no-deadlock.prp.license new file mode 100644 index 00000000..5a43e6b7 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/properties/no-deadlock.prp.license @@ -0,0 +1,6 @@ +This file is part of the SV-Benchmarks collection of verification tasks: +https://github.com/sosy-lab/sv-benchmarks + +SPDX-FileCopyrightText: 2011-2020 The SV-Benchmarks Community + +SPDX-License-Identifier: Apache-2.0 \ No newline at end of file diff --git a/targets/sv-comp/swat-benchmarks/java/properties/no-runtime-exception.prp b/targets/sv-comp/swat-benchmarks/java/properties/no-runtime-exception.prp new file mode 100644 index 00000000..55b5c59a --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/properties/no-runtime-exception.prp @@ -0,0 +1 @@ +CHECK(init(Main.main()), LTL(G ! uncaught(java.lang.RuntimeException))) diff --git a/targets/sv-comp/swat-benchmarks/java/properties/no-runtime-exception.prp.license b/targets/sv-comp/swat-benchmarks/java/properties/no-runtime-exception.prp.license new file mode 100644 index 00000000..b76c19f5 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/properties/no-runtime-exception.prp.license @@ -0,0 +1,6 @@ +This file is part of the SV-Benchmarks collection of verification tasks: +https://github.com/sosy-lab/sv-benchmarks + +SPDX-FileCopyrightText: 2011-2020 The SV-Benchmarks Community + +SPDX-License-Identifier: Apache-2.0 diff --git a/targets/sv-comp/swat-benchmarks/java/properties/valid-assert.prp b/targets/sv-comp/swat-benchmarks/java/properties/valid-assert.prp new file mode 100644 index 00000000..a9d5a52b --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/properties/valid-assert.prp @@ -0,0 +1 @@ +CHECK( init(Main.main()), LTL(G assert) ) diff --git a/targets/sv-comp/swat-benchmarks/java/properties/valid-assert.prp.license b/targets/sv-comp/swat-benchmarks/java/properties/valid-assert.prp.license new file mode 100644 index 00000000..5a43e6b7 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/properties/valid-assert.prp.license @@ -0,0 +1,6 @@ +This file is part of the SV-Benchmarks collection of verification tasks: +https://github.com/sosy-lab/sv-benchmarks + +SPDX-FileCopyrightText: 2011-2020 The SV-Benchmarks Community + +SPDX-License-Identifier: Apache-2.0 \ No newline at end of file diff --git a/targets/sv-comp/swat-benchmarks/java/real-tools/getopt-minitar_false.yml b/targets/sv-comp/swat-benchmarks/java/real-tools/getopt-minitar_false.yml new file mode 100644 index 00000000..f9b51e61 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/real-tools/getopt-minitar_false.yml @@ -0,0 +1,10 @@ +format_version: '2.0' +input_files: + - ../common/ + - lib-getopt/ + - getopt-minitar_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/real-tools/getopt-minitar_false/Main.java b/targets/sv-comp/swat-benchmarks/java/real-tools/getopt-minitar_false/Main.java new file mode 100644 index 00000000..bf258c33 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/real-tools/getopt-minitar_false/Main.java @@ -0,0 +1,87 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): string:-xv string:-f string:x +import gnu.getopt.Getopt; +import org.sosy_lab.sv_benchmarks.Verifier; + +// A small tar-like CLI on GNU getopt (java-getopt 1.0.13, unmodified). Three nondet argv +// strings. Help, version, usage errors and the create action leave without reaching the assert; +// only extracting with a verbose flag and the archive name "x" does. +public class Main { + static int verbose; + static boolean create, extract, list; + static String file; + static String level; + + public static void main(String[] args) { + String[] argv = new String[3]; + for (int i = 0; i < argv.length; i++) { + argv[i] = Verifier.nondetString(); + } + Getopt g = new Getopt("minitar", argv, ":hVvcxtf:z::"); + g.setOpterr(false); + int c; + while ((c = g.getopt()) != -1) { + switch (c) { + case 'h': usage(0); return; + case 'V': version(); return; + case 'v': verbose++; break; + case 'c': create = true; break; + case 'x': extract = true; break; + case 't': list = true; break; + case 'f': file = g.getOptarg(); break; + case 'z': level = g.getOptarg(); break; + case ':': usage(2); return; // missing argument + default: usage(1); return; // '?': unknown option + } + } + int actions = (create ? 1 : 0) + (extract ? 1 : 0) + (list ? 1 : 0); + if (actions != 1) { + usage(3); + return; + } + if (file == null) { + usage(4); + return; + } + if (create) { + doCreate(file, level); + } else if (list) { + doList(file); + } else { + doExtract(file, g.getOptind(), argv.length); + } + } + + static int code; + + static void usage(int why) { + if (why == 0) code = 0; + else if (why == 2) code = 64; + else code = 65 + why; + } + + static void version() { + code = verbose > 0 ? 1 : 0; + } + + static void doCreate(String archive, String lvl) { + int l = 6; + if (lvl != null && lvl.length() == 1) { + char d = lvl.charAt(0); + if (d >= '0' && d <= '9') l = d - '0'; + } + if (archive.length() > 3) l++; + code = l; + } + + static void doList(String archive) { + if (archive.startsWith("-")) code = 1; + else code = archive.length(); + } + + static void doExtract(String archive, int optind, int argc) { + if (verbose > 0 && archive.equals("x")) { + if (optind == argc) assert false; + } + code = optind < argc ? 1 : 0; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/real-tools/getopt-minitar_true.yml b/targets/sv-comp/swat-benchmarks/java/real-tools/getopt-minitar_true.yml new file mode 100644 index 00000000..ab330216 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/real-tools/getopt-minitar_true.yml @@ -0,0 +1,10 @@ +format_version: '2.0' +input_files: + - ../common/ + - lib-getopt/ + - getopt-minitar_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/real-tools/getopt-minitar_true/Main.java b/targets/sv-comp/swat-benchmarks/java/real-tools/getopt-minitar_true/Main.java new file mode 100644 index 00000000..708c79a7 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/real-tools/getopt-minitar_true/Main.java @@ -0,0 +1,88 @@ +// Expected verdict: SAFE. doExtract() is only called when create is false (exactly one action, and it is not create or list). +// Paired VIOLATION inputs (must not fail here): string:-xv string:-f string:x +import gnu.getopt.Getopt; +import org.sosy_lab.sv_benchmarks.Verifier; + +// A small tar-like CLI on GNU getopt (java-getopt 1.0.13, unmodified). Three nondet argv +// strings. Help, version, usage errors and the create action leave without reaching the assert; +// only extracting with a verbose flag and the archive name "x" does. +public class Main { + static int verbose; + static boolean create, extract, list; + static String file; + static String level; + + public static void main(String[] args) { + String[] argv = new String[3]; + for (int i = 0; i < argv.length; i++) { + argv[i] = Verifier.nondetString(); + } + Getopt g = new Getopt("minitar", argv, ":hVvcxtf:z::"); + g.setOpterr(false); + int c; + while ((c = g.getopt()) != -1) { + switch (c) { + case 'h': usage(0); return; + case 'V': version(); return; + case 'v': verbose++; break; + case 'c': create = true; break; + case 'x': extract = true; break; + case 't': list = true; break; + case 'f': file = g.getOptarg(); break; + case 'z': level = g.getOptarg(); break; + case ':': usage(2); return; // missing argument + default: usage(1); return; // '?': unknown option + } + } + int actions = (create ? 1 : 0) + (extract ? 1 : 0) + (list ? 1 : 0); + if (actions != 1) { + usage(3); + return; + } + if (file == null) { + usage(4); + return; + } + if (create) { + doCreate(file, level); + } else if (list) { + doList(file); + } else { + doExtract(file, g.getOptind(), argv.length); + } + } + + static int code; + + static void usage(int why) { + if (why == 0) code = 0; + else if (why == 2) code = 64; + else code = 65 + why; + } + + static void version() { + code = verbose > 0 ? 1 : 0; + } + + static void doCreate(String archive, String lvl) { + int l = 6; + if (lvl != null && lvl.length() == 1) { + char d = lvl.charAt(0); + if (d >= '0' && d <= '9') l = d - '0'; + } + if (archive.length() > 3) l++; + code = l; + } + + static void doList(String archive) { + if (archive.startsWith("-")) code = 1; + else code = archive.length(); + } + + static void doExtract(String archive, int optind, int argc) { + if (verbose > 0 && archive.equals("x")) { + if (optind == argc && create) assert false; + } + code = optind < argc ? 1 : 0; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/real-tools/opencsv-import_false.yml b/targets/sv-comp/swat-benchmarks/java/real-tools/opencsv-import_false.yml new file mode 100644 index 00000000..55918df2 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/real-tools/opencsv-import_false.yml @@ -0,0 +1,10 @@ +format_version: '2.0' +input_files: + - ../common/ + - lib-opencsv/ + - opencsv-import_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/real-tools/opencsv-import_false/Main.java b/targets/sv-comp/swat-benchmarks/java/real-tools/opencsv-import_false/Main.java new file mode 100644 index 00000000..4200c55a --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/real-tools/opencsv-import_false/Main.java @@ -0,0 +1,33 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): string:,, +import au.com.bytecode.opencsv.CSVParser; +import java.io.IOException; +import org.sosy_lab.sv_benchmarks.Verifier; + +// A CSV import step on opencsv 2.3's CSVParser (unmodified). One nondet input line. Malformed +// lines (unterminated quote) and records without exactly three fields are rejected; the assert +// checks a property of well-formed three-field records. +public class Main { + static int rejected; + + public static void main(String[] args) { + String line = Verifier.nondetString(); + CSVParser parser = new CSVParser(); + String[] fields; + try { + fields = parser.parseLine(line); + } catch (IOException e) { + reject(line); + return; + } + if (fields.length != 3) { + reject(line); + return; + } + if (line.length() < 3) assert false; + } + + static void reject(String line) { + if (line.length() > 10) rejected += 2; + else rejected++; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/real-tools/opencsv-import_true.yml b/targets/sv-comp/swat-benchmarks/java/real-tools/opencsv-import_true.yml new file mode 100644 index 00000000..0ea01ce8 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/real-tools/opencsv-import_true.yml @@ -0,0 +1,10 @@ +format_version: '2.0' +input_files: + - ../common/ + - lib-opencsv/ + - opencsv-import_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/real-tools/opencsv-import_true/Main.java b/targets/sv-comp/swat-benchmarks/java/real-tools/opencsv-import_true/Main.java new file mode 100644 index 00000000..852c33b0 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/real-tools/opencsv-import_true/Main.java @@ -0,0 +1,34 @@ +// Expected verdict: SAFE. three fields need two separators outside quotes, so the line has at least 2 characters. +// Paired VIOLATION inputs (must not fail here): string:,, +import au.com.bytecode.opencsv.CSVParser; +import java.io.IOException; +import org.sosy_lab.sv_benchmarks.Verifier; + +// A CSV import step on opencsv 2.3's CSVParser (unmodified). One nondet input line. Malformed +// lines (unterminated quote) and records without exactly three fields are rejected; the assert +// checks a property of well-formed three-field records. +public class Main { + static int rejected; + + public static void main(String[] args) { + String line = Verifier.nondetString(); + CSVParser parser = new CSVParser(); + String[] fields; + try { + fields = parser.parseLine(line); + } catch (IOException e) { + reject(line); + return; + } + if (fields.length != 3) { + reject(line); + return; + } + if (line.length() < 2) assert false; + } + + static void reject(String line) { + if (line.length() > 10) rejected += 2; + else rejected++; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/alias-field_false.yml b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/alias-field_false.yml new file mode 100644 index 00000000..961ca6bf --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/alias-field_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - alias-field_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/alias-field_false/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/alias-field_false/Main.java new file mode 100644 index 00000000..1b578e2a --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/alias-field_false/Main.java @@ -0,0 +1,23 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:3 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// The region writes a field through an alias of the object the assert's guard reads. +public class Main { + static class Box { + int v; + } + + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + Box p = new Box(); + Box q = p; + for (int i = 0; i < 10; i++) { + if (i == val) { + q.v = 1; + } + } + if (p.v == 1) { + if (b > 1000 && b < 100000) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/alias-field_true.yml b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/alias-field_true.yml new file mode 100644 index 00000000..56e1d004 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/alias-field_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - alias-field_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/alias-field_true/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/alias-field_true/Main.java new file mode 100644 index 00000000..4a3878b1 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/alias-field_true/Main.java @@ -0,0 +1,24 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:3 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// The region writes a field through an alias of the object the assert's guard reads. +public class Main { + static class Box { + int v; + } + + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + Box p = new Box(); + Box q = p; + for (int i = 0; i < 10; i++) { + if (i == val) { + q.v = 1; + } + } + if (p.v == 1) { + if (b > 1000 && b < 1001) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/array-write_false.yml b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/array-write_false.yml new file mode 100644 index 00000000..4f0cea2a --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/array-write_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - array-write_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/array-write_false/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/array-write_false/Main.java new file mode 100644 index 00000000..a5c9e3c9 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/array-write_false/Main.java @@ -0,0 +1,22 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:3 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// The region writes an array element that is summed up later. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int[] marks = new int[10]; + for (int i = 0; i < 10; i++) { + if (i == val) { + marks[i] = 1; + } + } + int sum = 0; + for (int j = 0; j < 10; j++) { + sum += marks[j]; + } + if (sum == 1) { + if (b > 1000 && b < 100000) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/array-write_true.yml b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/array-write_true.yml new file mode 100644 index 00000000..1b4286bd --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/array-write_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - array-write_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/array-write_true/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/array-write_true/Main.java new file mode 100644 index 00000000..f3f2cf4c --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/array-write_true/Main.java @@ -0,0 +1,23 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:3 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// The region writes an array element that is summed up later. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int[] marks = new int[10]; + for (int i = 0; i < 10; i++) { + if (i == val) { + marks[i] = 1; + } + } + int sum = 0; + for (int j = 0; j < 10; j++) { + sum += marks[j]; + } + if (sum == 1) { + if (b > 1000 && b < 1001) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/assume-halt_false.yml b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/assume-halt_false.yml new file mode 100644 index 00000000..7c067de5 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/assume-halt_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - assume-halt_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/assume-halt_false/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/assume-halt_false/Main.java new file mode 100644 index 00000000..3c6fcb9a --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/assume-halt_false/Main.java @@ -0,0 +1,16 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:9 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// An assumption in the region ends the initial run (val = 0) before the assert. +// By design, the all-zero run ends early. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + for (int i = 0; i < 10; i++) { + if (val < i) { + Verifier.assume(false); + } + } + if (b > 1000 && b < 100000) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/assume-halt_true.yml b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/assume-halt_true.yml new file mode 100644 index 00000000..3f2f7001 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/assume-halt_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - assume-halt_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/assume-halt_true/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/assume-halt_true/Main.java new file mode 100644 index 00000000..87321e3c --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/assume-halt_true/Main.java @@ -0,0 +1,17 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:9 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// An assumption in the region ends the initial run (val = 0) before the assert. +// By design, the all-zero run ends early. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + for (int i = 0; i < 10; i++) { + if (val < i) { + Verifier.assume(false); + } + } + if (b > 1000 && b < 1001) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/callback-write_false.yml b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/callback-write_false.yml new file mode 100644 index 00000000..dc62af11 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/callback-write_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - callback-write_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/callback-write_false/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/callback-write_false/Main.java new file mode 100644 index 00000000..6e3ac606 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/callback-write_false/Main.java @@ -0,0 +1,32 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:2 int:1001 +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import org.sosy_lab.sv_benchmarks.Verifier; +// The region calls library code that calls back into a comparator, which writes a field the +// assert's guard reads. +public class Main { + static boolean compared; + + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + List list = new ArrayList(); + list.add(2); + list.add(1); + for (int i = 0; i < 5; i++) { + if (i == val) { + Collections.sort(list, new Comparator() { + public int compare(Integer x, Integer y) { + compared = true; + return x - y; + } + }); + } + } + if (compared) { + if (b > 1000 && b < 100000) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/callback-write_true.yml b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/callback-write_true.yml new file mode 100644 index 00000000..83239fcf --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/callback-write_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - callback-write_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/callback-write_true/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/callback-write_true/Main.java new file mode 100644 index 00000000..6d755fe5 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/callback-write_true/Main.java @@ -0,0 +1,33 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:2 int:1001 +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import org.sosy_lab.sv_benchmarks.Verifier; +// The region calls library code that calls back into a comparator, which writes a field the +// assert's guard reads. +public class Main { + static boolean compared; + + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + List list = new ArrayList(); + list.add(2); + list.add(1); + for (int i = 0; i < 5; i++) { + if (i == val) { + Collections.sort(list, new Comparator() { + public int compare(Integer x, Integer y) { + compared = true; + return x - y; + } + }); + } + } + if (compared) { + if (b > 1000 && b < 1001) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/callee-write_false.yml b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/callee-write_false.yml new file mode 100644 index 00000000..9ab52a95 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/callee-write_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - callee-write_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/callee-write_false/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/callee-write_false/Main.java new file mode 100644 index 00000000..e110a973 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/callee-write_false/Main.java @@ -0,0 +1,23 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:3 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// The region calls a method that writes a field the assert's guard reads. +public class Main { + static boolean marked; + + static void mark() { + marked = true; + } + + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + for (int i = 0; i < 10; i++) { + if (i == val) { + mark(); + } + } + if (marked) { + if (b > 1000 && b < 100000) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/callee-write_true.yml b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/callee-write_true.yml new file mode 100644 index 00000000..3f9775e8 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/callee-write_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - callee-write_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/callee-write_true/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/callee-write_true/Main.java new file mode 100644 index 00000000..0513c70c --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/callee-write_true/Main.java @@ -0,0 +1,24 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:3 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// The region calls a method that writes a field the assert's guard reads. +public class Main { + static boolean marked; + + static void mark() { + marked = true; + } + + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + for (int i = 0; i < 10; i++) { + if (i == val) { + mark(); + } + } + if (marked) { + if (b > 1000 && b < 1001) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/clinit-in-region_false.yml b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/clinit-in-region_false.yml new file mode 100644 index 00000000..d5969504 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/clinit-in-region_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - clinit-in-region_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/clinit-in-region_false/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/clinit-in-region_false/Main.java new file mode 100644 index 00000000..9028326a --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/clinit-in-region_false/Main.java @@ -0,0 +1,29 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:2 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// The region is where a class is first used; its static initializer writes a field the assert's +// guard reads. +public class Main { + static boolean initialized; + + static class Config { + static int level; + + static { + level = 3; + Main.initialized = true; + } + } + + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + for (int i = 0; i < 5; i++) { + if (i == val) { + System.out.println(Config.level); + } + } + if (initialized) { + if (b > 1000 && b < 100000) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/clinit-in-region_true.yml b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/clinit-in-region_true.yml new file mode 100644 index 00000000..4402a6a5 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/clinit-in-region_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - clinit-in-region_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/clinit-in-region_true/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/clinit-in-region_true/Main.java new file mode 100644 index 00000000..dd6d690f --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/clinit-in-region_true/Main.java @@ -0,0 +1,30 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:2 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// The region is where a class is first used; its static initializer writes a field the assert's +// guard reads. +public class Main { + static boolean initialized; + + static class Config { + static int level; + + static { + level = 3; + Main.initialized = true; + } + } + + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + for (int i = 0; i < 5; i++) { + if (i == val) { + System.out.println(Config.level); + } + } + if (initialized) { + if (b > 1000 && b < 1001) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/crash-in-callee_false.yml b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/crash-in-callee_false.yml new file mode 100644 index 00000000..d4e9d47c --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/crash-in-callee_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - crash-in-callee_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/crash-in-callee_false/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/crash-in-callee_false/Main.java new file mode 100644 index 00000000..a24a86a1 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/crash-in-callee_false/Main.java @@ -0,0 +1,22 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:4 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// The initial run (val = 0) throws an uncaught exception in a callee inside the region. +// By design, the all-zero run ends early. +public class Main { + static void check(int i, int v) { + if (i > v + 5) { + throw new RuntimeException("too far"); + } + } + + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + for (int i = 0; i < 10; i++) { + if (i != val) { + check(i, val); + } + } + if (b > 1000 && b < 100000) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/crash-in-callee_true.yml b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/crash-in-callee_true.yml new file mode 100644 index 00000000..a95562b8 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/crash-in-callee_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - crash-in-callee_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/crash-in-callee_true/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/crash-in-callee_true/Main.java new file mode 100644 index 00000000..42090143 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/crash-in-callee_true/Main.java @@ -0,0 +1,23 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:4 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// The initial run (val = 0) throws an uncaught exception in a callee inside the region. +// By design, the all-zero run ends early. +public class Main { + static void check(int i, int v) { + if (i > v + 5) { + throw new RuntimeException("too far"); + } + } + + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + for (int i = 0; i < 10; i++) { + if (i != val) { + check(i, val); + } + } + if (b > 1000 && b < 1001) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/crash-on-initial-path_false.yml b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/crash-on-initial-path_false.yml new file mode 100644 index 00000000..1615f7e1 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/crash-on-initial-path_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - crash-on-initial-path_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/crash-on-initial-path_false/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/crash-on-initial-path_false/Main.java new file mode 100644 index 00000000..14292385 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/crash-on-initial-path_false/Main.java @@ -0,0 +1,17 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:10 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// The initial run (val = 0) ends with an uncaught exception inside the region, before the assert. +// By design, the all-zero run ends early. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int[] a = new int[5]; + for (int i = 0; i < 10; i++) { + if (i >= val) { + a[i] = 1; + } + } + if (b > 1000 && b < 100000) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/crash-on-initial-path_true.yml b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/crash-on-initial-path_true.yml new file mode 100644 index 00000000..46e670f8 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/crash-on-initial-path_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - crash-on-initial-path_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/crash-on-initial-path_true/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/crash-on-initial-path_true/Main.java new file mode 100644 index 00000000..e1d3cc41 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/crash-on-initial-path_true/Main.java @@ -0,0 +1,18 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:10 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// The initial run (val = 0) ends with an uncaught exception inside the region, before the assert. +// By design, the all-zero run ends early. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int[] a = new int[5]; + for (int i = 0; i < 10; i++) { + if (i >= val) { + a[i] = 1; + } + } + if (b > 1000 && b < 1001) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/exception-skip_false.yml b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/exception-skip_false.yml new file mode 100644 index 00000000..399fec47 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/exception-skip_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - exception-skip_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/exception-skip_false/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/exception-skip_false/Main.java new file mode 100644 index 00000000..147ea28e --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/exception-skip_false/Main.java @@ -0,0 +1,28 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:5 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// The region throws to a handler in the caller, which skips a write the assert's guard reads. +public class Main { + static int done; + + static void work(int val) { + for (int i = 0; i < 10; i++) { + if (i == val - 5) { + throw new IllegalStateException("stop"); + } + } + done = 1; + } + + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + try { + work(val); + } catch (IllegalStateException e) { + System.out.println("caught"); + } + if (done == 0) { + if (b > 1000 && b < 100000) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/exception-skip_true.yml b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/exception-skip_true.yml new file mode 100644 index 00000000..dd45234c --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/exception-skip_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - exception-skip_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/exception-skip_true/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/exception-skip_true/Main.java new file mode 100644 index 00000000..1ec4137b --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/exception-skip_true/Main.java @@ -0,0 +1,29 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:5 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// The region throws to a handler in the caller, which skips a write the assert's guard reads. +public class Main { + static int done; + + static void work(int val) { + for (int i = 0; i < 10; i++) { + if (i == val - 5) { + throw new IllegalStateException("stop"); + } + } + done = 1; + } + + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + try { + work(val); + } catch (IllegalStateException e) { + System.out.println("caught"); + } + if (done == 0) { + if (b > 1000 && b < 1001) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/exit-in-region_false.yml b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/exit-in-region_false.yml new file mode 100644 index 00000000..2aeddd10 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/exit-in-region_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - exit-in-region_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/exit-in-region_false/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/exit-in-region_false/Main.java new file mode 100644 index 00000000..8a6d74f2 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/exit-in-region_false/Main.java @@ -0,0 +1,17 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:9 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// System.exit in the region ends the initial run (val = 0) before the assert. +// By design, the all-zero run ends early. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + for (int i = 0; i < 10; i++) { + if (val < i) { + System.out.println("bye"); + System.exit(0); + } + } + if (b > 1000 && b < 100000) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/exit-in-region_true.yml b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/exit-in-region_true.yml new file mode 100644 index 00000000..85f06ffa --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/exit-in-region_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - exit-in-region_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/exit-in-region_true/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/exit-in-region_true/Main.java new file mode 100644 index 00000000..293e6a80 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/exit-in-region_true/Main.java @@ -0,0 +1,18 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:9 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// System.exit in the region ends the initial run (val = 0) before the assert. +// By design, the all-zero run ends early. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + for (int i = 0; i < 10; i++) { + if (val < i) { + System.out.println("bye"); + System.exit(0); + } + } + if (b > 1000 && b < 1001) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/field-write_false.yml b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/field-write_false.yml new file mode 100644 index 00000000..420da0de --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/field-write_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - field-write_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/field-write_false/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/field-write_false/Main.java new file mode 100644 index 00000000..9b2474a6 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/field-write_false/Main.java @@ -0,0 +1,19 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:3 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// The region writes a static field the assert's guard reads. +public class Main { + static int flag; + + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + for (int i = 0; i < 10; i++) { + if (i == val) { + flag = 1; + } + } + if (flag == 1) { + if (b > 1000 && b < 100000) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/field-write_true.yml b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/field-write_true.yml new file mode 100644 index 00000000..671b71de --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/field-write_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - field-write_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/field-write_true/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/field-write_true/Main.java new file mode 100644 index 00000000..e8b5068d --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/field-write_true/Main.java @@ -0,0 +1,20 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:3 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// The region writes a static field the assert's guard reads. +public class Main { + static int flag; + + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + for (int i = 0; i < 10; i++) { + if (i == val) { + flag = 1; + } + } + if (flag == 1) { + if (b > 1000 && b < 1001) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/input-shift_false.yml b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/input-shift_false.yml new file mode 100644 index 00000000..1db62079 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/input-shift_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - input-shift_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/input-shift_false/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/input-shift_false/Main.java new file mode 100644 index 00000000..53b415df --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/input-shift_false/Main.java @@ -0,0 +1,18 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:2 int:0 int:0 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// The region reads inputs, which shifts which input every later read gets. +public class Main { + public static void main(String[] args) { + int x = Verifier.nondetInt(); + for (int i = 0; i < 3; i++) { + if (i < x) { + int junk = Verifier.nondetInt(); + System.out.println(junk); + } + } + int z = Verifier.nondetInt(); + if (x == 2) { + if (z > 1000 && z < 100000) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/input-shift_true.yml b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/input-shift_true.yml new file mode 100644 index 00000000..48abf369 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/input-shift_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - input-shift_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/input-shift_true/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/input-shift_true/Main.java new file mode 100644 index 00000000..46d1b838 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/input-shift_true/Main.java @@ -0,0 +1,19 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:2 int:0 int:0 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// The region reads inputs, which shifts which input every later read gets. +public class Main { + public static void main(String[] args) { + int x = Verifier.nondetInt(); + for (int i = 0; i < 3; i++) { + if (i < x) { + int junk = Verifier.nondetInt(); + System.out.println(junk); + } + } + int z = Verifier.nondetInt(); + if (x == 2) { + if (z > 1000 && z < 1001) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/library-state_false.yml b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/library-state_false.yml new file mode 100644 index 00000000..3551c0bf --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/library-state_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - library-state_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/library-state_false/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/library-state_false/Main.java new file mode 100644 index 00000000..6cd9a7be --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/library-state_false/Main.java @@ -0,0 +1,20 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:3 int:1001 +import java.util.ArrayList; +import java.util.List; +import org.sosy_lab.sv_benchmarks.Verifier; +// The region adds to a library list whose size the assert's guard reads. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + List list = new ArrayList(); + for (int i = 0; i < 10; i++) { + if (i == val) { + list.add(i); + } + } + if (list.size() == 1) { + if (b > 1000 && b < 100000) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/library-state_true.yml b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/library-state_true.yml new file mode 100644 index 00000000..b22be0f4 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/library-state_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - library-state_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/library-state_true/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/library-state_true/Main.java new file mode 100644 index 00000000..2a0c8bfa --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/library-state_true/Main.java @@ -0,0 +1,21 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:3 int:1001 +import java.util.ArrayList; +import java.util.List; +import org.sosy_lab.sv_benchmarks.Verifier; +// The region adds to a library list whose size the assert's guard reads. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + List list = new ArrayList(); + for (int i = 0; i < 10; i++) { + if (i == val) { + list.add(i); + } + } + if (list.size() == 1) { + if (b > 1000 && b < 1001) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/local-handler_false.yml b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/local-handler_false.yml new file mode 100644 index 00000000..e3b4165a --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/local-handler_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - local-handler_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/local-handler_false/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/local-handler_false/Main.java new file mode 100644 index 00000000..108013d8 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/local-handler_false/Main.java @@ -0,0 +1,22 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:3 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// The region catches an exception of a library call locally and records it. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int state = 0; + for (int i = 0; i < 10; i++) { + if (i == val) { + try { + Integer.parseInt("x" + i); + } catch (NumberFormatException e) { + state = 1; + } + } + } + if (state == 1) { + if (b > 1000 && b < 100000) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/local-handler_true.yml b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/local-handler_true.yml new file mode 100644 index 00000000..053753f5 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/local-handler_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - local-handler_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/local-handler_true/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/local-handler_true/Main.java new file mode 100644 index 00000000..9bc4a5b5 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/local-handler_true/Main.java @@ -0,0 +1,23 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:3 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// The region catches an exception of a library call locally and records it. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int state = 0; + for (int i = 0; i < 10; i++) { + if (i == val) { + try { + Integer.parseInt("x" + i); + } catch (NumberFormatException e) { + state = 1; + } + } + } + if (state == 1) { + if (b > 1000 && b < 1001) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/loop-break_false.yml b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/loop-break_false.yml new file mode 100644 index 00000000..42494124 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/loop-break_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - loop-break_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/loop-break_false/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/loop-break_false/Main.java new file mode 100644 index 00000000..abd2dd75 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/loop-break_false/Main.java @@ -0,0 +1,18 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:6 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// The branch leaves the loop early, which decides the counter read after it. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int i; + for (i = 0; i < 10; i++) { + if (i == val) { + break; + } + } + if (i == 6) { + if (b > 1000 && b < 100000) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/loop-break_true.yml b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/loop-break_true.yml new file mode 100644 index 00000000..ae37e89a --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/loop-break_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - loop-break_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/loop-break_true/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/loop-break_true/Main.java new file mode 100644 index 00000000..fe75c482 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/loop-break_true/Main.java @@ -0,0 +1,19 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:6 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// The branch leaves the loop early, which decides the counter read after it. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int i; + for (i = 0; i < 10; i++) { + if (i == val) { + break; + } + } + if (i == 6) { + if (b > 1000 && b < 1001) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/print-callback_false.yml b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/print-callback_false.yml new file mode 100644 index 00000000..6a3100fd --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/print-callback_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - print-callback_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/print-callback_false/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/print-callback_false/Main.java new file mode 100644 index 00000000..41edca9f --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/print-callback_false/Main.java @@ -0,0 +1,27 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:2 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// The region prints an object whose toString() (called back by the library) counts. +public class Main { + static int printed; + + static class Item { + public String toString() { + printed++; + return "item"; + } + } + + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + Item it = new Item(); + for (int i = 0; i < 5; i++) { + if (i == val) { + System.out.println(it); + } + } + if (printed == 1) { + if (b > 1000 && b < 100000) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/print-callback_true.yml b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/print-callback_true.yml new file mode 100644 index 00000000..7b117de9 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/print-callback_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - print-callback_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/print-callback_true/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/print-callback_true/Main.java new file mode 100644 index 00000000..c841bab4 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/print-callback_true/Main.java @@ -0,0 +1,28 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:2 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// The region prints an object whose toString() (called back by the library) counts. +public class Main { + static int printed; + + static class Item { + public String toString() { + printed++; + return "item"; + } + } + + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + Item it = new Item(); + for (int i = 0; i < 5; i++) { + if (i == val) { + System.out.println(it); + } + } + if (printed == 1) { + if (b > 1000 && b < 1001) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/relevant-counter_false.yml b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/relevant-counter_false.yml new file mode 100644 index 00000000..4c5e3327 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/relevant-counter_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - relevant-counter_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/relevant-counter_false/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/relevant-counter_false/Main.java new file mode 100644 index 00000000..dfaf0118 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/relevant-counter_false/Main.java @@ -0,0 +1,18 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:7 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// The loop's branch counts, and the count decides whether the assert is reached: relevant. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int count = 0; + for (int i = 0; i < 10; i++) { + if (i < val) { + count++; + } + } + if (count == 7) { + if (b > 1000 && b < 100000) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/relevant-counter_true.yml b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/relevant-counter_true.yml new file mode 100644 index 00000000..72492d60 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/relevant-counter_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - relevant-counter_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/relevant-counter_true/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/relevant-counter_true/Main.java new file mode 100644 index 00000000..60af1078 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/relevant-counter_true/Main.java @@ -0,0 +1,19 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:7 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// The loop's branch counts, and the count decides whether the assert is reached: relevant. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int count = 0; + for (int i = 0; i < 10; i++) { + if (i < val) { + count++; + } + } + if (count == 7) { + if (b > 1000 && b < 1001) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/return-value_false.yml b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/return-value_false.yml new file mode 100644 index 00000000..d2ce8cd7 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/return-value_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - return-value_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/return-value_false/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/return-value_false/Main.java new file mode 100644 index 00000000..e52030ea --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/return-value_false/Main.java @@ -0,0 +1,22 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:4 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// The region decides a callee's return value. +public class Main { + static int scan(int val) { + int found = -1; + for (int i = 0; i < 10; i++) { + if (i == val) { + found = i; + } + } + return found; + } + + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + if (scan(val) == 4) { + if (b > 1000 && b < 100000) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/return-value_true.yml b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/return-value_true.yml new file mode 100644 index 00000000..a4a848a7 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/return-value_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - return-value_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/return-value_true/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/return-value_true/Main.java new file mode 100644 index 00000000..13349638 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/return-value_true/Main.java @@ -0,0 +1,23 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:4 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// The region decides a callee's return value. +public class Main { + static int scan(int val) { + int found = -1; + for (int i = 0; i < 10; i++) { + if (i == val) { + found = i; + } + } + return found; + } + + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + if (scan(val) == 4) { + if (b > 1000 && b < 1001) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/shared-helper_false.yml b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/shared-helper_false.yml new file mode 100644 index 00000000..a87464ae --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/shared-helper_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - shared-helper_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/shared-helper_false/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/shared-helper_false/Main.java new file mode 100644 index 00000000..ddac8e3c --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/shared-helper_false/Main.java @@ -0,0 +1,25 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:101 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// A helper with a branch is called both in an irrelevant region and to compute what the assert's +// guard reads. +public class Main { + static int clamp(int x) { + if (x > 100) { + return 100; + } + return x; + } + + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + for (int i = 0; i < 10; i++) { + if (i < val) { + System.out.println(clamp(i)); + } + } + if (clamp(val) == 100) { + if (b > 1000 && b < 100000) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/shared-helper_true.yml b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/shared-helper_true.yml new file mode 100644 index 00000000..b98131db --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/shared-helper_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - shared-helper_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/shared-helper_true/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/shared-helper_true/Main.java new file mode 100644 index 00000000..b7ccc570 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/shared-helper_true/Main.java @@ -0,0 +1,26 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:101 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// A helper with a branch is called both in an irrelevant region and to compute what the assert's +// guard reads. +public class Main { + static int clamp(int x) { + if (x > 100) { + return 100; + } + return x; + } + + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + for (int i = 0; i < 10; i++) { + if (i < val) { + System.out.println(clamp(i)); + } + } + if (clamp(val) == 100) { + if (b > 1000 && b < 1001) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/string-builder_false.yml b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/string-builder_false.yml new file mode 100644 index 00000000..6a35ee1e --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/string-builder_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - string-builder_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/string-builder_false/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/string-builder_false/Main.java new file mode 100644 index 00000000..aec204f1 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/string-builder_false/Main.java @@ -0,0 +1,18 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:3 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// The region appends to a StringBuilder whose length the assert's guard reads. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < 10; i++) { + if (i == val) { + sb.append('x'); + } + } + if (sb.length() == 1) { + if (b > 1000 && b < 100000) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/string-builder_true.yml b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/string-builder_true.yml new file mode 100644 index 00000000..a2ee8fd4 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/string-builder_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - string-builder_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/string-builder_true/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/string-builder_true/Main.java new file mode 100644 index 00000000..952fa125 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/string-builder_true/Main.java @@ -0,0 +1,19 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:3 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// The region appends to a StringBuilder whose length the assert's guard reads. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < 10; i++) { + if (i == val) { + sb.append('x'); + } + } + if (sb.length() == 1) { + if (b > 1000 && b < 1001) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/switch-in-region_false.yml b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/switch-in-region_false.yml new file mode 100644 index 00000000..591a5781 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/switch-in-region_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - switch-in-region_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/switch-in-region_false/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/switch-in-region_false/Main.java new file mode 100644 index 00000000..a8b11ebb --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/switch-in-region_false/Main.java @@ -0,0 +1,25 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:2 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// A switch in the region sets the mode the assert's guard reads. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int mode = 0; + for (int i = 0; i < 4; i++) { + switch (i - val) { + case 0: + mode = 2; + break; + case 1: + System.out.println("one"); + break; + default: + break; + } + } + if (mode == 2) { + if (b > 1000 && b < 100000) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/switch-in-region_true.yml b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/switch-in-region_true.yml new file mode 100644 index 00000000..c33e55de --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/switch-in-region_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - switch-in-region_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance-soundness/switch-in-region_true/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/switch-in-region_true/Main.java new file mode 100644 index 00000000..4cf66a27 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance-soundness/switch-in-region_true/Main.java @@ -0,0 +1,26 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:2 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// A switch in the region sets the mode the assert's guard reads. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int mode = 0; + for (int i = 0; i < 4; i++) { + switch (i - val) { + case 0: + mode = 2; + break; + case 1: + System.out.println("one"); + break; + default: + break; + } + } + if (mode == 2) { + if (b > 1000 && b < 1001) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance/example-long_false.yml b/targets/sv-comp/swat-benchmarks/java/relevance/example-long_false.yml new file mode 100644 index 00000000..75175d59 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance/example-long_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - example-long_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance/example-long_false/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance/example-long_false/Main.java new file mode 100644 index 00000000..5e9267cf --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance/example-long_false/Main.java @@ -0,0 +1,15 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:3141592 +import org.sosy_lab.sv_benchmarks.Verifier; +// relevance/example: the loop's branches all lead to the assert either way, so reachability +// pruning keeps them; none of them can change val, so slicing does not flip them. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + for (int i = 0; i < 10000; i++) { + if (i < val) { + System.out.println(i); + } + } + assert val != 3141592; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance/example_false.yml b/targets/sv-comp/swat-benchmarks/java/relevance/example_false.yml new file mode 100644 index 00000000..1912e689 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance/example_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - example_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance/example_false/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance/example_false/Main.java new file mode 100644 index 00000000..6cfcfddc --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance/example_false/Main.java @@ -0,0 +1,15 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:3141592 +import org.sosy_lab.sv_benchmarks.Verifier; +// relevance/example: the loop's branches all lead to the assert either way, so reachability +// pruning keeps them; none of them can change val, so slicing does not flip them. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + for (int i = 0; i < 100; i++) { + if (i < val) { + System.out.println(i); + } + } + assert val != 3141592; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance/loop-helper_n0010_false.yml b/targets/sv-comp/swat-benchmarks/java/relevance/loop-helper_n0010_false.yml new file mode 100644 index 00000000..22a3cecd --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance/loop-helper_n0010_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - loop-helper_n0010_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance/loop-helper_n0010_false/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance/loop-helper_n0010_false/Main.java new file mode 100644 index 00000000..43a59374 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance/loop-helper_n0010_false/Main.java @@ -0,0 +1,25 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// relevance/loop-helper: each iteration calls a helper with input-dependent branches that only +// update a counter the assert does not read. +public class Main { + static int logged; + + static void log(int i, int v) { + if (i % 2 == 0) { + if (i < v) { + logged++; + } + } else if (i > v) { + logged += 2; + } + } + + public static void main(String[] args) { + int val = Verifier.nondetInt(); + for (int i = 0; i < 10; i++) { + log(i, val); + } + if (val > 1000 && val < 100000) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance/loop-helper_n0010_true.yml b/targets/sv-comp/swat-benchmarks/java/relevance/loop-helper_n0010_true.yml new file mode 100644 index 00000000..cec9a08c --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance/loop-helper_n0010_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - loop-helper_n0010_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance/loop-helper_n0010_true/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance/loop-helper_n0010_true/Main.java new file mode 100644 index 00000000..11acd3e8 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance/loop-helper_n0010_true/Main.java @@ -0,0 +1,26 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// relevance/loop-helper: each iteration calls a helper with input-dependent branches that only +// update a counter the assert does not read. +public class Main { + static int logged; + + static void log(int i, int v) { + if (i % 2 == 0) { + if (i < v) { + logged++; + } + } else if (i > v) { + logged += 2; + } + } + + public static void main(String[] args) { + int val = Verifier.nondetInt(); + for (int i = 0; i < 10; i++) { + log(i, val); + } + if (val > 1000 && val < 1001) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance/loop-helper_n0100_false.yml b/targets/sv-comp/swat-benchmarks/java/relevance/loop-helper_n0100_false.yml new file mode 100644 index 00000000..6ef347f0 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance/loop-helper_n0100_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - loop-helper_n0100_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance/loop-helper_n0100_false/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance/loop-helper_n0100_false/Main.java new file mode 100644 index 00000000..e4d19f75 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance/loop-helper_n0100_false/Main.java @@ -0,0 +1,25 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// relevance/loop-helper: each iteration calls a helper with input-dependent branches that only +// update a counter the assert does not read. +public class Main { + static int logged; + + static void log(int i, int v) { + if (i % 2 == 0) { + if (i < v) { + logged++; + } + } else if (i > v) { + logged += 2; + } + } + + public static void main(String[] args) { + int val = Verifier.nondetInt(); + for (int i = 0; i < 100; i++) { + log(i, val); + } + if (val > 1000 && val < 100000) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance/loop-helper_n0100_true.yml b/targets/sv-comp/swat-benchmarks/java/relevance/loop-helper_n0100_true.yml new file mode 100644 index 00000000..291c74d6 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance/loop-helper_n0100_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - loop-helper_n0100_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance/loop-helper_n0100_true/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance/loop-helper_n0100_true/Main.java new file mode 100644 index 00000000..6b38b168 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance/loop-helper_n0100_true/Main.java @@ -0,0 +1,26 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// relevance/loop-helper: each iteration calls a helper with input-dependent branches that only +// update a counter the assert does not read. +public class Main { + static int logged; + + static void log(int i, int v) { + if (i % 2 == 0) { + if (i < v) { + logged++; + } + } else if (i > v) { + logged += 2; + } + } + + public static void main(String[] args) { + int val = Verifier.nondetInt(); + for (int i = 0; i < 100; i++) { + log(i, val); + } + if (val > 1000 && val < 1001) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance/loop-helper_n1000_false.yml b/targets/sv-comp/swat-benchmarks/java/relevance/loop-helper_n1000_false.yml new file mode 100644 index 00000000..aa042707 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance/loop-helper_n1000_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - loop-helper_n1000_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance/loop-helper_n1000_false/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance/loop-helper_n1000_false/Main.java new file mode 100644 index 00000000..dc111d41 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance/loop-helper_n1000_false/Main.java @@ -0,0 +1,25 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// relevance/loop-helper: each iteration calls a helper with input-dependent branches that only +// update a counter the assert does not read. +public class Main { + static int logged; + + static void log(int i, int v) { + if (i % 2 == 0) { + if (i < v) { + logged++; + } + } else if (i > v) { + logged += 2; + } + } + + public static void main(String[] args) { + int val = Verifier.nondetInt(); + for (int i = 0; i < 1000; i++) { + log(i, val); + } + if (val > 1000 && val < 100000) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance/loop-helper_n1000_true.yml b/targets/sv-comp/swat-benchmarks/java/relevance/loop-helper_n1000_true.yml new file mode 100644 index 00000000..531b516d --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance/loop-helper_n1000_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - loop-helper_n1000_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance/loop-helper_n1000_true/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance/loop-helper_n1000_true/Main.java new file mode 100644 index 00000000..ab1754df --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance/loop-helper_n1000_true/Main.java @@ -0,0 +1,26 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// relevance/loop-helper: each iteration calls a helper with input-dependent branches that only +// update a counter the assert does not read. +public class Main { + static int logged; + + static void log(int i, int v) { + if (i % 2 == 0) { + if (i < v) { + logged++; + } + } else if (i > v) { + logged += 2; + } + } + + public static void main(String[] args) { + int val = Verifier.nondetInt(); + for (int i = 0; i < 1000; i++) { + log(i, val); + } + if (val > 1000 && val < 1001) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance/loop-print_n0010_false.yml b/targets/sv-comp/swat-benchmarks/java/relevance/loop-print_n0010_false.yml new file mode 100644 index 00000000..5fb24d94 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance/loop-print_n0010_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - loop-print_n0010_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance/loop-print_n0010_false/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance/loop-print_n0010_false/Main.java new file mode 100644 index 00000000..deb50a54 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance/loop-print_n0010_false/Main.java @@ -0,0 +1,14 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// relevance/loop-print: an input-dependent branch per iteration that only prints. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + for (int i = 0; i < 10; i++) { + if (i < val) { + System.out.println(i); + } + } + if (val > 1000 && val < 100000) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance/loop-print_n0010_true.yml b/targets/sv-comp/swat-benchmarks/java/relevance/loop-print_n0010_true.yml new file mode 100644 index 00000000..e182d05c --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance/loop-print_n0010_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - loop-print_n0010_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance/loop-print_n0010_true/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance/loop-print_n0010_true/Main.java new file mode 100644 index 00000000..5d1c6be2 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance/loop-print_n0010_true/Main.java @@ -0,0 +1,15 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// relevance/loop-print: an input-dependent branch per iteration that only prints. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + for (int i = 0; i < 10; i++) { + if (i < val) { + System.out.println(i); + } + } + if (val > 1000 && val < 1001) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance/loop-print_n0100_false.yml b/targets/sv-comp/swat-benchmarks/java/relevance/loop-print_n0100_false.yml new file mode 100644 index 00000000..c6899bf3 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance/loop-print_n0100_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - loop-print_n0100_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance/loop-print_n0100_false/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance/loop-print_n0100_false/Main.java new file mode 100644 index 00000000..052d18b6 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance/loop-print_n0100_false/Main.java @@ -0,0 +1,14 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// relevance/loop-print: an input-dependent branch per iteration that only prints. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + for (int i = 0; i < 100; i++) { + if (i < val) { + System.out.println(i); + } + } + if (val > 1000 && val < 100000) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance/loop-print_n0100_true.yml b/targets/sv-comp/swat-benchmarks/java/relevance/loop-print_n0100_true.yml new file mode 100644 index 00000000..37dbc9b6 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance/loop-print_n0100_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - loop-print_n0100_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance/loop-print_n0100_true/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance/loop-print_n0100_true/Main.java new file mode 100644 index 00000000..494f352e --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance/loop-print_n0100_true/Main.java @@ -0,0 +1,15 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// relevance/loop-print: an input-dependent branch per iteration that only prints. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + for (int i = 0; i < 100; i++) { + if (i < val) { + System.out.println(i); + } + } + if (val > 1000 && val < 1001) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance/loop-print_n1000_false.yml b/targets/sv-comp/swat-benchmarks/java/relevance/loop-print_n1000_false.yml new file mode 100644 index 00000000..cc56ab4c --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance/loop-print_n1000_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - loop-print_n1000_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance/loop-print_n1000_false/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance/loop-print_n1000_false/Main.java new file mode 100644 index 00000000..d5a1c9f1 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance/loop-print_n1000_false/Main.java @@ -0,0 +1,14 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// relevance/loop-print: an input-dependent branch per iteration that only prints. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + for (int i = 0; i < 1000; i++) { + if (i < val) { + System.out.println(i); + } + } + if (val > 1000 && val < 100000) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance/loop-print_n1000_true.yml b/targets/sv-comp/swat-benchmarks/java/relevance/loop-print_n1000_true.yml new file mode 100644 index 00000000..ac9ab566 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance/loop-print_n1000_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - loop-print_n1000_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance/loop-print_n1000_true/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance/loop-print_n1000_true/Main.java new file mode 100644 index 00000000..760140d4 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance/loop-print_n1000_true/Main.java @@ -0,0 +1,15 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// relevance/loop-print: an input-dependent branch per iteration that only prints. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + for (int i = 0; i < 1000; i++) { + if (i < val) { + System.out.println(i); + } + } + if (val > 1000 && val < 1001) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance/scratch-array_n0010_false.yml b/targets/sv-comp/swat-benchmarks/java/relevance/scratch-array_n0010_false.yml new file mode 100644 index 00000000..0e589bf6 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance/scratch-array_n0010_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - scratch-array_n0010_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance/scratch-array_n0010_false/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance/scratch-array_n0010_false/Main.java new file mode 100644 index 00000000..da12042f --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance/scratch-array_n0010_false/Main.java @@ -0,0 +1,18 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// relevance/scratch-array: the loop fills a scratch array (bounds-checked) the assert never reads. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int[] hist = new int[10]; + for (int i = 0; i < 10; i++) { + if (i < val) { + hist[i] = i; + } else { + hist[i] = -i; + } + } + System.out.println(hist.length); + if (val > 1000 && val < 100000) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance/scratch-array_n0010_true.yml b/targets/sv-comp/swat-benchmarks/java/relevance/scratch-array_n0010_true.yml new file mode 100644 index 00000000..009ccd2c --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance/scratch-array_n0010_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - scratch-array_n0010_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance/scratch-array_n0010_true/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance/scratch-array_n0010_true/Main.java new file mode 100644 index 00000000..977fa03c --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance/scratch-array_n0010_true/Main.java @@ -0,0 +1,19 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// relevance/scratch-array: the loop fills a scratch array (bounds-checked) the assert never reads. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int[] hist = new int[10]; + for (int i = 0; i < 10; i++) { + if (i < val) { + hist[i] = i; + } else { + hist[i] = -i; + } + } + System.out.println(hist.length); + if (val > 1000 && val < 1001) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance/scratch-array_n0100_false.yml b/targets/sv-comp/swat-benchmarks/java/relevance/scratch-array_n0100_false.yml new file mode 100644 index 00000000..c25171a6 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance/scratch-array_n0100_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - scratch-array_n0100_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance/scratch-array_n0100_false/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance/scratch-array_n0100_false/Main.java new file mode 100644 index 00000000..39aaf6f5 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance/scratch-array_n0100_false/Main.java @@ -0,0 +1,18 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// relevance/scratch-array: the loop fills a scratch array (bounds-checked) the assert never reads. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int[] hist = new int[100]; + for (int i = 0; i < 100; i++) { + if (i < val) { + hist[i] = i; + } else { + hist[i] = -i; + } + } + System.out.println(hist.length); + if (val > 1000 && val < 100000) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance/scratch-array_n0100_true.yml b/targets/sv-comp/swat-benchmarks/java/relevance/scratch-array_n0100_true.yml new file mode 100644 index 00000000..4de0cb83 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance/scratch-array_n0100_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - scratch-array_n0100_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance/scratch-array_n0100_true/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance/scratch-array_n0100_true/Main.java new file mode 100644 index 00000000..4f158854 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance/scratch-array_n0100_true/Main.java @@ -0,0 +1,19 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// relevance/scratch-array: the loop fills a scratch array (bounds-checked) the assert never reads. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int[] hist = new int[100]; + for (int i = 0; i < 100; i++) { + if (i < val) { + hist[i] = i; + } else { + hist[i] = -i; + } + } + System.out.println(hist.length); + if (val > 1000 && val < 1001) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance/scratch-array_n1000_false.yml b/targets/sv-comp/swat-benchmarks/java/relevance/scratch-array_n1000_false.yml new file mode 100644 index 00000000..e27d8c2b --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance/scratch-array_n1000_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - scratch-array_n1000_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance/scratch-array_n1000_false/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance/scratch-array_n1000_false/Main.java new file mode 100644 index 00000000..1c8e4f9b --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance/scratch-array_n1000_false/Main.java @@ -0,0 +1,18 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// relevance/scratch-array: the loop fills a scratch array (bounds-checked) the assert never reads. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int[] hist = new int[1000]; + for (int i = 0; i < 1000; i++) { + if (i < val) { + hist[i] = i; + } else { + hist[i] = -i; + } + } + System.out.println(hist.length); + if (val > 1000 && val < 100000) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance/scratch-array_n1000_true.yml b/targets/sv-comp/swat-benchmarks/java/relevance/scratch-array_n1000_true.yml new file mode 100644 index 00000000..8e6623ce --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance/scratch-array_n1000_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - scratch-array_n1000_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance/scratch-array_n1000_true/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance/scratch-array_n1000_true/Main.java new file mode 100644 index 00000000..8dde5533 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance/scratch-array_n1000_true/Main.java @@ -0,0 +1,19 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// relevance/scratch-array: the loop fills a scratch array (bounds-checked) the assert never reads. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int[] hist = new int[1000]; + for (int i = 0; i < 1000; i++) { + if (i < val) { + hist[i] = i; + } else { + hist[i] = -i; + } + } + System.out.println(hist.length); + if (val > 1000 && val < 1001) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance/two-inputs_n0010_false.yml b/targets/sv-comp/swat-benchmarks/java/relevance/two-inputs_n0010_false.yml new file mode 100644 index 00000000..05017f11 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance/two-inputs_n0010_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - two-inputs_n0010_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance/two-inputs_n0010_false/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance/two-inputs_n0010_false/Main.java new file mode 100644 index 00000000..155a1525 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance/two-inputs_n0010_false/Main.java @@ -0,0 +1,20 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:0 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// relevance/two-inputs: the loop branches on one input, the assert on another. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + for (int i = 0; i < 10; i++) { + if (i < a) { + System.out.print('.'); + } else if (i == a + 1) { + System.out.print('#'); + } + } + System.out.println(); + if (b > a) { + if (b > 1000 && b < 100000) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance/two-inputs_n0010_true.yml b/targets/sv-comp/swat-benchmarks/java/relevance/two-inputs_n0010_true.yml new file mode 100644 index 00000000..0b9d21dc --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance/two-inputs_n0010_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - two-inputs_n0010_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance/two-inputs_n0010_true/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance/two-inputs_n0010_true/Main.java new file mode 100644 index 00000000..8eab3a90 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance/two-inputs_n0010_true/Main.java @@ -0,0 +1,21 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:0 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// relevance/two-inputs: the loop branches on one input, the assert on another. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + for (int i = 0; i < 10; i++) { + if (i < a) { + System.out.print('.'); + } else if (i == a + 1) { + System.out.print('#'); + } + } + System.out.println(); + if (b > a) { + if (b > 1000 && b < 1001) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance/two-inputs_n0100_false.yml b/targets/sv-comp/swat-benchmarks/java/relevance/two-inputs_n0100_false.yml new file mode 100644 index 00000000..ab43910d --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance/two-inputs_n0100_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - two-inputs_n0100_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance/two-inputs_n0100_false/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance/two-inputs_n0100_false/Main.java new file mode 100644 index 00000000..41096474 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance/two-inputs_n0100_false/Main.java @@ -0,0 +1,20 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:0 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// relevance/two-inputs: the loop branches on one input, the assert on another. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + for (int i = 0; i < 100; i++) { + if (i < a) { + System.out.print('.'); + } else if (i == a + 1) { + System.out.print('#'); + } + } + System.out.println(); + if (b > a) { + if (b > 1000 && b < 100000) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance/two-inputs_n0100_true.yml b/targets/sv-comp/swat-benchmarks/java/relevance/two-inputs_n0100_true.yml new file mode 100644 index 00000000..37a3d513 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance/two-inputs_n0100_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - two-inputs_n0100_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance/two-inputs_n0100_true/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance/two-inputs_n0100_true/Main.java new file mode 100644 index 00000000..c56fb2f1 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance/two-inputs_n0100_true/Main.java @@ -0,0 +1,21 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:0 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// relevance/two-inputs: the loop branches on one input, the assert on another. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + for (int i = 0; i < 100; i++) { + if (i < a) { + System.out.print('.'); + } else if (i == a + 1) { + System.out.print('#'); + } + } + System.out.println(); + if (b > a) { + if (b > 1000 && b < 1001) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance/two-inputs_n1000_false.yml b/targets/sv-comp/swat-benchmarks/java/relevance/two-inputs_n1000_false.yml new file mode 100644 index 00000000..3af6ff24 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance/two-inputs_n1000_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - two-inputs_n1000_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance/two-inputs_n1000_false/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance/two-inputs_n1000_false/Main.java new file mode 100644 index 00000000..31e2fbce --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance/two-inputs_n1000_false/Main.java @@ -0,0 +1,20 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:0 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// relevance/two-inputs: the loop branches on one input, the assert on another. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + for (int i = 0; i < 1000; i++) { + if (i < a) { + System.out.print('.'); + } else if (i == a + 1) { + System.out.print('#'); + } + } + System.out.println(); + if (b > a) { + if (b > 1000 && b < 100000) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/relevance/two-inputs_n1000_true.yml b/targets/sv-comp/swat-benchmarks/java/relevance/two-inputs_n1000_true.yml new file mode 100644 index 00000000..73b2408d --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance/two-inputs_n1000_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - two-inputs_n1000_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/relevance/two-inputs_n1000_true/Main.java b/targets/sv-comp/swat-benchmarks/java/relevance/two-inputs_n1000_true/Main.java new file mode 100644 index 00000000..bc0bdded --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/relevance/two-inputs_n1000_true/Main.java @@ -0,0 +1,21 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:0 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// relevance/two-inputs: the loop branches on one input, the assert on another. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + for (int i = 0; i < 1000; i++) { + if (i < a) { + System.out.print('.'); + } else if (i == a + 1) { + System.out.print('#'); + } + } + System.out.println(); + if (b > a) { + if (b > 1000 && b < 1001) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/anonymous-class_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/anonymous-class_false.yml new file mode 100644 index 00000000..05f41681 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/anonymous-class_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - anonymous-class_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/anonymous-class_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/anonymous-class_false/Main.java new file mode 100644 index 00000000..c49a38b5 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/anonymous-class_false/Main.java @@ -0,0 +1,19 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// The assert is in an anonymous class's run(), called on an input-dependent path. +public class Main { + static int input; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + input = a; + Runnable r = new Runnable() { + public void run() { + if (input > 1000 && input < 100000) assert false; + } + }; + if (b == 7) r.run(); + if (a > 3) a--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/anonymous-class_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/anonymous-class_true.yml new file mode 100644 index 00000000..de82e3d6 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/anonymous-class_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - anonymous-class_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/anonymous-class_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/anonymous-class_true/Main.java new file mode 100644 index 00000000..db5c0725 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/anonymous-class_true/Main.java @@ -0,0 +1,20 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// The assert is in an anonymous class's run(), called on an input-dependent path. +public class Main { + static int input; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + input = a; + Runnable r = new Runnable() { + public void run() { + if (input > 1000 && input < 1001) assert false; + } + }; + if (b == 7) r.run(); + if (a > 3) a--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/anonymous-static-field_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/anonymous-static-field_false.yml new file mode 100644 index 00000000..e982cad0 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/anonymous-static-field_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - anonymous-static-field_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/anonymous-static-field_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/anonymous-static-field_false/Main.java new file mode 100644 index 00000000..f402f215 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/anonymous-static-field_false/Main.java @@ -0,0 +1,20 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// The assert is in an anonymous class stored in a static field (receiver read from a field). +public class Main { + static int input; + + static final Runnable CHECK = new Runnable() { + public void run() { + if (input > 1000 && input < 100000) assert false; + } + }; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + input = a; + if (b == 7) CHECK.run(); + if (a > 3) a--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/anonymous-static-field_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/anonymous-static-field_true.yml new file mode 100644 index 00000000..00f7defb --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/anonymous-static-field_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - anonymous-static-field_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/anonymous-static-field_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/anonymous-static-field_true/Main.java new file mode 100644 index 00000000..69d55e35 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/anonymous-static-field_true/Main.java @@ -0,0 +1,21 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// The assert is in an anonymous class stored in a static field (receiver read from a field). +public class Main { + static int input; + + static final Runnable CHECK = new Runnable() { + public void run() { + if (input > 1000 && input < 1001) assert false; + } + }; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + input = a; + if (b == 7) CHECK.run(); + if (a > 3) a--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/arith-catch-generic_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/arith-catch-generic_false.yml new file mode 100644 index 00000000..76539a78 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/arith-catch-generic_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - arith-catch-generic_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/arith-catch-generic_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/arith-catch-generic_false/Main.java new file mode 100644 index 00000000..7e4c5113 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/arith-catch-generic_false/Main.java @@ -0,0 +1,22 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// The division's ArithmeticException is caught as RuntimeException one frame up. +public class Main { + static int div(int x, int y) { + return x / y; + } + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int r; + try { + r = div(100, b - 7); + if (r > 3) r--; + } catch (RuntimeException e) { + if (a > 1000 && a < 100000) assert false; + r = 0; + } + if (r > 50) r--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/arith-catch-generic_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/arith-catch-generic_true.yml new file mode 100644 index 00000000..18766a8d --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/arith-catch-generic_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - arith-catch-generic_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/arith-catch-generic_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/arith-catch-generic_true/Main.java new file mode 100644 index 00000000..8cc8dd72 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/arith-catch-generic_true/Main.java @@ -0,0 +1,23 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// The division's ArithmeticException is caught as RuntimeException one frame up. +public class Main { + static int div(int x, int y) { + return x / y; + } + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int r; + try { + r = div(100, b - 7); + if (r > 3) r--; + } catch (RuntimeException e) { + if (a > 1000 && a < 1001) assert false; + r = 0; + } + if (r > 50) r--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/arith-catch-local_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/arith-catch-local_false.yml new file mode 100644 index 00000000..72d61010 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/arith-catch-local_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - arith-catch-local_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/arith-catch-local_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/arith-catch-local_false/Main.java new file mode 100644 index 00000000..ea426048 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/arith-catch-local_false/Main.java @@ -0,0 +1,17 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// The assert is in a catch (ArithmeticException) in the method that divides. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int r; + try { + r = 100 / (b - 7); + } catch (ArithmeticException e) { + if (a > 1000 && a < 100000) assert false; + r = 0; + } + if (r > 50) r--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/arith-catch-local_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/arith-catch-local_true.yml new file mode 100644 index 00000000..57e414dc --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/arith-catch-local_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - arith-catch-local_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/arith-catch-local_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/arith-catch-local_true/Main.java new file mode 100644 index 00000000..dce4d554 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/arith-catch-local_true/Main.java @@ -0,0 +1,18 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// The assert is in a catch (ArithmeticException) in the method that divides. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int r; + try { + r = 100 / (b - 7); + } catch (ArithmeticException e) { + if (a > 1000 && a < 1001) assert false; + r = 0; + } + if (r > 50) r--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/arith-catch-up1_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/arith-catch-up1_false.yml new file mode 100644 index 00000000..15c0e436 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/arith-catch-up1_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - arith-catch-up1_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/arith-catch-up1_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/arith-catch-up1_false/Main.java new file mode 100644 index 00000000..466c78b2 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/arith-catch-up1_false/Main.java @@ -0,0 +1,23 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// The assert is in a catch (ArithmeticException) one frame above the division. +public class Main { + static int div(int x, int y) { + int q = x / y; + if (q > 10) q--; + return q; + } + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int r; + try { + r = div(100, b - 7); + } catch (ArithmeticException e) { + if (a > 1000 && a < 100000) assert false; + r = 0; + } + if (r > 50) r--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/arith-catch-up1_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/arith-catch-up1_true.yml new file mode 100644 index 00000000..0c458180 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/arith-catch-up1_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - arith-catch-up1_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/arith-catch-up1_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/arith-catch-up1_true/Main.java new file mode 100644 index 00000000..680a20b4 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/arith-catch-up1_true/Main.java @@ -0,0 +1,24 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// The assert is in a catch (ArithmeticException) one frame above the division. +public class Main { + static int div(int x, int y) { + int q = x / y; + if (q > 10) q--; + return q; + } + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int r; + try { + r = div(100, b - 7); + } catch (ArithmeticException e) { + if (a > 1000 && a < 1001) assert false; + r = 0; + } + if (r > 50) r--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/arith-catch-up2_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/arith-catch-up2_false.yml new file mode 100644 index 00000000..5d50e65d --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/arith-catch-up2_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - arith-catch-up2_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/arith-catch-up2_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/arith-catch-up2_false/Main.java new file mode 100644 index 00000000..3a3904cb --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/arith-catch-up2_false/Main.java @@ -0,0 +1,29 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// The assert is in a catch (ArithmeticException) two frames above the division. +public class Main { + static int div(int x, int y) { + int q = x / y; + if (q > 10) q--; + return q; + } + + static int mid(int x, int y) { + int r = div(x, y); + if (r < 0) r = -r; + return r; + } + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int r; + try { + r = mid(100, b - 7); + } catch (ArithmeticException e) { + if (a > 1000 && a < 100000) assert false; + r = 0; + } + if (r > 50) r--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/arith-catch-up2_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/arith-catch-up2_true.yml new file mode 100644 index 00000000..df91b099 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/arith-catch-up2_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - arith-catch-up2_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/arith-catch-up2_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/arith-catch-up2_true/Main.java new file mode 100644 index 00000000..2f2a9edf --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/arith-catch-up2_true/Main.java @@ -0,0 +1,30 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// The assert is in a catch (ArithmeticException) two frames above the division. +public class Main { + static int div(int x, int y) { + int q = x / y; + if (q > 10) q--; + return q; + } + + static int mid(int x, int y) { + int r = div(x, y); + if (r < 0) r = -r; + return r; + } + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int r; + try { + r = mid(100, b - 7); + } catch (ArithmeticException e) { + if (a > 1000 && a < 1001) assert false; + r = 0; + } + if (r > 50) r--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/bounds-catch-up1_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/bounds-catch-up1_false.yml new file mode 100644 index 00000000..2e22d56d --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/bounds-catch-up1_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - bounds-catch-up1_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/bounds-catch-up1_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/bounds-catch-up1_false/Main.java new file mode 100644 index 00000000..eb5b6fec --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/bounds-catch-up1_false/Main.java @@ -0,0 +1,25 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// The assert is in a catch (ArrayIndexOutOfBoundsException) one frame above the array read. +public class Main { + static final int[] TABLE = {1, 2, 3}; + + static int get(int i) { + int v = TABLE[i]; + if (v > 1) v--; + return v; + } + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int r; + try { + r = get(b); + } catch (ArrayIndexOutOfBoundsException e) { + if (a > 1000 && a < 100000) assert false; + r = 0; + } + if (r > 1) r--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/bounds-catch-up1_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/bounds-catch-up1_true.yml new file mode 100644 index 00000000..eefa9b7a --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/bounds-catch-up1_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - bounds-catch-up1_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/bounds-catch-up1_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/bounds-catch-up1_true/Main.java new file mode 100644 index 00000000..5bed62c3 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/bounds-catch-up1_true/Main.java @@ -0,0 +1,26 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// The assert is in a catch (ArrayIndexOutOfBoundsException) one frame above the array read. +public class Main { + static final int[] TABLE = {1, 2, 3}; + + static int get(int i) { + int v = TABLE[i]; + if (v > 1) v--; + return v; + } + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int r; + try { + r = get(b); + } catch (ArrayIndexOutOfBoundsException e) { + if (a > 1000 && a < 1001) assert false; + r = 0; + } + if (r > 1) r--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/bounds-catch-up2_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/bounds-catch-up2_false.yml new file mode 100644 index 00000000..47386146 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/bounds-catch-up2_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - bounds-catch-up2_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/bounds-catch-up2_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/bounds-catch-up2_false/Main.java new file mode 100644 index 00000000..7f34d934 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/bounds-catch-up2_false/Main.java @@ -0,0 +1,31 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// The assert is in a catch (ArrayIndexOutOfBoundsException) two frames above the array read. +public class Main { + static final int[] TABLE = {1, 2, 3}; + + static int get(int i) { + int v = TABLE[i]; + if (v > 1) v--; + return v; + } + + static int lookup(int i) { + int v = get(i); + if (v < 0) v = 0; + return v; + } + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int r; + try { + r = lookup(b); + } catch (ArrayIndexOutOfBoundsException e) { + if (a > 1000 && a < 100000) assert false; + r = 0; + } + if (r > 1) r--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/bounds-catch-up2_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/bounds-catch-up2_true.yml new file mode 100644 index 00000000..9ec56be2 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/bounds-catch-up2_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - bounds-catch-up2_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/bounds-catch-up2_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/bounds-catch-up2_true/Main.java new file mode 100644 index 00000000..2de756aa --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/bounds-catch-up2_true/Main.java @@ -0,0 +1,32 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// The assert is in a catch (ArrayIndexOutOfBoundsException) two frames above the array read. +public class Main { + static final int[] TABLE = {1, 2, 3}; + + static int get(int i) { + int v = TABLE[i]; + if (v > 1) v--; + return v; + } + + static int lookup(int i) { + int v = get(i); + if (v < 0) v = 0; + return v; + } + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int r; + try { + r = lookup(b); + } catch (ArrayIndexOutOfBoundsException e) { + if (a > 1000 && a < 1001) assert false; + r = 0; + } + if (r > 1) r--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/cce-catch-up1_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/cce-catch-up1_false.yml new file mode 100644 index 00000000..0b3d33b9 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/cce-catch-up1_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - cce-catch-up1_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/cce-catch-up1_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/cce-catch-up1_false/Main.java new file mode 100644 index 00000000..41fe649f --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/cce-catch-up1_false/Main.java @@ -0,0 +1,32 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// The assert is in a catch (ClassCastException) one frame above the cast. +public class Main { + static class A { + } + + static class B extends A { + int v = 3; + } + + static int unwrap(A o) { + B bb = (B) o; + int r = bb.v; + if (r > 2) r--; + return r; + } + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + A o = b == 7 ? new A() : new B(); + int r; + try { + r = unwrap(o); + } catch (ClassCastException e) { + if (a > 1000 && a < 100000) assert false; + r = 0; + } + if (r > 50) r--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/cce-catch-up1_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/cce-catch-up1_true.yml new file mode 100644 index 00000000..274c789b --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/cce-catch-up1_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - cce-catch-up1_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/cce-catch-up1_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/cce-catch-up1_true/Main.java new file mode 100644 index 00000000..16128329 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/cce-catch-up1_true/Main.java @@ -0,0 +1,33 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// The assert is in a catch (ClassCastException) one frame above the cast. +public class Main { + static class A { + } + + static class B extends A { + int v = 3; + } + + static int unwrap(A o) { + B bb = (B) o; + int r = bb.v; + if (r > 2) r--; + return r; + } + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + A o = b == 7 ? new A() : new B(); + int r; + try { + r = unwrap(o); + } catch (ClassCastException e) { + if (a > 1000 && a < 1001) assert false; + r = 0; + } + if (r > 50) r--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/cce-catch_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/cce-catch_false.yml new file mode 100644 index 00000000..f0c85d82 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/cce-catch_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - cce-catch_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/cce-catch_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/cce-catch_false/Main.java new file mode 100644 index 00000000..c5aa5f6d --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/cce-catch_false/Main.java @@ -0,0 +1,27 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// The assert is in a catch (ClassCastException). +public class Main { + static class A { + } + + static class B extends A { + int v = 3; + } + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + A o = b == 7 ? new A() : new B(); + int r; + try { + B bb = (B) o; + r = bb.v; + if (r > 2) r--; + } catch (ClassCastException e) { + if (a > 1000 && a < 100000) assert false; + r = 0; + } + if (r > 50) r--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/cce-catch_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/cce-catch_true.yml new file mode 100644 index 00000000..d7bd39d0 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/cce-catch_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - cce-catch_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/cce-catch_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/cce-catch_true/Main.java new file mode 100644 index 00000000..e51d6efa --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/cce-catch_true/Main.java @@ -0,0 +1,28 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// The assert is in a catch (ClassCastException). +public class Main { + static class A { + } + + static class B extends A { + int v = 3; + } + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + A o = b == 7 ? new A() : new B(); + int r; + try { + B bb = (B) o; + r = bb.v; + if (r > 2) r--; + } catch (ClassCastException e) { + if (a > 1000 && a < 1001) assert false; + r = 0; + } + if (r > 50) r--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-field_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-field_false.yml new file mode 100644 index 00000000..340f48b4 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-field_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - clinit-field_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-field_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-field_false/Main.java new file mode 100644 index 00000000..72ac0f09 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-field_false/Main.java @@ -0,0 +1,30 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// The assert is in the static initializer of a class first used (static field read) on an +// input-dependent path. +public class Main { + static int input; + + static class Late { + static int K; + + static { + if (Main.input > 1000 && Main.input < 100000) assert false; + K = 1; + } + + static int get() { + return K; + } + } + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + input = a; + int r = 0; + if (b == 7) { + r = Late.K; + } + if (r > 3) r--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-field_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-field_true.yml new file mode 100644 index 00000000..d1ab43a5 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-field_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - clinit-field_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-field_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-field_true/Main.java new file mode 100644 index 00000000..ff4c6ac2 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-field_true/Main.java @@ -0,0 +1,31 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// The assert is in the static initializer of a class first used (static field read) on an +// input-dependent path. +public class Main { + static int input; + + static class Late { + static int K; + + static { + if (Main.input > 1000 && Main.input < 1001) assert false; + K = 1; + } + + static int get() { + return K; + } + } + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + input = a; + int r = 0; + if (b == 7) { + r = Late.K; + } + if (r > 3) r--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-in-callee_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-in-callee_false.yml new file mode 100644 index 00000000..4ff6d01c --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-in-callee_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - clinit-in-callee_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-in-callee_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-in-callee_false/Main.java new file mode 100644 index 00000000..6727e6c2 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-in-callee_false/Main.java @@ -0,0 +1,33 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// The assert is in a static initializer triggered inside a callee that has no branches of its own. +public class Main { + static int input; + + static class Late { + static int K; + + static { + if (Main.input > 1000 && Main.input < 100000) assert false; + K = 1; + } + + static int get() { + return K; + } + } + static int touch() { + return Late.get() + 1; + } + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + input = a; + int r = 0; + if (b == 7) { + r = touch(); + } + if (r > 3) r--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-in-callee_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-in-callee_true.yml new file mode 100644 index 00000000..ec013dcf --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-in-callee_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - clinit-in-callee_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-in-callee_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-in-callee_true/Main.java new file mode 100644 index 00000000..603218f0 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-in-callee_true/Main.java @@ -0,0 +1,34 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// The assert is in a static initializer triggered inside a callee that has no branches of its own. +public class Main { + static int input; + + static class Late { + static int K; + + static { + if (Main.input > 1000 && Main.input < 1001) assert false; + K = 1; + } + + static int get() { + return K; + } + } + static int touch() { + return Late.get() + 1; + } + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + input = a; + int r = 0; + if (b == 7) { + r = touch(); + } + if (r > 3) r--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-new_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-new_false.yml new file mode 100644 index 00000000..36f1a744 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-new_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - clinit-new_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-new_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-new_false/Main.java new file mode 100644 index 00000000..357e8ed8 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-new_false/Main.java @@ -0,0 +1,29 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// The assert is in the static initializer of a class first used (new) on an input-dependent path. +public class Main { + static int input; + + static class Late { + static int K; + + static { + if (Main.input > 1000 && Main.input < 100000) assert false; + K = 1; + } + + static int get() { + return K; + } + } + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + input = a; + Object o = null; + if (b == 7) { + o = new Late(); + } + if (o != null) input++; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-new_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-new_true.yml new file mode 100644 index 00000000..b450bbb7 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-new_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - clinit-new_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-new_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-new_true/Main.java new file mode 100644 index 00000000..14f0b7d7 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-new_true/Main.java @@ -0,0 +1,30 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// The assert is in the static initializer of a class first used (new) on an input-dependent path. +public class Main { + static int input; + + static class Late { + static int K; + + static { + if (Main.input > 1000 && Main.input < 1001) assert false; + K = 1; + } + + static int get() { + return K; + } + } + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + input = a; + Object o = null; + if (b == 7) { + o = new Late(); + } + if (o != null) input++; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-static-call_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-static-call_false.yml new file mode 100644 index 00000000..1dd48c6b --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-static-call_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - clinit-static-call_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-static-call_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-static-call_false/Main.java new file mode 100644 index 00000000..92504b9a --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-static-call_false/Main.java @@ -0,0 +1,30 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// The assert is in the static initializer of a class first used (static call) on an +// input-dependent path. +public class Main { + static int input; + + static class Late { + static int K; + + static { + if (Main.input > 1000 && Main.input < 100000) assert false; + K = 1; + } + + static int get() { + return K; + } + } + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + input = a; + int r = 0; + if (b == 7) { + r = Late.get(); + } + if (r > 3) r--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-static-call_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-static-call_true.yml new file mode 100644 index 00000000..625207b8 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-static-call_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - clinit-static-call_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-static-call_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-static-call_true/Main.java new file mode 100644 index 00000000..c04bda8a --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-static-call_true/Main.java @@ -0,0 +1,31 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// The assert is in the static initializer of a class first used (static call) on an +// input-dependent path. +public class Main { + static int input; + + static class Late { + static int K; + + static { + if (Main.input > 1000 && Main.input < 1001) assert false; + K = 1; + } + + static int get() { + return K; + } + } + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + input = a; + int r = 0; + if (b == 7) { + r = Late.get(); + } + if (r > 3) r--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-unconditional_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-unconditional_false.yml new file mode 100644 index 00000000..4bacdb29 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-unconditional_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - clinit-unconditional_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-unconditional_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-unconditional_false/Main.java new file mode 100644 index 00000000..2e287e09 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-unconditional_false/Main.java @@ -0,0 +1,28 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// Control for the clinit pairs: the class is first used on every path, so its runs in +// the first round, inside the masked part of the walk. Only the input-dependent first use fails. +public class Main { + static int input; + + static class Late { + static int K; + + static { + if (Main.input > 1000 && Main.input < 100000) assert false; + K = 1; + } + + static int get() { + return K; + } + } + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + input = a; + int r = Late.get(); + if (b == 7) r++; + if (r > 3) r--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-unconditional_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-unconditional_true.yml new file mode 100644 index 00000000..83d4f2bb --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-unconditional_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - clinit-unconditional_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-unconditional_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-unconditional_true/Main.java new file mode 100644 index 00000000..2c85cefb --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/clinit-unconditional_true/Main.java @@ -0,0 +1,29 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// Control for the clinit pairs: the class is first used on every path, so its runs in +// the first round, inside the masked part of the walk. Only the input-dependent first use fails. +public class Main { + static int input; + + static class Late { + static int K; + + static { + if (Main.input > 1000 && Main.input < 1001) assert false; + K = 1; + } + + static int get() { + return K; + } + } + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + input = a; + int r = Late.get(); + if (b == 7) r++; + if (r > 3) r--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/default-method_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/default-method_false.yml new file mode 100644 index 00000000..41595b67 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/default-method_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - default-method_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/default-method_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/default-method_false/Main.java new file mode 100644 index 00000000..a6f7c4d8 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/default-method_false/Main.java @@ -0,0 +1,32 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// The assert is in an interface default method, called on a class that does not override it. +public class Main { + interface Checker { + default void check(int a) { + if (a > 1000 && a < 100000) assert false; + } + } + + static class Impl implements Checker { + } + + static class Other implements Checker { + int cnt; + + public void check(int a) { + cnt++; + } + } + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + if (b == 7) { + new Impl().check(a); + } else { + new Other().check(a); + } + if (a > 3) a--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/default-method_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/default-method_true.yml new file mode 100644 index 00000000..b993fd01 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/default-method_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - default-method_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/default-method_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/default-method_true/Main.java new file mode 100644 index 00000000..f8f46009 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/default-method_true/Main.java @@ -0,0 +1,33 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// The assert is in an interface default method, called on a class that does not override it. +public class Main { + interface Checker { + default void check(int a) { + if (a > 1000 && a < 1001) assert false; + } + } + + static class Impl implements Checker { + } + + static class Other implements Checker { + int cnt; + + public void check(int a) { + cnt++; + } + } + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + if (b == 7) { + new Impl().check(a); + } else { + new Other().check(a); + } + if (a > 3) a--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/enum-switch_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/enum-switch_false.yml new file mode 100644 index 00000000..d2161b0b --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/enum-switch_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - enum-switch_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/enum-switch_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/enum-switch_false/Main.java new file mode 100644 index 00000000..25162071 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/enum-switch_false/Main.java @@ -0,0 +1,22 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// A switch on an enum (synthetic $SwitchMap class with its own ) selects the asserting case. +public class Main { + enum Op { START, STOP, STATUS } + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + Op op = b == 1 ? Op.START : b == 7 ? Op.STOP : Op.STATUS; + int r; + switch (op) { + case START: r = 1; break; + case STOP: + if (a > 1000 && a < 100000) assert false; + r = 2; + break; + default: r = 3; + } + if (r > 2) r--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/enum-switch_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/enum-switch_true.yml new file mode 100644 index 00000000..ea6ba482 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/enum-switch_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - enum-switch_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/enum-switch_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/enum-switch_true/Main.java new file mode 100644 index 00000000..b1d83bd9 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/enum-switch_true/Main.java @@ -0,0 +1,23 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// A switch on an enum (synthetic $SwitchMap class with its own ) selects the asserting case. +public class Main { + enum Op { START, STOP, STATUS } + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + Op op = b == 1 ? Op.START : b == 7 ? Op.STOP : Op.STATUS; + int r; + switch (op) { + case START: r = 1; break; + case STOP: + if (a > 1000 && a < 1001) assert false; + r = 2; + break; + default: r = 3; + } + if (r > 2) r--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/error-in-initializer-catch_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/error-in-initializer-catch_false.yml new file mode 100644 index 00000000..9bce0577 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/error-in-initializer-catch_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - error-in-initializer-catch_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/error-in-initializer-catch_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/error-in-initializer-catch_false/Main.java new file mode 100644 index 00000000..04535296 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/error-in-initializer-catch_false/Main.java @@ -0,0 +1,30 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// A static initializer throws on b == 7; the ExceptionInInitializerError (an Error) is caught as +// Error in main, where the assert is. +public class Main { + static int input; + + static class Late { + static int K; + + static { + if (Main.input == 7) throw new IllegalStateException(); + K = 1; + } + } + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + input = b; + int r; + try { + r = Late.K; + } catch (Error e) { + if (a > 1000 && a < 100000) assert false; + r = 0; + } + if (r > 50) r--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/error-in-initializer-catch_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/error-in-initializer-catch_true.yml new file mode 100644 index 00000000..c90566fd --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/error-in-initializer-catch_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - error-in-initializer-catch_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/error-in-initializer-catch_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/error-in-initializer-catch_true/Main.java new file mode 100644 index 00000000..8a58639e --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/error-in-initializer-catch_true/Main.java @@ -0,0 +1,31 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// A static initializer throws on b == 7; the ExceptionInInitializerError (an Error) is caught as +// Error in main, where the assert is. +public class Main { + static int input; + + static class Late { + static int K; + + static { + if (Main.input == 7) throw new IllegalStateException(); + K = 1; + } + } + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + input = b; + int r; + try { + r = Late.K; + } catch (Error e) { + if (a > 1000 && a < 1001) assert false; + r = 0; + } + if (r > 50) r--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-equal-string-inputs_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-equal-string-inputs_false.yml new file mode 100644 index 00000000..756324fa --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-equal-string-inputs_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - exec-equal-string-inputs_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-equal-string-inputs_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-equal-string-inputs_false/Main.java new file mode 100644 index 00000000..20fd7b9d --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-equal-string-inputs_false/Main.java @@ -0,0 +1,14 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): string: string:x int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// Two String inputs that the solver may set to the same value (here both empty at first): each +// must keep its own symbolic variable. +public class Main { + public static void main(String[] args) { + String s = Verifier.nondetString(); + String t = Verifier.nondetString(); + int a = Verifier.nondetInt(); + if (s.length() == 0 && t.length() == 1) { + if (a > 1000 && a < 100000) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-equal-string-inputs_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-equal-string-inputs_true.yml new file mode 100644 index 00000000..18ea6775 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-equal-string-inputs_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - exec-equal-string-inputs_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-equal-string-inputs_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-equal-string-inputs_true/Main.java new file mode 100644 index 00000000..1d781b2c --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-equal-string-inputs_true/Main.java @@ -0,0 +1,15 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): string: string:x int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// Two String inputs that the solver may set to the same value (here both empty at first): each +// must keep its own symbolic variable. +public class Main { + public static void main(String[] args) { + String s = Verifier.nondetString(); + String t = Verifier.nondetString(); + int a = Verifier.nondetInt(); + if (s.length() == 0 && t.length() == 1) { + if (a > 1000 && a < 1001) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-indexof-negative-from_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-indexof-negative-from_false.yml new file mode 100644 index 00000000..18b58e77 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-indexof-negative-from_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - exec-indexof-negative-from_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-indexof-negative-from_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-indexof-negative-from_false/Main.java new file mode 100644 index 00000000..7d30f563 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-indexof-negative-from_false/Main.java @@ -0,0 +1,13 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): string:a int:-6 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// String.indexOf(String, int) treats a negative fromIndex as 0. +public class Main { + public static void main(String[] args) { + String s = Verifier.nondetString(); + int from = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + if (from < -5 && s.indexOf("a", from) == 0) { + if (a > 1000 && a < 100000) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-indexof-negative-from_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-indexof-negative-from_true.yml new file mode 100644 index 00000000..20963725 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-indexof-negative-from_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - exec-indexof-negative-from_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-indexof-negative-from_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-indexof-negative-from_true/Main.java new file mode 100644 index 00000000..b0f4b8a3 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-indexof-negative-from_true/Main.java @@ -0,0 +1,14 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): string:a int:-6 int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// String.indexOf(String, int) treats a negative fromIndex as 0. +public class Main { + public static void main(String[] args) { + String s = Verifier.nondetString(); + int from = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + if (from < -5 && s.indexOf("a", from) == 0) { + if (a > 1000 && a < 1001) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-int-array-index_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-int-array-index_false.yml new file mode 100644 index 00000000..fcbf3b80 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-int-array-index_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - exec-int-array-index_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-int-array-index_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-int-array-index_false/Main.java new file mode 100644 index 00000000..39a8048b --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-int-array-index_false/Main.java @@ -0,0 +1,16 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:2 +import org.sosy_lab.sv_benchmarks.Verifier; +// Control for exec-object-array-index: the same with an int array. +public class Main { + static final int[] TABLE = {0, 0, 7}; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + if (b >= 0 && b < TABLE.length) { + if (TABLE[b] == 7) { + if (a > 1000 && a < 100000) assert false; + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-int-array-index_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-int-array-index_true.yml new file mode 100644 index 00000000..d77c902e --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-int-array-index_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - exec-int-array-index_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-int-array-index_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-int-array-index_true/Main.java new file mode 100644 index 00000000..e6fff707 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-int-array-index_true/Main.java @@ -0,0 +1,17 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 int:2 +import org.sosy_lab.sv_benchmarks.Verifier; +// Control for exec-object-array-index: the same with an int array. +public class Main { + static final int[] TABLE = {0, 0, 7}; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + if (b >= 0 && b < TABLE.length) { + if (TABLE[b] == 7) { + if (a > 1000 && a < 1001) assert false; + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-loop-iid-wrap_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-loop-iid-wrap_false.yml new file mode 100644 index 00000000..3479581c --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-loop-iid-wrap_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - exec-loop-iid-wrap_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-loop-iid-wrap_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-loop-iid-wrap_false/Main.java new file mode 100644 index 00000000..e1d6afc5 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-loop-iid-wrap_false/Main.java @@ -0,0 +1,20 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:66 +import org.sosy_lab.sv_benchmarks.Verifier; +// A branch in a loop is reported once per iteration, and its iid carries the iteration count, +// which wraps at 64: iterations 2 and 66 share an iid. Solving iteration 66 must not use iteration +// 2's constraint (the explorer's constraint cache was keyed by iid). +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int hit = -1; + for (int i = 0; i < 70; i++) { + if (b == i) { + hit = i; + } + } + if (hit == 66) { + if (a > 1000 && a < 100000) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-loop-iid-wrap_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-loop-iid-wrap_true.yml new file mode 100644 index 00000000..54760186 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-loop-iid-wrap_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - exec-loop-iid-wrap_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-loop-iid-wrap_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-loop-iid-wrap_true/Main.java new file mode 100644 index 00000000..875d558c --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-loop-iid-wrap_true/Main.java @@ -0,0 +1,21 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 int:66 +import org.sosy_lab.sv_benchmarks.Verifier; +// A branch in a loop is reported once per iteration, and its iid carries the iteration count, +// which wraps at 64: iterations 2 and 66 share an iid. Solving iteration 66 must not use iteration +// 2's constraint (the explorer's constraint cache was keyed by iid). +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int hit = -1; + for (int i = 0; i < 70; i++) { + if (b == i) { + hit = i; + } + } + if (hit == 66) { + if (a > 1000 && a < 1001) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-object-array-index_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-object-array-index_false.yml new file mode 100644 index 00000000..122d9627 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-object-array-index_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - exec-object-array-index_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-object-array-index_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-object-array-index_false/Main.java new file mode 100644 index 00000000..dc8f3c93 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-object-array-index_false/Main.java @@ -0,0 +1,24 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:2 +import org.sosy_lab.sv_benchmarks.Verifier; +// An object read from an array at an input-dependent index decides whether the assert is reached. +public class Main { + static class C { + final int k; + + C(int k) { + this.k = k; + } + } + + static final C[] TABLE = {new C(0), new C(0), new C(7)}; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + if (b >= 0 && b < TABLE.length) { + if (TABLE[b].k == 7) { + if (a > 1000 && a < 100000) assert false; + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-object-array-index_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-object-array-index_true.yml new file mode 100644 index 00000000..d1312320 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-object-array-index_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - exec-object-array-index_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-object-array-index_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-object-array-index_true/Main.java new file mode 100644 index 00000000..fd00bc40 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-object-array-index_true/Main.java @@ -0,0 +1,25 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 int:2 +import org.sosy_lab.sv_benchmarks.Verifier; +// An object read from an array at an input-dependent index decides whether the assert is reached. +public class Main { + static class C { + final int k; + + C(int k) { + this.k = k; + } + } + + static final C[] TABLE = {new C(0), new C(0), new C(7)}; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + if (b >= 0 && b < TABLE.length) { + if (TABLE[b].k == 7) { + if (a > 1000 && a < 1001) assert false; + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-string-char_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-string-char_false.yml new file mode 100644 index 00000000..df987348 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-string-char_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - exec-string-char_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-string-char_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-string-char_false/Main.java new file mode 100644 index 00000000..1279c89a --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-string-char_false/Main.java @@ -0,0 +1,14 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): string:- int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// Z3 may solve s.charAt(0) == '-' with a supplementary code point (e.g. U+2002D): the executor +// encodes charAt as the code point truncated to 16 bits, and String inputs have no range bound, +// so the next run diverges. +public class Main { + public static void main(String[] args) { + String s = Verifier.nondetString(); + int a = Verifier.nondetInt(); + if (s.length() == 1 && s.charAt(0) == '-') { + if (a > 1000 && a < 100000) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-string-char_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-string-char_true.yml new file mode 100644 index 00000000..4116c4dc --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-string-char_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - exec-string-char_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-string-char_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-string-char_true/Main.java new file mode 100644 index 00000000..ed19c102 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-string-char_true/Main.java @@ -0,0 +1,15 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): string:- int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// Z3 may solve s.charAt(0) == '-' with a supplementary code point (e.g. U+2002D): the executor +// encodes charAt as the code point truncated to 16 bits, and String inputs have no range bound, +// so the next run diverges. +public class Main { + public static void main(String[] args) { + String s = Verifier.nondetString(); + int a = Verifier.nondetInt(); + if (s.length() == 1 && s.charAt(0) == '-') { + if (a > 1000 && a < 1001) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-stringbuilder-setlength_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-stringbuilder-setlength_false.yml new file mode 100644 index 00000000..abf50234 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-stringbuilder-setlength_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - exec-stringbuilder-setlength_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-stringbuilder-setlength_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-stringbuilder-setlength_false/Main.java new file mode 100644 index 00000000..9763c62f --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-stringbuilder-setlength_false/Main.java @@ -0,0 +1,16 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): string:ab int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// A StringBuilder is reused with setLength(0), as parsers do between fields. +public class Main { + public static void main(String[] args) { + String s = Verifier.nondetString(); + int a = Verifier.nondetInt(); + StringBuilder sb = new StringBuilder(); + sb.append(s); + sb.setLength(0); + sb.append('x'); + if (sb.length() == 1 && s.length() == 2) { + if (a > 1000 && a < 100000) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-stringbuilder-setlength_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-stringbuilder-setlength_true.yml new file mode 100644 index 00000000..7245f006 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-stringbuilder-setlength_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - exec-stringbuilder-setlength_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-stringbuilder-setlength_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-stringbuilder-setlength_true/Main.java new file mode 100644 index 00000000..0268ae60 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-stringbuilder-setlength_true/Main.java @@ -0,0 +1,17 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): string:ab int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// A StringBuilder is reused with setLength(0), as parsers do between fields. +public class Main { + public static void main(String[] args) { + String s = Verifier.nondetString(); + int a = Verifier.nondetInt(); + StringBuilder sb = new StringBuilder(); + sb.append(s); + sb.setLength(0); + sb.append('x'); + if (sb.length() == 1 && s.length() == 2) { + if (a > 1000 && a < 1001) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-system-exit-late_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-system-exit-late_false.yml new file mode 100644 index 00000000..3602fb6a --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-system-exit-late_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - exec-system-exit-late_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-system-exit-late_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-system-exit-late_false/Main.java new file mode 100644 index 00000000..c36f67d3 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-system-exit-late_false/Main.java @@ -0,0 +1,16 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// As exec-system-exit, but a branch on the input is recorded before the exit. +public class Main { + static int cnt; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + if (a > 3) cnt++; + if (b != 7) { + System.exit(0); + } + if (a > 1000 && a < 100000) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-system-exit-late_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-system-exit-late_true.yml new file mode 100644 index 00000000..7133bacf --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-system-exit-late_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - exec-system-exit-late_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-system-exit-late_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-system-exit-late_true/Main.java new file mode 100644 index 00000000..5bd29f0a --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-system-exit-late_true/Main.java @@ -0,0 +1,17 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// As exec-system-exit, but a branch on the input is recorded before the exit. +public class Main { + static int cnt; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + if (a > 3) cnt++; + if (b != 7) { + System.exit(0); + } + if (a > 1000 && a < 1001) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-system-exit_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-system-exit_false.yml new file mode 100644 index 00000000..515cd672 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-system-exit_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - exec-system-exit_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-system-exit_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-system-exit_false/Main.java new file mode 100644 index 00000000..2a3a3092 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-system-exit_false/Main.java @@ -0,0 +1,13 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// The first run (b = 0) ends in System.exit before the assert. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + if (b != 7) { + System.exit(0); + } + if (a > 1000 && a < 100000) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-system-exit_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-system-exit_true.yml new file mode 100644 index 00000000..f38e2fd6 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-system-exit_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - exec-system-exit_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-system-exit_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-system-exit_true/Main.java new file mode 100644 index 00000000..eda090c4 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/exec-system-exit_true/Main.java @@ -0,0 +1,14 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// The first run (b = 0) ends in System.exit before the assert. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + if (b != 7) { + System.exit(0); + } + if (a > 1000 && a < 1001) assert false; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/explicit-throw-generic-catch_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/explicit-throw-generic-catch_false.yml new file mode 100644 index 00000000..0aa5fcce --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/explicit-throw-generic-catch_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - explicit-throw-generic-catch_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/explicit-throw-generic-catch_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/explicit-throw-generic-catch_false/Main.java new file mode 100644 index 00000000..824fa385 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/explicit-throw-generic-catch_false/Main.java @@ -0,0 +1,23 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// Control: an explicit throw of IllegalStateException one frame down, caught as RuntimeException. +// The graph models explicit throws, so this one must work. +public class Main { + static int check(int b) { + if (b == 7) throw new IllegalStateException(); + return b; + } + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int r; + try { + r = check(b); + } catch (RuntimeException e) { + if (a > 1000 && a < 100000) assert false; + r = 0; + } + if (r > 50) r--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/explicit-throw-generic-catch_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/explicit-throw-generic-catch_true.yml new file mode 100644 index 00000000..42c795f6 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/explicit-throw-generic-catch_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - explicit-throw-generic-catch_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/explicit-throw-generic-catch_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/explicit-throw-generic-catch_true/Main.java new file mode 100644 index 00000000..5ddb1085 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/explicit-throw-generic-catch_true/Main.java @@ -0,0 +1,24 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// Control: an explicit throw of IllegalStateException one frame down, caught as RuntimeException. +// The graph models explicit throws, so this one must work. +public class Main { + static int check(int b) { + if (b == 7) throw new IllegalStateException(); + return b; + } + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int r; + try { + r = check(b); + } catch (RuntimeException e) { + if (a > 1000 && a < 1001) assert false; + r = 0; + } + if (r > 50) r--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/finally-assert_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/finally-assert_false.yml new file mode 100644 index 00000000..632e36de --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/finally-assert_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - finally-assert_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/finally-assert_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/finally-assert_false/Main.java new file mode 100644 index 00000000..45cc5764 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/finally-assert_false/Main.java @@ -0,0 +1,28 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// The assert is inside a finally block, which javac copies onto every exit path of try. +public class Main { + static int cnt; + + static void g(int a, int b) { + try { + if (b == 7) return; + cnt++; + if (b == 8) throw new IllegalStateException(); + } finally { + if (b == 7) { + if (a > 1000 && a < 100000) assert false; + } + } + } + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + try { + g(a, b); + } catch (IllegalStateException e) { + cnt--; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/finally-assert_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/finally-assert_true.yml new file mode 100644 index 00000000..76b49b21 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/finally-assert_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - finally-assert_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/finally-assert_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/finally-assert_true/Main.java new file mode 100644 index 00000000..a6cf8a55 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/finally-assert_true/Main.java @@ -0,0 +1,29 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// The assert is inside a finally block, which javac copies onto every exit path of try. +public class Main { + static int cnt; + + static void g(int a, int b) { + try { + if (b == 7) return; + cnt++; + if (b == 8) throw new IllegalStateException(); + } finally { + if (b == 7) { + if (a > 1000 && a < 1001) assert false; + } + } + } + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + try { + g(a, b); + } catch (IllegalStateException e) { + cnt--; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/finally-exits_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/finally-exits_false.yml new file mode 100644 index 00000000..f38c9074 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/finally-exits_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - finally-exits_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/finally-exits_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/finally-exits_false/Main.java new file mode 100644 index 00000000..5fc37ea7 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/finally-exits_false/Main.java @@ -0,0 +1,32 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// try/finally with return and throw inside try: javac copies the finally block (with its branch) +// onto each exit path. The assert is behind the throwing exit. +public class Main { + static int cnt; + + static int f(int a, int b) { + try { + if (b == 8) return 1; + if (b == 7) throw new IllegalStateException(); + return 0; + } finally { + cnt++; + if (a > 50) cnt++; + } + } + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int r; + try { + r = f(a, b); + } catch (IllegalStateException e) { + r = 2; + } + if (r == 2) { + if (a > 1000 && a < 100000) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/finally-exits_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/finally-exits_true.yml new file mode 100644 index 00000000..7f8ecf77 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/finally-exits_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - finally-exits_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/finally-exits_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/finally-exits_true/Main.java new file mode 100644 index 00000000..d38e03f8 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/finally-exits_true/Main.java @@ -0,0 +1,33 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// try/finally with return and throw inside try: javac copies the finally block (with its branch) +// onto each exit path. The assert is behind the throwing exit. +public class Main { + static int cnt; + + static int f(int a, int b) { + try { + if (b == 8) return 1; + if (b == 7) throw new IllegalStateException(); + return 0; + } finally { + cnt++; + if (a > 50) cnt++; + } + } + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int r; + try { + r = f(a, b); + } catch (IllegalStateException e) { + r = 2; + } + if (r == 2) { + if (a > 1000 && a < 1001) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/finally-npe_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/finally-npe_false.yml new file mode 100644 index 00000000..6f7139a6 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/finally-npe_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - finally-npe_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/finally-npe_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/finally-npe_false/Main.java new file mode 100644 index 00000000..2ac533e2 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/finally-npe_false/Main.java @@ -0,0 +1,32 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// A NullPointerException leaves a try block with a finally (a catch-any handler in bytecode); the +// assert is in the finally block. main catches the exception afterwards. +public class Main { + static class Box { + int v = 3; + } + + static int read(Box box, int a) { + try { + return box.v; + } finally { + if (box == null) { + if (a > 1000 && a < 100000) assert false; + } + } + } + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + Box box = b == 7 ? null : new Box(); + int r; + try { + r = read(box, a); + } catch (NullPointerException e) { + r = 0; + } + if (r > 50) r--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/finally-npe_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/finally-npe_true.yml new file mode 100644 index 00000000..ff2afd23 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/finally-npe_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - finally-npe_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/finally-npe_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/finally-npe_true/Main.java new file mode 100644 index 00000000..f239c37c --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/finally-npe_true/Main.java @@ -0,0 +1,33 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// A NullPointerException leaves a try block with a finally (a catch-any handler in bytecode); the +// assert is in the finally block. main catches the exception afterwards. +public class Main { + static class Box { + int v = 3; + } + + static int read(Box box, int a) { + try { + return box.v; + } finally { + if (box == null) { + if (a > 1000 && a < 1001) assert false; + } + } + } + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + Box box = b == 7 ? null : new Box(); + int r; + try { + r = read(box, a); + } catch (NullPointerException e) { + r = 0; + } + if (r > 50) r--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/inner-accessor_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/inner-accessor_false.yml new file mode 100644 index 00000000..a209a53d --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/inner-accessor_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - inner-accessor_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/inner-accessor_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/inner-accessor_false/Main.java new file mode 100644 index 00000000..0fb6f964 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/inner-accessor_false/Main.java @@ -0,0 +1,28 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// An inner class reads a private field and calls a private method of its outer class through +// javac's synthetic access$ methods (Java 8 target). +public class Main { + private int limit = 100000; + + private boolean inRange(int a) { + return a > 1000; + } + + class Inner { + void check(int a) { + if (inRange(a) && a < limit) { + if (a > 1000 && a < 100000) assert false; + } + } + } + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + Main m = new Main(); + Main.Inner in = m.new Inner(); + if (b == 7) in.check(a); + if (a > 3) a--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/inner-accessor_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/inner-accessor_true.yml new file mode 100644 index 00000000..ef23c07e --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/inner-accessor_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - inner-accessor_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/inner-accessor_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/inner-accessor_true/Main.java new file mode 100644 index 00000000..39c4107b --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/inner-accessor_true/Main.java @@ -0,0 +1,29 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// An inner class reads a private field and calls a private method of its outer class through +// javac's synthetic access$ methods (Java 8 target). +public class Main { + private int limit = 100000; + + private boolean inRange(int a) { + return a > 1000; + } + + class Inner { + void check(int a) { + if (inRange(a) && a < limit) { + if (a > 1000 && a < 1001) assert false; + } + } + } + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + Main m = new Main(); + Main.Inner in = m.new Inner(); + if (b == 7) in.check(a); + if (a > 3) a--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/jdk-getmethods_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/jdk-getmethods_false.yml new file mode 100644 index 00000000..d6cdc1e1 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/jdk-getmethods_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - jdk-getmethods_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/jdk-getmethods_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/jdk-getmethods_false/Main.java new file mode 100644 index 00000000..5055a44b --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/jdk-getmethods_false/Main.java @@ -0,0 +1,17 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:7 +import java.lang.reflect.Method; +import org.sosy_lab.sv_benchmarks.Verifier; +// Class.getMethods() returns an array the executor does not track; it is read before the assert. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + Method[] ms = Main.class.getMethods(); + int n = ms.length; + String name = ms[0].getName(); + if (b == 7) { + if (a > 1000 && a < 100000) assert false; + } + if (n > 3) n--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/jdk-getmethods_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/jdk-getmethods_true.yml new file mode 100644 index 00000000..282e5cd6 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/jdk-getmethods_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - jdk-getmethods_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/jdk-getmethods_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/jdk-getmethods_true/Main.java new file mode 100644 index 00000000..cc1002bd --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/jdk-getmethods_true/Main.java @@ -0,0 +1,18 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 int:7 +import java.lang.reflect.Method; +import org.sosy_lab.sv_benchmarks.Verifier; +// Class.getMethods() returns an array the executor does not track; it is read before the assert. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + Method[] ms = Main.class.getMethods(); + int n = ms.length; + String name = ms[0].getName(); + if (b == 7) { + if (a > 1000 && a < 1001) assert false; + } + if (n > 3) n--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/jdk-toarray_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/jdk-toarray_false.yml new file mode 100644 index 00000000..4ebcd2da --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/jdk-toarray_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - jdk-toarray_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/jdk-toarray_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/jdk-toarray_false/Main.java new file mode 100644 index 00000000..afd499d8 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/jdk-toarray_false/Main.java @@ -0,0 +1,20 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:7 +import java.util.ArrayList; +import java.util.List; +import org.sosy_lab.sv_benchmarks.Verifier; +// An array returned by an uninstrumented JDK call is read before the assert (bounds guard on an +// untracked array; regression test for the phantom bounds-guard bug). +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + List list = new ArrayList(); + list.add(3); + list.add(5); + Object[] arr = list.toArray(); + int x = (Integer) arr[1]; + if (b == 7) { + if (a + x - 5 > 1000 && a + x - 5 < 100000) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/jdk-toarray_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/jdk-toarray_true.yml new file mode 100644 index 00000000..29f6c841 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/jdk-toarray_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - jdk-toarray_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/jdk-toarray_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/jdk-toarray_true/Main.java new file mode 100644 index 00000000..0cfc2190 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/jdk-toarray_true/Main.java @@ -0,0 +1,21 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 int:7 +import java.util.ArrayList; +import java.util.List; +import org.sosy_lab.sv_benchmarks.Verifier; +// An array returned by an uninstrumented JDK call is read before the assert (bounds guard on an +// untracked array; regression test for the phantom bounds-guard bug). +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + List list = new ArrayList(); + list.add(3); + list.add(5); + Object[] arr = list.toArray(); + int x = (Integer) arr[1]; + if (b == 7) { + if (a + x - 5 > 1000 && a + x - 5 < 1001) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-consumer_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-consumer_false.yml new file mode 100644 index 00000000..3fffb572 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-consumer_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - lambda-consumer_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-consumer_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-consumer_false/Main.java new file mode 100644 index 00000000..eaf89103 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-consumer_false/Main.java @@ -0,0 +1,15 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:7 +import java.util.function.IntConsumer; +import org.sosy_lab.sv_benchmarks.Verifier; +// The assert is inside a lambda created in main and called on an input-dependent path. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + IntConsumer check = x -> { + if (x > 1000 && x < 100000) assert false; + }; + if (b == 7) check.accept(a); + if (a > 3) a--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-consumer_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-consumer_true.yml new file mode 100644 index 00000000..e71149e7 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-consumer_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - lambda-consumer_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-consumer_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-consumer_true/Main.java new file mode 100644 index 00000000..73db8c0c --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-consumer_true/Main.java @@ -0,0 +1,16 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 int:7 +import java.util.function.IntConsumer; +import org.sosy_lab.sv_benchmarks.Verifier; +// The assert is inside a lambda created in main and called on an input-dependent path. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + IntConsumer check = x -> { + if (x > 1000 && x < 1001) assert false; + }; + if (b == 7) check.accept(a); + if (a > 3) a--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-local-noarg_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-local-noarg_false.yml new file mode 100644 index 00000000..a25de8df --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-local-noarg_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - lambda-local-noarg_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-local-noarg_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-local-noarg_false/Main.java new file mode 100644 index 00000000..4f251883 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-local-noarg_false/Main.java @@ -0,0 +1,17 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// As lambda-static-field-noarg, but the lambda is created in main (invokedynamic in the graph). +public class Main { + static int input; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + input = a; + Runnable check = () -> { + if (input > 1000 && input < 100000) assert false; + }; + if (b == 7) check.run(); + if (a > 3) a--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-local-noarg_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-local-noarg_true.yml new file mode 100644 index 00000000..39cebb38 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-local-noarg_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - lambda-local-noarg_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-local-noarg_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-local-noarg_true/Main.java new file mode 100644 index 00000000..2c0618f3 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-local-noarg_true/Main.java @@ -0,0 +1,18 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// As lambda-static-field-noarg, but the lambda is created in main (invokedynamic in the graph). +public class Main { + static int input; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + input = a; + Runnable check = () -> { + if (input > 1000 && input < 1001) assert false; + }; + if (b == 7) check.run(); + if (a > 3) a--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-predicate_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-predicate_false.yml new file mode 100644 index 00000000..9ffcce01 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-predicate_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - lambda-predicate_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-predicate_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-predicate_false/Main.java new file mode 100644 index 00000000..925307dc --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-predicate_false/Main.java @@ -0,0 +1,14 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:7 +import java.util.function.IntPredicate; +import org.sosy_lab.sv_benchmarks.Verifier; +// The assert condition is evaluated by a lambda (invokedynamic) created in main. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + IntPredicate p = x -> x > 1000 && x < 100000; + if (b == 7) { + if (p.test(a)) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-predicate_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-predicate_true.yml new file mode 100644 index 00000000..14e81736 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-predicate_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - lambda-predicate_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-predicate_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-predicate_true/Main.java new file mode 100644 index 00000000..109a59ed --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-predicate_true/Main.java @@ -0,0 +1,15 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 int:7 +import java.util.function.IntPredicate; +import org.sosy_lab.sv_benchmarks.Verifier; +// The assert condition is evaluated by a lambda (invokedynamic) created in main. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + IntPredicate p = x -> x > 1000 && x < 1001; + if (b == 7) { + if (p.test(a)) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-static-field-noarg_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-static-field-noarg_false.yml new file mode 100644 index 00000000..60adc4fe --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-static-field-noarg_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - lambda-static-field-noarg_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-static-field-noarg_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-static-field-noarg_false/Main.java new file mode 100644 index 00000000..3d237fa7 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-static-field-noarg_false/Main.java @@ -0,0 +1,19 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// The assert is inside a no-argument lambda stored in a static field; it reads the input from a +// static field, so no argument passes through JDK frames. +public class Main { + static int input; + + static final Runnable CHECK = () -> { + if (input > 1000 && input < 100000) assert false; + }; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + input = a; + if (b == 7) CHECK.run(); + if (a > 3) a--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-static-field-noarg_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-static-field-noarg_true.yml new file mode 100644 index 00000000..42a31239 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-static-field-noarg_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - lambda-static-field-noarg_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-static-field-noarg_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-static-field-noarg_true/Main.java new file mode 100644 index 00000000..a14752b4 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-static-field-noarg_true/Main.java @@ -0,0 +1,20 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// The assert is inside a no-argument lambda stored in a static field; it reads the input from a +// static field, so no argument passes through JDK frames. +public class Main { + static int input; + + static final Runnable CHECK = () -> { + if (input > 1000 && input < 1001) assert false; + }; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + input = a; + if (b == 7) CHECK.run(); + if (a > 3) a--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-static-field_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-static-field_false.yml new file mode 100644 index 00000000..37fff840 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-static-field_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - lambda-static-field_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-static-field_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-static-field_false/Main.java new file mode 100644 index 00000000..055c2cd5 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-static-field_false/Main.java @@ -0,0 +1,17 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:7 +import java.util.function.IntConsumer; +import org.sosy_lab.sv_benchmarks.Verifier; +// The assert is inside a lambda stored in a static field (created in ) and called on an +// input-dependent path. +public class Main { + static final IntConsumer CHECK = x -> { + if (x > 1000 && x < 100000) assert false; + }; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + if (b == 7) CHECK.accept(a); + if (a > 3) a--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-static-field_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-static-field_true.yml new file mode 100644 index 00000000..0cd2de0d --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-static-field_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - lambda-static-field_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-static-field_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-static-field_true/Main.java new file mode 100644 index 00000000..d6f49d13 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/lambda-static-field_true/Main.java @@ -0,0 +1,18 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 int:7 +import java.util.function.IntConsumer; +import org.sosy_lab.sv_benchmarks.Verifier; +// The assert is inside a lambda stored in a static field (created in ) and called on an +// input-dependent path. +public class Main { + static final IntConsumer CHECK = x -> { + if (x > 1000 && x < 1001) assert false; + }; + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + if (b == 7) CHECK.accept(a); + if (a > 3) a--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/library-callback-assert_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/library-callback-assert_false.yml new file mode 100644 index 00000000..f6735f1b --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/library-callback-assert_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - library-callback-assert_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/library-callback-assert_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/library-callback-assert_false/Main.java new file mode 100644 index 00000000..97ba18e8 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/library-callback-assert_false/Main.java @@ -0,0 +1,35 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:7 +import java.io.IOException; +import java.io.PushbackReader; +import java.io.Reader; +import org.sosy_lab.sv_benchmarks.Verifier; +// The assert is in application code that library code calls back (PushbackReader.read() calls +// the application's Reader), on an input-dependent path. The graph does not have the callback, so +// it treats the library call as unable to reach an assert. Known false SAFE with SA (RESULTS.md). +public class Main { + static int input; + + static class Chars extends Reader { + public int read() { + if (input > 1000 && input < 100000) assert false; + return 1; + } + + public int read(char[] buf, int off, int len) { + return -1; + } + + public void close() { + } + } + + public static void main(String[] args) throws IOException { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + input = a; + if (b == 7) { + new PushbackReader(new Chars()).read(); + } + if (a > 3) a--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/library-callback-assert_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/library-callback-assert_true.yml new file mode 100644 index 00000000..39d403bb --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/library-callback-assert_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - library-callback-assert_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/library-callback-assert_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/library-callback-assert_true/Main.java new file mode 100644 index 00000000..c4948e25 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/library-callback-assert_true/Main.java @@ -0,0 +1,36 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 int:7 +import java.io.IOException; +import java.io.PushbackReader; +import java.io.Reader; +import org.sosy_lab.sv_benchmarks.Verifier; +// The assert is in application code that library code calls back (PushbackReader.read() calls +// the application's Reader), on an input-dependent path. The graph does not have the callback, so +// it treats the library call as unable to reach an assert. Known false SAFE with SA (RESULTS.md). +public class Main { + static int input; + + static class Chars extends Reader { + public int read() { + if (input > 1000 && input < 1001) assert false; + return 1; + } + + public int read(char[] buf, int off, int len) { + return -1; + } + + public void close() { + } + } + + public static void main(String[] args) throws IOException { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + input = a; + if (b == 7) { + new PushbackReader(new Chars()).read(); + } + if (a > 3) a--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/library-callback-throw_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/library-callback-throw_false.yml new file mode 100644 index 00000000..2d53924f --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/library-callback-throw_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - library-callback-throw_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/library-callback-throw_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/library-callback-throw_false/Main.java new file mode 100644 index 00000000..109f09bc --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/library-callback-throw_false/Main.java @@ -0,0 +1,41 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:7 +import java.io.IOException; +import java.io.PushbackReader; +import java.io.Reader; +import org.sosy_lab.sv_benchmarks.Verifier; +// As library-callback, but the callback throws: the exception comes out of the library call into +// the application's handler, where the assert is. (The input is read from a static field: an +// object handed to library code with symbolic fields counts as context loss.) +public class Main { + static int first; + + static class Chars extends Reader { + public int read() throws IOException { + if (first == 7) { + throw new IOException("end"); + } + return first > 100 ? 100 : first; + } + + public int read(char[] buf, int off, int len) { + return -1; + } + + public void close() { + } + } + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + first = b; + int c; + try { + c = new PushbackReader(new Chars()).read(); + } catch (IOException e) { + if (a > 1000 && a < 100000) assert false; + c = 0; + } + if (c > 50) c--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/library-callback-throw_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/library-callback-throw_true.yml new file mode 100644 index 00000000..8752f472 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/library-callback-throw_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - library-callback-throw_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/library-callback-throw_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/library-callback-throw_true/Main.java new file mode 100644 index 00000000..24af4a00 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/library-callback-throw_true/Main.java @@ -0,0 +1,42 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 int:7 +import java.io.IOException; +import java.io.PushbackReader; +import java.io.Reader; +import org.sosy_lab.sv_benchmarks.Verifier; +// As library-callback, but the callback throws: the exception comes out of the library call into +// the application's handler, where the assert is. (The input is read from a static field: an +// object handed to library code with symbolic fields counts as context loss.) +public class Main { + static int first; + + static class Chars extends Reader { + public int read() throws IOException { + if (first == 7) { + throw new IOException("end"); + } + return first > 100 ? 100 : first; + } + + public int read(char[] buf, int off, int len) { + return -1; + } + + public void close() { + } + } + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + first = b; + int c; + try { + c = new PushbackReader(new Chars()).read(); + } catch (IOException e) { + if (a > 1000 && a < 1001) assert false; + c = 0; + } + if (c > 50) c--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/library-callback_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/library-callback_false.yml new file mode 100644 index 00000000..b58f9650 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/library-callback_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - library-callback_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/library-callback_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/library-callback_false/Main.java new file mode 100644 index 00000000..5acf40e5 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/library-callback_false/Main.java @@ -0,0 +1,45 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:7 +import java.io.IOException; +import java.io.PushbackReader; +import java.io.Reader; +import org.sosy_lab.sv_benchmarks.Verifier; +// Library code calls back into application code: PushbackReader.read() calls the application's +// Reader, whose read() branches. The graph has the call as a plain library call, so the walk has +// to step over the callback's branches (from nanoxml, java-ranger-regression). +public class Main { + static class Chars extends Reader { + private final int[] data; + private int next = 0; + + Chars(int[] data) { + this.data = data; + } + + public int read() { + if (next >= data.length) { + return -1; + } + int c = data[next]; + next = next + 1; + return c > 100 ? 100 : c; + } + + public int read(char[] buf, int off, int len) { + return -1; + } + + public void close() { + } + } + + public static void main(String[] args) throws IOException { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + PushbackReader in = new PushbackReader(new Chars(new int[] {b, 5})); + int c = in.read(); + if (c == 7) { + if (a > 1000 && a < 100000) assert false; + } + if (c > 50) c--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/library-callback_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/library-callback_true.yml new file mode 100644 index 00000000..270d2e98 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/library-callback_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - library-callback_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/library-callback_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/library-callback_true/Main.java new file mode 100644 index 00000000..71b984cd --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/library-callback_true/Main.java @@ -0,0 +1,46 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 int:7 +import java.io.IOException; +import java.io.PushbackReader; +import java.io.Reader; +import org.sosy_lab.sv_benchmarks.Verifier; +// Library code calls back into application code: PushbackReader.read() calls the application's +// Reader, whose read() branches. The graph has the call as a plain library call, so the walk has +// to step over the callback's branches (from nanoxml, java-ranger-regression). +public class Main { + static class Chars extends Reader { + private final int[] data; + private int next = 0; + + Chars(int[] data) { + this.data = data; + } + + public int read() { + if (next >= data.length) { + return -1; + } + int c = data[next]; + next = next + 1; + return c > 100 ? 100 : c; + } + + public int read(char[] buf, int off, int len) { + return -1; + } + + public void close() { + } + } + + public static void main(String[] args) throws IOException { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + PushbackReader in = new PushbackReader(new Chars(new int[] {b, 5})); + int c = in.read(); + if (c == 7) { + if (a > 1000 && a < 1001) assert false; + } + if (c > 50) c--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/library-throw-catch_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/library-throw-catch_false.yml new file mode 100644 index 00000000..90f84b27 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/library-throw-catch_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - library-throw-catch_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/library-throw-catch_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/library-throw-catch_false/Main.java new file mode 100644 index 00000000..2295f814 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/library-throw-catch_false/Main.java @@ -0,0 +1,19 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// A JDK method (Integer.parseInt) throws, and the application catches it: the usual way CLI tools +// handle a malformed argument. The assert is in the handler. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + String arg = b == 7 ? "x7" : "12"; + int v; + try { + v = Integer.parseInt(arg); + } catch (NumberFormatException e) { + if (a > 1000 && a < 100000) assert false; + v = 0; + } + if (v > 5) v--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/library-throw-catch_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/library-throw-catch_true.yml new file mode 100644 index 00000000..a454a2a0 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/library-throw-catch_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - library-throw-catch_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/library-throw-catch_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/library-throw-catch_true/Main.java new file mode 100644 index 00000000..283222e4 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/library-throw-catch_true/Main.java @@ -0,0 +1,20 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// A JDK method (Integer.parseInt) throws, and the application catches it: the usual way CLI tools +// handle a malformed argument. The assert is in the handler. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + String arg = b == 7 ? "x7" : "12"; + int v; + try { + v = Integer.parseInt(arg); + } catch (NumberFormatException e) { + if (a > 1000 && a < 1001) assert false; + v = 0; + } + if (v > 5) v--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-catch-local_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-catch-local_false.yml new file mode 100644 index 00000000..69c5566e --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-catch-local_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - npe-catch-local_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-catch-local_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-catch-local_false/Main.java new file mode 100644 index 00000000..a4c59bca --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-catch-local_false/Main.java @@ -0,0 +1,23 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// The assert is in a catch (NullPointerException) in the method that dereferences. +public class Main { + static class Box { + int v = 3; + } + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + Box box = b == 7 ? null : new Box(); + int r; + try { + r = box.v; + if (r > 2) r--; + } catch (NullPointerException e) { + if (a > 1000 && a < 100000) assert false; + r = 0; + } + if (r > 50) r--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-catch-local_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-catch-local_true.yml new file mode 100644 index 00000000..70c6ee07 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-catch-local_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - npe-catch-local_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-catch-local_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-catch-local_true/Main.java new file mode 100644 index 00000000..21af869e --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-catch-local_true/Main.java @@ -0,0 +1,24 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// The assert is in a catch (NullPointerException) in the method that dereferences. +public class Main { + static class Box { + int v = 3; + } + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + Box box = b == 7 ? null : new Box(); + int r; + try { + r = box.v; + if (r > 2) r--; + } catch (NullPointerException e) { + if (a > 1000 && a < 1001) assert false; + r = 0; + } + if (r > 50) r--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-catch-up1_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-catch-up1_false.yml new file mode 100644 index 00000000..bd958f6d --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-catch-up1_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - npe-catch-up1_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-catch-up1_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-catch-up1_false/Main.java new file mode 100644 index 00000000..184bdfac --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-catch-up1_false/Main.java @@ -0,0 +1,28 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// The assert is in a catch (NullPointerException) one frame above the dereference. +public class Main { + static class Box { + int v = 3; + } + + static int read(Box box) { + int v = box.v; + if (v > 2) v--; + return v; + } + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + Box box = b == 7 ? null : new Box(); + int r; + try { + r = read(box); + } catch (NullPointerException e) { + if (a > 1000 && a < 100000) assert false; + r = 0; + } + if (r > 50) r--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-catch-up1_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-catch-up1_true.yml new file mode 100644 index 00000000..b94d5759 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-catch-up1_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - npe-catch-up1_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-catch-up1_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-catch-up1_true/Main.java new file mode 100644 index 00000000..d2f28440 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-catch-up1_true/Main.java @@ -0,0 +1,29 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// The assert is in a catch (NullPointerException) one frame above the dereference. +public class Main { + static class Box { + int v = 3; + } + + static int read(Box box) { + int v = box.v; + if (v > 2) v--; + return v; + } + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + Box box = b == 7 ? null : new Box(); + int r; + try { + r = read(box); + } catch (NullPointerException e) { + if (a > 1000 && a < 1001) assert false; + r = 0; + } + if (r > 50) r--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-handler-in-callee_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-handler-in-callee_false.yml new file mode 100644 index 00000000..a5130bd0 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-handler-in-callee_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - npe-handler-in-callee_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-handler-in-callee_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-handler-in-callee_false/Main.java new file mode 100644 index 00000000..824693de --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-handler-in-callee_false/Main.java @@ -0,0 +1,30 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// The last branch before the null dereference is in main, but the handler is in a callee entered +// after it: the walk cannot know that callee's call site and must give up instead. +public class Main { + static class Box { + int v = 3; + } + + static int read(Box box) { + return box.v; + } + + static int guarded(Box box, int a) { + try { + return read(box); + } catch (NullPointerException e) { + if (a > 1000 && a < 100000) assert false; + return 0; + } + } + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + Box box = b == 7 ? null : new Box(); + int r = guarded(box, a); + if (r > 50) r--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-handler-in-callee_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-handler-in-callee_true.yml new file mode 100644 index 00000000..b6744baf --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-handler-in-callee_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - npe-handler-in-callee_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-handler-in-callee_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-handler-in-callee_true/Main.java new file mode 100644 index 00000000..e36481f6 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-handler-in-callee_true/Main.java @@ -0,0 +1,31 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// The last branch before the null dereference is in main, but the handler is in a callee entered +// after it: the walk cannot know that callee's call site and must give up instead. +public class Main { + static class Box { + int v = 3; + } + + static int read(Box box) { + return box.v; + } + + static int guarded(Box box, int a) { + try { + return read(box); + } catch (NullPointerException e) { + if (a > 1000 && a < 1001) assert false; + return 0; + } + } + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + Box box = b == 7 ? null : new Box(); + int r = guarded(box, a); + if (r > 50) r--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-in-callee-branch_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-in-callee-branch_false.yml new file mode 100644 index 00000000..c1ac0a58 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-in-callee-branch_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - npe-in-callee-branch_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-in-callee-branch_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-in-callee-branch_false/Main.java new file mode 100644 index 00000000..5cc89db5 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-in-callee-branch_false/Main.java @@ -0,0 +1,29 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// Whether the callee dereferences null is decided by a branch inside the callee; the +// NullPointerException is caught in main, where the assert is. +public class Main { + static class Box { + int v = 3; + } + + static int read(Box box, int b) { + Box x = b == 7 ? null : box; + int v = x.v; + if (v > 2) v--; + return v; + } + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int r; + try { + r = read(new Box(), b); + } catch (NullPointerException e) { + if (a > 1000 && a < 100000) assert false; + r = 0; + } + if (r > 50) r--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-in-callee-branch_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-in-callee-branch_true.yml new file mode 100644 index 00000000..3b53687e --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-in-callee-branch_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - npe-in-callee-branch_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-in-callee-branch_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-in-callee-branch_true/Main.java new file mode 100644 index 00000000..664f53f4 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-in-callee-branch_true/Main.java @@ -0,0 +1,30 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// Whether the callee dereferences null is decided by a branch inside the callee; the +// NullPointerException is caught in main, where the assert is. +public class Main { + static class Box { + int v = 3; + } + + static int read(Box box, int b) { + Box x = b == 7 ? null : box; + int v = x.v; + if (v > 2) v--; + return v; + } + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int r; + try { + r = read(new Box(), b); + } catch (NullPointerException e) { + if (a > 1000 && a < 1001) assert false; + r = 0; + } + if (r > 50) r--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-recursive-catch_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-recursive-catch_false.yml new file mode 100644 index 00000000..425d4ec9 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-recursive-catch_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - npe-recursive-catch_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-recursive-catch_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-recursive-catch_false/Main.java new file mode 100644 index 00000000..46219f52 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-recursive-catch_false/Main.java @@ -0,0 +1,32 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// A recursive method dereferences null at depth 3 (b == 7); only the activation at depth 2 has a +// handler around its recursive call, with the assert. Resuming the walk must pick that frame. +public class Main { + static class Box { + int v = 3; + } + + static int rec(int depth, int a, int b) { + if (depth == 3) { + Box x = b == 7 ? null : new Box(); + return x.v; + } + if (depth == 2) { + try { + return rec(depth + 1, a, b); + } catch (NullPointerException e) { + if (a > 1000 && a < 100000) assert false; + return 0; + } + } + return rec(depth + 1, a, b) + 1; + } + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int r = rec(0, a, b); + if (r > 50) r--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-recursive-catch_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-recursive-catch_true.yml new file mode 100644 index 00000000..397bbbcc --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-recursive-catch_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - npe-recursive-catch_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-recursive-catch_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-recursive-catch_true/Main.java new file mode 100644 index 00000000..29dc3625 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/npe-recursive-catch_true/Main.java @@ -0,0 +1,33 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// A recursive method dereferences null at depth 3 (b == 7); only the activation at depth 2 has a +// handler around its recursive call, with the assert. Resuming the walk must pick that frame. +public class Main { + static class Box { + int v = 3; + } + + static int rec(int depth, int a, int b) { + if (depth == 3) { + Box x = b == 7 ? null : new Box(); + return x.v; + } + if (depth == 2) { + try { + return rec(depth + 1, a, b); + } catch (NullPointerException e) { + if (a > 1000 && a < 1001) assert false; + return 0; + } + } + return rec(depth + 1, a, b) + 1; + } + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int r = rec(0, a, b); + if (r > 50) r--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/recursion-depth_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/recursion-depth_false.yml new file mode 100644 index 00000000..2413cd67 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/recursion-depth_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - recursion-depth_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/recursion-depth_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/recursion-depth_false/Main.java new file mode 100644 index 00000000..5eabf261 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/recursion-depth_false/Main.java @@ -0,0 +1,18 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:4 +import org.sosy_lab.sv_benchmarks.Verifier; +// The assert is at an input-dependent recursion depth (b = 4). +public class Main { + static void rec(int depth, int a, int b) { + if (depth == b && depth > 3) { + if (a > 1000 && a < 100000) assert false; + return; + } + if (depth < 6) rec(depth + 1, a, b); + } + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + rec(0, a, b); + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/recursion-depth_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/recursion-depth_true.yml new file mode 100644 index 00000000..aa6c3691 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/recursion-depth_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - recursion-depth_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/recursion-depth_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/recursion-depth_true/Main.java new file mode 100644 index 00000000..ddedd255 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/recursion-depth_true/Main.java @@ -0,0 +1,19 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 int:4 +import org.sosy_lab.sv_benchmarks.Verifier; +// The assert is at an input-dependent recursion depth (b = 4). +public class Main { + static void rec(int depth, int a, int b) { + if (depth == b && depth > 3) { + if (a > 1000 && a < 1001) assert false; + return; + } + if (depth < 6) rec(depth + 1, a, b); + } + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + rec(0, a, b); + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/recursion-mutual_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/recursion-mutual_false.yml new file mode 100644 index 00000000..8610eb0d --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/recursion-mutual_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - recursion-mutual_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/recursion-mutual_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/recursion-mutual_false/Main.java new file mode 100644 index 00000000..76ebd892 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/recursion-mutual_false/Main.java @@ -0,0 +1,22 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:5 +import org.sosy_lab.sv_benchmarks.Verifier; +// Mutual recursion; the assert is only reachable at an odd depth b > 3 (b = 5). +public class Main { + static void even(int depth, int a, int b) { + if (depth < 8) odd(depth + 1, a, b); + } + + static void odd(int depth, int a, int b) { + if (depth == b && depth > 3) { + if (a > 1000 && a < 100000) assert false; + return; + } + if (depth < 8) even(depth + 1, a, b); + } + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + even(0, a, b); + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/recursion-mutual_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/recursion-mutual_true.yml new file mode 100644 index 00000000..948e63b1 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/recursion-mutual_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - recursion-mutual_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/recursion-mutual_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/recursion-mutual_true/Main.java new file mode 100644 index 00000000..9fb18edb --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/recursion-mutual_true/Main.java @@ -0,0 +1,23 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 int:5 +import org.sosy_lab.sv_benchmarks.Verifier; +// Mutual recursion; the assert is only reachable at an odd depth b > 3 (b = 5). +public class Main { + static void even(int depth, int a, int b) { + if (depth < 8) odd(depth + 1, a, b); + } + + static void odd(int depth, int a, int b) { + if (depth == b && depth > 3) { + if (a > 1000 && a < 1001) assert false; + return; + } + if (depth < 8) even(depth + 1, a, b); + } + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + even(0, a, b); + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/ref-eq_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/ref-eq_false.yml new file mode 100644 index 00000000..613774e9 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/ref-eq_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - ref-eq_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/ref-eq_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/ref-eq_false/Main.java new file mode 100644 index 00000000..f46b2455 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/ref-eq_false/Main.java @@ -0,0 +1,14 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// Object == before the assert (rewritten to UtilInstrumented.refEquals, which adds branch events). +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + Object o1 = new Object(); + Object o2 = b == 7 ? o1 : new Object(); + if (o1 == o2) { + if (a > 1000 && a < 100000) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/ref-eq_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/ref-eq_true.yml new file mode 100644 index 00000000..f96c3bc4 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/ref-eq_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - ref-eq_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/ref-eq_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/ref-eq_true/Main.java new file mode 100644 index 00000000..8f2c1254 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/ref-eq_true/Main.java @@ -0,0 +1,15 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// Object == before the assert (rewritten to UtilInstrumented.refEquals, which adds branch events). +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + Object o1 = new Object(); + Object o2 = b == 7 ? o1 : new Object(); + if (o1 == o2) { + if (a > 1000 && a < 1001) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/ref-ne-chain_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/ref-ne-chain_false.yml new file mode 100644 index 00000000..68040f15 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/ref-ne-chain_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - ref-ne-chain_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/ref-ne-chain_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/ref-ne-chain_false/Main.java new file mode 100644 index 00000000..3e0f897d --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/ref-ne-chain_false/Main.java @@ -0,0 +1,20 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// Several object != comparisons, then branches, then the assert. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + Object o1 = new Object(); + Object o2 = b == 7 ? o1 : new Object(); + Object o3 = new Object(); + int r = 0; + if (o1 != o3) r++; + if (o2 != o3) r++; + if (a > 5) r++; + if (o1 != o2) return; + if (r == 3) { + if (a > 1000 && a < 100000) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/ref-ne-chain_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/ref-ne-chain_true.yml new file mode 100644 index 00000000..abdb61d0 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/ref-ne-chain_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - ref-ne-chain_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/ref-ne-chain_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/ref-ne-chain_true/Main.java new file mode 100644 index 00000000..1bb57d4e --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/ref-ne-chain_true/Main.java @@ -0,0 +1,21 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// Several object != comparisons, then branches, then the assert. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + Object o1 = new Object(); + Object o2 = b == 7 ? o1 : new Object(); + Object o3 = new Object(); + int r = 0; + if (o1 != o3) r++; + if (o2 != o3) r++; + if (a > 5) r++; + if (o1 != o2) return; + if (r == 3) { + if (a > 1000 && a < 1001) assert false; + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/reflection-desync_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/reflection-desync_false.yml new file mode 100644 index 00000000..7480a1bc --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/reflection-desync_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - reflection-desync_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/reflection-desync_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/reflection-desync_false/Main.java new file mode 100644 index 00000000..71dec248 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/reflection-desync_false/Main.java @@ -0,0 +1,20 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 +import java.lang.reflect.Method; +import org.sosy_lab.sv_benchmarks.Verifier; +// Method.invoke runs a method with branches unconditionally; main has a branch of its own after +// the call, which the walk may pair with the callee's branches. +public class Main { + static int cnt; + + public static void check(int x) { + if (x > 1000 && x < 100000) assert false; + } + + public static void main(String[] args) throws Exception { + int a = Verifier.nondetInt(); + Method m = Main.class.getMethod("check", int.class); + m.invoke(null, a); + if (a > 3) cnt++; + if (a > 5) cnt++; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/reflection-desync_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/reflection-desync_true.yml new file mode 100644 index 00000000..ddf0689f --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/reflection-desync_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - reflection-desync_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/reflection-desync_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/reflection-desync_true/Main.java new file mode 100644 index 00000000..e66464d4 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/reflection-desync_true/Main.java @@ -0,0 +1,21 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 +import java.lang.reflect.Method; +import org.sosy_lab.sv_benchmarks.Verifier; +// Method.invoke runs a method with branches unconditionally; main has a branch of its own after +// the call, which the walk may pair with the callee's branches. +public class Main { + static int cnt; + + public static void check(int x) { + if (x > 1000 && x < 1001) assert false; + } + + public static void main(String[] args) throws Exception { + int a = Verifier.nondetInt(); + Method m = Main.class.getMethod("check", int.class); + m.invoke(null, a); + if (a > 3) cnt++; + if (a > 5) cnt++; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/reflection-invoke_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/reflection-invoke_false.yml new file mode 100644 index 00000000..666a89e3 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/reflection-invoke_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - reflection-invoke_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/reflection-invoke_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/reflection-invoke_false/Main.java new file mode 100644 index 00000000..b1ab8b72 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/reflection-invoke_false/Main.java @@ -0,0 +1,19 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:7 +import java.lang.reflect.Method; +import org.sosy_lab.sv_benchmarks.Verifier; +// The asserting method is called through Method.invoke on an input-dependent path. +public class Main { + public static void check(int x) { + if (x > 1000 && x < 100000) assert false; + } + + public static void main(String[] args) throws Exception { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + Method m = Main.class.getMethod("check", int.class); + if (b == 7) { + m.invoke(null, a); + } + if (a > 3) a--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/reflection-invoke_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/reflection-invoke_true.yml new file mode 100644 index 00000000..bfec3c9e --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/reflection-invoke_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - reflection-invoke_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/reflection-invoke_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/reflection-invoke_true/Main.java new file mode 100644 index 00000000..62951a35 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/reflection-invoke_true/Main.java @@ -0,0 +1,20 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 int:7 +import java.lang.reflect.Method; +import org.sosy_lab.sv_benchmarks.Verifier; +// The asserting method is called through Method.invoke on an input-dependent path. +public class Main { + public static void check(int x) { + if (x > 1000 && x < 1001) assert false; + } + + public static void main(String[] args) throws Exception { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + Method m = Main.class.getMethod("check", int.class); + if (b == 7) { + m.invoke(null, a); + } + if (a > 3) a--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/reflection-noarg_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/reflection-noarg_false.yml new file mode 100644 index 00000000..6ca95368 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/reflection-noarg_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - reflection-noarg_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/reflection-noarg_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/reflection-noarg_false/Main.java new file mode 100644 index 00000000..d715212e --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/reflection-noarg_false/Main.java @@ -0,0 +1,23 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:7 +import java.lang.reflect.Method; +import org.sosy_lab.sv_benchmarks.Verifier; +// The asserting no-argument method is called through Method.invoke; it reads the input from a +// static field. +public class Main { + static int input; + + public static void check() { + if (input > 1000 && input < 100000) assert false; + } + + public static void main(String[] args) throws Exception { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + input = a; + Method m = Main.class.getMethod("check"); + if (b == 7) { + m.invoke(null); + } + if (a > 3) a--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/reflection-noarg_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/reflection-noarg_true.yml new file mode 100644 index 00000000..0bef64dd --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/reflection-noarg_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - reflection-noarg_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/reflection-noarg_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/reflection-noarg_true/Main.java new file mode 100644 index 00000000..8f8d4653 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/reflection-noarg_true/Main.java @@ -0,0 +1,24 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 int:7 +import java.lang.reflect.Method; +import org.sosy_lab.sv_benchmarks.Verifier; +// The asserting no-argument method is called through Method.invoke; it reads the input from a +// static field. +public class Main { + static int input; + + public static void check() { + if (input > 1000 && input < 1001) assert false; + } + + public static void main(String[] args) throws Exception { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + input = a; + Method m = Main.class.getMethod("check"); + if (b == 7) { + m.invoke(null); + } + if (a > 3) a--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/stream-foreach_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/stream-foreach_false.yml new file mode 100644 index 00000000..6eece3fd --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/stream-foreach_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - stream-foreach_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/stream-foreach_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/stream-foreach_false/Main.java new file mode 100644 index 00000000..c43ed311 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/stream-foreach_false/Main.java @@ -0,0 +1,18 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:7 +import java.util.stream.IntStream; +import org.sosy_lab.sv_benchmarks.Verifier; +// The asserting method is called back from JDK stream code through a method reference. +public class Main { + static void check(int x) { + if (x > 1000 && x < 100000) assert false; + } + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + if (b == 7) { + IntStream.of(a).forEach(Main::check); + } + if (a > 3) a--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/stream-foreach_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/stream-foreach_true.yml new file mode 100644 index 00000000..dbc6b6e2 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/stream-foreach_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - stream-foreach_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/stream-foreach_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/stream-foreach_true/Main.java new file mode 100644 index 00000000..ad13730c --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/stream-foreach_true/Main.java @@ -0,0 +1,19 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 int:7 +import java.util.stream.IntStream; +import org.sosy_lab.sv_benchmarks.Verifier; +// The asserting method is called back from JDK stream code through a method reference. +public class Main { + static void check(int x) { + if (x > 1000 && x < 1001) assert false; + } + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + if (b == 7) { + IntStream.of(a).forEach(Main::check); + } + if (a > 3) a--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/string-switch-nondet_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/string-switch-nondet_false.yml new file mode 100644 index 00000000..ef21f7ff --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/string-switch-nondet_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - string-switch-nondet_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/string-switch-nondet_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/string-switch-nondet_false/Main.java new file mode 100644 index 00000000..99e23126 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/string-switch-nondet_false/Main.java @@ -0,0 +1,20 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): string:stop int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// A switch on a nondet String selects the asserting case. +public class Main { + public static void main(String[] args) { + String cmd = Verifier.nondetString(); + int a = Verifier.nondetInt(); + int r; + switch (cmd) { + case "start": r = 1; break; + case "stop": + if (a > 1000 && a < 100000) assert false; + r = 2; + break; + case "status": r = 3; break; + default: r = 0; + } + if (r > 2) r--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/string-switch-nondet_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/string-switch-nondet_true.yml new file mode 100644 index 00000000..a0891ced --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/string-switch-nondet_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - string-switch-nondet_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/string-switch-nondet_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/string-switch-nondet_true/Main.java new file mode 100644 index 00000000..5a9ac7ae --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/string-switch-nondet_true/Main.java @@ -0,0 +1,21 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): string:stop int:1001 +import org.sosy_lab.sv_benchmarks.Verifier; +// A switch on a nondet String selects the asserting case. +public class Main { + public static void main(String[] args) { + String cmd = Verifier.nondetString(); + int a = Verifier.nondetInt(); + int r; + switch (cmd) { + case "start": r = 1; break; + case "stop": + if (a > 1000 && a < 1001) assert false; + r = 2; + break; + case "status": r = 3; break; + default: r = 0; + } + if (r > 2) r--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/string-switch_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/string-switch_false.yml new file mode 100644 index 00000000..08dcdfa3 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/string-switch_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - string-switch_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/string-switch_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/string-switch_false/Main.java new file mode 100644 index 00000000..4bab33f7 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/string-switch_false/Main.java @@ -0,0 +1,21 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// A switch on a String (hashCode lookupswitch + equals branches) selects the asserting case. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + String cmd = b == 1 ? "start" : b == 7 ? "stop" : "status"; + int r; + switch (cmd) { + case "start": r = 1; break; + case "stop": + if (a > 1000 && a < 100000) assert false; + r = 2; + break; + case "status": r = 3; break; + default: r = 0; + } + if (r > 2) r--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/string-switch_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/string-switch_true.yml new file mode 100644 index 00000000..18d1569f --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/string-switch_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - string-switch_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/string-switch_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/string-switch_true/Main.java new file mode 100644 index 00000000..d3e44742 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/string-switch_true/Main.java @@ -0,0 +1,22 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// A switch on a String (hashCode lookupswitch + equals branches) selects the asserting case. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + String cmd = b == 1 ? "start" : b == 7 ? "stop" : "status"; + int r; + switch (cmd) { + case "start": r = 1; break; + case "stop": + if (a > 1000 && a < 1001) assert false; + r = 2; + break; + case "status": r = 3; break; + default: r = 0; + } + if (r > 2) r--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/synchronized-block_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/synchronized-block_false.yml new file mode 100644 index 00000000..24d32530 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/synchronized-block_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - synchronized-block_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/synchronized-block_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/synchronized-block_false/Main.java new file mode 100644 index 00000000..6f3ea64e --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/synchronized-block_false/Main.java @@ -0,0 +1,24 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// The assert is inside a synchronized block (monitorexit exception handler) and a synchronized +// method. +public class Main { + static final Object LOCK = new Object(); + static int cnt; + + static synchronized void bump(int a) { + if (a > 3) cnt++; + } + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + bump(a); + synchronized (LOCK) { + if (b == 7) { + if (a > 1000 && a < 100000) assert false; + } + } + if (a > 5) cnt++; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/synchronized-block_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/synchronized-block_true.yml new file mode 100644 index 00000000..b2317f4b --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/synchronized-block_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - synchronized-block_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/synchronized-block_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/synchronized-block_true/Main.java new file mode 100644 index 00000000..1598fe76 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/synchronized-block_true/Main.java @@ -0,0 +1,25 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// The assert is inside a synchronized block (monitorexit exception handler) and a synchronized +// method. +public class Main { + static final Object LOCK = new Object(); + static int cnt; + + static synchronized void bump(int a) { + if (a > 3) cnt++; + } + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + bump(a); + synchronized (LOCK) { + if (b == 7) { + if (a > 1000 && a < 1001) assert false; + } + } + if (a > 5) cnt++; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/synchronized-throw_false.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/synchronized-throw_false.yml new file mode 100644 index 00000000..9f2d14d5 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/synchronized-throw_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - synchronized-throw_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/synchronized-throw_false/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/synchronized-throw_false/Main.java new file mode 100644 index 00000000..0632d104 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/synchronized-throw_false/Main.java @@ -0,0 +1,23 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// An ArithmeticException leaves a synchronized block (through its catch-any handler, which +// rethrows) and is caught outside, where the assert is. +public class Main { + static final Object LOCK = new Object(); + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int r; + try { + synchronized (LOCK) { + r = 100 / (b - 7); + if (r > 3) r--; + } + } catch (ArithmeticException e) { + if (a > 1000 && a < 100000) assert false; + r = 0; + } + if (r > 50) r--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/synchronized-throw_true.yml b/targets/sv-comp/swat-benchmarks/java/sa-soundness/synchronized-throw_true.yml new file mode 100644 index 00000000..25f14d68 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/synchronized-throw_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - synchronized-throw_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/sa-soundness/synchronized-throw_true/Main.java b/targets/sv-comp/swat-benchmarks/java/sa-soundness/synchronized-throw_true/Main.java new file mode 100644 index 00000000..30bab848 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/sa-soundness/synchronized-throw_true/Main.java @@ -0,0 +1,24 @@ +// Expected verdict: SAFE. the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable. +// Paired VIOLATION inputs (must not fail here): int:1001 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; +// An ArithmeticException leaves a synchronized block (through its catch-any handler, which +// rethrows) and is caught outside, where the assert is. +public class Main { + static final Object LOCK = new Object(); + + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int r; + try { + synchronized (LOCK) { + r = 100 / (b - 7); + if (r > 3) r--; + } + } catch (ArithmeticException e) { + if (a > 1000 && a < 1001) assert false; + r = 0; + } + if (r > 50) r--; + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n02_false.yml b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n02_false.yml new file mode 100644 index 00000000..5078e6df --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n02_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - exit_n02_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n02_false/Main.java b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n02_false/Main.java new file mode 100644 index 00000000..ccc7b4cf --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n02_false/Main.java @@ -0,0 +1,47 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:2996 int:0 int:1 +import org.sosy_lab.sv_benchmarks.Verifier; + +// validation-gauntlet/exit: 2 checks that each leave early, then the only assert. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int v0 = Verifier.nondetInt(); + int v1 = Verifier.nondetInt(); + run(a, b, v0, v1); + } + + static void run(int a, int b, int v0, int v1) { + if (v0 < 0) { report(0, v0); System.exit(1); } + if (v1 < 1) { report(1, v1); System.exit(1); } + stage1(a, b); + } + + static void stage1(int a, int b) { + stage2(a + 1, b); + } + + static void stage2(int a, int b) { + target(a - 1, b); + } + + static int errors; + + // Classifies a rejected value; three branches, no assert. + static void report(int check, int v) { + if (v < -1000) errors += 100; + if (v < -10) errors += 10; + if (v == check - 1) errors += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n02_true.yml b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n02_true.yml new file mode 100644 index 00000000..603043b3 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n02_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - exit_n02_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n02_true/Main.java b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n02_true/Main.java new file mode 100644 index 00000000..8e58cdf8 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n02_true/Main.java @@ -0,0 +1,48 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:1001 int:2996 int:0 int:1 +import org.sosy_lab.sv_benchmarks.Verifier; + +// validation-gauntlet/exit: 2 checks that each leave early, then the only assert. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int v0 = Verifier.nondetInt(); + int v1 = Verifier.nondetInt(); + run(a, b, v0, v1); + } + + static void run(int a, int b, int v0, int v1) { + if (v0 < 0) { report(0, v0); System.exit(1); } + if (v1 < 1) { report(1, v1); System.exit(1); } + stage1(a, b); + } + + static void stage1(int a, int b) { + stage2(a + 1, b); + } + + static void stage2(int a, int b) { + target(a - 1, b); + } + + static int errors; + + // Classifies a rejected value; three branches, no assert. + static void report(int check, int v) { + if (v < -1000) errors += 100; + if (v < -10) errors += 10; + if (v == check - 1) errors += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n04_false.yml b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n04_false.yml new file mode 100644 index 00000000..c4e91c38 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n04_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - exit_n04_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n04_false/Main.java b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n04_false/Main.java new file mode 100644 index 00000000..93eace69 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n04_false/Main.java @@ -0,0 +1,51 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:2996 int:0 int:1 int:2 int:3 +import org.sosy_lab.sv_benchmarks.Verifier; + +// validation-gauntlet/exit: 4 checks that each leave early, then the only assert. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int v0 = Verifier.nondetInt(); + int v1 = Verifier.nondetInt(); + int v2 = Verifier.nondetInt(); + int v3 = Verifier.nondetInt(); + run(a, b, v0, v1, v2, v3); + } + + static void run(int a, int b, int v0, int v1, int v2, int v3) { + if (v0 < 0) { report(0, v0); System.exit(1); } + if (v1 < 1) { report(1, v1); System.exit(1); } + if (v2 < 2) { report(2, v2); System.exit(1); } + if (v3 < 3) { report(3, v3); System.exit(1); } + stage1(a, b); + } + + static void stage1(int a, int b) { + stage2(a + 1, b); + } + + static void stage2(int a, int b) { + target(a - 1, b); + } + + static int errors; + + // Classifies a rejected value; three branches, no assert. + static void report(int check, int v) { + if (v < -1000) errors += 100; + if (v < -10) errors += 10; + if (v == check - 1) errors += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n04_true.yml b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n04_true.yml new file mode 100644 index 00000000..5f8c005d --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n04_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - exit_n04_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n04_true/Main.java b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n04_true/Main.java new file mode 100644 index 00000000..b5598005 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n04_true/Main.java @@ -0,0 +1,52 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:1001 int:2996 int:0 int:1 int:2 int:3 +import org.sosy_lab.sv_benchmarks.Verifier; + +// validation-gauntlet/exit: 4 checks that each leave early, then the only assert. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int v0 = Verifier.nondetInt(); + int v1 = Verifier.nondetInt(); + int v2 = Verifier.nondetInt(); + int v3 = Verifier.nondetInt(); + run(a, b, v0, v1, v2, v3); + } + + static void run(int a, int b, int v0, int v1, int v2, int v3) { + if (v0 < 0) { report(0, v0); System.exit(1); } + if (v1 < 1) { report(1, v1); System.exit(1); } + if (v2 < 2) { report(2, v2); System.exit(1); } + if (v3 < 3) { report(3, v3); System.exit(1); } + stage1(a, b); + } + + static void stage1(int a, int b) { + stage2(a + 1, b); + } + + static void stage2(int a, int b) { + target(a - 1, b); + } + + static int errors; + + // Classifies a rejected value; three branches, no assert. + static void report(int check, int v) { + if (v < -1000) errors += 100; + if (v < -10) errors += 10; + if (v == check - 1) errors += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n08_false.yml b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n08_false.yml new file mode 100644 index 00000000..37d25009 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n08_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - exit_n08_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n08_false/Main.java b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n08_false/Main.java new file mode 100644 index 00000000..a9e69975 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n08_false/Main.java @@ -0,0 +1,59 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:2996 int:0 int:1 int:2 int:3 int:4 int:5 int:6 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; + +// validation-gauntlet/exit: 8 checks that each leave early, then the only assert. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int v0 = Verifier.nondetInt(); + int v1 = Verifier.nondetInt(); + int v2 = Verifier.nondetInt(); + int v3 = Verifier.nondetInt(); + int v4 = Verifier.nondetInt(); + int v5 = Verifier.nondetInt(); + int v6 = Verifier.nondetInt(); + int v7 = Verifier.nondetInt(); + run(a, b, v0, v1, v2, v3, v4, v5, v6, v7); + } + + static void run(int a, int b, int v0, int v1, int v2, int v3, int v4, int v5, int v6, int v7) { + if (v0 < 0) { report(0, v0); System.exit(1); } + if (v1 < 1) { report(1, v1); System.exit(1); } + if (v2 < 2) { report(2, v2); System.exit(1); } + if (v3 < 3) { report(3, v3); System.exit(1); } + if (v4 < 4) { report(4, v4); System.exit(1); } + if (v5 < 5) { report(5, v5); System.exit(1); } + if (v6 < 6) { report(6, v6); System.exit(1); } + if (v7 < 7) { report(7, v7); System.exit(1); } + stage1(a, b); + } + + static void stage1(int a, int b) { + stage2(a + 1, b); + } + + static void stage2(int a, int b) { + target(a - 1, b); + } + + static int errors; + + // Classifies a rejected value; three branches, no assert. + static void report(int check, int v) { + if (v < -1000) errors += 100; + if (v < -10) errors += 10; + if (v == check - 1) errors += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n08_true.yml b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n08_true.yml new file mode 100644 index 00000000..522d64cd --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n08_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - exit_n08_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n08_true/Main.java b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n08_true/Main.java new file mode 100644 index 00000000..1a7f0fc6 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n08_true/Main.java @@ -0,0 +1,60 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:1001 int:2996 int:0 int:1 int:2 int:3 int:4 int:5 int:6 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; + +// validation-gauntlet/exit: 8 checks that each leave early, then the only assert. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int v0 = Verifier.nondetInt(); + int v1 = Verifier.nondetInt(); + int v2 = Verifier.nondetInt(); + int v3 = Verifier.nondetInt(); + int v4 = Verifier.nondetInt(); + int v5 = Verifier.nondetInt(); + int v6 = Verifier.nondetInt(); + int v7 = Verifier.nondetInt(); + run(a, b, v0, v1, v2, v3, v4, v5, v6, v7); + } + + static void run(int a, int b, int v0, int v1, int v2, int v3, int v4, int v5, int v6, int v7) { + if (v0 < 0) { report(0, v0); System.exit(1); } + if (v1 < 1) { report(1, v1); System.exit(1); } + if (v2 < 2) { report(2, v2); System.exit(1); } + if (v3 < 3) { report(3, v3); System.exit(1); } + if (v4 < 4) { report(4, v4); System.exit(1); } + if (v5 < 5) { report(5, v5); System.exit(1); } + if (v6 < 6) { report(6, v6); System.exit(1); } + if (v7 < 7) { report(7, v7); System.exit(1); } + stage1(a, b); + } + + static void stage1(int a, int b) { + stage2(a + 1, b); + } + + static void stage2(int a, int b) { + target(a - 1, b); + } + + static int errors; + + // Classifies a rejected value; three branches, no assert. + static void report(int check, int v) { + if (v < -1000) errors += 100; + if (v < -10) errors += 10; + if (v == check - 1) errors += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n16_false.yml b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n16_false.yml new file mode 100644 index 00000000..557c42fe --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n16_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - exit_n16_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n16_false/Main.java b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n16_false/Main.java new file mode 100644 index 00000000..2d27cdca --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n16_false/Main.java @@ -0,0 +1,75 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:2996 int:0 int:1 int:2 int:3 int:4 int:5 int:6 int:7 int:8 int:9 int:10 int:11 int:12 int:13 int:14 int:15 +import org.sosy_lab.sv_benchmarks.Verifier; + +// validation-gauntlet/exit: 16 checks that each leave early, then the only assert. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int v0 = Verifier.nondetInt(); + int v1 = Verifier.nondetInt(); + int v2 = Verifier.nondetInt(); + int v3 = Verifier.nondetInt(); + int v4 = Verifier.nondetInt(); + int v5 = Verifier.nondetInt(); + int v6 = Verifier.nondetInt(); + int v7 = Verifier.nondetInt(); + int v8 = Verifier.nondetInt(); + int v9 = Verifier.nondetInt(); + int v10 = Verifier.nondetInt(); + int v11 = Verifier.nondetInt(); + int v12 = Verifier.nondetInt(); + int v13 = Verifier.nondetInt(); + int v14 = Verifier.nondetInt(); + int v15 = Verifier.nondetInt(); + run(a, b, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15); + } + + static void run(int a, int b, int v0, int v1, int v2, int v3, int v4, int v5, int v6, int v7, int v8, int v9, int v10, int v11, int v12, int v13, int v14, int v15) { + if (v0 < 0) { report(0, v0); System.exit(1); } + if (v1 < 1) { report(1, v1); System.exit(1); } + if (v2 < 2) { report(2, v2); System.exit(1); } + if (v3 < 3) { report(3, v3); System.exit(1); } + if (v4 < 4) { report(4, v4); System.exit(1); } + if (v5 < 5) { report(5, v5); System.exit(1); } + if (v6 < 6) { report(6, v6); System.exit(1); } + if (v7 < 7) { report(7, v7); System.exit(1); } + if (v8 < 8) { report(8, v8); System.exit(1); } + if (v9 < 9) { report(9, v9); System.exit(1); } + if (v10 < 10) { report(10, v10); System.exit(1); } + if (v11 < 11) { report(11, v11); System.exit(1); } + if (v12 < 12) { report(12, v12); System.exit(1); } + if (v13 < 13) { report(13, v13); System.exit(1); } + if (v14 < 14) { report(14, v14); System.exit(1); } + if (v15 < 15) { report(15, v15); System.exit(1); } + stage1(a, b); + } + + static void stage1(int a, int b) { + stage2(a + 1, b); + } + + static void stage2(int a, int b) { + target(a - 1, b); + } + + static int errors; + + // Classifies a rejected value; three branches, no assert. + static void report(int check, int v) { + if (v < -1000) errors += 100; + if (v < -10) errors += 10; + if (v == check - 1) errors += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n16_true.yml b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n16_true.yml new file mode 100644 index 00000000..54237567 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n16_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - exit_n16_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n16_true/Main.java b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n16_true/Main.java new file mode 100644 index 00000000..35e58eb9 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n16_true/Main.java @@ -0,0 +1,76 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:1001 int:2996 int:0 int:1 int:2 int:3 int:4 int:5 int:6 int:7 int:8 int:9 int:10 int:11 int:12 int:13 int:14 int:15 +import org.sosy_lab.sv_benchmarks.Verifier; + +// validation-gauntlet/exit: 16 checks that each leave early, then the only assert. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int v0 = Verifier.nondetInt(); + int v1 = Verifier.nondetInt(); + int v2 = Verifier.nondetInt(); + int v3 = Verifier.nondetInt(); + int v4 = Verifier.nondetInt(); + int v5 = Verifier.nondetInt(); + int v6 = Verifier.nondetInt(); + int v7 = Verifier.nondetInt(); + int v8 = Verifier.nondetInt(); + int v9 = Verifier.nondetInt(); + int v10 = Verifier.nondetInt(); + int v11 = Verifier.nondetInt(); + int v12 = Verifier.nondetInt(); + int v13 = Verifier.nondetInt(); + int v14 = Verifier.nondetInt(); + int v15 = Verifier.nondetInt(); + run(a, b, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15); + } + + static void run(int a, int b, int v0, int v1, int v2, int v3, int v4, int v5, int v6, int v7, int v8, int v9, int v10, int v11, int v12, int v13, int v14, int v15) { + if (v0 < 0) { report(0, v0); System.exit(1); } + if (v1 < 1) { report(1, v1); System.exit(1); } + if (v2 < 2) { report(2, v2); System.exit(1); } + if (v3 < 3) { report(3, v3); System.exit(1); } + if (v4 < 4) { report(4, v4); System.exit(1); } + if (v5 < 5) { report(5, v5); System.exit(1); } + if (v6 < 6) { report(6, v6); System.exit(1); } + if (v7 < 7) { report(7, v7); System.exit(1); } + if (v8 < 8) { report(8, v8); System.exit(1); } + if (v9 < 9) { report(9, v9); System.exit(1); } + if (v10 < 10) { report(10, v10); System.exit(1); } + if (v11 < 11) { report(11, v11); System.exit(1); } + if (v12 < 12) { report(12, v12); System.exit(1); } + if (v13 < 13) { report(13, v13); System.exit(1); } + if (v14 < 14) { report(14, v14); System.exit(1); } + if (v15 < 15) { report(15, v15); System.exit(1); } + stage1(a, b); + } + + static void stage1(int a, int b) { + stage2(a + 1, b); + } + + static void stage2(int a, int b) { + target(a - 1, b); + } + + static int errors; + + // Classifies a rejected value; three branches, no assert. + static void report(int check, int v) { + if (v < -1000) errors += 100; + if (v < -10) errors += 10; + if (v == check - 1) errors += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n32_false.yml b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n32_false.yml new file mode 100644 index 00000000..3005aa11 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n32_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - exit_n32_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n32_false/Main.java b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n32_false/Main.java new file mode 100644 index 00000000..60580733 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n32_false/Main.java @@ -0,0 +1,107 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:2996 int:0 int:1 int:2 int:3 int:4 int:5 int:6 int:7 int:8 int:9 int:10 int:11 int:12 int:13 int:14 int:15 int:16 int:17 int:18 int:19 int:20 int:21 int:22 int:23 int:24 int:25 int:26 int:27 int:28 int:29 int:30 int:31 +import org.sosy_lab.sv_benchmarks.Verifier; + +// validation-gauntlet/exit: 32 checks that each leave early, then the only assert. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int v0 = Verifier.nondetInt(); + int v1 = Verifier.nondetInt(); + int v2 = Verifier.nondetInt(); + int v3 = Verifier.nondetInt(); + int v4 = Verifier.nondetInt(); + int v5 = Verifier.nondetInt(); + int v6 = Verifier.nondetInt(); + int v7 = Verifier.nondetInt(); + int v8 = Verifier.nondetInt(); + int v9 = Verifier.nondetInt(); + int v10 = Verifier.nondetInt(); + int v11 = Verifier.nondetInt(); + int v12 = Verifier.nondetInt(); + int v13 = Verifier.nondetInt(); + int v14 = Verifier.nondetInt(); + int v15 = Verifier.nondetInt(); + int v16 = Verifier.nondetInt(); + int v17 = Verifier.nondetInt(); + int v18 = Verifier.nondetInt(); + int v19 = Verifier.nondetInt(); + int v20 = Verifier.nondetInt(); + int v21 = Verifier.nondetInt(); + int v22 = Verifier.nondetInt(); + int v23 = Verifier.nondetInt(); + int v24 = Verifier.nondetInt(); + int v25 = Verifier.nondetInt(); + int v26 = Verifier.nondetInt(); + int v27 = Verifier.nondetInt(); + int v28 = Verifier.nondetInt(); + int v29 = Verifier.nondetInt(); + int v30 = Verifier.nondetInt(); + int v31 = Verifier.nondetInt(); + run(a, b, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31); + } + + static void run(int a, int b, int v0, int v1, int v2, int v3, int v4, int v5, int v6, int v7, int v8, int v9, int v10, int v11, int v12, int v13, int v14, int v15, int v16, int v17, int v18, int v19, int v20, int v21, int v22, int v23, int v24, int v25, int v26, int v27, int v28, int v29, int v30, int v31) { + if (v0 < 0) { report(0, v0); System.exit(1); } + if (v1 < 1) { report(1, v1); System.exit(1); } + if (v2 < 2) { report(2, v2); System.exit(1); } + if (v3 < 3) { report(3, v3); System.exit(1); } + if (v4 < 4) { report(4, v4); System.exit(1); } + if (v5 < 5) { report(5, v5); System.exit(1); } + if (v6 < 6) { report(6, v6); System.exit(1); } + if (v7 < 7) { report(7, v7); System.exit(1); } + if (v8 < 8) { report(8, v8); System.exit(1); } + if (v9 < 9) { report(9, v9); System.exit(1); } + if (v10 < 10) { report(10, v10); System.exit(1); } + if (v11 < 11) { report(11, v11); System.exit(1); } + if (v12 < 12) { report(12, v12); System.exit(1); } + if (v13 < 13) { report(13, v13); System.exit(1); } + if (v14 < 14) { report(14, v14); System.exit(1); } + if (v15 < 15) { report(15, v15); System.exit(1); } + if (v16 < 16) { report(16, v16); System.exit(1); } + if (v17 < 17) { report(17, v17); System.exit(1); } + if (v18 < 18) { report(18, v18); System.exit(1); } + if (v19 < 19) { report(19, v19); System.exit(1); } + if (v20 < 20) { report(20, v20); System.exit(1); } + if (v21 < 21) { report(21, v21); System.exit(1); } + if (v22 < 22) { report(22, v22); System.exit(1); } + if (v23 < 23) { report(23, v23); System.exit(1); } + if (v24 < 24) { report(24, v24); System.exit(1); } + if (v25 < 25) { report(25, v25); System.exit(1); } + if (v26 < 26) { report(26, v26); System.exit(1); } + if (v27 < 27) { report(27, v27); System.exit(1); } + if (v28 < 28) { report(28, v28); System.exit(1); } + if (v29 < 29) { report(29, v29); System.exit(1); } + if (v30 < 30) { report(30, v30); System.exit(1); } + if (v31 < 31) { report(31, v31); System.exit(1); } + stage1(a, b); + } + + static void stage1(int a, int b) { + stage2(a + 1, b); + } + + static void stage2(int a, int b) { + target(a - 1, b); + } + + static int errors; + + // Classifies a rejected value; three branches, no assert. + static void report(int check, int v) { + if (v < -1000) errors += 100; + if (v < -10) errors += 10; + if (v == check - 1) errors += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n32_true.yml b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n32_true.yml new file mode 100644 index 00000000..3117e8d8 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n32_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - exit_n32_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n32_true/Main.java b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n32_true/Main.java new file mode 100644 index 00000000..19ed9ddc --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/exit_n32_true/Main.java @@ -0,0 +1,108 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:1001 int:2996 int:0 int:1 int:2 int:3 int:4 int:5 int:6 int:7 int:8 int:9 int:10 int:11 int:12 int:13 int:14 int:15 int:16 int:17 int:18 int:19 int:20 int:21 int:22 int:23 int:24 int:25 int:26 int:27 int:28 int:29 int:30 int:31 +import org.sosy_lab.sv_benchmarks.Verifier; + +// validation-gauntlet/exit: 32 checks that each leave early, then the only assert. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int v0 = Verifier.nondetInt(); + int v1 = Verifier.nondetInt(); + int v2 = Verifier.nondetInt(); + int v3 = Verifier.nondetInt(); + int v4 = Verifier.nondetInt(); + int v5 = Verifier.nondetInt(); + int v6 = Verifier.nondetInt(); + int v7 = Verifier.nondetInt(); + int v8 = Verifier.nondetInt(); + int v9 = Verifier.nondetInt(); + int v10 = Verifier.nondetInt(); + int v11 = Verifier.nondetInt(); + int v12 = Verifier.nondetInt(); + int v13 = Verifier.nondetInt(); + int v14 = Verifier.nondetInt(); + int v15 = Verifier.nondetInt(); + int v16 = Verifier.nondetInt(); + int v17 = Verifier.nondetInt(); + int v18 = Verifier.nondetInt(); + int v19 = Verifier.nondetInt(); + int v20 = Verifier.nondetInt(); + int v21 = Verifier.nondetInt(); + int v22 = Verifier.nondetInt(); + int v23 = Verifier.nondetInt(); + int v24 = Verifier.nondetInt(); + int v25 = Verifier.nondetInt(); + int v26 = Verifier.nondetInt(); + int v27 = Verifier.nondetInt(); + int v28 = Verifier.nondetInt(); + int v29 = Verifier.nondetInt(); + int v30 = Verifier.nondetInt(); + int v31 = Verifier.nondetInt(); + run(a, b, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31); + } + + static void run(int a, int b, int v0, int v1, int v2, int v3, int v4, int v5, int v6, int v7, int v8, int v9, int v10, int v11, int v12, int v13, int v14, int v15, int v16, int v17, int v18, int v19, int v20, int v21, int v22, int v23, int v24, int v25, int v26, int v27, int v28, int v29, int v30, int v31) { + if (v0 < 0) { report(0, v0); System.exit(1); } + if (v1 < 1) { report(1, v1); System.exit(1); } + if (v2 < 2) { report(2, v2); System.exit(1); } + if (v3 < 3) { report(3, v3); System.exit(1); } + if (v4 < 4) { report(4, v4); System.exit(1); } + if (v5 < 5) { report(5, v5); System.exit(1); } + if (v6 < 6) { report(6, v6); System.exit(1); } + if (v7 < 7) { report(7, v7); System.exit(1); } + if (v8 < 8) { report(8, v8); System.exit(1); } + if (v9 < 9) { report(9, v9); System.exit(1); } + if (v10 < 10) { report(10, v10); System.exit(1); } + if (v11 < 11) { report(11, v11); System.exit(1); } + if (v12 < 12) { report(12, v12); System.exit(1); } + if (v13 < 13) { report(13, v13); System.exit(1); } + if (v14 < 14) { report(14, v14); System.exit(1); } + if (v15 < 15) { report(15, v15); System.exit(1); } + if (v16 < 16) { report(16, v16); System.exit(1); } + if (v17 < 17) { report(17, v17); System.exit(1); } + if (v18 < 18) { report(18, v18); System.exit(1); } + if (v19 < 19) { report(19, v19); System.exit(1); } + if (v20 < 20) { report(20, v20); System.exit(1); } + if (v21 < 21) { report(21, v21); System.exit(1); } + if (v22 < 22) { report(22, v22); System.exit(1); } + if (v23 < 23) { report(23, v23); System.exit(1); } + if (v24 < 24) { report(24, v24); System.exit(1); } + if (v25 < 25) { report(25, v25); System.exit(1); } + if (v26 < 26) { report(26, v26); System.exit(1); } + if (v27 < 27) { report(27, v27); System.exit(1); } + if (v28 < 28) { report(28, v28); System.exit(1); } + if (v29 < 29) { report(29, v29); System.exit(1); } + if (v30 < 30) { report(30, v30); System.exit(1); } + if (v31 < 31) { report(31, v31); System.exit(1); } + stage1(a, b); + } + + static void stage1(int a, int b) { + stage2(a + 1, b); + } + + static void stage2(int a, int b) { + target(a - 1, b); + } + + static int errors; + + // Classifies a rejected value; three branches, no assert. + static void report(int check, int v) { + if (v < -1000) errors += 100; + if (v < -10) errors += 10; + if (v == check - 1) errors += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n02_false.yml b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n02_false.yml new file mode 100644 index 00000000..f1cf6a30 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n02_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - report_n02_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n02_false/Main.java b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n02_false/Main.java new file mode 100644 index 00000000..b148c92b --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n02_false/Main.java @@ -0,0 +1,47 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:2996 int:0 int:1 +import org.sosy_lab.sv_benchmarks.Verifier; + +// validation-gauntlet/report: 2 checks that each leave early, then the only assert. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int v0 = Verifier.nondetInt(); + int v1 = Verifier.nondetInt(); + run(a, b, v0, v1); + } + + static void run(int a, int b, int v0, int v1) { + if (v0 < 0) { report(0, v0); return; } + if (v1 < 1) { report(1, v1); return; } + stage1(a, b); + } + + static void stage1(int a, int b) { + stage2(a + 1, b); + } + + static void stage2(int a, int b) { + target(a - 1, b); + } + + static int errors; + + // Classifies a rejected value; three branches, no assert. + static void report(int check, int v) { + if (v < -1000) errors += 100; + if (v < -10) errors += 10; + if (v == check - 1) errors += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n02_true.yml b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n02_true.yml new file mode 100644 index 00000000..27e90228 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n02_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - report_n02_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n02_true/Main.java b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n02_true/Main.java new file mode 100644 index 00000000..39861ad8 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n02_true/Main.java @@ -0,0 +1,48 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:1001 int:2996 int:0 int:1 +import org.sosy_lab.sv_benchmarks.Verifier; + +// validation-gauntlet/report: 2 checks that each leave early, then the only assert. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int v0 = Verifier.nondetInt(); + int v1 = Verifier.nondetInt(); + run(a, b, v0, v1); + } + + static void run(int a, int b, int v0, int v1) { + if (v0 < 0) { report(0, v0); return; } + if (v1 < 1) { report(1, v1); return; } + stage1(a, b); + } + + static void stage1(int a, int b) { + stage2(a + 1, b); + } + + static void stage2(int a, int b) { + target(a - 1, b); + } + + static int errors; + + // Classifies a rejected value; three branches, no assert. + static void report(int check, int v) { + if (v < -1000) errors += 100; + if (v < -10) errors += 10; + if (v == check - 1) errors += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n04_false.yml b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n04_false.yml new file mode 100644 index 00000000..ac5258fa --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n04_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - report_n04_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n04_false/Main.java b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n04_false/Main.java new file mode 100644 index 00000000..3d6c66b9 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n04_false/Main.java @@ -0,0 +1,51 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:2996 int:0 int:1 int:2 int:3 +import org.sosy_lab.sv_benchmarks.Verifier; + +// validation-gauntlet/report: 4 checks that each leave early, then the only assert. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int v0 = Verifier.nondetInt(); + int v1 = Verifier.nondetInt(); + int v2 = Verifier.nondetInt(); + int v3 = Verifier.nondetInt(); + run(a, b, v0, v1, v2, v3); + } + + static void run(int a, int b, int v0, int v1, int v2, int v3) { + if (v0 < 0) { report(0, v0); return; } + if (v1 < 1) { report(1, v1); return; } + if (v2 < 2) { report(2, v2); return; } + if (v3 < 3) { report(3, v3); return; } + stage1(a, b); + } + + static void stage1(int a, int b) { + stage2(a + 1, b); + } + + static void stage2(int a, int b) { + target(a - 1, b); + } + + static int errors; + + // Classifies a rejected value; three branches, no assert. + static void report(int check, int v) { + if (v < -1000) errors += 100; + if (v < -10) errors += 10; + if (v == check - 1) errors += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n04_true.yml b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n04_true.yml new file mode 100644 index 00000000..0ec6a2d7 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n04_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - report_n04_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n04_true/Main.java b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n04_true/Main.java new file mode 100644 index 00000000..d3a1e994 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n04_true/Main.java @@ -0,0 +1,52 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:1001 int:2996 int:0 int:1 int:2 int:3 +import org.sosy_lab.sv_benchmarks.Verifier; + +// validation-gauntlet/report: 4 checks that each leave early, then the only assert. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int v0 = Verifier.nondetInt(); + int v1 = Verifier.nondetInt(); + int v2 = Verifier.nondetInt(); + int v3 = Verifier.nondetInt(); + run(a, b, v0, v1, v2, v3); + } + + static void run(int a, int b, int v0, int v1, int v2, int v3) { + if (v0 < 0) { report(0, v0); return; } + if (v1 < 1) { report(1, v1); return; } + if (v2 < 2) { report(2, v2); return; } + if (v3 < 3) { report(3, v3); return; } + stage1(a, b); + } + + static void stage1(int a, int b) { + stage2(a + 1, b); + } + + static void stage2(int a, int b) { + target(a - 1, b); + } + + static int errors; + + // Classifies a rejected value; three branches, no assert. + static void report(int check, int v) { + if (v < -1000) errors += 100; + if (v < -10) errors += 10; + if (v == check - 1) errors += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n08_false.yml b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n08_false.yml new file mode 100644 index 00000000..ed9565e7 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n08_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - report_n08_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n08_false/Main.java b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n08_false/Main.java new file mode 100644 index 00000000..e15b2bac --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n08_false/Main.java @@ -0,0 +1,59 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:2996 int:0 int:1 int:2 int:3 int:4 int:5 int:6 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; + +// validation-gauntlet/report: 8 checks that each leave early, then the only assert. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int v0 = Verifier.nondetInt(); + int v1 = Verifier.nondetInt(); + int v2 = Verifier.nondetInt(); + int v3 = Verifier.nondetInt(); + int v4 = Verifier.nondetInt(); + int v5 = Verifier.nondetInt(); + int v6 = Verifier.nondetInt(); + int v7 = Verifier.nondetInt(); + run(a, b, v0, v1, v2, v3, v4, v5, v6, v7); + } + + static void run(int a, int b, int v0, int v1, int v2, int v3, int v4, int v5, int v6, int v7) { + if (v0 < 0) { report(0, v0); return; } + if (v1 < 1) { report(1, v1); return; } + if (v2 < 2) { report(2, v2); return; } + if (v3 < 3) { report(3, v3); return; } + if (v4 < 4) { report(4, v4); return; } + if (v5 < 5) { report(5, v5); return; } + if (v6 < 6) { report(6, v6); return; } + if (v7 < 7) { report(7, v7); return; } + stage1(a, b); + } + + static void stage1(int a, int b) { + stage2(a + 1, b); + } + + static void stage2(int a, int b) { + target(a - 1, b); + } + + static int errors; + + // Classifies a rejected value; three branches, no assert. + static void report(int check, int v) { + if (v < -1000) errors += 100; + if (v < -10) errors += 10; + if (v == check - 1) errors += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n08_true.yml b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n08_true.yml new file mode 100644 index 00000000..d165056a --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n08_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - report_n08_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n08_true/Main.java b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n08_true/Main.java new file mode 100644 index 00000000..957d9bbc --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n08_true/Main.java @@ -0,0 +1,60 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:1001 int:2996 int:0 int:1 int:2 int:3 int:4 int:5 int:6 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; + +// validation-gauntlet/report: 8 checks that each leave early, then the only assert. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int v0 = Verifier.nondetInt(); + int v1 = Verifier.nondetInt(); + int v2 = Verifier.nondetInt(); + int v3 = Verifier.nondetInt(); + int v4 = Verifier.nondetInt(); + int v5 = Verifier.nondetInt(); + int v6 = Verifier.nondetInt(); + int v7 = Verifier.nondetInt(); + run(a, b, v0, v1, v2, v3, v4, v5, v6, v7); + } + + static void run(int a, int b, int v0, int v1, int v2, int v3, int v4, int v5, int v6, int v7) { + if (v0 < 0) { report(0, v0); return; } + if (v1 < 1) { report(1, v1); return; } + if (v2 < 2) { report(2, v2); return; } + if (v3 < 3) { report(3, v3); return; } + if (v4 < 4) { report(4, v4); return; } + if (v5 < 5) { report(5, v5); return; } + if (v6 < 6) { report(6, v6); return; } + if (v7 < 7) { report(7, v7); return; } + stage1(a, b); + } + + static void stage1(int a, int b) { + stage2(a + 1, b); + } + + static void stage2(int a, int b) { + target(a - 1, b); + } + + static int errors; + + // Classifies a rejected value; three branches, no assert. + static void report(int check, int v) { + if (v < -1000) errors += 100; + if (v < -10) errors += 10; + if (v == check - 1) errors += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n16_false.yml b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n16_false.yml new file mode 100644 index 00000000..78f0af40 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n16_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - report_n16_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n16_false/Main.java b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n16_false/Main.java new file mode 100644 index 00000000..976ad2e0 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n16_false/Main.java @@ -0,0 +1,75 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:2996 int:0 int:1 int:2 int:3 int:4 int:5 int:6 int:7 int:8 int:9 int:10 int:11 int:12 int:13 int:14 int:15 +import org.sosy_lab.sv_benchmarks.Verifier; + +// validation-gauntlet/report: 16 checks that each leave early, then the only assert. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int v0 = Verifier.nondetInt(); + int v1 = Verifier.nondetInt(); + int v2 = Verifier.nondetInt(); + int v3 = Verifier.nondetInt(); + int v4 = Verifier.nondetInt(); + int v5 = Verifier.nondetInt(); + int v6 = Verifier.nondetInt(); + int v7 = Verifier.nondetInt(); + int v8 = Verifier.nondetInt(); + int v9 = Verifier.nondetInt(); + int v10 = Verifier.nondetInt(); + int v11 = Verifier.nondetInt(); + int v12 = Verifier.nondetInt(); + int v13 = Verifier.nondetInt(); + int v14 = Verifier.nondetInt(); + int v15 = Verifier.nondetInt(); + run(a, b, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15); + } + + static void run(int a, int b, int v0, int v1, int v2, int v3, int v4, int v5, int v6, int v7, int v8, int v9, int v10, int v11, int v12, int v13, int v14, int v15) { + if (v0 < 0) { report(0, v0); return; } + if (v1 < 1) { report(1, v1); return; } + if (v2 < 2) { report(2, v2); return; } + if (v3 < 3) { report(3, v3); return; } + if (v4 < 4) { report(4, v4); return; } + if (v5 < 5) { report(5, v5); return; } + if (v6 < 6) { report(6, v6); return; } + if (v7 < 7) { report(7, v7); return; } + if (v8 < 8) { report(8, v8); return; } + if (v9 < 9) { report(9, v9); return; } + if (v10 < 10) { report(10, v10); return; } + if (v11 < 11) { report(11, v11); return; } + if (v12 < 12) { report(12, v12); return; } + if (v13 < 13) { report(13, v13); return; } + if (v14 < 14) { report(14, v14); return; } + if (v15 < 15) { report(15, v15); return; } + stage1(a, b); + } + + static void stage1(int a, int b) { + stage2(a + 1, b); + } + + static void stage2(int a, int b) { + target(a - 1, b); + } + + static int errors; + + // Classifies a rejected value; three branches, no assert. + static void report(int check, int v) { + if (v < -1000) errors += 100; + if (v < -10) errors += 10; + if (v == check - 1) errors += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n16_true.yml b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n16_true.yml new file mode 100644 index 00000000..f7ef519e --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n16_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - report_n16_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n16_true/Main.java b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n16_true/Main.java new file mode 100644 index 00000000..40436c8d --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n16_true/Main.java @@ -0,0 +1,76 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:1001 int:2996 int:0 int:1 int:2 int:3 int:4 int:5 int:6 int:7 int:8 int:9 int:10 int:11 int:12 int:13 int:14 int:15 +import org.sosy_lab.sv_benchmarks.Verifier; + +// validation-gauntlet/report: 16 checks that each leave early, then the only assert. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int v0 = Verifier.nondetInt(); + int v1 = Verifier.nondetInt(); + int v2 = Verifier.nondetInt(); + int v3 = Verifier.nondetInt(); + int v4 = Verifier.nondetInt(); + int v5 = Verifier.nondetInt(); + int v6 = Verifier.nondetInt(); + int v7 = Verifier.nondetInt(); + int v8 = Verifier.nondetInt(); + int v9 = Verifier.nondetInt(); + int v10 = Verifier.nondetInt(); + int v11 = Verifier.nondetInt(); + int v12 = Verifier.nondetInt(); + int v13 = Verifier.nondetInt(); + int v14 = Verifier.nondetInt(); + int v15 = Verifier.nondetInt(); + run(a, b, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15); + } + + static void run(int a, int b, int v0, int v1, int v2, int v3, int v4, int v5, int v6, int v7, int v8, int v9, int v10, int v11, int v12, int v13, int v14, int v15) { + if (v0 < 0) { report(0, v0); return; } + if (v1 < 1) { report(1, v1); return; } + if (v2 < 2) { report(2, v2); return; } + if (v3 < 3) { report(3, v3); return; } + if (v4 < 4) { report(4, v4); return; } + if (v5 < 5) { report(5, v5); return; } + if (v6 < 6) { report(6, v6); return; } + if (v7 < 7) { report(7, v7); return; } + if (v8 < 8) { report(8, v8); return; } + if (v9 < 9) { report(9, v9); return; } + if (v10 < 10) { report(10, v10); return; } + if (v11 < 11) { report(11, v11); return; } + if (v12 < 12) { report(12, v12); return; } + if (v13 < 13) { report(13, v13); return; } + if (v14 < 14) { report(14, v14); return; } + if (v15 < 15) { report(15, v15); return; } + stage1(a, b); + } + + static void stage1(int a, int b) { + stage2(a + 1, b); + } + + static void stage2(int a, int b) { + target(a - 1, b); + } + + static int errors; + + // Classifies a rejected value; three branches, no assert. + static void report(int check, int v) { + if (v < -1000) errors += 100; + if (v < -10) errors += 10; + if (v == check - 1) errors += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n32_false.yml b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n32_false.yml new file mode 100644 index 00000000..49affaa0 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n32_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - report_n32_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n32_false/Main.java b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n32_false/Main.java new file mode 100644 index 00000000..f1da4d26 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n32_false/Main.java @@ -0,0 +1,107 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:2996 int:0 int:1 int:2 int:3 int:4 int:5 int:6 int:7 int:8 int:9 int:10 int:11 int:12 int:13 int:14 int:15 int:16 int:17 int:18 int:19 int:20 int:21 int:22 int:23 int:24 int:25 int:26 int:27 int:28 int:29 int:30 int:31 +import org.sosy_lab.sv_benchmarks.Verifier; + +// validation-gauntlet/report: 32 checks that each leave early, then the only assert. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int v0 = Verifier.nondetInt(); + int v1 = Verifier.nondetInt(); + int v2 = Verifier.nondetInt(); + int v3 = Verifier.nondetInt(); + int v4 = Verifier.nondetInt(); + int v5 = Verifier.nondetInt(); + int v6 = Verifier.nondetInt(); + int v7 = Verifier.nondetInt(); + int v8 = Verifier.nondetInt(); + int v9 = Verifier.nondetInt(); + int v10 = Verifier.nondetInt(); + int v11 = Verifier.nondetInt(); + int v12 = Verifier.nondetInt(); + int v13 = Verifier.nondetInt(); + int v14 = Verifier.nondetInt(); + int v15 = Verifier.nondetInt(); + int v16 = Verifier.nondetInt(); + int v17 = Verifier.nondetInt(); + int v18 = Verifier.nondetInt(); + int v19 = Verifier.nondetInt(); + int v20 = Verifier.nondetInt(); + int v21 = Verifier.nondetInt(); + int v22 = Verifier.nondetInt(); + int v23 = Verifier.nondetInt(); + int v24 = Verifier.nondetInt(); + int v25 = Verifier.nondetInt(); + int v26 = Verifier.nondetInt(); + int v27 = Verifier.nondetInt(); + int v28 = Verifier.nondetInt(); + int v29 = Verifier.nondetInt(); + int v30 = Verifier.nondetInt(); + int v31 = Verifier.nondetInt(); + run(a, b, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31); + } + + static void run(int a, int b, int v0, int v1, int v2, int v3, int v4, int v5, int v6, int v7, int v8, int v9, int v10, int v11, int v12, int v13, int v14, int v15, int v16, int v17, int v18, int v19, int v20, int v21, int v22, int v23, int v24, int v25, int v26, int v27, int v28, int v29, int v30, int v31) { + if (v0 < 0) { report(0, v0); return; } + if (v1 < 1) { report(1, v1); return; } + if (v2 < 2) { report(2, v2); return; } + if (v3 < 3) { report(3, v3); return; } + if (v4 < 4) { report(4, v4); return; } + if (v5 < 5) { report(5, v5); return; } + if (v6 < 6) { report(6, v6); return; } + if (v7 < 7) { report(7, v7); return; } + if (v8 < 8) { report(8, v8); return; } + if (v9 < 9) { report(9, v9); return; } + if (v10 < 10) { report(10, v10); return; } + if (v11 < 11) { report(11, v11); return; } + if (v12 < 12) { report(12, v12); return; } + if (v13 < 13) { report(13, v13); return; } + if (v14 < 14) { report(14, v14); return; } + if (v15 < 15) { report(15, v15); return; } + if (v16 < 16) { report(16, v16); return; } + if (v17 < 17) { report(17, v17); return; } + if (v18 < 18) { report(18, v18); return; } + if (v19 < 19) { report(19, v19); return; } + if (v20 < 20) { report(20, v20); return; } + if (v21 < 21) { report(21, v21); return; } + if (v22 < 22) { report(22, v22); return; } + if (v23 < 23) { report(23, v23); return; } + if (v24 < 24) { report(24, v24); return; } + if (v25 < 25) { report(25, v25); return; } + if (v26 < 26) { report(26, v26); return; } + if (v27 < 27) { report(27, v27); return; } + if (v28 < 28) { report(28, v28); return; } + if (v29 < 29) { report(29, v29); return; } + if (v30 < 30) { report(30, v30); return; } + if (v31 < 31) { report(31, v31); return; } + stage1(a, b); + } + + static void stage1(int a, int b) { + stage2(a + 1, b); + } + + static void stage2(int a, int b) { + target(a - 1, b); + } + + static int errors; + + // Classifies a rejected value; three branches, no assert. + static void report(int check, int v) { + if (v < -1000) errors += 100; + if (v < -10) errors += 10; + if (v == check - 1) errors += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n32_true.yml b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n32_true.yml new file mode 100644 index 00000000..8d182159 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n32_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - report_n32_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n32_true/Main.java b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n32_true/Main.java new file mode 100644 index 00000000..2cc23cbc --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/report_n32_true/Main.java @@ -0,0 +1,108 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:1001 int:2996 int:0 int:1 int:2 int:3 int:4 int:5 int:6 int:7 int:8 int:9 int:10 int:11 int:12 int:13 int:14 int:15 int:16 int:17 int:18 int:19 int:20 int:21 int:22 int:23 int:24 int:25 int:26 int:27 int:28 int:29 int:30 int:31 +import org.sosy_lab.sv_benchmarks.Verifier; + +// validation-gauntlet/report: 32 checks that each leave early, then the only assert. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int v0 = Verifier.nondetInt(); + int v1 = Verifier.nondetInt(); + int v2 = Verifier.nondetInt(); + int v3 = Verifier.nondetInt(); + int v4 = Verifier.nondetInt(); + int v5 = Verifier.nondetInt(); + int v6 = Verifier.nondetInt(); + int v7 = Verifier.nondetInt(); + int v8 = Verifier.nondetInt(); + int v9 = Verifier.nondetInt(); + int v10 = Verifier.nondetInt(); + int v11 = Verifier.nondetInt(); + int v12 = Verifier.nondetInt(); + int v13 = Verifier.nondetInt(); + int v14 = Verifier.nondetInt(); + int v15 = Verifier.nondetInt(); + int v16 = Verifier.nondetInt(); + int v17 = Verifier.nondetInt(); + int v18 = Verifier.nondetInt(); + int v19 = Verifier.nondetInt(); + int v20 = Verifier.nondetInt(); + int v21 = Verifier.nondetInt(); + int v22 = Verifier.nondetInt(); + int v23 = Verifier.nondetInt(); + int v24 = Verifier.nondetInt(); + int v25 = Verifier.nondetInt(); + int v26 = Verifier.nondetInt(); + int v27 = Verifier.nondetInt(); + int v28 = Verifier.nondetInt(); + int v29 = Verifier.nondetInt(); + int v30 = Verifier.nondetInt(); + int v31 = Verifier.nondetInt(); + run(a, b, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31); + } + + static void run(int a, int b, int v0, int v1, int v2, int v3, int v4, int v5, int v6, int v7, int v8, int v9, int v10, int v11, int v12, int v13, int v14, int v15, int v16, int v17, int v18, int v19, int v20, int v21, int v22, int v23, int v24, int v25, int v26, int v27, int v28, int v29, int v30, int v31) { + if (v0 < 0) { report(0, v0); return; } + if (v1 < 1) { report(1, v1); return; } + if (v2 < 2) { report(2, v2); return; } + if (v3 < 3) { report(3, v3); return; } + if (v4 < 4) { report(4, v4); return; } + if (v5 < 5) { report(5, v5); return; } + if (v6 < 6) { report(6, v6); return; } + if (v7 < 7) { report(7, v7); return; } + if (v8 < 8) { report(8, v8); return; } + if (v9 < 9) { report(9, v9); return; } + if (v10 < 10) { report(10, v10); return; } + if (v11 < 11) { report(11, v11); return; } + if (v12 < 12) { report(12, v12); return; } + if (v13 < 13) { report(13, v13); return; } + if (v14 < 14) { report(14, v14); return; } + if (v15 < 15) { report(15, v15); return; } + if (v16 < 16) { report(16, v16); return; } + if (v17 < 17) { report(17, v17); return; } + if (v18 < 18) { report(18, v18); return; } + if (v19 < 19) { report(19, v19); return; } + if (v20 < 20) { report(20, v20); return; } + if (v21 < 21) { report(21, v21); return; } + if (v22 < 22) { report(22, v22); return; } + if (v23 < 23) { report(23, v23); return; } + if (v24 < 24) { report(24, v24); return; } + if (v25 < 25) { report(25, v25); return; } + if (v26 < 26) { report(26, v26); return; } + if (v27 < 27) { report(27, v27); return; } + if (v28 < 28) { report(28, v28); return; } + if (v29 < 29) { report(29, v29); return; } + if (v30 < 30) { report(30, v30); return; } + if (v31 < 31) { report(31, v31); return; } + stage1(a, b); + } + + static void stage1(int a, int b) { + stage2(a + 1, b); + } + + static void stage2(int a, int b) { + target(a - 1, b); + } + + static int errors; + + // Classifies a rejected value; three branches, no assert. + static void report(int check, int v) { + if (v < -1000) errors += 100; + if (v < -10) errors += 10; + if (v == check - 1) errors += 1; + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n02_false.yml b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n02_false.yml new file mode 100644 index 00000000..d4af981b --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n02_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - return_n02_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n02_false/Main.java b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n02_false/Main.java new file mode 100644 index 00000000..18ffeebf --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n02_false/Main.java @@ -0,0 +1,39 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:2996 int:0 int:1 +import org.sosy_lab.sv_benchmarks.Verifier; + +// validation-gauntlet/return: 2 checks that each leave early, then the only assert. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int v0 = Verifier.nondetInt(); + int v1 = Verifier.nondetInt(); + run(a, b, v0, v1); + } + + static void run(int a, int b, int v0, int v1) { + if (v0 < 0) return; + if (v1 < 1) return; + stage1(a, b); + } + + static void stage1(int a, int b) { + stage2(a + 1, b); + } + + static void stage2(int a, int b) { + target(a - 1, b); + } + + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n02_true.yml b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n02_true.yml new file mode 100644 index 00000000..c94bd6e5 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n02_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - return_n02_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n02_true/Main.java b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n02_true/Main.java new file mode 100644 index 00000000..a7dbc285 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n02_true/Main.java @@ -0,0 +1,40 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:1001 int:2996 int:0 int:1 +import org.sosy_lab.sv_benchmarks.Verifier; + +// validation-gauntlet/return: 2 checks that each leave early, then the only assert. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int v0 = Verifier.nondetInt(); + int v1 = Verifier.nondetInt(); + run(a, b, v0, v1); + } + + static void run(int a, int b, int v0, int v1) { + if (v0 < 0) return; + if (v1 < 1) return; + stage1(a, b); + } + + static void stage1(int a, int b) { + stage2(a + 1, b); + } + + static void stage2(int a, int b) { + target(a - 1, b); + } + + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n04_false.yml b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n04_false.yml new file mode 100644 index 00000000..2db19617 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n04_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - return_n04_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n04_false/Main.java b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n04_false/Main.java new file mode 100644 index 00000000..c6a6c32e --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n04_false/Main.java @@ -0,0 +1,43 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:2996 int:0 int:1 int:2 int:3 +import org.sosy_lab.sv_benchmarks.Verifier; + +// validation-gauntlet/return: 4 checks that each leave early, then the only assert. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int v0 = Verifier.nondetInt(); + int v1 = Verifier.nondetInt(); + int v2 = Verifier.nondetInt(); + int v3 = Verifier.nondetInt(); + run(a, b, v0, v1, v2, v3); + } + + static void run(int a, int b, int v0, int v1, int v2, int v3) { + if (v0 < 0) return; + if (v1 < 1) return; + if (v2 < 2) return; + if (v3 < 3) return; + stage1(a, b); + } + + static void stage1(int a, int b) { + stage2(a + 1, b); + } + + static void stage2(int a, int b) { + target(a - 1, b); + } + + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n04_true.yml b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n04_true.yml new file mode 100644 index 00000000..278d4240 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n04_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - return_n04_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n04_true/Main.java b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n04_true/Main.java new file mode 100644 index 00000000..f7e707a8 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n04_true/Main.java @@ -0,0 +1,44 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:1001 int:2996 int:0 int:1 int:2 int:3 +import org.sosy_lab.sv_benchmarks.Verifier; + +// validation-gauntlet/return: 4 checks that each leave early, then the only assert. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int v0 = Verifier.nondetInt(); + int v1 = Verifier.nondetInt(); + int v2 = Verifier.nondetInt(); + int v3 = Verifier.nondetInt(); + run(a, b, v0, v1, v2, v3); + } + + static void run(int a, int b, int v0, int v1, int v2, int v3) { + if (v0 < 0) return; + if (v1 < 1) return; + if (v2 < 2) return; + if (v3 < 3) return; + stage1(a, b); + } + + static void stage1(int a, int b) { + stage2(a + 1, b); + } + + static void stage2(int a, int b) { + target(a - 1, b); + } + + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n08_false.yml b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n08_false.yml new file mode 100644 index 00000000..063836a7 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n08_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - return_n08_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n08_false/Main.java b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n08_false/Main.java new file mode 100644 index 00000000..d5a9c376 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n08_false/Main.java @@ -0,0 +1,51 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:2996 int:0 int:1 int:2 int:3 int:4 int:5 int:6 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; + +// validation-gauntlet/return: 8 checks that each leave early, then the only assert. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int v0 = Verifier.nondetInt(); + int v1 = Verifier.nondetInt(); + int v2 = Verifier.nondetInt(); + int v3 = Verifier.nondetInt(); + int v4 = Verifier.nondetInt(); + int v5 = Verifier.nondetInt(); + int v6 = Verifier.nondetInt(); + int v7 = Verifier.nondetInt(); + run(a, b, v0, v1, v2, v3, v4, v5, v6, v7); + } + + static void run(int a, int b, int v0, int v1, int v2, int v3, int v4, int v5, int v6, int v7) { + if (v0 < 0) return; + if (v1 < 1) return; + if (v2 < 2) return; + if (v3 < 3) return; + if (v4 < 4) return; + if (v5 < 5) return; + if (v6 < 6) return; + if (v7 < 7) return; + stage1(a, b); + } + + static void stage1(int a, int b) { + stage2(a + 1, b); + } + + static void stage2(int a, int b) { + target(a - 1, b); + } + + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n08_true.yml b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n08_true.yml new file mode 100644 index 00000000..baa93665 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n08_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - return_n08_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n08_true/Main.java b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n08_true/Main.java new file mode 100644 index 00000000..f8ad2bf5 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n08_true/Main.java @@ -0,0 +1,52 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:1001 int:2996 int:0 int:1 int:2 int:3 int:4 int:5 int:6 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; + +// validation-gauntlet/return: 8 checks that each leave early, then the only assert. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int v0 = Verifier.nondetInt(); + int v1 = Verifier.nondetInt(); + int v2 = Verifier.nondetInt(); + int v3 = Verifier.nondetInt(); + int v4 = Verifier.nondetInt(); + int v5 = Verifier.nondetInt(); + int v6 = Verifier.nondetInt(); + int v7 = Verifier.nondetInt(); + run(a, b, v0, v1, v2, v3, v4, v5, v6, v7); + } + + static void run(int a, int b, int v0, int v1, int v2, int v3, int v4, int v5, int v6, int v7) { + if (v0 < 0) return; + if (v1 < 1) return; + if (v2 < 2) return; + if (v3 < 3) return; + if (v4 < 4) return; + if (v5 < 5) return; + if (v6 < 6) return; + if (v7 < 7) return; + stage1(a, b); + } + + static void stage1(int a, int b) { + stage2(a + 1, b); + } + + static void stage2(int a, int b) { + target(a - 1, b); + } + + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n16_false.yml b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n16_false.yml new file mode 100644 index 00000000..2f12b851 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n16_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - return_n16_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n16_false/Main.java b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n16_false/Main.java new file mode 100644 index 00000000..a04e144a --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n16_false/Main.java @@ -0,0 +1,67 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:2996 int:0 int:1 int:2 int:3 int:4 int:5 int:6 int:7 int:8 int:9 int:10 int:11 int:12 int:13 int:14 int:15 +import org.sosy_lab.sv_benchmarks.Verifier; + +// validation-gauntlet/return: 16 checks that each leave early, then the only assert. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int v0 = Verifier.nondetInt(); + int v1 = Verifier.nondetInt(); + int v2 = Verifier.nondetInt(); + int v3 = Verifier.nondetInt(); + int v4 = Verifier.nondetInt(); + int v5 = Verifier.nondetInt(); + int v6 = Verifier.nondetInt(); + int v7 = Verifier.nondetInt(); + int v8 = Verifier.nondetInt(); + int v9 = Verifier.nondetInt(); + int v10 = Verifier.nondetInt(); + int v11 = Verifier.nondetInt(); + int v12 = Verifier.nondetInt(); + int v13 = Verifier.nondetInt(); + int v14 = Verifier.nondetInt(); + int v15 = Verifier.nondetInt(); + run(a, b, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15); + } + + static void run(int a, int b, int v0, int v1, int v2, int v3, int v4, int v5, int v6, int v7, int v8, int v9, int v10, int v11, int v12, int v13, int v14, int v15) { + if (v0 < 0) return; + if (v1 < 1) return; + if (v2 < 2) return; + if (v3 < 3) return; + if (v4 < 4) return; + if (v5 < 5) return; + if (v6 < 6) return; + if (v7 < 7) return; + if (v8 < 8) return; + if (v9 < 9) return; + if (v10 < 10) return; + if (v11 < 11) return; + if (v12 < 12) return; + if (v13 < 13) return; + if (v14 < 14) return; + if (v15 < 15) return; + stage1(a, b); + } + + static void stage1(int a, int b) { + stage2(a + 1, b); + } + + static void stage2(int a, int b) { + target(a - 1, b); + } + + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n16_true.yml b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n16_true.yml new file mode 100644 index 00000000..8f76694a --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n16_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - return_n16_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n16_true/Main.java b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n16_true/Main.java new file mode 100644 index 00000000..77e489a9 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n16_true/Main.java @@ -0,0 +1,68 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:1001 int:2996 int:0 int:1 int:2 int:3 int:4 int:5 int:6 int:7 int:8 int:9 int:10 int:11 int:12 int:13 int:14 int:15 +import org.sosy_lab.sv_benchmarks.Verifier; + +// validation-gauntlet/return: 16 checks that each leave early, then the only assert. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int v0 = Verifier.nondetInt(); + int v1 = Verifier.nondetInt(); + int v2 = Verifier.nondetInt(); + int v3 = Verifier.nondetInt(); + int v4 = Verifier.nondetInt(); + int v5 = Verifier.nondetInt(); + int v6 = Verifier.nondetInt(); + int v7 = Verifier.nondetInt(); + int v8 = Verifier.nondetInt(); + int v9 = Verifier.nondetInt(); + int v10 = Verifier.nondetInt(); + int v11 = Verifier.nondetInt(); + int v12 = Verifier.nondetInt(); + int v13 = Verifier.nondetInt(); + int v14 = Verifier.nondetInt(); + int v15 = Verifier.nondetInt(); + run(a, b, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15); + } + + static void run(int a, int b, int v0, int v1, int v2, int v3, int v4, int v5, int v6, int v7, int v8, int v9, int v10, int v11, int v12, int v13, int v14, int v15) { + if (v0 < 0) return; + if (v1 < 1) return; + if (v2 < 2) return; + if (v3 < 3) return; + if (v4 < 4) return; + if (v5 < 5) return; + if (v6 < 6) return; + if (v7 < 7) return; + if (v8 < 8) return; + if (v9 < 9) return; + if (v10 < 10) return; + if (v11 < 11) return; + if (v12 < 12) return; + if (v13 < 13) return; + if (v14 < 14) return; + if (v15 < 15) return; + stage1(a, b); + } + + static void stage1(int a, int b) { + stage2(a + 1, b); + } + + static void stage2(int a, int b) { + target(a - 1, b); + } + + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n32_false.yml b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n32_false.yml new file mode 100644 index 00000000..7e6b4c16 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n32_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - return_n32_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n32_false/Main.java b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n32_false/Main.java new file mode 100644 index 00000000..804117cc --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n32_false/Main.java @@ -0,0 +1,99 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:2996 int:0 int:1 int:2 int:3 int:4 int:5 int:6 int:7 int:8 int:9 int:10 int:11 int:12 int:13 int:14 int:15 int:16 int:17 int:18 int:19 int:20 int:21 int:22 int:23 int:24 int:25 int:26 int:27 int:28 int:29 int:30 int:31 +import org.sosy_lab.sv_benchmarks.Verifier; + +// validation-gauntlet/return: 32 checks that each leave early, then the only assert. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int v0 = Verifier.nondetInt(); + int v1 = Verifier.nondetInt(); + int v2 = Verifier.nondetInt(); + int v3 = Verifier.nondetInt(); + int v4 = Verifier.nondetInt(); + int v5 = Verifier.nondetInt(); + int v6 = Verifier.nondetInt(); + int v7 = Verifier.nondetInt(); + int v8 = Verifier.nondetInt(); + int v9 = Verifier.nondetInt(); + int v10 = Verifier.nondetInt(); + int v11 = Verifier.nondetInt(); + int v12 = Verifier.nondetInt(); + int v13 = Verifier.nondetInt(); + int v14 = Verifier.nondetInt(); + int v15 = Verifier.nondetInt(); + int v16 = Verifier.nondetInt(); + int v17 = Verifier.nondetInt(); + int v18 = Verifier.nondetInt(); + int v19 = Verifier.nondetInt(); + int v20 = Verifier.nondetInt(); + int v21 = Verifier.nondetInt(); + int v22 = Verifier.nondetInt(); + int v23 = Verifier.nondetInt(); + int v24 = Verifier.nondetInt(); + int v25 = Verifier.nondetInt(); + int v26 = Verifier.nondetInt(); + int v27 = Verifier.nondetInt(); + int v28 = Verifier.nondetInt(); + int v29 = Verifier.nondetInt(); + int v30 = Verifier.nondetInt(); + int v31 = Verifier.nondetInt(); + run(a, b, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31); + } + + static void run(int a, int b, int v0, int v1, int v2, int v3, int v4, int v5, int v6, int v7, int v8, int v9, int v10, int v11, int v12, int v13, int v14, int v15, int v16, int v17, int v18, int v19, int v20, int v21, int v22, int v23, int v24, int v25, int v26, int v27, int v28, int v29, int v30, int v31) { + if (v0 < 0) return; + if (v1 < 1) return; + if (v2 < 2) return; + if (v3 < 3) return; + if (v4 < 4) return; + if (v5 < 5) return; + if (v6 < 6) return; + if (v7 < 7) return; + if (v8 < 8) return; + if (v9 < 9) return; + if (v10 < 10) return; + if (v11 < 11) return; + if (v12 < 12) return; + if (v13 < 13) return; + if (v14 < 14) return; + if (v15 < 15) return; + if (v16 < 16) return; + if (v17 < 17) return; + if (v18 < 18) return; + if (v19 < 19) return; + if (v20 < 20) return; + if (v21 < 21) return; + if (v22 < 22) return; + if (v23 < 23) return; + if (v24 < 24) return; + if (v25 < 25) return; + if (v26 < 26) return; + if (v27 < 27) return; + if (v28 < 28) return; + if (v29 < 29) return; + if (v30 < 30) return; + if (v31 < 31) return; + stage1(a, b); + } + + static void stage1(int a, int b) { + stage2(a + 1, b); + } + + static void stage2(int a, int b) { + target(a - 1, b); + } + + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n32_true.yml b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n32_true.yml new file mode 100644 index 00000000..2d618ab3 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n32_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - return_n32_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n32_true/Main.java b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n32_true/Main.java new file mode 100644 index 00000000..eef6b207 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/return_n32_true/Main.java @@ -0,0 +1,100 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:1001 int:2996 int:0 int:1 int:2 int:3 int:4 int:5 int:6 int:7 int:8 int:9 int:10 int:11 int:12 int:13 int:14 int:15 int:16 int:17 int:18 int:19 int:20 int:21 int:22 int:23 int:24 int:25 int:26 int:27 int:28 int:29 int:30 int:31 +import org.sosy_lab.sv_benchmarks.Verifier; + +// validation-gauntlet/return: 32 checks that each leave early, then the only assert. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int v0 = Verifier.nondetInt(); + int v1 = Verifier.nondetInt(); + int v2 = Verifier.nondetInt(); + int v3 = Verifier.nondetInt(); + int v4 = Verifier.nondetInt(); + int v5 = Verifier.nondetInt(); + int v6 = Verifier.nondetInt(); + int v7 = Verifier.nondetInt(); + int v8 = Verifier.nondetInt(); + int v9 = Verifier.nondetInt(); + int v10 = Verifier.nondetInt(); + int v11 = Verifier.nondetInt(); + int v12 = Verifier.nondetInt(); + int v13 = Verifier.nondetInt(); + int v14 = Verifier.nondetInt(); + int v15 = Verifier.nondetInt(); + int v16 = Verifier.nondetInt(); + int v17 = Verifier.nondetInt(); + int v18 = Verifier.nondetInt(); + int v19 = Verifier.nondetInt(); + int v20 = Verifier.nondetInt(); + int v21 = Verifier.nondetInt(); + int v22 = Verifier.nondetInt(); + int v23 = Verifier.nondetInt(); + int v24 = Verifier.nondetInt(); + int v25 = Verifier.nondetInt(); + int v26 = Verifier.nondetInt(); + int v27 = Verifier.nondetInt(); + int v28 = Verifier.nondetInt(); + int v29 = Verifier.nondetInt(); + int v30 = Verifier.nondetInt(); + int v31 = Verifier.nondetInt(); + run(a, b, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31); + } + + static void run(int a, int b, int v0, int v1, int v2, int v3, int v4, int v5, int v6, int v7, int v8, int v9, int v10, int v11, int v12, int v13, int v14, int v15, int v16, int v17, int v18, int v19, int v20, int v21, int v22, int v23, int v24, int v25, int v26, int v27, int v28, int v29, int v30, int v31) { + if (v0 < 0) return; + if (v1 < 1) return; + if (v2 < 2) return; + if (v3 < 3) return; + if (v4 < 4) return; + if (v5 < 5) return; + if (v6 < 6) return; + if (v7 < 7) return; + if (v8 < 8) return; + if (v9 < 9) return; + if (v10 < 10) return; + if (v11 < 11) return; + if (v12 < 12) return; + if (v13 < 13) return; + if (v14 < 14) return; + if (v15 < 15) return; + if (v16 < 16) return; + if (v17 < 17) return; + if (v18 < 18) return; + if (v19 < 19) return; + if (v20 < 20) return; + if (v21 < 21) return; + if (v22 < 22) return; + if (v23 < 23) return; + if (v24 < 24) return; + if (v25 < 25) return; + if (v26 < 26) return; + if (v27 < 27) return; + if (v28 < 28) return; + if (v29 < 29) return; + if (v30 < 30) return; + if (v31 < 31) return; + stage1(a, b); + } + + static void stage1(int a, int b) { + stage2(a + 1, b); + } + + static void stage2(int a, int b) { + target(a - 1, b); + } + + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n02_false.yml b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n02_false.yml new file mode 100644 index 00000000..d1709a81 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n02_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - throw_n02_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n02_false/Main.java b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n02_false/Main.java new file mode 100644 index 00000000..90a7a64a --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n02_false/Main.java @@ -0,0 +1,43 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:2996 int:0 int:1 +import org.sosy_lab.sv_benchmarks.Verifier; + +// validation-gauntlet/throw: 2 checks that each leave early, then the only assert. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int v0 = Verifier.nondetInt(); + int v1 = Verifier.nondetInt(); + try { + run(a, b, v0, v1); + } catch (IllegalArgumentException e) { + return; + } + } + + static void run(int a, int b, int v0, int v1) { + if (v0 < 0) throw new IllegalArgumentException(); + if (v1 < 1) throw new IllegalArgumentException(); + stage1(a, b); + } + + static void stage1(int a, int b) { + stage2(a + 1, b); + } + + static void stage2(int a, int b) { + target(a - 1, b); + } + + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n02_true.yml b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n02_true.yml new file mode 100644 index 00000000..72a6b8e5 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n02_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - throw_n02_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n02_true/Main.java b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n02_true/Main.java new file mode 100644 index 00000000..25be5ad8 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n02_true/Main.java @@ -0,0 +1,44 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:1001 int:2996 int:0 int:1 +import org.sosy_lab.sv_benchmarks.Verifier; + +// validation-gauntlet/throw: 2 checks that each leave early, then the only assert. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int v0 = Verifier.nondetInt(); + int v1 = Verifier.nondetInt(); + try { + run(a, b, v0, v1); + } catch (IllegalArgumentException e) { + return; + } + } + + static void run(int a, int b, int v0, int v1) { + if (v0 < 0) throw new IllegalArgumentException(); + if (v1 < 1) throw new IllegalArgumentException(); + stage1(a, b); + } + + static void stage1(int a, int b) { + stage2(a + 1, b); + } + + static void stage2(int a, int b) { + target(a - 1, b); + } + + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n04_false.yml b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n04_false.yml new file mode 100644 index 00000000..2ec31baf --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n04_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - throw_n04_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n04_false/Main.java b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n04_false/Main.java new file mode 100644 index 00000000..ab4fdc8f --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n04_false/Main.java @@ -0,0 +1,47 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:2996 int:0 int:1 int:2 int:3 +import org.sosy_lab.sv_benchmarks.Verifier; + +// validation-gauntlet/throw: 4 checks that each leave early, then the only assert. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int v0 = Verifier.nondetInt(); + int v1 = Verifier.nondetInt(); + int v2 = Verifier.nondetInt(); + int v3 = Verifier.nondetInt(); + try { + run(a, b, v0, v1, v2, v3); + } catch (IllegalArgumentException e) { + return; + } + } + + static void run(int a, int b, int v0, int v1, int v2, int v3) { + if (v0 < 0) throw new IllegalArgumentException(); + if (v1 < 1) throw new IllegalArgumentException(); + if (v2 < 2) throw new IllegalArgumentException(); + if (v3 < 3) throw new IllegalArgumentException(); + stage1(a, b); + } + + static void stage1(int a, int b) { + stage2(a + 1, b); + } + + static void stage2(int a, int b) { + target(a - 1, b); + } + + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n04_true.yml b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n04_true.yml new file mode 100644 index 00000000..c29cd9a5 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n04_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - throw_n04_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n04_true/Main.java b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n04_true/Main.java new file mode 100644 index 00000000..7ab7e66c --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n04_true/Main.java @@ -0,0 +1,48 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:1001 int:2996 int:0 int:1 int:2 int:3 +import org.sosy_lab.sv_benchmarks.Verifier; + +// validation-gauntlet/throw: 4 checks that each leave early, then the only assert. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int v0 = Verifier.nondetInt(); + int v1 = Verifier.nondetInt(); + int v2 = Verifier.nondetInt(); + int v3 = Verifier.nondetInt(); + try { + run(a, b, v0, v1, v2, v3); + } catch (IllegalArgumentException e) { + return; + } + } + + static void run(int a, int b, int v0, int v1, int v2, int v3) { + if (v0 < 0) throw new IllegalArgumentException(); + if (v1 < 1) throw new IllegalArgumentException(); + if (v2 < 2) throw new IllegalArgumentException(); + if (v3 < 3) throw new IllegalArgumentException(); + stage1(a, b); + } + + static void stage1(int a, int b) { + stage2(a + 1, b); + } + + static void stage2(int a, int b) { + target(a - 1, b); + } + + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n08_false.yml b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n08_false.yml new file mode 100644 index 00000000..bfb51db7 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n08_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - throw_n08_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n08_false/Main.java b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n08_false/Main.java new file mode 100644 index 00000000..5f92c397 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n08_false/Main.java @@ -0,0 +1,55 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:2996 int:0 int:1 int:2 int:3 int:4 int:5 int:6 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; + +// validation-gauntlet/throw: 8 checks that each leave early, then the only assert. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int v0 = Verifier.nondetInt(); + int v1 = Verifier.nondetInt(); + int v2 = Verifier.nondetInt(); + int v3 = Verifier.nondetInt(); + int v4 = Verifier.nondetInt(); + int v5 = Verifier.nondetInt(); + int v6 = Verifier.nondetInt(); + int v7 = Verifier.nondetInt(); + try { + run(a, b, v0, v1, v2, v3, v4, v5, v6, v7); + } catch (IllegalArgumentException e) { + return; + } + } + + static void run(int a, int b, int v0, int v1, int v2, int v3, int v4, int v5, int v6, int v7) { + if (v0 < 0) throw new IllegalArgumentException(); + if (v1 < 1) throw new IllegalArgumentException(); + if (v2 < 2) throw new IllegalArgumentException(); + if (v3 < 3) throw new IllegalArgumentException(); + if (v4 < 4) throw new IllegalArgumentException(); + if (v5 < 5) throw new IllegalArgumentException(); + if (v6 < 6) throw new IllegalArgumentException(); + if (v7 < 7) throw new IllegalArgumentException(); + stage1(a, b); + } + + static void stage1(int a, int b) { + stage2(a + 1, b); + } + + static void stage2(int a, int b) { + target(a - 1, b); + } + + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n08_true.yml b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n08_true.yml new file mode 100644 index 00000000..8d3fd0c6 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n08_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - throw_n08_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n08_true/Main.java b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n08_true/Main.java new file mode 100644 index 00000000..e31ef8c8 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n08_true/Main.java @@ -0,0 +1,56 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:1001 int:2996 int:0 int:1 int:2 int:3 int:4 int:5 int:6 int:7 +import org.sosy_lab.sv_benchmarks.Verifier; + +// validation-gauntlet/throw: 8 checks that each leave early, then the only assert. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int v0 = Verifier.nondetInt(); + int v1 = Verifier.nondetInt(); + int v2 = Verifier.nondetInt(); + int v3 = Verifier.nondetInt(); + int v4 = Verifier.nondetInt(); + int v5 = Verifier.nondetInt(); + int v6 = Verifier.nondetInt(); + int v7 = Verifier.nondetInt(); + try { + run(a, b, v0, v1, v2, v3, v4, v5, v6, v7); + } catch (IllegalArgumentException e) { + return; + } + } + + static void run(int a, int b, int v0, int v1, int v2, int v3, int v4, int v5, int v6, int v7) { + if (v0 < 0) throw new IllegalArgumentException(); + if (v1 < 1) throw new IllegalArgumentException(); + if (v2 < 2) throw new IllegalArgumentException(); + if (v3 < 3) throw new IllegalArgumentException(); + if (v4 < 4) throw new IllegalArgumentException(); + if (v5 < 5) throw new IllegalArgumentException(); + if (v6 < 6) throw new IllegalArgumentException(); + if (v7 < 7) throw new IllegalArgumentException(); + stage1(a, b); + } + + static void stage1(int a, int b) { + stage2(a + 1, b); + } + + static void stage2(int a, int b) { + target(a - 1, b); + } + + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n16_false.yml b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n16_false.yml new file mode 100644 index 00000000..b6834ce8 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n16_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - throw_n16_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n16_false/Main.java b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n16_false/Main.java new file mode 100644 index 00000000..2290b180 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n16_false/Main.java @@ -0,0 +1,71 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:2996 int:0 int:1 int:2 int:3 int:4 int:5 int:6 int:7 int:8 int:9 int:10 int:11 int:12 int:13 int:14 int:15 +import org.sosy_lab.sv_benchmarks.Verifier; + +// validation-gauntlet/throw: 16 checks that each leave early, then the only assert. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int v0 = Verifier.nondetInt(); + int v1 = Verifier.nondetInt(); + int v2 = Verifier.nondetInt(); + int v3 = Verifier.nondetInt(); + int v4 = Verifier.nondetInt(); + int v5 = Verifier.nondetInt(); + int v6 = Verifier.nondetInt(); + int v7 = Verifier.nondetInt(); + int v8 = Verifier.nondetInt(); + int v9 = Verifier.nondetInt(); + int v10 = Verifier.nondetInt(); + int v11 = Verifier.nondetInt(); + int v12 = Verifier.nondetInt(); + int v13 = Verifier.nondetInt(); + int v14 = Verifier.nondetInt(); + int v15 = Verifier.nondetInt(); + try { + run(a, b, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15); + } catch (IllegalArgumentException e) { + return; + } + } + + static void run(int a, int b, int v0, int v1, int v2, int v3, int v4, int v5, int v6, int v7, int v8, int v9, int v10, int v11, int v12, int v13, int v14, int v15) { + if (v0 < 0) throw new IllegalArgumentException(); + if (v1 < 1) throw new IllegalArgumentException(); + if (v2 < 2) throw new IllegalArgumentException(); + if (v3 < 3) throw new IllegalArgumentException(); + if (v4 < 4) throw new IllegalArgumentException(); + if (v5 < 5) throw new IllegalArgumentException(); + if (v6 < 6) throw new IllegalArgumentException(); + if (v7 < 7) throw new IllegalArgumentException(); + if (v8 < 8) throw new IllegalArgumentException(); + if (v9 < 9) throw new IllegalArgumentException(); + if (v10 < 10) throw new IllegalArgumentException(); + if (v11 < 11) throw new IllegalArgumentException(); + if (v12 < 12) throw new IllegalArgumentException(); + if (v13 < 13) throw new IllegalArgumentException(); + if (v14 < 14) throw new IllegalArgumentException(); + if (v15 < 15) throw new IllegalArgumentException(); + stage1(a, b); + } + + static void stage1(int a, int b) { + stage2(a + 1, b); + } + + static void stage2(int a, int b) { + target(a - 1, b); + } + + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n16_true.yml b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n16_true.yml new file mode 100644 index 00000000..de9ec428 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n16_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - throw_n16_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n16_true/Main.java b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n16_true/Main.java new file mode 100644 index 00000000..e6327b30 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n16_true/Main.java @@ -0,0 +1,72 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:1001 int:2996 int:0 int:1 int:2 int:3 int:4 int:5 int:6 int:7 int:8 int:9 int:10 int:11 int:12 int:13 int:14 int:15 +import org.sosy_lab.sv_benchmarks.Verifier; + +// validation-gauntlet/throw: 16 checks that each leave early, then the only assert. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int v0 = Verifier.nondetInt(); + int v1 = Verifier.nondetInt(); + int v2 = Verifier.nondetInt(); + int v3 = Verifier.nondetInt(); + int v4 = Verifier.nondetInt(); + int v5 = Verifier.nondetInt(); + int v6 = Verifier.nondetInt(); + int v7 = Verifier.nondetInt(); + int v8 = Verifier.nondetInt(); + int v9 = Verifier.nondetInt(); + int v10 = Verifier.nondetInt(); + int v11 = Verifier.nondetInt(); + int v12 = Verifier.nondetInt(); + int v13 = Verifier.nondetInt(); + int v14 = Verifier.nondetInt(); + int v15 = Verifier.nondetInt(); + try { + run(a, b, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15); + } catch (IllegalArgumentException e) { + return; + } + } + + static void run(int a, int b, int v0, int v1, int v2, int v3, int v4, int v5, int v6, int v7, int v8, int v9, int v10, int v11, int v12, int v13, int v14, int v15) { + if (v0 < 0) throw new IllegalArgumentException(); + if (v1 < 1) throw new IllegalArgumentException(); + if (v2 < 2) throw new IllegalArgumentException(); + if (v3 < 3) throw new IllegalArgumentException(); + if (v4 < 4) throw new IllegalArgumentException(); + if (v5 < 5) throw new IllegalArgumentException(); + if (v6 < 6) throw new IllegalArgumentException(); + if (v7 < 7) throw new IllegalArgumentException(); + if (v8 < 8) throw new IllegalArgumentException(); + if (v9 < 9) throw new IllegalArgumentException(); + if (v10 < 10) throw new IllegalArgumentException(); + if (v11 < 11) throw new IllegalArgumentException(); + if (v12 < 12) throw new IllegalArgumentException(); + if (v13 < 13) throw new IllegalArgumentException(); + if (v14 < 14) throw new IllegalArgumentException(); + if (v15 < 15) throw new IllegalArgumentException(); + stage1(a, b); + } + + static void stage1(int a, int b) { + stage2(a + 1, b); + } + + static void stage2(int a, int b) { + target(a - 1, b); + } + + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n32_false.yml b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n32_false.yml new file mode 100644 index 00000000..6804ac9a --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n32_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - throw_n32_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n32_false/Main.java b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n32_false/Main.java new file mode 100644 index 00000000..75bfc1be --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n32_false/Main.java @@ -0,0 +1,103 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1001 int:2996 int:0 int:1 int:2 int:3 int:4 int:5 int:6 int:7 int:8 int:9 int:10 int:11 int:12 int:13 int:14 int:15 int:16 int:17 int:18 int:19 int:20 int:21 int:22 int:23 int:24 int:25 int:26 int:27 int:28 int:29 int:30 int:31 +import org.sosy_lab.sv_benchmarks.Verifier; + +// validation-gauntlet/throw: 32 checks that each leave early, then the only assert. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int v0 = Verifier.nondetInt(); + int v1 = Verifier.nondetInt(); + int v2 = Verifier.nondetInt(); + int v3 = Verifier.nondetInt(); + int v4 = Verifier.nondetInt(); + int v5 = Verifier.nondetInt(); + int v6 = Verifier.nondetInt(); + int v7 = Verifier.nondetInt(); + int v8 = Verifier.nondetInt(); + int v9 = Verifier.nondetInt(); + int v10 = Verifier.nondetInt(); + int v11 = Verifier.nondetInt(); + int v12 = Verifier.nondetInt(); + int v13 = Verifier.nondetInt(); + int v14 = Verifier.nondetInt(); + int v15 = Verifier.nondetInt(); + int v16 = Verifier.nondetInt(); + int v17 = Verifier.nondetInt(); + int v18 = Verifier.nondetInt(); + int v19 = Verifier.nondetInt(); + int v20 = Verifier.nondetInt(); + int v21 = Verifier.nondetInt(); + int v22 = Verifier.nondetInt(); + int v23 = Verifier.nondetInt(); + int v24 = Verifier.nondetInt(); + int v25 = Verifier.nondetInt(); + int v26 = Verifier.nondetInt(); + int v27 = Verifier.nondetInt(); + int v28 = Verifier.nondetInt(); + int v29 = Verifier.nondetInt(); + int v30 = Verifier.nondetInt(); + int v31 = Verifier.nondetInt(); + try { + run(a, b, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31); + } catch (IllegalArgumentException e) { + return; + } + } + + static void run(int a, int b, int v0, int v1, int v2, int v3, int v4, int v5, int v6, int v7, int v8, int v9, int v10, int v11, int v12, int v13, int v14, int v15, int v16, int v17, int v18, int v19, int v20, int v21, int v22, int v23, int v24, int v25, int v26, int v27, int v28, int v29, int v30, int v31) { + if (v0 < 0) throw new IllegalArgumentException(); + if (v1 < 1) throw new IllegalArgumentException(); + if (v2 < 2) throw new IllegalArgumentException(); + if (v3 < 3) throw new IllegalArgumentException(); + if (v4 < 4) throw new IllegalArgumentException(); + if (v5 < 5) throw new IllegalArgumentException(); + if (v6 < 6) throw new IllegalArgumentException(); + if (v7 < 7) throw new IllegalArgumentException(); + if (v8 < 8) throw new IllegalArgumentException(); + if (v9 < 9) throw new IllegalArgumentException(); + if (v10 < 10) throw new IllegalArgumentException(); + if (v11 < 11) throw new IllegalArgumentException(); + if (v12 < 12) throw new IllegalArgumentException(); + if (v13 < 13) throw new IllegalArgumentException(); + if (v14 < 14) throw new IllegalArgumentException(); + if (v15 < 15) throw new IllegalArgumentException(); + if (v16 < 16) throw new IllegalArgumentException(); + if (v17 < 17) throw new IllegalArgumentException(); + if (v18 < 18) throw new IllegalArgumentException(); + if (v19 < 19) throw new IllegalArgumentException(); + if (v20 < 20) throw new IllegalArgumentException(); + if (v21 < 21) throw new IllegalArgumentException(); + if (v22 < 22) throw new IllegalArgumentException(); + if (v23 < 23) throw new IllegalArgumentException(); + if (v24 < 24) throw new IllegalArgumentException(); + if (v25 < 25) throw new IllegalArgumentException(); + if (v26 < 26) throw new IllegalArgumentException(); + if (v27 < 27) throw new IllegalArgumentException(); + if (v28 < 28) throw new IllegalArgumentException(); + if (v29 < 29) throw new IllegalArgumentException(); + if (v30 < 30) throw new IllegalArgumentException(); + if (v31 < 31) throw new IllegalArgumentException(); + stage1(a, b); + } + + static void stage1(int a, int b) { + stage2(a + 1, b); + } + + static void stage2(int a, int b) { + target(a - 1, b); + } + + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n32_true.yml b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n32_true.yml new file mode 100644 index 00000000..05caace4 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n32_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - throw_n32_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n32_true/Main.java b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n32_true/Main.java new file mode 100644 index 00000000..9582fabb --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/validation-gauntlet/throw_n32_true/Main.java @@ -0,0 +1,104 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:1001 int:2996 int:0 int:1 int:2 int:3 int:4 int:5 int:6 int:7 int:8 int:9 int:10 int:11 int:12 int:13 int:14 int:15 int:16 int:17 int:18 int:19 int:20 int:21 int:22 int:23 int:24 int:25 int:26 int:27 int:28 int:29 int:30 int:31 +import org.sosy_lab.sv_benchmarks.Verifier; + +// validation-gauntlet/throw: 32 checks that each leave early, then the only assert. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int v0 = Verifier.nondetInt(); + int v1 = Verifier.nondetInt(); + int v2 = Verifier.nondetInt(); + int v3 = Verifier.nondetInt(); + int v4 = Verifier.nondetInt(); + int v5 = Verifier.nondetInt(); + int v6 = Verifier.nondetInt(); + int v7 = Verifier.nondetInt(); + int v8 = Verifier.nondetInt(); + int v9 = Verifier.nondetInt(); + int v10 = Verifier.nondetInt(); + int v11 = Verifier.nondetInt(); + int v12 = Verifier.nondetInt(); + int v13 = Verifier.nondetInt(); + int v14 = Verifier.nondetInt(); + int v15 = Verifier.nondetInt(); + int v16 = Verifier.nondetInt(); + int v17 = Verifier.nondetInt(); + int v18 = Verifier.nondetInt(); + int v19 = Verifier.nondetInt(); + int v20 = Verifier.nondetInt(); + int v21 = Verifier.nondetInt(); + int v22 = Verifier.nondetInt(); + int v23 = Verifier.nondetInt(); + int v24 = Verifier.nondetInt(); + int v25 = Verifier.nondetInt(); + int v26 = Verifier.nondetInt(); + int v27 = Verifier.nondetInt(); + int v28 = Verifier.nondetInt(); + int v29 = Verifier.nondetInt(); + int v30 = Verifier.nondetInt(); + int v31 = Verifier.nondetInt(); + try { + run(a, b, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31); + } catch (IllegalArgumentException e) { + return; + } + } + + static void run(int a, int b, int v0, int v1, int v2, int v3, int v4, int v5, int v6, int v7, int v8, int v9, int v10, int v11, int v12, int v13, int v14, int v15, int v16, int v17, int v18, int v19, int v20, int v21, int v22, int v23, int v24, int v25, int v26, int v27, int v28, int v29, int v30, int v31) { + if (v0 < 0) throw new IllegalArgumentException(); + if (v1 < 1) throw new IllegalArgumentException(); + if (v2 < 2) throw new IllegalArgumentException(); + if (v3 < 3) throw new IllegalArgumentException(); + if (v4 < 4) throw new IllegalArgumentException(); + if (v5 < 5) throw new IllegalArgumentException(); + if (v6 < 6) throw new IllegalArgumentException(); + if (v7 < 7) throw new IllegalArgumentException(); + if (v8 < 8) throw new IllegalArgumentException(); + if (v9 < 9) throw new IllegalArgumentException(); + if (v10 < 10) throw new IllegalArgumentException(); + if (v11 < 11) throw new IllegalArgumentException(); + if (v12 < 12) throw new IllegalArgumentException(); + if (v13 < 13) throw new IllegalArgumentException(); + if (v14 < 14) throw new IllegalArgumentException(); + if (v15 < 15) throw new IllegalArgumentException(); + if (v16 < 16) throw new IllegalArgumentException(); + if (v17 < 17) throw new IllegalArgumentException(); + if (v18 < 18) throw new IllegalArgumentException(); + if (v19 < 19) throw new IllegalArgumentException(); + if (v20 < 20) throw new IllegalArgumentException(); + if (v21 < 21) throw new IllegalArgumentException(); + if (v22 < 22) throw new IllegalArgumentException(); + if (v23 < 23) throw new IllegalArgumentException(); + if (v24 < 24) throw new IllegalArgumentException(); + if (v25 < 25) throw new IllegalArgumentException(); + if (v26 < 26) throw new IllegalArgumentException(); + if (v27 < 27) throw new IllegalArgumentException(); + if (v28 < 28) throw new IllegalArgumentException(); + if (v29 < 29) throw new IllegalArgumentException(); + if (v30 < 30) throw new IllegalArgumentException(); + if (v31 < 31) throw new IllegalArgumentException(); + stage1(a, b); + } + + static void stage1(int a, int b) { + stage2(a + 1, b); + } + + static void stage2(int a, int b) { + target(a - 1, b); + } + + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n02_false.yml b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n02_false.yml new file mode 100644 index 00000000..a92b4a46 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n02_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - array_n02_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n02_false/Main.java b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n02_false/Main.java new file mode 100644 index 00000000..5eb84e0b --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n02_false/Main.java @@ -0,0 +1,54 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// virtual-dispatch/array: cmd selects one of 2 subclasses, only C1.run() reaches the assert. +public class Main { + static abstract class Cmd { + abstract int run(int a, int b, int c, int d); + } + + static class C0 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + } + + static class C1 extends Cmd { + int run(int a, int b, int c, int d) { + target(a, b); + return 0; + } + } + + static final Cmd[] TABLE = {new C0(), new C1()}; + + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + if (cmd < 0 || cmd >= TABLE.length) return -1; + return TABLE[cmd].run(a, b, c, d); + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n02_true.yml b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n02_true.yml new file mode 100644 index 00000000..3f8f14e5 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n02_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - array_n02_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n02_true/Main.java b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n02_true/Main.java new file mode 100644 index 00000000..de98eb68 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n02_true/Main.java @@ -0,0 +1,55 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:1 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// virtual-dispatch/array: cmd selects one of 2 subclasses, only C1.run() reaches the assert. +public class Main { + static abstract class Cmd { + abstract int run(int a, int b, int c, int d); + } + + static class C0 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + } + + static class C1 extends Cmd { + int run(int a, int b, int c, int d) { + target(a, b); + return 0; + } + } + + static final Cmd[] TABLE = {new C0(), new C1()}; + + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + if (cmd < 0 || cmd >= TABLE.length) return -1; + return TABLE[cmd].run(a, b, c, d); + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n04_false.yml b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n04_false.yml new file mode 100644 index 00000000..9947e5bc --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n04_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - array_n04_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n04_false/Main.java b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n04_false/Main.java new file mode 100644 index 00000000..2d52af86 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n04_false/Main.java @@ -0,0 +1,76 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:3 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// virtual-dispatch/array: cmd selects one of 4 subclasses, only C3.run() reaches the assert. +public class Main { + static abstract class Cmd { + abstract int run(int a, int b, int c, int d); + } + + static class C0 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + } + + static class C1 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + } + + static class C2 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + } + + static class C3 extends Cmd { + int run(int a, int b, int c, int d) { + target(a, b); + return 0; + } + } + + static final Cmd[] TABLE = {new C0(), new C1(), new C2(), new C3()}; + + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + if (cmd < 0 || cmd >= TABLE.length) return -1; + return TABLE[cmd].run(a, b, c, d); + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n04_true.yml b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n04_true.yml new file mode 100644 index 00000000..c315b11a --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n04_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - array_n04_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n04_true/Main.java b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n04_true/Main.java new file mode 100644 index 00000000..4a3d1fdf --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n04_true/Main.java @@ -0,0 +1,77 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:3 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// virtual-dispatch/array: cmd selects one of 4 subclasses, only C3.run() reaches the assert. +public class Main { + static abstract class Cmd { + abstract int run(int a, int b, int c, int d); + } + + static class C0 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + } + + static class C1 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + } + + static class C2 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + } + + static class C3 extends Cmd { + int run(int a, int b, int c, int d) { + target(a, b); + return 0; + } + } + + static final Cmd[] TABLE = {new C0(), new C1(), new C2(), new C3()}; + + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + if (cmd < 0 || cmd >= TABLE.length) return -1; + return TABLE[cmd].run(a, b, c, d); + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n08_false.yml b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n08_false.yml new file mode 100644 index 00000000..5c5f31c0 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n08_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - array_n08_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n08_false/Main.java b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n08_false/Main.java new file mode 100644 index 00000000..9fba87e4 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n08_false/Main.java @@ -0,0 +1,120 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:7 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// virtual-dispatch/array: cmd selects one of 8 subclasses, only C7.run() reaches the assert. +public class Main { + static abstract class Cmd { + abstract int run(int a, int b, int c, int d); + } + + static class C0 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + } + + static class C1 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + } + + static class C2 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + } + + static class C3 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 3; + if (a > 3) r += 1; + if (b > 6) r += 2; + if (c < -3) r += 4; + if (d == 8) r += 8; + return r; + } + } + + static class C4 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 4; + if (a > 4) r += 1; + if (b > 8) r += 2; + if (c < -4) r += 4; + if (d == 9) r += 8; + return r; + } + } + + static class C5 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 5; + if (a > 5) r += 1; + if (b > 10) r += 2; + if (c < -5) r += 4; + if (d == 10) r += 8; + return r; + } + } + + static class C6 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 6; + if (a > 6) r += 1; + if (b > 12) r += 2; + if (c < -6) r += 4; + if (d == 11) r += 8; + return r; + } + } + + static class C7 extends Cmd { + int run(int a, int b, int c, int d) { + target(a, b); + return 0; + } + } + + static final Cmd[] TABLE = {new C0(), new C1(), new C2(), new C3(), new C4(), new C5(), new C6(), new C7()}; + + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + if (cmd < 0 || cmd >= TABLE.length) return -1; + return TABLE[cmd].run(a, b, c, d); + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n08_true.yml b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n08_true.yml new file mode 100644 index 00000000..083279f0 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n08_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - array_n08_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n08_true/Main.java b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n08_true/Main.java new file mode 100644 index 00000000..6b86848e --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n08_true/Main.java @@ -0,0 +1,121 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:7 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// virtual-dispatch/array: cmd selects one of 8 subclasses, only C7.run() reaches the assert. +public class Main { + static abstract class Cmd { + abstract int run(int a, int b, int c, int d); + } + + static class C0 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + } + + static class C1 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + } + + static class C2 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + } + + static class C3 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 3; + if (a > 3) r += 1; + if (b > 6) r += 2; + if (c < -3) r += 4; + if (d == 8) r += 8; + return r; + } + } + + static class C4 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 4; + if (a > 4) r += 1; + if (b > 8) r += 2; + if (c < -4) r += 4; + if (d == 9) r += 8; + return r; + } + } + + static class C5 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 5; + if (a > 5) r += 1; + if (b > 10) r += 2; + if (c < -5) r += 4; + if (d == 10) r += 8; + return r; + } + } + + static class C6 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 6; + if (a > 6) r += 1; + if (b > 12) r += 2; + if (c < -6) r += 4; + if (d == 11) r += 8; + return r; + } + } + + static class C7 extends Cmd { + int run(int a, int b, int c, int d) { + target(a, b); + return 0; + } + } + + static final Cmd[] TABLE = {new C0(), new C1(), new C2(), new C3(), new C4(), new C5(), new C6(), new C7()}; + + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + if (cmd < 0 || cmd >= TABLE.length) return -1; + return TABLE[cmd].run(a, b, c, d); + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n16_false.yml b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n16_false.yml new file mode 100644 index 00000000..21259a2f --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n16_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - array_n16_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n16_false/Main.java b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n16_false/Main.java new file mode 100644 index 00000000..6f992e25 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n16_false/Main.java @@ -0,0 +1,208 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:15 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// virtual-dispatch/array: cmd selects one of 16 subclasses, only C15.run() reaches the assert. +public class Main { + static abstract class Cmd { + abstract int run(int a, int b, int c, int d); + } + + static class C0 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + } + + static class C1 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + } + + static class C2 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + } + + static class C3 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 3; + if (a > 3) r += 1; + if (b > 6) r += 2; + if (c < -3) r += 4; + if (d == 8) r += 8; + return r; + } + } + + static class C4 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 4; + if (a > 4) r += 1; + if (b > 8) r += 2; + if (c < -4) r += 4; + if (d == 9) r += 8; + return r; + } + } + + static class C5 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 5; + if (a > 5) r += 1; + if (b > 10) r += 2; + if (c < -5) r += 4; + if (d == 10) r += 8; + return r; + } + } + + static class C6 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 6; + if (a > 6) r += 1; + if (b > 12) r += 2; + if (c < -6) r += 4; + if (d == 11) r += 8; + return r; + } + } + + static class C7 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 7; + if (a > 7) r += 1; + if (b > 14) r += 2; + if (c < -7) r += 4; + if (d == 12) r += 8; + return r; + } + } + + static class C8 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 8; + if (a > 8) r += 1; + if (b > 16) r += 2; + if (c < -8) r += 4; + if (d == 13) r += 8; + return r; + } + } + + static class C9 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 9; + if (a > 9) r += 1; + if (b > 18) r += 2; + if (c < -9) r += 4; + if (d == 14) r += 8; + return r; + } + } + + static class C10 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 10; + if (a > 10) r += 1; + if (b > 20) r += 2; + if (c < -10) r += 4; + if (d == 15) r += 8; + return r; + } + } + + static class C11 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 11; + if (a > 11) r += 1; + if (b > 22) r += 2; + if (c < -11) r += 4; + if (d == 16) r += 8; + return r; + } + } + + static class C12 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 12; + if (a > 12) r += 1; + if (b > 24) r += 2; + if (c < -12) r += 4; + if (d == 17) r += 8; + return r; + } + } + + static class C13 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 13; + if (a > 13) r += 1; + if (b > 26) r += 2; + if (c < -13) r += 4; + if (d == 18) r += 8; + return r; + } + } + + static class C14 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 14; + if (a > 14) r += 1; + if (b > 28) r += 2; + if (c < -14) r += 4; + if (d == 19) r += 8; + return r; + } + } + + static class C15 extends Cmd { + int run(int a, int b, int c, int d) { + target(a, b); + return 0; + } + } + + static final Cmd[] TABLE = {new C0(), new C1(), new C2(), new C3(), new C4(), new C5(), new C6(), new C7(), new C8(), new C9(), new C10(), new C11(), new C12(), new C13(), new C14(), new C15()}; + + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + if (cmd < 0 || cmd >= TABLE.length) return -1; + return TABLE[cmd].run(a, b, c, d); + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n16_true.yml b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n16_true.yml new file mode 100644 index 00000000..7eec3c21 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n16_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - array_n16_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n16_true/Main.java b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n16_true/Main.java new file mode 100644 index 00000000..19f45433 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n16_true/Main.java @@ -0,0 +1,209 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:15 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// virtual-dispatch/array: cmd selects one of 16 subclasses, only C15.run() reaches the assert. +public class Main { + static abstract class Cmd { + abstract int run(int a, int b, int c, int d); + } + + static class C0 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + } + + static class C1 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + } + + static class C2 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + } + + static class C3 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 3; + if (a > 3) r += 1; + if (b > 6) r += 2; + if (c < -3) r += 4; + if (d == 8) r += 8; + return r; + } + } + + static class C4 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 4; + if (a > 4) r += 1; + if (b > 8) r += 2; + if (c < -4) r += 4; + if (d == 9) r += 8; + return r; + } + } + + static class C5 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 5; + if (a > 5) r += 1; + if (b > 10) r += 2; + if (c < -5) r += 4; + if (d == 10) r += 8; + return r; + } + } + + static class C6 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 6; + if (a > 6) r += 1; + if (b > 12) r += 2; + if (c < -6) r += 4; + if (d == 11) r += 8; + return r; + } + } + + static class C7 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 7; + if (a > 7) r += 1; + if (b > 14) r += 2; + if (c < -7) r += 4; + if (d == 12) r += 8; + return r; + } + } + + static class C8 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 8; + if (a > 8) r += 1; + if (b > 16) r += 2; + if (c < -8) r += 4; + if (d == 13) r += 8; + return r; + } + } + + static class C9 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 9; + if (a > 9) r += 1; + if (b > 18) r += 2; + if (c < -9) r += 4; + if (d == 14) r += 8; + return r; + } + } + + static class C10 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 10; + if (a > 10) r += 1; + if (b > 20) r += 2; + if (c < -10) r += 4; + if (d == 15) r += 8; + return r; + } + } + + static class C11 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 11; + if (a > 11) r += 1; + if (b > 22) r += 2; + if (c < -11) r += 4; + if (d == 16) r += 8; + return r; + } + } + + static class C12 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 12; + if (a > 12) r += 1; + if (b > 24) r += 2; + if (c < -12) r += 4; + if (d == 17) r += 8; + return r; + } + } + + static class C13 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 13; + if (a > 13) r += 1; + if (b > 26) r += 2; + if (c < -13) r += 4; + if (d == 18) r += 8; + return r; + } + } + + static class C14 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 14; + if (a > 14) r += 1; + if (b > 28) r += 2; + if (c < -14) r += 4; + if (d == 19) r += 8; + return r; + } + } + + static class C15 extends Cmd { + int run(int a, int b, int c, int d) { + target(a, b); + return 0; + } + } + + static final Cmd[] TABLE = {new C0(), new C1(), new C2(), new C3(), new C4(), new C5(), new C6(), new C7(), new C8(), new C9(), new C10(), new C11(), new C12(), new C13(), new C14(), new C15()}; + + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + if (cmd < 0 || cmd >= TABLE.length) return -1; + return TABLE[cmd].run(a, b, c, d); + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n32_false.yml b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n32_false.yml new file mode 100644 index 00000000..1e0d7de9 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n32_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - array_n32_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n32_false/Main.java b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n32_false/Main.java new file mode 100644 index 00000000..dbbb9fd5 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n32_false/Main.java @@ -0,0 +1,384 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:31 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// virtual-dispatch/array: cmd selects one of 32 subclasses, only C31.run() reaches the assert. +public class Main { + static abstract class Cmd { + abstract int run(int a, int b, int c, int d); + } + + static class C0 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + } + + static class C1 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + } + + static class C2 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + } + + static class C3 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 3; + if (a > 3) r += 1; + if (b > 6) r += 2; + if (c < -3) r += 4; + if (d == 8) r += 8; + return r; + } + } + + static class C4 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 4; + if (a > 4) r += 1; + if (b > 8) r += 2; + if (c < -4) r += 4; + if (d == 9) r += 8; + return r; + } + } + + static class C5 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 5; + if (a > 5) r += 1; + if (b > 10) r += 2; + if (c < -5) r += 4; + if (d == 10) r += 8; + return r; + } + } + + static class C6 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 6; + if (a > 6) r += 1; + if (b > 12) r += 2; + if (c < -6) r += 4; + if (d == 11) r += 8; + return r; + } + } + + static class C7 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 7; + if (a > 7) r += 1; + if (b > 14) r += 2; + if (c < -7) r += 4; + if (d == 12) r += 8; + return r; + } + } + + static class C8 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 8; + if (a > 8) r += 1; + if (b > 16) r += 2; + if (c < -8) r += 4; + if (d == 13) r += 8; + return r; + } + } + + static class C9 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 9; + if (a > 9) r += 1; + if (b > 18) r += 2; + if (c < -9) r += 4; + if (d == 14) r += 8; + return r; + } + } + + static class C10 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 10; + if (a > 10) r += 1; + if (b > 20) r += 2; + if (c < -10) r += 4; + if (d == 15) r += 8; + return r; + } + } + + static class C11 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 11; + if (a > 11) r += 1; + if (b > 22) r += 2; + if (c < -11) r += 4; + if (d == 16) r += 8; + return r; + } + } + + static class C12 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 12; + if (a > 12) r += 1; + if (b > 24) r += 2; + if (c < -12) r += 4; + if (d == 17) r += 8; + return r; + } + } + + static class C13 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 13; + if (a > 13) r += 1; + if (b > 26) r += 2; + if (c < -13) r += 4; + if (d == 18) r += 8; + return r; + } + } + + static class C14 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 14; + if (a > 14) r += 1; + if (b > 28) r += 2; + if (c < -14) r += 4; + if (d == 19) r += 8; + return r; + } + } + + static class C15 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 15; + if (a > 15) r += 1; + if (b > 30) r += 2; + if (c < -15) r += 4; + if (d == 20) r += 8; + return r; + } + } + + static class C16 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 16; + if (a > 16) r += 1; + if (b > 32) r += 2; + if (c < -16) r += 4; + if (d == 21) r += 8; + return r; + } + } + + static class C17 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 17; + if (a > 17) r += 1; + if (b > 34) r += 2; + if (c < -17) r += 4; + if (d == 22) r += 8; + return r; + } + } + + static class C18 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 18; + if (a > 18) r += 1; + if (b > 36) r += 2; + if (c < -18) r += 4; + if (d == 23) r += 8; + return r; + } + } + + static class C19 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 19; + if (a > 19) r += 1; + if (b > 38) r += 2; + if (c < -19) r += 4; + if (d == 24) r += 8; + return r; + } + } + + static class C20 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 20; + if (a > 20) r += 1; + if (b > 40) r += 2; + if (c < -20) r += 4; + if (d == 25) r += 8; + return r; + } + } + + static class C21 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 21; + if (a > 21) r += 1; + if (b > 42) r += 2; + if (c < -21) r += 4; + if (d == 26) r += 8; + return r; + } + } + + static class C22 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 22; + if (a > 22) r += 1; + if (b > 44) r += 2; + if (c < -22) r += 4; + if (d == 27) r += 8; + return r; + } + } + + static class C23 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 23; + if (a > 23) r += 1; + if (b > 46) r += 2; + if (c < -23) r += 4; + if (d == 28) r += 8; + return r; + } + } + + static class C24 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 24; + if (a > 24) r += 1; + if (b > 48) r += 2; + if (c < -24) r += 4; + if (d == 29) r += 8; + return r; + } + } + + static class C25 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 25; + if (a > 25) r += 1; + if (b > 50) r += 2; + if (c < -25) r += 4; + if (d == 30) r += 8; + return r; + } + } + + static class C26 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 26; + if (a > 26) r += 1; + if (b > 52) r += 2; + if (c < -26) r += 4; + if (d == 31) r += 8; + return r; + } + } + + static class C27 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 27; + if (a > 27) r += 1; + if (b > 54) r += 2; + if (c < -27) r += 4; + if (d == 32) r += 8; + return r; + } + } + + static class C28 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 28; + if (a > 28) r += 1; + if (b > 56) r += 2; + if (c < -28) r += 4; + if (d == 33) r += 8; + return r; + } + } + + static class C29 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 29; + if (a > 29) r += 1; + if (b > 58) r += 2; + if (c < -29) r += 4; + if (d == 34) r += 8; + return r; + } + } + + static class C30 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 30; + if (a > 30) r += 1; + if (b > 60) r += 2; + if (c < -30) r += 4; + if (d == 35) r += 8; + return r; + } + } + + static class C31 extends Cmd { + int run(int a, int b, int c, int d) { + target(a, b); + return 0; + } + } + + static final Cmd[] TABLE = {new C0(), new C1(), new C2(), new C3(), new C4(), new C5(), new C6(), new C7(), new C8(), new C9(), new C10(), new C11(), new C12(), new C13(), new C14(), new C15(), new C16(), new C17(), new C18(), new C19(), new C20(), new C21(), new C22(), new C23(), new C24(), new C25(), new C26(), new C27(), new C28(), new C29(), new C30(), new C31()}; + + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + if (cmd < 0 || cmd >= TABLE.length) return -1; + return TABLE[cmd].run(a, b, c, d); + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n32_true.yml b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n32_true.yml new file mode 100644 index 00000000..fbc5ab71 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n32_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - array_n32_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n32_true/Main.java b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n32_true/Main.java new file mode 100644 index 00000000..25406ea7 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/array_n32_true/Main.java @@ -0,0 +1,385 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:31 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// virtual-dispatch/array: cmd selects one of 32 subclasses, only C31.run() reaches the assert. +public class Main { + static abstract class Cmd { + abstract int run(int a, int b, int c, int d); + } + + static class C0 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + } + + static class C1 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + } + + static class C2 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + } + + static class C3 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 3; + if (a > 3) r += 1; + if (b > 6) r += 2; + if (c < -3) r += 4; + if (d == 8) r += 8; + return r; + } + } + + static class C4 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 4; + if (a > 4) r += 1; + if (b > 8) r += 2; + if (c < -4) r += 4; + if (d == 9) r += 8; + return r; + } + } + + static class C5 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 5; + if (a > 5) r += 1; + if (b > 10) r += 2; + if (c < -5) r += 4; + if (d == 10) r += 8; + return r; + } + } + + static class C6 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 6; + if (a > 6) r += 1; + if (b > 12) r += 2; + if (c < -6) r += 4; + if (d == 11) r += 8; + return r; + } + } + + static class C7 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 7; + if (a > 7) r += 1; + if (b > 14) r += 2; + if (c < -7) r += 4; + if (d == 12) r += 8; + return r; + } + } + + static class C8 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 8; + if (a > 8) r += 1; + if (b > 16) r += 2; + if (c < -8) r += 4; + if (d == 13) r += 8; + return r; + } + } + + static class C9 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 9; + if (a > 9) r += 1; + if (b > 18) r += 2; + if (c < -9) r += 4; + if (d == 14) r += 8; + return r; + } + } + + static class C10 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 10; + if (a > 10) r += 1; + if (b > 20) r += 2; + if (c < -10) r += 4; + if (d == 15) r += 8; + return r; + } + } + + static class C11 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 11; + if (a > 11) r += 1; + if (b > 22) r += 2; + if (c < -11) r += 4; + if (d == 16) r += 8; + return r; + } + } + + static class C12 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 12; + if (a > 12) r += 1; + if (b > 24) r += 2; + if (c < -12) r += 4; + if (d == 17) r += 8; + return r; + } + } + + static class C13 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 13; + if (a > 13) r += 1; + if (b > 26) r += 2; + if (c < -13) r += 4; + if (d == 18) r += 8; + return r; + } + } + + static class C14 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 14; + if (a > 14) r += 1; + if (b > 28) r += 2; + if (c < -14) r += 4; + if (d == 19) r += 8; + return r; + } + } + + static class C15 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 15; + if (a > 15) r += 1; + if (b > 30) r += 2; + if (c < -15) r += 4; + if (d == 20) r += 8; + return r; + } + } + + static class C16 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 16; + if (a > 16) r += 1; + if (b > 32) r += 2; + if (c < -16) r += 4; + if (d == 21) r += 8; + return r; + } + } + + static class C17 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 17; + if (a > 17) r += 1; + if (b > 34) r += 2; + if (c < -17) r += 4; + if (d == 22) r += 8; + return r; + } + } + + static class C18 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 18; + if (a > 18) r += 1; + if (b > 36) r += 2; + if (c < -18) r += 4; + if (d == 23) r += 8; + return r; + } + } + + static class C19 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 19; + if (a > 19) r += 1; + if (b > 38) r += 2; + if (c < -19) r += 4; + if (d == 24) r += 8; + return r; + } + } + + static class C20 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 20; + if (a > 20) r += 1; + if (b > 40) r += 2; + if (c < -20) r += 4; + if (d == 25) r += 8; + return r; + } + } + + static class C21 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 21; + if (a > 21) r += 1; + if (b > 42) r += 2; + if (c < -21) r += 4; + if (d == 26) r += 8; + return r; + } + } + + static class C22 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 22; + if (a > 22) r += 1; + if (b > 44) r += 2; + if (c < -22) r += 4; + if (d == 27) r += 8; + return r; + } + } + + static class C23 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 23; + if (a > 23) r += 1; + if (b > 46) r += 2; + if (c < -23) r += 4; + if (d == 28) r += 8; + return r; + } + } + + static class C24 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 24; + if (a > 24) r += 1; + if (b > 48) r += 2; + if (c < -24) r += 4; + if (d == 29) r += 8; + return r; + } + } + + static class C25 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 25; + if (a > 25) r += 1; + if (b > 50) r += 2; + if (c < -25) r += 4; + if (d == 30) r += 8; + return r; + } + } + + static class C26 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 26; + if (a > 26) r += 1; + if (b > 52) r += 2; + if (c < -26) r += 4; + if (d == 31) r += 8; + return r; + } + } + + static class C27 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 27; + if (a > 27) r += 1; + if (b > 54) r += 2; + if (c < -27) r += 4; + if (d == 32) r += 8; + return r; + } + } + + static class C28 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 28; + if (a > 28) r += 1; + if (b > 56) r += 2; + if (c < -28) r += 4; + if (d == 33) r += 8; + return r; + } + } + + static class C29 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 29; + if (a > 29) r += 1; + if (b > 58) r += 2; + if (c < -29) r += 4; + if (d == 34) r += 8; + return r; + } + } + + static class C30 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 30; + if (a > 30) r += 1; + if (b > 60) r += 2; + if (c < -30) r += 4; + if (d == 35) r += 8; + return r; + } + } + + static class C31 extends Cmd { + int run(int a, int b, int c, int d) { + target(a, b); + return 0; + } + } + + static final Cmd[] TABLE = {new C0(), new C1(), new C2(), new C3(), new C4(), new C5(), new C6(), new C7(), new C8(), new C9(), new C10(), new C11(), new C12(), new C13(), new C14(), new C15(), new C16(), new C17(), new C18(), new C19(), new C20(), new C21(), new C22(), new C23(), new C24(), new C25(), new C26(), new C27(), new C28(), new C29(), new C30(), new C31()}; + + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + if (cmd < 0 || cmd >= TABLE.length) return -1; + return TABLE[cmd].run(a, b, c, d); + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n02_false.yml b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n02_false.yml new file mode 100644 index 00000000..479a2b76 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n02_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - factory_n02_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n02_false/Main.java b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n02_false/Main.java new file mode 100644 index 00000000..fcdadaa7 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n02_false/Main.java @@ -0,0 +1,61 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// virtual-dispatch/factory: cmd selects one of 2 subclasses, only C1.run() reaches the assert. +public class Main { + static abstract class Cmd { + abstract int run(int a, int b, int c, int d); + } + + static class C0 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + } + + static class C1 extends Cmd { + int run(int a, int b, int c, int d) { + target(a, b); + return 0; + } + } + + static Cmd make(int cmd) { + switch (cmd) { + case 0: return new C0(); + case 1: return new C1(); + default: return null; + } + } + + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + Cmd command = make(cmd); + if (command == null) return -1; + return command.run(a, b, c, d); + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n02_true.yml b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n02_true.yml new file mode 100644 index 00000000..d35003c9 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n02_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - factory_n02_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n02_true/Main.java b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n02_true/Main.java new file mode 100644 index 00000000..59f3893a --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n02_true/Main.java @@ -0,0 +1,62 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:1 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// virtual-dispatch/factory: cmd selects one of 2 subclasses, only C1.run() reaches the assert. +public class Main { + static abstract class Cmd { + abstract int run(int a, int b, int c, int d); + } + + static class C0 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + } + + static class C1 extends Cmd { + int run(int a, int b, int c, int d) { + target(a, b); + return 0; + } + } + + static Cmd make(int cmd) { + switch (cmd) { + case 0: return new C0(); + case 1: return new C1(); + default: return null; + } + } + + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + Cmd command = make(cmd); + if (command == null) return -1; + return command.run(a, b, c, d); + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n04_false.yml b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n04_false.yml new file mode 100644 index 00000000..f95d21aa --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n04_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - factory_n04_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n04_false/Main.java b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n04_false/Main.java new file mode 100644 index 00000000..4478905a --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n04_false/Main.java @@ -0,0 +1,85 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:3 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// virtual-dispatch/factory: cmd selects one of 4 subclasses, only C3.run() reaches the assert. +public class Main { + static abstract class Cmd { + abstract int run(int a, int b, int c, int d); + } + + static class C0 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + } + + static class C1 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + } + + static class C2 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + } + + static class C3 extends Cmd { + int run(int a, int b, int c, int d) { + target(a, b); + return 0; + } + } + + static Cmd make(int cmd) { + switch (cmd) { + case 0: return new C0(); + case 1: return new C1(); + case 2: return new C2(); + case 3: return new C3(); + default: return null; + } + } + + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + Cmd command = make(cmd); + if (command == null) return -1; + return command.run(a, b, c, d); + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n04_true.yml b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n04_true.yml new file mode 100644 index 00000000..7b3707a1 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n04_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - factory_n04_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n04_true/Main.java b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n04_true/Main.java new file mode 100644 index 00000000..2b474b5d --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n04_true/Main.java @@ -0,0 +1,86 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:3 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// virtual-dispatch/factory: cmd selects one of 4 subclasses, only C3.run() reaches the assert. +public class Main { + static abstract class Cmd { + abstract int run(int a, int b, int c, int d); + } + + static class C0 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + } + + static class C1 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + } + + static class C2 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + } + + static class C3 extends Cmd { + int run(int a, int b, int c, int d) { + target(a, b); + return 0; + } + } + + static Cmd make(int cmd) { + switch (cmd) { + case 0: return new C0(); + case 1: return new C1(); + case 2: return new C2(); + case 3: return new C3(); + default: return null; + } + } + + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + Cmd command = make(cmd); + if (command == null) return -1; + return command.run(a, b, c, d); + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n08_false.yml b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n08_false.yml new file mode 100644 index 00000000..952b5d42 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n08_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - factory_n08_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n08_false/Main.java b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n08_false/Main.java new file mode 100644 index 00000000..95d71dc8 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n08_false/Main.java @@ -0,0 +1,133 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:7 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// virtual-dispatch/factory: cmd selects one of 8 subclasses, only C7.run() reaches the assert. +public class Main { + static abstract class Cmd { + abstract int run(int a, int b, int c, int d); + } + + static class C0 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + } + + static class C1 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + } + + static class C2 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + } + + static class C3 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 3; + if (a > 3) r += 1; + if (b > 6) r += 2; + if (c < -3) r += 4; + if (d == 8) r += 8; + return r; + } + } + + static class C4 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 4; + if (a > 4) r += 1; + if (b > 8) r += 2; + if (c < -4) r += 4; + if (d == 9) r += 8; + return r; + } + } + + static class C5 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 5; + if (a > 5) r += 1; + if (b > 10) r += 2; + if (c < -5) r += 4; + if (d == 10) r += 8; + return r; + } + } + + static class C6 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 6; + if (a > 6) r += 1; + if (b > 12) r += 2; + if (c < -6) r += 4; + if (d == 11) r += 8; + return r; + } + } + + static class C7 extends Cmd { + int run(int a, int b, int c, int d) { + target(a, b); + return 0; + } + } + + static Cmd make(int cmd) { + switch (cmd) { + case 0: return new C0(); + case 1: return new C1(); + case 2: return new C2(); + case 3: return new C3(); + case 4: return new C4(); + case 5: return new C5(); + case 6: return new C6(); + case 7: return new C7(); + default: return null; + } + } + + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + Cmd command = make(cmd); + if (command == null) return -1; + return command.run(a, b, c, d); + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n08_true.yml b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n08_true.yml new file mode 100644 index 00000000..27b21645 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n08_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - factory_n08_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n08_true/Main.java b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n08_true/Main.java new file mode 100644 index 00000000..2b6650b2 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n08_true/Main.java @@ -0,0 +1,134 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:7 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// virtual-dispatch/factory: cmd selects one of 8 subclasses, only C7.run() reaches the assert. +public class Main { + static abstract class Cmd { + abstract int run(int a, int b, int c, int d); + } + + static class C0 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + } + + static class C1 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + } + + static class C2 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + } + + static class C3 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 3; + if (a > 3) r += 1; + if (b > 6) r += 2; + if (c < -3) r += 4; + if (d == 8) r += 8; + return r; + } + } + + static class C4 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 4; + if (a > 4) r += 1; + if (b > 8) r += 2; + if (c < -4) r += 4; + if (d == 9) r += 8; + return r; + } + } + + static class C5 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 5; + if (a > 5) r += 1; + if (b > 10) r += 2; + if (c < -5) r += 4; + if (d == 10) r += 8; + return r; + } + } + + static class C6 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 6; + if (a > 6) r += 1; + if (b > 12) r += 2; + if (c < -6) r += 4; + if (d == 11) r += 8; + return r; + } + } + + static class C7 extends Cmd { + int run(int a, int b, int c, int d) { + target(a, b); + return 0; + } + } + + static Cmd make(int cmd) { + switch (cmd) { + case 0: return new C0(); + case 1: return new C1(); + case 2: return new C2(); + case 3: return new C3(); + case 4: return new C4(); + case 5: return new C5(); + case 6: return new C6(); + case 7: return new C7(); + default: return null; + } + } + + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + Cmd command = make(cmd); + if (command == null) return -1; + return command.run(a, b, c, d); + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n16_false.yml b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n16_false.yml new file mode 100644 index 00000000..339f62a2 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n16_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - factory_n16_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n16_false/Main.java b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n16_false/Main.java new file mode 100644 index 00000000..2791cde2 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n16_false/Main.java @@ -0,0 +1,229 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:15 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// virtual-dispatch/factory: cmd selects one of 16 subclasses, only C15.run() reaches the assert. +public class Main { + static abstract class Cmd { + abstract int run(int a, int b, int c, int d); + } + + static class C0 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + } + + static class C1 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + } + + static class C2 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + } + + static class C3 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 3; + if (a > 3) r += 1; + if (b > 6) r += 2; + if (c < -3) r += 4; + if (d == 8) r += 8; + return r; + } + } + + static class C4 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 4; + if (a > 4) r += 1; + if (b > 8) r += 2; + if (c < -4) r += 4; + if (d == 9) r += 8; + return r; + } + } + + static class C5 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 5; + if (a > 5) r += 1; + if (b > 10) r += 2; + if (c < -5) r += 4; + if (d == 10) r += 8; + return r; + } + } + + static class C6 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 6; + if (a > 6) r += 1; + if (b > 12) r += 2; + if (c < -6) r += 4; + if (d == 11) r += 8; + return r; + } + } + + static class C7 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 7; + if (a > 7) r += 1; + if (b > 14) r += 2; + if (c < -7) r += 4; + if (d == 12) r += 8; + return r; + } + } + + static class C8 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 8; + if (a > 8) r += 1; + if (b > 16) r += 2; + if (c < -8) r += 4; + if (d == 13) r += 8; + return r; + } + } + + static class C9 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 9; + if (a > 9) r += 1; + if (b > 18) r += 2; + if (c < -9) r += 4; + if (d == 14) r += 8; + return r; + } + } + + static class C10 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 10; + if (a > 10) r += 1; + if (b > 20) r += 2; + if (c < -10) r += 4; + if (d == 15) r += 8; + return r; + } + } + + static class C11 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 11; + if (a > 11) r += 1; + if (b > 22) r += 2; + if (c < -11) r += 4; + if (d == 16) r += 8; + return r; + } + } + + static class C12 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 12; + if (a > 12) r += 1; + if (b > 24) r += 2; + if (c < -12) r += 4; + if (d == 17) r += 8; + return r; + } + } + + static class C13 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 13; + if (a > 13) r += 1; + if (b > 26) r += 2; + if (c < -13) r += 4; + if (d == 18) r += 8; + return r; + } + } + + static class C14 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 14; + if (a > 14) r += 1; + if (b > 28) r += 2; + if (c < -14) r += 4; + if (d == 19) r += 8; + return r; + } + } + + static class C15 extends Cmd { + int run(int a, int b, int c, int d) { + target(a, b); + return 0; + } + } + + static Cmd make(int cmd) { + switch (cmd) { + case 0: return new C0(); + case 1: return new C1(); + case 2: return new C2(); + case 3: return new C3(); + case 4: return new C4(); + case 5: return new C5(); + case 6: return new C6(); + case 7: return new C7(); + case 8: return new C8(); + case 9: return new C9(); + case 10: return new C10(); + case 11: return new C11(); + case 12: return new C12(); + case 13: return new C13(); + case 14: return new C14(); + case 15: return new C15(); + default: return null; + } + } + + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + Cmd command = make(cmd); + if (command == null) return -1; + return command.run(a, b, c, d); + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n16_true.yml b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n16_true.yml new file mode 100644 index 00000000..9d1d2e93 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n16_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - factory_n16_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n16_true/Main.java b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n16_true/Main.java new file mode 100644 index 00000000..86590bdc --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n16_true/Main.java @@ -0,0 +1,230 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:15 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// virtual-dispatch/factory: cmd selects one of 16 subclasses, only C15.run() reaches the assert. +public class Main { + static abstract class Cmd { + abstract int run(int a, int b, int c, int d); + } + + static class C0 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + } + + static class C1 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + } + + static class C2 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + } + + static class C3 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 3; + if (a > 3) r += 1; + if (b > 6) r += 2; + if (c < -3) r += 4; + if (d == 8) r += 8; + return r; + } + } + + static class C4 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 4; + if (a > 4) r += 1; + if (b > 8) r += 2; + if (c < -4) r += 4; + if (d == 9) r += 8; + return r; + } + } + + static class C5 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 5; + if (a > 5) r += 1; + if (b > 10) r += 2; + if (c < -5) r += 4; + if (d == 10) r += 8; + return r; + } + } + + static class C6 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 6; + if (a > 6) r += 1; + if (b > 12) r += 2; + if (c < -6) r += 4; + if (d == 11) r += 8; + return r; + } + } + + static class C7 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 7; + if (a > 7) r += 1; + if (b > 14) r += 2; + if (c < -7) r += 4; + if (d == 12) r += 8; + return r; + } + } + + static class C8 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 8; + if (a > 8) r += 1; + if (b > 16) r += 2; + if (c < -8) r += 4; + if (d == 13) r += 8; + return r; + } + } + + static class C9 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 9; + if (a > 9) r += 1; + if (b > 18) r += 2; + if (c < -9) r += 4; + if (d == 14) r += 8; + return r; + } + } + + static class C10 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 10; + if (a > 10) r += 1; + if (b > 20) r += 2; + if (c < -10) r += 4; + if (d == 15) r += 8; + return r; + } + } + + static class C11 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 11; + if (a > 11) r += 1; + if (b > 22) r += 2; + if (c < -11) r += 4; + if (d == 16) r += 8; + return r; + } + } + + static class C12 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 12; + if (a > 12) r += 1; + if (b > 24) r += 2; + if (c < -12) r += 4; + if (d == 17) r += 8; + return r; + } + } + + static class C13 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 13; + if (a > 13) r += 1; + if (b > 26) r += 2; + if (c < -13) r += 4; + if (d == 18) r += 8; + return r; + } + } + + static class C14 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 14; + if (a > 14) r += 1; + if (b > 28) r += 2; + if (c < -14) r += 4; + if (d == 19) r += 8; + return r; + } + } + + static class C15 extends Cmd { + int run(int a, int b, int c, int d) { + target(a, b); + return 0; + } + } + + static Cmd make(int cmd) { + switch (cmd) { + case 0: return new C0(); + case 1: return new C1(); + case 2: return new C2(); + case 3: return new C3(); + case 4: return new C4(); + case 5: return new C5(); + case 6: return new C6(); + case 7: return new C7(); + case 8: return new C8(); + case 9: return new C9(); + case 10: return new C10(); + case 11: return new C11(); + case 12: return new C12(); + case 13: return new C13(); + case 14: return new C14(); + case 15: return new C15(); + default: return null; + } + } + + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + Cmd command = make(cmd); + if (command == null) return -1; + return command.run(a, b, c, d); + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n32_false.yml b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n32_false.yml new file mode 100644 index 00000000..d288fc0c --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n32_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - factory_n32_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n32_false/Main.java b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n32_false/Main.java new file mode 100644 index 00000000..0d839110 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n32_false/Main.java @@ -0,0 +1,421 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:31 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// virtual-dispatch/factory: cmd selects one of 32 subclasses, only C31.run() reaches the assert. +public class Main { + static abstract class Cmd { + abstract int run(int a, int b, int c, int d); + } + + static class C0 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + } + + static class C1 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + } + + static class C2 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + } + + static class C3 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 3; + if (a > 3) r += 1; + if (b > 6) r += 2; + if (c < -3) r += 4; + if (d == 8) r += 8; + return r; + } + } + + static class C4 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 4; + if (a > 4) r += 1; + if (b > 8) r += 2; + if (c < -4) r += 4; + if (d == 9) r += 8; + return r; + } + } + + static class C5 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 5; + if (a > 5) r += 1; + if (b > 10) r += 2; + if (c < -5) r += 4; + if (d == 10) r += 8; + return r; + } + } + + static class C6 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 6; + if (a > 6) r += 1; + if (b > 12) r += 2; + if (c < -6) r += 4; + if (d == 11) r += 8; + return r; + } + } + + static class C7 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 7; + if (a > 7) r += 1; + if (b > 14) r += 2; + if (c < -7) r += 4; + if (d == 12) r += 8; + return r; + } + } + + static class C8 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 8; + if (a > 8) r += 1; + if (b > 16) r += 2; + if (c < -8) r += 4; + if (d == 13) r += 8; + return r; + } + } + + static class C9 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 9; + if (a > 9) r += 1; + if (b > 18) r += 2; + if (c < -9) r += 4; + if (d == 14) r += 8; + return r; + } + } + + static class C10 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 10; + if (a > 10) r += 1; + if (b > 20) r += 2; + if (c < -10) r += 4; + if (d == 15) r += 8; + return r; + } + } + + static class C11 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 11; + if (a > 11) r += 1; + if (b > 22) r += 2; + if (c < -11) r += 4; + if (d == 16) r += 8; + return r; + } + } + + static class C12 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 12; + if (a > 12) r += 1; + if (b > 24) r += 2; + if (c < -12) r += 4; + if (d == 17) r += 8; + return r; + } + } + + static class C13 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 13; + if (a > 13) r += 1; + if (b > 26) r += 2; + if (c < -13) r += 4; + if (d == 18) r += 8; + return r; + } + } + + static class C14 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 14; + if (a > 14) r += 1; + if (b > 28) r += 2; + if (c < -14) r += 4; + if (d == 19) r += 8; + return r; + } + } + + static class C15 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 15; + if (a > 15) r += 1; + if (b > 30) r += 2; + if (c < -15) r += 4; + if (d == 20) r += 8; + return r; + } + } + + static class C16 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 16; + if (a > 16) r += 1; + if (b > 32) r += 2; + if (c < -16) r += 4; + if (d == 21) r += 8; + return r; + } + } + + static class C17 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 17; + if (a > 17) r += 1; + if (b > 34) r += 2; + if (c < -17) r += 4; + if (d == 22) r += 8; + return r; + } + } + + static class C18 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 18; + if (a > 18) r += 1; + if (b > 36) r += 2; + if (c < -18) r += 4; + if (d == 23) r += 8; + return r; + } + } + + static class C19 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 19; + if (a > 19) r += 1; + if (b > 38) r += 2; + if (c < -19) r += 4; + if (d == 24) r += 8; + return r; + } + } + + static class C20 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 20; + if (a > 20) r += 1; + if (b > 40) r += 2; + if (c < -20) r += 4; + if (d == 25) r += 8; + return r; + } + } + + static class C21 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 21; + if (a > 21) r += 1; + if (b > 42) r += 2; + if (c < -21) r += 4; + if (d == 26) r += 8; + return r; + } + } + + static class C22 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 22; + if (a > 22) r += 1; + if (b > 44) r += 2; + if (c < -22) r += 4; + if (d == 27) r += 8; + return r; + } + } + + static class C23 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 23; + if (a > 23) r += 1; + if (b > 46) r += 2; + if (c < -23) r += 4; + if (d == 28) r += 8; + return r; + } + } + + static class C24 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 24; + if (a > 24) r += 1; + if (b > 48) r += 2; + if (c < -24) r += 4; + if (d == 29) r += 8; + return r; + } + } + + static class C25 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 25; + if (a > 25) r += 1; + if (b > 50) r += 2; + if (c < -25) r += 4; + if (d == 30) r += 8; + return r; + } + } + + static class C26 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 26; + if (a > 26) r += 1; + if (b > 52) r += 2; + if (c < -26) r += 4; + if (d == 31) r += 8; + return r; + } + } + + static class C27 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 27; + if (a > 27) r += 1; + if (b > 54) r += 2; + if (c < -27) r += 4; + if (d == 32) r += 8; + return r; + } + } + + static class C28 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 28; + if (a > 28) r += 1; + if (b > 56) r += 2; + if (c < -28) r += 4; + if (d == 33) r += 8; + return r; + } + } + + static class C29 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 29; + if (a > 29) r += 1; + if (b > 58) r += 2; + if (c < -29) r += 4; + if (d == 34) r += 8; + return r; + } + } + + static class C30 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 30; + if (a > 30) r += 1; + if (b > 60) r += 2; + if (c < -30) r += 4; + if (d == 35) r += 8; + return r; + } + } + + static class C31 extends Cmd { + int run(int a, int b, int c, int d) { + target(a, b); + return 0; + } + } + + static Cmd make(int cmd) { + switch (cmd) { + case 0: return new C0(); + case 1: return new C1(); + case 2: return new C2(); + case 3: return new C3(); + case 4: return new C4(); + case 5: return new C5(); + case 6: return new C6(); + case 7: return new C7(); + case 8: return new C8(); + case 9: return new C9(); + case 10: return new C10(); + case 11: return new C11(); + case 12: return new C12(); + case 13: return new C13(); + case 14: return new C14(); + case 15: return new C15(); + case 16: return new C16(); + case 17: return new C17(); + case 18: return new C18(); + case 19: return new C19(); + case 20: return new C20(); + case 21: return new C21(); + case 22: return new C22(); + case 23: return new C23(); + case 24: return new C24(); + case 25: return new C25(); + case 26: return new C26(); + case 27: return new C27(); + case 28: return new C28(); + case 29: return new C29(); + case 30: return new C30(); + case 31: return new C31(); + default: return null; + } + } + + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + Cmd command = make(cmd); + if (command == null) return -1; + return command.run(a, b, c, d); + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n32_true.yml b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n32_true.yml new file mode 100644 index 00000000..4c14352b --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n32_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - factory_n32_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n32_true/Main.java b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n32_true/Main.java new file mode 100644 index 00000000..a0fa899c --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/factory_n32_true/Main.java @@ -0,0 +1,422 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:31 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// virtual-dispatch/factory: cmd selects one of 32 subclasses, only C31.run() reaches the assert. +public class Main { + static abstract class Cmd { + abstract int run(int a, int b, int c, int d); + } + + static class C0 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + } + + static class C1 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + } + + static class C2 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + } + + static class C3 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 3; + if (a > 3) r += 1; + if (b > 6) r += 2; + if (c < -3) r += 4; + if (d == 8) r += 8; + return r; + } + } + + static class C4 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 4; + if (a > 4) r += 1; + if (b > 8) r += 2; + if (c < -4) r += 4; + if (d == 9) r += 8; + return r; + } + } + + static class C5 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 5; + if (a > 5) r += 1; + if (b > 10) r += 2; + if (c < -5) r += 4; + if (d == 10) r += 8; + return r; + } + } + + static class C6 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 6; + if (a > 6) r += 1; + if (b > 12) r += 2; + if (c < -6) r += 4; + if (d == 11) r += 8; + return r; + } + } + + static class C7 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 7; + if (a > 7) r += 1; + if (b > 14) r += 2; + if (c < -7) r += 4; + if (d == 12) r += 8; + return r; + } + } + + static class C8 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 8; + if (a > 8) r += 1; + if (b > 16) r += 2; + if (c < -8) r += 4; + if (d == 13) r += 8; + return r; + } + } + + static class C9 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 9; + if (a > 9) r += 1; + if (b > 18) r += 2; + if (c < -9) r += 4; + if (d == 14) r += 8; + return r; + } + } + + static class C10 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 10; + if (a > 10) r += 1; + if (b > 20) r += 2; + if (c < -10) r += 4; + if (d == 15) r += 8; + return r; + } + } + + static class C11 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 11; + if (a > 11) r += 1; + if (b > 22) r += 2; + if (c < -11) r += 4; + if (d == 16) r += 8; + return r; + } + } + + static class C12 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 12; + if (a > 12) r += 1; + if (b > 24) r += 2; + if (c < -12) r += 4; + if (d == 17) r += 8; + return r; + } + } + + static class C13 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 13; + if (a > 13) r += 1; + if (b > 26) r += 2; + if (c < -13) r += 4; + if (d == 18) r += 8; + return r; + } + } + + static class C14 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 14; + if (a > 14) r += 1; + if (b > 28) r += 2; + if (c < -14) r += 4; + if (d == 19) r += 8; + return r; + } + } + + static class C15 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 15; + if (a > 15) r += 1; + if (b > 30) r += 2; + if (c < -15) r += 4; + if (d == 20) r += 8; + return r; + } + } + + static class C16 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 16; + if (a > 16) r += 1; + if (b > 32) r += 2; + if (c < -16) r += 4; + if (d == 21) r += 8; + return r; + } + } + + static class C17 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 17; + if (a > 17) r += 1; + if (b > 34) r += 2; + if (c < -17) r += 4; + if (d == 22) r += 8; + return r; + } + } + + static class C18 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 18; + if (a > 18) r += 1; + if (b > 36) r += 2; + if (c < -18) r += 4; + if (d == 23) r += 8; + return r; + } + } + + static class C19 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 19; + if (a > 19) r += 1; + if (b > 38) r += 2; + if (c < -19) r += 4; + if (d == 24) r += 8; + return r; + } + } + + static class C20 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 20; + if (a > 20) r += 1; + if (b > 40) r += 2; + if (c < -20) r += 4; + if (d == 25) r += 8; + return r; + } + } + + static class C21 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 21; + if (a > 21) r += 1; + if (b > 42) r += 2; + if (c < -21) r += 4; + if (d == 26) r += 8; + return r; + } + } + + static class C22 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 22; + if (a > 22) r += 1; + if (b > 44) r += 2; + if (c < -22) r += 4; + if (d == 27) r += 8; + return r; + } + } + + static class C23 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 23; + if (a > 23) r += 1; + if (b > 46) r += 2; + if (c < -23) r += 4; + if (d == 28) r += 8; + return r; + } + } + + static class C24 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 24; + if (a > 24) r += 1; + if (b > 48) r += 2; + if (c < -24) r += 4; + if (d == 29) r += 8; + return r; + } + } + + static class C25 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 25; + if (a > 25) r += 1; + if (b > 50) r += 2; + if (c < -25) r += 4; + if (d == 30) r += 8; + return r; + } + } + + static class C26 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 26; + if (a > 26) r += 1; + if (b > 52) r += 2; + if (c < -26) r += 4; + if (d == 31) r += 8; + return r; + } + } + + static class C27 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 27; + if (a > 27) r += 1; + if (b > 54) r += 2; + if (c < -27) r += 4; + if (d == 32) r += 8; + return r; + } + } + + static class C28 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 28; + if (a > 28) r += 1; + if (b > 56) r += 2; + if (c < -28) r += 4; + if (d == 33) r += 8; + return r; + } + } + + static class C29 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 29; + if (a > 29) r += 1; + if (b > 58) r += 2; + if (c < -29) r += 4; + if (d == 34) r += 8; + return r; + } + } + + static class C30 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 30; + if (a > 30) r += 1; + if (b > 60) r += 2; + if (c < -30) r += 4; + if (d == 35) r += 8; + return r; + } + } + + static class C31 extends Cmd { + int run(int a, int b, int c, int d) { + target(a, b); + return 0; + } + } + + static Cmd make(int cmd) { + switch (cmd) { + case 0: return new C0(); + case 1: return new C1(); + case 2: return new C2(); + case 3: return new C3(); + case 4: return new C4(); + case 5: return new C5(); + case 6: return new C6(); + case 7: return new C7(); + case 8: return new C8(); + case 9: return new C9(); + case 10: return new C10(); + case 11: return new C11(); + case 12: return new C12(); + case 13: return new C13(); + case 14: return new C14(); + case 15: return new C15(); + case 16: return new C16(); + case 17: return new C17(); + case 18: return new C18(); + case 19: return new C19(); + case 20: return new C20(); + case 21: return new C21(); + case 22: return new C22(); + case 23: return new C23(); + case 24: return new C24(); + case 25: return new C25(); + case 26: return new C26(); + case 27: return new C27(); + case 28: return new C28(); + case 29: return new C29(); + case 30: return new C30(); + case 31: return new C31(); + default: return null; + } + } + + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + Cmd command = make(cmd); + if (command == null) return -1; + return command.run(a, b, c, d); + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n02_false.yml b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n02_false.yml new file mode 100644 index 00000000..8cda0405 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n02_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - local_n02_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n02_false/Main.java b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n02_false/Main.java new file mode 100644 index 00000000..428b562e --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n02_false/Main.java @@ -0,0 +1,55 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:1 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// virtual-dispatch/local: cmd selects one of 2 subclasses, only C1.run() reaches the assert. +public class Main { + static abstract class Cmd { + abstract int run(int a, int b, int c, int d); + } + + static class C0 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + } + + static class C1 extends Cmd { + int run(int a, int b, int c, int d) { + target(a, b); + return 0; + } + } + + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + switch (cmd) { + case 0: return new C0().run(a, b, c, d); + case 1: return new C1().run(a, b, c, d); + default: return -1; + } + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n02_true.yml b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n02_true.yml new file mode 100644 index 00000000..a7f93b7e --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n02_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - local_n02_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n02_true/Main.java b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n02_true/Main.java new file mode 100644 index 00000000..db6e20d5 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n02_true/Main.java @@ -0,0 +1,56 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:1 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// virtual-dispatch/local: cmd selects one of 2 subclasses, only C1.run() reaches the assert. +public class Main { + static abstract class Cmd { + abstract int run(int a, int b, int c, int d); + } + + static class C0 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + } + + static class C1 extends Cmd { + int run(int a, int b, int c, int d) { + target(a, b); + return 0; + } + } + + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + switch (cmd) { + case 0: return new C0().run(a, b, c, d); + case 1: return new C1().run(a, b, c, d); + default: return -1; + } + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n04_false.yml b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n04_false.yml new file mode 100644 index 00000000..f595253f --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n04_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - local_n04_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n04_false/Main.java b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n04_false/Main.java new file mode 100644 index 00000000..98b5e0fc --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n04_false/Main.java @@ -0,0 +1,79 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:3 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// virtual-dispatch/local: cmd selects one of 4 subclasses, only C3.run() reaches the assert. +public class Main { + static abstract class Cmd { + abstract int run(int a, int b, int c, int d); + } + + static class C0 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + } + + static class C1 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + } + + static class C2 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + } + + static class C3 extends Cmd { + int run(int a, int b, int c, int d) { + target(a, b); + return 0; + } + } + + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + switch (cmd) { + case 0: return new C0().run(a, b, c, d); + case 1: return new C1().run(a, b, c, d); + case 2: return new C2().run(a, b, c, d); + case 3: return new C3().run(a, b, c, d); + default: return -1; + } + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n04_true.yml b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n04_true.yml new file mode 100644 index 00000000..f980ad34 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n04_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - local_n04_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n04_true/Main.java b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n04_true/Main.java new file mode 100644 index 00000000..42a57d47 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n04_true/Main.java @@ -0,0 +1,80 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:3 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// virtual-dispatch/local: cmd selects one of 4 subclasses, only C3.run() reaches the assert. +public class Main { + static abstract class Cmd { + abstract int run(int a, int b, int c, int d); + } + + static class C0 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + } + + static class C1 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + } + + static class C2 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + } + + static class C3 extends Cmd { + int run(int a, int b, int c, int d) { + target(a, b); + return 0; + } + } + + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + switch (cmd) { + case 0: return new C0().run(a, b, c, d); + case 1: return new C1().run(a, b, c, d); + case 2: return new C2().run(a, b, c, d); + case 3: return new C3().run(a, b, c, d); + default: return -1; + } + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n08_false.yml b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n08_false.yml new file mode 100644 index 00000000..7d1580ac --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n08_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - local_n08_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n08_false/Main.java b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n08_false/Main.java new file mode 100644 index 00000000..671efe20 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n08_false/Main.java @@ -0,0 +1,127 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:7 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// virtual-dispatch/local: cmd selects one of 8 subclasses, only C7.run() reaches the assert. +public class Main { + static abstract class Cmd { + abstract int run(int a, int b, int c, int d); + } + + static class C0 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + } + + static class C1 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + } + + static class C2 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + } + + static class C3 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 3; + if (a > 3) r += 1; + if (b > 6) r += 2; + if (c < -3) r += 4; + if (d == 8) r += 8; + return r; + } + } + + static class C4 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 4; + if (a > 4) r += 1; + if (b > 8) r += 2; + if (c < -4) r += 4; + if (d == 9) r += 8; + return r; + } + } + + static class C5 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 5; + if (a > 5) r += 1; + if (b > 10) r += 2; + if (c < -5) r += 4; + if (d == 10) r += 8; + return r; + } + } + + static class C6 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 6; + if (a > 6) r += 1; + if (b > 12) r += 2; + if (c < -6) r += 4; + if (d == 11) r += 8; + return r; + } + } + + static class C7 extends Cmd { + int run(int a, int b, int c, int d) { + target(a, b); + return 0; + } + } + + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + switch (cmd) { + case 0: return new C0().run(a, b, c, d); + case 1: return new C1().run(a, b, c, d); + case 2: return new C2().run(a, b, c, d); + case 3: return new C3().run(a, b, c, d); + case 4: return new C4().run(a, b, c, d); + case 5: return new C5().run(a, b, c, d); + case 6: return new C6().run(a, b, c, d); + case 7: return new C7().run(a, b, c, d); + default: return -1; + } + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n08_true.yml b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n08_true.yml new file mode 100644 index 00000000..4b28b60a --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n08_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - local_n08_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n08_true/Main.java b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n08_true/Main.java new file mode 100644 index 00000000..e65a10c8 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n08_true/Main.java @@ -0,0 +1,128 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:7 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// virtual-dispatch/local: cmd selects one of 8 subclasses, only C7.run() reaches the assert. +public class Main { + static abstract class Cmd { + abstract int run(int a, int b, int c, int d); + } + + static class C0 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + } + + static class C1 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + } + + static class C2 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + } + + static class C3 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 3; + if (a > 3) r += 1; + if (b > 6) r += 2; + if (c < -3) r += 4; + if (d == 8) r += 8; + return r; + } + } + + static class C4 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 4; + if (a > 4) r += 1; + if (b > 8) r += 2; + if (c < -4) r += 4; + if (d == 9) r += 8; + return r; + } + } + + static class C5 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 5; + if (a > 5) r += 1; + if (b > 10) r += 2; + if (c < -5) r += 4; + if (d == 10) r += 8; + return r; + } + } + + static class C6 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 6; + if (a > 6) r += 1; + if (b > 12) r += 2; + if (c < -6) r += 4; + if (d == 11) r += 8; + return r; + } + } + + static class C7 extends Cmd { + int run(int a, int b, int c, int d) { + target(a, b); + return 0; + } + } + + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + switch (cmd) { + case 0: return new C0().run(a, b, c, d); + case 1: return new C1().run(a, b, c, d); + case 2: return new C2().run(a, b, c, d); + case 3: return new C3().run(a, b, c, d); + case 4: return new C4().run(a, b, c, d); + case 5: return new C5().run(a, b, c, d); + case 6: return new C6().run(a, b, c, d); + case 7: return new C7().run(a, b, c, d); + default: return -1; + } + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n16_false.yml b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n16_false.yml new file mode 100644 index 00000000..d17e0af5 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n16_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - local_n16_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n16_false/Main.java b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n16_false/Main.java new file mode 100644 index 00000000..65d5690d --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n16_false/Main.java @@ -0,0 +1,223 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:15 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// virtual-dispatch/local: cmd selects one of 16 subclasses, only C15.run() reaches the assert. +public class Main { + static abstract class Cmd { + abstract int run(int a, int b, int c, int d); + } + + static class C0 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + } + + static class C1 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + } + + static class C2 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + } + + static class C3 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 3; + if (a > 3) r += 1; + if (b > 6) r += 2; + if (c < -3) r += 4; + if (d == 8) r += 8; + return r; + } + } + + static class C4 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 4; + if (a > 4) r += 1; + if (b > 8) r += 2; + if (c < -4) r += 4; + if (d == 9) r += 8; + return r; + } + } + + static class C5 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 5; + if (a > 5) r += 1; + if (b > 10) r += 2; + if (c < -5) r += 4; + if (d == 10) r += 8; + return r; + } + } + + static class C6 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 6; + if (a > 6) r += 1; + if (b > 12) r += 2; + if (c < -6) r += 4; + if (d == 11) r += 8; + return r; + } + } + + static class C7 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 7; + if (a > 7) r += 1; + if (b > 14) r += 2; + if (c < -7) r += 4; + if (d == 12) r += 8; + return r; + } + } + + static class C8 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 8; + if (a > 8) r += 1; + if (b > 16) r += 2; + if (c < -8) r += 4; + if (d == 13) r += 8; + return r; + } + } + + static class C9 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 9; + if (a > 9) r += 1; + if (b > 18) r += 2; + if (c < -9) r += 4; + if (d == 14) r += 8; + return r; + } + } + + static class C10 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 10; + if (a > 10) r += 1; + if (b > 20) r += 2; + if (c < -10) r += 4; + if (d == 15) r += 8; + return r; + } + } + + static class C11 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 11; + if (a > 11) r += 1; + if (b > 22) r += 2; + if (c < -11) r += 4; + if (d == 16) r += 8; + return r; + } + } + + static class C12 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 12; + if (a > 12) r += 1; + if (b > 24) r += 2; + if (c < -12) r += 4; + if (d == 17) r += 8; + return r; + } + } + + static class C13 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 13; + if (a > 13) r += 1; + if (b > 26) r += 2; + if (c < -13) r += 4; + if (d == 18) r += 8; + return r; + } + } + + static class C14 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 14; + if (a > 14) r += 1; + if (b > 28) r += 2; + if (c < -14) r += 4; + if (d == 19) r += 8; + return r; + } + } + + static class C15 extends Cmd { + int run(int a, int b, int c, int d) { + target(a, b); + return 0; + } + } + + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + switch (cmd) { + case 0: return new C0().run(a, b, c, d); + case 1: return new C1().run(a, b, c, d); + case 2: return new C2().run(a, b, c, d); + case 3: return new C3().run(a, b, c, d); + case 4: return new C4().run(a, b, c, d); + case 5: return new C5().run(a, b, c, d); + case 6: return new C6().run(a, b, c, d); + case 7: return new C7().run(a, b, c, d); + case 8: return new C8().run(a, b, c, d); + case 9: return new C9().run(a, b, c, d); + case 10: return new C10().run(a, b, c, d); + case 11: return new C11().run(a, b, c, d); + case 12: return new C12().run(a, b, c, d); + case 13: return new C13().run(a, b, c, d); + case 14: return new C14().run(a, b, c, d); + case 15: return new C15().run(a, b, c, d); + default: return -1; + } + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n16_true.yml b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n16_true.yml new file mode 100644 index 00000000..b353465f --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n16_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - local_n16_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n16_true/Main.java b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n16_true/Main.java new file mode 100644 index 00000000..be8912ec --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n16_true/Main.java @@ -0,0 +1,224 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:15 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// virtual-dispatch/local: cmd selects one of 16 subclasses, only C15.run() reaches the assert. +public class Main { + static abstract class Cmd { + abstract int run(int a, int b, int c, int d); + } + + static class C0 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + } + + static class C1 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + } + + static class C2 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + } + + static class C3 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 3; + if (a > 3) r += 1; + if (b > 6) r += 2; + if (c < -3) r += 4; + if (d == 8) r += 8; + return r; + } + } + + static class C4 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 4; + if (a > 4) r += 1; + if (b > 8) r += 2; + if (c < -4) r += 4; + if (d == 9) r += 8; + return r; + } + } + + static class C5 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 5; + if (a > 5) r += 1; + if (b > 10) r += 2; + if (c < -5) r += 4; + if (d == 10) r += 8; + return r; + } + } + + static class C6 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 6; + if (a > 6) r += 1; + if (b > 12) r += 2; + if (c < -6) r += 4; + if (d == 11) r += 8; + return r; + } + } + + static class C7 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 7; + if (a > 7) r += 1; + if (b > 14) r += 2; + if (c < -7) r += 4; + if (d == 12) r += 8; + return r; + } + } + + static class C8 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 8; + if (a > 8) r += 1; + if (b > 16) r += 2; + if (c < -8) r += 4; + if (d == 13) r += 8; + return r; + } + } + + static class C9 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 9; + if (a > 9) r += 1; + if (b > 18) r += 2; + if (c < -9) r += 4; + if (d == 14) r += 8; + return r; + } + } + + static class C10 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 10; + if (a > 10) r += 1; + if (b > 20) r += 2; + if (c < -10) r += 4; + if (d == 15) r += 8; + return r; + } + } + + static class C11 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 11; + if (a > 11) r += 1; + if (b > 22) r += 2; + if (c < -11) r += 4; + if (d == 16) r += 8; + return r; + } + } + + static class C12 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 12; + if (a > 12) r += 1; + if (b > 24) r += 2; + if (c < -12) r += 4; + if (d == 17) r += 8; + return r; + } + } + + static class C13 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 13; + if (a > 13) r += 1; + if (b > 26) r += 2; + if (c < -13) r += 4; + if (d == 18) r += 8; + return r; + } + } + + static class C14 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 14; + if (a > 14) r += 1; + if (b > 28) r += 2; + if (c < -14) r += 4; + if (d == 19) r += 8; + return r; + } + } + + static class C15 extends Cmd { + int run(int a, int b, int c, int d) { + target(a, b); + return 0; + } + } + + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + switch (cmd) { + case 0: return new C0().run(a, b, c, d); + case 1: return new C1().run(a, b, c, d); + case 2: return new C2().run(a, b, c, d); + case 3: return new C3().run(a, b, c, d); + case 4: return new C4().run(a, b, c, d); + case 5: return new C5().run(a, b, c, d); + case 6: return new C6().run(a, b, c, d); + case 7: return new C7().run(a, b, c, d); + case 8: return new C8().run(a, b, c, d); + case 9: return new C9().run(a, b, c, d); + case 10: return new C10().run(a, b, c, d); + case 11: return new C11().run(a, b, c, d); + case 12: return new C12().run(a, b, c, d); + case 13: return new C13().run(a, b, c, d); + case 14: return new C14().run(a, b, c, d); + case 15: return new C15().run(a, b, c, d); + default: return -1; + } + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n32_false.yml b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n32_false.yml new file mode 100644 index 00000000..f4fd45eb --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n32_false.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - local_n32_false/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: false +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n32_false/Main.java b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n32_false/Main.java new file mode 100644 index 00000000..46d4aa8e --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n32_false/Main.java @@ -0,0 +1,415 @@ +// Expected verdict: VIOLATION. Violating inputs (nondet call order): int:31 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// virtual-dispatch/local: cmd selects one of 32 subclasses, only C31.run() reaches the assert. +public class Main { + static abstract class Cmd { + abstract int run(int a, int b, int c, int d); + } + + static class C0 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + } + + static class C1 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + } + + static class C2 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + } + + static class C3 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 3; + if (a > 3) r += 1; + if (b > 6) r += 2; + if (c < -3) r += 4; + if (d == 8) r += 8; + return r; + } + } + + static class C4 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 4; + if (a > 4) r += 1; + if (b > 8) r += 2; + if (c < -4) r += 4; + if (d == 9) r += 8; + return r; + } + } + + static class C5 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 5; + if (a > 5) r += 1; + if (b > 10) r += 2; + if (c < -5) r += 4; + if (d == 10) r += 8; + return r; + } + } + + static class C6 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 6; + if (a > 6) r += 1; + if (b > 12) r += 2; + if (c < -6) r += 4; + if (d == 11) r += 8; + return r; + } + } + + static class C7 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 7; + if (a > 7) r += 1; + if (b > 14) r += 2; + if (c < -7) r += 4; + if (d == 12) r += 8; + return r; + } + } + + static class C8 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 8; + if (a > 8) r += 1; + if (b > 16) r += 2; + if (c < -8) r += 4; + if (d == 13) r += 8; + return r; + } + } + + static class C9 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 9; + if (a > 9) r += 1; + if (b > 18) r += 2; + if (c < -9) r += 4; + if (d == 14) r += 8; + return r; + } + } + + static class C10 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 10; + if (a > 10) r += 1; + if (b > 20) r += 2; + if (c < -10) r += 4; + if (d == 15) r += 8; + return r; + } + } + + static class C11 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 11; + if (a > 11) r += 1; + if (b > 22) r += 2; + if (c < -11) r += 4; + if (d == 16) r += 8; + return r; + } + } + + static class C12 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 12; + if (a > 12) r += 1; + if (b > 24) r += 2; + if (c < -12) r += 4; + if (d == 17) r += 8; + return r; + } + } + + static class C13 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 13; + if (a > 13) r += 1; + if (b > 26) r += 2; + if (c < -13) r += 4; + if (d == 18) r += 8; + return r; + } + } + + static class C14 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 14; + if (a > 14) r += 1; + if (b > 28) r += 2; + if (c < -14) r += 4; + if (d == 19) r += 8; + return r; + } + } + + static class C15 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 15; + if (a > 15) r += 1; + if (b > 30) r += 2; + if (c < -15) r += 4; + if (d == 20) r += 8; + return r; + } + } + + static class C16 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 16; + if (a > 16) r += 1; + if (b > 32) r += 2; + if (c < -16) r += 4; + if (d == 21) r += 8; + return r; + } + } + + static class C17 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 17; + if (a > 17) r += 1; + if (b > 34) r += 2; + if (c < -17) r += 4; + if (d == 22) r += 8; + return r; + } + } + + static class C18 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 18; + if (a > 18) r += 1; + if (b > 36) r += 2; + if (c < -18) r += 4; + if (d == 23) r += 8; + return r; + } + } + + static class C19 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 19; + if (a > 19) r += 1; + if (b > 38) r += 2; + if (c < -19) r += 4; + if (d == 24) r += 8; + return r; + } + } + + static class C20 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 20; + if (a > 20) r += 1; + if (b > 40) r += 2; + if (c < -20) r += 4; + if (d == 25) r += 8; + return r; + } + } + + static class C21 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 21; + if (a > 21) r += 1; + if (b > 42) r += 2; + if (c < -21) r += 4; + if (d == 26) r += 8; + return r; + } + } + + static class C22 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 22; + if (a > 22) r += 1; + if (b > 44) r += 2; + if (c < -22) r += 4; + if (d == 27) r += 8; + return r; + } + } + + static class C23 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 23; + if (a > 23) r += 1; + if (b > 46) r += 2; + if (c < -23) r += 4; + if (d == 28) r += 8; + return r; + } + } + + static class C24 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 24; + if (a > 24) r += 1; + if (b > 48) r += 2; + if (c < -24) r += 4; + if (d == 29) r += 8; + return r; + } + } + + static class C25 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 25; + if (a > 25) r += 1; + if (b > 50) r += 2; + if (c < -25) r += 4; + if (d == 30) r += 8; + return r; + } + } + + static class C26 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 26; + if (a > 26) r += 1; + if (b > 52) r += 2; + if (c < -26) r += 4; + if (d == 31) r += 8; + return r; + } + } + + static class C27 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 27; + if (a > 27) r += 1; + if (b > 54) r += 2; + if (c < -27) r += 4; + if (d == 32) r += 8; + return r; + } + } + + static class C28 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 28; + if (a > 28) r += 1; + if (b > 56) r += 2; + if (c < -28) r += 4; + if (d == 33) r += 8; + return r; + } + } + + static class C29 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 29; + if (a > 29) r += 1; + if (b > 58) r += 2; + if (c < -29) r += 4; + if (d == 34) r += 8; + return r; + } + } + + static class C30 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 30; + if (a > 30) r += 1; + if (b > 60) r += 2; + if (c < -30) r += 4; + if (d == 35) r += 8; + return r; + } + } + + static class C31 extends Cmd { + int run(int a, int b, int c, int d) { + target(a, b); + return 0; + } + } + + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + switch (cmd) { + case 0: return new C0().run(a, b, c, d); + case 1: return new C1().run(a, b, c, d); + case 2: return new C2().run(a, b, c, d); + case 3: return new C3().run(a, b, c, d); + case 4: return new C4().run(a, b, c, d); + case 5: return new C5().run(a, b, c, d); + case 6: return new C6().run(a, b, c, d); + case 7: return new C7().run(a, b, c, d); + case 8: return new C8().run(a, b, c, d); + case 9: return new C9().run(a, b, c, d); + case 10: return new C10().run(a, b, c, d); + case 11: return new C11().run(a, b, c, d); + case 12: return new C12().run(a, b, c, d); + case 13: return new C13().run(a, b, c, d); + case 14: return new C14().run(a, b, c, d); + case 15: return new C15().run(a, b, c, d); + case 16: return new C16().run(a, b, c, d); + case 17: return new C17().run(a, b, c, d); + case 18: return new C18().run(a, b, c, d); + case 19: return new C19().run(a, b, c, d); + case 20: return new C20().run(a, b, c, d); + case 21: return new C21().run(a, b, c, d); + case 22: return new C22().run(a, b, c, d); + case 23: return new C23().run(a, b, c, d); + case 24: return new C24().run(a, b, c, d); + case 25: return new C25().run(a, b, c, d); + case 26: return new C26().run(a, b, c, d); + case 27: return new C27().run(a, b, c, d); + case 28: return new C28().run(a, b, c, d); + case 29: return new C29().run(a, b, c, d); + case 30: return new C30().run(a, b, c, d); + case 31: return new C31().run(a, b, c, d); + default: return -1; + } + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b > a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n32_true.yml b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n32_true.yml new file mode 100644 index 00000000..c1b74d67 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n32_true.yml @@ -0,0 +1,9 @@ +format_version: '2.0' +input_files: + - ../common/ + - local_n32_true/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: true +options: + language: Java diff --git a/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n32_true/Main.java b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n32_true/Main.java new file mode 100644 index 00000000..24ecfad5 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/java/virtual-dispatch/local_n32_true/Main.java @@ -0,0 +1,416 @@ +// Expected verdict: SAFE. target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. a < 100000 rules out overflow in 3 * a. +// Paired VIOLATION inputs (must not fail here): int:31 int:1001 int:2996 int:0 int:0 +import org.sosy_lab.sv_benchmarks.Verifier; + +// virtual-dispatch/local: cmd selects one of 32 subclasses, only C31.run() reaches the assert. +public class Main { + static abstract class Cmd { + abstract int run(int a, int b, int c, int d); + } + + static class C0 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 0; + if (a > 0) r += 1; + if (b > 0) r += 2; + if (c < 0) r += 4; + if (d == 5) r += 8; + return r; + } + } + + static class C1 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 1; + if (a > 1) r += 1; + if (b > 2) r += 2; + if (c < -1) r += 4; + if (d == 6) r += 8; + return r; + } + } + + static class C2 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 2; + if (a > 2) r += 1; + if (b > 4) r += 2; + if (c < -2) r += 4; + if (d == 7) r += 8; + return r; + } + } + + static class C3 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 3; + if (a > 3) r += 1; + if (b > 6) r += 2; + if (c < -3) r += 4; + if (d == 8) r += 8; + return r; + } + } + + static class C4 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 4; + if (a > 4) r += 1; + if (b > 8) r += 2; + if (c < -4) r += 4; + if (d == 9) r += 8; + return r; + } + } + + static class C5 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 5; + if (a > 5) r += 1; + if (b > 10) r += 2; + if (c < -5) r += 4; + if (d == 10) r += 8; + return r; + } + } + + static class C6 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 6; + if (a > 6) r += 1; + if (b > 12) r += 2; + if (c < -6) r += 4; + if (d == 11) r += 8; + return r; + } + } + + static class C7 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 7; + if (a > 7) r += 1; + if (b > 14) r += 2; + if (c < -7) r += 4; + if (d == 12) r += 8; + return r; + } + } + + static class C8 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 8; + if (a > 8) r += 1; + if (b > 16) r += 2; + if (c < -8) r += 4; + if (d == 13) r += 8; + return r; + } + } + + static class C9 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 9; + if (a > 9) r += 1; + if (b > 18) r += 2; + if (c < -9) r += 4; + if (d == 14) r += 8; + return r; + } + } + + static class C10 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 10; + if (a > 10) r += 1; + if (b > 20) r += 2; + if (c < -10) r += 4; + if (d == 15) r += 8; + return r; + } + } + + static class C11 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 11; + if (a > 11) r += 1; + if (b > 22) r += 2; + if (c < -11) r += 4; + if (d == 16) r += 8; + return r; + } + } + + static class C12 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 12; + if (a > 12) r += 1; + if (b > 24) r += 2; + if (c < -12) r += 4; + if (d == 17) r += 8; + return r; + } + } + + static class C13 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 13; + if (a > 13) r += 1; + if (b > 26) r += 2; + if (c < -13) r += 4; + if (d == 18) r += 8; + return r; + } + } + + static class C14 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 14; + if (a > 14) r += 1; + if (b > 28) r += 2; + if (c < -14) r += 4; + if (d == 19) r += 8; + return r; + } + } + + static class C15 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 15; + if (a > 15) r += 1; + if (b > 30) r += 2; + if (c < -15) r += 4; + if (d == 20) r += 8; + return r; + } + } + + static class C16 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 16; + if (a > 16) r += 1; + if (b > 32) r += 2; + if (c < -16) r += 4; + if (d == 21) r += 8; + return r; + } + } + + static class C17 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 17; + if (a > 17) r += 1; + if (b > 34) r += 2; + if (c < -17) r += 4; + if (d == 22) r += 8; + return r; + } + } + + static class C18 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 18; + if (a > 18) r += 1; + if (b > 36) r += 2; + if (c < -18) r += 4; + if (d == 23) r += 8; + return r; + } + } + + static class C19 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 19; + if (a > 19) r += 1; + if (b > 38) r += 2; + if (c < -19) r += 4; + if (d == 24) r += 8; + return r; + } + } + + static class C20 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 20; + if (a > 20) r += 1; + if (b > 40) r += 2; + if (c < -20) r += 4; + if (d == 25) r += 8; + return r; + } + } + + static class C21 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 21; + if (a > 21) r += 1; + if (b > 42) r += 2; + if (c < -21) r += 4; + if (d == 26) r += 8; + return r; + } + } + + static class C22 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 22; + if (a > 22) r += 1; + if (b > 44) r += 2; + if (c < -22) r += 4; + if (d == 27) r += 8; + return r; + } + } + + static class C23 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 23; + if (a > 23) r += 1; + if (b > 46) r += 2; + if (c < -23) r += 4; + if (d == 28) r += 8; + return r; + } + } + + static class C24 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 24; + if (a > 24) r += 1; + if (b > 48) r += 2; + if (c < -24) r += 4; + if (d == 29) r += 8; + return r; + } + } + + static class C25 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 25; + if (a > 25) r += 1; + if (b > 50) r += 2; + if (c < -25) r += 4; + if (d == 30) r += 8; + return r; + } + } + + static class C26 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 26; + if (a > 26) r += 1; + if (b > 52) r += 2; + if (c < -26) r += 4; + if (d == 31) r += 8; + return r; + } + } + + static class C27 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 27; + if (a > 27) r += 1; + if (b > 54) r += 2; + if (c < -27) r += 4; + if (d == 32) r += 8; + return r; + } + } + + static class C28 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 28; + if (a > 28) r += 1; + if (b > 56) r += 2; + if (c < -28) r += 4; + if (d == 33) r += 8; + return r; + } + } + + static class C29 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 29; + if (a > 29) r += 1; + if (b > 58) r += 2; + if (c < -29) r += 4; + if (d == 34) r += 8; + return r; + } + } + + static class C30 extends Cmd { + int run(int a, int b, int c, int d) { + int r = 30; + if (a > 30) r += 1; + if (b > 60) r += 2; + if (c < -30) r += 4; + if (d == 35) r += 8; + return r; + } + } + + static class C31 extends Cmd { + int run(int a, int b, int c, int d) { + target(a, b); + return 0; + } + } + + public static void main(String[] args) { + int cmd = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = Verifier.nondetInt(); + int d = Verifier.nondetInt(); + dispatch(cmd, a, b, c, d); + } + + static int dispatch(int cmd, int a, int b, int c, int d) { + switch (cmd) { + case 0: return new C0().run(a, b, c, d); + case 1: return new C1().run(a, b, c, d); + case 2: return new C2().run(a, b, c, d); + case 3: return new C3().run(a, b, c, d); + case 4: return new C4().run(a, b, c, d); + case 5: return new C5().run(a, b, c, d); + case 6: return new C6().run(a, b, c, d); + case 7: return new C7().run(a, b, c, d); + case 8: return new C8().run(a, b, c, d); + case 9: return new C9().run(a, b, c, d); + case 10: return new C10().run(a, b, c, d); + case 11: return new C11().run(a, b, c, d); + case 12: return new C12().run(a, b, c, d); + case 13: return new C13().run(a, b, c, d); + case 14: return new C14().run(a, b, c, d); + case 15: return new C15().run(a, b, c, d); + case 16: return new C16().run(a, b, c, d); + case 17: return new C17().run(a, b, c, d); + case 18: return new C18().run(a, b, c, d); + case 19: return new C19().run(a, b, c, d); + case 20: return new C20().run(a, b, c, d); + case 21: return new C21().run(a, b, c, d); + case 22: return new C22().run(a, b, c, d); + case 23: return new C23().run(a, b, c, d); + case 24: return new C24().run(a, b, c, d); + case 25: return new C25().run(a, b, c, d); + case 26: return new C26().run(a, b, c, d); + case 27: return new C27().run(a, b, c, d); + case 28: return new C28().run(a, b, c, d); + case 29: return new C29().run(a, b, c, d); + case 30: return new C30().run(a, b, c, d); + case 31: return new C31().run(a, b, c, d); + default: return -1; + } + } + + // The only assertion in the program. + static void target(int a, int b) { + if (a > 1000 && a < 100000) { + if (b == 3 * a - 7) { + if (b < a) { + assert false; + } + } + } + } +} diff --git a/targets/sv-comp/swat-benchmarks/report/families.md b/targets/sv-comp/swat-benchmarks/report/families.md new file mode 100644 index 00000000..787dfca4 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/report/families.md @@ -0,0 +1,128 @@ +Rounds (wall time in s) without SA -> with SA. TO: timed out (rounds so far). + +### dispatcher + +| variant | n | SAFE: no SA | SAFE: SA | VIOLATION: no SA | VIOLATION: SA | +|---|---:|---|---|---|---| +| ifchain | 2 | 21 (35) | 5 (16) | 21 (35) | 5 (16) | +| ifchain | 4 | 53 (88) | 5 (16) | 53 (88) | 5 (17) | +| ifchain | 8 | 117 (197) | 5 (17) | 117 (195) | 5 (16) | +| ifchain | 16 | TO(183) (300) | 5 (16) | TO(178) (300) | 5 (16) | +| ifchain | 32 | TO(175) (300) | 5 (17) | TO(175) (300) | 5 (17) | +| instance | 2 | 21 (35) | 5 (16) | 6 (10) | 5 (16) | +| instance | 4 | 53 (89) | 5 (16) | 8 (14) | 5 (16) | +| instance | 8 | 117 (203) | 5 (17) | 12 (21) | 5 (17) | +| instance | 16 | TO(172) (300) | 5 (17) | 20 (35) | 5 (17) | +| instance | 32 | TO(174) (300) | 5 (17) | TO(169) (300) | 5 (17) | +| loop | 2 | TO(172) (300) | TO(194) (300) | 8 (14) | 8 (24) | +| loop | 4 | TO(169) (300) | TO(192) (300) | 12 (21) | 12 (32) | +| loop | 8 | TO(165) (300) | TO(190) (300) | 20 (36) | 20 (48) | +| loop | 16 | TO(161) (300) | TO(182) (301) | 36 (66) | 36 (86) | +| loop | 32 | TO(159) (300) | TO(181) (301) | TO(155) (300) | TO(179) (300) | +| switch | 2 | 21 (35) | 5 (16) | 6 (10) | 5 (17) | +| switch | 4 | 53 (88) | 5 (16) | 8 (14) | 5 (17) | +| switch | 8 | 117 (198) | 5 (16) | 12 (21) | 5 (17) | +| switch | 16 | TO(174) (300) | 5 (17) | 20 (35) | 5 (17) | +| switch | 32 | TO(171) (300) | 5 (17) | TO(176) (300) | 5 (17) | + +### validation-gauntlet + +| variant | n | SAFE: no SA | SAFE: SA | VIOLATION: no SA | VIOLATION: SA | +|---|---:|---|---|---|---| +| exit | 2 | 1 (1) | 1 (8) | 1 (1) **false SAFE** | 1 (8) **false SAFE** | +| exit | 4 | 1 (2) | 1 (8) | 1 (1) **false SAFE** | 1 (8) **false SAFE** | +| exit | 8 | 1 (2) | 1 (8) | 1 (1) **false SAFE** | 1 (8) **false SAFE** | +| exit | 16 | 1 (2) | 1 (8) | 1 (2) **false SAFE** | 1 (8) **false SAFE** | +| exit | 32 | 1 (2) | 1 (7) | 1 (2) **false SAFE** | 1 (8) **false SAFE** | +| report | 2 | 12 (20) | 4 (13) | 12 (20) | 4 (15) | +| report | 4 | 20 (34) | 6 (19) | 20 (33) | 6 (19) | +| report | 8 | 36 (61) | 10 (26) | 36 (60) | 10 (26) | +| report | 16 | 68 (120) | 18 (45) | 68 (119) | 18 (43) | +| report | 32 | 132 (253) | 34 (80) | 132 (252) | 34 (81) | +| return | 2 | 6 (10) | 4 (14) | 6 (10) | 4 (14) | +| return | 4 | 8 (14) | 6 (18) | 8 (14) | 6 (18) | +| return | 8 | 12 (21) | 10 (25) | 12 (21) | 10 (26) | +| return | 16 | 20 (35) | 18 (43) | 20 (35) | 18 (44) | +| return | 32 | 36 (67) | 34 (80) | 36 (67) | 34 (80) | +| throw | 2 | 6 (10) | 4 (14) | 6 (10) | 4 (15) | +| throw | 4 | 8 (14) | 6 (19) | 8 (14) | 6 (18) | +| throw | 8 | 12 (21) | 10 (27) | 12 (21) | 10 (26) | +| throw | 16 | 20 (36) | 18 (45) | 20 (35) | 18 (44) | +| throw | 32 | 36 (69) | 34 (82) | 36 (68) | 34 (82) | + +### error-handling + +| variant | n | SAFE: no SA | SAFE: SA | VIOLATION: no SA | VIOLATION: SA | +|---|---:|---|---|---|---| +| arith | 2 | 6 (11) | 3 (14) | 6 (11) | 3 (14) | +| arith | 4 | 8 (14) | 3 (14) | 8 (14) | 3 (15) | +| arith | 8 | 12 (21) | 3 (15) | 12 (21) | 3 (14) | +| arith | 16 | 20 (37) | 3 (14) | 20 (36) | 3 (15) | +| arith | 32 | 36 (71) | 3 (16) | 36 (71) | 3 (16) | +| bounds | 2 | 10 (17) | 3 (14) | 10 (18) | 3 (15) | +| bounds | 4 | 16 (28) | 3 (15) | 16 (29) | 3 (14) | +| bounds | 8 | 32 (59) | 3 (15) | 32 (59) | 3 (14) | +| bounds | 16 | 64 (129) | 3 (15) | 64 (129) | 3 (16) | +| bounds | 32 | TO(126) (300) | 3 (19) | TO(126) (300) | 3 (19) | +| deep | 2 | 8 (14) | 3 (13) | 8 (14) | 3 (14) | +| deep | 4 | 12 (21) | 3 (15) | 12 (21) | 3 (15) | +| deep | 8 | 20 (36) | 3 (15) | 20 (36) | 3 (15) | +| deep | 16 | 36 (68) | 3 (15) | 36 (68) | 3 (15) | +| deep | 32 | 68 (139) | 3 (15) | 68 (138) | 3 (16) | +| explicit | 2 | 8 (14) | 3 (15) | 8 (14) | 3 (13) | +| explicit | 4 | 12 (21) | 3 (14) | 12 (21) | 3 (14) | +| explicit | 8 | 20 (36) | 3 (15) | 20 (36) | 3 (15) | +| explicit | 16 | 36 (67) | 3 (16) | 36 (67) | 3 (15) | +| explicit | 32 | 68 (137) | 3 (16) | 68 (136) | 3 (14) | + +### dead-callees + +| variant | n | SAFE: no SA | SAFE: SA | VIOLATION: no SA | VIOLATION: SA | +|---|---:|---|---|---|---| +| guarded | 1 | 7 (12) | 3 (11) | 7 (12) | 3 (11) | +| guarded | 2 | 13 (21) | 3 (11) | 13 (21) | 3 (10) | +| guarded | 3 | 31 (49) | 3 (9) | 31 (51) | 3 (11) | +| guarded | 4 | 85 (123) | 3 (11) | 85 (132) | 3 (11) | +| guarded | 5 | TO(220) (300) | 3 (11) | TO(221) (300) | 3 (11) | +| guarded | 6 | TO(206) (300) | 3 (11) | TO(204) (300) | 3 (11) | +| shared | 1 | 9 (15) | 5 (14) | 9 (15) | 5 (14) | +| shared | 2 | 15 (24) | 5 (12) | 15 (24) | 5 (15) | +| shared | 3 | 33 (48) | 5 (12) | 33 (54) | 5 (15) | +| shared | 4 | 87 (122) | 5 (11) | 87 (133) | 5 (14) | +| shared | 5 | TO(223) (300) | 5 (12) | TO(225) (300) | 5 (11) | +| shared | 6 | TO(208) (300) | 5 (15) | TO(206) (300) | 5 (14) | +| suffix | 1 | 12 (19) | 3 (11) | 4 (7) | 3 (10) | +| suffix | 2 | 36 (59) | 3 (11) | 4 (7) | 3 (10) | +| suffix | 3 | 108 (161) | 3 (11) | 4 (7) | 3 (9) | +| suffix | 4 | TO(214) (300) | 3 (11) | 4 (7) | 3 (11) | +| suffix | 5 | TO(210) (300) | 3 (11) | 4 (7) | 3 (9) | +| suffix | 6 | TO(213) (300) | 3 (11) | 4 (7) | 3 (10) | + +### virtual-dispatch + +| variant | n | SAFE: no SA | SAFE: SA | VIOLATION: no SA | VIOLATION: SA | +|---|---:|---|---|---|---| +| array | 2 | 18 (30) | 16 (36) | 18 (30) **false SAFE** | 16 (35) **false SAFE** | +| array | 4 | 18 (30) | 16 (35) | 18 (30) **false SAFE** | 16 (36) **false SAFE** | +| array | 8 | 18 (30) | 16 (36) | 18 (30) **false SAFE** | 16 (35) **false SAFE** | +| array | 16 | 18 (31) | 16 (36) | 18 (31) **false SAFE** | 16 (36) **false SAFE** | +| array | 32 | 18 (32) | 16 (38) | 18 (32) **false SAFE** | 16 (36) **false SAFE** | +| factory | 2 | 21 (35) | 21 (40) | 6 (10) | 6 (16) | +| factory | 4 | 53 (89) | 53 (83) | 8 (14) | 8 (20) | +| factory | 8 | 117 (192) | 117 (168) | 12 (21) | 12 (29) | +| factory | 16 | TO(197) (300) | TO(215) (300) | 20 (35) | 20 (41) | +| factory | 32 | TO(191) (300) | TO(208) (300) | TO(190) (300) | TO(209) (300) | +| local | 2 | 21 (35) | 5 (16) | 6 (10) | 5 (15) | +| local | 4 | 53 (87) | 5 (15) | 8 (14) | 5 (15) | +| local | 8 | 117 (188) | 5 (15) | 12 (20) | 5 (15) | +| local | 16 | TO(207) (300) | 5 (15) | 20 (34) | 5 (15) | +| local | 32 | TO(201) (300) | 5 (16) | TO(199) (300) | 5 (17) | + +### irrelevant-prefix + +| variant | n | SAFE: no SA | SAFE: SA | VIOLATION: no SA | VIOLATION: SA | +|---|---:|---|---|---|---| +| count | 2 | 16 (26) | 12 (32) | 6 (10) | 5 (18) | +| count | 4 | 64 (107) | 48 (105) | 8 (14) | 7 (23) | +| count | 6 | TO(176) (300) | 192 (298) | 10 (17) | 9 (28) | +| count | 8 | TO(174) (300) | TO(191) (300) | 12 (21) | 11 (30) | diff --git a/targets/sv-comp/swat-benchmarks/report/rounds.png b/targets/sv-comp/swat-benchmarks/report/rounds.png new file mode 100644 index 00000000..7e389543 Binary files /dev/null and b/targets/sv-comp/swat-benchmarks/report/rounds.png differ diff --git a/targets/sv-comp/swat-benchmarks/report/time.png b/targets/sv-comp/swat-benchmarks/report/time.png new file mode 100644 index 00000000..a2bfff25 Binary files /dev/null and b/targets/sv-comp/swat-benchmarks/report/time.png differ diff --git a/targets/sv-comp/swat-benchmarks/scripts/build.sh b/targets/sv-comp/swat-benchmarks/scripts/build.sh new file mode 100755 index 00000000..55aed04d --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/scripts/build.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# Compiles every task in place (.class files next to the sources, as in sv-benchmarks). A suite's +# lib-* folders (see fetch_tools.sh) are compiled first and are on its tasks' classpath. +# Usage: ./build.sh [suite...] (default: all suites) +set -euo pipefail +JAVA_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../java" && pwd)" +JAVAC_FLAGS="--release 8 -g:source,lines -nowarn -Xlint:-options" + +javac $JAVAC_FLAGS -d "$JAVA_DIR/common" $(find "$JAVA_DIR/common" -name '*.java') + +if [ $# -eq 0 ]; then + suites=$(find "$JAVA_DIR" -mindepth 1 -maxdepth 1 -type d ! -name common ! -name properties -printf '%f\n') +else + suites="$*" +fi +for suite in $suites; do + cp="$JAVA_DIR/common" + for lib in $(find "$JAVA_DIR/$suite" -mindepth 1 -maxdepth 1 -type d -name 'lib-*' | sort); do + find "$lib" -name '*.class' -delete + javac $JAVAC_FLAGS -cp "$cp" -d "$lib" $(find "$lib" -name '*.java') + cp="$cp:$lib" + done + find "$JAVA_DIR/$suite" -mindepth 1 -maxdepth 1 -type d ! -name 'lib-*' \ + | xargs -r -P 32 -I{} sh -c "find {} -name '*.class' -delete && javac $JAVAC_FLAGS -cp '$cp' -d {} \$(find {} -name '*.java')" +done +echo "Built $(find "$JAVA_DIR" -name Main.class | wc -l) tasks." diff --git a/targets/sv-comp/swat-benchmarks/scripts/check_labels.py b/targets/sv-comp/swat-benchmarks/scripts/check_labels.py new file mode 100755 index 00000000..bc38ed08 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/scripts/check_labels.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 +"""Checks every task's label by running it (with -ea) on its witness inputs, through a stub Verifier +that replays them (stub/). A VIOLATION task must fail an assert; a SAFE task must not fail on its +paired VIOLATION task's inputs (the minimal difference is what makes it safe; that it is safe for +all inputs is argued in the task's header comment). Also checks that all inputs 0, SWAT's first +round, reach no assert, so every violation needs at least one flipped branch. + + ./check_labels.py [suite...] (run ./build.sh first) +""" + +import re +import subprocess +import sys +import tempfile +from concurrent.futures import ThreadPoolExecutor +from pathlib import Path + +HERE = Path(__file__).resolve().parent +JAVA_DIR = HERE.parent / 'java' + + +def run(task_dir: Path, stub: Path, inputs: str) -> str: + libs = ''.join(f':{d}' for d in sorted(task_dir.parent.glob('lib-*'))) + cmd = ['java', '-ea', f'-Dinputs={inputs}', '-cp', f'{stub}:{JAVA_DIR / "common"}{libs}:{task_dir}', 'Main'] + p = subprocess.run(cmd, capture_output=True, text=True, timeout=60) + if 'java.lang.AssertionError' in p.stderr: + return 'assert' + return 'ok' if p.returncode == 0 else f'exit {p.returncode}: {p.stderr.strip()[-300:]}' + + +def check(task_dir: Path, stub: Path) -> str | None: + src = (task_dir / 'Main.java').read_text() + m = re.search(r'(?:Violating inputs \(nondet call order\)|Paired VIOLATION inputs \(must not fail here\)): (.*)', src) + inputs = '\n'.join(m.group(1).split()) + safe = task_dir.name.endswith('_true') + # The gauntlet's exit variant ends the program on rejection; some relevance tasks end the + # all-zero run early on purpose. + exits = 'System.exit' in src or 'the all-zero run ends early' in src + got = run(task_dir, stub, inputs) + if got != ('ok' if safe else 'assert'): + return f'{task_dir.relative_to(JAVA_DIR)}: witness {inputs} gives {got}' + zero = run(task_dir, stub, '') + if zero != 'ok' and not (exits and zero.startswith('exit 1')): + return f'{task_dir.relative_to(JAVA_DIR)}: all-zero inputs give {zero}' + return None + + +def main(suites): + with tempfile.TemporaryDirectory() as tmp: + stub = Path(tmp) + subprocess.run(['javac', '-nowarn', '-d', str(stub), '-cp', str(JAVA_DIR / 'common'), + str(HERE / 'stub/org/sosy_lab/sv_benchmarks/Verifier.java')], check=True) + dirs = sorted(d for d in JAVA_DIR.glob('*/*') if (d / 'Main.java').exists() and not d.name.startswith('lib-') + and (not suites or d.parent.name in suites)) + with ThreadPoolExecutor(32) as pool: + problems = [p for p in pool.map(lambda d: check(d, stub), dirs) if p] + for p in problems: + print(p) + print(f'{len(dirs) - len(problems)}/{len(dirs)} labels confirmed.') + return 1 if problems else 0 + + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) diff --git a/targets/sv-comp/swat-benchmarks/scripts/compare.py b/targets/sv-comp/swat-benchmarks/scripts/compare.py new file mode 100644 index 00000000..616fd0a4 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/scripts/compare.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python3 +"""Compares SV-COMP runs task by task, e.g. with and without static pre-analysis. + + ./compare.py runs/run_A runs/run_B [...] [--csv out.csv] [--suite S] [--only-diff] + +Per task and run: the result case (expected -> actual), wall time, rounds, solver calls, the SA +duration, and from explorer.log the DFS visits (one `[DFS]` line per branch visited, summed over +all rounds), how many of them had a side marked uninteresting (pruned), and how many had no SA +node (`/None`: the walker gave up there). Rounds of a task that timed out (no stats.json) are +counted from the ROUND headers in explorer.log. +""" + +import argparse +import csv +import glob +import json +import re +import sys +from pathlib import Path + +SHORT = {'safe -> safe': 'ok', 'violation -> violation': 'ok', 'violation -> safe': 'FALSE-SAFE', + 'safe -> violation': 'FALSE-VIOL', 'safe -> unknown': 'unk', 'violation -> unknown': 'unk', + 'safe -> crash': 'crash', 'violation -> crash': 'crash'} + +DFS_RE = re.compile(r'\[DFS\] @\S+?/(\S+) .*skip_is_interesting=(\w+), branch_is_interesting=(\w+)') + + +def explorer_stats(log: Path) -> dict: + visits = pruned = none = rounds = 0 + if log.exists(): + with open(log, errors='replace') as f: + for line in f: + if '[DFS]' in line: + m = DFS_RE.search(line) + if not m: + continue + visits += 1 + if m.group(1) == 'None': + none += 1 + if 'False' in (m.group(2), m.group(3)): + pruned += 1 + elif ' ROUND (' in line: + rounds += 1 + return {'visits': visits, 'pruned': pruned, 'none': none, 'log_rounds': rounds} + + +def load_run(run: Path) -> dict: + info = json.loads((run / 'run_info.json').read_text()) if (run / 'run_info.json').exists() else {} + mode = info.get('sa_mode', '?') + if info.get('sa_rank_distance'): + mode += '+rank' + if info.get('sa_callback_targets') is False and mode != 'none': + mode += '-cb' + tasks = {} + for f in glob.glob(str(run / 'results' / 'results_valid-assert.prp_*.json')): + for name, r in json.load(open(f))['results'].items(): + log_dir = run / 'logs' / f'{name}_valid-assert' + row = {'case': r[0], 'status': r[2], 'time': r[5]} + stats_file = log_dir / 'stats.json' + ex = explorer_stats(log_dir / 'explorer.log') + if stats_file.exists(): + s = json.loads(stats_file.read_text()) + row['rounds'] = s['performance']['symbolic_exec_iterations'] + row['solver'] = s['performance']['nr_solver_calls'] + sa = s.get('static_analysis') or {} + row['sa_s'] = sa.get('duration_s') + row['sa_status'] = sa.get('status') + row['context_loss'] = s['soundness'].get('symbolic_context_loss') + else: + row['rounds'] = ex['log_rounds'] + row['solver'] = None + row.update(ex) + tasks[name] = row + return {'name': run.name, 'mode': mode, 'tasks': tasks} + + +def fmt(v, spec=''): + return '-' if v is None else format(v, spec) + + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument('runs', nargs='+', type=Path) + ap.add_argument('--csv', type=Path) + ap.add_argument('--suite') + ap.add_argument('--only-diff', action='store_true', help='only tasks whose result case differs') + args = ap.parse_args() + runs = [load_run(r) for r in args.runs] + names = sorted(set().union(*(r['tasks'] for r in runs))) + if args.suite: + names = [n for n in names if n.split('/')[0] == args.suite] + + head = f'{"task":48}' + ''.join(f' | {r["mode"][:14]:14} {"time":>6} {"rnds":>5} {"solv":>5} {"vis":>7} {"prun":>6} {"none":>6}' for r in runs) + print(head) + print('-' * len(head)) + rows = [] + for n in names: + cells = [r['tasks'].get(n) for r in runs] + if args.only_diff and len({c and c['case'] for c in cells}) == 1: + continue + line = f'{n:48}' + for c in cells: + if c is None: + line += f' | {"(missing)":14} {"":>6} {"":>5} {"":>5} {"":>7} {"":>6} {"":>6}' + continue + tag = SHORT.get(c['case'], c['case']) + if c['status'] == 'timeout': + tag = 'TIMEOUT' if tag in ('crash', 'unk') else tag + '/TO' + line += (f' | {tag:14} {c["time"]:6.1f} {fmt(c["rounds"]):>5} {fmt(c["solver"]):>5}' + f' {c["visits"]:7} {c["pruned"]:6} {c["none"]:6}') + print(line) + for r, c in zip(runs, cells): + if c: + rows.append({'task': n, 'run': r['name'], 'mode': r['mode'], **c}) + for r in runs: + cases = [t['case'] for n, t in r['tasks'].items() if n in names] + bad = sum(1 for c in cases if c == 'violation -> safe') + print(f'{r["name"]} ({r["mode"]}): {len(cases)} tasks, ' + f'{sum(1 for c in cases if SHORT.get(c) == "ok")} correct, {bad} false SAFE, ' + f'{sum(1 for c in cases if c == "safe -> violation")} false VIOLATION') + if args.csv: + with open(args.csv, 'w', newline='') as f: + w = csv.DictWriter(f, fieldnames=sorted({k for row in rows for k in row})) + w.writeheader() + w.writerows(rows) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/targets/sv-comp/swat-benchmarks/scripts/families.py b/targets/sv-comp/swat-benchmarks/scripts/families.py new file mode 100644 index 00000000..5d6a51d2 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/scripts/families.py @@ -0,0 +1,520 @@ +"""Parameterised task families that are expected to benefit from SA pruning, plus a control. + +Each family is a function (variant, n, safe) -> Task. All families share TARGET: the only +assertion, reached through three input-dependent branches. The VIOLATION and SAFE variants differ +in one comparison only, so both have the same branch structure and the same SA graph shape. +""" + +from task import Task, ints + +# The VIOLATION variant fails for a = 1001, b = 2996. In the SAFE variant b = 3a - 7 and b < a +# would need a < 3.5, which contradicts a > 1000; a < 100000 rules out int overflow in 3 * a. +TARGET_WITNESS = [1001, 2996] + + +def target(safe: bool, indent: str = ' ', static: bool = True, name: str = 'target') -> str: + op = '<' if safe else '>' + return f'''{indent}// The only assertion in the program. +{indent}{'static ' if static else ''}void {name}(int a, int b) {{ +{indent} if (a > 1000 && a < 100000) {{ +{indent} if (b == 3 * a - 7) {{ +{indent} if (b {op} a) {{ +{indent} assert false; +{indent} }} +{indent} }} +{indent} }} +{indent}}} +''' + + +SAFE_REASON = ('target() needs a > 1000, b == 3 * a - 7 and b < a, i.e. a < 3.5: unsatisfiable. ' + 'a < 100000 rules out overflow in 3 * a.') + + +def handler(i: int, k: int, static: bool = True, name: str = None, indent: str = ' ') -> str: + """A method with k independent input-dependent branches (2^k feasible paths), no assert.""" + conds = [f'a > {i}', f'b > {2 * i}', f'c < {-i}', f'd == {i + 5}'][:k] + body = ''.join(f'{indent} if ({cond}) r += {1 << j};\n' for j, cond in enumerate(conds)) + return f'''{indent}{'static ' if static else ''}int {name or f'handler{i}'}(int a, int b, int c, int d) {{ +{indent} int r = {i}; +{body}{indent} return r; +{indent}}} +''' + + +HEADER = '''import org.sosy_lab.sv_benchmarks.Verifier; + +''' + + +def read_inputs(names, indent=' '): + return ''.join(f'{indent}int {v} = Verifier.nondetInt();\n' for v in names) + + +# --- dispatcher ------------------------------------------------------------------------------ +# Input `cmd` picks one of n commands. Commands 0 .. n-2 run a handler with 4 independent +# branches (16 paths each); command n-1, the last one, runs target(). Unpruned DFS explores +# ~16 (n - 1) paths, pruned DFS only the dispatch and target(). + +DISPATCHER_VARIANTS = ['switch', 'ifchain', 'instance', 'loop'] + + +def dispatcher(variant: str, n: int, safe: bool) -> Task: + k = 4 + inputs = ['cmd', 'a', 'b', 'c', 'd'] + witness = [n - 1] + TARGET_WITNESS + [0, 0] + static = variant != 'instance' + recv = 'm.' if variant == 'instance' else '' + handlers = ''.join(handler(i, k, static=static) for i in range(n - 1)) + + if variant in ('switch', 'instance', 'loop'): + cases = ''.join(f' case {i}: r = {recv}handler{i}(a, b, c, d); break;\n' for i in range(n - 1)) + dispatch = f''' int r; + switch (cmd) {{ +{cases} case {n - 1}: {recv}target(a, b); r = 0; break; + default: r = -1; + }} + return r; +''' + elif variant == 'ifchain': + chain = ''.join(f' {"else " if i else ""}if (cmd == {i}) r = handler{i}(a, b, c, d);\n' for i in range(n - 1)) + dispatch = f''' int r = -1; +{chain} {"else " if n > 1 else ""}if (cmd == {n - 1}) {{ target(a, b); r = 0; }} + return r; +''' + else: + raise ValueError(variant) + + if variant == 'instance': + main = f''' int calls; + + public static void main(String[] args) {{ +{read_inputs(inputs)} Main m = new Main(); + m.calls = dispatch(m, cmd, a, b, c, d); + }} + + static int dispatch(Main m, int cmd, int a, int b, int c, int d) {{ +{dispatch} }} +''' + elif variant == 'loop': + # Two rounds of dispatch: a handler in round 0 can still reach target() in round 1, so + # only round 1 can be pruned (the event-loop shape, as in MinePump). + main = f''' public static void main(String[] args) {{ + for (int round = 0; round < 2; round++) {{ +{read_inputs(inputs, indent=' ')} dispatch(cmd, a, b, c, d); + }} + }} + + static int dispatch(int cmd, int a, int b, int c, int d) {{ +{dispatch} }} +''' + else: + main = f''' public static void main(String[] args) {{ +{read_inputs(inputs)} dispatch(cmd, a, b, c, d); + }} + + static int dispatch(int cmd, int a, int b, int c, int d) {{ +{dispatch} }} +''' + src = f'''{HEADER}// dispatcher/{variant}: cmd picks one of {n} commands, only the last one (cmd == {n - 1}) can reach the assert. +public class Main {{ +{main} +{handlers} +{target(safe, static=static)}}} +''' + return Task('dispatcher', f'{variant}_n{n:02d}', safe, src, ints(witness), SAFE_REASON) + + +# --- validation-gauntlet --------------------------------------------------------------------- +# n checks, each rejecting its own input v_i and leaving early, then target(). The early-exit +# sides lead away from the assert. +# return: `if (v_i < i) return;` a leaf: pruning saves one round per check +# report: `if (v_i < i) { report(i, v_i); return; }` report() has 3 branches on v_i +# throw: `if (v_i < i) throw new IllegalArgumentException();`, caught in main, which returns +# exit: `if (v_i < i) { report(i, v_i); System.exit(1); }` the graph does not know exit() +# ends the program, so it sees target() as reachable: no gain expected + +GAUNTLET_VARIANTS = ['return', 'report', 'throw', 'exit'] + +REPORT = ''' static int errors; + + // Classifies a rejected value; three branches, no assert. + static void report(int check, int v) { + if (v < -1000) errors += 100; + if (v < -10) errors += 10; + if (v == check - 1) errors += 1; + } +''' + + +def gauntlet(variant: str, n: int, safe: bool) -> Task: + vs = [f'v{i}' for i in range(n)] + inputs = ['a', 'b'] + vs + witness = TARGET_WITNESS + [i for i in range(n)] # v_i = i passes check i + reject = { + 'return': 'return;', + 'report': '{{ report({i}, v{i}); return; }}', + 'throw': 'throw new IllegalArgumentException();', + 'exit': '{{ report({i}, v{i}); System.exit(1); }}', + }[variant] + checks = ''.join(f' if (v{i} < {i}) ' + reject.format(i=i) + '\n' for i in range(n)) + params = ', '.join(f'int {v}' for v in inputs) + args = ', '.join(inputs) + if variant == 'throw': + call = f''' try {{ + run({args}); + }} catch (IllegalArgumentException e) {{ + return; + }} +''' + else: + call = f' run({args});\n' + src = f'''{HEADER}// validation-gauntlet/{variant}: {n} checks that each leave early, then the only assert. +public class Main {{ + public static void main(String[] args) {{ +{read_inputs(inputs)}{call} }} + + static void run({params}) {{ +{checks} stage1(a, b); + }} + + static void stage1(int a, int b) {{ + stage2(a + 1, b); + }} + + static void stage2(int a, int b) {{ + target(a - 1, b); + }} + +{REPORT if variant in ('report', 'exit') else ''} +{target(safe)}}} +''' + return Task('validation-gauntlet', f'{variant}_n{n:02d}', safe, src, ints(witness), SAFE_REASON) + + +# --- error-handling -------------------------------------------------------------------------- +# n steps, each in its own try/catch in main. A step fails on a bad input; its catch block logs +# (three branches) and returns. The assert is on the success path after all steps. +# explicit: the step throws IllegalArgumentException itself (one frame below the catch) +# deep: the throw is two frames below the catch +# arith: implicit ArithmeticException from `100 / (x - i)` +# bounds: implicit ArrayIndexOutOfBoundsException from `TABLE[x]` + +ERROR_VARIANTS = ['explicit', 'deep', 'arith', 'bounds'] + + +def error_handling(variant: str, n: int, safe: bool) -> Task: + xs = [f'x{i}' for i in range(n)] + inputs = ['a', 'b'] + xs + exc = {'explicit': 'IllegalArgumentException', 'deep': 'IllegalArgumentException', + 'arith': 'ArithmeticException', 'bounds': 'ArrayIndexOutOfBoundsException'}[variant] + if variant == 'explicit': + step = ''' static int step{i}(int x) {{ + if (x < {lo}) throw new IllegalArgumentException(); + return x + {i}; + }} +''' + ok = lambda i: 0 + elif variant == 'deep': + step = ''' static int step{i}(int x) {{ + return check{i}(x) + {i}; + }} + + static int check{i}(int x) {{ + if (x < {lo}) throw new IllegalArgumentException(); + return x; + }} +''' + ok = lambda i: 0 + elif variant == 'arith': + # Fails for x == i + 1 only; x = 0 passes every step (divisor -i - 1 != 0). + step = ''' static int step{i}(int x) {{ + return 100 / (x - {i} - 1); + }} +''' + ok = lambda i: 0 + elif variant == 'bounds': + step = ''' static int step{i}(int x) {{ + return TABLE[x] + {i}; + }} +''' + ok = lambda i: 0 + steps = ''.join(step.format(i=i, lo=-(i + 1)) for i in range(n)) + tries = ''.join(f''' try {{ + acc += step{i}(x{i}); + }} catch ({exc} e) {{ + log({i}, x{i}); + return; + }} +''' for i in range(n)) + witness = TARGET_WITNESS + [ok(i) for i in range(n)] + table = ' static final int[] TABLE = {3, 1, 4, 1};\n\n' if variant == 'bounds' else '' + src = f'''{HEADER}// error-handling/{variant}: {n} steps that can fail with {exc}; each catch block logs and returns. +public class Main {{ +{table} static int logged; + + public static void main(String[] args) {{ +{read_inputs(inputs)} int acc = 0; +{tries} target(a, b); + }} + +{steps} + // Logs a failed step; three branches, no assert. + static void log(int step, int x) {{ + if (x < -1000) logged += 100; + if (x > 1000) logged += 10; + if (x == step) logged += 1; + }} + +{target(safe)}}} +''' + return Task('error-handling', f'{variant}_n{n:02d}', safe, src, ints(witness), SAFE_REASON) + + +# --- dead-callees ---------------------------------------------------------------------------- +# n calls to helpers with two branches each (3 paths), on paths that cannot reach the assert. +# suffix: the calls come after target(); unpruned DFS explores 3^n paths behind it +# guarded: the calls are made, and main returns, if `mode == 1`; target() comes after +# shared: as guarded, but all calls go to one helper that the live path also calls before +# target(): only a context-sensitive walk can prune the dead calls + +DEAD_VARIANTS = ['suffix', 'guarded', 'shared'] + + +def dead_callees(variant: str, n: int, safe: bool) -> Task: + cs = [f'c{i}' for i in range(n)] + inputs = ['mode', 'p', 'a', 'b'] + cs + witness = [0, 2] + TARGET_WITNESS + [0] * n + if variant == 'shared': + helpers = ''' // Two branches, three paths. + static int classify(int x) { + if (x < 0) return 0; + if (x < 100) return 1; + return 2; + } +''' + calls = ''.join(f' stat += classify(c{i});\n' for i in range(n)) + else: + helpers = ''.join(f''' static int helper{i}(int x) {{ + if (x < {i}) return 0; + if (x < {100 + i}) return 1; + return 2; + }} + +''' for i in range(n)) + calls = ''.join(f' stat += helper{i}(c{i});\n' for i in range(n)) + if variant == 'suffix': + body = f''' target(a, b); + int stat = 0; +{calls.replace(' ', ' ')} statistics = stat; +''' + elif variant == 'guarded': + body = f''' if (mode == 1) {{ + int stat = 0; +{calls} statistics = stat; + return; + }} + target(a, b); +''' + else: + body = f''' if (mode == 1) {{ + int stat = 0; +{calls} statistics = stat; + return; + }} + if (classify(p) == 2) {{ + target(a, b); + }} +''' + witness = [0, 100] + TARGET_WITNESS + [0] * n + src = f'''{HEADER}// dead-callees/{variant}: {n} helper calls that cannot reach the assert. +public class Main {{ + static int statistics; + + public static void main(String[] args) {{ +{read_inputs(inputs)}{body} }} + +{helpers} +{target(safe)}}} +''' + return Task('dead-callees', f'{variant}_n{n:02d}', safe, src, ints(witness), SAFE_REASON) + + +# --- virtual-dispatch ------------------------------------------------------------------------ +# cmd selects one of n subclasses of Cmd; only the last one's run() calls target(). +# local: `case i: new Ci().run(...)`: the receiver type is exact, so the call resolves +# factory: `make(cmd).run(...)`: make() can return any Ci, so the call is ambiguous and the +# graph is incomplete there; no gain expected, but the verdict must stay right +# array: `TABLE[cmd].run(...)`: a receiver read from an array is untraceable; likewise + +VIRTUAL_VARIANTS = ['local', 'factory', 'array'] + + +def virtual_dispatch(variant: str, n: int, safe: bool) -> Task: + k = 4 + inputs = ['cmd', 'a', 'b', 'c', 'd'] + witness = [n - 1] + TARGET_WITNESS + [0, 0] + classes = ''.join(f''' static class C{i} extends Cmd {{ +{handler(i, k, static=False, name='run', indent=' ')} }} + +''' for i in range(n - 1)) + classes += f''' static class C{n - 1} extends Cmd {{ + int run(int a, int b, int c, int d) {{ + target(a, b); + return 0; + }} + }} +''' + if variant == 'local': + cases = ''.join(f' case {i}: return new C{i}().run(a, b, c, d);\n' for i in range(n)) + dispatch = f''' switch (cmd) {{ +{cases} default: return -1; + }} +''' + extra = '' + elif variant == 'factory': + cases = ''.join(f' case {i}: return new C{i}();\n' for i in range(n)) + dispatch = ''' Cmd command = make(cmd); + if (command == null) return -1; + return command.run(a, b, c, d); +''' + extra = f''' static Cmd make(int cmd) {{ + switch (cmd) {{ +{cases} default: return null; + }} + }} + +''' + elif variant == 'array': + dispatch = ''' if (cmd < 0 || cmd >= TABLE.length) return -1; + return TABLE[cmd].run(a, b, c, d); +''' + extra = ' static final Cmd[] TABLE = {' + ', '.join(f'new C{i}()' for i in range(n)) + '};\n\n' + src = f'''{HEADER}// virtual-dispatch/{variant}: cmd selects one of {n} subclasses, only C{n - 1}.run() reaches the assert. +public class Main {{ + static abstract class Cmd {{ + abstract int run(int a, int b, int c, int d); + }} + +{classes} +{extra} public static void main(String[] args) {{ +{read_inputs(inputs)} dispatch(cmd, a, b, c, d); + }} + + static int dispatch(int cmd, int a, int b, int c, int d) {{ +{dispatch} }} + +{target(safe)}}} +''' + return Task('virtual-dispatch', f'{variant}_n{n:02d}', safe, src, ints(witness), SAFE_REASON) + + +# --- irrelevant-prefix (control) ------------------------------------------------------------- +# n independent branches on their own inputs before target(). Every path reaches the assert, +# so reachability pruning cannot remove anything: 2^n paths either way. + +PREFIX_VARIANTS = ['count'] + + +def irrelevant_prefix(variant: str, n: int, safe: bool) -> Task: + xs = [f'x{i}' for i in range(n)] + inputs = ['a', 'b'] + xs + witness = TARGET_WITNESS + [0] * n + branches = ''.join(f' if (x{i} > 0) count++;\n' for i in range(n)) + src = f'''{HEADER}// irrelevant-prefix/{variant}: {n} independent branches before the only assert (control: nothing to prune). +public class Main {{ + static int count; + + public static void main(String[] args) {{ +{read_inputs(inputs)}{branches} target(a, b); + }} + +{target(safe)}}} +''' + return Task('irrelevant-prefix', f'{variant}_n{n:02d}', safe, src, ints(witness), SAFE_REASON) + + +# --- exception-dispatch ---------------------------------------------------------------------- +# Measures what SA loses after an exception it does not model. The command is parsed from input +# t; for t < 0 parsing throws and the handler derives the command from t as well, so both paths +# lead into the same dispatcher as in dispatcher/switch (only the last command reaches the +# assert). +# npe: a NullPointerException (no SWAT guard: after it the walk gives up for the path, so +# the dead handlers behind the exception path are not pruned) +# explicit: control, an explicit IllegalArgumentException, which the graph models precisely + +EXCEPTION_DISPATCH_VARIANTS = ['npe', 'explicit'] + + +def exception_dispatch(variant: str, n: int, safe: bool) -> Task: + k = 4 + inputs = ['t', 'a', 'b', 'c', 'd'] + witness = [-n] + TARGET_WITNESS + [0, 0] # t = -n: the exception path, then cmd = n - 1 + if variant == 'npe': + parse = ''' static int parse(int t) { + Holder h = t < 0 ? null : HOLDER; + return h.base + t; + } +''' + caught = 'NullPointerException' + else: + parse = ''' static int parse(int t) { + if (t < 0) throw new IllegalArgumentException(); + return HOLDER.base + t; + } +''' + caught = 'IllegalArgumentException' + handlers = ''.join(handler(i, k) for i in range(n - 1)) + cases = ''.join(f' case {i}: r = handler{i}(a, b, c, d); break;\n' for i in range(n - 1)) + src = f'''{HEADER}// exception-dispatch/{variant}: the command is parsed from t; for t < 0 parsing throws +// ({caught}) and the handler derives the command as -t - 1. Only command {n - 1} reaches the assert. +public class Main {{ + static class Holder {{ + int base = 0; + }} + + static final Holder HOLDER = new Holder(); + +{parse} + public static void main(String[] args) {{ +{read_inputs(inputs)} int cmd; + try {{ + cmd = parse(t); + }} catch ({caught} e) {{ + cmd = -t - 1; + }} + dispatch(cmd, a, b, c, d); + }} + + static int dispatch(int cmd, int a, int b, int c, int d) {{ + int r; + switch (cmd) {{ +{cases} case {n - 1}: target(a, b); r = 0; break; + default: r = -1; + }} + return r; + }} + +{handlers} +{target(safe)}}} +''' + return Task('exception-dispatch', f'{variant}_n{n:02d}', safe, src, ints(witness), SAFE_REASON) + + +FAMILIES = { + 'dispatcher': (dispatcher, DISPATCHER_VARIANTS, [2, 4, 8, 16, 32]), + 'validation-gauntlet': (gauntlet, GAUNTLET_VARIANTS, [2, 4, 8, 16, 32]), + 'error-handling': (error_handling, ERROR_VARIANTS, [2, 4, 8, 16, 32]), + 'dead-callees': (dead_callees, DEAD_VARIANTS, [1, 2, 3, 4, 5, 6]), + 'virtual-dispatch': (virtual_dispatch, VIRTUAL_VARIANTS, [2, 4, 8, 16, 32]), + 'irrelevant-prefix': (irrelevant_prefix, PREFIX_VARIANTS, [2, 4, 6, 8]), + 'exception-dispatch': (exception_dispatch, EXCEPTION_DISPATCH_VARIANTS, [2, 4, 8, 16, 32]), +} + + +def all_family_tasks(): + for fn, variants, ns in FAMILIES.values(): + for variant in variants: + for n in ns: + for safe in (True, False): + yield fn(variant, n, safe) diff --git a/targets/sv-comp/swat-benchmarks/scripts/fetch_tools.sh b/targets/sv-comp/swat-benchmarks/scripts/fetch_tools.sh new file mode 100755 index 00000000..7c3c1aaf --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/scripts/fetch_tools.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# Downloads the real-tools libraries' sources from Maven Central into java/real-tools/lib-*/ +# (gitignored); build.sh compiles them. Unmodified upstream sources, kept under their licenses. +set -euo pipefail +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../java/real-tools" && pwd)" +MAVEN=https://repo1.maven.org/maven2 +fetch() { # lib-dir maven-path + rm -rf "$DIR/$1" && mkdir -p "$DIR/$1" + curl -sSfL "$MAVEN/$2-sources.jar" -o "$DIR/$1/sources.jar" + (cd "$DIR/$1" && unzip -q sources.jar && rm -rf sources.jar META-INF) +} +fetch lib-getopt gnu/getopt/java-getopt/1.0.13/java-getopt-1.0.13 +rm -f "$DIR/lib-getopt/gnu/getopt/GetoptDemo.java" +fetch lib-opencsv net/sf/opencsv/opencsv/2.3/opencsv-2.3 +# Only the parser: the rest needs java.sql and bean introspection. +find "$DIR/lib-opencsv" -name '*.java' ! -name CSVParser.java -delete +echo "Fetched: $(ls "$DIR" | grep '^lib-' | tr '\n' ' ')" diff --git a/targets/sv-comp/swat-benchmarks/scripts/gen.py b/targets/sv-comp/swat-benchmarks/scripts/gen.py new file mode 100755 index 00000000..71e1c640 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/scripts/gen.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +"""Generates the swat-benchmarks tasks (sources and .yml files) into ../java/. + + ./gen.py # all suites + ./gen.py dispatcher ... # only these suites + +Generated suite folders are emptied first (except lib-* folders), so stale tasks do not linger. +""" + +import shutil +import sys +from pathlib import Path + +from families import all_family_tasks +from soundness import all_soundness_tasks +from tools import all_tool_tasks +from relevance import all_relevance_tasks +from loopsummary import all_loop_summary_tasks + +JAVA_DIR = Path(__file__).resolve().parent.parent / 'java' + + +def main(suites): + tasks = [*all_family_tasks(), *all_soundness_tasks(), *all_tool_tasks(), *all_relevance_tasks(), *all_loop_summary_tasks()] + if suites: + tasks = [t for t in tasks if t.suite in suites] + for suite in sorted({t.suite for t in tasks}): + # Keep the libraries fetch_tools.sh put there. + for entry in (JAVA_DIR / suite).glob('*'): + if not entry.name.startswith('lib-'): + shutil.rmtree(entry) if entry.is_dir() else entry.unlink() + for t in tasks: + t.write(JAVA_DIR) + print(f'Wrote {len(tasks)} tasks into {len({t.suite for t in tasks})} suites.') + + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/targets/sv-comp/swat-benchmarks/scripts/loopsummary.py b/targets/sv-comp/swat-benchmarks/scripts/loopsummary.py new file mode 100644 index 00000000..a14ea0cc --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/scripts/loopsummary.py @@ -0,0 +1,424 @@ +"""Tasks for loop summarization (see the executor's loop summaries and RESULTS.md). + +`loop-summary`: families over the loop length n in which the assert depends on what a loop +computes from an input, so the loop's branches are relevant and slicing cannot help: +- diamonds: an input-dependent branch per iteration that only updates a counter or an accumulator + (`count-below`, `ternary-sum`, `found-index`); merged, the counter is a formula of the input and + the assert's branch can be flipped directly, instead of one more iteration per round; +- counting loops whose bound is an input (`count-up`, `count-down`); summarized, the loop's + variables are closed forms of the input and its trip count needs no enumeration. +SAFE variants ask for a value the loop can never produce. + +`loop-summary-soundness`: SAFE/VIOLATION pairs that must not be summarized (a call, a branch or a +library object in the body, a non-strict bound) or where summarizing must keep Java's overflow, +plus nested loops. As in sa-soundness, @HIT(x)@ is `x > 1000 && x < HI`. +""" + +from soundness import render +from task import Task, ints + +IMPORT = 'import org.sosy_lab.sv_benchmarks.Verifier;\n' + +FAMILIES = { + # name: (source, violating value of K / SAFE value of K, witness as a function of n) + 'count-below': ('''IMPORT +// loop-summary/count-below: count = number of i < n with i < val. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int count = 0; + for (int i = 0; i < @N@; i++) { + if (i < val) { + count++; + } + } + if (count == @K@) assert false; + } +} +''', lambda n: n - 3, lambda n: n + 1, lambda n: ints([n - 3]), + 'count is at most n.'), + 'ternary-sum': ('''IMPORT +// loop-summary/ternary-sum: s grows by 2 in every iteration with i < val (a conditional expression). +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int s = 0; + for (int i = 0; i < @N@; i++) { + s = i < val ? s + 2 : s; + } + if (s == @K@) assert false; + } +} +''', lambda n: 2 * (n - 3), lambda n: 2 * n + 2, lambda n: ints([n - 3]), + 's is at most 2n.'), + 'found-index': ('''IMPORT +// loop-summary/found-index: found = the index equal to val, or -1. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int found = -1; + for (int i = 0; i < @N@; i++) { + if (i == val) { + found = i; + } + } + if (found == @K@) assert false; + } +} +''', lambda n: n - 2, lambda n: n, lambda n: ints([n - 2]), + 'found is below n.'), + 'count-up': ('''IMPORT +// loop-summary/count-up: a counting loop up to an input bound. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int i = 0; + int s = 0; + while (i < val) { + i++; + s += 3; + } + if (s == @K@) assert false; + } +} +''', lambda n: 3 * n, lambda n: 3 * n + 1, lambda n: ints([n]), + 's = 3 * max(val, 0) with val <= 2^31 - 1, which is never 3n + 1 (mod 2^32).'), + 'count-down': ('''IMPORT +// loop-summary/count-down: a loop counting an input down to zero. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int i = val; + int c = 0; + while (i > 0) { + i--; + c += 2; + } + if (c == @K@) assert false; + } +} +''', lambda n: 2 * n, lambda n: 2 * n + 1, lambda n: ints([n]), + 'c is even.'), + 'count-even': ('''IMPORT +// loop-summary/count-even: a nested condition per iteration (i % 2 on a constant divisor). +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int even = 0; + int odd = 0; + for (int i = 0; i < @N@; i++) { + if (i < val) { + if (i % 2 == 0) { + even++; + } else { + odd++; + } + } + } + if (even == @K@) assert false; + } +} +''', lambda n: (n - 3 + 1) // 2, lambda n: n // 2 + 1, lambda n: ints([n - 3]), + 'even counts the even i < min(val, n): at most n / 2.'), + 'count-upto': ('''IMPORT +// loop-summary/count-upto: a counting loop up to and including an input bound. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int i = 1; + int s = 0; + while (i <= val) { + i++; + s += 2; + } + if (s == @K@) assert false; + } +} +''', lambda n: 2 * n, lambda n: 2 * n + 1, lambda n: ints([n]), + 's is even.'), + 'count-up-print': ('''IMPORT +// loop-summary/count-up-print: a counting loop up to an input bound that also prints. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int i = 0; + int s = 0; + while (i < val) { + i++; + s += 3; + System.out.println(i); + } + if (s == @K@) assert false; + } +} +''', lambda n: 3 * n, lambda n: 3 * n + 1, lambda n: ints([n]), + 's = 3 * max(val, 0) with val <= 2^31 - 1, which is never 3n + 1 (mod 2^32).'), + 'count-up-log': ('''IMPORT +// loop-summary/count-up-log: a counting loop that also logs into a field and a scratch array. +public class Main { + static int logged; + static int[] recent = new int[8]; + + static void log(int i) { + logged++; + recent[i & 7] = i; + } + + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int i = 0; + int s = 0; + while (i < val) { + i++; + s += 2; + log(i); + } + if (s == @K@) assert false; + } +} +''', lambda n: 2 * n, lambda n: 2 * n + 1, lambda n: ints([n]), + 's is even.'), +} + +SOUNDNESS = { + 'body-field-relevant': ('''IMPORT +// A counting loop whose body also counts in a field the assert's guard reads: the field's count +// depends on the trip count but has no closed form, so the summary must not be used. +public class Main { + static int hits; + + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int i = 0; + int s = 0; + while (i < val) { + i++; + s++; + hits = hits + 1; + } + if (hits == 5) { + if (@HIT(b)@) assert false; + } + } +} +''', ints([5, 1001])), + 'body-call-relevant': ('''IMPORT +// A counting loop whose body calls a helper that sets a flag the assert's guard reads. +public class Main { + static boolean reached; + + static void visit(int i) { + if (i == 4) { + reached = true; + } + } + + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int i = 0; + while (i < val) { + i++; + visit(i); + } + if (reached) { + if (@HIT(b)@) assert false; + } + } +} +''', ints([4, 1001])), + 'body-call': ('''IMPORT +// A counting loop with a call in its body: not a pure loop. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int i = 0; + int s = 0; + while (i < val) { + i++; + s += 2; + System.out.println(i); + } + if (s == 10) { + if (@HIT(b)@) assert false; + } + } +} +''', ints([5, 1001])), + 'body-branch': ('''IMPORT +// A counting loop with a branch in its body. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int i = 0; + int s = 0; + while (i < val) { + i++; + if (i == 3) { + s += 10; + } + s += 1; + } + if (s == 15) { + if (@HIT(b)@) assert false; + } + } +} +''', ints([5, 1001])), + 'heap-in-loop': ('''import java.util.ArrayList; +import java.util.List; +IMPORT +// A counting loop that adds to a list, whose size the assert's guard reads. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + List list = new ArrayList(); + int i = 0; + while (i < val) { + i++; + list.add(i); + } + if (list.size() == 4) { + if (@HIT(b)@) assert false; + } + } +} +''', ints([4, 1001])), + 'non-strict-bound': ('''IMPORT +// i <= val: the loop never ends for val = 2^31 - 1, so it has no closed form. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + if (val > 100) return; + int i = 0; + int s = 0; + while (i <= val) { + i++; + s++; + } + if (s == 6) { + if (@HIT(b)@) assert false; + } + } +} +''', ints([5, 1001])), + 'counter-overflow': ('''IMPORT +// The merged counter must wrap around like Java's int: three steps of 10^9 are negative. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int count = 0; + for (int i = 0; i < 5; i++) { + if (i < val) { + count += 1000000000; + } + } + if (count < 0) { + if (@HIT(b)@) assert false; + } + } +} +''', ints([3, 1001])), + 'step-overflow': ('''IMPORT +// The summarized loop's accumulator must wrap around like Java's int. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + if (val > 100) return; + int i = 0; + int s = 0; + while (i < val) { + i++; + s += 1000000000; + } + if (s < 0) { + if (@HIT(b)@) assert false; + } + } +} +''', ints([3, 1001])), + 'nested-loops': ('''IMPORT +// A counting loop inside a loop: c = 4 * max(val, 0). +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int c = 0; + for (int k = 0; k < 4; k++) { + int j = 0; + while (j < val) { + j++; + c++; + } + } + if (c == 12) { + if (@HIT(b)@) assert false; + } + } +} +''', ints([3, 1001])), + 'diamond-then-check': ('''IMPORT +// Merged counters feed a later input-dependent check. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int even = 0; + int odd = 0; + for (int i = 0; i < 10; i++) { + if (i < val) { + if (i % 2 == 0) { + even++; + } else { + odd++; + } + } + } + if (even == 3 && odd == 2) { + if (@HIT(b)@) assert false; + } + } +} +''', ints([5, 1001])), + 'negative-start': ('''IMPORT +// A counting loop from an input start to an input bound. +public class Main { + public static void main(String[] args) { + int lo = Verifier.nondetInt(); + int hi = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + if (hi - lo > 50 || lo < -50) return; + int i = lo; + int n = 0; + while (i < hi) { + i++; + n++; + } + if (n == 7 && i == 3) { + if (@HIT(b)@) assert false; + } + } +} +''', ints([-4, 3, 1001])), +} + +SAFE_REASON = 'the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable.' + + +def all_loop_summary_tasks(): + for name, (src, k_violation, k_safe, witness, reason) in FAMILIES.items(): + for n in (10, 100, 1000): + for safe in (True, False): + k = k_safe(n) if safe else k_violation(n) + text = src.replace('IMPORT\n', IMPORT).replace('@N@', str(n)).replace('@K@', str(k)) + yield Task('loop-summary', f'{name}_n{n:04d}', safe, text, witness(n), reason) + for name, (src, witness) in SOUNDNESS.items(): + for safe in (True, False): + yield Task('loop-summary-soundness', name, safe, render(src, safe), witness, SAFE_REASON) diff --git a/targets/sv-comp/swat-benchmarks/scripts/relevance.py b/targets/sv-comp/swat-benchmarks/scripts/relevance.py new file mode 100644 index 00000000..6c219fff --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/scripts/relevance.py @@ -0,0 +1,595 @@ +"""Tasks for the relevance analysis (slicing, see SWAT's Relevance.py). + +`relevance`: tasks where most branches cannot affect the assert, so that reachability pruning keeps +them all and the search has to enumerate their combinations, while slicing does not flip them: +the example from the design discussion verbatim (`example`, `example-long`), and families over the +loop length n. + +`relevance-soundness`: SAFE/VIOLATION pairs in which a branch that looks irrelevant at first sight +does matter (it writes what the assert reads, through a field, an array, an alias, a callee, a +library object, a callback or a static initializer; it reads inputs; it may end the program; it +throws to a handler elsewhere), or in which the initial path ends inside an irrelevant region. A +false SAFE on a VIOLATION task means the slice was wrong. @HIT(x)@ is `x > 1000 && x < HI` with +HI = 100000 (VIOLATION) or 1001 (SAFE, unsatisfiable), as in sa-soundness. +""" + +from soundness import render +from task import Task, ints + +SAFE_REASON = 'the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable.' +IMPORT = 'import org.sosy_lab.sv_benchmarks.Verifier;\n' + +EXAMPLE = '''IMPORT +// relevance/example: the loop's branches all lead to the assert either way, so reachability +// pruning keeps them; none of them can change val, so slicing does not flip them. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + for (int i = 0; i < @N@; i++) { + if (i < val) { + System.out.println(i); + } + } + assert val != 3141592; + } +} +''' + +FAMILIES = { + # name: (source, witness); @N@ is the loop length + 'loop-print': ('''IMPORT +// relevance/loop-print: an input-dependent branch per iteration that only prints. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + for (int i = 0; i < @N@; i++) { + if (i < val) { + System.out.println(i); + } + } + if (@HIT(val)@) assert false; + } +} +''', ints([1001])), + 'loop-helper': ('''IMPORT +// relevance/loop-helper: each iteration calls a helper with input-dependent branches that only +// update a counter the assert does not read. +public class Main { + static int logged; + + static void log(int i, int v) { + if (i % 2 == 0) { + if (i < v) { + logged++; + } + } else if (i > v) { + logged += 2; + } + } + + public static void main(String[] args) { + int val = Verifier.nondetInt(); + for (int i = 0; i < @N@; i++) { + log(i, val); + } + if (@HIT(val)@) assert false; + } +} +''', ints([1001])), + 'scratch-array': ('''IMPORT +// relevance/scratch-array: the loop fills a scratch array (bounds-checked) the assert never reads. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int[] hist = new int[@N@]; + for (int i = 0; i < @N@; i++) { + if (i < val) { + hist[i] = i; + } else { + hist[i] = -i; + } + } + System.out.println(hist.length); + if (@HIT(val)@) assert false; + } +} +''', ints([1001])), + 'two-inputs': ('''IMPORT +// relevance/two-inputs: the loop branches on one input, the assert on another. +public class Main { + public static void main(String[] args) { + int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + for (int i = 0; i < @N@; i++) { + if (i < a) { + System.out.print('.'); + } else if (i == a + 1) { + System.out.print('#'); + } + } + System.out.println(); + if (b > a) { + if (@HIT(b)@) assert false; + } + } +} +''', ints([0, 1001])), +} + +SOUNDNESS = { + 'relevant-counter': ('''IMPORT +// The loop's branch counts, and the count decides whether the assert is reached: relevant. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int count = 0; + for (int i = 0; i < 10; i++) { + if (i < val) { + count++; + } + } + if (count == 7) { + if (@HIT(b)@) assert false; + } + } +} +''', ints([7, 1001])), + 'field-write': ('''IMPORT +// The region writes a static field the assert's guard reads. +public class Main { + static int flag; + + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + for (int i = 0; i < 10; i++) { + if (i == val) { + flag = 1; + } + } + if (flag == 1) { + if (@HIT(b)@) assert false; + } + } +} +''', ints([3, 1001])), + 'array-write': ('''IMPORT +// The region writes an array element that is summed up later. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int[] marks = new int[10]; + for (int i = 0; i < 10; i++) { + if (i == val) { + marks[i] = 1; + } + } + int sum = 0; + for (int j = 0; j < 10; j++) { + sum += marks[j]; + } + if (sum == 1) { + if (@HIT(b)@) assert false; + } + } +} +''', ints([3, 1001])), + 'alias-field': ('''IMPORT +// The region writes a field through an alias of the object the assert's guard reads. +public class Main { + static class Box { + int v; + } + + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + Box p = new Box(); + Box q = p; + for (int i = 0; i < 10; i++) { + if (i == val) { + q.v = 1; + } + } + if (p.v == 1) { + if (@HIT(b)@) assert false; + } + } +} +''', ints([3, 1001])), + 'callee-write': ('''IMPORT +// The region calls a method that writes a field the assert's guard reads. +public class Main { + static boolean marked; + + static void mark() { + marked = true; + } + + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + for (int i = 0; i < 10; i++) { + if (i == val) { + mark(); + } + } + if (marked) { + if (@HIT(b)@) assert false; + } + } +} +''', ints([3, 1001])), + 'return-value': ('''IMPORT +// The region decides a callee's return value. +public class Main { + static int scan(int val) { + int found = -1; + for (int i = 0; i < 10; i++) { + if (i == val) { + found = i; + } + } + return found; + } + + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + if (scan(val) == 4) { + if (@HIT(b)@) assert false; + } + } +} +''', ints([4, 1001])), + 'loop-break': ('''IMPORT +// The branch leaves the loop early, which decides the counter read after it. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int i; + for (i = 0; i < 10; i++) { + if (i == val) { + break; + } + } + if (i == 6) { + if (@HIT(b)@) assert false; + } + } +} +''', ints([6, 1001])), + 'input-shift': ('''IMPORT +// The region reads inputs, which shifts which input every later read gets. +public class Main { + public static void main(String[] args) { + int x = Verifier.nondetInt(); + for (int i = 0; i < 3; i++) { + if (i < x) { + int junk = Verifier.nondetInt(); + System.out.println(junk); + } + } + int z = Verifier.nondetInt(); + if (x == 2) { + if (@HIT(z)@) assert false; + } + } +} +''', ints([2, 0, 0, 1001])), + 'assume-halt': ('''IMPORT +// An assumption in the region ends the initial run (val = 0) before the assert. +// By design, the all-zero run ends early. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + for (int i = 0; i < 10; i++) { + if (val < i) { + Verifier.assume(false); + } + } + if (@HIT(b)@) assert false; + } +} +''', ints([9, 1001])), + 'exit-in-region': ('''IMPORT +// System.exit in the region ends the initial run (val = 0) before the assert. +// By design, the all-zero run ends early. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + for (int i = 0; i < 10; i++) { + if (val < i) { + System.out.println("bye"); + System.exit(0); + } + } + if (@HIT(b)@) assert false; + } +} +''', ints([9, 1001])), + 'exception-skip': ('''IMPORT +// The region throws to a handler in the caller, which skips a write the assert's guard reads. +public class Main { + static int done; + + static void work(int val) { + for (int i = 0; i < 10; i++) { + if (i == val - 5) { + throw new IllegalStateException("stop"); + } + } + done = 1; + } + + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + try { + work(val); + } catch (IllegalStateException e) { + System.out.println("caught"); + } + if (done == 0) { + if (@HIT(b)@) assert false; + } + } +} +''', ints([5, 1001])), + 'local-handler': ('''IMPORT +// The region catches an exception of a library call locally and records it. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int state = 0; + for (int i = 0; i < 10; i++) { + if (i == val) { + try { + Integer.parseInt("x" + i); + } catch (NumberFormatException e) { + state = 1; + } + } + } + if (state == 1) { + if (@HIT(b)@) assert false; + } + } +} +''', ints([3, 1001])), + 'crash-on-initial-path': ('''IMPORT +// The initial run (val = 0) ends with an uncaught exception inside the region, before the assert. +// By design, the all-zero run ends early. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int[] a = new int[5]; + for (int i = 0; i < 10; i++) { + if (i >= val) { + a[i] = 1; + } + } + if (@HIT(b)@) assert false; + } +} +''', ints([10, 1001])), + 'crash-in-callee': ('''IMPORT +// The initial run (val = 0) throws an uncaught exception in a callee inside the region. +// By design, the all-zero run ends early. +public class Main { + static void check(int i, int v) { + if (i > v + 5) { + throw new RuntimeException("too far"); + } + } + + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + for (int i = 0; i < 10; i++) { + if (i != val) { + check(i, val); + } + } + if (@HIT(b)@) assert false; + } +} +''', ints([4, 1001])), + 'library-state': ('''import java.util.ArrayList; +import java.util.List; +IMPORT +// The region adds to a library list whose size the assert's guard reads. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + List list = new ArrayList(); + for (int i = 0; i < 10; i++) { + if (i == val) { + list.add(i); + } + } + if (list.size() == 1) { + if (@HIT(b)@) assert false; + } + } +} +''', ints([3, 1001])), + 'string-builder': ('''IMPORT +// The region appends to a StringBuilder whose length the assert's guard reads. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < 10; i++) { + if (i == val) { + sb.append('x'); + } + } + if (sb.length() == 1) { + if (@HIT(b)@) assert false; + } + } +} +''', ints([3, 1001])), + 'callback-write': ('''import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +IMPORT +// The region calls library code that calls back into a comparator, which writes a field the +// assert's guard reads. +public class Main { + static boolean compared; + + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + List list = new ArrayList(); + list.add(2); + list.add(1); + for (int i = 0; i < 5; i++) { + if (i == val) { + Collections.sort(list, new Comparator() { + public int compare(Integer x, Integer y) { + compared = true; + return x - y; + } + }); + } + } + if (compared) { + if (@HIT(b)@) assert false; + } + } +} +''', ints([2, 1001])), + 'print-callback': ('''IMPORT +// The region prints an object whose toString() (called back by the library) counts. +public class Main { + static int printed; + + static class Item { + public String toString() { + printed++; + return "item"; + } + } + + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + Item it = new Item(); + for (int i = 0; i < 5; i++) { + if (i == val) { + System.out.println(it); + } + } + if (printed == 1) { + if (@HIT(b)@) assert false; + } + } +} +''', ints([2, 1001])), + 'clinit-in-region': ('''IMPORT +// The region is where a class is first used; its static initializer writes a field the assert's +// guard reads. +public class Main { + static boolean initialized; + + static class Config { + static int level; + + static { + level = 3; + Main.initialized = true; + } + } + + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + for (int i = 0; i < 5; i++) { + if (i == val) { + System.out.println(Config.level); + } + } + if (initialized) { + if (@HIT(b)@) assert false; + } + } +} +''', ints([2, 1001])), + 'shared-helper': ('''IMPORT +// A helper with a branch is called both in an irrelevant region and to compute what the assert's +// guard reads. +public class Main { + static int clamp(int x) { + if (x > 100) { + return 100; + } + return x; + } + + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + for (int i = 0; i < 10; i++) { + if (i < val) { + System.out.println(clamp(i)); + } + } + if (clamp(val) == 100) { + if (@HIT(b)@) assert false; + } + } +} +''', ints([101, 1001])), + 'switch-in-region': ('''IMPORT +// A switch in the region sets the mode the assert's guard reads. +public class Main { + public static void main(String[] args) { + int val = Verifier.nondetInt(); + int b = Verifier.nondetInt(); + int mode = 0; + for (int i = 0; i < 4; i++) { + switch (i - val) { + case 0: + mode = 2; + break; + case 1: + System.out.println("one"); + break; + default: + break; + } + } + if (mode == 2) { + if (@HIT(b)@) assert false; + } + } +} +''', ints([2, 1001])), +} + + +def all_relevance_tasks(): + for n, base in ((100, 'example'), (10000, 'example-long')): + src = EXAMPLE.replace('IMPORT\n', IMPORT).replace('@N@', str(n)) + yield Task('relevance', base, False, src, ints([3141592]), '') + for name, (src, witness) in FAMILIES.items(): + for n in (10, 100, 1000): + for safe in (True, False): + yield Task('relevance', f'{name}_n{n:04d}', safe, + render(src.replace('@N@', str(n)), safe), witness, SAFE_REASON) + for name, (src, witness) in SOUNDNESS.items(): + for safe in (True, False): + yield Task('relevance-soundness', name, safe, render(src, safe), witness, SAFE_REASON) diff --git a/targets/sv-comp/swat-benchmarks/scripts/report.py b/targets/sv-comp/swat-benchmarks/scripts/report.py new file mode 100644 index 00000000..d9d8b21f --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/scripts/report.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python3 +"""Plots rounds and wall time against the family parameter n, with and without SA, and writes +the same numbers as Markdown tables (families.md). + + ../../scripts/.venv/bin/python3 report.py ../../runs/run_SA ../../runs/run_NOSA --out ../report + +One small panel per family variant, SAFE tasks only (their search is exhaustive, so it shows the +whole search space; VIOLATION tasks stop early). A task that timed out is drawn as a hollow marker +at its last round count / at the timeout: a lower bound. +""" + +import argparse +import re +import sys +from collections import defaultdict +from pathlib import Path + +import matplotlib +matplotlib.use('Agg') +import matplotlib.pyplot as plt +import matplotlib.ticker as ticker + +sys.path.insert(0, str(Path(__file__).resolve().parent)) +from compare import load_run # noqa: E402 + +FAMILIES = ['dispatcher', 'validation-gauntlet', 'error-handling', 'dead-callees', + 'virtual-dispatch', 'irrelevant-prefix'] +STYLE = { # mode -> (label, color, marker, linestyle); color is not the only cue + 'none': ('no SA', '#2a78d6', 'o', '--'), + 'sequential': ('SA', '#eb6834', 's', '-'), +} +INK, MUTED, GRID = '#1a1a19', '#6b6a63', '#e6e5e0' + + +def series(run): + """{(family, variant): {n: row}} for the SAFE tasks of a run.""" + out = defaultdict(dict) + for name, row in run['tasks'].items(): + m = re.fullmatch(r'([\w-]+)/(\w+)_n(\d+)_true', name) + if m and m.group(1) in FAMILIES: + out[(m.group(1), m.group(2))][int(m.group(3))] = row + return out + + +def plot(runs, metric, ylabel, out: Path): + data = [(r['mode'], series(r)) for r in runs] + panels = sorted({k for _, s in data for k in s}, key=lambda k: (FAMILIES.index(k[0]), k[1])) + cols = 4 + rows = (len(panels) + cols - 1) // cols + fig, axes = plt.subplots(rows, cols, figsize=(3.2 * cols, 2.5 * rows), squeeze=False) + for ax, key in zip(axes.flat, panels): + for mode, s in data: + label, color, marker, ls = STYLE[mode] + pts = sorted(s.get(key, {}).items()) + xs = [n for n, _ in pts] + ys = [max(row[metric] or 0, 1) for _, row in pts] + ax.plot(xs, ys, color=color, linestyle=ls, linewidth=2, label=label) + done = [(x, y) for (x, y), (_, row) in zip(zip(xs, ys), pts) if row['status'] != 'timeout'] + to = [(x, y) for (x, y), (_, row) in zip(zip(xs, ys), pts) if row['status'] == 'timeout'] + if done: + ax.scatter(*zip(*done), color=color, marker=marker, s=40, zorder=3, + edgecolors='white', linewidths=1.5) + if to: + ax.scatter(*zip(*to), facecolors='white', edgecolors=color, marker=marker, s=40, + linewidths=2, zorder=3) + ax.set_title(f'{key[0]}/{key[1]}', fontsize=9, color=INK) + ns = sorted({n for _, s in data for n in s.get(key, {})}) + if all(n & (n - 1) == 0 for n in ns): + ax.set_xscale('log', base=2) + ax.set_xticks(ns) + ax.xaxis.set_major_formatter(ticker.FormatStrFormatter('%d')) + ax.xaxis.set_minor_locator(ticker.NullLocator()) + ax.set_yscale('log') + ax.yaxis.set_major_locator(ticker.LogLocator(subs=(1, 2, 5))) + ax.yaxis.set_major_formatter(ticker.FuncFormatter(lambda v, _: f'{v:g}')) + ax.yaxis.set_minor_formatter(ticker.NullFormatter()) + ax.tick_params(colors=MUTED, labelsize=7) + for sp in ('top', 'right'): + ax.spines[sp].set_visible(False) + for sp in ('left', 'bottom'): + ax.spines[sp].set_color(GRID) + ax.grid(True, color=GRID, linewidth=0.6) + ax.set_xlabel('n', fontsize=8, color=MUTED) + ax.set_ylabel(ylabel, fontsize=8, color=MUTED) + for ax in list(axes.flat)[len(panels):]: + ax.axis('off') + handles, labels = axes.flat[0].get_legend_handles_labels() + fig.legend(handles, labels, loc='upper center', ncol=2, frameon=False, fontsize=9) + fig.text(0.5, 0.005, 'SAFE tasks. Hollow marker: timed out (lower bound).', ha='center', + fontsize=8, color=MUTED) + fig.tight_layout(rect=(0, 0.02, 1, 0.96)) + fig.savefig(out, dpi=130, facecolor='#fcfcfb') + print(f'wrote {out}') + + +def table(runs, out: Path): + """One Markdown table per family: rounds and time per variant and n, SAFE and VIOLATION.""" + by_mode = {r['mode']: r['tasks'] for r in runs} + sa, nosa = by_mode['sequential'], by_mode['none'] + lines = ['Rounds (wall time in s) without SA -> with SA. TO: timed out (rounds so far).', ''] + + def cell(row): + if row is None: + return '-' + r = f'TO({row["rounds"]})' if row['status'] == 'timeout' else str(row['rounds']) + tag = {'violation -> safe': ' **false SAFE**', 'safe -> unknown': ' unk', + 'violation -> unknown': ' unk'}.get(row['case'], '') + return f'{r} ({row["time"]:.0f}){tag}' + + for fam in FAMILIES: + keys = sorted({re.fullmatch(r'[\w-]+/(\w+)_n(\d+)_(true|false)', n).groups() + for n in sa if n.startswith(fam + '/')}, key=lambda k: (k[0], int(k[1]), k[2])) + if not keys: + continue + lines += [f'### {fam}', '', '| variant | n | SAFE: no SA | SAFE: SA | VIOLATION: no SA | VIOLATION: SA |', + '|---|---:|---|---|---|---|'] + for variant, n in sorted({(k[0], int(k[1])) for k in keys}): + name = f'{fam}/{variant}_n{n:02d}' + lines.append(f'| {variant} | {n} | {cell(nosa.get(name + "_true"))} | {cell(sa.get(name + "_true"))} ' + f'| {cell(nosa.get(name + "_false"))} | {cell(sa.get(name + "_false"))} |') + lines.append('') + out.write_text('\n'.join(lines)) + print(f'wrote {out}') + + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument('runs', nargs=2, type=Path, help='run with SA, run without') + ap.add_argument('--out', type=Path, default=Path('.')) + args = ap.parse_args() + runs = [load_run(r) for r in args.runs] + args.out.mkdir(parents=True, exist_ok=True) + plot(runs, 'rounds', 'rounds', args.out / 'rounds.png') + plot(runs, 'time', 'wall time [s]', args.out / 'time.png') + table(runs, args.out / 'families.md') + + +if __name__ == '__main__': + main() diff --git a/targets/sv-comp/swat-benchmarks/scripts/run_configs.sh b/targets/sv-comp/swat-benchmarks/scripts/run_configs.sh new file mode 100755 index 00000000..d92049b1 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/scripts/run_configs.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# Runs swat-benchmarks once per configuration, one run after the other, and prints the run dirs. +# Usage: ./run_configs.sh [svcomp test run options...] e.g. --suite dispatcher +# Env: CONFIGS (default "sa nosa rank"; also "nocb": SA without callback targets), TIMEOUT (default 300), WORKERS (default 48) +set -euo pipefail +SV_COMP="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +cd "$SV_COMP" +if pgrep -f "python3? .*svcomp.py test run" > /dev/null; then + echo "Another SV-COMP run is live; not starting." >&2 + exit 1 +fi +"$SV_COMP/swat-benchmarks/scripts/build.sh" > /dev/null +for cfg in ${CONFIGS:-sa nosa rank}; do + case $cfg in + sa) flags="" ;; + nosa) flags="--no-sa" ;; + rank) flags="--sa-rank-distance" ;; + nocb) flags="--sa-no-callback-targets" ;; + *) echo "unknown config $cfg" >&2; exit 1 ;; + esac + ./scripts/svcomp test run --mode parallel --no-witness --categories valid-assert.prp \ + --benchmark-dir swat-benchmarks --workers "${WORKERS:-48}" \ + --testcase-timeout-s "${TIMEOUT:-300}" $flags "$@" > /dev/null 2>&1 + echo "$cfg $(ls -td runs/run_* | head -1)" +done diff --git a/targets/sv-comp/swat-benchmarks/scripts/soundness.py b/targets/sv-comp/swat-benchmarks/scripts/soundness.py new file mode 100644 index 00000000..a939ea14 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/scripts/soundness.py @@ -0,0 +1,1570 @@ +"""sa-soundness: SAFE/VIOLATION pairs around constructs where the SA graph and the executor can +disagree. In every pair the assert sits behind the risky construct, and the initial run (all +inputs 0) does not reach it: some input-dependent branch has to be flipped first, which a pruning +bug can wrongly skip. A false SAFE on a VIOLATION task means exactly that. + +@HIT(x)@ is the condition at the assert: `x > 1000 && x < 100000` in the VIOLATION variant and +the unsatisfiable `x > 1000 && x < 1001` in the SAFE one. Each entry: name -> (source, witness), +witness being the violating inputs in nondet call order. +""" + +import re + +from task import Task, ints + +SAFE_REASON = 'the assert needs x > 1000 && x < 1001 for an int x: unsatisfiable.' + +IMPORT = 'import org.sosy_lab.sv_benchmarks.Verifier;\n' +AB = ''' int a = Verifier.nondetInt(); + int b = Verifier.nondetInt(); +''' +W = ints([1001, 7]) # a = 1001, b = 7 + +TASKS = {} + + +def task(name, witness=W): + def register(fn): + TASKS[name] = (fn.__doc__, witness) + return fn + return register + + +# --- arrays from uninstrumented JDK calls ------------------------------------------------------ + +@task('jdk-toarray') +def _(): + """import java.util.ArrayList; +import java.util.List; +IMPORT +// An array returned by an uninstrumented JDK call is read before the assert (bounds guard on an +// untracked array; regression test for the phantom bounds-guard bug). +public class Main { + public static void main(String[] args) { +AB List list = new ArrayList(); + list.add(3); + list.add(5); + Object[] arr = list.toArray(); + int x = (Integer) arr[1]; + if (b == 7) { + if (@HIT(a + x - 5)@) assert false; + } + } +} +""" + + +@task('jdk-getmethods') +def _(): + """import java.lang.reflect.Method; +IMPORT +// Class.getMethods() returns an array the executor does not track; it is read before the assert. +public class Main { + public static void main(String[] args) { +AB Method[] ms = Main.class.getMethods(); + int n = ms.length; + String name = ms[0].getName(); + if (b == 7) { + if (@HIT(a)@) assert false; + } + if (n > 3) n--; + } +} +""" + + +# --- implicit exceptions unwinding across frames ----------------------------------------------- + +@task('arith-catch-local') +def _(): + """IMPORT +// The assert is in a catch (ArithmeticException) in the method that divides. +public class Main { + public static void main(String[] args) { +AB int r; + try { + r = 100 / (b - 7); + } catch (ArithmeticException e) { + if (@HIT(a)@) assert false; + r = 0; + } + if (r > 50) r--; + } +} +""" + + +@task('arith-catch-up1') +def _(): + """IMPORT +// The assert is in a catch (ArithmeticException) one frame above the division. +public class Main { + static int div(int x, int y) { + int q = x / y; + if (q > 10) q--; + return q; + } + + public static void main(String[] args) { +AB int r; + try { + r = div(100, b - 7); + } catch (ArithmeticException e) { + if (@HIT(a)@) assert false; + r = 0; + } + if (r > 50) r--; + } +} +""" + + +@task('arith-catch-up2') +def _(): + """IMPORT +// The assert is in a catch (ArithmeticException) two frames above the division. +public class Main { + static int div(int x, int y) { + int q = x / y; + if (q > 10) q--; + return q; + } + + static int mid(int x, int y) { + int r = div(x, y); + if (r < 0) r = -r; + return r; + } + + public static void main(String[] args) { +AB int r; + try { + r = mid(100, b - 7); + } catch (ArithmeticException e) { + if (@HIT(a)@) assert false; + r = 0; + } + if (r > 50) r--; + } +} +""" + + +@task('bounds-catch-up1') +def _(): + """IMPORT +// The assert is in a catch (ArrayIndexOutOfBoundsException) one frame above the array read. +public class Main { + static final int[] TABLE = {1, 2, 3}; + + static int get(int i) { + int v = TABLE[i]; + if (v > 1) v--; + return v; + } + + public static void main(String[] args) { +AB int r; + try { + r = get(b); + } catch (ArrayIndexOutOfBoundsException e) { + if (@HIT(a)@) assert false; + r = 0; + } + if (r > 1) r--; + } +} +""" + + +@task('bounds-catch-up2') +def _(): + """IMPORT +// The assert is in a catch (ArrayIndexOutOfBoundsException) two frames above the array read. +public class Main { + static final int[] TABLE = {1, 2, 3}; + + static int get(int i) { + int v = TABLE[i]; + if (v > 1) v--; + return v; + } + + static int lookup(int i) { + int v = get(i); + if (v < 0) v = 0; + return v; + } + + public static void main(String[] args) { +AB int r; + try { + r = lookup(b); + } catch (ArrayIndexOutOfBoundsException e) { + if (@HIT(a)@) assert false; + r = 0; + } + if (r > 1) r--; + } +} +""" + + +@task('arith-catch-generic') +def _(): + """IMPORT +// The division's ArithmeticException is caught as RuntimeException one frame up. +public class Main { + static int div(int x, int y) { + return x / y; + } + + public static void main(String[] args) { +AB int r; + try { + r = div(100, b - 7); + if (r > 3) r--; + } catch (RuntimeException e) { + if (@HIT(a)@) assert false; + r = 0; + } + if (r > 50) r--; + } +} +""" + + +# --- exceptions SWAT has no guard branch for --------------------------------------------------- + +@task('npe-catch-local') +def _(): + """IMPORT +// The assert is in a catch (NullPointerException) in the method that dereferences. +public class Main { + static class Box { + int v = 3; + } + + public static void main(String[] args) { +AB Box box = b == 7 ? null : new Box(); + int r; + try { + r = box.v; + if (r > 2) r--; + } catch (NullPointerException e) { + if (@HIT(a)@) assert false; + r = 0; + } + if (r > 50) r--; + } +} +""" + + +@task('npe-catch-up1') +def _(): + """IMPORT +// The assert is in a catch (NullPointerException) one frame above the dereference. +public class Main { + static class Box { + int v = 3; + } + + static int read(Box box) { + int v = box.v; + if (v > 2) v--; + return v; + } + + public static void main(String[] args) { +AB Box box = b == 7 ? null : new Box(); + int r; + try { + r = read(box); + } catch (NullPointerException e) { + if (@HIT(a)@) assert false; + r = 0; + } + if (r > 50) r--; + } +} +""" + + +@task('cce-catch') +def _(): + """IMPORT +// The assert is in a catch (ClassCastException). +public class Main { + static class A { + } + + static class B extends A { + int v = 3; + } + + public static void main(String[] args) { +AB A o = b == 7 ? new A() : new B(); + int r; + try { + B bb = (B) o; + r = bb.v; + if (r > 2) r--; + } catch (ClassCastException e) { + if (@HIT(a)@) assert false; + r = 0; + } + if (r > 50) r--; + } +} +""" + + +# --- static initializers ----------------------------------------------------------------------- + +CLINIT = ''' static int input; + + static class Late { + static int K; + + static { + if (@HIT(Main.input)@) assert false; + K = 1; + } + + static int get() { + return K; + } + } +''' + + +@task('clinit-static-call') +def _(): + """IMPORT +// The assert is in the static initializer of a class first used (static call) on an +// input-dependent path. +public class Main { +CLINIT + public static void main(String[] args) { +AB input = a; + int r = 0; + if (b == 7) { + r = Late.get(); + } + if (r > 3) r--; + } +} +""" + + +@task('clinit-new') +def _(): + """IMPORT +// The assert is in the static initializer of a class first used (new) on an input-dependent path. +public class Main { +CLINIT + public static void main(String[] args) { +AB input = a; + Object o = null; + if (b == 7) { + o = new Late(); + } + if (o != null) input++; + } +} +""" + + +@task('clinit-field') +def _(): + """IMPORT +// The assert is in the static initializer of a class first used (static field read) on an +// input-dependent path. +public class Main { +CLINIT + public static void main(String[] args) { +AB input = a; + int r = 0; + if (b == 7) { + r = Late.K; + } + if (r > 3) r--; + } +} +""" + + +@task('clinit-in-callee') +def _(): + """IMPORT +// The assert is in a static initializer triggered inside a callee that has no branches of its own. +public class Main { +CLINIT + static int touch() { + return Late.get() + 1; + } + + public static void main(String[] args) { +AB input = a; + int r = 0; + if (b == 7) { + r = touch(); + } + if (r > 3) r--; + } +} +""" + + +# --- switch on String and enum ----------------------------------------------------------------- + +@task('string-switch') +def _(): + """IMPORT +// A switch on a String (hashCode lookupswitch + equals branches) selects the asserting case. +public class Main { + public static void main(String[] args) { +AB String cmd = b == 1 ? "start" : b == 7 ? "stop" : "status"; + int r; + switch (cmd) { + case "start": r = 1; break; + case "stop": + if (@HIT(a)@) assert false; + r = 2; + break; + case "status": r = 3; break; + default: r = 0; + } + if (r > 2) r--; + } +} +""" + + +@task('string-switch-nondet', witness=[('string', 'stop'), ('int', 1001)]) +def _(): + """IMPORT +// A switch on a nondet String selects the asserting case. +public class Main { + public static void main(String[] args) { + String cmd = Verifier.nondetString(); + int a = Verifier.nondetInt(); + int r; + switch (cmd) { + case "start": r = 1; break; + case "stop": + if (@HIT(a)@) assert false; + r = 2; + break; + case "status": r = 3; break; + default: r = 0; + } + if (r > 2) r--; + } +} +""" + + +@task('enum-switch') +def _(): + """IMPORT +// A switch on an enum (synthetic $SwitchMap class with its own ) selects the asserting case. +public class Main { + enum Op { START, STOP, STATUS } + + public static void main(String[] args) { +AB Op op = b == 1 ? Op.START : b == 7 ? Op.STOP : Op.STATUS; + int r; + switch (op) { + case START: r = 1; break; + case STOP: + if (@HIT(a)@) assert false; + r = 2; + break; + default: r = 3; + } + if (r > 2) r--; + } +} +""" + + +# --- try/finally ------------------------------------------------------------------------------- + +@task('finally-exits') +def _(): + """IMPORT +// try/finally with return and throw inside try: javac copies the finally block (with its branch) +// onto each exit path. The assert is behind the throwing exit. +public class Main { + static int cnt; + + static int f(int a, int b) { + try { + if (b == 8) return 1; + if (b == 7) throw new IllegalStateException(); + return 0; + } finally { + cnt++; + if (a > 50) cnt++; + } + } + + public static void main(String[] args) { +AB int r; + try { + r = f(a, b); + } catch (IllegalStateException e) { + r = 2; + } + if (r == 2) { + if (@HIT(a)@) assert false; + } + } +} +""" + + +@task('finally-assert') +def _(): + """IMPORT +// The assert is inside a finally block, which javac copies onto every exit path of try. +public class Main { + static int cnt; + + static void g(int a, int b) { + try { + if (b == 7) return; + cnt++; + if (b == 8) throw new IllegalStateException(); + } finally { + if (b == 7) { + if (@HIT(a)@) assert false; + } + } + } + + public static void main(String[] args) { +AB try { + g(a, b); + } catch (IllegalStateException e) { + cnt--; + } + } +} +""" + + +# --- reference comparisons --------------------------------------------------------------------- + +@task('ref-eq') +def _(): + """IMPORT +// Object == before the assert (rewritten to UtilInstrumented.refEquals, which adds branch events). +public class Main { + public static void main(String[] args) { +AB Object o1 = new Object(); + Object o2 = b == 7 ? o1 : new Object(); + if (o1 == o2) { + if (@HIT(a)@) assert false; + } + } +} +""" + + +@task('ref-ne-chain') +def _(): + """IMPORT +// Several object != comparisons, then branches, then the assert. +public class Main { + public static void main(String[] args) { +AB Object o1 = new Object(); + Object o2 = b == 7 ? o1 : new Object(); + Object o3 = new Object(); + int r = 0; + if (o1 != o3) r++; + if (o2 != o3) r++; + if (a > 5) r++; + if (o1 != o2) return; + if (r == 3) { + if (@HIT(a)@) assert false; + } + } +} +""" + + +# --- lambdas and streams ----------------------------------------------------------------------- + +@task('lambda-predicate') +def _(): + """import java.util.function.IntPredicate; +IMPORT +// The assert condition is evaluated by a lambda (invokedynamic) created in main. +public class Main { + public static void main(String[] args) { +AB IntPredicate p = x -> @HIT(x)@; + if (b == 7) { + if (p.test(a)) assert false; + } + } +} +""" + + +@task('lambda-consumer') +def _(): + """import java.util.function.IntConsumer; +IMPORT +// The assert is inside a lambda created in main and called on an input-dependent path. +public class Main { + public static void main(String[] args) { +AB IntConsumer check = x -> { + if (@HIT(x)@) assert false; + }; + if (b == 7) check.accept(a); + if (a > 3) a--; + } +} +""" + + +@task('lambda-static-field') +def _(): + """import java.util.function.IntConsumer; +IMPORT +// The assert is inside a lambda stored in a static field (created in ) and called on an +// input-dependent path. +public class Main { + static final IntConsumer CHECK = x -> { + if (@HIT(x)@) assert false; + }; + + public static void main(String[] args) { +AB if (b == 7) CHECK.accept(a); + if (a > 3) a--; + } +} +""" + + +@task('stream-foreach') +def _(): + """import java.util.stream.IntStream; +IMPORT +// The asserting method is called back from JDK stream code through a method reference. +public class Main { + static void check(int x) { + if (@HIT(x)@) assert false; + } + + public static void main(String[] args) { +AB if (b == 7) { + IntStream.of(a).forEach(Main::check); + } + if (a > 3) a--; + } +} +""" + + +# --- reflection -------------------------------------------------------------------------------- + +@task('reflection-invoke') +def _(): + """import java.lang.reflect.Method; +IMPORT +// The asserting method is called through Method.invoke on an input-dependent path. +public class Main { + public static void check(int x) { + if (@HIT(x)@) assert false; + } + + public static void main(String[] args) throws Exception { +AB Method m = Main.class.getMethod("check", int.class); + if (b == 7) { + m.invoke(null, a); + } + if (a > 3) a--; + } +} +""" + + +@task('reflection-desync', witness=ints([1001])) +def _(): + """import java.lang.reflect.Method; +IMPORT +// Method.invoke runs a method with branches unconditionally; main has a branch of its own after +// the call, which the walk may pair with the callee's branches. +public class Main { + static int cnt; + + public static void check(int x) { + if (@HIT(x)@) assert false; + } + + public static void main(String[] args) throws Exception { + int a = Verifier.nondetInt(); + Method m = Main.class.getMethod("check", int.class); + m.invoke(null, a); + if (a > 3) cnt++; + if (a > 5) cnt++; + } +} +""" + + +# --- recursion --------------------------------------------------------------------------------- + +@task('recursion-depth', witness=ints([1001, 4])) +def _(): + """IMPORT +// The assert is at an input-dependent recursion depth (b = 4). +public class Main { + static void rec(int depth, int a, int b) { + if (depth == b && depth > 3) { + if (@HIT(a)@) assert false; + return; + } + if (depth < 6) rec(depth + 1, a, b); + } + + public static void main(String[] args) { +AB rec(0, a, b); + } +} +""" + + +@task('recursion-mutual', witness=ints([1001, 5])) +def _(): + """IMPORT +// Mutual recursion; the assert is only reachable at an odd depth b > 3 (b = 5). +public class Main { + static void even(int depth, int a, int b) { + if (depth < 8) odd(depth + 1, a, b); + } + + static void odd(int depth, int a, int b) { + if (depth == b && depth > 3) { + if (@HIT(a)@) assert false; + return; + } + if (depth < 8) even(depth + 1, a, b); + } + + public static void main(String[] args) { +AB even(0, a, b); + } +} +""" + + +# --- unusual bytecode: default methods, anonymous and inner classes, synchronized --------------- + +@task('default-method') +def _(): + """IMPORT +// The assert is in an interface default method, called on a class that does not override it. +public class Main { + interface Checker { + default void check(int a) { + if (@HIT(a)@) assert false; + } + } + + static class Impl implements Checker { + } + + static class Other implements Checker { + int cnt; + + public void check(int a) { + cnt++; + } + } + + public static void main(String[] args) { +AB if (b == 7) { + new Impl().check(a); + } else { + new Other().check(a); + } + if (a > 3) a--; + } +} +""" + + +@task('anonymous-class') +def _(): + """IMPORT +// The assert is in an anonymous class's run(), called on an input-dependent path. +public class Main { + static int input; + + public static void main(String[] args) { +AB input = a; + Runnable r = new Runnable() { + public void run() { + if (@HIT(input)@) assert false; + } + }; + if (b == 7) r.run(); + if (a > 3) a--; + } +} +""" + + +@task('anonymous-static-field') +def _(): + """IMPORT +// The assert is in an anonymous class stored in a static field (receiver read from a field). +public class Main { + static int input; + + static final Runnable CHECK = new Runnable() { + public void run() { + if (@HIT(input)@) assert false; + } + }; + + public static void main(String[] args) { +AB input = a; + if (b == 7) CHECK.run(); + if (a > 3) a--; + } +} +""" + + +@task('inner-accessor') +def _(): + """IMPORT +// An inner class reads a private field and calls a private method of its outer class through +// javac's synthetic access$ methods (Java 8 target). +public class Main { + private int limit = 100000; + + private boolean inRange(int a) { + return a > 1000; + } + + class Inner { + void check(int a) { + if (inRange(a) && a < limit) { + if (@HIT(a)@) assert false; + } + } + } + + public static void main(String[] args) { +AB Main m = new Main(); + Main.Inner in = m.new Inner(); + if (b == 7) in.check(a); + if (a > 3) a--; + } +} +""" + + +@task('synchronized-block') +def _(): + """IMPORT +// The assert is inside a synchronized block (monitorexit exception handler) and a synchronized +// method. +public class Main { + static final Object LOCK = new Object(); + static int cnt; + + static synchronized void bump(int a) { + if (a > 3) cnt++; + } + + public static void main(String[] args) { +AB bump(a); + synchronized (LOCK) { + if (b == 7) { + if (@HIT(a)@) assert false; + } + } + if (a > 5) cnt++; + } +} +""" + + +@task('synchronized-throw') +def _(): + """IMPORT +// An ArithmeticException leaves a synchronized block (through its catch-any handler, which +// rethrows) and is caught outside, where the assert is. +public class Main { + static final Object LOCK = new Object(); + + public static void main(String[] args) { +AB int r; + try { + synchronized (LOCK) { + r = 100 / (b - 7); + if (r > 3) r--; + } + } catch (ArithmeticException e) { + if (@HIT(a)@) assert false; + r = 0; + } + if (r > 50) r--; + } +} +""" + + +# --- narrowing tasks (added after the first runs) ------------------------------------------------ +# The lambda and reflection pairs above lose the symbolic argument in the JDK frames between the +# call and the callee, so SWAT misses the violation even without pruning. These variants pass the +# input through a static field instead, which keeps it symbolic, to separate that executor +# limitation from what pruning does. + +@task('lambda-static-field-noarg') +def _(): + """IMPORT +// The assert is inside a no-argument lambda stored in a static field; it reads the input from a +// static field, so no argument passes through JDK frames. +public class Main { + static int input; + + static final Runnable CHECK = () -> { + if (@HIT(input)@) assert false; + }; + + public static void main(String[] args) { +AB input = a; + if (b == 7) CHECK.run(); + if (a > 3) a--; + } +} +""" + + +@task('lambda-local-noarg') +def _(): + """IMPORT +// As lambda-static-field-noarg, but the lambda is created in main (invokedynamic in the graph). +public class Main { + static int input; + + public static void main(String[] args) { +AB input = a; + Runnable check = () -> { + if (@HIT(input)@) assert false; + }; + if (b == 7) check.run(); + if (a > 3) a--; + } +} +""" + + +@task('reflection-noarg') +def _(): + """import java.lang.reflect.Method; +IMPORT +// The asserting no-argument method is called through Method.invoke; it reads the input from a +// static field. +public class Main { + static int input; + + public static void check() { + if (@HIT(input)@) assert false; + } + + public static void main(String[] args) throws Exception { +AB input = a; + Method m = Main.class.getMethod("check"); + if (b == 7) { + m.invoke(null); + } + if (a > 3) a--; + } +} +""" + + +# Exceptions thrown where the graph has no edge for them, caught by the application. + +@task('library-throw-catch') +def _(): + """IMPORT +// A JDK method (Integer.parseInt) throws, and the application catches it: the usual way CLI tools +// handle a malformed argument. The assert is in the handler. +public class Main { + public static void main(String[] args) { +AB String arg = b == 7 ? "x7" : "12"; + int v; + try { + v = Integer.parseInt(arg); + } catch (NumberFormatException e) { + if (@HIT(a)@) assert false; + v = 0; + } + if (v > 5) v--; + } +} +""" + + +@task('cce-catch-up1') +def _(): + """IMPORT +// The assert is in a catch (ClassCastException) one frame above the cast. +public class Main { + static class A { + } + + static class B extends A { + int v = 3; + } + + static int unwrap(A o) { + B bb = (B) o; + int r = bb.v; + if (r > 2) r--; + return r; + } + + public static void main(String[] args) { +AB A o = b == 7 ? new A() : new B(); + int r; + try { + r = unwrap(o); + } catch (ClassCastException e) { + if (@HIT(a)@) assert false; + r = 0; + } + if (r > 50) r--; + } +} +""" + + +@task('explicit-throw-generic-catch') +def _(): + """IMPORT +// Control: an explicit throw of IllegalStateException one frame down, caught as RuntimeException. +// The graph models explicit throws, so this one must work. +public class Main { + static int check(int b) { + if (b == 7) throw new IllegalStateException(); + return b; + } + + public static void main(String[] args) { +AB int r; + try { + r = check(b); + } catch (RuntimeException e) { + if (@HIT(a)@) assert false; + r = 0; + } + if (r > 50) r--; + } +} +""" + + +@task('clinit-unconditional') +def _(): + """IMPORT +// Control for the clinit pairs: the class is first used on every path, so its runs in +// the first round, inside the masked part of the walk. Only the input-dependent first use fails. +public class Main { +CLINIT + public static void main(String[] args) { +AB input = a; + int r = Late.get(); + if (b == 7) r++; + if (r > 3) r--; + } +} +""" + + +# Executor limitations that give false SAFE verdicts without pruning too (controls: both +# configurations must agree on them). + +@task('exec-system-exit') +def _(): + """IMPORT +// The first run (b = 0) ends in System.exit before the assert. +public class Main { + public static void main(String[] args) { +AB if (b != 7) { + System.exit(0); + } + if (@HIT(a)@) assert false; + } +} +""" + + +@task('exec-system-exit-late') +def _(): + """IMPORT +// As exec-system-exit, but a branch on the input is recorded before the exit. +public class Main { + static int cnt; + + public static void main(String[] args) { +AB if (a > 3) cnt++; + if (b != 7) { + System.exit(0); + } + if (@HIT(a)@) assert false; + } +} +""" + + +@task('exec-object-array-index', witness=ints([1001, 2])) +def _(): + """IMPORT +// An object read from an array at an input-dependent index decides whether the assert is reached. +public class Main { + static class C { + final int k; + + C(int k) { + this.k = k; + } + } + + static final C[] TABLE = {new C(0), new C(0), new C(7)}; + + public static void main(String[] args) { +AB if (b >= 0 && b < TABLE.length) { + if (TABLE[b].k == 7) { + if (@HIT(a)@) assert false; + } + } + } +} +""" + + +@task('exec-int-array-index', witness=ints([1001, 2])) +def _(): + """IMPORT +// Control for exec-object-array-index: the same with an int array. +public class Main { + static final int[] TABLE = {0, 0, 7}; + + public static void main(String[] args) { +AB if (b >= 0 && b < TABLE.length) { + if (TABLE[b] == 7) { + if (@HIT(a)@) assert false; + } + } + } +} +""" + + +@task('exec-string-char', witness=[('string', '-'), ('int', 1001)]) +def _(): + """IMPORT +// Z3 may solve s.charAt(0) == '-' with a supplementary code point (e.g. U+2002D): the executor +// encodes charAt as the code point truncated to 16 bits, and String inputs have no range bound, +// so the next run diverges. +public class Main { + public static void main(String[] args) { + String s = Verifier.nondetString(); + int a = Verifier.nondetInt(); + if (s.length() == 1 && s.charAt(0) == '-') { + if (@HIT(a)@) assert false; + } + } +} +""" + +@task('exec-stringbuilder-setlength', witness=[('string', 'ab'), ('int', 1001)]) +def _(): + """IMPORT +// A StringBuilder is reused with setLength(0), as parsers do between fields. +public class Main { + public static void main(String[] args) { + String s = Verifier.nondetString(); + int a = Verifier.nondetInt(); + StringBuilder sb = new StringBuilder(); + sb.append(s); + sb.setLength(0); + sb.append('x'); + if (sb.length() == 1 && s.length() == 2) { + if (@HIT(a)@) assert false; + } + } +} +""" + + +@task('exec-equal-string-inputs', witness=[('string', ''), ('string', 'x'), ('int', 1001)]) +def _(): + """IMPORT +// Two String inputs that the solver may set to the same value (here both empty at first): each +// must keep its own symbolic variable. +public class Main { + public static void main(String[] args) { + String s = Verifier.nondetString(); + String t = Verifier.nondetString(); + int a = Verifier.nondetInt(); + if (s.length() == 0 && t.length() == 1) { + if (@HIT(a)@) assert false; + } + } +} +""" + + +@task('exec-indexof-negative-from', witness=[('string', 'a'), ('int', -6), ('int', 1001)]) +def _(): + """IMPORT +// String.indexOf(String, int) treats a negative fromIndex as 0. +public class Main { + public static void main(String[] args) { + String s = Verifier.nondetString(); + int from = Verifier.nondetInt(); + int a = Verifier.nondetInt(); + if (from < -5 && s.indexOf("a", from) == 0) { + if (@HIT(a)@) assert false; + } + } +} +""" + + +@task('npe-in-callee-branch') +def _(): + """IMPORT +// Whether the callee dereferences null is decided by a branch inside the callee; the +// NullPointerException is caught in main, where the assert is. +public class Main { + static class Box { + int v = 3; + } + + static int read(Box box, int b) { + Box x = b == 7 ? null : box; + int v = x.v; + if (v > 2) v--; + return v; + } + + public static void main(String[] args) { +AB int r; + try { + r = read(new Box(), b); + } catch (NullPointerException e) { + if (@HIT(a)@) assert false; + r = 0; + } + if (r > 50) r--; + } +} +""" + + +@task('error-in-initializer-catch') +def _(): + """IMPORT +// A static initializer throws on b == 7; the ExceptionInInitializerError (an Error) is caught as +// Error in main, where the assert is. +public class Main { + static int input; + + static class Late { + static int K; + + static { + if (Main.input == 7) throw new IllegalStateException(); + K = 1; + } + } + + public static void main(String[] args) { +AB input = b; + int r; + try { + r = Late.K; + } catch (Error e) { + if (@HIT(a)@) assert false; + r = 0; + } + if (r > 50) r--; + } +} +""" + + +@task('finally-npe') +def _(): + """IMPORT +// A NullPointerException leaves a try block with a finally (a catch-any handler in bytecode); the +// assert is in the finally block. main catches the exception afterwards. +public class Main { + static class Box { + int v = 3; + } + + static int read(Box box, int a) { + try { + return box.v; + } finally { + if (box == null) { + if (@HIT(a)@) assert false; + } + } + } + + public static void main(String[] args) { +AB Box box = b == 7 ? null : new Box(); + int r; + try { + r = read(box, a); + } catch (NullPointerException e) { + r = 0; + } + if (r > 50) r--; + } +} +""" + + +@task('npe-recursive-catch') +def _(): + """IMPORT +// A recursive method dereferences null at depth 3 (b == 7); only the activation at depth 2 has a +// handler around its recursive call, with the assert. Resuming the walk must pick that frame. +public class Main { + static class Box { + int v = 3; + } + + static int rec(int depth, int a, int b) { + if (depth == 3) { + Box x = b == 7 ? null : new Box(); + return x.v; + } + if (depth == 2) { + try { + return rec(depth + 1, a, b); + } catch (NullPointerException e) { + if (@HIT(a)@) assert false; + return 0; + } + } + return rec(depth + 1, a, b) + 1; + } + + public static void main(String[] args) { +AB int r = rec(0, a, b); + if (r > 50) r--; + } +} +""" + + +@task('npe-handler-in-callee') +def _(): + """IMPORT +// The last branch before the null dereference is in main, but the handler is in a callee entered +// after it: the walk cannot know that callee's call site and must give up instead. +public class Main { + static class Box { + int v = 3; + } + + static int read(Box box) { + return box.v; + } + + static int guarded(Box box, int a) { + try { + return read(box); + } catch (NullPointerException e) { + if (@HIT(a)@) assert false; + return 0; + } + } + + public static void main(String[] args) { +AB Box box = b == 7 ? null : new Box(); + int r = guarded(box, a); + if (r > 50) r--; + } +} +""" + + +@task('library-callback') +def _(): + """import java.io.IOException; +import java.io.PushbackReader; +import java.io.Reader; +IMPORT +// Library code calls back into application code: PushbackReader.read() calls the application's +// Reader, whose read() branches. The graph has the call as a plain library call, so the walk has +// to step over the callback's branches (from nanoxml, java-ranger-regression). +public class Main { + static class Chars extends Reader { + private final int[] data; + private int next = 0; + + Chars(int[] data) { + this.data = data; + } + + public int read() { + if (next >= data.length) { + return -1; + } + int c = data[next]; + next = next + 1; + return c > 100 ? 100 : c; + } + + public int read(char[] buf, int off, int len) { + return -1; + } + + public void close() { + } + } + + public static void main(String[] args) throws IOException { +AB PushbackReader in = new PushbackReader(new Chars(new int[] {b, 5})); + int c = in.read(); + if (c == 7) { + if (@HIT(a)@) assert false; + } + if (c > 50) c--; + } +} +""" + + +@task('library-callback-throw') +def _(): + """import java.io.IOException; +import java.io.PushbackReader; +import java.io.Reader; +IMPORT +// As library-callback, but the callback throws: the exception comes out of the library call into +// the application's handler, where the assert is. (The input is read from a static field: an +// object handed to library code with symbolic fields counts as context loss.) +public class Main { + static int first; + + static class Chars extends Reader { + public int read() throws IOException { + if (first == 7) { + throw new IOException("end"); + } + return first > 100 ? 100 : first; + } + + public int read(char[] buf, int off, int len) { + return -1; + } + + public void close() { + } + } + + public static void main(String[] args) { +AB first = b; + int c; + try { + c = new PushbackReader(new Chars()).read(); + } catch (IOException e) { + if (@HIT(a)@) assert false; + c = 0; + } + if (c > 50) c--; + } +} +""" + + +@task('library-callback-assert') +def _(): + """import java.io.IOException; +import java.io.PushbackReader; +import java.io.Reader; +IMPORT +// The assert is in application code that library code calls back (PushbackReader.read() calls +// the application's Reader), on an input-dependent path. The graph does not have the callback, so +// it treats the library call as unable to reach an assert. Known false SAFE with SA (RESULTS.md). +public class Main { + static int input; + + static class Chars extends Reader { + public int read() { + if (@HIT(input)@) assert false; + return 1; + } + + public int read(char[] buf, int off, int len) { + return -1; + } + + public void close() { + } + } + + public static void main(String[] args) throws IOException { +AB input = a; + if (b == 7) { + new PushbackReader(new Chars()).read(); + } + if (a > 3) a--; + } +} +""" + + +@task('exec-loop-iid-wrap', witness=ints([1001, 66])) +def _(): + """IMPORT +// A branch in a loop is reported once per iteration, and its iid carries the iteration count, +// which wraps at 64: iterations 2 and 66 share an iid. Solving iteration 66 must not use iteration +// 2's constraint (the explorer's constraint cache was keyed by iid). +public class Main { + public static void main(String[] args) { +AB int hit = -1; + for (int i = 0; i < 70; i++) { + if (b == i) { + hit = i; + } + } + if (hit == 66) { + if (@HIT(a)@) assert false; + } + } +} +""" + + +def render(src: str, safe: bool) -> str: + src = src.replace('IMPORT\n', IMPORT).replace('CLINIT\n', CLINIT) + src = re.sub(r'^AB', AB, src, flags=re.M) + hi = '1001' if safe else '100000' + return re.sub(r'@HIT\((.*?)\)@', lambda m: f'{m.group(1)} > 1000 && {m.group(1)} < {hi}', src) + + +def all_soundness_tasks(): + for name, (src, witness) in TASKS.items(): + for safe in (True, False): + yield Task('sa-soundness', name, safe, render(src, safe), witness, SAFE_REASON) diff --git a/targets/sv-comp/swat-benchmarks/scripts/stub/org/sosy_lab/sv_benchmarks/Verifier.java b/targets/sv-comp/swat-benchmarks/scripts/stub/org/sosy_lab/sv_benchmarks/Verifier.java new file mode 100644 index 00000000..352bbb11 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/scripts/stub/org/sosy_lab/sv_benchmarks/Verifier.java @@ -0,0 +1,36 @@ +package org.sosy_lab.sv_benchmarks; + +/** + * Replays chosen inputs, for checking task labels: -Dinputs="int:1001\nint:7\nstring:stop" (newline-separated) gives the + * values in nondet call order; once they run out, every call returns 0 / false / "". + */ +public final class Verifier { + private static final String[] INPUTS = + System.getProperty("inputs", "").isEmpty() ? new String[0] : System.getProperty("inputs").split("\n"); + private static int next; + + private static String take(String type) { + if (next >= INPUTS.length) return null; + String[] tv = INPUTS[next++].split(":", 2); + if (!tv[0].equals(type)) throw new IllegalStateException("input " + next + " is " + tv[0] + ", not " + type); + return tv[1]; + } + + public static void assume(boolean condition) { + if (!condition) Runtime.getRuntime().halt(1); + } + + public static boolean nondetBoolean() { String v = take("boolean"); return v != null && Boolean.parseBoolean(v); } + public static byte nondetByte() { String v = take("byte"); return v == null ? 0 : Byte.parseByte(v); } + public static char nondetChar() { String v = take("char"); return v == null ? 0 : v.charAt(0); } + public static short nondetShort() { String v = take("short"); return v == null ? 0 : Short.parseShort(v); } + public static int nondetInt() { String v = take("int"); return v == null ? 0 : Integer.parseInt(v); } + public static long nondetLong() { String v = take("long"); return v == null ? 0 : Long.parseLong(v); } + public static float nondetFloat() { String v = take("float"); return v == null ? 0 : Float.parseFloat(v); } + public static double nondetDouble() { String v = take("double"); return v == null ? 0 : Double.parseDouble(v); } + public static String nondetString() { String v = take("string"); return v == null ? "" : v; } + + public static T nondetObject(Class type, ObjectFactory factory) { + return factory.createObject(); + } +} diff --git a/targets/sv-comp/swat-benchmarks/scripts/task.py b/targets/sv-comp/swat-benchmarks/scripts/task.py new file mode 100644 index 00000000..a475d679 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/scripts/task.py @@ -0,0 +1,57 @@ +"""A generated verification task and how it is written to disk.""" + +from dataclasses import dataclass +from pathlib import Path + +YML = '''format_version: '2.0' +input_files: + - ../common/ +{libs} - {name}/ +properties: + - property_file: ../properties/valid-assert.prp + expected_verdict: {verdict} +options: + language: Java +''' + + +def ints(values): + return [('int', v) for v in values] + + +@dataclass +class Task: + suite: str + base: str # task name without the _true/_false suffix + safe: bool + source: str # Main.java + # The inputs of a violating run, in the order the Verifier.nondet* calls happen, as + # (type, value) pairs. For a SAFE task: the paired VIOLATION task's inputs, which must not + # fail here (the minimal difference is what makes the task safe). + witness: list + safe_reason: str # why the SAFE variant is safe + extra_files: dict = None # further sources next to Main.java: {file name: text} + libs: tuple = () # library folders of the suite on the task's input_files, e.g. lib-getopt + + @property + def name(self) -> str: + return f'{self.base}_{"true" if self.safe else "false"}' + + def header(self) -> str: + values = ' '.join(f'{t}:{v}' for t, v in self.witness) + if self.safe: + return (f'// Expected verdict: SAFE. {self.safe_reason}\n' + f'// Paired VIOLATION inputs (must not fail here): {values}\n') + return f'// Expected verdict: VIOLATION. Violating inputs (nondet call order): {values}\n' + + def write(self, java_dir: Path) -> Path: + suite_dir = java_dir / self.suite + task_dir = suite_dir / self.name + task_dir.mkdir(parents=True, exist_ok=True) + (task_dir / 'Main.java').write_text(self.header() + self.source) + for fname, text in (self.extra_files or {}).items(): + (task_dir / fname).write_text(text) + (suite_dir / f'{self.name}.yml').write_text( + YML.format(name=self.name, verdict='true' if self.safe else 'false', + libs=''.join(f' - {lib}/\n' for lib in self.libs))) + return task_dir diff --git a/targets/sv-comp/swat-benchmarks/scripts/tools.py b/targets/sv-comp/swat-benchmarks/scripts/tools.py new file mode 100644 index 00000000..85dabfa0 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/scripts/tools.py @@ -0,0 +1,37 @@ +"""real-tools: harnesses around unmodified open-source tools. The tools' sources are not in the +repository; fetch_tools.sh downloads them from Maven Central into java/real-tools/lib-/. + +Each harness in tools/ has an @COND@ at its assert: the VIOLATION condition is satisfiable, the +SAFE one contradicts what the harness established before (see each entry's reason). +""" + +from pathlib import Path + +from task import Task + +HERE = Path(__file__).resolve().parent + +TOOLS = { + # name: (lib folder, violating inputs, VIOLATION condition, SAFE condition, why SAFE) + 'getopt-minitar': ( + 'lib-getopt', + [('string', '-xv'), ('string', '-f'), ('string', 'x')], + 'optind == argc', + 'optind == argc && create', + 'doExtract() is only called when create is false (exactly one action, and it is not ' + 'create or list).'), + 'opencsv-import': ( + 'lib-opencsv', + [('string', ',,')], + 'line.length() < 3', + 'line.length() < 2', + 'three fields need two separators outside quotes, so the line has at least 2 characters.'), +} + + +def all_tool_tasks(): + for name, (lib, witness, cond_v, cond_s, reason) in TOOLS.items(): + src = (HERE / 'tools' / f'{name}.java').read_text() + for safe in (True, False): + yield Task('real-tools', name, safe, src.replace('@COND@', cond_s if safe else cond_v), + witness, reason, libs=(lib,)) diff --git a/targets/sv-comp/swat-benchmarks/scripts/tools/getopt-minitar.java b/targets/sv-comp/swat-benchmarks/scripts/tools/getopt-minitar.java new file mode 100644 index 00000000..a9d06334 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/scripts/tools/getopt-minitar.java @@ -0,0 +1,86 @@ +import gnu.getopt.Getopt; +import org.sosy_lab.sv_benchmarks.Verifier; + +// A small tar-like CLI on GNU getopt (java-getopt 1.0.13, unmodified). Three nondet argv +// strings. Help, version, usage errors and the create action leave without reaching the assert; +// only extracting with a verbose flag and the archive name "x" does. +public class Main { + static int verbose; + static boolean create, extract, list; + static String file; + static String level; + + public static void main(String[] args) { + String[] argv = new String[3]; + for (int i = 0; i < argv.length; i++) { + argv[i] = Verifier.nondetString(); + } + Getopt g = new Getopt("minitar", argv, ":hVvcxtf:z::"); + g.setOpterr(false); + int c; + while ((c = g.getopt()) != -1) { + switch (c) { + case 'h': usage(0); return; + case 'V': version(); return; + case 'v': verbose++; break; + case 'c': create = true; break; + case 'x': extract = true; break; + case 't': list = true; break; + case 'f': file = g.getOptarg(); break; + case 'z': level = g.getOptarg(); break; + case ':': usage(2); return; // missing argument + default: usage(1); return; // '?': unknown option + } + } + int actions = (create ? 1 : 0) + (extract ? 1 : 0) + (list ? 1 : 0); + if (actions != 1) { + usage(3); + return; + } + if (file == null) { + usage(4); + return; + } + if (create) { + doCreate(file, level); + } else if (list) { + doList(file); + } else { + doExtract(file, g.getOptind(), argv.length); + } + } + + static int code; + + static void usage(int why) { + if (why == 0) code = 0; + else if (why == 2) code = 64; + else code = 65 + why; + } + + static void version() { + code = verbose > 0 ? 1 : 0; + } + + static void doCreate(String archive, String lvl) { + int l = 6; + if (lvl != null && lvl.length() == 1) { + char d = lvl.charAt(0); + if (d >= '0' && d <= '9') l = d - '0'; + } + if (archive.length() > 3) l++; + code = l; + } + + static void doList(String archive) { + if (archive.startsWith("-")) code = 1; + else code = archive.length(); + } + + static void doExtract(String archive, int optind, int argc) { + if (verbose > 0 && archive.equals("x")) { + if (@COND@) assert false; + } + code = optind < argc ? 1 : 0; + } +} diff --git a/targets/sv-comp/swat-benchmarks/scripts/tools/opencsv-import.java b/targets/sv-comp/swat-benchmarks/scripts/tools/opencsv-import.java new file mode 100644 index 00000000..7f71fc64 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/scripts/tools/opencsv-import.java @@ -0,0 +1,32 @@ +import au.com.bytecode.opencsv.CSVParser; +import java.io.IOException; +import org.sosy_lab.sv_benchmarks.Verifier; + +// A CSV import step on opencsv 2.3's CSVParser (unmodified). One nondet input line. Malformed +// lines (unterminated quote) and records without exactly three fields are rejected; the assert +// checks a property of well-formed three-field records. +public class Main { + static int rejected; + + public static void main(String[] args) { + String line = Verifier.nondetString(); + CSVParser parser = new CSVParser(); + String[] fields; + try { + fields = parser.parseLine(line); + } catch (IOException e) { + reject(line); + return; + } + if (fields.length != 3) { + reject(line); + return; + } + if (@COND@) assert false; + } + + static void reject(String line) { + if (line.length() > 10) rejected += 2; + else rejected++; + } +} diff --git a/targets/sv-comp/swat-debug.cfg b/targets/sv-comp/swat-debug.cfg index 3cf6a97a..a3e14144 100644 --- a/targets/sv-comp/swat-debug.cfg +++ b/targets/sv-comp/swat-debug.cfg @@ -29,3 +29,12 @@ constraintsOnly=false solver.mode=HTTP svcomp.randomInputs=false + +# Report loop-head states with the trace; the explorer prunes below repeated ones +loopStateMatching=true + +# Merge diamonds and counting loops into formulas (symbolic/summary); --no-loop-summaries turns it off +loopSummaries=true + +# End a run whose application threads are deadlocked (thread/DeadlockWatchdog) +deadlockWatchdog=true