diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 65c9273f..2ca2d812 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -6,9 +6,9 @@ on: workflow_dispatch: env: - SVCOMP_VENV_DIR_NAME: .venv_ubuntu_24_04_1__x86_64 - WITNESS_CREATOR_VERSION: v0.1.1 - WITNESS_CREATOR_SHA256: 8fc8810237c99bf7d709bdb8692142695e407b5681e818fcfe8b45f9c598d814 + # 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: @@ -16,41 +16,23 @@ 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 --no-daemon build -x test - - name: Collect JavaSMT JAR - run: | - java_smt_dir="${HOME}/.gradle/caches/modules-2/files-2.1/org.sosy-lab/java-smt" - if [[ ! -d "$java_smt_dir" ]]; then - echo "JavaSMT Gradle cache directory not found: $java_smt_dir" >&2 - exit 1 - fi - - java_smt_jar="$(find "$java_smt_dir" -type f -name 'java-smt-*.jar' ! -name '*-sources.jar' ! -name '*-javadoc.jar' | sort | tail -n 1)" - if [[ -z "$java_smt_jar" ]]; then - echo "JavaSMT JAR not found in Gradle cache: $java_smt_dir" >&2 - exit 1 - fi - - mkdir -p build/svcomp-runtime - cp "$java_smt_jar" build/svcomp-runtime/java-smt-latest.jar - name: Upload JARs - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: release-jars-${{ github.sha }} - path: | - symbolic-executor/lib/symbolic-executor.jar - build/svcomp-runtime/java-smt-latest.jar + path: symbolic-executor/lib/symbolic-executor.jar if-no-files-found: error retention-days: 1 @@ -74,34 +56,12 @@ jobs: prerelease: ${{ steps.meta.outputs.prerelease }} artifact-name: ${{ steps.meta.outputs.artifact-name }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v7 - name: Download JARs - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: release-jars-${{ github.sha }} path: ${{ runner.temp }}/release-jars - - name: Install JavaSMT packaging artifact - run: | - mkdir -p libs/java-library-path - cp "${RUNNER_TEMP}/release-jars/build/svcomp-runtime/java-smt-latest.jar" \ - libs/java-library-path/java-smt-latest.jar - - name: Download WitnessCreator runtime - run: | - version="${WITNESS_CREATOR_VERSION}" - zip_name="witness-creator-${version#v}.zip" - zip_path="${RUNNER_TEMP}/${zip_name}" - runtime_dir="${RUNNER_TEMP}/witness-creator-runtime" - - curl --fail --location --retry 3 \ - --output "${zip_path}" \ - "https://github.com/SWAT-project/WitnessCreator/releases/download/${version}/${zip_name}" - echo "${WITNESS_CREATOR_SHA256} ${zip_path}" | sha256sum --check - - - mkdir -p "${runtime_dir}" - unzip -q "${zip_path}" -d "${runtime_dir}" - test -f "${runtime_dir}/WitnessCreator/build/libs/WitnessCreator.jar" - test -f "${runtime_dir}/WitnessCreator/witnesses/default_violation.st" - test -f "${runtime_dir}/WitnessCreator/witnesses/witness.st" - name: Determine release channel id: meta run: | @@ -148,26 +108,21 @@ jobs: } >> "${GITHUB_OUTPUT}" - name: Build SV-COMP Python runtime env: - VENV_DIR: ${{ runner.temp }}/svcomp-runtime/${{ env.SVCOMP_VENV_DIR_NAME }} - run: | - mkdir -p "$(dirname "${VENV_DIR}")" - /usr/bin/python3 -m venv --copies "${VENV_DIR}" - "${VENV_DIR}/bin/python3" -m pip install --no-deps -r scripts/svcomp-package/requirements.txt - "${VENV_DIR}/bin/python3" -m pip check - "${VENV_DIR}/bin/python3" -m pip freeze --all > "${VENV_DIR}/requirements.freeze.txt" - "${VENV_DIR}/bin/python3" -c "import fastapi, pydantic, z3" + 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_RUNTIME_DIR: ${{ runner.temp }}/svcomp-runtime - SWAT_SVCOMP_WITNESS_CREATOR_DIR: ${{ runner.temp }}/witness-creator-runtime/WitnessCreator + 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@v4 + uses: actions/upload-artifact@v7 with: name: ${{ steps.meta.outputs.artifact-name }} path: build/distributions/${{ steps.meta.outputs.zip-name }} @@ -186,7 +141,7 @@ jobs: contents: write steps: - name: Download SV-COMP ZIP artifact - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: ${{ needs.package.outputs.artifact-name }} path: ${{ runner.temp }}/svcomp-package @@ -234,21 +189,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 @@ -260,18 +215,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 @@ -282,11 +237,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 57b3d545..3a446e27 100644 --- a/.gitignore +++ b/.gitignore @@ -23,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..a9da5d96 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,138 @@ +# 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 used in `strategy/DFS.py`. + +- **Lockstep walk:** `dfs` walks the execution tree and the SA graph together, pairing branch for + branch **by position**. 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. +- **Keys check the walk:** 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`). `dfs` compares them with + the graph node's key and its own call stack (`SAGraph.check_branch`): a mismatch logs + `[DFS] SA desync`, stops the walk on that path and records `sa_desync`, which turns SAFE into + UNKNOWN. When the walk has no node, `SAGraph.resync` finds the next branch by key, with the call + stack rebuilt from the trace. The extractor computes the same keys; change both together. +- **Concrete branches count:** they are in the tree too. Filtering to symbolic branches happens + after `dfs`. +- **Walker giving up is safe:** when `walk_till_branch` returns `None`, the walker has no + information, and `dfs` treats everything as interesting. That fallback is always safe. +- **Call sites with several callees** (exact dispatch to known overrides): `walk_till_branch` + stops there, and the walk resyncs inside whichever callee ran; summaries take the union. +- **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. +- **`` and callbacks are masked:** static initializers are not in the graph, and neither is + application code that library code calls back (the executor flags those branches `callback`, + see `ShadowContext.inCallback`), so the walk is frozen inside them. +- **Asserts are only the first target:** pruning aims at assertion points for now because they + are easy to test (SV-COMP's valid-assert). Later targets include shell calls, SQL statement + building and uncaught exceptions, so prefer changes that work for any kind of target point. +- **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` special + event and `dfs` stops trusting the graph until it finds it again at a later branch by key + (resync, see below). +- **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. + +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 +``` + +`dfs` only descends into real `data.BinaryExecutionTree.Node` instances. In tests, build them with +`Node.__new__(Node)` and set the fields directly. `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, --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`. +- **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). +- **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/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/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..129bbefb --- /dev/null +++ b/scripts/check-svcomp-package.sh @@ -0,0 +1,51 @@ +#!/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, 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; do + [[ -f "${tool_dir}/${f}" ]] || fail "missing ${f} in archive root ${top_dirs}/" +done +[[ -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; } + ./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 +' +echo "SV-COMP package check passed." diff --git a/scripts/package-svcomp.sh b/scripts/package-svcomp.sh index c3a5e788..fc53cf1b 100755 --- a/scripts/package-svcomp.sh +++ b/scripts/package-svcomp.sh @@ -10,20 +10,12 @@ set -euo pipefail # # - symbolic-executor/lib/symbolic-executor.jar # -# Set SWAT_SVCOMP_WITNESS_CREATOR_DIR to the extracted WitnessCreator runtime -# root from https://github.com/SWAT-project/WitnessCreator. The root must -# contain: +# 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). # -# - build/libs/WitnessCreator.jar -# - witnesses/default_violation.st -# - witnesses/witness.st -# -# Set SWAT_SVCOMP_RUNTIME_DIR to a runtime package root that contains the -# pinned SV-COMP Python environment: -# -# - .venv_ubuntu_24_04_1__x86_64/ -# -# Z3 and JavaSMT are taken from the vendored files in this repository. +# Z3 and JavaSMT are taken from the vendored files in this repository (libs/). # ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" cd "$ROOT_DIR" @@ -38,19 +30,13 @@ 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)" -RUNTIME_DIR="${SWAT_SVCOMP_RUNTIME_DIR:-${SWAT_SVCOMP_REFERENCE_DIR:-}}" -WITNESS_CREATOR_DIR="${SWAT_SVCOMP_WITNESS_CREATOR_DIR:-}" -VENV_DIR_NAME="${SWAT_SVCOMP_VENV_DIR_NAME:-.venv_ubuntu_24_04_1__x86_64}" -LINUX_Z3_DIST="z3-4.15.4-x64-glibc-2.39" +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_JAR="${ROOT_DIR}/libs/java-library-path/java-smt-latest.jar" - -if [[ -n "$RUNTIME_DIR" ]]; then - RUNTIME_DIR="$(cd "$RUNTIME_DIR" && pwd)" -fi +JAVA_SMT_DIR="${ROOT_DIR}/libs/maven/org/sosy-lab/java-smt" -if [[ -n "$WITNESS_CREATOR_DIR" ]]; then - WITNESS_CREATOR_DIR="$(cd "$WITNESS_CREATOR_DIR" && pwd)" +if [[ -n "$PYTHON_DIR" ]]; then + PYTHON_DIR="$(cd "$PYTHON_DIR" && pwd)" fi fail() { @@ -103,14 +89,17 @@ copy_artifact_tree() { cp -a "$src" "$dest" } +# Installs of the vendored Linux Z3 distribution (relative to its root) into +# libs/java-library-path/ under its file name. install_z3_runtime_file() { - local name="$1" + local path="$1" local mode="${2:-0644}" - local dest="${PACKAGE_DIR}/libs/java-library-path/${name}" + local dest="${PACKAGE_DIR}/libs/java-library-path/$(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}/bin/${name}" > "$dest" + unzip -p "$LINUX_Z3_ZIP" "${LINUX_Z3_DIST}/${path}" > "$dest" + [[ -s "$dest" ]] || fail "missing ${path} in ${LINUX_Z3_ZIP}" chmod "$mode" "$dest" } @@ -143,24 +132,23 @@ copy_artifact_file "$EXECUTOR_JAR" "$PACKAGE_DIR/symbolic-executor/lib/symbolic- copy_tree_files symbolic-explorer "$PACKAGE_DIR/symbolic-explorer" -[[ -n "$WITNESS_CREATOR_DIR" ]] || fail "SWAT_SVCOMP_WITNESS_CREATOR_DIR must point to the extracted WitnessCreator runtime root" -[[ -f "$WITNESS_CREATOR_DIR/build/libs/WitnessCreator.jar" ]] || fail "missing WitnessCreator JAR: ${WITNESS_CREATOR_DIR}/build/libs/WitnessCreator.jar" -[[ -f "$WITNESS_CREATOR_DIR/witnesses/default_violation.st" ]] || fail "missing WitnessCreator template: ${WITNESS_CREATOR_DIR}/witnesses/default_violation.st" -[[ -f "$WITNESS_CREATOR_DIR/witnesses/witness.st" ]] || fail "missing WitnessCreator template: ${WITNESS_CREATOR_DIR}/witnesses/witness.st" -copy_artifact_tree "$WITNESS_CREATOR_DIR" "$PACKAGE_DIR/WitnessCreator" - -install_z3_runtime_file z3 0755 -install_z3_runtime_file libz3.so -install_z3_runtime_file libz3java.so -install_z3_runtime_file com.microsoft.z3.jar -install_z3_runtime_file libz3.a -[[ -f "$JAVA_SMT_JAR" ]] || fail "missing JavaSMT JAR: ${JAVA_SMT_JAR}" +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 +# 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 "$RUNTIME_DIR" ]] || fail "SWAT_SVCOMP_RUNTIME_DIR must point to a runtime package root containing ${VENV_DIR_NAME}" -copy_artifact_tree "${RUNTIME_DIR}/${VENV_DIR_NAME}" "${PACKAGE_DIR}/${VENV_DIR_NAME}" +[[ -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" mkdir -p "$DIST_DIR" ZIP_PATH="${DIST_DIR}/${PACKAGE_NAME}.zip" diff --git a/scripts/svcomp-package/README.md b/scripts/svcomp-package/README.md index 46852fdf..bf7fc7e2 100644 --- a/scripts/svcomp-package/README.md +++ b/scripts/svcomp-package/README.md @@ -4,13 +4,21 @@ This archive contains a runnable SWAT package for SV-COMP style Java verificatio Important entry points: -- `run-swat.sh`: verifier entry point used by BenchExec. +- `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 two bundled smoke-test targets. +- `smoketest.sh`: runs SWAT on the bundled smoke-test target. - `sv-comp.cfg`: SWAT configuration for SV-COMP mode. -The package expects Java 17 and the bundled `.venv_ubuntu_24_04_1__x86_64` -Python environment. `run-swat.sh` intentionally uses only that environment. +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: diff --git a/scripts/svcomp-package/requirements.txt b/scripts/svcomp-package/requirements.txt index 5c32b0a4..ff3568e3 100644 --- a/scripts/svcomp-package/requirements.txt +++ b/scripts/svcomp-package/requirements.txt @@ -1,19 +1,21 @@ -annotated-types==0.7.0 -anyio==4.6.2.post1 -boto==2.49.0 -certifi==2025.11.12 -charset-normalizer==3.4.4 -click==8.1.7 -fastapi==0.115.5 -h11==0.14.0 -idna==3.10 -pydantic==2.10.0 -pydantic_core==2.27.0 -PyYAML==6.0.2 -requests==2.32.5 -sniffio==1.3.1 -starlette==0.41.3 -typing_extensions==4.12.2 -urllib3==2.5.0 -uvicorn==0.32.1 -z3-solver==4.15.4.0 +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 index 424a3188..a10bb7b5 100755 --- a/scripts/svcomp-package/run-swat.sh +++ b/scripts/svcomp-package/run-swat.sh @@ -1,17 +1,13 @@ #!/bin/bash +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -# Check if the '-v' argument is among the arguments, indicating that the user wants to see the version +# '-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 - # Check if '-v' is among the arguments - if [ "$arg" == "-v" ]; then - # Execute the Java command with base dir of this script - pushd "$(dirname "$0")" > /dev/null - java -cp ./symbolic-executor/lib/symbolic-executor.jar de.uzl.its.swat.Version - popd > /dev/null - # Exit with status 0 - exit 0 + if [ "$arg" == "-v" ] || [ "$arg" == "--version" ]; then + exec java -cp "$SCRIPT_DIR/symbolic-executor/lib/symbolic-executor.jar" de.uzl.its.swat.Version fi done @@ -27,46 +23,41 @@ if [ -f "$second_last_path/Main.java" ]; then set -- "${@:1:$(($#-2))}" "$last_path" "$second_last_path" fi -# Loop through all provided paths and copy Java files preserving directory structure -for path in "$@"; do - if [ -d "$path" ]; then - # Find all Java files and preserve relative directory structure - find "$path" -type f -name "*.java" | while read src; do - rel_path="${src#$path/}" - dst_dir="./WitnessCreator/$(dirname "$rel_path")" - mkdir -p "$dst_dir" - cp "$src" "$dst_dir/" - done - echo "Java files from $path copied successfully to ./WitnessCreator/" - elif [ -f "$path" ] && [[ "$path" == *.java ]]; then - # If the argument is a single Java file, copy it - cp "$path" ./WitnessCreator/ - echo "File $path copied successfully to ./WitnessCreator/" - fi -done - -# # copy Main.java for Witness creation -# last_path="${@: -1}" -# file_to_copy="${last_path}/Main.java" -# -# # Check if the file exists -# if [ -f "$file_to_copy" ]; then -# # Copy the Main.java file to the current directory -# cp "$file_to_copy" ./WitnessCreator/. -# echo "File copied successfully to ./WitnessCreator/" -# else -# echo "Error: File not found." -# fi - - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -VENV_PYTHON="$SCRIPT_DIR/.venv_ubuntu_24_04_1__x86_64/bin/python3" - -if [ ! -x "$VENV_PYTHON" ]; then - echo "Error: Python runtime not found at $VENV_PYTHON" +# 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: $@" -"$VENV_PYTHON" -u "$SCRIPT_DIR/run_swat.py" "$@" > out.log 2>&1 -cat out.log +"$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 index 6b883330..052d9a88 100755 --- a/scripts/svcomp-package/run_swat.py +++ b/scripts/svcomp-package/run_swat.py @@ -1,5 +1,5 @@ from contextlib import contextmanager -import logging, os, enum, subprocess, sys, base64, uuid +import logging, os, enum, shutil, subprocess, sys, uuid import concurrent.futures, json, datetime from typing import List, Optional @@ -7,7 +7,12 @@ logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) -WITNESS_PATH = os.path.join(SCRIPT_DIR, 'WitnessCreator/build/libs/WitnessCreator.jar') +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') class ExecutionStatus(enum.Enum): @@ -93,24 +98,29 @@ def run_command_with_timeout(cmd: list[str], timeout: int | None = None) -> tupl -def generate_command(args, property_file: str): +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') - logging_dir = os.path.join(SCRIPT_DIR, 'logs') - python3_path = os.path.join(SCRIPT_DIR, '.venv_ubuntu_24_04_1__x86_64/bin/python3') - - base_command = [ python3_path, "-u", os.path.join(SCRIPT_DIR, 'symbolic-explorer', 'SymbolicExplorer.py'), + # 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, "--config", config_path, "-z3", library_path, - "--logdir", logging_dir, + "--logdir", LOG_DIR, "--mode", "sv-comp", '--port', "8000", + "--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:]: @@ -140,35 +150,31 @@ def compile_target(args, local_target_dir): log_output(output) -def extract_last_round(out, marker="============================== ROUND"): - - return "\n ".join(out).split(marker)[-1] - - -def extract_markers(out, marker): - res = [] - for line in out.split("\n"): - print(line) - if marker in line: - res.append(line) - return res - -def generate_witness(witness): - witness_dir = os.path.join(SCRIPT_DIR, 'WitnessCreator/') - with pushd(witness_dir): - # base64 encode the string - enc = base64.b64encode(witness.encode()) - cmd = [ - 'java', - '-jar', - WITNESS_PATH, - enc, - witness_dir - ] - print(cmd) - _, output = run_command_with_timeout(cmd) - print("WITNESS CREATED") - print(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: @@ -180,9 +186,11 @@ def log_output(output: List[str]): # 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 @@ -193,8 +201,15 @@ def log_output(output: List[str]): 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) + 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) @@ -202,38 +217,32 @@ def log_output(output: List[str]): 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): - execution_status, output = run_command_with_timeout(cmd) - log_output(output) - print(f"Execution status: {execution_status}") - if execution_status == ExecutionStatus.TIMEOUT or execution_status == ExecutionStatus.ERROR: - print(f'Error: {output}') - exit(1) - - - out = extract_last_round(output) - verdict = extract_markers(out, f'[VERDICT {property_file}]') - if "== ERROR" in str(verdict): - witnesses = extract_markers(out, '[WITNESS]') - generate_witness("\n".join(witnesses)) - print(f'Witnesses:\n {witnesses}') - - # Print the generated witness file contents for validation on benchmark server - witness_file_path = os.path.join(SCRIPT_DIR, 'WitnessCreator', 'witness.graphml') - if os.path.exists(witness_file_path): - print("\n" + "="*80) - print("WITNESS FILE CONTENTS:") - print("="*80) - with open(witness_file_path, 'r') as f: - witness_content = f.read() - print(witness_content) - print("="*80) - print("END OF WITNESS FILE") - print("="*80 + "\n") - else: - print(f"WARNING: Witness file not found at {witness_file_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 index 95fb8c05..22228fae 100755 --- a/scripts/svcomp-package/smoketest.sh +++ b/scripts/svcomp-package/smoketest.sh @@ -1,12 +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 passed!" - exit 0 -else - echo "Smoketest failed!" +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/settings.gradle b/settings.gradle index 430c6ddb..8db92855 100644 --- a/settings.gradle +++ b/settings.gradle @@ -60,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-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']) diff --git a/symbolic-executor/build.gradle b/symbolic-executor/build.gradle index 94841476..9bf435ac 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() } @@ -50,10 +58,12 @@ test { systemProperty "java.library.path", "../libs/java-library-path" // 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 @@ -63,6 +73,22 @@ test { } } +// 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 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 18a888a1..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 @@ -50,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()); 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..6285ad4a 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 @@ -42,10 +42,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! @@ -233,14 +229,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 +414,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/config/Config.java b/symbolic-executor/src/main/java/de/uzl/its/swat/config/Config.java index 2f9a014e..5db06a76 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,9 @@ 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; private static final boolean DEFAULT_STRING_INTERNING = true; @Getter private String stringProxy; private static final String DEFAULT_STRING_PROXY = "Ljava/lang/String;"; @@ -446,6 +449,7 @@ private void readProperties() { // Fontus options // ------------------------------------ useStringInterning = readBoolean("useStringInterning", DEFAULT_STRING_INTERNING); + loopStateMatching = readBoolean("loopStateMatching", DEFAULT_LOOP_STATE_MATCHING); stringProxy = readString("stringProxy", DEFAULT_STRING_PROXY); } 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/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 48061c8f..ecf4c184 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,7 +4,9 @@ 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; @@ -64,41 +66,84 @@ 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 { + int endpointID; + if (config.getInstrumentationTransformer().equals(TransformerType.SV_COMP)) { + endpointID = 0; + } else { + endpointID = endpoint.hashCode(); + } + ThreadHandler.addThreadContext(currentThread().getId(), endpoint, endpointID); + + 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 @@ -106,6 +151,10 @@ public static void init(String endpoint) { */ public static void terminate() { try { + if (ThreadHandler.leaveNestedEndpoint(currentThread().getId())) { + logger.info("Leaving nested endpoint (Thread: {})", currentThread().getId()); + return; + } Transformer.logMissedClasses(); ThreadHandler.disableThreadContext(currentThread().getId()); logger.info( @@ -138,7 +187,7 @@ public static void terminate() { break; case PRINT: if (!ThreadHandler.isThreadContextAborted(currentThread().getId())) { - System.out.println(ThreadHandler.getSymbolicVisitor(currentThread().getId()).getSymbolicTraceHandler().getTraceDTO(ThreadHandler.getStatsStorage(currentThread().getId()))); + printTrace(currentThread().getId()); } break; case NONE: @@ -148,7 +197,12 @@ 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()); + } ThreadHandler.removeThreadContext(currentThread().getId()); } catch (NoThreadContextException | ThreadAlreadyDisabledException e) { logger.warn(e.toString()); @@ -162,6 +216,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..aa76fe5d 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,6 +3,7 @@ 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.Util; import de.uzl.its.swat.common.exceptions.InstrumentationException; @@ -37,6 +38,8 @@ public abstract class Transformer implements ClassFileTransformer { static { // 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(); LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory(); // this will flush and close all appenders (including your buffered FileAppender) lc.stop(); @@ -132,6 +135,10 @@ public static void logMissedClasses(){ @SuppressWarnings("unused") public static void premain(String agentArgs, Instrumentation inst) { try { + // 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(); ThreadHandler.init(); ThreadHandler.addThreadContext(-1, "SymbolicThread", -1); // Main thread used during initialization and instrumentation 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..44af9465 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,39 @@ public MethodVisitor visitMethod( name, desc, cname); + if (Config.instance().isLoopStateMatching() && !cname.startsWith("de/uzl/its/swat/")) { + // Collect the method to find its loop heads, then hand it on + return new MethodNode(Opcodes.ASM9, access, name, desc, signature, exceptions) { + @Override + public void visitEnd() { + super.visitEnd(); + ima.setLoopHeads(LoopHeads.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..e62b1d75 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,27 @@ 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; + } + + /** + * 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); + } + } /** Push a value onto the stack. */ private static void addBipushInsn(MethodVisitor mv, int val) { Utils.addBipushInsn(mv, val); @@ -154,6 +208,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 +229,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 +792,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 +853,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 +1547,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 +1623,7 @@ private void addMethodWithTryCatch( mv.visitJumpInsn(GOTO, jumpTarget); mv.visitLabel(handler); + addStackOverflowCheck(mv); addJpushInsn(mv, instrumentationState.incAndGetId()); addJpushInsn(mv, invokeId); @@ -1581,6 +1662,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 +1700,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 +1737,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,6 +1801,9 @@ 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()); @@ -1954,6 +2041,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 +2078,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 +2115,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/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/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/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..3a58d5f3 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 @@ -300,8 +300,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 +401,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 +438,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..e40ae9b7 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; @@ -99,7 +100,10 @@ public byte[] transform( 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)) { 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/staticcheck/AbsValue.java b/symbolic-executor/src/main/java/de/uzl/its/swat/staticcheck/AbsValue.java new file mode 100644 index 00000000..5fb9b97f --- /dev/null +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/staticcheck/AbsValue.java @@ -0,0 +1,226 @@ +package de.uzl.its.swat.staticcheck; + +import java.util.Objects; +import org.objectweb.asm.tree.analysis.Value; + +/** + * Abstract value of a local variable or operand stack slot for {@link ExceptionFreedomCheck}. + * + *

Every fact is an over-approximation of the concrete values the slot can hold: {@code BOTTOM} + * holds none (not reached yet), {@code NULL} only null, {@code NONNULL} only references, {@code REF} + * references or null, {@code PRIM} a primitive and {@code UNKNOWN} anything. The optional facts + * narrow this further and are dropped (set to null) whenever two values with different facts meet. + */ +final class AbsValue implements Value { + + enum Kind { + BOTTOM, + PRIM, + NULL, + NONNULL, + REF, + UNKNOWN + } + + final Kind kind; + final int size; + /** PRIM: the int or long value, if it is a known constant. */ + final Long constant; + /** NONNULL array: its length, if it is a known constant. */ + final Integer length; + /** + * NONNULL array: its length equals the number of constants of this enum class. PRIM: the value + * is that number. + */ + final String lengthOf; + /** PRIM: the value is the ordinal of a constant of this enum class (0 <= v < its constant count). */ + final String ordinalOf; + /** NONNULL: the runtime class of the object (internal name, or descriptor for arrays). */ + final String exactType; + /** NULL/NONNULL/REF: a type every non-null value is an instance of. */ + final String staticType; + /** The receiver of the current instance method, unchanged since method entry. */ + final boolean isThis; + /** The slot may hold the receiver of the current instance method (a may-alias fact, kept by joins). */ + final boolean mayBeThis; + /** + * The exception caught by the handler with this index in the method's try-catch list (-1: not a + * caught exception, or not known to be one). + */ + final int caughtBy; + + static final AbsValue UNKNOWN1 = new AbsValue(Kind.UNKNOWN, 1, null, null, null, null, null, null, false); + static final AbsValue PRIM1 = prim(1); + static final AbsValue PRIM2 = prim(2); + static final AbsValue NULL = new AbsValue(Kind.NULL, 1, null, null, null, null, null, null, false); + + AbsValue(Kind kind, int size, Long constant, Integer length, String lengthOf, String ordinalOf, + String exactType, String staticType, boolean isThis) { + this(kind, size, constant, length, lengthOf, ordinalOf, exactType, staticType, isThis, -1); + } + + AbsValue(Kind kind, int size, Long constant, Integer length, String lengthOf, String ordinalOf, + String exactType, String staticType, boolean isThis, int caughtBy) { + this(kind, size, constant, length, lengthOf, ordinalOf, exactType, staticType, isThis, caughtBy, isThis); + } + + private AbsValue(Kind kind, int size, Long constant, Integer length, String lengthOf, String ordinalOf, + String exactType, String staticType, boolean isThis, int caughtBy, boolean mayBeThis) { + this.mayBeThis = mayBeThis || isThis; + this.kind = kind; + this.size = size; + this.constant = constant; + this.length = length; + this.lengthOf = lengthOf; + this.ordinalOf = ordinalOf; + this.exactType = exactType; + this.staticType = staticType; + this.isThis = isThis; + this.caughtBy = caughtBy; + } + + static AbsValue prim(int size) { + return new AbsValue(Kind.PRIM, size, null, null, null, null, null, null, false); + } + + static AbsValue constant(int size, long value) { + return new AbsValue(Kind.PRIM, size, value, null, null, null, null, null, false); + } + + static AbsValue bottom(int size) { + return new AbsValue(Kind.BOTTOM, size, null, null, null, null, null, null, false); + } + + static AbsValue ref(String staticType) { + return new AbsValue(Kind.REF, 1, null, null, null, null, null, staticType, false); + } + + static AbsValue nonNull(String staticType) { + return new AbsValue(Kind.NONNULL, 1, null, null, null, null, null, staticType, false); + } + + /** A non-null object whose runtime class is known (a fresh allocation, a String literal, ...). */ + static AbsValue exact(String type) { + return new AbsValue(Kind.NONNULL, 1, null, null, null, null, type, type, false); + } + + static AbsValue array(String type, Integer length, String lengthOf) { + return new AbsValue(Kind.NONNULL, 1, null, length, lengthOf, null, type, type, false); + } + + AbsValue withThis() { + return new AbsValue(kind, size, constant, length, lengthOf, ordinalOf, exactType, staticType, true); + } + + static AbsValue caught(String type, int handler) { + return new AbsValue(Kind.NONNULL, 1, null, null, null, null, null, type, false, handler); + } + + /** The same value without the facts that only hold where it was computed (receiver, handler). */ + AbsValue escaped() { + if (!mayBeThis && caughtBy < 0) return this; + return new AbsValue(kind, size, constant, length, lengthOf, ordinalOf, exactType, staticType, false, -1); + } + + AbsValue withOrdinalOf(String enumType) { + return new AbsValue(kind, size, constant, length, lengthOf, enumType, exactType, staticType, isThis, caughtBy, mayBeThis); + } + + AbsValue withLengthOf(String enumType) { + return new AbsValue(kind, size, constant, length, enumType, ordinalOf, exactType, staticType, isThis, caughtBy, mayBeThis); + } + + boolean isBottom() { + return kind == Kind.BOTTOM; + } + + boolean isNull() { + return kind == Kind.NULL; + } + + /** No concrete value of this slot can be null (BOTTOM holds no value at all). */ + boolean isNonNull() { + return kind == Kind.NONNULL || kind == Kind.BOTTOM; + } + + boolean isReference() { + return kind == Kind.NULL || kind == Kind.NONNULL || kind == Kind.REF; + } + + @Override + public int getSize() { + return size; + } + + /** The least upper bound of two values. */ + static AbsValue join(AbsValue a, AbsValue b) { + AbsValue j = joinFacts(a, b); + if ((a.mayBeThis || b.mayBeThis) && !j.mayBeThis) { + j = new AbsValue(j.kind, j.size, j.constant, j.length, j.lengthOf, j.ordinalOf, j.exactType, j.staticType, + j.isThis, j.caughtBy, true); + } + return j; + } + + private static AbsValue joinFacts(AbsValue a, AbsValue b) { + if (a.equals(b)) return a; + if (a.isBottom()) return b; + if (b.isBottom()) return a; + if (a.size != b.size) return UNKNOWN1; + if (a.kind == Kind.PRIM && b.kind == Kind.PRIM) { + return new AbsValue(Kind.PRIM, a.size, same(a.constant, b.constant), null, + same(a.lengthOf, b.lengthOf), same(a.ordinalOf, b.ordinalOf), null, null, false); + } + if (a.isReference() && b.isReference()) { + boolean thisRef = a.isThis && b.isThis; + if (a.kind == Kind.NULL || b.kind == Kind.NULL) { + AbsValue other = a.kind == Kind.NULL ? b : a; + return new AbsValue(other.kind == Kind.NULL ? Kind.NULL : Kind.REF, 1, null, null, null, null, + null, other.staticType, false); + } + if (a.kind == Kind.NONNULL && b.kind == Kind.NONNULL) { + return new AbsValue(Kind.NONNULL, 1, null, same(a.length, b.length), same(a.lengthOf, b.lengthOf), + null, same(a.exactType, b.exactType), same(a.staticType, b.staticType), thisRef, + a.caughtBy == b.caughtBy ? a.caughtBy : -1); + } + return new AbsValue(Kind.REF, 1, null, null, null, null, null, same(a.staticType, b.staticType), false); + } + return a.size == 1 ? UNKNOWN1 : new AbsValue(Kind.UNKNOWN, a.size, null, null, null, null, null, null, false); + } + + private static T same(T a, T b) { + return Objects.equals(a, b) ? a : null; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof AbsValue)) return false; + AbsValue v = (AbsValue) o; + return kind == v.kind && size == v.size && isThis == v.isThis && mayBeThis == v.mayBeThis && caughtBy == v.caughtBy + && Objects.equals(constant, v.constant) + && Objects.equals(length, v.length) && Objects.equals(lengthOf, v.lengthOf) + && Objects.equals(ordinalOf, v.ordinalOf) && Objects.equals(exactType, v.exactType) + && Objects.equals(staticType, v.staticType); + } + + @Override + public int hashCode() { + return Objects.hash(kind, size, constant, length, lengthOf, ordinalOf, exactType, staticType, isThis, mayBeThis, caughtBy); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(kind.name()); + if (constant != null) sb.append('=').append(constant); + if (length != null) sb.append(" len=").append(length); + if (lengthOf != null) sb.append(" lenOf=").append(lengthOf); + if (ordinalOf != null) sb.append(" ordOf=").append(ordinalOf); + if (exactType != null) sb.append(" exact=").append(exactType); + else if (staticType != null) sb.append(" type=").append(staticType); + if (isThis) sb.append(" this"); + else if (mayBeThis) sb.append(" maybe-this"); + if (caughtBy >= 0) sb.append(" caught#").append(caughtBy); + return sb.toString(); + } +} diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/staticcheck/ExceptionFreedomCheck.java b/symbolic-executor/src/main/java/de/uzl/its/swat/staticcheck/ExceptionFreedomCheck.java new file mode 100644 index 00000000..bf750356 --- /dev/null +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/staticcheck/ExceptionFreedomCheck.java @@ -0,0 +1,920 @@ +package de.uzl.its.swat.staticcheck; + +import java.nio.file.Path; +import java.util.*; +import org.objectweb.asm.ConstantDynamic; +import org.objectweb.asm.Handle; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.*; +import org.objectweb.asm.tree.analysis.Analyzer; +import org.objectweb.asm.tree.analysis.AnalyzerException; +import org.objectweb.asm.tree.analysis.Frame; + +/** + * A conservative static check that a program can never end with an uncaught RuntimeException, + * the SV-COMP no-runtime-exception property. If it succeeds, the property holds on every + * execution; if it fails, nothing is known. + * + *

The program is every class on its classpath, entered through {@code Main.main}. The check + * proves that no RuntimeException can escape {@code main} or a static initializer that may run: + * + *

    + *
  • Every instruction that the JVM specification lets throw a RuntimeException (array access + * and allocation, field access, calls, casts, integer division, {@code athrow}, monitors, + * {@code invokedynamic}) is proven not to throw one, from abstract values that track + * nullness, constant ints, array lengths, enum ordinals and runtime types, or is covered by a + * handler that catches every RuntimeException (whose own code is checked in turn). + *
  • Calls go to application methods that are themselves proven (a least fixpoint over the call + * graph, with class-hierarchy dispatch), to the modelled {@code Verifier}, or to the library + * methods of {@link JdkModel}, which throw nothing and never call back into application code. + * Every other library call fails the check. + *
  • Facts about fields, parameters and return values are the joins of all values the program + * stores, passes or returns (a least fixpoint from "no value"). A reference field also holds + * its default null, unless the field is assigned before any code that could read it runs + * (see {@link #initializedBeforeRead}). + *
+ * + *

Errors (AssertionError, StackOverflowError, OutOfMemoryError, linkage errors, ...) are not + * RuntimeExceptions and are not considered. Enum classes are assumed to be compiled from Java + * source, so the ordinal of a constant is below the number of constants. + */ +public final class ExceptionFreedomCheck implements FactInterpreter.Facts, Opcodes { + + static final String MAIN_CLASS = "Main"; + static final String MAIN_DESC = "([Ljava/lang/String;)V"; + private static final int MAX_ROUNDS = 100; + + /** + * The outcome: whether the program is proven free of uncaught RuntimeExceptions, whether it is + * proven to never create an AssertionError (so no assert can fail), and why not. + */ + public static final class Result { + public final boolean exceptionFree; + public final String reason; + public final boolean assertionFree; + public final String assertionReason; + + Result(boolean exceptionFree, String reason, boolean assertionFree, String assertionReason) { + this.exceptionFree = exceptionFree; + this.reason = reason; + this.assertionFree = assertionFree; + this.assertionReason = assertionReason; + } + + static Result neither(String reason) { + return new Result(false, reason, false, reason); + } + } + + private final Program program; + private final Map fieldFacts = new HashMap<>(); + private final Map parameterFacts = new HashMap<>(); + private final Map returnFacts = new HashMap<>(); + private final Set initializedBeforeRead = new HashSet<>(); + private final Map[]> frames = new HashMap<>(); + private final Map targetCache = new IdentityHashMap<>(); + private final Set unknownTargets = Collections.newSetFromMap(new IdentityHashMap<>()); + private Program.Method entry; + private boolean changed; + + ExceptionFreedomCheck(Program program) { + this.program = program; + } + + public static Result check(List classpath) { + try { + return new ExceptionFreedomCheck(new Program(classpath)).run(); + } catch (Exception | StackOverflowError e) { + return Result.neither("analysis failed: " + e); + } + } + + Result run() throws AnalyzerException { + if (program.unsupportedClasspath() != null) return Result.neither(program.unsupportedClasspath()); + entry = program.method(MAIN_CLASS, "main", MAIN_DESC); + if (entry == null || !entry.isStatic() || !entry.hasCode()) return Result.neither("no Main.main"); + + computeInitializedBeforeRead(); + for (ClassNode c : program.appClasses()) { + for (FieldNode f : c.fields) { + String key = c.name + "." + f.name + ":" + f.desc; + Type t = Type.getType(f.desc); + if (t.getSort() != Type.OBJECT && t.getSort() != Type.ARRAY) { + fieldFacts.put(key, AbsValue.prim(t.getSize())); + continue; + } + // The JVM only applies a ConstantValue attribute to static fields. + if (f.value instanceof String && (f.access & ACC_STATIC) != 0) fieldFacts.put(key, AbsValue.exact("java/lang/String")); + else if (!initializedBeforeRead.contains(key)) fieldFacts.put(key, AbsValue.NULL); + } + } + + // Facts: least fixpoint, from "no value" upwards. + int round = 0; + do { + if (++round > MAX_ROUNDS) return Result.neither("facts did not converge"); + changed = false; + for (Program.Method m : program.methods()) { + if (!m.hasCode()) continue; + Frame[] f = new Analyzer<>(new FactInterpreter(this, m)).analyze(m.owner.name, m.node); + frames.put(m.key, f); + collectFacts(m, f); + } + } while (changed); + + // May-throw: least fixpoint over the call graph. + Map mayThrow = new LinkedHashMap<>(); + boolean grew; + do { + grew = false; + for (Program.Method m : program.methods()) { + if (!m.hasCode() || mayThrow.containsKey(m.key)) continue; + String reason = escapingException(m, mayThrow); + if (reason != null) { + mayThrow.put(m.key, reason); + grew = true; + } + } + } while (grew); + + List roots = new ArrayList<>(); + roots.add(entry); + roots.addAll(initializers(MAIN_CLASS)); + for (ClassNode c : program.appClasses()) { + // The finalizer thread may run it on any unreachable object, even one whose constructor + // threw before it assigned its fields. + Program.Method fin = program.method(c.name, "finalize", "()V"); + if (fin != null && !fin.isStatic()) return Result.neither("finalizer " + fin.key); + } + Set reachable = new LinkedHashSet<>(); + String open = openWorldCall(roots, reachable); + if (open != null) return Result.neither(open); + String assertion = assertionCreation(reachable); + boolean assertionFree = assertion == null; + if (assertionFree) assertion = "no AssertionError is created in code reachable from Main.main or a static initializer"; + // Main.main and every static initializer that may run (an exception escaping one surfaces + // as an ExceptionInInitializerError, but is still counted). + List mustNotThrow = new ArrayList<>(); + mustNotThrow.add(entry); + for (Program.Method m : reachable) if (m.node.name.equals("")) mustNotThrow.add(m); + for (Program.Method r : mustNotThrow) { + if (mayThrow.containsKey(r.key)) return new Result(false, explain(r.key, mayThrow), assertionFree, assertion); + } + return new Result(true, "no instruction reachable from Main.main or a static initializer can throw an uncaught RuntimeException", + assertionFree, assertion); + } + + /** + * Where reachable code may create an AssertionError (null if nowhere). With a closed world, + * only application code creates objects of application classes or of AssertionError: the + * modelled library methods create none, and no assert of the JDK is enabled. + */ + private String assertionCreation(Set reachable) { + for (Program.Method m : reachable) { + Frame[] f = frames.get(m.key); + AbstractInsnNode[] insns = m.node.instructions.toArray(); + for (int i = 0; i < insns.length; i++) { + if (f[i] == null) continue; + String type = null; + if (insns[i].getOpcode() == NEW) type = ((TypeInsnNode) insns[i]).desc; + else if (insns[i].getOpcode() == INVOKESPECIAL && ((MethodInsnNode) insns[i]).name.equals("")) + type = ((MethodInsnNode) insns[i]).owner; + if (type != null && program.isAssignable(type, "java/lang/AssertionError")) + return m.key + ": line " + line(insns[i]) + ": creates " + type; + } + } + return null; + } + + /** + * The analysis assumes a closed world: application code only runs when application code calls + * it, with arguments the facts account for. A reachable call that may run unknown code (an + * unmodelled library method, which could call back into the application) breaks that even if a + * handler catches what it throws, so it fails the check. Returns why, or null, and collects the + * reachable methods, including the static initializers of every class reachable code may + * initialize (new, getstatic, putstatic, invokestatic). Classes are initialized in no other way: + * reflection and method handles are not modelled. + */ + private String openWorldCall(List roots, Set reachable) { + Deque work = new ArrayDeque<>(roots); + while (!work.isEmpty()) { + Program.Method m = work.poll(); + if (!reachable.add(m)) continue; + Frame[] f = frames.get(m.key); + if (f == null) return "reachable method without code: " + m.key; + AbstractInsnNode[] insns = m.node.instructions.toArray(); + for (int i = 0; i < insns.length; i++) { + if (f[i] == null) continue; + if (insns[i] instanceof InvokeDynamicInsnNode && !isStringConcat((InvokeDynamicInsnNode) insns[i])) + return m.key + ": line " + line(insns[i]) + ": invokedynamic"; + if (insns[i] instanceof LdcInsnNode && (((LdcInsnNode) insns[i]).cst instanceof Handle + || ((LdcInsnNode) insns[i]).cst instanceof ConstantDynamic)) + return m.key + ": line " + line(insns[i]) + ": ldc of a method handle or dynamic constant"; + int op = insns[i].getOpcode(); + if (op == NEW) work.addAll(initializers(((TypeInsnNode) insns[i]).desc)); + else if (op == GETSTATIC || op == PUTSTATIC) work.addAll(initializers(((FieldInsnNode) insns[i]).owner)); + else if (op == INVOKESTATIC) work.addAll(initializers(((MethodInsnNode) insns[i]).owner)); + if (!(insns[i] instanceof MethodInsnNode)) continue; + MethodInsnNode mi = (MethodInsnNode) insns[i]; + String reason = openWorldReason(mi, f[i]); + if (reason != null) return m.key + ": line " + line(mi) + ": " + reason; + Program.Targets t = mi.owner.startsWith(Program.VERIFIER_PACKAGE) ? null : targets(mi); + if (t != null) work.addAll(t.methods); + } + } + return null; + } + + /** + * The static initializers that may run when a class is initialized: its own and those of its + * application supertypes (a superset of what the JVM initializes, see JVMS 5.5). + */ + private List initializers(String className) { + List out = new ArrayList<>(); + if (!program.isApp(className)) return out; + for (String t : program.appSupertypes(className)) { + Program.Method clinit = program.method(t, "", "()V"); + if (clinit != null) out.add(clinit); + } + return out; + } + + /** Follows the may-throw reasons from a root down to the instruction that may throw. */ + private static String explain(String key, Map mayThrow) { + StringBuilder sb = new StringBuilder(); + Set seen = new HashSet<>(); + String k = key; + while (k != null && seen.add(k)) { + String reason = mayThrow.get(k); + if (sb.length() > 0) sb.append(" <- "); + sb.append(k).append(": ").append(reason); + k = null; + int i = reason.indexOf("call to "); + if (i >= 0) { + String callee = reason.substring(i + "call to ".length()); + if (mayThrow.containsKey(callee)) k = callee; + } + } + return sb.toString(); + } + + // --------------------------------------------------------------------------------------------- + // Facts + + private void joinInto(Map map, String key, AbsValue v) { + v = v.escaped(); + AbsValue old = map.get(key); + AbsValue joined = old == null ? v : AbsValue.join(old, v); + if (!joined.equals(old)) { + map.put(key, joined); + changed = true; + } + } + + private void collectFacts(Program.Method m, Frame[] f) { + AbstractInsnNode[] insns = m.node.instructions.toArray(); + for (int i = 0; i < insns.length; i++) { + Frame fr = f[i]; + if (fr == null) continue; + AbstractInsnNode insn = insns[i]; + int op = insn.getOpcode(); + if (op == PUTFIELD || op == PUTSTATIC) { + FieldInsnNode fi = (FieldInsnNode) insn; + String key = fieldKey(fi); + if (key != null && isReferenceDesc(fi.desc)) joinInto(fieldFacts, key, top(fr, 0)); + } else if (op == ARETURN) { + joinInto(returnFacts, m.key, top(fr, 0)); + } else if (op >= IRETURN && op <= DRETURN) { + joinInto(returnFacts, m.key, top(fr, 0)); + } else if (insn instanceof MethodInsnNode) { + MethodInsnNode mi = (MethodInsnNode) insn; + Program.Targets t = targets(mi); + if (t == null) continue; + Type[] args = Type.getArgumentTypes(mi.desc); + for (Program.Method target : t.methods) { + AbsValue[] params = parameterFacts.computeIfAbsent(target.key, k -> new AbsValue[args.length]); + for (int a = 0; a < args.length; a++) { + AbsValue v = top(fr, args.length - 1 - a).escaped(); + AbsValue joined = params[a] == null ? v : AbsValue.join(params[a], v); + if (!joined.equals(params[a])) { + params[a] = joined; + changed = true; + } + } + } + } + } + } + + private static boolean isReferenceDesc(String desc) { + return desc.startsWith("L") || desc.startsWith("["); + } + + private static AbsValue top(Frame f, int depth) { + return f.getStack(f.getStackSize() - 1 - depth); + } + + /** The key of the application field a field instruction accesses, or null for a library field. */ + private String fieldKey(FieldInsnNode fi) { + String[] decl = new String[1]; + FieldNode f = program.resolveField(fi.owner, fi.name, fi.desc, decl); + return f == null ? null : decl[0] + "." + f.name + ":" + f.desc; + } + + @Override + public AbsValue field(FieldInsnNode insn) { + if (insn.getOpcode() == GETSTATIC && insn.owner.equals("java/lang/System") + && (insn.name.equals("out") || insn.name.equals("err")) && insn.desc.equals("Ljava/io/PrintStream;")) { + // Non-null, and exactly a PrintStream: System.setOut/setErr are not modelled, so no + // program that passes the check can replace them. + return AbsValue.exact("java/io/PrintStream"); + } + String key = fieldKey(insn); + if (key == null) return FactInterpreter.ofType(Type.getType(insn.desc)); + AbsValue v = fieldFacts.get(key); + if (v == null) return AbsValue.bottom(Type.getType(insn.desc).getSize()); + return v; + } + + @Override + public AbsValue parameter(Program.Method method, int index, Type type) { + AbsValue[] params = parameterFacts.get(method.key); + if (method == entry) { + // The launcher passes a non-null array; the program may call main itself as well. + AbsValue launcher = AbsValue.array(MAIN_DESC.substring(1, MAIN_DESC.indexOf(')')), null, null); + return params == null || params[index] == null ? launcher : AbsValue.join(launcher, params[index]); + } + if (params == null || params[index] == null) return AbsValue.bottom(type.getSize()); + return params[index]; + } + + @Override + public AbsValue callResult(MethodInsnNode insn, List args) { + Type ret = Type.getReturnType(insn.desc); + if (ret.getSort() == Type.VOID) return null; + if (insn.owner.startsWith(Program.VERIFIER_PACKAGE)) { + if (insn.name.equals("nondetString")) return AbsValue.exact("java/lang/String"); + return FactInterpreter.ofType(ret); + } + if (insn.getOpcode() == INVOKESTATIC && insn.name.equals("values") && program.isEnum(insn.owner) + && insn.desc.equals("()[L" + insn.owner + ";")) { + return AbsValue.array("[L" + insn.owner + ";", null, insn.owner); + } + if (insn.name.equals("ordinal") && insn.desc.equals("()I") && insn.getOpcode() != INVOKESTATIC) { + return program.isEnum(insn.owner) ? AbsValue.PRIM1.withOrdinalOf(insn.owner) : AbsValue.PRIM1; + } + Program.Targets t = targets(insn); + if (t == null) return FactInterpreter.ofType(ret); + AbsValue result = null; + for (Program.Method m : t.methods) { + AbsValue r = returnFacts.get(m.key); + if (r == null) r = AbsValue.bottom(ret.getSize()); + result = result == null ? r : AbsValue.join(result, r); + } + for (String lib : t.libraryMethods) { + AbsValue r = libraryResult(lib, ret, args); + result = result == null ? r : AbsValue.join(result, r); + } + return result == null ? FactInterpreter.ofType(ret) : result; + } + + private static AbsValue libraryResult(String lib, Type ret, List args) { + String[] parts = splitMethod(lib); + JdkModel.Entry e = JdkModel.lookup(parts[0], parts[1], parts[2]); + if (e == null) return FactInterpreter.ofType(ret); + switch (e.returns) { + case NONNULL_EXACT: + return AbsValue.exact(ret.getInternalName()); + case RECEIVER: + return args.get(0).escaped(); + case CLONE: { + AbsValue r = args.get(0); + return new AbsValue(AbsValue.Kind.NONNULL, 1, null, r.length, r.lengthOf, null, r.exactType, + r.exactType != null ? r.exactType : r.staticType, false); + } + default: + return FactInterpreter.ofType(ret); + } + } + + /** owner, name and descriptor of "owner.name(desc)". */ + static String[] splitMethod(String key) { + int paren = key.indexOf('('); + int dot = key.lastIndexOf('.', paren); + return new String[] {key.substring(0, dot), key.substring(dot + 1, paren), key.substring(paren)}; + } + + /** The possible targets of a call, or null if they cannot be determined. Not for Verifier calls. */ + private Program.Targets targets(MethodInsnNode mi) { + if (unknownTargets.contains(mi)) return null; + Program.Targets cached = targetCache.get(mi); + if (cached != null) return cached; + Program.Targets t = computeTargets(mi); + if (t == null) unknownTargets.add(mi); + else targetCache.put(mi, t); + return t; + } + + private Program.Targets computeTargets(MethodInsnNode mi) { + if (mi.owner.startsWith(Program.VERIFIER_PACKAGE)) return new Program.Targets(); + int op = mi.getOpcode(); + if ((op == INVOKEVIRTUAL || op == INVOKEINTERFACE) && program.isApp(mi.owner)) { + return program.virtualTargets(mi.owner, mi.name, mi.desc); + } + Program.Targets t = new Program.Targets(); + if (op == INVOKEVIRTUAL || op == INVOKEINTERFACE) { + t.libraryMethods.add(mi.owner + "." + mi.name + mi.desc); + return t; + } + if (op == INVOKESPECIAL && mi.name.equals("")) { + Program.Method m = program.method(mi.owner, mi.name, mi.desc); + if (m != null) t.methods.add(m); + else if (program.isApp(mi.owner)) return null; + else t.libraryMethods.add(mi.owner + "." + mi.name + mi.desc); + return t; + } + String[] lib = new String[1]; + Program.Method m = program.resolveInChain(mi.owner, mi.name, mi.desc, lib); + if (m != null) { + if (!m.hasCode()) return null; + t.methods.add(m); + } else if (lib[0] != null) { + t.libraryMethods.add(lib[0] + "." + mi.name + mi.desc); + } else { + return null; + } + return t; + } + + // --------------------------------------------------------------------------------------------- + // Fields initialized before any read + + /** + * Finds the reference fields that hold a value from their first assignment on, before any code + * that could read them runs. Such a field never shows its default null, so its fact is the join + * of the values stored into it only. + * + *

A static field qualifies if its class initializer assigns it in its straight-line prefix + * (no jumps and no exception handlers), and nothing that prefix may execute (the methods it calls + * and the static initializers of the classes it, its callees or the superclasses touch, all + * transitively) reads a field of that name. An instance field qualifies if every constructor of + * its class assigns it on the receiver in such a prefix, which neither lets the receiver escape + * nor may execute a read of the field. An object is only reachable through the receiver of its + * constructor until it escapes, so no read can come from elsewhere; a constructor that throws + * before the assignment leaves no reference to its object behind. + */ + private void computeInitializedBeforeRead() throws AnalyzerException { + for (ClassNode c : program.appClasses()) { + for (FieldNode f : c.fields) { + if (!isReferenceDesc(f.desc)) continue; + String key = c.name + "." + f.name + ":" + f.desc; + boolean isStatic = (f.access & ACC_STATIC) != 0; + if (isStatic ? staticFieldInitializedFirst(c, f) : instanceFieldInitializedFirst(c, f)) { + initializedBeforeRead.add(key); + } + } + } + } + + /** The instructions every execution of the method runs first, in order, up to the first jump. */ + private static List straightLinePrefix(MethodNode m) { + List out = new ArrayList<>(); + for (AbstractInsnNode insn = m.instructions.getFirst(); insn != null; insn = insn.getNext()) { + if (insn.getOpcode() < 0) continue; // labels, line numbers, frames + int op = insn.getOpcode(); + if (insn instanceof JumpInsnNode || insn instanceof TableSwitchInsnNode || insn instanceof LookupSwitchInsnNode + || op == ATHROW || op == RET || (op >= IRETURN && op <= RETURN)) break; + out.add(insn); + } + return out; + } + + private static boolean coveredByHandler(MethodNode m, AbstractInsnNode insn) { + int idx = m.instructions.indexOf(insn); + for (TryCatchBlockNode tcb : m.tryCatchBlocks) { + if (m.instructions.indexOf(tcb.start) <= idx && idx < m.instructions.indexOf(tcb.end)) return true; + } + return false; + } + + private boolean staticFieldInitializedFirst(ClassNode c, FieldNode f) { + Program.Method clinit = program.method(c.name, "", "()V"); + if (clinit == null) return false; + List before = new ArrayList<>(); + for (AbstractInsnNode insn : straightLinePrefix(clinit.node)) { + if (coveredByHandler(clinit.node, insn)) return false; + if (insn.getOpcode() == PUTSTATIC && assigns((FieldInsnNode) insn, c, f)) { + // The supertypes are initialized before the class; the class itself is being + // initialized already, so touching it again does not rerun its initializer. + Set supertypes = new HashSet<>(program.appSupertypes(c.name)); + supertypes.remove(c.name); + return !mayRead(before, supertypes, Set.of(c.name), f.name, f.desc, true); + } + before.add(insn); + } + return false; + } + + private boolean instanceFieldInitializedFirst(ClassNode c, FieldNode f) throws AnalyzerException { + boolean any = false; + for (MethodNode mn : c.methods) { + if (!mn.name.equals("")) continue; + any = true; + Program.Method ctor = program.method(c.name, mn.name, mn.desc); + Frame[] fr = new Analyzer<>(new FactInterpreter(UNKNOWN_FACTS, ctor)).analyze(c.name, mn); + List before = new ArrayList<>(); + boolean assigned = false; + for (AbstractInsnNode insn : straightLinePrefix(mn)) { + if (coveredByHandler(mn, insn)) return false; + Frame frame = fr[mn.instructions.indexOf(insn)]; + if (frame == null) return false; + if (insn.getOpcode() == PUTFIELD && assigns((FieldInsnNode) insn, c, f) && top(frame, 1).isThis + && !top(frame, 0).mayBeThis) { + assigned = true; + break; + } + if (letsReceiverEscape(insn, frame, new HashSet<>())) return false; + before.add(insn); + } + if (!assigned || mayRead(before, Set.of(), Set.of(), f.name, f.desc, false)) return false; + } + return any; + } + + private boolean assigns(FieldInsnNode fi, ClassNode c, FieldNode f) { + String[] decl = new String[1]; + FieldNode target = program.resolveField(fi.owner, fi.name, fi.desc, decl); + return target == f && c.name.equals(decl[0]); + } + + /** + * Whether the instruction hands the receiver (this) to code that could keep it: as an argument + * or receiver of a call, or as a value stored into a field or an array. The superclass or + * delegated constructor may take it if its own code does not let it escape either. + */ + private boolean letsReceiverEscape(AbstractInsnNode insn, Frame frame, Set checking) { + int op = insn.getOpcode(); + if (op == PUTFIELD || op == PUTSTATIC || op == AASTORE || op == ARETURN || op == ATHROW) { + return top(frame, 0).mayBeThis; + } + if (insn instanceof InvokeDynamicInsnNode) { + int n = Type.getArgumentTypes(((InvokeDynamicInsnNode) insn).desc).length; + for (int i = 0; i < n; i++) if (top(frame, i).mayBeThis) return true; + return false; + } + if (!(insn instanceof MethodInsnNode)) return false; + MethodInsnNode mi = (MethodInsnNode) insn; + int n = Type.getArgumentTypes(mi.desc).length; + for (int i = 0; i < n; i++) if (top(frame, i).mayBeThis) return true; + if (op == INVOKESTATIC || !top(frame, n).mayBeThis) return false; + if (op != INVOKESPECIAL || !mi.name.equals("") || !top(frame, n).isThis) return true; + if (mi.owner.equals("java/lang/Object") && mi.desc.equals("()V")) return false; + if (mi.owner.equals("java/lang/Enum") && mi.desc.equals("(Ljava/lang/String;I)V")) return false; + Program.Method ctor = program.method(mi.owner, mi.name, mi.desc); + if (ctor == null || !ctor.hasCode() || !checking.add(ctor.key)) return true; + try { + Frame[] fr = new Analyzer<>(new FactInterpreter(UNKNOWN_FACTS, ctor)).analyze(ctor.owner.name, ctor.node); + AbstractInsnNode[] insns = ctor.node.instructions.toArray(); + for (int i = 0; i < insns.length; i++) { + if (fr[i] != null && letsReceiverEscape(insns[i], fr[i], checking)) return true; + } + return false; + } catch (AnalyzerException e) { + return true; + } + } + + /** + * Whether running the given instructions may read a field with this name and descriptor: + * directly, in a method they may call (transitively), or in a static initializer they may + * trigger (except those of the classes being initialized already). Unknown callees count as + * reads. + */ + private boolean mayRead(List start, Set initializedClasses, Set initializing, + String name, String desc, boolean isStatic) { + Deque work = new ArrayDeque<>(); + Set seen = new HashSet<>(); + Set classes = new HashSet<>(initializing); + Deque pendingClasses = new ArrayDeque<>(initializedClasses); + List insns = new ArrayList<>(start); + while (true) { + for (AbstractInsnNode insn : insns) { + int op = insn.getOpcode(); + if (insn instanceof FieldInsnNode) { + FieldInsnNode fi = (FieldInsnNode) insn; + if ((op == (isStatic ? GETSTATIC : GETFIELD)) && fi.name.equals(name) && fi.desc.equals(desc)) return true; + if (op == GETSTATIC || op == PUTSTATIC) pendingClasses.add(fi.owner); + } else if (op == NEW) { + pendingClasses.add(((TypeInsnNode) insn).desc); + } else if (insn instanceof InvokeDynamicInsnNode) { + if (!isStringConcat((InvokeDynamicInsnNode) insn)) return true; + } else if (insn instanceof MethodInsnNode) { + MethodInsnNode mi = (MethodInsnNode) insn; + if (mi.owner.startsWith(Program.VERIFIER_PACKAGE)) { + if (!isModelledVerifierCall(mi)) return true; + continue; + } + if (op == INVOKESTATIC) pendingClasses.add(mi.owner); + Program.Targets t = targets(mi); + if (t == null) return true; + for (String lib : t.libraryMethods) { + String[] p = splitMethod(lib); + JdkModel.Entry e = JdkModel.lookup(p[0], p[1], p[2]); + // Without values, a library call is only known not to call back if its model + // has no argument condition about callbacks and no override can intervene. + if (e == null || e.condition == JdkModel.Condition.STRING_ARGS) return true; + if (op != INVOKESTATIC && !program.isApp(mi.owner) && !JdkModel.cannotBeOverridden(p[0], p[1], p[2]) + && !e.exactReceiver) return true; + } + for (Program.Method m : t.methods) if (seen.add(m.key)) work.add(m); + } + } + while (!pendingClasses.isEmpty()) { + String cls = pendingClasses.poll(); + if (cls.startsWith("[") || !program.isApp(cls)) continue; + for (String sup : program.appSupertypes(cls)) { + if (!classes.add(sup)) continue; + Program.Method clinit = program.method(sup, "", "()V"); + if (clinit != null && seen.add(clinit.key)) work.add(clinit); + } + } + if (work.isEmpty()) return false; + Program.Method m = work.poll(); + insns = new ArrayList<>(); + if (!m.hasCode()) return true; + for (AbstractInsnNode insn : m.node.instructions) insns.add(insn); + } + } + + /** Facts for analyses that only need the receiver flag: nothing is known about other values. */ + private static final FactInterpreter.Facts UNKNOWN_FACTS = new FactInterpreter.Facts() { + @Override + public AbsValue field(FieldInsnNode insn) { + return FactInterpreter.ofType(Type.getType(insn.desc)); + } + + @Override + public AbsValue parameter(Program.Method method, int index, Type type) { + return FactInterpreter.ofType(type); + } + + @Override + public AbsValue callResult(MethodInsnNode insn, List args) { + return FactInterpreter.ofType(Type.getReturnType(insn.desc)); + } + }; + + // --------------------------------------------------------------------------------------------- + // Checking instructions + + static boolean isStringConcat(InvokeDynamicInsnNode indy) { + if (!indy.bsm.getOwner().equals("java/lang/invoke/StringConcatFactory")) return false; + for (Type t : Type.getArgumentTypes(indy.desc)) { + if (t.getSort() == Type.OBJECT && !t.getInternalName().equals("java/lang/String")) return false; + if (t.getSort() == Type.ARRAY) return false; + } + return true; + } + + private static boolean isModelledVerifierCall(MethodInsnNode mi) { + return mi.owner.equals(Program.VERIFIER_PACKAGE + "Verifier") && mi.getOpcode() == INVOKESTATIC + && (mi.name.equals("assume") || (mi.name.startsWith("nondet") && !mi.name.equals("nondetObject"))); + } + + /** + * Why a RuntimeException may escape the method, or null if none can, given the methods already + * known to let one escape. + */ + private String escapingException(Program.Method m, Map mayThrow) { + Frame[] f = frames.get(m.key); + MethodNode mn = m.node; + AbstractInsnNode[] insns = mn.instructions.toArray(); + int n = insns.length; + List handlers = mn.tryCatchBlocks; + boolean[][] covers = new boolean[handlers.size()][n]; + for (int h = 0; h < handlers.size(); h++) { + TryCatchBlockNode tcb = handlers.get(h); + int s = mn.instructions.indexOf(tcb.start), e = mn.instructions.indexOf(tcb.end); + for (int i = s; i < e; i++) covers[h][i] = true; + } + // An instruction may raise a RuntimeException; handlers that may catch one. Rethrowing a + // caught exception raises one only if its handler may have caught one: iterate. + String[] raises = new String[n]; + boolean[] handlerCatches = new boolean[handlers.size()]; + boolean grew = true; + while (grew) { + grew = false; + for (int i = 0; i < n; i++) { + if (f[i] == null || raises[i] != null) continue; + String r = raises(m, insns[i], f[i], mayThrow, handlerCatches); + if (r == null) continue; + raises[i] = r; + grew = true; + // Over-approximated: every handler that covers it may have caught it. + for (int h = 0; h < handlers.size(); h++) if (covers[h][i]) handlerCatches[h] = true; + } + } + for (int i = 0; i < n; i++) { + if (raises[i] == null) continue; + boolean caught = false; + for (int h = 0; h < handlers.size() && !caught; h++) { + caught = covers[h][i] && catchesEveryRuntimeException(handlers.get(h).type, insns[i]); + } + if (!caught) return "line " + line(insns[i]) + ": " + raises[i]; + } + return null; + } + + private static int line(AbstractInsnNode insn) { + for (AbstractInsnNode p = insn; p != null; p = p.getPrevious()) { + if (p instanceof LineNumberNode) return ((LineNumberNode) p).line; + } + return -1; + } + + private static boolean catchesEveryRuntimeException(String type, AbstractInsnNode insn) { + if (type == null || type.equals("java/lang/Throwable") || type.equals("java/lang/Exception") + || type.equals("java/lang/RuntimeException")) return true; + // Integer division can only throw ArithmeticException. + int op = insn.getOpcode(); + return type.equals("java/lang/ArithmeticException") && (op == IDIV || op == IREM || op == LDIV || op == LREM); + } + + private String raises(Program.Method m, AbstractInsnNode insn, Frame f, Map mayThrow, + boolean[] handlerCatches) { + int op = insn.getOpcode(); + switch (op) { + case IDIV: case IREM: case LDIV: case LREM: { + AbsValue d = top(f, 0); + if (d.isBottom() || (d.constant != null && d.constant != 0)) return null; + return "division by a value that may be zero"; + } + case IALOAD: case LALOAD: case FALOAD: case DALOAD: case BALOAD: case CALOAD: case SALOAD: case AALOAD: + return arrayAccess(top(f, 1), top(f, 0)); + case IASTORE: case LASTORE: case FASTORE: case DASTORE: case BASTORE: case CASTORE: case SASTORE: + return arrayAccess(top(f, 2), top(f, 1)); + case AASTORE: { + String r = arrayAccess(top(f, 2), top(f, 1)); + if (r != null) return r; + AbsValue arr = top(f, 2), v = top(f, 0); + if (v.isNull() || v.isBottom() || arr.isBottom()) return null; + String elem = arr.exactType != null ? arr.exactType.substring(1) : null; + if (elem != null && elem.startsWith("L")) elem = elem.substring(1, elem.length() - 1); + if (elem != null && program.isAssignable(trustedType(v), elem)) return null; + return "array store of a value that may not fit the array"; + } + case ARRAYLENGTH: + return top(f, 0).isNonNull() ? null : "length of an array that may be null"; + case NEWARRAY: case ANEWARRAY: + return nonNegative(top(f, 0)) ? null : "array size may be negative"; + case MULTIANEWARRAY: { + int dims = ((MultiANewArrayInsnNode) insn).dims; + for (int i = 0; i < dims; i++) if (!nonNegative(top(f, i))) return "array size may be negative"; + return null; + } + case GETFIELD: + return top(f, 0).isNonNull() ? null : "field read on a reference that may be null"; + case PUTFIELD: + return top(f, 1).isNonNull() ? null : "field write on a reference that may be null"; + case CHECKCAST: { + AbsValue v = top(f, 0); + if (v.isNull() || v.isBottom() || program.isAssignable(trustedType(v), ((TypeInsnNode) insn).desc)) return null; + return "cast that may fail"; + } + case ATHROW: { + AbsValue v = top(f, 0); + if (v.isBottom()) return null; + if (v.caughtBy >= 0) return handlerCatches[v.caughtBy] ? "rethrow of a caught RuntimeException" : null; + if (v.exactType != null && isNeverRuntimeException(v.exactType)) return null; + return "throw of a value that may be a RuntimeException"; + } + case MONITORENTER: case MONITOREXIT: + return "monitor instruction"; + case INVOKEDYNAMIC: + return isStringConcat((InvokeDynamicInsnNode) insn) ? null : "invokedynamic"; + case INVOKEVIRTUAL: case INVOKESPECIAL: case INVOKESTATIC: case INVOKEINTERFACE: + return call((MethodInsnNode) insn, f, mayThrow); + default: + return null; + } + } + + private String arrayAccess(AbsValue arr, AbsValue idx) { + if (arr.isBottom() || idx.isBottom()) return null; + if (!arr.isNonNull()) return "access to an array that may be null"; + if (arr.length != null && idx.constant != null && idx.constant >= 0 && idx.constant < arr.length) return null; + if (arr.lengthOf != null && arr.lengthOf.equals(idx.ordinalOf)) return null; + return "array index that may be out of bounds"; + } + + private static boolean nonNegative(AbsValue count) { + return count.isBottom() || (count.constant != null && count.constant >= 0) || count.lengthOf != null; + } + + /** A type every non-null value of the slot is an instance of, if one is reliably known. */ + private String trustedType(AbsValue v) { + if (v.exactType != null) return v.exactType; + String t = v.staticType; + if (t == null) return null; + // The verifier does not check interface types, so only class and array types are trusted. + if (t.startsWith("[") || t.equals("java/lang/Object") || JdkModel.isFinalClass(t)) return t; + ClassNode c = program.clazz(t); + if (c != null && program.isApp(t) && (c.access & ACC_INTERFACE) == 0) return t; + return null; + } + + /** The runtime class is a Throwable that is not a RuntimeException. */ + private boolean isNeverRuntimeException(String exactType) { + List chain = program.superChain(exactType); + String root = chain.get(chain.size() - 1); + if (program.isApp(root)) return false; + return root.equals("java/lang/AssertionError") || root.equals("java/lang/Error") + || root.equals("java/lang/Exception") || root.equals("java/lang/Throwable"); + } + + /** + * Why the call may run code the analysis does not know (null if it cannot): an unresolved or + * unmodelled callee, or a library method whose model does not cover this receiver or these + * arguments. + */ + private String openWorldReason(MethodInsnNode mi, Frame f) { + int op = mi.getOpcode(); + if (mi.owner.startsWith(Program.VERIFIER_PACKAGE)) { + return isModelledVerifierCall(mi) ? null : "call to " + mi.owner + "." + mi.name; + } + Type[] args = Type.getArgumentTypes(mi.desc); + AbsValue receiver = op == INVOKESTATIC ? null : top(f, args.length); + if (receiver != null && receiver.isBottom()) return null; + Program.Targets t = targets(mi); + if (t == null) return "unresolved call to " + mi.owner + "." + mi.name + mi.desc; + for (String lib : t.libraryMethods) { + String[] p = splitMethod(lib); + JdkModel.Entry e = JdkModel.lookup(p[0], p[1], p[2]); + if (e == null) return "library call to " + lib; + boolean viaLibraryOwner = !program.isApp(mi.owner) && op != INVOKESTATIC && !(op == INVOKESPECIAL && mi.name.equals("")); + boolean exactOwner = receiver != null && p[0].equals(receiver.exactType); + if (viaLibraryOwner && !JdkModel.cannotBeOverridden(p[0], p[1], p[2]) && !exactOwner) + return "library call that an override may intercept: " + lib; + if (e.exactReceiver && !exactOwner) return "library call on a receiver of unknown class: " + lib; + if (e.condition != JdkModel.Condition.STRING_ARGS) continue; + for (int a = 0; a < args.length; a++) { + if (args[a].getSort() != Type.OBJECT && args[a].getSort() != Type.ARRAY) continue; + AbsValue v = top(f, args.length - 1 - a); + if (!v.isNull() && !v.isBottom() + && (trustedType(v) == null || !JdkModel.SAFE_TO_STRING.contains(trustedType(v)))) + return "library call that may call toString() of the argument: " + lib; + } + } + return null; + } + + private String call(MethodInsnNode mi, Frame f, Map mayThrow) { + String open = openWorldReason(mi, f); + if (open != null) return open; + if (mi.owner.startsWith(Program.VERIFIER_PACKAGE)) return null; + int op = mi.getOpcode(); + Type[] args = Type.getArgumentTypes(mi.desc); + AbsValue receiver = op == INVOKESTATIC ? null : top(f, args.length); + if (receiver != null && !receiver.isNonNull()) return "call on a reference that may be null: " + mi.owner + "." + mi.name; + if (receiver != null && receiver.isBottom()) return null; + Program.Targets t = targets(mi); + for (String lib : t.libraryMethods) { + String[] p = splitMethod(lib); + JdkModel.Entry e = JdkModel.lookup(p[0], p[1], p[2]); + if (e.condition != JdkModel.Condition.NONNULL_ARGS) continue; + for (int a = 0; a < args.length; a++) { + if (args[a].getSort() != Type.OBJECT && args[a].getSort() != Type.ARRAY) continue; + if (!top(f, args.length - 1 - a).isNonNull()) + return "library call with an argument that may be null: " + lib; + } + } + for (Program.Method target : t.methods) { + if (mayThrow.containsKey(target.key)) return "call to " + target.key; + } + return null; + } + + // --------------------------------------------------------------------------------------------- + + /** + * Command line: the classpath entries of the program. Prints one JSON object with the result. + */ + public static void main(String[] args) { + List cp = new ArrayList<>(); + for (String a : args) for (String p : a.split(java.io.File.pathSeparator)) if (!p.isEmpty()) cp.add(Path.of(p)); + Result r = check(cp); + System.out.println("{\"exception_free\": " + r.exceptionFree + ", \"reason\": \"" + escape(r.reason) + + "\", \"assertion_free\": " + r.assertionFree + ", \"assertion_reason\": \"" + escape(r.assertionReason) + "\"}"); + } + + private static String escape(String s) { + StringBuilder sb = new StringBuilder(); + for (char c : s.toCharArray()) { + if (c == '"' || c == '\\') sb.append('\\').append(c); + else if (c < 0x20) sb.append(String.format("\\u%04x", (int) c)); + else sb.append(c); + } + return sb.toString(); + } +} diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/staticcheck/FactInterpreter.java b/symbolic-executor/src/main/java/de/uzl/its/swat/staticcheck/FactInterpreter.java new file mode 100644 index 00000000..71fb8bff --- /dev/null +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/staticcheck/FactInterpreter.java @@ -0,0 +1,277 @@ +package de.uzl.its.swat.staticcheck; + +import java.util.List; +import org.objectweb.asm.Handle; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.*; +import org.objectweb.asm.tree.analysis.AnalyzerException; +import org.objectweb.asm.tree.analysis.Frame; +import org.objectweb.asm.tree.analysis.Interpreter; + +/** + * Computes {@link AbsValue}s for one method, using the facts the check has collected so far for + * fields, parameters and the results of calls. + */ +final class FactInterpreter extends Interpreter implements Opcodes { + + /** Facts about the rest of the program, as far as they are known. */ + interface Facts { + AbsValue field(FieldInsnNode insn); + + /** The value of parameter number {@code index} (not counting the receiver) of the method. */ + AbsValue parameter(Program.Method method, int index, Type type); + + AbsValue callResult(MethodInsnNode insn, List args); + } + + private final Facts facts; + private final Program.Method method; + private final int[] localToParameter; + + FactInterpreter(Facts facts, Program.Method method) { + super(ASM9); + this.facts = facts; + this.method = method; + Type[] params = Type.getArgumentTypes(method.node.desc); + int locals = method.isStatic() ? 0 : 1; + for (Type t : params) locals += t.getSize(); + localToParameter = new int[locals]; + java.util.Arrays.fill(localToParameter, -1); + int local = method.isStatic() ? 0 : 1; + for (int i = 0; i < params.length; i++) { + localToParameter[local] = i; + local += params[i].getSize(); + } + } + + static AbsValue ofType(Type type) { + if (type == null) return AbsValue.UNKNOWN1; + switch (type.getSort()) { + case Type.VOID: + return null; + case Type.LONG: + case Type.DOUBLE: + return AbsValue.PRIM2; + case Type.OBJECT: + case Type.ARRAY: + return AbsValue.ref(type.getInternalName()); + case Type.METHOD: + return AbsValue.UNKNOWN1; + default: + return AbsValue.PRIM1; + } + } + + @Override + public AbsValue newValue(Type type) { + return ofType(type); + } + + @Override + public AbsValue newParameterValue(boolean isInstanceMethod, int local, Type type) { + if (isInstanceMethod && local == 0) return AbsValue.nonNull(type.getInternalName()).withThis(); + int index = local < localToParameter.length ? localToParameter[local] : -1; + if (index < 0) return ofType(type); + return facts.parameter(method, index, type); + } + + @Override + public AbsValue newEmptyValue(int local) { + return AbsValue.UNKNOWN1; + } + + @Override + public AbsValue newExceptionValue(TryCatchBlockNode tryCatch, Frame handlerFrame, Type exceptionType) { + return AbsValue.caught(exceptionType.getInternalName(), method.node.tryCatchBlocks.indexOf(tryCatch)); + } + + @Override + public AbsValue newOperation(AbstractInsnNode insn) throws AnalyzerException { + switch (insn.getOpcode()) { + case ACONST_NULL: + return AbsValue.NULL; + case ICONST_M1: case ICONST_0: case ICONST_1: case ICONST_2: case ICONST_3: case ICONST_4: case ICONST_5: + return AbsValue.constant(1, insn.getOpcode() - ICONST_0); + case LCONST_0: case LCONST_1: + return AbsValue.constant(2, insn.getOpcode() - LCONST_0); + case FCONST_0: case FCONST_1: case FCONST_2: + return AbsValue.PRIM1; + case DCONST_0: case DCONST_1: + return AbsValue.PRIM2; + case BIPUSH: case SIPUSH: + return AbsValue.constant(1, ((IntInsnNode) insn).operand); + case LDC: { + Object c = ((LdcInsnNode) insn).cst; + if (c instanceof Integer) return AbsValue.constant(1, (Integer) c); + if (c instanceof Long) return AbsValue.constant(2, (Long) c); + if (c instanceof Float) return AbsValue.PRIM1; + if (c instanceof Double) return AbsValue.PRIM2; + if (c instanceof String) return AbsValue.exact("java/lang/String"); + if (c instanceof Type) { + int sort = ((Type) c).getSort(); + if (sort == Type.METHOD) return AbsValue.exact("java/lang/invoke/MethodType"); + return AbsValue.exact("java/lang/Class"); + } + if (c instanceof Handle) return AbsValue.nonNull("java/lang/invoke/MethodHandle"); + return AbsValue.UNKNOWN1; + } + case JSR: + return AbsValue.UNKNOWN1; + case GETSTATIC: + return facts.field((FieldInsnNode) insn); + case NEW: + return AbsValue.exact(((TypeInsnNode) insn).desc); + default: + throw new AnalyzerException(insn, "Unexpected opcode"); + } + } + + @Override + public AbsValue copyOperation(AbstractInsnNode insn, AbsValue value) { + return value; + } + + @Override + public AbsValue unaryOperation(AbstractInsnNode insn, AbsValue value) throws AnalyzerException { + switch (insn.getOpcode()) { + case INEG: + return value.constant != null ? AbsValue.constant(1, (int) -value.constant) : AbsValue.PRIM1; + case IINC: + return value.constant != null + ? AbsValue.constant(1, (int) (value.constant + ((IincInsnNode) insn).incr)) : AbsValue.PRIM1; + case L2I: + return value.constant != null ? AbsValue.constant(1, (int) (long) value.constant) : AbsValue.PRIM1; + case I2L: + return value.constant != null ? AbsValue.constant(2, value.constant) : AbsValue.PRIM2; + case I2B: + return value.constant != null ? AbsValue.constant(1, (byte) (long) value.constant) : AbsValue.PRIM1; + case I2C: + return value.constant != null ? AbsValue.constant(1, (char) (long) value.constant) : AbsValue.PRIM1; + case I2S: + return value.constant != null ? AbsValue.constant(1, (short) (long) value.constant) : AbsValue.PRIM1; + case LNEG: + return value.constant != null ? AbsValue.constant(2, -value.constant) : AbsValue.PRIM2; + case FNEG: case I2F: case L2F: case D2F: case F2I: case D2I: + return AbsValue.PRIM1; + case DNEG: case I2D: case L2D: case F2D: case F2L: case D2L: + return AbsValue.PRIM2; + case IFEQ: case IFNE: case IFLT: case IFGE: case IFGT: case IFLE: + case TABLESWITCH: case LOOKUPSWITCH: + case IRETURN: case LRETURN: case FRETURN: case DRETURN: case ARETURN: + case PUTSTATIC: case ATHROW: case MONITORENTER: case MONITOREXIT: case IFNULL: case IFNONNULL: + return null; + case GETFIELD: + return facts.field((FieldInsnNode) insn); + case NEWARRAY: { + String desc = "[" + primitiveDescriptor(((IntInsnNode) insn).operand); + return AbsValue.array(desc, nonNegative(value), value.lengthOf); + } + case ANEWARRAY: { + String elem = ((TypeInsnNode) insn).desc; + String desc = "[" + (elem.startsWith("[") ? elem : "L" + elem + ";"); + return AbsValue.array(desc, nonNegative(value), value.lengthOf); + } + case ARRAYLENGTH: { + AbsValue len = value.length != null ? AbsValue.constant(1, value.length) : AbsValue.PRIM1; + return value.lengthOf != null ? len.withLengthOf(value.lengthOf) : len; + } + case CHECKCAST: + return value; + case INSTANCEOF: + return AbsValue.PRIM1; + default: + throw new AnalyzerException(insn, "Unexpected opcode"); + } + } + + private static Integer nonNegative(AbsValue count) { + if (count.constant == null || count.constant < 0 || count.constant > Integer.MAX_VALUE) return null; + return (int) (long) count.constant; + } + + static String primitiveDescriptor(int operand) { + switch (operand) { + case T_BOOLEAN: return "Z"; + case T_CHAR: return "C"; + case T_BYTE: return "B"; + case T_SHORT: return "S"; + case T_INT: return "I"; + case T_FLOAT: return "F"; + case T_DOUBLE: return "D"; + case T_LONG: return "J"; + default: return "?"; + } + } + + @Override + public AbsValue binaryOperation(AbstractInsnNode insn, AbsValue v1, AbsValue v2) throws AnalyzerException { + int op = insn.getOpcode(); + switch (op) { + case IALOAD: case BALOAD: case CALOAD: case SALOAD: case FALOAD: + return AbsValue.PRIM1; + case LALOAD: case DALOAD: + return AbsValue.PRIM2; + case AALOAD: { + String t = v1.staticType; + if (t != null && t.startsWith("[")) return AbsValue.ref(Type.getType(t.substring(1)).getInternalName()); + return AbsValue.ref(null); + } + case IADD: case ISUB: case IMUL: case ISHL: case ISHR: case IUSHR: case IAND: case IOR: case IXOR: + if (v1.constant != null && v2.constant != null) { + int a = (int) (long) v1.constant, b = (int) (long) v2.constant; + switch (op) { + case IADD: return AbsValue.constant(1, a + b); + case ISUB: return AbsValue.constant(1, a - b); + case IMUL: return AbsValue.constant(1, a * b); + case ISHL: return AbsValue.constant(1, a << b); + case ISHR: return AbsValue.constant(1, a >> b); + case IUSHR: return AbsValue.constant(1, a >>> b); + case IAND: return AbsValue.constant(1, a & b); + case IOR: return AbsValue.constant(1, a | b); + default: return AbsValue.constant(1, a ^ b); + } + } + return AbsValue.PRIM1; + case IDIV: case IREM: case FADD: case FSUB: case FMUL: case FDIV: case FREM: + case LCMP: case FCMPL: case FCMPG: case DCMPL: case DCMPG: + return AbsValue.PRIM1; + case LADD: case LSUB: case LMUL: case LDIV: case LREM: case LSHL: case LSHR: case LUSHR: + case LAND: case LOR: case LXOR: case DADD: case DSUB: case DMUL: case DDIV: case DREM: + return AbsValue.PRIM2; + case IF_ICMPEQ: case IF_ICMPNE: case IF_ICMPLT: case IF_ICMPGE: case IF_ICMPGT: case IF_ICMPLE: + case IF_ACMPEQ: case IF_ACMPNE: case PUTFIELD: + return null; + default: + throw new AnalyzerException(insn, "Unexpected opcode"); + } + } + + @Override + public AbsValue ternaryOperation(AbstractInsnNode insn, AbsValue v1, AbsValue v2, AbsValue v3) { + return null; + } + + @Override + public AbsValue naryOperation(AbstractInsnNode insn, List values) { + int op = insn.getOpcode(); + if (op == MULTIANEWARRAY) { + String desc = ((MultiANewArrayInsnNode) insn).desc; + return AbsValue.array(desc, nonNegative(values.get(0)), values.get(0).lengthOf); + } + if (op == INVOKEDYNAMIC) { + InvokeDynamicInsnNode indy = (InvokeDynamicInsnNode) insn; + if (ExceptionFreedomCheck.isStringConcat(indy)) return AbsValue.exact("java/lang/String"); + return ofType(Type.getReturnType(indy.desc)); + } + return facts.callResult((MethodInsnNode) insn, values); + } + + @Override + public void returnOperation(AbstractInsnNode insn, AbsValue value, AbsValue expected) {} + + @Override + public AbsValue merge(AbsValue a, AbsValue b) { + return AbsValue.join(a, b); + } +} diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/staticcheck/JdkModel.java b/symbolic-executor/src/main/java/de/uzl/its/swat/staticcheck/JdkModel.java new file mode 100644 index 00000000..79f64a51 --- /dev/null +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/staticcheck/JdkModel.java @@ -0,0 +1,281 @@ +package de.uzl.its.swat.staticcheck; + +import java.util.*; + +/** + * The library (JDK) methods {@link ExceptionFreedomCheck} lets a program call. + * + *

Every method listed here throws no RuntimeException for any arguments that meet its + * condition, and never calls back into application code: it calls no method of an argument or + * receiver that an application class could override. A call to any other library method fails the + * check. Entries are matched by owner, name and descriptor as written in the call instruction. + */ +final class JdkModel { + + enum Condition { + NONE, + /** Every reference argument is non-null. */ + NONNULL_ARGS, + /** + * Every reference argument is null or of a final library type whose toString() is one of the + * listed safe methods (the method converts its argument with String.valueOf). + */ + STRING_ARGS + } + + enum Returns { + /** Nothing known beyond the declared return type. */ + DECLARED, + /** A non-null object of exactly the declared return type. */ + NONNULL_EXACT, + /** The receiver (StringBuilder.append). */ + RECEIVER, + /** A copy of the receiver array (same runtime type and length). */ + CLONE + } + + static final class Entry { + final Condition condition; + final Returns returns; + /** The receiver must be exactly of the owner class: subclasses could override the method. */ + final boolean exactReceiver; + + Entry(Condition condition, Returns returns, boolean exactReceiver) { + this.condition = condition; + this.returns = returns; + this.exactReceiver = exactReceiver; + } + } + + /** Final classes: no subclass can override their methods. */ + private static final Set FINAL_CLASSES = Set.of( + "java/lang/String", "java/lang/StringBuilder", "java/lang/StringBuffer", "java/lang/Integer", + "java/lang/Long", "java/lang/Short", "java/lang/Byte", "java/lang/Character", "java/lang/Boolean", + "java/lang/Double", "java/lang/Float", "java/lang/Math", "java/lang/StrictMath", "java/lang/System", + "java/lang/Class"); + + /** Library types whose toString() neither throws nor calls application code. */ + static final Set SAFE_TO_STRING = Set.of( + "java/lang/String", "java/lang/Integer", "java/lang/Long", "java/lang/Short", "java/lang/Byte", + "java/lang/Character", "java/lang/Boolean", "java/lang/Double", "java/lang/Float", + "java/lang/StringBuilder", "java/lang/StringBuffer"); + + /** Math/StrictMath methods that never throw, for every overload. */ + private static final Set MATH = Set.of( + "abs", "max", "min", "sqrt", "cbrt", "pow", "exp", "expm1", "log", "log10", "log1p", "sin", "cos", + "tan", "asin", "acos", "atan", "atan2", "sinh", "cosh", "tanh", "hypot", "ceil", "floor", "rint", + "round", "signum", "toRadians", "toDegrees", "ulp", "copySign", "getExponent", "nextUp", "nextDown", + "nextAfter", "scalb", "IEEEremainder", "random", "fma"); + + private static final Map ENTRIES = new HashMap<>(); + + /** Methods declared by java/lang/Object and java/lang/Enum, the library superclasses we know. */ + private static final Map> DECLARED = new HashMap<>(); + + /** Known library supertypes, for assignability. */ + private static final Map> SUPERTYPES = new HashMap<>(); + + private static void add(String owner, Condition c, Returns r, String... methods) { + for (String m : methods) ENTRIES.put(owner + "." + m, new Entry(c, r, false)); + } + + private static void addExact(String owner, Condition c, Returns r, String... methods) { + for (String m : methods) ENTRIES.put(owner + "." + m, new Entry(c, r, true)); + } + + static { + Condition none = Condition.NONE, nonNull = Condition.NONNULL_ARGS, str = Condition.STRING_ARGS; + Returns decl = Returns.DECLARED, fresh = Returns.NONNULL_EXACT, self = Returns.RECEIVER; + + DECLARED.put("java/lang/Object", Set.of("()V", "getClass()Ljava/lang/Class;", "hashCode()I", + "equals(Ljava/lang/Object;)Z", "clone()Ljava/lang/Object;", "toString()Ljava/lang/String;", + "notify()V", "notifyAll()V", "wait()V", "wait(J)V", "wait(JI)V", "finalize()V")); + DECLARED.put("java/lang/Enum", Set.of("(Ljava/lang/String;I)V", "name()Ljava/lang/String;", + "ordinal()I", "toString()Ljava/lang/String;", "equals(Ljava/lang/Object;)Z", "hashCode()I", + "clone()Ljava/lang/Object;", "compareTo(Ljava/lang/Enum;)I", "getDeclaringClass()Ljava/lang/Class;", + "finalize()V", "describeConstable()Ljava/util/Optional;")); + + SUPERTYPES.put("java/lang/String", List.of("java/lang/Object", "java/io/Serializable", + "java/lang/Comparable", "java/lang/CharSequence")); + SUPERTYPES.put("java/lang/Enum", List.of("java/lang/Object", "java/io/Serializable", "java/lang/Comparable")); + for (String n : List.of("Integer", "Long", "Short", "Byte", "Double", "Float")) + SUPERTYPES.put("java/lang/" + n, List.of("java/lang/Number", "java/lang/Comparable")); + SUPERTYPES.put("java/lang/Number", List.of("java/lang/Object", "java/io/Serializable")); + SUPERTYPES.put("java/lang/Character", List.of("java/lang/Object", "java/io/Serializable", "java/lang/Comparable")); + SUPERTYPES.put("java/lang/Boolean", List.of("java/lang/Object", "java/io/Serializable", "java/lang/Comparable")); + SUPERTYPES.put("java/lang/AssertionError", List.of("java/lang/Error")); + SUPERTYPES.put("java/lang/Error", List.of("java/lang/Throwable")); + SUPERTYPES.put("java/lang/Exception", List.of("java/lang/Throwable")); + SUPERTYPES.put("java/lang/Throwable", List.of("java/lang/Object", "java/io/Serializable")); + + // Object and Enum: the implementations an application class may inherit. The final methods + // are safe for every receiver; the others only as the implementation a class inherits. + add("java/lang/Object", none, decl, "()V", "hashCode()I", "equals(Ljava/lang/Object;)Z"); + add("java/lang/Object", none, Returns.DECLARED, "getClass()Ljava/lang/Class;"); + add("java/lang/Enum", none, decl, "(Ljava/lang/String;I)V", "name()Ljava/lang/String;", "ordinal()I", + "toString()Ljava/lang/String;", "equals(Ljava/lang/Object;)Z", "hashCode()I", + "getDeclaringClass()Ljava/lang/Class;"); + + add("java/lang/Class", none, decl, "desiredAssertionStatus()Z"); + + // Throwables: constructing one is safe as long as the object is exactly the library class (a + // subclass could override fillInStackTrace). Throwing it is checked separately. + addExact("java/lang/AssertionError", none, decl, "()V", "(Z)V", "(C)V", "(I)V", + "(J)V", "(F)V", "(D)V", "(Ljava/lang/String;Ljava/lang/Throwable;)V"); + addExact("java/lang/AssertionError", str, decl, "(Ljava/lang/Object;)V"); + for (String t : List.of("java/lang/Error", "java/lang/Exception", "java/lang/RuntimeException")) + addExact(t, none, decl, "()V", "(Ljava/lang/String;)V"); + + // Boxes and their static helpers (no parsing: that throws NumberFormatException). + for (String[] b : new String[][] {{"Integer", "I"}, {"Long", "J"}, {"Short", "S"}, {"Byte", "B"}}) { + String o = "java/lang/" + b[0], p = b[1]; + add(o, none, fresh, "valueOf(" + p + ")L" + o + ";", "toString(" + p + ")Ljava/lang/String;", + "toString()Ljava/lang/String;"); + add(o, none, decl, "compare(" + p + p + ")I", "hashCode(" + p + ")I", "hashCode()I", + "equals(Ljava/lang/Object;)Z", "intValue()I", "longValue()J", "shortValue()S", "byteValue()B", + "floatValue()F", "doubleValue()D"); + add(o, nonNull, decl, "compareTo(L" + o + ";)I"); + } + for (String o : List.of("java/lang/Integer", "java/lang/Long")) { + String p = o.endsWith("Integer") ? "I" : "J"; + add(o, none, decl, "signum(" + p + ")I", "bitCount(" + p + ")I", "reverse(" + p + ")" + p, + "reverseBytes(" + p + ")" + p, "highestOneBit(" + p + ")" + p, "lowestOneBit(" + p + ")" + p, + "numberOfLeadingZeros(" + p + ")I", "numberOfTrailingZeros(" + p + ")I", + "rotateLeft(" + p + "I)" + p, "rotateRight(" + p + "I)" + p, "sum(" + p + p + ")" + p, + "max(" + p + p + ")" + p, "min(" + p + p + ")" + p, "compareUnsigned(" + p + p + ")I"); + add(o, none, fresh, "toString(" + p + "I)Ljava/lang/String;", "toBinaryString(" + p + ")Ljava/lang/String;", + "toHexString(" + p + ")Ljava/lang/String;", "toOctalString(" + p + ")Ljava/lang/String;"); + } + add("java/lang/Integer", none, decl, "toUnsignedLong(I)J"); + for (String[] b : new String[][] {{"Double", "D", "J"}, {"Float", "F", "I"}}) { + String o = "java/lang/" + b[0], p = b[1], bits = b[2]; + String toBits = p.equals("D") ? "doubleToLongBits" : "floatToIntBits"; + String toRaw = p.equals("D") ? "doubleToRawLongBits" : "floatToRawIntBits"; + String fromBits = p.equals("D") ? "longBitsToDouble" : "intBitsToFloat"; + add(o, none, fresh, "valueOf(" + p + ")L" + o + ";", "toString(" + p + ")Ljava/lang/String;", + "toHexString(" + p + ")Ljava/lang/String;", "toString()Ljava/lang/String;"); + add(o, none, decl, "isNaN(" + p + ")Z", "isInfinite(" + p + ")Z", "isFinite(" + p + ")Z", + "compare(" + p + p + ")I", toBits + "(" + p + ")" + bits, toRaw + "(" + p + ")" + bits, + fromBits + "(" + bits + ")" + p, "hashCode(" + p + ")I", "sum(" + p + p + ")" + p, + "max(" + p + p + ")" + p, "min(" + p + p + ")" + p, "isNaN()Z", "isInfinite()Z", "hashCode()I", + "equals(Ljava/lang/Object;)Z", "intValue()I", "longValue()J", "shortValue()S", "byteValue()B", + "floatValue()F", "doubleValue()D"); + add(o, nonNull, decl, "compareTo(L" + o + ";)I"); + } + add("java/lang/Boolean", none, fresh, "valueOf(Z)Ljava/lang/Boolean;", "toString(Z)Ljava/lang/String;", + "toString()Ljava/lang/String;"); + add("java/lang/Boolean", none, decl, "booleanValue()Z", "compare(ZZ)I", "hashCode(Z)I", "hashCode()I", + "equals(Ljava/lang/Object;)Z", "logicalAnd(ZZ)Z", "logicalOr(ZZ)Z", "logicalXor(ZZ)Z", + "parseBoolean(Ljava/lang/String;)Z"); + add("java/lang/Character", none, fresh, "valueOf(C)Ljava/lang/Character;", "toString(C)Ljava/lang/String;", + "toString()Ljava/lang/String;"); + add("java/lang/Character", none, decl, "charValue()C", "compare(CC)I", "hashCode(C)I", "hashCode()I", + "equals(Ljava/lang/Object;)Z", "digit(CI)I", "digit(II)I", "forDigit(II)C", "getNumericValue(C)I", + "getNumericValue(I)I"); + for (String pred : List.of("isDigit", "isLetter", "isLetterOrDigit", "isWhitespace", "isSpaceChar", + "isUpperCase", "isLowerCase", "isAlphabetic", "isJavaIdentifierStart", "isJavaIdentifierPart", + "isISOControl", "isTitleCase", "isDefined")) { + add("java/lang/Character", none, decl, pred + "(C)Z", pred + "(I)Z"); + } + add("java/lang/Character", none, decl, "toUpperCase(C)C", "toLowerCase(C)C", "toUpperCase(I)I", + "toLowerCase(I)I"); + + // String (final): methods that neither index nor parse. + add("java/lang/String", none, decl, "length()I", "isEmpty()Z", "hashCode()I", "equals(Ljava/lang/Object;)Z", + "equalsIgnoreCase(Ljava/lang/String;)Z", "indexOf(I)I", "indexOf(II)I", "lastIndexOf(I)I", + "lastIndexOf(II)I"); + add("java/lang/String", none, fresh, "toString()Ljava/lang/String;", "intern()Ljava/lang/String;", + "trim()Ljava/lang/String;", + "replace(CC)Ljava/lang/String;", "valueOf(I)Ljava/lang/String;", "valueOf(J)Ljava/lang/String;", + "valueOf(F)Ljava/lang/String;", "valueOf(D)Ljava/lang/String;", "valueOf(Z)Ljava/lang/String;", + "valueOf(C)Ljava/lang/String;", "()V"); + add("java/lang/String", nonNull, decl, "compareTo(Ljava/lang/String;)I", + "compareToIgnoreCase(Ljava/lang/String;)I", "startsWith(Ljava/lang/String;)Z", + "startsWith(Ljava/lang/String;I)Z", "endsWith(Ljava/lang/String;)Z", "indexOf(Ljava/lang/String;)I", + "indexOf(Ljava/lang/String;I)I", "lastIndexOf(Ljava/lang/String;)I", "(Ljava/lang/String;)V", + "([C)V"); + // Not concat, toLowerCase, toUpperCase or new StringBuilder(String): on JDK 8 their size + // computations overflow for huge strings and throw NegativeArraySizeException. + add("java/lang/String", nonNull, fresh, "valueOf([C)Ljava/lang/String;"); + add("java/lang/String", none, Returns.NONNULL_EXACT, "toCharArray()[C"); + add("java/lang/String", str, fresh, "valueOf(Ljava/lang/Object;)Ljava/lang/String;"); + + for (String sb : List.of("java/lang/StringBuilder", "java/lang/StringBuffer")) { + String l = "L" + sb + ";"; + add(sb, none, decl, "()V", "length()I"); + add(sb, none, self, "append(Z)" + l, "append(C)" + l, "append(I)" + l, "append(J)" + l, + "append(F)" + l, "append(D)" + l, "append(Ljava/lang/String;)" + l, "reverse()" + l); + add(sb, str, self, "append(Ljava/lang/Object;)" + l, "append(Ljava/lang/CharSequence;)" + l); + add(sb, nonNull, self, "append([C)" + l); + add(sb, none, fresh, "toString()Ljava/lang/String;"); + } + + // System.out / System.err (exactly java.io.PrintStream, see FactInterpreter). + String ps = "java/io/PrintStream"; + addExact(ps, none, decl, "println()V", "println(Z)V", "println(C)V", "println(I)V", "println(J)V", + "println(F)V", "println(D)V", "println(Ljava/lang/String;)V", "print(Z)V", "print(C)V", "print(I)V", + "print(J)V", "print(F)V", "print(D)V", "print(Ljava/lang/String;)V", "flush()V"); + addExact(ps, str, decl, "println(Ljava/lang/Object;)V", "print(Ljava/lang/Object;)V"); + addExact(ps, nonNull, decl, "println([C)V", "print([C)V"); + + add("java/lang/System", none, decl, "currentTimeMillis()J", "nanoTime()J", + "identityHashCode(Ljava/lang/Object;)I", "exit(I)V"); + add("java/lang/System", none, fresh, "lineSeparator()Ljava/lang/String;"); + } + + /** The model of a library method, or null if calls to it fail the check. */ + static Entry lookup(String owner, String name, String desc) { + if (owner.equals("java/lang/Math") || owner.equals("java/lang/StrictMath")) { + return MATH.contains(name) ? new Entry(Condition.NONE, Returns.DECLARED, false) : null; + } + if (owner.startsWith("[") && name.equals("clone") && desc.equals("()Ljava/lang/Object;")) { + return new Entry(Condition.NONE, Returns.CLONE, false); + } + return ENTRIES.get(owner + "." + name + desc); + } + + /** No class can override this library method: its class is final, or the method is. */ + static boolean cannotBeOverridden(String owner, String name, String desc) { + if (FINAL_CLASSES.contains(owner) || owner.startsWith("[")) return true; + if (owner.equals("java/lang/Object")) return name.equals("getClass") || name.equals(""); + if (owner.equals("java/lang/Enum")) { + return !name.equals("toString"); + } + return name.equals(""); + } + + /** Whether we know every method the class (with its library superclasses) declares. */ + static boolean knowsMethodsOf(String owner) { + return DECLARED.containsKey(owner); + } + + /** Whether the library class or one of its library superclasses declares the method. */ + static boolean declares(String owner, String name, String desc) { + return declaringClass(owner, name, desc) != null; + } + + /** The class among the known library classes that declares the method an instance of owner inherits. */ + static String declaringClass(String owner, String name, String desc) { + for (String c = owner; c != null; c = c.equals("java/lang/Enum") ? "java/lang/Object" : null) { + Set d = DECLARED.get(c); + if (d != null && d.contains(name + desc)) return c; + } + return null; + } + + static List knownSupertypes(String type) { + return SUPERTYPES.getOrDefault(type, List.of("java/lang/Object")); + } + + /** Library interfaces known to declare no default methods (on JDK 8 to 21). */ + private static final Set NO_DEFAULTS = Set.of("java/io/Serializable", "java/lang/Cloneable", + "java/lang/Comparable", "java/lang/Runnable", "java/lang/AutoCloseable", "java/io/Closeable"); + + static boolean hasNoDefaultMethods(String iface) { + return NO_DEFAULTS.contains(iface); + } + + static boolean isFinalClass(String owner) { + return FINAL_CLASSES.contains(owner); + } +} diff --git a/symbolic-executor/src/main/java/de/uzl/its/swat/staticcheck/Program.java b/symbolic-executor/src/main/java/de/uzl/its/swat/staticcheck/Program.java new file mode 100644 index 00000000..b99e2b8d --- /dev/null +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/staticcheck/Program.java @@ -0,0 +1,361 @@ +package de.uzl.its.swat.staticcheck; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.stream.Stream; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.FieldNode; +import org.objectweb.asm.tree.MethodNode; + +/** + * The application classes of a program (every class file on its classpath) and the queries the + * check needs on them: hierarchy, method and field resolution, and the possible targets of a call. + * Every class that is not on the classpath is a library (JDK) class. + */ +final class Program { + + /** + * The SV-COMP Verifier package: modelled by the check instead of analysed. Built at run time, + * so that the shadow jar's relocation of org.sosy_lab (java-smt) does not rewrite it. + */ + static final String VERIFIER_PACKAGE = "org/".concat("sosy_lab/sv_benchmarks/"); + + /** A method of an application class. */ + static final class Method { + final ClassNode owner; + final MethodNode node; + final String key; + + Method(ClassNode owner, MethodNode node) { + this.owner = owner; + this.node = node; + this.key = owner.name + "." + node.name + node.desc; + } + + boolean isStatic() { + return (node.access & Opcodes.ACC_STATIC) != 0; + } + + boolean hasCode() { + return (node.access & (Opcodes.ACC_ABSTRACT | Opcodes.ACC_NATIVE)) == 0; + } + + @Override + public String toString() { + return key; + } + } + + /** + * Where a call may go: application methods, plus library methods (owner.name+desc) when the + * receiver can be a library object or inherit a library implementation. + */ + static final class Targets { + final Set methods = new LinkedHashSet<>(); + final Set libraryMethods = new LinkedHashSet<>(); + } + + private final Map classes = new LinkedHashMap<>(); + private final Map methods = new LinkedHashMap<>(); + private final Map> subtypes = new HashMap<>(); + + Program(List classpath) throws IOException { + for (Path entry : classpath) { + if (Files.isDirectory(entry)) { + try (Stream files = Files.walk(entry)) { + for (Path p : (Iterable) files.filter(f -> f.toString().endsWith(".class"))::iterator) { + try (InputStream in = Files.newInputStream(p)) { + add(in); + } + } + } + } else if (entry.toString().endsWith(".jar") && Files.isRegularFile(entry)) { + try (JarFile jar = new JarFile(entry.toFile())) { + for (JarEntry e : Collections.list(jar.entries())) { + if (e.getName().endsWith(".class") && !e.getName().startsWith("META-INF/")) { + try (InputStream in = jar.getInputStream(e)) { + add(in); + } + } + } + } + } + } + for (ClassNode c : classes.values()) { + if (c.superName != null) subtypes.computeIfAbsent(c.superName, k -> new ArrayList<>()).add(c.name); + for (String i : c.interfaces) subtypes.computeIfAbsent(i, k -> new ArrayList<>()).add(c.name); + } + } + + private void add(InputStream in) throws IOException { + ClassNode c = new ClassNode(); + new ClassReader(in).accept(c, ClassReader.SKIP_FRAMES); + // The first definition on the classpath wins, as for the JVM's class loader. + if (classes.containsKey(c.name)) return; + if (unsupported == null) unsupported = unsupportedReason(c.name); + classes.put(c.name, c); + if (c.name.startsWith(VERIFIER_PACKAGE)) return; + for (MethodNode m : c.methods) { + Method method = new Method(c, m); + methods.put(method.key, method); + } + } + + /** + * Why a class on the classpath breaks the assumption that the classpath is the program (null + * if it does not). The JVM does not load such a class the way the analysis models it: it + * refuses java/ packages, serves packages the platform defines from the platform, and the + * analysis models only Verifier and ObjectFactory of the Verifier package, so the static + * initializer of any other class there would run unanalysed. + */ + private static String unsupportedReason(String name) { + int slash = name.lastIndexOf('/'); + String pkg = slash < 0 ? "" : name.substring(0, slash); + if (name.startsWith(VERIFIER_PACKAGE)) { + if (name.equals(VERIFIER_PACKAGE + "Verifier") || name.equals(VERIFIER_PACKAGE + "ObjectFactory")) return null; + return "class " + name + " in the Verifier package"; + } + for (String prefix : PLATFORM_PREFIXES) { + if (name.startsWith(prefix)) return "class " + name + " in a platform package"; + } + if (PLATFORM_PACKAGES.contains(pkg.replace('/', '.'))) return "class " + name + " in a package of the JDK"; + return null; + } + + /** + * Package prefixes of the platform: java/, javax/, jdk/, sun/, and those that exist only in the + * JDK 8 runtime (not as modules of the JDK the check runs on). + */ + private static final List PLATFORM_PREFIXES = List.of("java/", "javax/", "jdk/", "sun/", "com/sun/", + "org/omg/", "org/w3c/", "org/xml/", "org/ietf/", "org/jcp/"); + + /** The packages of the JDK's boot modules (the platform the check runs on). */ + private static final Set PLATFORM_PACKAGES = new HashSet<>(); + + static { + for (Module m : ModuleLayer.boot().modules()) PLATFORM_PACKAGES.addAll(m.getPackages()); + } + + /** Set if a class on the classpath breaks the model of class loading; the check then fails. */ + private String unsupported; + + String unsupportedClasspath() { + return unsupported; + } + + /** An application class (not a library class, and not the modelled Verifier package). */ + boolean isApp(String internalName) { + return classes.containsKey(internalName) && !internalName.startsWith(VERIFIER_PACKAGE); + } + + boolean isOnClasspath(String internalName) { + return classes.containsKey(internalName); + } + + ClassNode clazz(String internalName) { + return classes.get(internalName); + } + + Collection methods() { + return methods.values(); + } + + Method method(String owner, String name, String desc) { + return methods.get(owner + "." + name + desc); + } + + Collection appClasses() { + List out = new ArrayList<>(); + for (ClassNode c : classes.values()) if (isApp(c.name)) out.add(c); + return out; + } + + boolean isEnum(String internalName) { + ClassNode c = classes.get(internalName); + return c != null && isApp(internalName) && (c.access & Opcodes.ACC_ENUM) != 0 + && "java/lang/Enum".equals(c.superName); + } + + /** The class and its application superclasses, ending with the first library class (if any). */ + List superChain(String internalName) { + List chain = new ArrayList<>(); + String c = internalName; + while (c != null) { + chain.add(c); + ClassNode node = classes.get(c); + if (node == null || !isApp(c)) break; + c = node.superName; + } + return chain; + } + + /** All application types that are subtypes of the given type, the type itself included. */ + Set appSubtypes(String type) { + Set out = new LinkedHashSet<>(); + Deque work = new ArrayDeque<>(); + work.add(type); + while (!work.isEmpty()) { + String t = work.poll(); + if (isApp(t) && !out.add(t)) continue; + for (String s : subtypes.getOrDefault(t, List.of())) if (!out.contains(s)) work.add(s); + } + return out; + } + + /** All application supertypes (classes and interfaces) of an application type, itself included. */ + Set appSupertypes(String type) { + Set out = new LinkedHashSet<>(); + Deque work = new ArrayDeque<>(); + work.add(type); + while (!work.isEmpty()) { + String t = work.poll(); + ClassNode c = classes.get(t); + if (c == null || !isApp(t) || !out.add(t)) continue; + if (c.superName != null) work.add(c.superName); + work.addAll(c.interfaces); + } + return out; + } + + /** The field a field instruction refers to: declared in the owner or a superclass/superinterface. */ + FieldNode resolveField(String owner, String name, String desc, String[] declaringClass) { + Deque work = new ArrayDeque<>(); + work.add(owner); + Set seen = new HashSet<>(); + while (!work.isEmpty()) { + String t = work.poll(); + if (!seen.add(t)) continue; + ClassNode c = classes.get(t); + if (c == null || !isApp(t)) continue; + for (FieldNode f : c.fields) { + if (f.name.equals(name) && f.desc.equals(desc)) { + declaringClass[0] = t; + return f; + } + } + work.addAll(c.interfaces); + if (c.superName != null) work.add(c.superName); + } + return null; + } + + /** + * The method a static or special call (or a virtual call on a known runtime class) executes, + * searching the class and its superclasses. Returns the application method, or null with + * libraryOwner[0] set to the first library class of the chain when the search leaves the + * application. + */ + Method resolveInChain(String owner, String name, String desc, String[] libraryOwner) { + for (String c : superChain(owner)) { + if (!isApp(c)) { + libraryOwner[0] = c; + return null; + } + Method m = method(c, name, desc); + if (m != null) return m; + } + return null; + } + + /** All default methods for name+desc in the application interfaces of a class. */ + private List defaults(String clazz, String name, String desc) { + List out = new ArrayList<>(); + for (String t : appSupertypes(clazz)) { + ClassNode c = classes.get(t); + if ((c.access & Opcodes.ACC_INTERFACE) == 0) continue; + Method m = method(t, name, desc); + if (m != null && m.hasCode() && !m.isStatic() && (m.node.access & Opcodes.ACC_PRIVATE) == 0) out.add(m); + } + return out; + } + + /** + * The targets of an invokevirtual/invokeinterface on an application owner type: a superset of + * the methods the JVM may select for each concrete application subtype of the owner (JVMS + * 5.4.6). Rather than following the selection rules exactly (package-private overriding, + * maximally-specific default methods), every non-private instance method with that name and + * descriptor in the superclass chain, and every default method of the application + * superinterfaces, counts. A library implementation counts unless a public or protected + * application method overrides it. Returns null if a library class or library interface whose + * methods are unknown might provide it. + */ + Targets virtualTargets(String owner, String name, String desc) { + Targets t = new Targets(); + Method declared = method(owner, name, desc); + if (declared != null && (declared.node.access & Opcodes.ACC_PRIVATE) != 0) { + // A private method is selected directly (invokevirtual on a nestmate's private method). + if (!declared.hasCode()) return null; + t.methods.add(declared); + return t; + } + for (String sub : appSubtypes(owner)) { + ClassNode c = classes.get(sub); + if ((c.access & (Opcodes.ACC_ABSTRACT | Opcodes.ACC_INTERFACE)) != 0) continue; + boolean overridesLibrary = false; + String lib = null; + for (String k : superChain(sub)) { + if (!isApp(k)) { + lib = k; + break; + } + Method m = method(k, name, desc); + if (m == null || m.isStatic() || (m.node.access & Opcodes.ACC_PRIVATE) != 0) continue; + if ((m.node.access & Opcodes.ACC_NATIVE) != 0) return null; + if (m.hasCode()) t.methods.add(m); + if ((m.node.access & (Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED)) != 0) overridesLibrary = true; + } + t.methods.addAll(defaults(sub, name, desc)); + if (overridesLibrary) continue; + // A default method of a library interface could be selected instead, and we do not know them. + for (String sup : appSupertypes(sub)) { + for (String i : classes.get(sup).interfaces) { + if (!isApp(i) && !JdkModel.hasNoDefaultMethods(i)) return null; + } + } + if (lib == null) continue; + String declaring = JdkModel.declaringClass(lib, name, desc); + if (declaring != null) t.libraryMethods.add(declaring + "." + name + desc); + else if (!JdkModel.knowsMethodsOf(lib)) return null; + } + return t; + } + + /** Whether a value of type {@code from} is always assignable to type {@code to}. */ + boolean isAssignable(String from, String to) { + if (from == null || to == null) return false; + if (from.equals(to) || to.equals("java/lang/Object")) return true; + if (from.startsWith("[")) { + if (to.equals("java/lang/Cloneable") || to.equals("java/io/Serializable")) return true; + if (!to.startsWith("[")) return false; + String fe = from.substring(1), te = to.substring(1); + if (fe.startsWith("[") || te.startsWith("[")) return isAssignable(fe, te); + if (fe.startsWith("L") && te.startsWith("L")) + return isAssignable(fe.substring(1, fe.length() - 1), te.substring(1, te.length() - 1)); + return fe.equals(te); + } + if (to.startsWith("[")) return false; + Deque work = new ArrayDeque<>(); + work.add(from); + Set seen = new HashSet<>(); + while (!work.isEmpty()) { + String t = work.poll(); + if (!seen.add(t)) continue; + if (t.equals(to)) return true; + if (isApp(t)) { + ClassNode c = classes.get(t); + if (c.superName != null) work.add(c.superName); + work.addAll(c.interfaces); + } else { + work.addAll(JdkModel.knownSupertypes(t)); + } + } + return false; + } +} 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..1abeade9 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,14 @@ 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? // 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(); @@ -84,11 +93,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 +153,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 +194,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 +323,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 +336,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 +361,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 +369,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 +405,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 +418,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 +437,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 +455,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 +465,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 +485,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 +501,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 +517,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 +528,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 +554,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 +574,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 +587,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 +601,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 +613,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 +631,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 +644,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 +658,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 +671,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 +685,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 +699,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 +713,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 +728,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 +740,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 +758,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 +771,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 +784,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 +799,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 +814,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 +827,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 +840,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 +856,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 +869,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 +884,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,8 +893,8 @@ 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)); @@ -735,8 +909,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 +924,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 +937,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 +953,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 +967,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 +982,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 +1003,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 +1025,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 +1044,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 +1063,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 +1077,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 +1086,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 +1100,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 +1115,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 +1127,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 +1145,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 +1158,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 +1171,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 +1186,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 +1201,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 +1214,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 +1227,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 +1240,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 +1255,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 +1268,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 +1283,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,8 +1298,8 @@ 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)); @@ -1137,8 +1314,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 +1330,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 +1343,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 +1358,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 +1387,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 +1400,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 +1410,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 +1424,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 +1447,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 +1464,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 +1499,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) { @@ -1318,7 +1512,7 @@ public void visitGETVALUE_Object(GETVALUE_Object inst) throws SymbolicInstruc 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 (peek.formula == null) { // TODO This needs to be cleaned up! stack.popOperand(); StringValue val = ValueFactory.createStringValue(s, inst.address); @@ -1363,7 +1557,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 +1575,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 +1607,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 +1616,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 +1625,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 +1634,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 +1643,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 +1652,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 +1661,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 +1670,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 +1683,16 @@ 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 { + } /** * 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 +1708,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 +1722,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 +1736,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 +1750,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 +1764,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 +1778,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 +1793,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 +1805,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 +1824,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 +1839,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 +1852,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 +1865,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 +1878,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 +1891,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 +1904,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 +1917,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 +1930,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 +1943,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 +1956,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 +1979,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(); @@ -1801,8 +2000,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(); @@ -1821,8 +2020,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(); @@ -1841,8 +2040,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(); @@ -1861,8 +2060,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(); @@ -1881,8 +2080,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(); @@ -1901,8 +2100,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 +2120,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 +2141,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 +2155,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 +2170,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 +2198,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); @@ -2028,8 +2226,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); @@ -2049,8 +2247,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); @@ -2070,8 +2268,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); @@ -2091,8 +2289,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); @@ -2112,8 +2310,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); @@ -2133,8 +2331,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 +2349,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 +2364,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 +2379,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 +2396,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 +2406,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 +2537,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 +2574,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 +2605,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 +2696,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 +2731,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 +2746,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 +2770,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 +2785,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 +2801,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 +2816,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 +2829,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 +2844,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 +2860,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 +2878,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 +2890,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 +2899,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 +2913,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 +2928,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 +2940,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 +2959,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 +2974,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 +2987,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 +3000,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 +3015,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 +3028,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 +3041,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 +3055,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 +3077,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 +3091,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 +3105,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 +3119,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 +3133,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 +3157,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 +3170,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 +3185,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 +3199,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 +3214,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 +3238,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 +3253,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 +3268,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 +3283,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 +3296,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 +3312,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 +3327,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 +3342,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 +3356,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 +3365,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 +3377,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 +3423,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 +3441,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 +3453,6 @@ public void visitSET_FIELD_REFLECTION(SET_FIELD_REFLECTION inst) throws Symbolic targetObject.setField(fieldIndex, valueToSet); } - stack.setReturnValue(PlaceHolder.instance); } @@ -3097,9 +3471,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 +3492,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 +3515,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 +3531,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 +3572,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 +3593,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 +3610,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 +3619,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 +3637,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 +3649,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 +3674,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 +3691,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 +3703,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 +3721,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 +3734,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 +3747,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 +3760,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 +3771,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 +3780,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 +3802,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 +3821,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 +3835,39 @@ 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{} + 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 +3877,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 +3885,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 +3904,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 +3930,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 +3958,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 +4023,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 +4034,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..5fbe9f73 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,12 @@ public static void MAKE_SYMBOLIC(long iid) throws NoThreadContextException { * @param iid The unique instruction ID set during instrumentation. */ @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..a6dadb82 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,9 @@ 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) {} + /** * 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..441cba4e 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,13 @@ public static void MAKE_SYMBOLIC(long iid) { * * @param iid The unique instruction ID set during instrumentation. */ + @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..c09056ca 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,8 @@ public interface IVisitor { void visitLOOP_BEGIN(LOOP_BEGIN inst) throws SymbolicInstructionException; + void visitLOOP_HEAD(LOOP_HEAD 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/invoke/InvocationHandler.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/InvocationHandler.java index 21507133..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); @@ -107,9 +149,10 @@ public class InvocationHandler { 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, 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..2a9c0470 --- /dev/null +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/invoke/java/lang/SampledMath.java @@ -0,0 +1,191 @@ +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. + */ +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 + }; + + 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. + */ + private 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(DOUBLE); + String name = UF_PREFIX + key + "_" + CALLS.getAndIncrement(); + FunctionDeclaration uf = fmgr.getUFManager().declareUF(name, DOUBLE, 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 inputs, and for each nice output t the input inverse(t), or a neighbour of it at + * which f gives exactly t. Non-finite inputs are dropped. + */ + static double[] samples(DoubleUnaryOperator f, DoubleUnaryOperator inverse) { + Map samples = new LinkedHashMap<>(); + for (double x : NICE_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..4074e46f --- /dev/null +++ b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/loopstate/LoopStateMatcher.java @@ -0,0 +1,409 @@ +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/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..068b079d 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,10 @@ public void LOOP_BEGIN(long iid) { processInstruction(new LOOP_BEGIN(iid)); } + 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..08155f17 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,9 @@ 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 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..f7a828f1 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,8 @@ void INVOKEDYNAMIC( void LOOP_BEGIN(long iid); + void LOOP_HEAD(long iid, String head, Object[] locals); + 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 a7167135..07aeab6c 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 @@ -35,6 +35,21 @@ public class Frame { /** The method name of the method that is invoked */ @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 @@ -108,6 +123,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 * 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..54580208 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,56 @@ public void putToHeap(int hashCode, Value value) { public void pushFrame(Frame frame) { activeFrame = frame; frameStack.push(frame); + if (!isSwatHelper(frame)) { + pushedSinceBranch.add(frame); + } + } + + /** + * A frame of SWAT's own instrumented helpers (e.g. {@code UtilInstrumented.refEquals}). The + * static pre-analysis graph has their branches inline in the caller, so the stack changes the + * trace reports leave their frames out. + */ + 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; + + /** + * 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 +127,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)) { + // not counted on push either + } else if (pushedSinceBranch.isEmpty()) { + poppedSinceBranch++; + } else { + pushedSinceBranch.remove(pushedSinceBranch.size() - 1); + } activeFrame = getActiveFrame(); return oldFrame; } @@ -318,9 +383,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/trace/BranchElement.java b/symbolic-executor/src/main/java/de/uzl/its/swat/symbolic/trace/BranchElement.java index ae0e9c5d..5c0de7a5 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 @@ -17,6 +17,27 @@ final class BranchElement extends Element { // 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 in application code that library code called back (see ShadowContext.inCallback). + @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(); /** * Constructs a branch element. @@ -26,8 +47,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 +61,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 e1156b95..8790713b 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,18 +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; @@ -31,14 +38,17 @@ */ 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, StatsStorage statsStorage) throws NoThreadContextException, JsonProcessingException, NotImplementedException { - return buildRequestBody(buildTraceDTO(symbolicTrace, statsStorage)); + protected static String encodeTrace(SymbolicTrace symbolicTrace, StatsStorage statsStorage, long threadId) throws NoThreadContextException, JsonProcessingException, NotImplementedException { + return buildRequestBody(buildTraceDTO(symbolicTrace, statsStorage, threadId)); } /** @@ -74,16 +84,17 @@ private static ArrayList buildMissingInvocations(StatsStorage sta * @return A {@link TraceDTO ConstraintDTO} that contains relevant all relevant information * to transfer symbolic traces */ - private static TraceDTO buildTraceDTO(SymbolicTrace symbolicTrace, StatsStorage statsStorage) 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(), @@ -94,11 +105,13 @@ private static TraceDTO buildTraceDTO(SymbolicTrace symbolicTrace, StatsStorage 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; @@ -108,33 +121,50 @@ private static TraceDTO buildTraceDTO(SymbolicTrace symbolicTrace, StatsStorage 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()) + .loopStates(be.getLoopStates())); } else if (el instanceof SpecialElement se) { trace.add(new BranchDTO(se.getIid(), se.getInst())); } } - return new TraceDTO(inputs, trace, ufs, buildMissingInvocations(statsStorage), 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 { @@ -164,6 +194,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 02119155..3c36f6b6 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 @@ -7,6 +7,9 @@ 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; @@ -14,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; /** @@ -40,6 +44,15 @@ public SymbolicTraceHandler() { * @param iid The iid of the branch instruction. */ public void checkAndSetBranch(boolean result, BooleanFormula constraint, long iid) throws NoThreadContextException { + 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 void checkAndSetBranch(boolean result, BooleanFormula constraint, long iid, String suffix) throws NoThreadContextException { long threadId = Thread.currentThread().getId(); BranchElement current; @@ -50,9 +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); } + /** + * 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(); + branch.setCallback(stack.inCallback()); + 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); + } + /** * Adds a special element to the symbolic trace. This is used to catch all unsupported branching * behaviour. @@ -83,6 +182,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); } /** @@ -110,7 +210,15 @@ public List getConstraints() { * @return The symbolic trace encoded as a JSON string. */ public String getTraceDTO(StatsStorage statsStorage) throws NoThreadContextException, JsonProcessingException, NotImplementedException { - return DTOBuilder.encodeTrace(symbolicTrace, statsStorage); + 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); } /** @@ -208,6 +316,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..0a29586a 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,63 @@ 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: 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/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 75eaf698..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 @@ -17,21 +17,43 @@ public class TraceDTO { @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, ArrayList missingInvocations, 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..111a04e9 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,8 @@ package de.uzl.its.swat.symbolic.value.primitive.numeric.floatingpoint; +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 +15,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 +25,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 @@ -236,7 +245,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 +254,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 +443,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..a61ebb90 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,8 @@ package de.uzl.its.swat.symbolic.value.primitive.numeric.floatingpoint; +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 +15,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 +25,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 @@ -276,49 +285,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 +407,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/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/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 24f8d599..d15dcd98 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,6 +43,7 @@ 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); } @@ -49,6 +51,7 @@ public StringValue(SolverContext context, String concrete, int address) { 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; } @@ -133,7 +136,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'))); } /** @@ -217,14 +226,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; } @@ -276,8 +307,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)); } /** @@ -291,8 +335,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)); } /** @@ -306,8 +363,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); } /** @@ -336,8 +414,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); + } } /** @@ -352,7 +480,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); } /** @@ -388,7 +577,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 { @@ -408,7 +598,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)); } /** @@ -460,15 +652,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; } @@ -485,38 +676,26 @@ 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()); } /** @@ -567,11 +746,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 }; dst.storeElement( - new IntValue(context, dstBegin.concrete + idx), + new IntValue(context, dstBegin.concrete + idx - srcBegin.concrete), (CharValue) invokeCharAt(charAtArgs, charAtDesc)); } return VoidValue.instance; @@ -651,10 +831,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) { @@ -662,7 +848,43 @@ 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))); + } + + /** 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)); + NumeralFormula.IntegerFormula c = ch.asIntegerFormula(); + return bmgr.and(imgr.greaterOrEquals(c, imgr.makeNumber(0)), + imgr.lessOrEquals(c, imgr.makeNumber(Character.MAX_CODE_POINT))); + } + + /** + * 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; + } + NumeralFormula.IntegerFormula c = ch.asIntegerFormula(); + NumeralFormula.IntegerFormula offset = imgr.subtract(c, imgr.makeNumber(Character.MIN_SUPPLEMENTARY_CODE_POINT)); + NumeralFormula.IntegerFormula high = imgr.add(imgr.makeNumber(Character.MIN_HIGH_SURROGATE), imgr.divide(offset, imgr.makeNumber(1024))); + NumeralFormula.IntegerFormula low = imgr.add(imgr.makeNumber(Character.MIN_LOW_SURROGATE), imgr.modulo(offset, imgr.makeNumber(1024))); + return bmgr.ifThenElse(imgr.lessThan(c, imgr.makeNumber(Character.MIN_SUPPLEMENTARY_CODE_POINT)), + smgr.fromCodePoint(c), + smgr.concat(smgr.fromCodePoint(high), smgr.fromCodePoint(low))); } /** @@ -707,7 +929,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))); } /** @@ -721,8 +943,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))); } /** @@ -767,7 +1033,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)); } /** @@ -796,8 +1068,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; } /** @@ -811,8 +1131,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); } /** @@ -840,41 +1169,33 @@ public BooleanFormula getBounds(boolean upper) { } private Value invokeReplace(ObjectValue target, ObjectValue replacement) { - if (target instanceof StringValue oldString && replacement instanceof StringValue newString) { - // ToDo (Nils): This is the correct implementation. However replaceAll is currently not - // supported by Z3 - /* - formula = smgr.replaceAll(this.formula, oldString.formula, newString.formula); - */ - - var newConcrete = this.concrete.replace(oldString.concrete, newString.concrete); - var newFormula = this.formula; - for (int i = 0; i < REPLACE_COUNT; i++) { - newFormula = smgr.replace(newFormula, oldString.formula, newString.formula); - } - - return new StringValue(context, newConcrete, newFormula, ObjectValue.ADDRESS_UNKNOWN); - } 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 - /* - formula = smgr.replaceAll(this.formula, oldString.formula, newString.formula); - */ - - var newConcrete = this.concrete.replace(oldString.concrete, newString.concrete); - var newFormula = this.formula; + 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++) { - newFormula = smgr.replace(newFormula, oldString.formula, newString.formula); + newFormula = smgr.replace(newFormula, target.formula, replacement.formula); } - return new StringValue(context, newConcrete, newFormula, ObjectValue.ADDRESS_UNKNOWN); } @@ -890,7 +1211,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); } /** @@ -905,7 +1243,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, smgr.makeString(target), replacement.formula), -1); } /** @@ -968,12 +1313,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)))); } /** @@ -1047,8 +1397,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; } /** @@ -1104,21 +1456,16 @@ public BooleanFormula getBounds(boolean upper) { 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); + // 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; } /** @@ -1133,7 +1480,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); } /** @@ -1163,7 +1510,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); } /** @@ -1208,7 +1555,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 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/ThreadContext.java b/symbolic-executor/src/main/java/de/uzl/its/swat/thread/ThreadContext.java index 38c571d8..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 @@ -31,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; @@ -65,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<>(); @@ -116,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. @@ -191,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"); 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 2ecdbac3..fa93c2fd 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 @@ -17,14 +17,17 @@ 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.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.common.log.LogManager; import org.sosy_lab.java_smt.SolverContextFactory; import org.sosy_lab.java_smt.api.SolverContext; @@ -55,7 +58,7 @@ public static void init() throws InvalidConfigurationException { solverContext = SolverContextFactory.createSolverContext( Configuration.defaultConfiguration(), - BasicLogManager.create(Configuration.defaultConfiguration()), + LogManager.createNullLogManager(), ShutdownManager.create().getNotifier(), SolverContextFactory.Solvers.Z3); } @@ -77,6 +80,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) { @@ -163,7 +224,7 @@ public static void sendData(long id) throws NoThreadContextException, JsonProces } if(!config.isCoverageOnly()) { ConstraintRequest.sendConstraints( - symbolicStateHandler.getTraceDTO(context.getStatsStorage()), endpointID, traceID); + symbolicStateHandler.getTraceDTO(context.getStatsStorage(), id), endpointID, traceID); } } else { @@ -175,6 +236,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) { @@ -347,6 +422,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..42fe139e 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,222 @@ 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; a target execution never needs this). */ + public static void reset() { + nondetReturns.clear(); + violation = null; + thrownAssertion = null; } } 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/staticcheck/ExceptionFreedomCheckSpec.groovy b/symbolic-executor/src/test/groovy/de/uzl/its/swat/staticcheck/ExceptionFreedomCheckSpec.groovy new file mode 100644 index 00000000..038e9251 --- /dev/null +++ b/symbolic-executor/src/test/groovy/de/uzl/its/swat/staticcheck/ExceptionFreedomCheckSpec.groovy @@ -0,0 +1,152 @@ +package de.uzl.its.swat.staticcheck + +import spock.lang.Specification +import spock.lang.TempDir +import spock.lang.Unroll + +import javax.tools.ToolProvider +import java.nio.file.Files +import java.nio.file.Path + +/** + * Compiles small programs and checks the verdict of the exception-freedom check. The programs + * that may throw an uncaught RuntimeException must never pass; the others document what the + * check is able to prove. + */ +class ExceptionFreedomCheckSpec extends Specification { + + static final String VERIFIER = ''' +package org.sosy_lab.sv_benchmarks; +public final class Verifier { + public static void assume(boolean c) { if (!c) Runtime.getRuntime().halt(1); } + public static boolean nondetBoolean() { return new java.util.Random().nextBoolean(); } + public static int nondetInt() { return new java.util.Random().nextInt(); } + public static long nondetLong() { return new java.util.Random().nextLong(); } + public static double nondetDouble() { return new java.util.Random().nextDouble(); } + public static String nondetString() { return "" + new java.util.Random().nextInt(); } + public static T nondetObject(Class t, ObjectFactory f) { return f.createObject(); } +} +''' + static final String FACTORY = ''' +package org.sosy_lab.sv_benchmarks; +public interface ObjectFactory { T createObject(); } +''' + + @TempDir + Path dir + + ExceptionFreedomCheck.Result check(String main, String release = '8', Map extra = [:]) { + Path src = dir.resolve('src'), out = dir.resolve('classes') + Files.createDirectories(src.resolve('org/sosy_lab/sv_benchmarks')) + Files.createDirectories(out) + Files.writeString(src.resolve('org/sosy_lab/sv_benchmarks/Verifier.java'), VERIFIER) + Files.writeString(src.resolve('org/sosy_lab/sv_benchmarks/ObjectFactory.java'), FACTORY) + Files.writeString(src.resolve('Main.java'), 'import org.sosy_lab.sv_benchmarks.Verifier;\n' + main) + extra.each { path, code -> + Files.createDirectories(src.resolve(path).parent) + Files.writeString(src.resolve(path), code) + } + def files = [] + src.toFile().eachFileRecurse { if (it.name.endsWith('.java')) files << it.path } + int rc = ToolProvider.getSystemJavaCompiler().run(null, null, null, + (['--release', release, '-nowarn', '-d', out.toString()] + files) as String[]) + assert rc == 0 + return ExceptionFreedomCheck.check([out]) + } + + @Unroll + def "may throw: #name"() { + expect: + !check(program, release).exceptionFree + + where: + name | release | program + 'division by input' | '8' | 'public class Main { public static void main(String[] a) { int x = Verifier.nondetInt(); int y = 10 / x; } }' + 'long division by zero constant' | '8' | 'public class Main { public static void main(String[] a) { long x = Verifier.nondetLong(); long y = x / 0L; } }' + 'array index from input' | '8' | 'public class Main { public static void main(String[] a) { int[] arr = new int[3]; arr[Verifier.nondetInt()] = 1; } }' + 'array index past the end' | '8' | 'public class Main { public static void main(String[] a) { int[] arr = new int[3]; int i = 2; i++; arr[i] = 1; } }' + 'array index in a loop' | '8' | 'public class Main { public static void main(String[] a) { int[] arr = new int[3]; for (int i = 0; i < 4; i++) arr[i] = 1; } }' + 'negative array size' | '8' | 'public class Main { public static void main(String[] a) { int[] arr = new int[Verifier.nondetInt()]; } }' + 'field never assigned' | '8' | 'class B { void g() {} } class A { B b; } public class Main { public static void main(String[] a) { new A().b.g(); } }' + 'field assigned on one path' | '8' | 'class B { void g() {} } class A { B b; A(boolean c) { if (c) b = new B(); } } public class Main { public static void main(String[] a) { new A(Verifier.nondetBoolean()).b.g(); } }' + 'field set to null later' | '8' | 'class B { void g() {} } class A { B b = new B(); } public class Main { public static void main(String[] a) { A x = new A(); if (Verifier.nondetBoolean()) x.b = null; x.b.g(); } }' + 'read in overridden method from super constructor' | '8' | 'class B { void g() {} } class P { P() { init(); } void init() {} } class C extends P { B b = new B(); void init() { b.g(); } } public class Main { public static void main(String[] a) { new C(); } }' + 'receiver escapes before assignment' | '8' | 'class B { void g() {} } class Reg { static A last = new A(1); } class A { B b; A(int x) { Reg.last = this; int y = 10 / x; b = new B(); } } public class Main { public static void main(String[] a) { try { new A(Verifier.nondetInt()); } catch (Exception e) {} Reg.last.b.g(); } }' + 'static field read before assignment' | '8' | 'class B { void g() {} } class T { static B peek() { return S.b; } } class S { static B a = T.peek(); static B b = new B(); } public class Main { public static void main(String[] a) { S.a.g(); } }' + 'static field never assigned' | '8' | 'class B { void g() {} } class S { static B b; } public class Main { public static void main(String[] a) { S.b.g(); } }' + 'null argument' | '8' | 'class B { void g() {} } public class Main { static void f(B b) { b.g(); } public static void main(String[] a) { f(Verifier.nondetBoolean() ? new B() : null); } }' + 'null return value' | '8' | 'class B { void g() {} } public class Main { static B make() { return Verifier.nondetBoolean() ? new B() : null; } public static void main(String[] a) { make().g(); } }' + 'interface implementation throws' | '8' | 'interface I { void f(); } class A implements I { public void f() { throw new IllegalStateException(); } } class C implements I { public void f() {} } public class Main { public static void main(String[] a) { I i = Verifier.nondetBoolean() ? new A() : new C(); i.f(); } }' + 'toString callback through valueOf' | '8' | 'class A { public String toString() { throw new IllegalStateException(); } } public class Main { public static void main(String[] a) { String s = String.valueOf(new A()); } }' + 'toString callback in concatenation' | '8' | 'class A { public String toString() { throw new IllegalStateException(); } } public class Main { public static void main(String[] a) { String s = "x" + new A(); } }' + 'toString callback through indy' | '17' | 'class A { public String toString() { throw new IllegalStateException(); } } public class Main { public static void main(String[] a) { String s = "x" + new A(); } }' + 'rethrow of caught exception' | '8' | 'public class Main { public static void main(String[] a) { try { int y = 1 / Verifier.nondetInt(); } catch (ArithmeticException e) { throw e; } } }' + 'finally after a throwing body' | '8' | 'public class Main { static int n; public static void main(String[] a) { try { int y = 1 / Verifier.nondetInt(); } finally { n++; } } }' + 'inner handler rethrows' | '8' | 'public class Main { public static void main(String[] a) { try { try { int y = 1 / Verifier.nondetInt(); } catch (Exception e) { throw new IllegalStateException(); } } catch (ArithmeticException e) {} } }' + 'static initializer throws' | '8' | 'class S { static int x = 10 / Verifier.nondetInt(); } public class Main { public static void main(String[] a) { int y = S.x; } }' + 'array store of wrong type' | '8' | 'public class Main { public static void main(String[] a) { Object[] arr = new String[1]; arr[0] = new Object(); } }' + 'failing cast' | '8' | 'public class Main { public static void main(String[] a) { Object o = Verifier.nondetBoolean() ? (Object) "s" : (Object) new Object(); String s = (String) o; } }' + 'unboxing null' | '8' | 'public class Main { public static void main(String[] a) { Integer i = Verifier.nondetBoolean() ? null : 3; int x = i; } }' + 'callback from a caught library call' | '8' | 'class B { void g() {} } class H { static B b = new B(); } class Cmp implements java.util.Comparator { public int compare(Object x, Object y) { H.b = (B) x; return 0; } } public class Main { public static void main(String[] a) { try { java.util.Arrays.sort(new Object[] {null, null}, new Cmp()); } catch (Exception e) {} H.b.g(); } }' + 'lambda' | '8' | 'public class Main { public static void main(String[] a) { Runnable r = () -> { int y = 1 / Verifier.nondetInt(); }; r.run(); } }' + 'runtime exception subclass thrown' | '8' | 'class E extends RuntimeException {} public class Main { public static void main(String[] a) { if (Verifier.nondetBoolean()) throw new E(); } }' + 'private method of abstract class' | '17' | 'public class Main { static abstract class P { private void m() { int z = Verifier.nondetInt(); int q = 1 / z; } void call() { m(); } } static class Q extends P { void m() {} } public static void main(String[] a) { new Q().call(); } }' + 'nondetObject' | '8' | 'class B { void g() {} } public class Main { public static void main(String[] a) { B b = Verifier.nondetObject(B.class, () -> new B()); b.g(); } }' + 'finalizer' | '8' | 'class B { protected void finalize() {} } public class Main { public static void main(String[] a) { new B(); } }' + 'unmodelled library call' | '8' | 'public class Main { public static void main(String[] a) { String s = Verifier.nondetString(); char c = s.charAt(3); } }' + 'println of an object' | '8' | 'class A { public String toString() { throw new IllegalStateException(); } } public class Main { public static void main(String[] a) { System.out.println(new A()); } }' + 'null string argument' | '8' | 'public class Main { public static void main(String[] a) { String s = Verifier.nondetBoolean() ? null : "x"; boolean b = "abc".startsWith(s); } }' + 'main called with null' | '8' | 'public class Main { public static void main(String[] a) { if (Verifier.nondetBoolean()) { main(null); } else { int n = a.length; } } }' + 'more specific default method' | '8' | 'interface I { default int m() { return 0; } } interface J extends I { default int m() { throw new IllegalStateException(); } } class C implements I, J {} public class Main { public static void main(String[] a) { new C().m(); } }' + 'private method does not implement' | '17' | 'interface I { default void m() { throw new IllegalStateException(); } } class B { private void m() {} } class C extends B implements I {} public class Main { public static void main(String[] a) { I x = new C(); x.m(); } }' + 'receiver escapes through a join' | '8' | 'interface G { String get(); } class G0 implements G { public String get() { return "ok"; } } class Holder { G g; Holder() { this.g = new G0(); } } abstract class Base implements G { Base() { G o = Verifier.nondetBoolean() ? this : Main.H.g; Main.H.g = o; } } class C extends Base { String f; C(int d) { super(); int z = 10 / d; this.f = "x"; } public String get() { return this.f; } } public class Main { static Holder H = new Holder(); public static void main(String[] a) { try { new C(Verifier.nondetInt()); } catch (RuntimeException e) { } String s = H.g.get(); int n = s.length(); } }' + 'concat overflows on JDK 8' | '8' | 'public class Main { public static void main(String[] a) { String s = Verifier.nondetString(); String t = s.concat(s); } }' + 'StringBuilder constructor overflows' | '8' | 'public class Main { public static void main(String[] a) { StringBuilder sb = new StringBuilder(Verifier.nondetString()); } }' + 'library default method' | '8' | 'class It implements java.util.Iterator { public boolean hasNext() { return false; } public Object next() { return null; } } public class Main { public static void main(String[] a) { if (Verifier.nondetInt() == 42) new It().remove(); } }' + 'library default calls back' | '17' | 'class X implements CharSequence { public int length() { throw new IllegalStateException(); } public char charAt(int i) { return 0; } public CharSequence subSequence(int s, int e) { return this; } public String toString() { return ""; } } public class Main { public static void main(String[] a) { if (Verifier.nondetBoolean()) { boolean b = new X().isEmpty(); } } }' + 'library default via app interface' | '8' | 'interface J extends java.util.Iterator {} abstract class Ab implements J { public boolean hasNext() { return false; } } class It extends Ab { public Object next() { return null; } } public class Main { public static void main(String[] a) { J j = new It(); j.remove(); } }' + } + + static final String STEAL = 'static { try { java.lang.reflect.Field f = Class.forName("Main").getDeclaredField("o"); f.setAccessible(true); f.set(null, null); } catch (Exception e) {} }' + + @Unroll + def "classes the JVM loads differently than modelled: #name"() { + when: + def r = check(program, '8', extra) + + then: + !r.exceptionFree + !r.assertionFree + + where: + name | program | extra + 'Verifier package helper' | 'public class Main { static Object o = new Object(); public static void main(String[] a) { int z = org.sosy_lab.sv_benchmarks.Helper.X; System.out.println(o.hashCode() + z); } }' | ['org/sosy_lab/sv_benchmarks/Helper.java': 'package org.sosy_lab.sv_benchmarks; public class Helper { public static int X; ' + STEAL + ' }'] + 'javax class nulls a field' | 'public class Main { static Object o = new Object(); public static void main(String[] a) { int z = javax.mine.Helper.X; System.out.println(o.hashCode() + z); } }' | ['javax/mine/Helper.java': 'package javax.mine; public class Helper { public static int X; ' + STEAL + ' }'] + 'javax class asserts' | 'public class Main { public static void main(String[] a) { int z = javax.mine.Config.LEVEL; System.out.println(z); } }' | ['javax/mine/Config.java': 'package javax.mine; public class Config { public static int LEVEL; static { LEVEL = -1; assert LEVEL >= 0 : "bad level"; } }'] + 'java class field' | 'public class Main { public static void main(String[] a) { int z = java.mine.Config.LEVEL; System.out.println(z); } }' | ['java/mine/Config.java': 'package java.mine; public class Config { public static int LEVEL = 1; }'] + 'java class array' | 'public class Main { public static void main(String[] a) { Object[] arr = new java.mine.Config[1]; System.out.println(arr.length); } }' | ['java/mine/Config.java': 'package java.mine; public class Config { public static int LEVEL = 1; }'] + 'class shadowed by the JDK' | 'import org.xml.sax.helpers.AttributesImpl; public class Main { public static void main(String[] a) { AttributesImpl x = new AttributesImpl(); x.removeAttribute(0); } }' | ['org/xml/sax/helpers/AttributesImpl.java': 'package org.xml.sax.helpers; public class AttributesImpl { public AttributesImpl() {} public void removeAttribute(int index) {} }'] + } + + @Unroll + def "proven exception-free: #name"() { + expect: + check(program, release).exceptionFree + + where: + name | release | program + 'arithmetic and an assert' | '8' | 'public class Main { public static void main(String[] a) { double x = Verifier.nondetDouble(); Verifier.assume(x > 0); double y = Math.sin(x) * x; assert y < 10; } }' + 'division by a constant' | '8' | 'public class Main { public static void main(String[] a) { int x = Verifier.nondetInt(); int d = 4; int y = x / d + x % 3; } }' + 'array with constant indices' | '8' | 'public class Main { public static void main(String[] a) { double[] arr = new double[] {1, 2, 3}; arr[2] = arr[0] + Verifier.nondetDouble(); } }' + 'fields assigned in the constructor' | '8' | 'class B { boolean on; boolean isOn() { return on; } } class A { B b; B c; A() { b = new B(); c = b; } void step() { if (b.isOn()) c.on = false; } } public class Main { public static void main(String[] a) { A x = new A(); while (Verifier.nondetBoolean()) x.step(); } }' + 'enum switch' | '8' | 'enum L { LOW, HIGH } class S { L l = L.LOW; void up() { switch (l) { case LOW: l = L.HIGH; break; default: break; } } } public class Main { public static void main(String[] a) { S s = new S(); if (Verifier.nondetBoolean()) s.up(); assert s.l != L.HIGH; } }' + 'string switch on input' | '8' | 'public class Main { public static void main(String[] a) { String s = Verifier.nondetString(); int r; switch (s) { case "a": r = 1; break; default: r = 0; } } }' + 'recursion' | '8' | 'public class Main { static int f(int n) { return n <= 0 ? 0 : 1 + f(n - 1); } public static void main(String[] a) { int r = f(Verifier.nondetInt()); assert r >= 0; } }' + 'caught division' | '8' | 'public class Main { public static void main(String[] a) { try { int y = 1 / Verifier.nondetInt(); } catch (ArithmeticException e) { assert false; } } }' + 'finally after a safe body' | '8' | 'public class Main { static int n; public static void main(String[] a) { try { n = Verifier.nondetInt(); } finally { n++; } } }' + 'checked exception' | '8' | 'public class Main { public static void main(String[] a) throws Exception { if (Verifier.nondetBoolean()) throw new Exception(); } }' + 'printing' | '8' | 'public class Main { public static void main(String[] a) { int x = Verifier.nondetInt(); System.out.println("x=" + x); } }' + 'static field from initializer' | '8' | 'public class Main { static String s = "abc"; static int[] t = new int[2]; public static void main(String[] a) { t[1] = s.length(); } }' + 'string concatenation with indy' | '17' | 'public class Main { public static void main(String[] a) { int x = Verifier.nondetInt(); String s = "x=" + x + Verifier.nondetString(); assert s.length() > 0; } }' + } +} 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/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/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/trace/SymbolRegistrySpec.groovy b/symbolic-executor/src/test/groovy/de/uzl/its/swat/symbolic/trace/SymbolRegistrySpec.groovy new file mode 100644 index 00000000..60986264 --- /dev/null +++ b/symbolic-executor/src/test/groovy/de/uzl/its/swat/symbolic/trace/SymbolRegistrySpec.groovy @@ -0,0 +1,120 @@ +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() { + ThreadHandler.init() + 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/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/FloatValueTest.groovy b/symbolic-executor/src/test/groovy/de/uzl/its/value/primitive/numeric/floatingpoint/FloatValueTest.groovy index 1081d342..06ac051b 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 @@ -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..89e6db3f --- /dev/null +++ b/symbolic-executor/src/test/groovy/de/uzl/its/value/reference/ContainerContentsTest.groovy @@ -0,0 +1,112 @@ +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() { + ThreadHandler.init() + 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..a056adaa --- /dev/null +++ b/symbolic-executor/src/test/groovy/de/uzl/its/value/reference/lang/NumberParsingExactnessTest.groovy @@ -0,0 +1,305 @@ +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", "ูก", "ูกูขูฃ", "-ู ู ูง", "๏ผ‘๏ผ’", "๏ฝ", "เฅงเฅจ", + "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() { + ThreadHandler.init() + 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) + } + + 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..c792ad61 --- /dev/null +++ b/symbolic-executor/src/test/groovy/de/uzl/its/value/reference/lang/StringCaseRegexModelExactnessTest.groovy @@ -0,0 +1,517 @@ +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 'ว…', 'ล‰', a ligature, Greek, surrogates. */ + static final List CASE_SAMPLES = ["", "a", "Ab", "aB", "รŸ", "SS", "ss", "ฤฐ", "i", "I", "ฤฑ", + "ลฟ", "s", "ยต", "ฮœ", "ฮผ", "รฟ", "ลธ", "ว„", + "ว…", "ว†", "ล‰", "๏ฌ€", "ฯƒ", "ฯ‚", "ฮ", + "x" + DESERET_UPPER, "x" + DESERET_LOWER, "\uD801", "\uDC00\uD801", + "แ‚ ", "โด€", "", "", "W1E!+O", "w1e!+o"] + + def setup() { + ThreadHandler.init() + 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", "ฤฑ", "ยต", "ว…", "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", "รŸ", "ว„", "ว†", "ฤฑ", "I", DESERET_UPPER, DESERET_LOWER, + "\uD801๐€", "๐จX", "๐จ\uD801"], ["", "a", "AB", "ว…", "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", "รŸ", "รฟ", "ลธ", "ว…", "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', '๏ผก', 'ูฃ', '-'], [-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..105cfea5 --- /dev/null +++ b/symbolic-executor/src/test/groovy/de/uzl/its/value/reference/lang/StringModelExactnessTest.groovy @@ -0,0 +1,681 @@ +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() { + ThreadHandler.init() + 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() + } + + 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 b31c470b..c9742a9a 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 @@ -194,7 +195,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) @@ -204,20 +205,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() @@ -225,6 +230,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-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..c42cb83d 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 @@ -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") @@ -131,6 +142,37 @@ def init_args(parser: argparse.ArgumentParser): 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("--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)") + parser.add_argument("--sa-retry-without", action="store_true", help="On a SAFE verdict reached with static pre-analysis, disable it and explore again (guards against false SAFE from incomplete SA)", default=False) + 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("--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-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("--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") @@ -148,6 +190,11 @@ def init_args(parser: argparse.ArgumentParser): # Initialize loggers log.initialize_loggers(args.logdir) + 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 diff --git a/symbolic-explorer/constraint/ConstraintController.py b/symbolic-explorer/constraint/ConstraintController.py index 7b205a09..29e7b7aa 100755 --- a/symbolic-explorer/constraint/ConstraintController.py +++ b/symbolic-explorer/constraint/ConstraintController.py @@ -42,6 +42,10 @@ def post(request: ConstraintRequest, endpointID: str = Query(...), traceID: str 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={ @@ -53,7 +57,11 @@ def post(request: ConstraintRequest, endpointID: str = Query(...), traceID: str '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 14cee249..1a08f27e 100755 --- a/symbolic-explorer/constraint/ConstraintService.py +++ b/symbolic-explorer/constraint/ConstraintService.py @@ -1,10 +1,10 @@ -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, InvocationItem +from parse.DataTransferObjects import TraceItem, InputItem, UFItem, InvocationItem, NondetReturnItem, ViolationItem from data.trace.Special import Special from data.trace.Branch import Branch @@ -24,7 +24,11 @@ class ConstraintService: 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, - missing_invocations: List[InvocationItem] = None): + 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. @@ -42,19 +46,40 @@ def add_constraints(endpoint_id: str, trace_id: str, trace: List[TraceItem], inp 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) - # 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 [])] - # 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) - logger.info(f'[CONSTRAINT SERVICE] Added trace {trace_id} to endpoint {endpoint_id}') + try: + # 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]}') + logger.info(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.info(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/Node.py b/symbolic-explorer/data/BinaryExecutionTree/Node.py index d7330be6..108d6b67 100755 --- a/symbolic-explorer/data/BinaryExecutionTree/Node.py +++ b/symbolic-explorer/data/BinaryExecutionTree/Node.py @@ -4,7 +4,7 @@ from typing import List, Optional 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 @@ -28,18 +28,21 @@ 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]): + def __init__(self, parent: Optional['Node'], branch, inputs: List[Input], ufs: List[UF]): """ - 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. """ @@ -52,20 +55,25 @@ def __init__(self, parent: Optional['Node'], trace: list, inputs: List[Input], u self.gid = GLOBAL_IID # Assign a unique global identifier GLOBAL_IID += 1 self.constraint = {} + self.inst = None # Only set for Special nodes + self.concrete_only = False # A branch recorded only to keep the SA walk in step, see Branch + # Only for 'Branch' nodes: its key, and the frame stack change since the previous branch + # (see Branch). The same on every trace through the node, since its path is the same. + self.key = None + self.pops = 0 + self.pushes = () + self.callback = False # in a callback from library code: no counterpart in the SA graph + self.choice = False # added by the executor to enumerate an operand's values: not in the SA graph + # 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). + self.unmodelled_exception = False + # Only for 'Branch' nodes: the loop-head states reached before this branch, since the + # previous one, each with its position (see Tree.register_loop_states). + self.loop_states = () - # Validate trace argument - if trace is None or len(trace) == 0: + if branch is None: raise ValueError(f'Cannot create Node with no branch!') - # 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 @@ -73,15 +81,35 @@ def __init__(self, parent: Optional['Node'], trace: list, inputs: List[Input], u # 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.loop_states = getattr(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 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): """ diff --git a/symbolic-explorer/data/BinaryExecutionTree/Tree.py b/symbolic-explorer/data/BinaryExecutionTree/Tree.py index 7544bc0f..458a61b4 100755 --- a/symbolic-explorer/data/BinaryExecutionTree/Tree.py +++ b/symbolic-explorer/data/BinaryExecutionTree/Tree.py @@ -1,6 +1,6 @@ from typing import List, Optional, Set, Union from data.trace.Branch import Branch -from data.trace.Special import Special +from data.trace.Special import Special, UNMODELLED_EXCEPTION from data.BinaryExecutionTree.Leaf import Leaf from data.BinaryExecutionTree.Node import Node from data.trace.Input import Input @@ -8,9 +8,11 @@ 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. @@ -34,6 +36,14 @@ def __init__(self, endpoint_id: Union[str, int]): 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() @@ -44,6 +54,11 @@ def __init__(self, endpoint_id: Union[str, int]): # [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. self.execution_errors: list = [] + # 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, tuple] = {} + # Branches the last dfs() left out because of a repeated loop-head state. + self.loop_state_pruned = 0 def record_inputs(self, inputs: List[Input]): @@ -125,65 +140,160 @@ def record_reference_semantic_change(self): 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) -> list[tuple]: + """ + 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). + """ + 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 branch to the tree based on the provided trace and inputs. + 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 + 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: + 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 + self._attach(parent, branched, Leaf(parent, inputs, ufs)) + 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]): """ - 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: - 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) + 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 = getattr(element, 'loop_states', ()) + if states: + positioned = [] + 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 - return node - + def _attach(self, parent: Optional[Node], branched: bool, child: Union[Node, Leaf]): + """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): + """ + 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 = [] + work = [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): + 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 # VISUALIZATIONS def get_constraint_label(self, parent: Node, node: Union[Node, Leaf]): @@ -195,8 +305,12 @@ def get_constraint_label(self, parent: Node, node: Union[Node, Leaf]): return "" def add_to_dot(self, node: Optional[Union[Node, Leaf]], graph, parent: Optional[Node] = None): - """Recursively add nodes and edges to the DOT graph.""" - if node is not None: + """Add the nodes and edges below node to the DOT graph.""" + work = [(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 @@ -204,12 +318,13 @@ 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) + work.append((node.skipped, node)) + work.append((node.branched, node)) def plot_tree(self, idx): """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") diff --git a/symbolic-explorer/data/Database.py b/symbolic-explorer/data/Database.py index 489190bc..a7c7eea7 100755 --- a/symbolic-explorer/data/Database.py +++ b/symbolic-explorer/data/Database.py @@ -26,59 +26,56 @@ 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 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 +85,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,175 +102,257 @@ 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, missing_invocations: list = None): + 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_missing_invocations(missing_invocations) - 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() + 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): + """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 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): endpoint_id = str(endpoint_id) - lock.acquire() - self.tree[endpoint_id].record_execution_error(kind, message) - lock.release() + 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() - - 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 + 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 - 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/SAGraph.py b/symbolic-explorer/data/StaticAnalysisGraph/SAGraph.py new file mode 100644 index 00000000..6938a0c9 --- /dev/null +++ b/symbolic-explorer/data/StaticAnalysisGraph/SAGraph.py @@ -0,0 +1,675 @@ +from __future__ import annotations +from dataclasses import dataclass, field +from typing import Optional, Tuple +from collections import deque +import json +import math +import re + +# 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 gives up with "no information", which is always safe. +MAX_WALK_STEPS = 100_000 +MAX_STACK_DEPTH = 10_000 + +@dataclass(eq=False) +class SANode: + id: str + # What an assert 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. + # reachesAssertSameLevel: 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 + reachesAssertSameLevel: bool = False + canReturn: bool = False + escapes: frozenset[tuple[str, ...]] = frozenset() + isAssert: bool = False # an assertion point, including those the extractor reports for incomplete nodes + isPhantomGuard: bool = False # a phantom guard is a generated explicit check for an implicit exception, e.g. `if (op2 != 0)` before an IDIV. + # The static analysis could not expand something here (a call whose target it could not pin + # down, or a throw whose destination it could not), so the statements and branches of + # whatever was left out are missing from the graph. We walk this graph in step with a real + # execution, matching branch for branch by position, so past this node the execution reports + # branches the graph has no counterpart for and every later pairing would be shifted by them. + # walk_till_branch() therefore stops here and reports "no information", which callers already + # treat as "everything is 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: tuple[str, ...] | None = None + prev: list[SANode] = field(default_factory=list, repr=False) # preceding nodes + next_fallthrough: SANode | None = None + next_branched: SANode | None = None + next_exceptional: list[SANode] = field(default_factory=list, repr=False) + # RETURN edges (exit -> every return site of the method) and EXCEPTIONAL_RETURN edges + # (uncaught[T] -> every handler T can unwind to). The walk never follows them -- which one + # applies depends on the call stack -- but they connect callers and callees for the backward + # marking of paths that lead to an assert. + next_returns: list[SANode] = field(default_factory=list, repr=False) + next_exceptional_returns: list[SANode] = field(default_factory=list, repr=False) + # Only for a call site with a CALL edge: the entries of the methods it may call (one, or + # several known overrides; with one it is also next_fallthrough, which the walk follows), + # 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 as the executor reports it (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 an assertion point, + # counting this node if it is a branch. Computed by compute_assert_distances(). Context- + # insensitive, so only a heuristic for ordering the search; pruning never depends on it. + assertDistance: float = math.inf + + def has_fallthrough_child(self): + return self.next_fallthrough is not None + + def has_branched_child(self): + return self.next_branched is not None + + def get_fallthrough_child(self) -> SANode: + assert self.next_fallthrough is not None + return self.next_fallthrough + + def get_branched_child(self) -> SANode: + assert self.next_branched is not None + return self.next_branched + + def is_branch(self): + if self.has_branched_child(): + assert self.has_fallthrough_child() + return True + return False + + def is_call_site(self): + return self.return_site is not 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 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, so stop trusting it + 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.get_fallthrough_child() # the CALL edge, into the callee + continue + if node.has_fallthrough_child(): + node = node.get_fallthrough_child() + 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.return_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 + + def add_fallthrough_child(self, child: SANode): + assert self.next_fallthrough is None, f"SANode {self.id} has multiple fallthrough edges!" + self.next_fallthrough = child + + def add_branched_child(self, child: SANode): + assert self.next_branched is None, f"SANode {self.id} has multiple branched edges!" + self.next_branched = child + + def add_exceptional_child(self, child: SANode): + self.next_exceptional.append(child) + + def add_return_child(self, child: SANode): + self.next_returns.append(child) + + def add_exceptional_return_child(self, child: SANode): + self.next_exceptional_returns.append(child) + + +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) -> int: + return 0 if stack is None else stack[2] + + +def unwind(exception_chain: tuple[str, ...], 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. The most specific handler of a frame wins, matching + the rule the extractor wires its EXCEPTIONAL_RETURN edges with. + """ + while stack is not None: + frame, stack = pop(stack) + for exception_type in exception_chain: + handler = frame.handlers.get(exception_type) + if handler is not None: + return handler, stack + return None + + +def is_pseudo(exception_chain: tuple[str, ...]) -> 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 = [] + while stack is not None: + frame, stack = pop(stack) + resumed.extend((handler, stack) for handler in frame.unknown_handlers.get(pseudo_type, [])) + return resumed + + +@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 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): a CallStack whose cells 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). LOST once the trace popped more frames than are +# known, e.g. returning from a frame entered before tracing began. +LOST = "LOST" + + +def apply_stack_delta(stack, pops: int, pushes: tuple, index: GraphIndex | None): + """`stack` (a trace stack, see LOST) after `pops` frames were popped and `pushes` pushed.""" + 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 = push(stack, call_site) + return stack + + +def same_stacks(walk: CallStack, trace) -> 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): + """ + 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's call 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 # nothing to check against (also: a trace from before keys and stack deltas) + 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, index: GraphIndex | None) -> tuple[SANode, CallStack] | None: + """ + Where the walk resumes after it lost the graph, at a branch: the graph's branch with the trace's + `key`, under the trace's call 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 = [] + s = trace_stack + while s is not None: + if s[0] is None: + return None + frames.append(s[0]) + s = s[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} # the CALL edges' callees + if node.method is None or node.method not in methods: + return None + return node, trace_stack + + +def is_interesting(node: SANode, stack: CallStack) -> bool: + """ + Whether an assert 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: an assert 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 = [(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.reachesAssertSameLevel: + 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.return_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) -> None: + """ + Computes each node's reachesAssertSameLevel, canReturn and escapes, as the least fixpoint of + + reachesAssertSameLevel(N) = isAssert(N) + or N is a call site into E, returning to R: + reachesAssertSameLevel(E) + or canReturn(E) and reachesAssertSameLevel(R) + or reachesAssertSameLevel(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 reachesAssert... + 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 and not n.is_call_site(): + 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.return_site, *n.handlers.values(), *unknown]: + dependents[id(s)].append(n) + + def evaluate(n: SANode) -> tuple[bool, bool, frozenset]: + if n.isIncomplete: + return True, True, frozenset() + ras = n.isAssert + cr = n.isExit + esc = set() + if n.exceptionChain is not None: + esc.add(n.exceptionChain) + for s in successors(n): + ras = ras or s.reachesAssertSameLevel + cr = cr or s.canReturn + esc |= s.escapes + if n.is_call_site(): + ret = n.return_site + for entry in n.callees: # any of them may run + ras = ras or entry.reachesAssertSameLevel + if entry.canReturn: + ras = ras or ret.reachesAssertSameLevel + cr = cr or ret.canReturn + esc |= ret.escapes + for chain in entry.escapes: + if is_pseudo(chain): + # Caught by any of the candidate handlers, or by none: then it escapes further. + for handler in n.unknown_handlers.get(chain[0], []): + ras = ras or handler.reachesAssertSameLevel + cr = cr or handler.canReturn + esc |= handler.escapes + esc.add(chain) + continue + handler = next((n.handlers[t] for t in chain if t in n.handlers), None) + if handler is None: + esc.add(chain) + else: + ras = ras or handler.reachesAssertSameLevel + cr = cr or handler.canReturn + esc |= handler.escapes + return ras, cr, frozenset(esc) + + worklist = list(nodes) + queued = {id(n) for n in nodes} + while worklist: + n = worklist.pop() + queued.discard(id(n)) + ras, cr, esc = evaluate(n) + if (ras, cr, esc) != (n.reachesAssertSameLevel, n.canReturn, n.escapes): + n.reachesAssertSameLevel, n.canReturn, n.escapes = ras, cr, esc + for d in dependents[id(n)]: + if id(d) not in queued: + queued.add(id(d)) + worklist.append(d) + + +def mark_assertion_path(node: SANode): + """ + Context-insensitive marking: every node with any path to `node`, following predecessors. + + Only used for graphs from extractors that predate CALL edges with return addresses, for which + the summaries cannot be computed. Sound but imprecise: it marks a shared callee's branch + interesting if any caller leads to an assert. + """ + # A worklist rather than recursion: the chain of predecessors is as long as the longest path + # through the graph, which easily exceeds Python's recursion limit. + worklist = [node] + while worklist: + current = worklist.pop() + if current.reachesAssertSameLevel: # already marked, prevent infinite looping + continue + current.reachesAssertSameLevel = True + worklist.extend(prev for prev in current.prev if not prev.reachesAssertSameLevel) + + +def compute_assert_distances(nodes) -> None: + """ + Sets each node's assertDistance: the fewest branches on any path from the node to an assertion + 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() + for n in nodes: + n.assertDistance = math.inf + if n.isAssert: + n.assertDistance = 0 + worklist.append(n) + while worklist: + current = worklist.popleft() + for pred in current.prev: + cost = 1 if pred.is_branch() else 0 + d = current.assertDistance + cost + if d < pred.assertDistance: + pred.assertDistance = d + if cost == 0: + worklist.appendleft(pred) + else: + worklist.append(pred) + + +# 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" + + +# 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"} + + +class SAGraph: + def __init__(self): + self.json_graph = {} + self.nodes: dict[str, SANode] = {} + self.entry_node: SANode | 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 an assert + 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): + with open(path, "r") as f: + self.json_graph = json.load(f) + + for json_node in self.json_graph["nodes"]: + id = json_node["id"] + chain = json_node.get("exceptionTypeChain") + # Older graphs carry no isIncomplete field; absent means the node is faithful. + self.nodes[id] = SANode(id, isIncomplete=json_node.get("isIncomplete", False), + exceptionChain=tuple(chain) if chain is not None else None) + metadata = json_node.get("metadata") + if isinstance(metadata, str) and metadata.startswith("method: "): + self.nodes[id].method = metadata[len("method: "):] + self.nodes[id].key = json_node.get("key") + 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] + + for exit_id in self.json_graph.get("exitNodeIds", []): + self.nodes[exit_id].isExit = True + + legacy_calls = False # a CALL edge without a return address, see below + 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 == "EXCEPTION": + source.add_exceptional_child(target) + + elif etype == "PHANTOM_TRUE_BRANCH": + source.add_branched_child(target) + source.isPhantomGuard = True + + elif etype == "TRUE_BRANCH": + source.add_branched_child(target) + + elif etype == "CALL": + # Graphs from before the call stack carry no return address. Their call sites then + # walk like a plain fall-through, their exits find an empty stack and give up, and + # they are marked context-insensitively. + if "returnSite" in json_edge: + 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()} + else: + source.add_fallthrough_child(target) + legacy_calls = True + + elif etype == "RETURN": + source.add_return_child(target) + + elif etype == "EXCEPTIONAL_RETURN": + source.add_exceptional_return_child(target) + + elif etype in FALLTHROUGH_EDGES: + source.add_fallthrough_child(target) + + else: + raise ValueError(f"Unknown edge type {etype} from {source.id} to {target.id}") + + target.prev.append(source) + + + # A call site with one callee walks into it like a fall-through. + for n in self.nodes.values(): + if len(n.callees) == 1: + n.add_fallthrough_child(n.callees[0]) + + self.entry_node = self.nodes[self.json_graph["entryNodeId"]] + graph_index = GraphIndex(keys, self.entry_node.method, + callbacks_reach_assert=bool(self.json_graph["metadata"].get("callbacksReachingAssert"))) + for n in self.nodes.values(): + n.graph_index = graph_index + + for assertion_point_id in self.json_graph["metadata"]["assertionPointIds"]: + self.nodes[assertion_point_id].isAssert = True + # An incomplete node may hide an assert in the part that was not expanded, so the paths + # leading to it have to stay interesting. The extractor already reports these as assertion + # points; treating them as such here too means a graph that ever forgets one half still + # errs on the safe side rather than pruning a path that leads to something we cannot see. + for node in self.nodes.values(): + if node.isIncomplete: + node.isAssert = True + + if legacy_calls: + for node in self.nodes.values(): + if node.isAssert: + mark_assertion_path(node) + else: + compute_summaries(list(self.nodes.values())) + compute_assert_distances(list(self.nodes.values())) + + + +if __name__ == "__main__": + import sys + tree = SAGraph() + tree.load_json_graph(sys.argv[1]) + print("not reachesAssertSameLevel:", [n.id for n in tree.nodes.values() if not n.reachesAssertSameLevel]) diff --git a/symbolic-explorer/data/trace/Branch.py b/symbolic-explorer/data/trace/Branch.py index 9b705c69..03298fc0 100755 --- a/symbolic-explorer/data/trace/Branch.py +++ b/symbolic-explorer/data/trace/Branch.py @@ -13,7 +13,9 @@ 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 = (), callback: bool = False, choice: bool = False, + loop_states: tuple = ()): """ Initializes a new instance of the Branch class. @@ -27,6 +29,19 @@ 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 + # The loop-head states reached since the previous branch (see Tree.register_loop_states). + self.loop_states = loop_states def __str__(self): 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/driver/SVCompDriver.py b/symbolic-explorer/driver/SVCompDriver.py index d3d75756..26e2231f 100644 --- a/symbolic-explorer/driver/SVCompDriver.py +++ b/symbolic-explorer/driver/SVCompDriver.py @@ -1,4 +1,6 @@ +import copy import glob +import sys import signal import subprocess import os @@ -8,16 +10,23 @@ 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 strategy.StrategyService import StrategyService +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 timing.TimingManager import TimingManager @@ -25,7 +34,33 @@ 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 integral input 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. +RETRY_BOUNDS = (16, 256, 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 @@ -35,6 +70,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" @@ -63,8 +100,20 @@ 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 | None = None + # 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: def __init__(self, args): self.args = args @@ -72,6 +121,31 @@ def __init__(self, args): self.symbolicStorage = SymbolicStorage() self.shutdown_flag = False self.verification_category = VerificationCategory(self.args.property) + self.sa_graph = 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]] = {} + # 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 + 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) @contextmanager def pushd(self, dirname): @@ -83,13 +157,15 @@ 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 ":" cmd = [ java_path, - f'-Xmx{mem}g', + f'-Xmx{heap_mib or self.budget.heap_mib}m', + *EXECUTOR_JVM_OPTIONS, f'-Dconfig.path={config}', f'-Dexplorer.port={port}', f'-javaagent:{javaagent}', @@ -104,21 +180,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: @@ -133,12 +218,33 @@ 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 logger.critical(f'[SVCOMP] SWAT Assertion failed: {l}') @@ -162,14 +268,40 @@ def determine_next_step(self, status: ExecutionStatus, output: List[str] ) -> Ac 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 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 len(Database.instance().get_tree(ENDPOINT_ID).symbolic_vars) == 0: + 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 @@ -180,6 +312,28 @@ 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: @@ -189,14 +343,30 @@ def log_output(self, output: List[str]): 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) + # 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) @@ -205,30 +375,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) # Visualize DB tree # print("Plotting DB Tree...", flush=True) - # Database.instance().get_tree(0).plot_tree(round_idx) - round_idx += 1 + # 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') @@ -241,6 +422,18 @@ 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 we are using SA and come to a SAFE verdict, then continue without SA + # this should prevent false SAFE classifications from incomplete SA + if self.args.sa_retry_without and self.sa_graph.entry_node is not None \ + and next_step == Action.REPORTVERDICT and self.state.verdict == Verdict.SAFE: + logger.info(f'[SYMBOLIC EXPLORATION] No violation found. Retrying without SA.') + self.sa_graph = SAGraph() # disable SA + next_step = self.retrieve_solution() # and try again if next_step == Action.REPORTVERDICT: return self.state.verdict @@ -250,44 +443,219 @@ def run_testcase(self, java_path, agentpath: str, configpath: str, z3path, port, + 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) -> 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 + # 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 self.state.solution_is_guess: + Database.instance().record_expected_divergence(ENDPOINT_ID) + failed = self.failed_solutions.setdefault(target.gid, []) + failed.append(self.state.solution or {}) + if len(failed) <= self.divergence_retries: + logger.info(f'[SVCOMP] Diverged from branch {target.id}{" (approximate model)" if self.state.solution_is_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, self.state.solution_is_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.""" + return [n for n, branched in self.given_up if (n.branched if branched else n.skipped) is None] + + 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, 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] 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): + """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 + 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: + 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): - possible_branches = StrategyService.select_branch(endpoint_id=ENDPOINT_ID) + 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) 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') - continue - branch_found = True - #logger.info(f'[SYMBOLIC EXPLORATION] Solving for branch {branch.id}') - sat, sol = StrategyService.solve_branch(branch, solver_timeout_ms=None) - - 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 - 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') + 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 - - if sat == SATResult.UNKNOWN: - logger.info(f'[SYMBOLIC EXPLORATION] SAT result is UNKNOWN') - self.state.verdict = Verdict.UNKNOWN - 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 @@ -304,28 +672,80 @@ 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: + # 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() @@ -337,12 +757,57 @@ def run(self): # Write the consolidated per-testcase statistics (verdict, soundness flags, missing # invocations and the context-loss subset) so the analysis can rely on structured data. - stats_file = os.path.join(log_dir, 'stats.json') - write_testcase_stats(Path(stats_file), verdict, self.verification_category, Database.instance().get_tree(ENDPOINT_ID)) + self.write_stats(verdict) self.kill_current_process() + + 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.sa_adopted_round), + 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..2e530f14 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,21 +90,28 @@ 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: + for line in proc.stdout or []: logger.info(f'[EXECUTOR] --> {line.strip()}') stdout.append(line) logger.info(f'[EXPLORER] Java Output End') @@ -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/parse/DataTransferObjects.py b/symbolic-explorer/parse/DataTransferObjects.py index 4d46be72..a513741a 100644 --- a/symbolic-explorer/parse/DataTransferObjects.py +++ b/symbolic-explorer/parse/DataTransferObjects.py @@ -10,6 +10,20 @@ 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 + # Hashes of the loop-head states the execution reached since the previous branch, in order. + loopStates: list[str] = [] class UFItem(BaseModel): definition: str @@ -37,14 +51,57 @@ class InvocationItem(BaseModel): 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..c2e974e6 100755 --- a/symbolic-explorer/parse/TraceParser.py +++ b/symbolic-explorer/parse/TraceParser.py @@ -18,7 +18,14 @@ 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, + 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..ba2a51b5 --- /dev/null +++ b/symbolic-explorer/solver/Int2BvLifting.py @@ -0,0 +1,330 @@ +""" +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, in [-1, max char]) 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. + +`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. `rewrite` records the strings whose length 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 that +bound in every model (and its models are models of the original query). +""" + +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_CHAR_CODE = 0x10FFFF + +_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): + self._alive = [] + self._rewritten = {} + self._lifted = {} + self._ranges = {} + self._mentions_int2bv = {} + # Strings whose length bound a rewrite relied on, by expression id + self._bounded = {} + self._needs = {} + + # 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), () + 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: + return (-1, MAX_CHAR_CODE), () + if k == z3.Z3_OP_BV2INT: + 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, () + + # 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 + 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 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 _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 _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), else None.""" + cond, then, other = e.children() + if _kind(cond) != z3.Z3_OP_SLEQ or _kind(then) != z3.Z3_OP_BV2INT: + return None + zero, x = cond.children() + if not (z3.is_bv_value(zero) and zero.as_long() == 0 and then.arg(0).eq(x)): + return None + m = 2 ** x.size() + 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 they rely on.""" + out = [lifting.rewrite(x) for x in exprs] + strings = {} + for x in exprs: + for s in lifting.needs(x): + strings[s.get_id()] = s + out.extend(z3.Length(s) <= JAVA_MAX_STRING_LENGTH for s in strings.values()) + return out diff --git a/symbolic-explorer/solver/SolverHandler.py b/symbolic-explorer/solver/SolverHandler.py index 471f0f8e..c8e0cf4a 100755 --- a/symbolic-explorer/solver/SolverHandler.py +++ b/symbolic-explorer/solver/SolverHandler.py @@ -1,6 +1,7 @@ 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,12 +13,38 @@ 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 + + class Z3Handler: """Class to handle symbolic solving and related tasks.""" @@ -176,7 +203,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 +269,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 +392,25 @@ def write_optimizer_to_file(optimizer: Optimize, output_dir: str = "smt_files") return filepath @staticmethod - def solve(node: Any, path_constraints: list, timeout_ms: int | None = 60 * 1000) -> Tuple[SATResult, Dict[str, Any]]: + def lift_int2bv(cache, assertions: list) -> list: + """The assertions with comparisons over int_to_bv of string lengths, indices and char codes + made over the Integers (see Int2BvLifting), which Z3 decides much faster. Equivalent for + Java strings (at most 2^31 - 1 chars); on any error, the assertions as they are.""" + from solver.Int2BvLifting import Int2BvLifting, lift_query + try: + lifting = getattr(cache, 'int2bv_lifting', None) + if lifting is None: + lifting = cache.int2bv_lifting = Int2BvLifting() + if not any(lifting.mentions_int2bv(a) for a in assertions): + return assertions + return lift_query(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 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 +420,18 @@ def solve(node: Any, path_constraints: list, timeout_ms: int | None = 60 * 1000) 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) @@ -401,16 +457,19 @@ def solve(node: Any, path_constraints: list, timeout_ms: int | None = 60 * 1000) # 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) 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) + assertions = [Not(expr) for expr in node_exprs] + all_exprs[len(node_exprs):] + solver.add(*Z3Handler.lift_int2bv(cache, assertions)) - # Add all path constraint expressions (already parsed and cached) - for i, path_expr in enumerate(all_exprs[len(node_exprs):]): - solver.add(path_expr) + for expr in extra_exprs or []: + solver.add(expr) - t_path = time.time() - t_start + # 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) @@ -436,13 +495,18 @@ def solve(node: Any, path_constraints: list, timeout_ms: int | None = 60 * 1000) if timeout_ms is not None: solver.set("timeout", timeout_ms) - t_start = time.time() + if rlimit is not None: + # Z3 gives each check this much on top of what the (shared) context has used already. + solver.set("rlimit", rlimit) + t_start = time.perf_counter() res = solver.check() - t_check = time.time() - t_start + t_check = time.perf_counter() - t_start + TimingManager.instance().record_solver_time(t_check) + rlimit_used = _rlimit_used(solver) if str(res) == SATResult.SAT.value: - t_start = time.time() + t_start = time.perf_counter() sol = solver.model() # Log model to disk for debugging (if enabled) @@ -462,33 +526,68 @@ def solve(node: Any, path_constraints: list, timeout_ms: int | None = 60 * 1000) 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. + 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 + 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})") + if str(res) == SATResult.UNSAT.value: + return SATResult.UNSAT, {} + # 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. + reason = solver.reason_unknown() + 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 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 + integral input is restricted to [-bound, bound] (chars: [0, bound]). + + 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)) + return exprs @staticmethod def solve_with_optimization(node: Any, path_constraints: list) -> Tuple[SATResult, Dict[str, Any]]: @@ -502,6 +601,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) @@ -524,6 +624,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}") @@ -536,8 +640,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..422b6e04 100755 --- a/symbolic-explorer/strategy/DFS.py +++ b/symbolic-explorer/strategy/DFS.py @@ -1,19 +1,144 @@ -from typing import List, Set - from data.BinaryExecutionTree.Node import Node +from data.BinaryExecutionTree.Leaf import Leaf +from data.StaticAnalysisGraph.SAGraph import SANode, CallStack, is_interesting, \ + apply_stack_delta, check_branch, resync +from data.trace.Special import UNMODELLED_EXCEPTION + +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 + +def dfs(visited: set[Node], tree, node: Node | Leaf | None, solved_branches: set[int], unsat_branch_ids: set[int], sa_node: SANode | None = None, clinit_depth: int = 0, sa_stack: CallStack = None, distances: dict[Node, float] | None = None, sa_index=None, trace_stack=None) -> list[Node]: + # sa_node and sa_stack together are where the SA walk stands: the node, and the call stack of + # call sites the walk entered to get there. The same graph node means different things under + # different stacks -- a method's exit returns to whichever call site is on top. + # If `distances` is given, it receives each returned node's SA distance to an assertion point + # along its unexplored side; nodes without SA information are left out. + # sa_index and trace_stack: the graph's keys, and the call stack as the trace reports it (see + # SAGraph.apply_stack_delta), against which every branch of the walk is checked. + possible_nodes = [] + if sa_index is None and sa_node is not None: + sa_index = sa_node.graph_index + # The walk is 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; each entry is a node with the walk's state there. + work = [(node, sa_node, clinit_depth, sa_stack, trace_stack)] + while work: + node, sa_node, clinit_depth, sa_stack, trace_stack = work.pop() + assert clinit_depth >= 0 + if node is None or node in visited or not isinstance(node, Node): + continue -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)) + + if node.unmodelled_exception: + # Some trace threw an exception the graph does not model right before this node (see + # below): the graph cannot say where execution went from there. + sa_node, sa_stack = None, None + + # Static initializers delimit themselves with CLINIT / INVOKECLINIT_END markers, so we + # know whether the branches below this node were executed inside a . The SA + # graph does not model static initializers, so those branches have no counterpart there. + if node.is_clinit_start(): + clinit_depth += 1 + elif node.is_clinit_end(): + # Note: A left via an exception emits no end marker, so the markers are not guaranteed to be balanced. + clinit_depth -= 1 + + if node.kind == "Special": # skip over Special nodes (includes CLINIT / INVOKECLINIT_END) + if node.inst == UNMODELLED_EXCEPTION: + # The executor saw an exception the graph does not model (a null dereference, a + # cast, a library call that threw, an Error): the graph cannot say where execution + # went, so the walk has no information -- until it finds the graph again at a + # later branch by key (resync, below). + sa_node, sa_stack = None, None + work.append((node.branched, sa_node, clinit_depth, sa_stack, trace_stack)) + continue + else: + assert node.kind == "Branch" + + # 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 getattr(node, 'loop_states', ()) and tree.dominated_by_loop_state(node): + tree.loop_state_pruned += 1 + continue # this subtree only; the walk goes on with the other entries + + # The frames entered and left since the previous branch, which happened before this one. + if sa_index is not None: + trace_stack = apply_stack_delta(trace_stack, node.pops, node.pushes, sa_index) + + # If clinit_depth > 0, we do not have corresponding SA information, so pretend that there is no SA and DON'T WALK the SA graph. + # Likewise for a branch in application code that library code called back: the graph walks + # over the library call, so the walk holds its place until the callback returns. + # A choice branch (the executor's search for the element a symbolic index selects) has no + # graph node either: both of its sides continue from the walk's current place. + mask_sa_node = clinit_depth > 0 or node.callback or node.choice + # Whether the walk's pairing of this branch with a graph node was checked (check_branch). + # A pairing that could not be checked is still followed, so that a later branch can be + # checked against the walk, but this branch is not pruned by it. + verified = False + + # Get information on interesting paths. If sa_node is None, all paths should be considered interesting + if sa_node and not mask_sa_node: + 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 False: + # 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}') + sa_node, sa_stack = None, None + elif in_step is not None: + verified = True + trace_stack = sa_stack # the same frames; sharing the cells keeps later checks cheap + if sa_node is None and not mask_sa_node and sa_index is not None: + resumed = resync(node.key, trace_stack, sa_index) + if resumed is not None: + sa_node, sa_stack = resumed + verified = True # found by the trace's key under the trace's own call stack + unpruned = mask_sa_node or (sa_node is None) or not verified + skip_is_interesting = unpruned or is_interesting(sa_node.get_fallthrough_child(), sa_stack) + branch_is_interesting = unpruned or is_interesting(sa_node.get_branched_child(), sa_stack) + + # 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): + logger.log(TRACE, f"[DFS] @{node.id}/{("CALLBACK" if node.callback else "CHOICE" if node.choice else "CLINIT") if mask_sa_node else sa_node and sa_node.id} ({"branched" if node.branched else ""}{"skipped" if node.skipped else ""}): skip_is_interesting={skip_is_interesting}, branch_is_interesting={branch_is_interesting}") + + # Add the node itself, if eligible + if (node.skipped is None and skip_is_interesting) or (node.branched is None and branch_is_interesting): + if node.gid not in solved_branches \ + and node.gid not in unsat_branch_ids \ + and node.kind != "Special" \ + and not node.concrete_only: + possible_nodes.append(node) + if distances is not None and sa_node and not mask_sa_node: + unexplored = sa_node.get_fallthrough_child() if node.skipped is None else sa_node.get_branched_child() + distances[node] = unexplored.assertDistance + + # Walk the SA graph in step with the tree. Each side needs its own successor held in its + # own variable: rebinding one shared local here would leave the branched walk + # descending from the fallthrough child instead of from this branch. + sa_skipped = sa_node + sa_branched = sa_node + if sa_node and not mask_sa_node: + # walk_till_branch() returned a node with both children, or None. + sa_skipped = sa_node.get_fallthrough_child() + sa_branched = sa_node.get_branched_child() + + # Only walk the tree further if the path is interesting (leads to an assert) or if we + # don't have information (sa_node is None). The branched side goes on the stack first, so + # that the skipped side's subtree is walked before it. + if branch_is_interesting: + work.append((node.branched, sa_branched, clinit_depth, sa_stack, trace_stack)) + if skip_is_interesting: + work.append((node.skipped, sa_skipped, clinit_depth, sa_stack, trace_stack)) + return possible_nodes diff --git a/symbolic-explorer/strategy/StrategyService.py b/symbolic-explorer/strategy/StrategyService.py index 56f39241..41098632 100755 --- a/symbolic-explorer/strategy/StrategyService.py +++ b/symbolic-explorer/strategy/StrategyService.py @@ -1,24 +1,57 @@ +import functools import json +import math import logging +import re +import time from data.BinaryExecutionTree.Node import Node from data.Database import Database from strategy.DFS import dfs from solver.SolverHandler import SATResult, Z3Handler +from timing.TimingManager import TimingManager logger = logging.getLogger(__name__) +# 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)) + 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) 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-an-assert 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 = {} if kwargs.get("rank_by_distance", False) else None + # 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, solved, unsat): + tree.loop_state_pruned = 0 # counted per walk; the tree itself persists + found = dfs(visited, tree, tree.root, solved, unsat, kwargs.get("sa_node", None), distances=distances) + return found, tree.loop_state_pruned + possible_branches, pruned = db.visit_tree(endpoint_id, walk) + if pruned: + logger.info(f'[DFS] Left out {pruned} subtrees below a repeated loop-head state') + if distances: + possible_branches.sort(key=lambda n: distances.get(n, math.inf)) return possible_branches @@ -82,23 +115,97 @@ def collect_uf_definitions(node: Node) -> list(): return uf_definitions @staticmethod - def solve_branch(possible_branch: Node, endpoint_id=None, solver_timeout_ms: int | None = 60 * 1000): - db = Database.instance() + def slice_uf_definitions(definitions: list, query: list) -> tuple[list, 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() + 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 + + @staticmethod + def collect_query(node: Node) -> tuple[list, 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.""" + path_constraints = StrategyService.collect_path_constrains(node) + 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 = StrategyService.collect_path_constrains(possible_branch) + @staticmethod + def is_approximate(ufs) -> 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) + + @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)[1]) - path_constraints.extend(StrategyService.collect_input_constrains(possible_branch)) + @staticmethod + def solve_branch(possible_branch: Node, endpoint_id=None, solver_timeout_ms: int | None = 60 * 1000, + solver_rlimit: int | None = None, extra_exprs: list | None = None, + approximate_rlimit: int | None = None): + """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.extend(StrategyService.collect_uf_definitions(possible_branch)) + path_constraints, ufs = StrategyService.collect_query(possible_branch) inputs = possible_branch.inputs - sat, sol = Z3Handler.solve(possible_branch, path_constraints, timeout_ms=solver_timeout_ms) + 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, 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,10 +229,22 @@ 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'] @@ -153,6 +272,7 @@ def is_symbolic_branch(node: Node) -> bool: logger.info(f"[is_symbolic_branch] Branch {node.id} is_symbolic: {is_symbolic}") + StrategyService._symbolic_branches[key] = is_symbolic return is_symbolic except Exception as e: diff --git a/symbolic-explorer/svcomp/PropertyPreCheck.py b/symbolic-explorer/svcomp/PropertyPreCheck.py new file mode 100644 index 00000000..624e6056 --- /dev/null +++ b/symbolic-explorer/svcomp/PropertyPreCheck.py @@ -0,0 +1,81 @@ +""" +Static property pre-check for the SV-COMP driver. + +The executor jar contains a conservative bytecode analysis (de.uzl.its.swat.staticcheck. +ExceptionFreedomCheck) of the whole program on the classpath. 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. +""" + +import json +import os +import subprocess +import time + +import log +logger = log.get_logger() + +CHECK_CLASS = "de.uzl.its.swat.staticcheck.ExceptionFreedomCheck" +CHECK_TIMEOUT_S = 60 + +# 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 + + @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 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.args.agent, 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() + 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.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})') + return self.proven + + def stats(self) -> dict: + return { + 'enabled': self.enabled, + 'status': self.status, + 'proven': self.proven, + 'reason': self.reason, + 'duration_s': self.duration, + } diff --git a/symbolic-explorer/svcomp/ResourceBudget.py b/symbolic-explorer/svcomp/ResourceBudget.py new file mode 100644 index 00000000..8fb9acc2 --- /dev/null +++ b/symbolic-explorer/svcomp/ResourceBudget.py @@ -0,0 +1,344 @@ +""" +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): + """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.""" + 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.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): + 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 + if cpu >= self.cpu_deadline: + 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(), + } diff --git a/symbolic-explorer/svcomp/SVCompHandler.py b/symbolic-explorer/svcomp/SVCompHandler.py deleted file mode 100755 index a14c9ae4..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, solver_timeout_ms=None) - - 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..43070ca4 --- /dev/null +++ b/symbolic-explorer/svcomp/StaticPreAnalysis.py @@ -0,0 +1,209 @@ +""" +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's only targets are asserts (``new AssertionError`` sites), so pruning by it is only sound +for the valid-assert property; 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 os +import subprocess +import threading +import time +import traceback +from enum import Enum + +from data.StaticAnalysisGraph.SAGraph import SAGraph + +import log +logger = log.get_logger() + +EXTRACTOR_TIMEOUT_S = 120 + +# The only property whose violations are the graph's targets (assertion points). +SA_PROPERTY = "valid-assert.prp" + + +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): + 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 | None = None + self._lock = threading.Lock() # orders starting the extractor against cancel() + self._cancelled = threading.Event() + self._done = threading.Event() + + @property + def requested(self) -> bool: + return bool(self.args.sa_file or self.args.sa_path) + + @property + def enabled(self) -> bool: + return self.requested and self.property_supported + + @property + def property_supported(self) -> bool: + return os.path.basename(getattr(self.args, "property", None) or "") == SA_PROPERTY + + def start(self): + if not self.enabled: + if self.requested: + logger.info(f'[EXPLORER] Static pre-analysis is disabled: its graph only targets asserts, ' + f'so it cannot prune for property {self.args.property} (only {SA_PROPERTY}).') + 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): + 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): + """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 = time.perf_counter() - self._start + + def _run(self): + 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) + 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(os.path.join(self.args.logdir, f"{self.args.target}_main_interprocedural.json")) + 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 + 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 = time.perf_counter() - self._start + 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", 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 if not self._is_swat_library(p)), + self.args.logdir, self.args.target, "main", "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: + return { + 'enabled': self.enabled, + 'requested': self.requested, + 'property_supported': self.property_supported, + 'mode': 'parallel' if self.args.parallel_sa else 'sequential', + 'callback_targets': not getattr(self.args, "sa_no_callback_targets", False), + '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 index 830f042c..c34f8b82 100644 --- a/symbolic-explorer/svcomp/TestcaseStats.py +++ b/symbolic-explorer/svcomp/TestcaseStats.py @@ -24,7 +24,9 @@ def _signature(inv: dict) -> str: return f"{inv['owner']}/{inv['name']}:{inv['desc']}" -def build_testcase_stats(verdict, category, tree) -> dict: +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. @@ -32,6 +34,9 @@ def build_testcase_stats(verdict, category, tree) -> dict: 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. @@ -52,6 +57,7 @@ def build_testcase_stats(verdict, category, tree) -> dict: '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, @@ -62,12 +68,28 @@ def build_testcase_stats(verdict, category, tree) -> dict: '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): +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) + 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: 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..fed623f5 --- /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.isAssert 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.isAssert] + 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.get_fallthrough_child(), branch.get_branched_child()): + 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.get_fallthrough_child(), branch.get_branched_child()]) + 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/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..2ae26fe4 --- /dev/null +++ b/symbolic-explorer/tests/test_divergence.py @@ -0,0 +1,334 @@ +""" +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.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) + 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) + 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 + self.assertEqual(self.calls, [(70, None, 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..01f92e11 --- /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: {}) + 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_int2bv_lifting.py b/symbolic-explorer/tests/test_int2bv_lifting.py new file mode 100644 index 00000000..2b39618b --- /dev/null +++ b/symbolic-explorer/tests/test_int2bv_lifting.py @@ -0,0 +1,165 @@ +""" +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, JAVA_MAX_STRING_LENGTH + + +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): + r = self.rnd + chars = ['a', 'b', 'รฟ', '๏ฟฟ', '\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_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)) + + +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..88d2c19f --- /dev/null +++ b/symbolic-explorer/tests/test_loop_state_matching.py @@ -0,0 +1,104 @@ +""" +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): + tree.loop_state_pruned = 0 + return sorted(n.id for n in dfs(set(), 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.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.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_property_precheck.py b/symbolic-explorer/tests/test_property_precheck.py new file mode 100644 index 00000000..3fe61a29 --- /dev/null +++ b/symbolic-explorer/tests/test_property_precheck.py @@ -0,0 +1,109 @@ +""" +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 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 = {"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) + 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[:4], ['java', '-Xmx2g', '-cp', '/swat/executor.jar']) + self.assertEqual(cmd[-2:], ['/task/common', '/task/Main']) + + 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_resource_budget.py b/symbolic-explorer/tests/test_resource_budget.py new file mode 100644 index 00000000..c9e248a2 --- /dev/null +++ b/symbolic-explorer/tests/test_resource_budget.py @@ -0,0 +1,244 @@ +""" +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) + 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..685f1bf5 --- /dev/null +++ b/symbolic-explorer/tests/test_round_overhead.py @@ -0,0 +1,121 @@ +""" +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() + 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..6f6702c9 --- /dev/null +++ b/symbolic-explorer/tests/test_sa_guards.py @@ -0,0 +1,128 @@ +""" +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'].get_fallthrough_child(), None)) + self.assertTrue(is_interesting(n['b'].get_branched_child(), 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'].get_fallthrough_child(), None)) + self.assertFalse(is_interesting(n['b'].get_branched_child(), 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_other_properties_load_nothing(self): + for prop in ['no-runtime-exception.prp', '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..d5f8f406 --- /dev/null +++ b/symbolic-explorer/tests/test_sa_walk.py @@ -0,0 +1,917 @@ +""" +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.StaticAnalysisGraph import SAGraph as sagraph +from data.StaticAnalysisGraph.SAGraph import SAGraph, mark_assertion_path, SANode +from strategy.DFS import dfs + +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 special_node(name, branched=None, inst=None): + """A tree node for a special (non-branch) event; dfs passes through to `branched`.""" + n = branch_node(name, branched=branched) + n.kind = 'Special' + n.inst = inst + 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(set(), 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.assertEqual({n.id for n in g.nodes['kx1'].next_returns}, {'r1', 'r2'}) + 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() + + def test_marking_long_chain_does_not_recurse(self): + # The old recursive marking hit the recursion limit on long predecessor chains. + nodes = [SANode(str(i)) for i in range(50_000)] + for a, b in zip(nodes, nodes[1:]): + b.prev.append(a) + limit = sys.getrecursionlimit() + sys.setrecursionlimit(1000) + try: + mark_assertion_path(nodes[-1]) + finally: + sys.setrecursionlimit(limit) + self.assertTrue(nodes[0].reachesAssertSameLevel) + + +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.get_branched_child().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 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.get_fallthrough_child().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.get_fallthrough_child().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()) + n1.skipped = special_node('x', branched=n2, inst='UNMODELLED_EXCEPTION') + 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, events, after_branch_in_f=True): + """b1 (skipped), [bf in f], then `events` (special nodes), then b2 with its TRUE side unexplored.""" + n2 = branch_node('n2', skipped=leaf(), branched=None) + nxt = n2 + for name, inst in reversed(events): + nxt = special_node(name, branched=nxt, inst=inst) + 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([('u', 'UNMODELLED_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: + """Collects what dfs records (record_execution_error).""" + def __init__(self): + self.execution_errors = [] + + 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 + + THROWS = (('u', 'UNMODELLED_EXCEPTION'),) + + def tree(self, b2_key=None, b2_pops=1, bf_key=None, bf_pushes=None, events=THROWS): + """ + n1 (b1, skipped), nf (bf in f, skipped), `events`, 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) + nxt = n2 + for name, inst in reversed(events): + nxt = special_node(name, branched=nxt, inst=inst) + nf = branch_node('nf', skipped=nxt, 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(set(), 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_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(set(), 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'].reachesAssertSameLevel) # through F.run + self.assertTrue(g.nodes['e0'].reachesAssertSameLevel) + self.assertFalse(g.nodes['r1'].reachesAssertSameLevel) + + 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'].reachesAssertSameLevel) + self.assertTrue(n['kb'].canReturn) + self.assertTrue(n['c2'].reachesAssertSameLevel) + self.assertFalse(n['c1'].reachesAssertSameLevel) + self.assertTrue(n['e0'].reachesAssertSameLevel) + + 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'].reachesAssertSameLevel) + + 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'].reachesAssertSameLevel) + self.assertEqual(n['c2'].escapes, frozenset({tuple(ARITH_CHAIN)})) + self.assertTrue(n['c1'].reachesAssertSameLevel) + 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'].reachesAssertSameLevel) + + 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'].reachesAssertSameLevel) + + 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'].reachesAssertSameLevel) + + 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'].reachesAssertSameLevel) + # 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)) + + def test_legacy_graph_without_return_addresses_is_marked_context_insensitively(self): + # An old extractor's CALL edge carries no returnSite. Its call sites are then plain + # fall-throughs, which the summaries cannot see through, so marking falls back to + # following predecessors -- including the RETURN edge into the asserting return site. + g = (GraphBuilder().nodes_('e0', 'c', 'r').node('a', assertion=True).node('x', exit=True) + .nodes_('f0', 'fb').node('fx', exit=True) + .edge('e0', 'c').edge('c', 'f0', 'CALL').edge('fx', 'r', 'RETURN') + .edge('r', 'a').edge('a', 'x') + .edge('f0', 'fb').branch('fb', 'fx', 'fx') + .load()) + self.assertTrue(sagraph.is_interesting(g.nodes['fx'], None)) + + +if __name__ == '__main__': + unittest.main() + + +class AssertDistanceTest(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].assertDistance 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'].assertDistance, 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'].assertDistance, 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(set(), 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(set(), 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'}) 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_tree.py b/symbolic-explorer/tests/test_tree.py new file mode 100644 index 00000000..0294fbb6 --- /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(set(), 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(set(), 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(set(), 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/examples/my-example-2/Example.java b/targets/examples/my-example-2/Example.java new file mode 100644 index 00000000..0d767085 --- /dev/null +++ b/targets/examples/my-example-2/Example.java @@ -0,0 +1,22 @@ +import de.uzl.its.swat.annotations.Symbolic; + +public class Example { + + public static void main(String[] args) { + test(5, 6); + } + + public static void test(@Symbolic int val, @Symbolic int distract) { + if (distract > 0) { + int sum = 0; + for (int i = 0; i < 10; i++) { + if (distract > i) { + sum += i; + } + } + System.out.println(sum); + } else { + assert val != 3141592; + } + } +} diff --git a/targets/examples/my-example-2/build.sh b/targets/examples/my-example-2/build.sh new file mode 100755 index 00000000..acaf5972 --- /dev/null +++ b/targets/examples/my-example-2/build.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +# Compile the target file +javac -cp ../../../annotations/build/libs/annotations-1.0.jar Example.java diff --git a/targets/examples/my-example-2/run.sh b/targets/examples/my-example-2/run.sh new file mode 100755 index 00000000..dc3b065d --- /dev/null +++ b/targets/examples/my-example-2/run.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +# Running the target from the symbolic explorer +./../../../symbolic-explorer/.venv/bin/python3 ../../../symbolic-explorer/SymbolicExplorer.py \ + --mode annotation \ + --target Example \ + --classpath . \ + --agent ../../../symbolic-executor/lib/symbolic-executor.jar \ + --z3dir ../../../libs/java-library-path \ + --config swat.cfg \ + "$@" # additional args are passed through diff --git a/targets/examples/my-example-2/swat.cfg b/targets/examples/my-example-2/swat.cfg new file mode 100644 index 00000000..2cbd03b5 --- /dev/null +++ b/targets/examples/my-example-2/swat.cfg @@ -0,0 +1,2 @@ +logging.debug=true +logging.classes=true diff --git a/targets/examples/my-example-3/build.sh b/targets/examples/my-example-3/build.sh new file mode 100755 index 00000000..3984e1af --- /dev/null +++ b/targets/examples/my-example-3/build.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +# Compile the target file +javac -cp ../../../annotations/build/libs/annotations-1.0.jar -d build src/VirtualDispatch.java diff --git a/targets/examples/my-example-3/run.sh b/targets/examples/my-example-3/run.sh new file mode 100755 index 00000000..86fe32df --- /dev/null +++ b/targets/examples/my-example-3/run.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +# Running the target from the symbolic explorer +./../../../symbolic-explorer/.venv/bin/python3 ../../../symbolic-explorer/SymbolicExplorer.py \ + --mode annotation \ + --target VirtualDispatch \ + --classpath build \ + --agent ../../../symbolic-executor/lib/symbolic-executor.jar \ + --z3dir ../../../libs/java-library-path \ + --config swat.cfg \ + "$@" # additional args are passed through diff --git a/targets/examples/my-example-3/src/VirtualDispatch.java b/targets/examples/my-example-3/src/VirtualDispatch.java new file mode 100644 index 00000000..ec1712af --- /dev/null +++ b/targets/examples/my-example-3/src/VirtualDispatch.java @@ -0,0 +1,28 @@ +import de.uzl.its.swat.annotations.Symbolic; + +public class VirtualDispatch { + + interface Writer { + void write(String s); + } + + static class Checker { + Writer writer() { + return new Writer() { + @Override + public void write(String s) { + assert !s.isEmpty(); + } + }; + } + } + + public static void main(String[] args) { + test("test"); + } + + private static void test(@Symbolic String msg) { + Writer w = new Checker().writer(); + w.write(msg); + } +} diff --git a/targets/examples/my-example-3/swat.cfg b/targets/examples/my-example-3/swat.cfg new file mode 100644 index 00000000..2cbd03b5 --- /dev/null +++ b/targets/examples/my-example-3/swat.cfg @@ -0,0 +1,2 @@ +logging.debug=true +logging.classes=true diff --git a/targets/examples/my-example/Example.java b/targets/examples/my-example/Example.java new file mode 100644 index 00000000..ff4dd753 --- /dev/null +++ b/targets/examples/my-example/Example.java @@ -0,0 +1,50 @@ +import de.uzl.its.swat.annotations.Symbolic; + +public class Example { + + public static void main(String[] args) { + test(5); + } + + public static void test(@Symbolic int val) { + int sum = 0; + // for (int i = 0; i < 100; i++) { + // if (val > i) { + // sum += i; + // } + // } + for (int i = 0; i < 100; i += 10) { + if (val > i) { + sum += i; + } + if (val > i + 1) { + sum += i + 1; + } + if (val > i + 2) { + sum += i + 2; + } + if (val > i + 3) { + sum += i + 3; + } + if (val > i + 4) { + sum += i + 4; + } + if (val > i + 5) { + sum += i + 5; + } + if (val > i + 6) { + sum += i + 6; + } + if (val > i + 7) { + sum += i + 7; + } + if (val > i + 8) { + sum += i + 8; + } + if (val > i + 9) { + sum += i + 9; + } + } + assert val != 3141592; + } +} diff --git a/targets/examples/my-example/build.sh b/targets/examples/my-example/build.sh new file mode 100755 index 00000000..acaf5972 --- /dev/null +++ b/targets/examples/my-example/build.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +# Compile the target file +javac -cp ../../../annotations/build/libs/annotations-1.0.jar Example.java diff --git a/targets/examples/my-example/run.sh b/targets/examples/my-example/run.sh new file mode 100755 index 00000000..cfcfcf9c --- /dev/null +++ b/targets/examples/my-example/run.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +# Running the target from the symbolic explorer +./../../../symbolic-explorer/.venv/bin/python3 ../../../symbolic-explorer/SymbolicExplorer.py \ + --mode annotation \ + --target Example \ + --classpath . \ + --agent ../../../symbolic-executor/lib/symbolic-executor.jar \ + --z3dir ../../../libs/java-library-path diff --git a/targets/sv-comp/scripts/README.md b/targets/sv-comp/scripts/README.md index 22ce37c6..d8aeb5dd 100644 --- a/targets/sv-comp/scripts/README.md +++ b/targets/sv-comp/scripts/README.md @@ -61,6 +61,15 @@ scripts/ ./svcomp setup all ``` +Witnesses of violation results are written by the Symbolic Explorer itself (from the trace of +the violating execution, via the `witnesscreator` package from `symbolic-explorer/requirements.txt`) +to the testcase's log directory as `witness/witness.yml` (YAML format 2.3) and +`witness/witness.graphml` (legacy). The harness only validates them: the YAML witness must have +exactly one `target` waypoint and, if `SWAT_WITNESSLINT` names a WitnessLint command +(e.g. `SWAT_WITNESSLINT="/path/venv/bin/python /path/witnesslint/witnesslinter.py"`), it is linted +with `--expectViolationWitness --expectedWitnessVersion 2`; the GraphML witness is validated with +wit4java (reported as `Validated:`). A proper validator setup is tracked in #47. + ### Test Commands ```bash diff --git a/targets/sv-comp/scripts/checkout.sh b/targets/sv-comp/scripts/checkout.sh index 28544e49..2add81bb 100755 --- a/targets/sv-comp/scripts/checkout.sh +++ b/targets/sv-comp/scripts/checkout.sh @@ -1,29 +1,57 @@ #!/bin/bash +# +# Checks out the SV-COMP Java benchmarks (with precompiled .class files) from SWAT's +# SV-Benchmarks repository into ../sv-benchmarks: a shallow, sparse checkout of java/ and of +# .last_sv_commit, the upstream sv-benchmarks commit they were built from. +# +# Usage: checkout.sh [SOURCE] +# SOURCE where to pull from, default git@github.com:SWAT-project/SV-Benchmarks.git; e.g. a local +# clone (/path/to/SV-Benchmarks) when GitHub is not reachable over SSH. origin is set to +# the default URL afterwards either way. +# +# An existing ../sv-benchmarks is never overwritten. To update, move it aside first, e.g. to +# ../sv-benchmarks-, so old runs can still be rerun against it (--benchmark-dir). # Get the directory where this script is located SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # Define variables REPO_URL="git@github.com:SWAT-project/SV-Benchmarks.git" +SOURCE="${1:-$REPO_URL}" TARGET_DIR="$SCRIPT_DIR/../sv-benchmarks" FOLDER="java" +if [ -e "$TARGET_DIR" ]; then + echo "Error: $TARGET_DIR already exists. Move it aside first (see the header of this script)." + exit 1 +fi + +# A local path is pulled over file:// so that --depth works. +if [ -d "$SOURCE" ]; then + SOURCE="file://$(cd "$SOURCE" && pwd)" +fi + # Step 1: Create the target directory and initialize an empty Git repository mkdir -p "$TARGET_DIR" cd "$TARGET_DIR" || exit git init # Step 2: Add the remote repository -git remote add origin "$REPO_URL" +git remote add origin "$SOURCE" # Step 3: Enable sparse checkout git config core.sparseCheckout true -# Step 4: Specify the folder to checkout +# Step 4: Specify what to check out echo "$FOLDER/" >> .git/info/sparse-checkout +echo "/.last_sv_commit" >> .git/info/sparse-checkout echo "Pulling... This may take some time" # Step 5: Pull only the specified folder from the main branch -git pull --depth=1 origin main +git pull --depth=1 origin main || exit 1 + +# Later pulls go to the canonical repository, not to wherever this checkout came from. +git remote set-url origin "$REPO_URL" -echo "Checked out '$FOLDER/' folder from $REPO_URL into $TARGET_DIR" +echo "Checked out '$FOLDER/' folder from $SOURCE into $TARGET_DIR" +echo "Upstream sv-benchmarks commit: $(cat .last_sv_commit 2>/dev/null || echo unknown)" diff --git a/targets/sv-comp/scripts/commands/analyze.py b/targets/sv-comp/scripts/commands/analyze.py index ce71578a..d63a07ab 100644 --- a/targets/sv-comp/scripts/commands/analyze.py +++ b/targets/sv-comp/scripts/commands/analyze.py @@ -40,15 +40,21 @@ def analyze_results(ctx, run_dir): @analyze.command(name='compare') -@click.argument('current_file', type=click.Path(exists=True)) -@click.argument('reference_file', type=click.Path(exists=True)) +@click.argument('current_file', type=click.Path(exists=True, path_type=Path)) +@click.argument('reference_file', type=click.Path(exists=True, path_type=Path)) @click.pass_context -def compare(ctx, current_file, reference_file): +def compare(ctx, current_file: Path, reference_file: Path): """Compare two result files. Shows what changed between REFERENCE_FILE and CURRENT_FILE. """ from lib import compare_results + + # shorthand notation + if current_file.is_dir(): + current_file = current_file / "results" / f"results_valid-assert.prp_{current_file.name[4:]}.json" + if reference_file.is_dir(): + reference_file = reference_file / "results" / f"results_valid-assert.prp_{reference_file.name[4:]}.json" click.echo(f"Comparing results:") click.echo(f" Reference: {reference_file}") diff --git a/targets/sv-comp/scripts/commands/setup.py b/targets/sv-comp/scripts/commands/setup.py index a14d113f..2550bae4 100644 --- a/targets/sv-comp/scripts/commands/setup.py +++ b/targets/sv-comp/scripts/commands/setup.py @@ -15,9 +15,12 @@ def setup(): @setup.command(name='checkout-benchmarks') +@click.option('--source', default=None, help='Where to pull from instead of the SWAT-project GitHub repository, e.g. a local clone of it') @click.pass_context -def checkout_benchmarks(ctx): - """Checkout SV-Benchmarks repository (sparse checkout of java folder).""" +def checkout_benchmarks(ctx, source): + """Checkout SV-Benchmarks repository (sparse checkout of java folder). + + Refuses to overwrite an existing sv-benchmarks/; move it aside first (see checkout.sh).""" script_dir = ctx.obj['script_dir'] checkout_script = script_dir / 'checkout.sh' @@ -30,7 +33,7 @@ def checkout_benchmarks(ctx): try: result = subprocess.run( - ['bash', str(checkout_script)], + ['bash', str(checkout_script)] + ([source] if source else []), cwd=script_dir, check=True, capture_output=True, diff --git a/targets/sv-comp/scripts/commands/test.py b/targets/sv-comp/scripts/commands/test.py index f0398c01..434ff9f4 100644 --- a/targets/sv-comp/scripts/commands/test.py +++ b/targets/sv-comp/scripts/commands/test.py @@ -3,8 +3,9 @@ import click import logging from pathlib import Path -import sys +import sys, shlex from lib.execution import target_execution +import subprocess logger = logging.getLogger(__name__) @@ -57,12 +58,22 @@ def list_tests(ctx, benchmark_dir, stats): @click.option('--mode', type=click.Choice(['single', 'parallel']), default='parallel', help='Execution mode') @click.option('--workers', type=int, default=50, help='Number of parallel workers') @click.option('--benchmark-dir', type=click.Path(exists=True), help='Path to sv-benchmarks directory') -@click.option('--config-file', default='sv-comp.cfg', help='Configuration file name') +@click.option('--config-file', default=None, help='Configuration file name (default: sv-comp.cfg or swat-debug.cfg in "single" mode)') @click.option('--categories', multiple=True, help='Verification categories to run (e.g., valid-assert.prp)') +@click.option('--suite', default=None, help='Run only tests from this benchmark subfolder (e.g., securibench, autostub)') +@click.option('--limit-nr-tests', type=int, default=None, help='Run only the first n tests') @click.option('--target', help='Single target to run (only for single mode)') @click.option('--no-witness', 'no_witness', is_flag=True, default=False, help='Skip witness creation and validation') +@click.option('--no-sa', 'no_sa', is_flag=True, default=False, help='Skip static pre-analysis') +@click.option('--sa-retry-without', 'sa_retry_without', is_flag=True, default=False, help='On a SAFE verdict reached with static pre-analysis, retry the exploration without it') +@click.option('--parallel-sa', 'parallel_sa', is_flag=True, default=False, help='Run the static pre-analysis in the background while exploring, instead of waiting for it (nondeterministic)') +@click.option('--sa-no-callback-targets', 'sa_no_callback_targets', is_flag=True, default=False, help='Do not count library calls as possible asserts when a callback from library code may reach one (unsound)') +@click.option('--sa-rank-distance', 'sa_rank_distance', is_flag=True, default=False, help='Try branches closest to an assert in the static pre-analysis graph first') +@click.option('--solver-rlimit', 'solver_rlimit', type=int, default=0, help='Z3 resource limit for the first query of each branch, growing on retries (0: unlimited)') +@click.option('--testcase-timeout-s', type=int, default=15 * 60, help='Timeout of the symbolic-explorer in seconds for each test case') @click.pass_context -def run_tests(ctx, mode, workers, benchmark_dir, config_file, categories, target: str, no_witness: bool): +def run_tests(ctx, mode, workers, benchmark_dir, config_file: str | None, categories, suite, limit_nr_tests: int | None, target: str, + no_witness: bool, no_sa: bool, sa_retry_without: bool, parallel_sa: bool, sa_rank_distance: bool, sa_no_callback_targets: bool, solver_rlimit: int, testcase_timeout_s: int): """Run verification tests.""" from lib import ( extract_testcases, @@ -72,7 +83,7 @@ def run_tests(ctx, mode, workers, benchmark_dir, config_file, categories, target check_port_availability, VerificationCategory, ) - from lib.command_gen import new_run_timestamp, run_dir as make_run_dir + from lib.command_gen import new_run_timestamp, run_dir as make_run_dir, write_run_info script_dir = ctx.obj['script_dir'] # Determine benchmark directory @@ -107,16 +118,27 @@ def run_tests(ctx, mode, workers, benchmark_dir, config_file, categories, target ver_tasks = [task for task in ver_tasks if task['category'] in selected_categories] click.echo(f"Filtered to {len(ver_tasks)} test cases for categories: {[c.value for c in selected_categories]}") + # Filter by suite (benchmark subfolder) if specified + if suite: + ver_tasks = [task for task in ver_tasks if suite in task['file_path'].parts] + click.echo(f"Filtered to {len(ver_tasks)} test cases for suite: {suite}") + if not ver_tasks: + click.echo(f"Error: No test cases found for suite '{suite}'", err=True) + ctx.exit(1) + if limit_nr_tests: + ver_tasks = ver_tasks[:limit_nr_tests] + click.echo(f"Limiting number of tests to {limit_nr_tests} (now {len(ver_tasks)})") + # Generate commands - if mode == 'single': - # Use default target from original script - config = 'swat-debug.cfg' - else: - config = config_file + if config_file is None: + if mode == 'single': + config_file = 'swat-debug.cfg' + else: + config_file = 'sv-comp.cfg' # One timestamp ties this run's per-testcase logs to its results dir. run_timestamp = new_run_timestamp() - ver_tasks_with_commands = generate_commands(ver_tasks, config, run_timestamp=run_timestamp) + ver_tasks_with_commands = generate_commands(ver_tasks, config_file, run_timestamp=run_timestamp, no_sa=no_sa, sa_retry_without=sa_retry_without, parallel_sa=parallel_sa, sa_rank_distance=sa_rank_distance, sa_no_callback_targets=sa_no_callback_targets, solver_rlimit=solver_rlimit, cpu_limit_s=testcase_timeout_s) click.echo(f"Generated {len(ver_tasks_with_commands)} commands") # Check port availability @@ -141,14 +163,27 @@ def run_tests(ctx, mode, workers, benchmark_dir, config_file, categories, target ctx.exit(1) click.echo(f"Running single target: {target}") - target_execution(ver_task, create_witness=not no_witness) + target_execution(ver_task, create_witness=not no_witness, testcase_timeout_s=testcase_timeout_s) else: # Run default single target run_single_target(ver_tasks_with_commands, create_witness=not no_witness) else: - run = make_run_dir(run_timestamp) - click.echo(f"Run directory: {run}") - run_parallel(ver_tasks_with_commands, max_workers=workers, create_witness=not no_witness, run_dir=run) + run_dir = make_run_dir(run_timestamp) + run_dir.mkdir(parents=True) + click.echo(f"Run directory: {run_dir}") + # Log argv and current commit for debugging and reproducibility + subprocess.run(f'(echo {shlex.quote(shlex.join(sys.argv))} && echo && git log -1 --pretty=format:"%h %s" && echo && git status) > {run_dir / "gitlog.txt"}', shell=True) + # Structured run settings, e.g. for compare_runs.py; the SA mode changes what the timing means. + write_run_info(run_dir, argv=sys.argv, run_timestamp=run_timestamp, mode=mode, config_file=config_file, + categories=list(categories), suite=suite, limit_nr_tests=limit_nr_tests, + testcase_timeout_s=testcase_timeout_s, create_witness=not no_witness, + sa_mode='none' if no_sa else 'parallel' if parallel_sa else 'sequential', + sa_retry_without=sa_retry_without and not no_sa, + sa_rank_distance=sa_rank_distance and not no_sa, + sa_callback_targets=not sa_no_callback_targets and not no_sa, + solver_rlimit=solver_rlimit) + + run_parallel(ver_tasks_with_commands, max_workers=workers, create_witness=not no_witness, run_dir=run_dir, testcase_timeout_s=testcase_timeout_s) click.secho("โœ“ Test execution complete", fg='green') diff --git a/targets/sv-comp/scripts/compare_runs.py b/targets/sv-comp/scripts/compare_runs.py new file mode 100644 index 00000000..f91c89c6 --- /dev/null +++ b/targets/sv-comp/scripts/compare_runs.py @@ -0,0 +1,1084 @@ +#!/usr/bin/env python3 +""" +Compare the timing behavior of two or more SV-COMP runs in a set of plots. + +Takes results__.json files, or run directories (runs/run_/), +in which case the results file for --prp is picked from their results/ folder. The +first run is the baseline; pairwise plots compare every other run against it. Per-task stats.json files +(iteration and solver-call counts) are read from the run's logs/ directory +next to results/ when they exist. + +Writes numbered SVGs, summary.md and a self-contained report.html into the +output directory, by default runs/comparison__vs_[_vs_...]/ next to the runs, +named after the labels or, without labels, the run directories. + +Static pre-analysis (SA) timing depends on the run's SA mode, read from the run's run_info.json +(older runs without one: inferred from gitlog.txt, where anything but --no-sa ran sequentially): + - none: no pre-analysis. + - sequential: (the default) exploration waited for SA; the static_pre_analysis stage is its + whole duration. + - parallel: (--parallel-sa) SA ran in the background while exploring; the static_pre_analysis stage is 0 and + its duration (static_pre_analysis_wall) overlaps the other stages. + +Usage: + compare_runs.py BASELINE.json OTHER.json [...] [-o outdir] [--label NAME ...] + compare_runs.py runs/run_A/ runs/run_B/ [...] [--prp valid-assert] [--label NAME ...] +""" + +import argparse +import base64 +import json +import math +import re +import statistics +from dataclasses import dataclass, field +from pathlib import Path +from typing import Optional + +import matplotlib + +matplotlib.use("Agg") +import matplotlib.pyplot as plt # noqa: E402 +import numpy as np # noqa: E402 +from matplotlib.colors import SymLogNorm, LinearSegmentedColormap # noqa: E402 +from matplotlib.patches import Patch # noqa: E402 +from matplotlib.lines import Line2D # noqa: E402 + +TIMING_INDEX = 6 +RUNS_DIR = Path(__file__).resolve().parent.parent / "runs" + +# Categorical order (runs); a run keeps its color by position on the command line. +RUN_COLORS = ["#2a78d6", "#eb6834", "#1baf7a", "#eda100", + "#e87ba4", "#008300", "#4a3aa7", "#e34948"] +# Stages use the same categorical order; runs and stages never share one plot's color key. +STAGE_ORDER = ["static_pre_analysis", "symbolic_executor", "smt_solver", "symbolic_explorer", + "witness_generation", "witness_validation"] +STAGE_COLORS = {"static_pre_analysis": "#2a78d6", "symbolic_executor": "#eb6834", + "smt_solver": "#1baf7a", "symbolic_explorer": "#eda100", + "witness_generation": "#e87ba4", "witness_validation": "#4a3aa7", + "untracked": "#b5b4ad"} +STAGE_NAMES = {"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", + "untracked": "outside explorer timer"} +# Verdict change of a task relative to the baseline (status colors, always paired with a marker shape). +CHANGE_STYLE = {"same": ("#8a8a85", "o", "same points"), + "better": ("#0ca30c", "^", "more points"), + "worse": ("#d03b3b", "v", "fewer points")} +TEXT = "#2b2b29" +MUTED = "#6f6e69" +GRID = "#e6e5e0" +EPS = 1e-4 # floor for log axes (seconds) +LINEAR = False # --linear: linear instead of log time axes in the cactus and score plots + + +@dataclass +class Task: + name: str + suite: str + case: str + points: int + status: str + wall: float + stages: Optional[dict] # None when the explorer wrote no timing (e.g. harness timeout) + iterations: Optional[int] = None + solver_calls: Optional[int] = None + sa_wall: Optional[float] = None # SA duration, blocking or not; None without SA or timing + sa_status: Optional[str] = None # from stats.json: loaded, cancelled, failed, timeout, ... + sa_adopted_round: Optional[int] = None + + @property + def total(self) -> Optional[float]: + return self.stages["total_time"] if self.stages else None + + @property + def finished(self) -> bool: + return self.stages is not None + + @property + def untracked(self) -> Optional[float]: + return max(0.0, self.wall - self.total) if self.finished else None + + def stage(self, s) -> Optional[float]: + if not self.finished: + return None + if s == "untracked": + return self.untracked + return self.stages.get(s, 0.0) + + +@dataclass +class Run: + label: str + path: Path + timeout: Optional[float] + tasks: dict = field(default_factory=dict) + color: str = "" + sa_mode: str = "unknown" # none, sequential, parallel (see the module docstring) + + def finished_totals(self): + return sorted(t.total for t in self.tasks.values() if t.finished) + + +# ----------------------------------------------------------------------------- loading + +def read_run_info(run_dir: Path) -> dict: + """run_info.json of a run, or {} for runs from before it was written.""" + try: + return json.loads((run_dir / "run_info.json").read_text()) + except (OSError, json.JSONDecodeError): + return {} + + +def parse_sa_mode(run_dir: Path, info: dict) -> str: + if info.get("sa_mode"): + return info["sa_mode"] + gitlog = run_dir / "gitlog.txt" + if gitlog.exists(): + argv = (gitlog.read_text().splitlines() or [""])[0] + if "--no-sa" in argv: + return "none" + # Before run_info.json the pre-analysis always ran before the exploration. + return "sequential" + return "unknown" + + +def parse_timeout(run_dir: Path, tasks, info: dict) -> Optional[float]: + if info.get("testcase_timeout_s"): + return float(info["testcase_timeout_s"]) + gitlog = run_dir / "gitlog.txt" + if gitlog.exists(): + m = re.search(r"--testcase-timeout-s[ =](\d+)", gitlog.read_text()) + if m: + return float(m.group(1)) + walls = [t.wall for t in tasks.values() if t.status == "timeout"] + return float(round(min(walls))) if walls else None + + +def default_label(path: Path) -> str: + run_dir = path.resolve().parent.parent + name = run_dir.name if re.fullmatch(r"run_\d{8}_\d{6}", run_dir.name) else path.stem + # Empty marker files next to logs/ and results/ (e.g. "high-timeout_SA") describe the run. + markers = sorted(p.name for p in run_dir.iterdir() if p.is_file() and p.stat().st_size == 0) \ + if run_dir.is_dir() else [] + sa_mode = parse_sa_mode(run_dir, read_run_info(run_dir)) + if sa_mode != "unknown": + markers.append(f"SA {sa_mode}") + return f"{name} ({', '.join(markers)})" if markers else name + + +def load_run(path: Path, label: str) -> Run: + with open(path) as f: + results = json.load(f)["results"] + m = re.match(r"results_(.+?)\.prp_", path.name) + prp = m.group(1) if m else None + run_dir = path.resolve().parent.parent + info = read_run_info(run_dir) + sa_mode = parse_sa_mode(run_dir, info) + logs = run_dir / "logs" + tasks = {} + for name, entry in results.items(): + case, points, status, _err, _validated, wall, timing = entry[:7] + stages = timing if isinstance(timing, dict) and "total_time" in timing else None + task = Task(name, name.split("/")[0], case, points, status, wall, stages) + if stages and sa_mode != "none": + # Older timing has no static_pre_analysis_wall: SA was sequential, so it is the stage. + task.sa_wall = stages.get("static_pre_analysis_wall", stages.get("static_pre_analysis")) + stats_file = logs / f"{name}_{prp}" / "stats.json" + if prp and stats_file.exists(): + try: + data = json.loads(stats_file.read_text()) + perf = data.get("performance", {}) + task.iterations = perf.get("symbolic_exec_iterations") + task.solver_calls = perf.get("nr_solver_calls") + sa = data.get("static_analysis", {}) + task.sa_status = sa.get("status") + if task.sa_status is None and sa.get("enabled"): # stats.json from sequential-only runs + task.sa_status = "timeout" if sa.get("timed_out") else "failed" if sa.get("failed") else "loaded" + task.sa_adopted_round = sa.get("adopted_at_round") + except (json.JSONDecodeError, OSError): + pass + tasks[name] = task + return Run(label, path, parse_timeout(run_dir, tasks, info), tasks, sa_mode=sa_mode) + + +def active_stages(runs): + used = [s for s in STAGE_ORDER + if any(t.stage(s) for r in runs for t in r.tasks.values() if t.finished)] + return used + ["untracked"] + + +def common_finished(runs): + names = set.intersection(*(set(n for n, t in r.tasks.items() if t.finished) for r in runs)) + return sorted(names) + + +def change(base: Task, other: Task) -> str: + return "better" if other.points > base.points else "worse" if other.points < base.points else "same" + + +# ----------------------------------------------------------------------------- styling + +def style_axes(ax, grid_axis="both"): + ax.grid(True, which="major", axis=grid_axis, color=GRID, linewidth=0.8) + ax.set_axisbelow(True) + for side in ("top", "right"): + ax.spines[side].set_visible(False) + for side in ("left", "bottom"): + ax.spines[side].set_color(MUTED) + ax.tick_params(colors=MUTED, labelcolor=TEXT) + + +def fmt_s(x): + if abs(x) >= 100: + return f"{x:,.0f} s" + if abs(x) >= 1: + return f"{x:.1f} s" + return f"{x:.2f} s" + + +def short_label(run: Run): + return run.label + + +plt.rcParams.update({ + "font.size": 10, "axes.titlesize": 11, "axes.titleweight": "bold", "axes.titlelocation": "left", + "axes.labelcolor": TEXT, "text.color": TEXT, "figure.facecolor": "white", + "axes.facecolor": "white", "legend.frameon": False, "savefig.bbox": "tight", + "svg.fonttype": "none", # keep text as text in the SVGs +}) + + +# ----------------------------------------------------------------------------- plots + +def time_axis(ax, lo, hi, label): + """Log x-axis from just below lo, or with --linear a linear one from 0 to just above hi.""" + if LINEAR: + ax.set_xlim(0, hi * 1.03 if hi > 0 else 1) + ax.set_xlabel(label) + else: + ax.set_xscale("log") + if lo < math.inf: + ax.set_xlim(left=max(EPS, lo) / 1.5) + ax.set_xlabel(f"{label} (log)") + + +def cactus(ax, runs, value, title, xlabel, timeout_lines=True, hi=0.0): + lo = math.inf + timeouts = sorted({r.timeout for r in runs if r.timeout}) if timeout_lines else [] + for r in runs: + vals = sorted(max(EPS, v) for v in (value(t) for t in r.tasks.values()) if v is not None) + if not vals: + continue + lo, hi = min(lo, vals[0]), max(hi, vals[-1]) + ax.step([EPS] + vals, range(len(vals) + 1), where="post", color=r.color, linewidth=2, + label=f"{short_label(r)}: {len(vals)}/{len(r.tasks)}") + time_axis(ax, lo, max([hi] + timeouts), xlabel) + if timeout_lines: + for to in timeouts: + ax.axvline(to, color=MUTED, linewidth=1, linestyle=":") + ax.text(to, 0.02, f" timeout {to:.0f} s", transform=ax.get_xaxis_transform(), + color=MUTED, fontsize=8, rotation=90, va="bottom", ha="right") + total = max(len(r.tasks) for r in runs) + ax.axhline(total, color=MUTED, linewidth=1, linestyle="--") + ax.set_ylim(0, total * 1.05) + ax.set_title(title) + ax.set_ylabel("tasks finished (cumulative)") + style_axes(ax) + ax.legend(loc="lower right", fontsize=8) + + +def plot_cactus(runs, out): + fig, axes = plt.subplots(1, 2, figsize=(14, 5.5), sharey=True) + cactus(axes[0], runs, lambda t: t.total, "Explorer total_time", "simulated timeout [s]") + cactus(axes[1], runs, lambda t: t.wall if t.finished else None, + "Harness wall time (includes process start/stop)", "simulated timeout [s]") + fig.suptitle("How many tasks finish within t seconds", x=0.01, ha="left", fontweight="bold") + save(fig, out) + + +def plot_cactus_free_sa(runs, out): + """Like the total_time cactus, but as if static pre-analysis took no time.""" + fig, ax = plt.subplots(figsize=(9, 5.5)) + measured_hi = 0.0 + for r in runs: # the measured curves, faint, for reference + vals = sorted(max(EPS, t.total) for t in r.tasks.values() if t.finished) + ax.step([EPS] + vals, range(len(vals) + 1), where="post", color=r.color, linewidth=1, + linestyle="--", alpha=0.6) + measured_hi = max([measured_hi] + vals[-1:]) + cactus(ax, runs, lambda t: t.total - t.stage("static_pre_analysis") if t.finished else None, + "Explorer total_time with static pre-analysis counted as 0 s", "simulated timeout [s]", + hi=measured_hi) + handles, labels = ax.get_legend_handles_labels() + handles += [Line2D([], [], color=MUTED, linewidth=2), + Line2D([], [], color=MUTED, linewidth=1, linestyle="--")] + labels += ["solid: pre-analysis free", "dashed: measured total_time"] + ax.legend(handles, labels, loc="center right", fontsize=8) + save(fig, out) + + +def plot_score(runs, out): + """SV-COMP score if the timeout were t: tasks slower than t score 0 (unknown).""" + fig, axes = plt.subplots(1, 2, figsize=(14, 5.5)) + for r in runs: + fin = sorted((t for t in r.tasks.values() if t.finished), key=lambda t: t.total) + xs = [EPS] + [max(EPS, t.total) for t in fin] + score = np.concatenate([[0], np.cumsum([t.points for t in fin])]) + correct = np.concatenate([[0], np.cumsum([t.points > 0 for t in fin])]) + wrong = np.concatenate([[0], np.cumsum([t.points < 0 for t in fin])]) + axes[0].step(xs, score, where="post", color=r.color, linewidth=2, + label=f"{short_label(r)}: {score[-1]} pts") + axes[1].step(xs, correct, where="post", color=r.color, linewidth=2, + label=f"{short_label(r)}: {correct[-1]} correct") + axes[1].step(xs, wrong, where="post", color=r.color, linewidth=1.2, linestyle="--") + if len(fin): + axes[0].annotate(f"{score[-1]}", (xs[-1], score[-1]), xytext=(4, 0), + textcoords="offset points", va="center", fontsize=8, color=TEXT) + lo = min(min(r.finished_totals()) for r in runs) + hi = max(max(r.finished_totals()) for r in runs) + for ax in axes: + time_axis(ax, lo, hi * 1.04, "simulated timeout [s]") # room for the end-of-line score + style_axes(ax) + axes[0].set_title("SV-COMP score at simulated timeout") + axes[0].set_ylabel("points") + axes[0].legend(loc="lower right", fontsize=8) + axes[1].set_title("Correct (solid) and wrong (dashed) verdicts") + axes[1].set_ylabel("tasks") + handles, labels = axes[1].get_legend_handles_labels() + handles += [Line2D([], [], color=MUTED, linewidth=1.2, linestyle="--")] + labels += ["wrong verdicts (points < 0)"] + axes[1].legend(handles, labels, loc="center right", fontsize=8) + save(fig, out) + + +def plot_stage_cactus(runs, stages, out): + n = len(stages) + cols = 3 + rows = math.ceil(n / cols) + fig, axes = plt.subplots(rows, cols, figsize=(15, 4.3 * rows), sharey=True, squeeze=False) + for ax, s in zip(axes.flat, stages): + cactus(ax, runs, lambda t, s=s: t.stage(s), STAGE_NAMES[s], "time in stage [s]", + timeout_lines=False) + ax.get_legend().remove() + for ax in list(axes.flat)[n:]: + ax.axis("off") + handles = [Line2D([], [], color=r.color, linewidth=2) for r in runs] + fig.legend(handles, [short_label(r) for r in runs], loc="upper right", ncol=len(runs), fontsize=9) + fig.suptitle("Per-stage cactus: tasks whose stage took <= t (finished tasks only)", + x=0.01, ha="left", fontweight="bold") + fig.tight_layout(rect=(0, 0, 1, 0.95)) + save(fig, out) + + +def pair_points(base: Run, other: Run, value): + """(x, y, change, name) for every task in both runs; unfinished tasks get value None.""" + pts = [] + for name, bt in base.tasks.items(): + ot = other.tasks.get(name) + if ot is None: + continue + pts.append((value(bt), value(ot), change(bt, ot), name)) + return pts + + +def scatter_pair(ax, base, other, value, label, unit="s", to_band=True, annotate=6, below="faster"): + pts = pair_points(base, other, value) + fin = [(x, y) for x, y, _, _ in pts if x is not None and y is not None] + if not fin: + ax.text(0.5, 0.5, "no data", transform=ax.transAxes, ha="center") + return + lo = max(EPS, min(min(max(EPS, x), max(EPS, y)) for x, y in fin)) / 1.5 + hi = max(max(x, y) for x, y in fin) + to_pos = hi * 2.5 # tasks that did not finish sit in a band beyond every finished value + for key, (color, marker, text) in CHANGE_STYLE.items(): + sel = [(x, y) for x, y, c, _ in pts if c == key and (x is not None or y is not None)] + if not sel: + continue + xs = [max(EPS, x) if x is not None else to_pos for x, _ in sel] + ys = [max(EPS, y) if y is not None else to_pos for _, y in sel] + ax.scatter(xs, ys, s=22 if key == "same" else 38, c=color, marker=marker, + alpha=0.55 if key == "same" else 0.9, edgecolors="white", linewidths=0.6, + label=f"{text} ({len(sel)})", zorder=3 if key != "same" else 2) + lim = (lo, to_pos * 1.6 if to_band else hi * 1.5) + xs_line = np.array(lim) + ax.plot(xs_line, xs_line, color=MUTED, linewidth=1) + for f, txt in ((2, "2ร— slower"), (0.5, "2ร— faster"), (10, "10ร— slower"), (0.1, "10ร— faster")): + ax.plot(xs_line, xs_line * f, color=MUTED, linewidth=0.7, linestyle=":") + ax.set_xscale("log") + ax.set_yscale("log") + ax.set_xlim(lim) + ax.set_ylim(lim) + if to_band: + for axis_line in (ax.axvline, ax.axhline): + axis_line(to_pos, color=GRID, linewidth=10, zorder=1) + ax.text(to_pos, lo * 1.1, "not\nfinished", ha="center", va="bottom", fontsize=7, color=MUTED) + ax.text(lo * 1.1, to_pos * 1.15, "not finished", ha="left", va="bottom", fontsize=7, color=MUTED) + # Name the tasks with the largest change among those that matter (> 10 units in either run). + cand = [(abs(math.log(max(EPS, y) / max(EPS, x))), x, y, n) for x, y, _, n in pts + if x is not None and y is not None and max(x, y) > 10] + for _, x, y, n in sorted(cand, reverse=True)[:annotate]: + ax.annotate(n.split("/")[-1], (x, y), xytext=(5, -3), textcoords="offset points", + fontsize=7, color=TEXT) + ax.set_xlabel(f"{short_label(base)} [{unit}] (log)") + ax.set_ylabel(f"{short_label(other)} [{unit}] (log)") + ax.set_title(f"{label}\n(below the diagonal: {below} than baseline; dotted: 2ร— and 10ร—)", + fontsize=10) + style_axes(ax) + ax.legend(loc="lower right", fontsize=8) + + +def plot_scatter(runs, out): + base, others = runs[0], runs[1:] + fig, axes = plt.subplots(1, len(others), figsize=(7.5 * len(others), 7), squeeze=False) + for ax, other in zip(axes.flat, others): + scatter_pair(ax, base, other, lambda t: t.total, "Per-task total_time vs baseline") + save(fig, out) + + +def plot_speedup(runs, out): + base, others = runs[0], runs[1:] + fig, axes = plt.subplots(len(others), 2, figsize=(15, 5.5 * len(others)), squeeze=False) + for row, other in zip(axes, others): + names = common_finished([base, other]) + ratios = np.array([max(EPS, other.tasks[n].total) / max(EPS, base.tasks[n].total) for n in names]) + bt = np.array([base.tasks[n].total for n in names]) + order = np.argsort(ratios) + ax = row[0] + ax.plot(np.arange(len(ratios)), ratios[order], color=other.color, linewidth=2) + ax.axhline(1, color=MUTED, linewidth=1) + ax.set_yscale("log") + faster = int((ratios < 1 / 1.1).sum()) + slower = int((ratios > 1.1).sum()) + gmean = float(np.exp(np.log(ratios).mean())) + ax.fill_between(np.arange(len(ratios)), ratios[order], 1, where=ratios[order] < 1, + color=other.color, alpha=0.12, step=None) + ax.fill_between(np.arange(len(ratios)), ratios[order], 1, where=ratios[order] > 1, + color=MUTED, alpha=0.12) + ax.text(0.02, 0.95, f"{faster} tasks >10% faster\n{slower} tasks >10% slower\n" + f"{len(ratios) - faster - slower} within ยฑ10%\ngeometric mean ratio {gmean:.2f}", + transform=ax.transAxes, va="top", fontsize=9) + ax.set_title(f"Speedup profile: {short_label(other)}\ndivided by baseline {short_label(base)}", + fontsize=10) + ax.set_xlabel("tasks finished in both runs, sorted by ratio") + ax.set_ylabel("total_time ratio (log, <1 = faster)") + style_axes(ax) + + ax = row[1] + cs = [CHANGE_STYLE[change(base.tasks[n], other.tasks[n])] for n in names] + for key, (color, marker, text) in CHANGE_STYLE.items(): + sel = [i for i, n in enumerate(names) if change(base.tasks[n], other.tasks[n]) == key] + if sel: + ax.scatter(np.maximum(EPS, bt[sel]), ratios[sel], s=20, c=color, marker=marker, + alpha=0.6 if key == "same" else 0.9, edgecolors="white", linewidths=0.5, + label=f"{text} ({len(sel)})") + del cs + # Running geometric mean over baseline-time buckets shows where the change pays off. + edges = np.logspace(np.log10(max(EPS, bt.min())), np.log10(bt.max()), 16) + mids, gms = [], [] + for a, b in zip(edges[:-1], edges[1:]): + sel = (bt >= a) & (bt < b) + if sel.sum() >= 3: + mids.append(math.sqrt(a * b)) + gms.append(float(np.exp(np.log(ratios[sel]).mean()))) + ax.plot(mids, gms, color=TEXT, linewidth=2, label="geometric mean per time bucket") + ax.axhline(1, color=MUTED, linewidth=1) + ax.set_xscale("log") + ax.set_yscale("log") + ax.set_title("Ratio vs baseline time:\nfor which task sizes does it pay off?", fontsize=10) + ax.set_xlabel(f"{short_label(base)} total_time [s] (log)") + ax.set_ylabel("total_time ratio (log, <1 = faster)") + style_axes(ax) + ax.legend(loc="upper right", fontsize=8) + save(fig, out) + + +BUCKETS = [("all tasks finished in every run", 0, math.inf), ("baseline < 10 s", 0, 10), + ("baseline 10โ€“100 s", 10, 100), ("baseline >= 100 s", 100, math.inf)] + + +def plot_stage_totals(runs, stages, out): + names = common_finished(runs) + base = runs[0] + fig, axes = plt.subplots(len(BUCKETS), 1, figsize=(13, 1.1 + 1.15 * len(runs) * len(BUCKETS)), + squeeze=False) + for ax, (title, lo, hi) in zip(axes.flat, BUCKETS): + sel = [n for n in names if lo <= base.tasks[n].total < hi] + ys = np.arange(len(runs))[::-1] + grand = [] + for y, r in zip(ys, runs): + left = 0.0 + for s in stages: + v = sum(r.tasks[n].stage(s) for n in sel) + ax.barh(y, v, left=left, color=STAGE_COLORS[s], height=0.62, edgecolor="white", + linewidth=2, hatch="//" if s == "untracked" else None) + left += v + grand.append(left) + xmax = max(grand) if grand and max(grand) > 0 else 1 + for y, r, g in zip(ys, runs, grand): + left = 0.0 + for s in stages: + v = sum(r.tasks[n].stage(s) for n in sel) + if v / xmax > 0.07: + ax.text(left + v / 2, y, fmt_s(v), ha="center", va="center", fontsize=8, color=TEXT) + left += v + ax.text(g + xmax * 0.01, y, fmt_s(g), va="center", fontsize=9, fontweight="bold") + ax.set_yticks(ys, [short_label(r) for r in runs]) + ax.set_xlim(0, xmax * 1.12) + ax.set_title(f"{title} ({len(sel)} tasks)") + style_axes(ax, grid_axis="x") + ax.set_xlabel("summed time [s]") + handles = [Patch(facecolor=STAGE_COLORS[s], hatch="//" if s == "untracked" else None, + edgecolor="white") for s in stages] + fig.legend(handles, [STAGE_NAMES[s] for s in stages], loc="upper center", + ncol=len(stages), fontsize=9, bbox_to_anchor=(0.5, 1.0)) + fig.suptitle("Where the time goes: summed stage time, tasks finished in every run, bucketed by " + "baseline total_time", x=0.01, y=1.04, ha="left", fontweight="bold") + fig.tight_layout(rect=(0, 0, 1, 0.97)) + save(fig, out) + + +def plot_suite_heatmap(runs, stages, out): + base, others = runs[0], runs[1:] + suites = sorted({t.suite for t in base.tasks.values()}) + cols = stages + ["total_time"] + fig, axes = plt.subplots(1, len(others), figsize=(12 * len(others), 0.45 * len(suites) + 2.5), + squeeze=False) + cmap = LinearSegmentedColormap.from_list("div", ["#1c5cab", "#86b6ef", "#f2f1ec", "#f0a088", "#b8321f"]) + for ax, other in zip(axes.flat, others): + names = common_finished([base, other]) + data = np.zeros((len(suites), len(cols))) + counts = [] + for i, su in enumerate(suites): + sel = [n for n in names if base.tasks[n].suite == su] + for j, c in enumerate(cols): + if c == "total_time": + data[i, j] = sum(other.tasks[n].total - base.tasks[n].total for n in sel) + else: + data[i, j] = sum(other.tasks[n].stage(c) - base.tasks[n].stage(c) for n in sel) + to_b = sum(1 for t in base.tasks.values() if t.suite == su and not t.finished) + to_o = sum(1 for t in other.tasks.values() if t.suite == su and not t.finished) + counts.append(f"{su} ({len(sel)} tasks, not finished {to_b}โ†’{to_o})") + vmax = max(1.0, np.abs(data).max()) + im = ax.imshow(data, cmap=cmap, norm=SymLogNorm(linthresh=1, vmin=-vmax, vmax=vmax), aspect="auto") + for i in range(len(suites)): + for j in range(len(cols)): + v = data[i, j] + ax.text(j, i, f"{v:+,.0f}" if abs(v) >= 1 else f"{v:+.2f}", ha="center", va="center", + fontsize=8, color="white" if abs(v) > vmax / 8 and abs(v) > 30 else TEXT) + ax.set_xticks(range(len(cols)), [STAGE_NAMES.get(c, "total") for c in cols], rotation=20, ha="right") + ax.set_yticks(range(len(suites)), counts) + for side in ax.spines.values(): + side.set_visible(False) + ax.set_title(f"Summed time difference [s], {short_label(other)} โˆ’ {short_label(base)}\n" + "blue = faster than baseline, red = slower; tasks finished in both runs") + fig.colorbar(im, ax=ax, shrink=0.7, label="ฮ” seconds (symlog)") + save(fig, out) + + +def plot_suite_dots(runs, out): + names = common_finished(runs) + suites = sorted({runs[0].tasks[n].suite for n in names}, + key=lambda su: sum(runs[0].tasks[n].total for n in names if runs[0].tasks[n].suite == su)) + fig, axes = plt.subplots(1, 2, figsize=(14, 0.4 * len(suites) + 2), sharey=True) + ys = np.arange(len(suites)) + for k, (ax, agg, title) in enumerate(zip( + axes, (sum, statistics.median), ("Summed total_time per suite", "Median total_time per task"))): + for i, su in enumerate(suites): + vals = [agg([r.tasks[n].total for n in names if r.tasks[n].suite == su]) for r in runs] + ax.plot([min(vals), max(vals)], [i, i], color=GRID, linewidth=3, zorder=1) + for r in runs: + vals = [agg([r.tasks[n].total for n in names if r.tasks[n].suite == su]) for su in suites] + ax.scatter(np.maximum(EPS, vals), ys, s=60, color=r.color, edgecolors="white", linewidths=1.5, + zorder=3, label=short_label(r)) + ax.set_xscale("log") + ax.set_title(title) + ax.set_xlabel("seconds (log)") + style_axes(ax, grid_axis="x") + axes[0].set_yticks(ys, [f"{su} ({sum(1 for n in names if runs[0].tasks[n].suite == su)})" for su in suites]) + fig.legend(*axes[0].get_legend_handles_labels(), loc="lower center", ncol=len(runs), fontsize=9, + bbox_to_anchor=(0.5, -0.04)) + fig.suptitle("Per suite, tasks finished in every run", x=0.01, ha="left", fontweight="bold") + save(fig, out) + + +def plot_stage_share(runs, stages, out): + fig, axes = plt.subplots(2 * len(runs), 1, figsize=(14, 4.2 * len(runs)), squeeze=False, + gridspec_kw={"height_ratios": [1, 2.2] * len(runs)}) + for k, r in enumerate(runs): + fin = sorted((t for t in r.tasks.values() if t.finished), key=lambda t: t.total) + x = np.arange(len(fin)) + top, ax = axes[2 * k, 0], axes[2 * k + 1, 0] + top.plot(x, [max(EPS, t.total) for t in fin], color=r.color, linewidth=2) + top.set_yscale("log") + top.set_ylabel("total [s]") + top.set_title(f"{short_label(r)}: stage share per task, tasks sorted by total_time") + style_axes(top) + top.set_xticklabels([]) + shares = [] + for s in stages: + shares.append([t.stage(s) / max(EPS, t.total + t.untracked) for t in fin]) + ax.stackplot(x, shares, colors=[STAGE_COLORS[s] for s in stages], linewidth=0) + ax.set_ylim(0, 1) + ax.set_xlim(0, len(fin) - 1) + top.set_xlim(0, len(fin) - 1) + ax.set_ylabel("share of wall time") + ax.set_xlabel("task rank (by total_time)") + style_axes(ax) + # Mark where the total crosses round numbers so rank maps back to seconds. + for sec in (1, 10, 100, 1000): + idx = np.searchsorted([t.total for t in fin], sec) + if 0 < idx < len(fin): + for a in (top, ax): + a.axvline(idx, color="white" if a is ax else GRID, linewidth=1, linestyle="--") + ax.text(idx, 1.01, f"{sec} s", ha="center", va="bottom", fontsize=7, color=MUTED, + transform=ax.get_xaxis_transform()) + handles = [Patch(facecolor=STAGE_COLORS[s]) for s in stages] + fig.legend(handles, [STAGE_NAMES[s] for s in stages], loc="upper center", ncol=len(stages), fontsize=9) + fig.tight_layout(rect=(0, 0, 1, 0.97)) + save(fig, out) + + +def plot_iterations(runs, out): + base, others = runs[0], runs[1:] + if not any(t.iterations for t in base.tasks.values()): + return False + fig, axes = plt.subplots(len(others), 3, figsize=(19, 6.3 * len(others)), squeeze=False) + for row, other in zip(axes, others): + scatter_pair(row[0], base, other, + lambda t: t.iterations if t.finished and t.iterations is not None else None, + "Symbolic-execution iterations", unit="iterations", annotate=5, below="fewer") + scatter_pair(row[1], base, other, + lambda t: t.solver_calls + 1 if t.finished and t.solver_calls is not None else None, + "Solver calls + 1", unit="calls + 1", annotate=5, below="fewer") + scatter_pair(row[2], base, other, + lambda t: t.stage("symbolic_executor") / t.iterations + if t.finished and t.iterations else None, + "Executor time per iteration", unit="s/iteration", annotate=5, below="cheaper") + save(fig, out) + return True + + +def plot_top_deltas(runs, stages, out, top=30): + base, others = runs[0], runs[1:] + fig, axes = plt.subplots(1, len(others), figsize=(12 * len(others), 0.32 * top + 2), squeeze=False) + for ax, other in zip(axes.flat, others): + names = common_finished([base, other]) + names = sorted(names, key=lambda n: abs(other.tasks[n].total + other.tasks[n].untracked + - base.tasks[n].total - base.tasks[n].untracked), + reverse=True)[:top][::-1] + ys = np.arange(len(names)) + for i, n in enumerate(names): + pos, neg = 0.0, 0.0 + for s in stages: + d = other.tasks[n].stage(s) - base.tasks[n].stage(s) + if d >= 0: + ax.barh(i, d, left=pos, color=STAGE_COLORS[s], height=0.7, edgecolor="white", + linewidth=1, hatch="//" if s == "untracked" else None) + pos += d + else: + ax.barh(i, d, left=neg, color=STAGE_COLORS[s], height=0.7, edgecolor="white", + linewidth=1, hatch="//" if s == "untracked" else None) + neg += d + net = pos + neg + ax.plot([net], [i], marker="D", color=TEXT, markersize=4, zorder=4) + b, o = base.tasks[n], other.tasks[n] + ax.text(1.01, i, f"{fmt_s(b.wall)} โ†’ {fmt_s(o.wall)}", transform=ax.get_yaxis_transform(), + va="center", fontsize=7, color=MUTED) + ax.axvline(0, color=MUTED, linewidth=1) + ax.set_xscale("symlog", linthresh=10) + ax.set_yticks(ys, names, fontsize=8) + ax.set_xlabel(f"ฮ” seconds per stage, {short_label(other)} โˆ’ baseline (symlog)") + ax.set_title(f"Top {len(names)} tasks by |ฮ” wall time| (โ—† = net change; right column: wall time)") + style_axes(ax, grid_axis="x") + handles = [Patch(facecolor=STAGE_COLORS[s], hatch="//" if s == "untracked" else None, + edgecolor="white") for s in stages] + ax.legend(handles, [STAGE_NAMES[s] for s in stages], loc="lower right", fontsize=8) + save(fig, out) + + +def sa_payoff_pairs(runs): + """(run with SA, run without SA) pairs of the baseline with another run.""" + base, others = runs[0], runs[1:] + pairs = [] + for other in others: + modes = {base.sa_mode, other.sa_mode} + if "none" in modes and modes & {"sequential", "parallel"}: + pairs.append((other, base) if base.sa_mode == "none" else (base, other)) + elif "unknown" in modes and modes <= {"unknown", "none", "sequential"}: + # No run_info.json or gitlog.txt: one run spending much more time in SA has it. + names = common_finished([base, other]) + sa_b = sum(base.tasks[n].stage("static_pre_analysis") for n in names) + sa_o = sum(other.tasks[n].stage("static_pre_analysis") for n in names) + if max(sa_b, sa_o) > 10 * max(min(sa_b, sa_o), 1e-9): + pairs.append((base, other) if sa_b > sa_o else (other, base)) + return pairs + + +def plot_sa_payoff(runs, out): + """For a pair where one run uses static pre-analysis and the other does not. + + With sequential SA, its cost is its (blocking) stage time. With parallel SA, it costs no + wall time of its own, so the x-axis is its background duration and the payoff is the + total_time saved.""" + pairs = sa_payoff_pairs(runs) + if not pairs: + return False + fig, axes = plt.subplots(1, len(pairs), figsize=(8 * len(pairs), 7), squeeze=False) + for ax, (sa, nosa) in zip(axes.flat, pairs): + names = common_finished([sa, nosa]) + parallel = sa.sa_mode == "parallel" + if parallel: + cost = np.array([sa.tasks[n].sa_wall or 0.0 for n in names]) + saved = np.array([nosa.tasks[n].total - sa.tasks[n].total for n in names]) + pays = saved > 0 + else: + cost = np.array([sa.tasks[n].stage("static_pre_analysis") for n in names]) + rest = lambda t: t.total - t.stage("static_pre_analysis") # noqa: E731 + saved = np.array([rest(nosa.tasks[n]) - rest(sa.tasks[n]) for n in names]) + pays = saved > cost + ax.scatter(np.maximum(EPS, cost[~pays]), saved[~pays], s=18, c=CHANGE_STYLE["same"][0], alpha=0.5, + edgecolors="white", linewidths=0.5, label=f"does not pay off ({(~pays).sum()})") + ax.scatter(np.maximum(EPS, cost[pays]), saved[pays], s=34, c=sa.color, marker="^", + edgecolors="white", linewidths=0.5, + label=f"pays off: {'total_time saved' if parallel else 'saved > cost'} ({pays.sum()})") + if not parallel: + xs = np.logspace(np.log10(max(EPS, cost.min())), np.log10(max(EPS, cost.max()) * 1.5), 50) + ax.plot(xs, xs, color=MUTED, linewidth=1) + ax.text(xs[-1], xs[-1], " saved = cost", fontsize=8, color=MUTED, va="bottom", ha="right") + ax.axhline(0, color=MUTED, linewidth=0.8, linestyle=":") + ax.set_xscale("log") + ax.set_yscale("symlog", linthresh=1) + order = np.argsort(-np.abs(saved))[:6] + for i in order: + ax.annotate(names[i].split("/")[-1], (max(EPS, cost[i]), saved[i]), xytext=(5, 0), + textcoords="offset points", fontsize=7) + if parallel: + box = (f"pre-analysis in the background: {fmt_s(cost.sum())}\n" + f"total_time saved (net): {fmt_s(float(saved.sum()))}") + else: + box = (f"pre-analysis cost: {fmt_s(cost.sum())}\n" + f"saved in other stages: {fmt_s(saved.sum())}\nnet: {fmt_s(float(saved.sum() - cost.sum()))}") + ax.text(0.98, 0.97, box, + transform=ax.transAxes, va="top", ha="right", fontsize=9, + bbox=dict(facecolor="white", edgecolor=GRID)) + nf_sa = sum(1 for t in sa.tasks.values() if not t.finished) + nf_no = sum(1 for t in nosa.tasks.values() if not t.finished) + ax.set_title(f"Does {sa.sa_mode} static pre-analysis pay off per task?\n{short_label(sa)} vs " + f"{short_label(nosa)} (unfinished: {nf_sa} vs {nf_no})") + if parallel: + ax.set_xlabel("static pre-analysis time in the background [s] (log)") + ax.set_ylabel("total_time saved [s] (symlog)") + else: + ax.set_xlabel("static pre-analysis time [s] (log)") + ax.set_ylabel("time saved in all other stages [s] (symlog)") + style_axes(ax) + ax.legend(loc="lower right", fontsize=8) + save(fig, out) + return True + + +SA_STATUS_STYLE = {"loaded": ("#1baf7a", "o", "graph used"), + "cancelled": ("#eda100", "s", "exploration finished first"), + "failed": ("#d03b3b", "x", "failed"), + "timeout": ("#d03b3b", "v", "extractor timed out")} + + +def plot_sa_background(runs, out): + """Parallel runs only: when did the background pre-analysis finish, relative to exploration?""" + par = [r for r in runs if r.sa_mode == "parallel"] + if not par: + return False + fig, axes = plt.subplots(1, len(par), figsize=(8 * len(par), 6.5), squeeze=False) + for ax, r in zip(axes.flat, par): + fin = [t for t in r.tasks.values() if t.finished and t.sa_wall is not None] + if not fin: + ax.text(0.5, 0.5, "no data", transform=ax.transAxes, ha="center") + continue + for status, (color, marker, text) in SA_STATUS_STYLE.items(): + sel = [t for t in fin if t.sa_status == status] + if sel: + ax.scatter([max(EPS, t.total) for t in sel], [max(EPS, t.sa_wall) for t in sel], s=18, + c=color, marker=marker, alpha=0.7, linewidths=0.8 if marker == "x" else 0, + label=f"{text} ({len(sel)})") + rest = [t for t in fin if t.sa_status not in SA_STATUS_STYLE] + if rest: + ax.scatter([max(EPS, t.total) for t in rest], [max(EPS, t.sa_wall) for t in rest], s=14, + c=MUTED, alpha=0.5, linewidths=0, label=f"status unknown ({len(rest)})") + ax.set_xscale("log") + ax.set_yscale("log") + lo = min(min(ax.get_xlim()), min(ax.get_ylim())) + hi = max(max(ax.get_xlim()), max(ax.get_ylim())) + ax.plot([lo, hi], [lo, hi], color=MUTED, linewidth=0.8, linestyle=":") + ax.text(hi, hi, " SA = total_time", fontsize=8, color=MUTED, va="bottom", ha="right") + rounds = [t.sa_adopted_round for t in fin if t.sa_adopted_round is not None] + med = f", graph first used in round {statistics.median(rounds):.0f} (median)" if rounds else "" + ax.set_title(f"{short_label(r)}: background pre-analysis vs total_time{med}") + ax.set_xlabel("explorer total_time [s] (log)") + ax.set_ylabel("static pre-analysis duration [s] (log)") + style_axes(ax) + ax.legend(loc="upper left", fontsize=8) + save(fig, out) + return True + + +def plot_overhead(runs, out): + """Wall time not covered by the explorer's own timer, per task.""" + fig, axes = plt.subplots(1, 2, figsize=(14, 5.5)) + for r in runs: + fin = [t for t in r.tasks.values() if t.finished] + axes[0].scatter([max(EPS, t.total) for t in fin], [max(EPS, t.untracked) for t in fin], s=14, + color=r.color, alpha=0.6, edgecolors="none", label=short_label(r)) + cactus(axes[1], runs, lambda t: t.untracked, "Cactus of time outside the explorer timer", + "wall time โˆ’ total_time [s]", timeout_lines=False) + ax = axes[0] + ax.set_xscale("log") + ax.set_yscale("log") + ax.set_xlabel("explorer total_time [s] (log)") + ax.set_ylabel("wall time โˆ’ total_time [s] (log)") + ax.set_title("Time outside the explorer's timer vs total_time") + lo, hi = ax.get_xlim() + ax.plot([lo, hi], [lo, hi], color=MUTED, linewidth=0.8, linestyle=":") + style_axes(ax) + ax.legend(loc="upper left", fontsize=8) + save(fig, out) + + +def save(fig, out): + fig.savefig(out) + plt.close(fig) + + +# ----------------------------------------------------------------------------- summary + +def summary_md(runs, stages): + base = runs[0] + lines = ["# Run comparison", "", "| run | SA mode | timeout | finished | score | correct | wrong | " + "median total | mean total | summed total | summed wall |", + "|---|---|---|---|---|---|---|---|---|---|---|"] + for r in runs: + fin = [t for t in r.tasks.values() if t.finished] + tot = [t.total for t in fin] + lines.append( + f"| {r.label} | {r.sa_mode} | {r.timeout or '?'} s | {len(fin)}/{len(r.tasks)} | " + f"{sum(t.points for t in r.tasks.values())} | {sum(t.points > 0 for t in r.tasks.values())} | " + f"{sum(t.points < 0 for t in r.tasks.values())} | {statistics.median(tot):.2f} s | " + f"{statistics.mean(tot):.1f} s | {sum(tot):,.0f} s | {sum(t.wall for t in fin):,.0f} s |") + sa_runs = [r for r in runs if r.sa_mode in ("sequential", "parallel")] + if sa_runs: + statuses = sorted({t.sa_status for r in sa_runs for t in r.tasks.values() if t.sa_status}) + lines += ["", "Static pre-analysis per run (finished tasks). In parallel runs it overlaps the " + "other stages, so its static_pre_analysis stage is 0 and its duration is shown here:", "", + "| run | SA mode | summed duration | median duration | " + + "".join(f"{s} | " for s in statuses), + "|---|---|---|---|" + "---|" * len(statuses)] + for r in sa_runs: + fin = [t for t in r.tasks.values() if t.finished and t.sa_wall is not None] + walls = [t.sa_wall for t in fin] + med = f"{statistics.median(walls):.2f} s" if walls else "โ€“" + lines.append(f"| {r.label} | {r.sa_mode} | {sum(walls):,.1f} s | {med} | " + + "".join(f"{sum(t.sa_status == s for t in fin)} | " for s in statuses)) + names = common_finished(runs) + lines += ["", f"Stage sums over the {len(names)} tasks finished in every run:", "", + "| run | " + " | ".join(STAGE_NAMES[s] for s in stages) + " |", + "|---|" + "---|" * len(stages)] + for r in runs: + lines.append(f"| {r.label} | " + " | ".join( + f"{sum(r.tasks[n].stage(s) for n in names):,.1f} s" for s in stages) + " |") + for other in runs[1:]: + lines += ["", f"## {other.label} vs {base.label}", ""] + only_b = [n for n, t in base.tasks.items() if t.finished and n in other.tasks and not other.tasks[n].finished] + only_o = [n for n, t in other.tasks.items() if t.finished and n in base.tasks and not base.tasks[n].finished] + lines.append(f"- Finished only in baseline ({len(only_b)}): " + ", ".join( + f"`{n}` ({fmt_s(base.tasks[n].total)})" for n in sorted(only_b))) + lines.append(f"- Finished only in {other.label} ({len(only_o)}): " + ", ".join( + f"`{n}` ({fmt_s(other.tasks[n].total)})" for n in sorted(only_o))) + changed = [n for n in base.tasks if n in other.tasks and base.tasks[n].case != other.tasks[n].case] + lines += ["", "| task | baseline | time | other | time |", "|---|---|---|---|---|"] + for n in sorted(changed): + b, o = base.tasks[n], other.tasks[n] + ts = lambda t: fmt_s(t.total) if t.finished else t.status # noqa: E731 + lines.append(f"| `{n}` | {b.case} ({b.points:+d}) | {ts(b)} | {o.case} ({o.points:+d}) | {ts(o)} |") + if not changed: + lines.append("| (no verdict changes) | | | | |") + return "\n".join(lines) + "\n" + + +CAPTIONS = { + "01b_cactus_free_sa.svg": "Same cactus with each task's static pre-analysis time subtracted: what the " + "runs would look like if the pre-analysis were free. Dashed: the measured curves. " + "Parallel SA runs have no pre-analysis stage, so both curves coincide.", + "01_cactus.svg": "Cumulative finished tasks over a simulated timeout, for the explorer's total_time " + "and for the harness wall time.", + "02_score.svg": "SV-COMP score if the timeout were t: a task slower than t counts as unknown (0 points).", + "03_stage_cactus.svg": "One cactus per stage. Shows which stage makes the difference and at which scale.", + "04_scatter.svg": "Every task, baseline time vs other run time (log-log). Dotted lines: 2x and 10x. " + "Tasks that did not finish sit in the grey band; markers show verdict changes.", + "05_speedup.svg": "Left: sorted per-task ratio. Right: ratio against baseline time with a bucketed " + "geometric mean, showing for which task sizes the change helps.", + "06_stage_totals.svg": "Stacked summed stage time on the tasks finished in every run, split by " + "baseline task size.", + "07_suite_heatmap.svg": "Summed per-stage time difference per suite (symlog color).", + "08_suite_dots.svg": "Sum and median of total_time per suite for each run.", + "09_stage_share.svg": "Stage share of each task's wall time, tasks sorted by total_time.", + "10_iterations.svg": "Iterations, solver calls and executor time per iteration, per task (from stats.json).", + "11_top_deltas.svg": "The tasks with the largest wall-time change, broken down by stage.", + "12_sa_payoff.svg": "Per task: static pre-analysis cost vs the time it saved in the other stages. For a " + "parallel SA run: its background duration vs the total_time it saved.", + "14_sa_background.svg": "Parallel SA runs: per task, the background pre-analysis duration against the " + "explorer's total_time. Below the diagonal the graph was ready before exploration " + "ended; 'exploration finished first' tasks were cancelled.", + "13_overhead.svg": "Wall time the explorer's timer does not cover (process start, stats.json, shutdown).", +} + + +def write_html(outdir: Path, images, summary): + parts = ["Run comparison", + ""] + parts.append(md_to_html(summary)) + for img in images: + data = base64.b64encode((outdir / img).read_bytes()).decode() + parts.append(f"

{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/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 26ce238b..7bd3f5fa 100644 --- a/targets/sv-comp/scripts/lib/analysis/context_loss.py +++ b/targets/sv-comp/scripts/lib/analysis/context_loss.py @@ -8,11 +8,13 @@ context-loss subset, execution errors This reads that structured data directly โ€” no log scraping โ€” and prints a scoring -summary (Correct / Failed / Unk / Error / Timeout) plus the missing-invocation -superset and its authoritative context-loss subset (issue #25). +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 re import sys from collections import defaultdict from pathlib import Path @@ -23,6 +25,14 @@ SCORE_BUCKETS = ['Correct', 'Failed', 'Unk', 'Error', 'Timeout'] +# "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.""" @@ -73,6 +83,17 @@ def signature(inv: dict) -> str: return f"{inv['owner']}/{inv['name']}:{inv['desc']}" +def error_signature(message: str) -> str: + """Reduce an error log line to a template that groups identical failures. + + 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 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) @@ -95,7 +116,15 @@ def analyze_run(run_dir: Path): # Missing-invocation aggregation across all testcases of the run. # signature -> {tasks: set, count: int, context_loss: bool, isSymbolic: bool} missing: dict = {} - exec_errors: dict = defaultdict(list) # message -> [tasks] + # 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 @@ -128,13 +157,27 @@ def analyze_run(run_dir: Path): 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', []): - exec_errors[e.get('message', '')].append(target) + 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: @@ -196,15 +239,38 @@ def _print_missing_invocations(missing: dict): 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) - print(f"EXECUTION ERRORS ({len(exec_errors)} distinct)") + 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 msg, tasks in sorted(exec_errors.items(), key=lambda kv: -len(kv[1])): - unique = list(dict.fromkeys(tasks)) - print(f" {len(unique):>4} tasks {msg[:90]}") + 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() 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 9bf39f79..6e135ff3 100644 --- a/targets/sv-comp/scripts/lib/command_gen.py +++ b/targets/sv-comp/scripts/lib/command_gen.py @@ -1,11 +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 typing import Optional -from .dtypes import Command, VerificationTask +from .dtypes import Command, VerificationCategory, VerificationTask logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) @@ -34,13 +35,17 @@ 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, sa_retry_without: bool = False, parallel_sa: bool = False, sa_rank_distance: bool = False, sa_no_callback_targets: bool = False, solver_rlimit: int = 0, cpu_limit_s: Optional[int] = None) -> 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 pre-analysis only targets asserts, so it can only prune for valid-assert (the explorer + # enforces this too). + no_sa = no_sa or ver_task['category'] != VerificationCategory.VALID_ASSERT_PRP base_command: list[str] = [str(PYENV_PATH), "-u", str(BASE_PATH / 'symbolic-explorer' / 'SymbolicExplorer.py'), "-prp", ver_task['category'].value, @@ -49,14 +54,31 @@ 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)]) + \ + (["--sa-retry-without"] if sa_retry_without and not no_sa else []) + \ + (["--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 []) + \ + (["--solver-rlimit", str(solver_rlimit)] if solver_rlimit else []) + \ + (["--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 @@ -71,7 +93,19 @@ def run_dir(run_timestamp: str) -> Path: return SCRIPT_DIR / '..' / 'runs' / f"run_{run_timestamp}" -def generate_commands(ver_tasks: list[VerificationTask], config_file: str = 'swat.cfg', run_timestamp: Optional[str] = None) -> list[VerificationTask]: +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, sa_retry_without: bool = False, parallel_sa: bool = False, sa_rank_distance: bool = False, sa_no_callback_targets: bool = False, solver_rlimit: int = 0, cpu_limit_s: Optional[int] = None) -> list[VerificationTask]: port = 9000 skipped_ports = [] @@ -96,9 +130,8 @@ 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 @@ -117,7 +150,7 @@ def generate_commands(ver_tasks: list[VerificationTask], config_file: str = 'swa '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, sa_retry_without=sa_retry_without, parallel_sa=parallel_sa, sa_rank_distance=sa_rank_distance, sa_no_callback_targets=sa_no_callback_targets, solver_rlimit=solver_rlimit, cpu_limit_s=cpu_limit_s) } 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 db5c354e..60729976 100644 --- a/targets/sv-comp/scripts/lib/execution.py +++ b/targets/sv-comp/scripts/lib/execution.py @@ -9,7 +9,8 @@ from .dtypes import Verdict, ExpectedVerdict, VerificationCategory, VerificationTask, Command from .utils import ci_print from .command_gen import extract_testcases, generate_commands, new_run_timestamp, run_dir as make_run_dir -from .witness import generate_and_validate_witness +from .witness import validate_witness +from .analysis.timing import stage_label from collections import Counter from pathlib import Path @@ -160,7 +161,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 +184,9 @@ 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) + if print_output: + log_output(output) error: bool = check_for_dse_error(output) points, case = determine_result(output, ver_task['category'], ver_task['verdict']) @@ -194,9 +196,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 +211,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 @@ -283,7 +287,7 @@ def run_command_with_timeout(cmd: list[str], timeout: int = 900) -> tuple[Execut -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): +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() @@ -306,7 +310,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 @@ -352,6 +356,7 @@ def evaluate_results(results, results_dir: Path, run_timestamp: str): 'total': 0 }, 'stage_timing': { + 'static_pre_analysis': 0.0, 'symbolic_executor': 0.0, 'smt_solver': 0.0, 'symbolic_explorer': 0.0, @@ -440,6 +445,7 @@ def evaluate_results(results, results_dir: Path, run_timestamp: str): 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}%)") @@ -453,6 +459,7 @@ def evaluate_results(results, results_dir: Path, run_timestamp: str): # 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, @@ -469,6 +476,7 @@ def evaluate_results(results, results_dir: Path, run_timestamp: str): 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}%)") @@ -600,7 +608,7 @@ def generate_timing_histogram(category_stats, results, results_dir: Path, run_ti 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) @@ -646,7 +654,7 @@ def save_results(category_stats, results, results_dir: Path, run_timestamp: str) 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'], @@ -677,6 +685,7 @@ def save_aggregate_stage_timing(category_stats, results_dir: Path, run_timestamp # 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, @@ -698,8 +707,7 @@ def save_aggregate_stage_timing(category_stats, results_dir: Path, run_timestamp # 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") @@ -806,11 +814,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) 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..e1b538de 100644 --- a/targets/sv-comp/scripts/lib/witness.py +++ b/targets/sv-comp/scripts/lib/witness.py @@ -1,19 +1,33 @@ -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__) 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 +35,63 @@ 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]): 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.""" + + +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 +112,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 +123,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 +137,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 +189,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 +206,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/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/sv-comp.cfg b/targets/sv-comp/sv-comp.cfg index d66a6727..533c3a8d 100644 --- a/targets/sv-comp/sv-comp.cfg +++ b/targets/sv-comp/sv-comp.cfg @@ -29,3 +29,6 @@ constraintsOnly=false solver.mode=HTTP svcomp.randomInputs=false + +# Report loop-head states with the trace; the explorer prunes below repeated ones +loopStateMatching=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..1089ad24 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/README.md @@ -0,0 +1,86 @@ +# 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. + +`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..ea980039 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/RESULTS.md @@ -0,0 +1,520 @@ +# 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. 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/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/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..ab0d88be --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/scripts/check_labels.py @@ -0,0 +1,62 @@ +#!/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') + exits = 'System.exit' in src # the gauntlet's exit variant ends the program on rejection + 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..85367e77 --- /dev/null +++ b/targets/sv-comp/swat-benchmarks/scripts/gen.py @@ -0,0 +1,36 @@ +#!/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 + +JAVA_DIR = Path(__file__).resolve().parent.parent / 'java' + + +def main(suites): + tasks = [*all_family_tasks(), *all_soundness_tasks(), *all_tool_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/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++; + } +}